PageRenderTime 533ms CodeModel.GetById 257ms app.highlight 259ms RepoModel.GetById 1ms app.codeStats 0ms

/UKSyntaxColoredTextViewController.m

http://github.com/bububa/MongoHub-Mac
Objective C | 1355 lines | 840 code | 244 blank | 271 comment | 207 complexity | 84f575989b941de20e5b3da798c28198 MD5 | raw file
   1//
   2//  UKSyntaxColoredTextViewController.m
   3//  UKSyntaxColoredDocument
   4//
   5//  Created by Uli Kusterer on 13.03.10.
   6//  Copyright 2010 Uli Kusterer.
   7//
   8//	This software is provided 'as-is', without any express or implied
   9//	warranty. In no event will the authors be held liable for any damages
  10//	arising from the use of this software.
  11//
  12//	Permission is granted to anyone to use this software for any purpose,
  13//	including commercial applications, and to alter it and redistribute it
  14//	freely, subject to the following restrictions:
  15//
  16//	   1. The origin of this software must not be misrepresented; you must not
  17//	   claim that you wrote the original software. If you use this software
  18//	   in a product, an acknowledgment in the product documentation would be
  19//	   appreciated but is not required.
  20//
  21//	   2. Altered source versions must be plainly marked as such, and must not be
  22//	   misrepresented as being the original software.
  23//
  24//	   3. This notice may not be removed or altered from any source
  25//	   distribution.
  26//
  27
  28// -----------------------------------------------------------------------------
  29//	Headers:
  30// -----------------------------------------------------------------------------
  31
  32#import "UKSyntaxColoredTextViewController.h"
  33#import "NSArray+Color.h"
  34#import "NSScanner+SkipUpToCharset.h"
  35
  36
  37// -----------------------------------------------------------------------------
  38//	Globals:
  39// -----------------------------------------------------------------------------
  40
  41static BOOL			sSyntaxColoredTextDocPrefsInited = NO;
  42
  43
  44// -----------------------------------------------------------------------------
  45//	Macros:
  46// -----------------------------------------------------------------------------
  47
  48#define	TEXTVIEW		((NSTextView*)[self view])
  49
  50
  51@implementation UKSyntaxColoredTextViewController
  52
  53+(void) makeSurePrefsAreInited
  54{
  55	if( !sSyntaxColoredTextDocPrefsInited )
  56	{
  57		NSUserDefaults*	prefs = [NSUserDefaults standardUserDefaults];
  58		[prefs registerDefaults: [NSDictionary dictionaryWithContentsOfFile: [[NSBundle mainBundle] pathForResource: @"SyntaxColorDefaults" ofType: @"plist"]]];
  59        
  60		sSyntaxColoredTextDocPrefsInited = YES;
  61	}
  62}
  63
  64
  65/* -----------------------------------------------------------------------------
  66 init:
  67 Constructor that inits sourceCode member variable as a flag. It's
  68 storage for the text until the NIB's been loaded.
  69 -------------------------------------------------------------------------- */
  70
  71-(id)	initWithNibName: (NSString*)inNibName bundle: (NSBundle*)inBundle
  72{
  73    self = [super initWithNibName: inNibName bundle: inBundle];
  74    if( self )
  75	{
  76		autoSyntaxColoring = YES;
  77		maintainIndentation = YES;
  78		recolorTimer = nil;
  79		syntaxColoringBusy = NO;
  80	}
  81    return self;
  82}
  83
  84
  85-(void)	dealloc
  86{
  87	[[NSNotificationCenter defaultCenter] removeObserver: self];
  88	
  89	[recolorTimer invalidate];
  90	[recolorTimer release];
  91	recolorTimer = nil;
  92	
  93	[replacementString release];
  94	replacementString = nil;
  95	
  96	[super dealloc];
  97}
  98
  99
 100-(void)	setUpSyntaxColoring
 101{
 102	// Set up some sensible defaults for syntax coloring:
 103	[[self class] makeSurePrefsAreInited];
 104	
 105	// Register for "text changed" notifications of our text storage:
 106	[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(processEditing:)
 107                                                 name: NSTextStorageDidProcessEditingNotification
 108                                               object: [TEXTVIEW textStorage]];
 109	
 110	// Make sure text isn't wrapped:
 111	[self turnOffWrapping];
 112	
 113	// Do initial syntax coloring of our file:
 114	[self recolorCompleteFile: nil];
 115	
 116	// Put selection at top like Project Builder has it, so user sees it:
 117	[TEXTVIEW setSelectedRange: NSMakeRange(0,0)];
 118	
 119	[self textView: TEXTVIEW willChangeSelectionFromCharacterRange: NSMakeRange(0,0)
 120  toCharacterRange: NSMakeRange(0,0)];
 121	
 122	// Make sure we can use "find" if we're on 10.3:
 123	if( [TEXTVIEW respondsToSelector: @selector(setUsesFindPanel:)] )
 124		[TEXTVIEW setUsesFindPanel: YES];
 125}
 126
 127
 128-(void)		setDelegate: (id<UKSyntaxColoredTextViewDelegate>)inDelegate
 129{
 130	delegate = inDelegate;
 131}
 132
 133
 134-(id)	delegate
 135{
 136	return delegate;
 137}
 138
 139
 140/* -----------------------------------------------------------------------------
 141 setView:
 142 We've just been given a view! Apply initial syntax coloring.
 143 -------------------------------------------------------------------------- */
 144
 145-(void)	setView: (NSView*)theView
 146{
 147    [super setView: theView];
 148	
 149	[(NSTextView*)theView setDelegate: self];
 150	[self setUpSyntaxColoring];	// +++ If someone calls this twice, we should only call part of this twice!
 151}
 152
 153
 154/* -----------------------------------------------------------------------------
 155 processEditing:
 156 Part of the text was changed. Recolor it.
 157 -------------------------------------------------------------------------- */
 158
 159-(void) processEditing: (NSNotification*)notification
 160{
 161    NSTextStorage	*textStorage = [notification object];
 162	NSRange			range = [textStorage editedRange];
 163	int				changeInLen = [textStorage changeInLength];
 164	BOOL			wasInUndoRedo = [[self undoManager] isUndoing] || [[self undoManager] isRedoing];
 165	BOOL			textLengthMayHaveChanged = NO;
 166	
 167	// Was delete op or undo that could have changed text length?
 168	if( wasInUndoRedo )
 169	{
 170		textLengthMayHaveChanged = YES;
 171		range = [TEXTVIEW selectedRange];
 172	}
 173	if( changeInLen <= 0 )
 174		textLengthMayHaveChanged = YES;
 175	
 176	//	Try to get chars around this to recolor any identifier we're in:
 177	if( textLengthMayHaveChanged )
 178	{
 179		if( range.location > 0 )
 180			range.location--;
 181		if( (range.location +range.length +2) < [textStorage length] )
 182			range.length += 2;
 183		else if( (range.location +range.length +1) < [textStorage length] )
 184			range.length += 1;
 185	}
 186	
 187	NSRange						currRange = range;
 188    
 189	// Perform the syntax coloring:
 190	if( autoSyntaxColoring && range.length > 0 )
 191	{
 192		NSRange			effectiveRange;
 193		NSString*		rangeMode;
 194		
 195		
 196		rangeMode = [textStorage attribute: TD_SYNTAX_COLORING_MODE_ATTR
 197                                   atIndex: currRange.location
 198                            effectiveRange: &effectiveRange];
 199		
 200		unsigned int		x = range.location;
 201		
 202		/* TODO: If we're in a multi-line comment and we're typing a comment-end
 203         character, or we're in a string and we're typing a quote character,
 204         this should include the rest of the text up to the next comment/string
 205         end character in the recalc. */
 206		
 207		// Scan up to prev line break:
 208		while( x > 0 )
 209		{
 210			unichar theCh = [[textStorage string] characterAtIndex: x];
 211			if( theCh == '\n' || theCh == '\r' )
 212				break;
 213			--x;
 214		}
 215		
 216		currRange.location = x;
 217		
 218		// Scan up to next line break:
 219		x = range.location +range.length;
 220		
 221		while( x < [textStorage length] )
 222		{
 223			unichar theCh = [[textStorage string] characterAtIndex: x];
 224			if( theCh == '\n' || theCh == '\r' )
 225				break;
 226			++x;
 227		}
 228		
 229		currRange.length = x -currRange.location;
 230		
 231		// Open identifier, comment etc.? Make sure we include the whole range.
 232		if( rangeMode != nil )
 233			currRange = NSUnionRange( currRange, effectiveRange );
 234		
 235		// Actually recolor the changed part:
 236		[self recolorRange: currRange];
 237	}
 238}
 239
 240
 241/* -----------------------------------------------------------------------------
 242 textView:shouldChangeTextinRange:replacementString:
 243 Perform indentation-maintaining if we're supposed to.
 244 -------------------------------------------------------------------------- */
 245
 246-(BOOL) textView:(NSTextView *)tv shouldChangeTextInRange:(NSRange)afcr replacementString:(NSString *)rps
 247{
 248	if( maintainIndentation )
 249	{
 250		affectedCharRange = afcr;
 251		if( replacementString )
 252		{
 253			[replacementString release];
 254			replacementString = nil;
 255		}
 256		replacementString = [rps retain];
 257		[self performSelector: @selector(didChangeText) withObject: nil afterDelay: 0.0];	// Queue this up on the event loop. If we change the text here, we only confuse the undo stack.
 258	}
 259	
 260	return YES;
 261}
 262
 263
 264-(void)	didChangeText	// This actually does what we want to do in textView:shouldChangeTextInRange:
 265{
 266	if( maintainIndentation && replacementString && ([replacementString isEqualToString:@"\n"]
 267                                                     || [replacementString isEqualToString:@"\r"]) )
 268	{
 269		NSMutableAttributedString*  textStore = [TEXTVIEW textStorage];
 270		BOOL						hadSpaces = NO;
 271		unsigned int				lastSpace = affectedCharRange.location,
 272        prevLineBreak = 0;
 273		NSRange						spacesRange = { 0, 0 };
 274		unichar						theChar = 0;
 275		unsigned int				x = (affectedCharRange.location == 0) ? 0 : affectedCharRange.location -1;
 276		NSString*					tsString = [textStore string];
 277		
 278		while( YES )
 279		{
 280			if( x > ([tsString length] -1) )
 281				break;
 282			
 283			theChar = [tsString characterAtIndex: x];
 284			
 285			switch( theChar )
 286			{
 287				case '\n':
 288				case '\r':
 289					prevLineBreak = x +1;
 290					x = 0;  // Terminate the loop.
 291					break;
 292                    
 293				case ' ':
 294				case '\t':
 295					if( !hadSpaces )
 296					{
 297						lastSpace = x;
 298						hadSpaces = YES;
 299					}
 300					break;
 301                    
 302				default:
 303					hadSpaces = NO;
 304					break;
 305			}
 306			
 307			if( x == 0 )
 308				break;
 309			
 310			x--;
 311		}
 312		
 313		if( hadSpaces )
 314		{
 315			spacesRange.location = prevLineBreak;
 316			spacesRange.length = lastSpace -prevLineBreak +1;
 317			if( spacesRange.length > 0 )
 318				[TEXTVIEW insertText: [tsString substringWithRange:spacesRange]];
 319		}
 320	}
 321}
 322
 323
 324/* -----------------------------------------------------------------------------
 325 toggleAutoSyntaxColoring:
 326 Action for menu item that toggles automatic syntax coloring on and off.
 327 -------------------------------------------------------------------------- */
 328
 329-(IBAction)	toggleAutoSyntaxColoring: (id)sender
 330{
 331	[self setAutoSyntaxColoring: ![self autoSyntaxColoring]];
 332	[self recolorCompleteFile: nil];
 333}
 334
 335
 336/* -----------------------------------------------------------------------------
 337 setAutoSyntaxColoring:
 338 Accessor to turn automatic syntax coloring on or off.
 339 -------------------------------------------------------------------------- */
 340
 341-(void)		setAutoSyntaxColoring: (BOOL)state
 342{
 343	autoSyntaxColoring = state;
 344}
 345
 346/* -----------------------------------------------------------------------------
 347 autoSyntaxColoring:
 348 Accessor for determining whether automatic syntax coloring is on or off.
 349 -------------------------------------------------------------------------- */
 350
 351-(BOOL)		autoSyntaxColoring
 352{
 353	return autoSyntaxColoring;
 354}
 355
 356
 357/* -----------------------------------------------------------------------------
 358 toggleMaintainIndentation:
 359 Action for menu item that toggles indentation maintaining on and off.
 360 -------------------------------------------------------------------------- */
 361
 362-(IBAction)	toggleMaintainIndentation: (id)sender
 363{
 364	[self setMaintainIndentation: ![self maintainIndentation]];
 365}
 366
 367
 368/* -----------------------------------------------------------------------------
 369 setMaintainIndentation:
 370 Accessor to turn indentation maintaining on or off.
 371 -------------------------------------------------------------------------- */
 372
 373-(void)		setMaintainIndentation: (BOOL)state
 374{
 375	maintainIndentation = state;
 376}
 377
 378/* -----------------------------------------------------------------------------
 379 maintainIndentation:
 380 Accessor for determining whether indentation maintaining is on or off.
 381 -------------------------------------------------------------------------- */
 382
 383-(BOOL)		maintainIndentation
 384{
 385	return maintainIndentation;
 386}
 387
 388
 389/* -----------------------------------------------------------------------------
 390 goToLine:
 391 This selects the specified line of the document.
 392 -------------------------------------------------------------------------- */
 393
 394-(void)	goToLine: (int)lineNum
 395{
 396	NSRange			theRange = { 0, 0 };
 397	NSString*		vString = [TEXTVIEW string];
 398	unsigned		currLine = 1;
 399	NSCharacterSet* vSet = [NSCharacterSet characterSetWithCharactersInString: @"\n\r"];
 400	unsigned		x;
 401	unsigned		lastBreakOffs = 0;
 402	unichar			lastBreakChar = 0;
 403	
 404	for( x = 0; x < [vString length]; x++ )
 405	{
 406		unichar		theCh = [vString characterAtIndex: x];
 407		
 408		// Skip non-linebreak chars:
 409		if( ![vSet characterIsMember: theCh] )
 410			continue;
 411		
 412		// If this is the LF in a CRLF sequence, only count it as one line break:
 413		if( theCh == '\n' && lastBreakOffs == (x-1)
 414           && lastBreakChar == '\r' )
 415		{
 416			lastBreakOffs = 0;
 417			lastBreakChar = 0;
 418			theRange.location++;
 419			continue;
 420		}
 421		
 422		// Calc range and increase line number:
 423		theRange.length = x -theRange.location +1;
 424		if( currLine >= lineNum )
 425			break;
 426		currLine++;
 427		theRange.location = theRange.location +theRange.length;
 428		lastBreakOffs = x;
 429		lastBreakChar = theCh;
 430	}
 431	
 432	[TEXTVIEW scrollRangeToVisible: theRange];
 433	[TEXTVIEW setSelectedRange: theRange];
 434}
 435
 436
 437/* -----------------------------------------------------------------------------
 438 turnOffWrapping:
 439 Makes the view so wide that text won't wrap anymore.
 440 -------------------------------------------------------------------------- */
 441
 442#define REALLY_LARGE_NUMBER	1.0e7	// FLT_MAX is too large and causes our rect to be shortened again.
 443
 444-(void) turnOffWrapping
 445{
 446	NSTextContainer*	textContainer = [TEXTVIEW textContainer];
 447	NSRect				frame = { { 0, 0 }, { 0, 0 } };
 448	NSScrollView*		scrollView = [TEXTVIEW enclosingScrollView];
 449	
 450	// Make sure we can see right edge of line:
 451    [scrollView setHasHorizontalScroller: YES];
 452	
 453	// Make text container so wide it won't wrap:
 454	[textContainer setContainerSize: NSMakeSize(REALLY_LARGE_NUMBER, REALLY_LARGE_NUMBER)];
 455	[textContainer setWidthTracksTextView: NO];
 456    [textContainer setHeightTracksTextView: NO];
 457    
 458	// Make sure text view is wide enough:
 459	frame.origin = NSMakePoint( 0.0, 0.0 );
 460    frame.size = [scrollView contentSize];
 461	
 462    [TEXTVIEW setMaxSize: NSMakeSize(REALLY_LARGE_NUMBER, REALLY_LARGE_NUMBER)];
 463    [TEXTVIEW setHorizontallyResizable: YES];
 464    [TEXTVIEW setVerticallyResizable: YES];
 465    [TEXTVIEW setAutoresizingMask: NSViewNotSizable];
 466}
 467
 468
 469/* -----------------------------------------------------------------------------
 470 goToCharacter:
 471 This selects the specified character in the document.
 472 -------------------------------------------------------------------------- */
 473
 474-(void)	goToCharacter: (int)charNum
 475{
 476	[self goToRangeFrom: charNum toChar: charNum +1];
 477}
 478
 479
 480/* -----------------------------------------------------------------------------
 481 goToRangeFrom:
 482 Main bottleneck for selecting ranges in our file.
 483 -------------------------------------------------------------------------- */
 484
 485-(void) goToRangeFrom: (int)startCh toChar: (int)endCh
 486{
 487	NSRange		theRange = { 0, 0 };
 488    
 489	theRange.location = startCh -1;
 490	theRange.length = endCh -startCh;
 491	
 492	if( startCh == 0 || startCh > [[TEXTVIEW string] length] )
 493		return;
 494	
 495	[TEXTVIEW scrollRangeToVisible: theRange];
 496	[TEXTVIEW setSelectedRange: theRange];
 497}
 498
 499
 500// -----------------------------------------------------------------------------
 501//	restoreText:
 502//		Main bottleneck for our (very primitive and inefficient) undo
 503//		implementation. This takes a string of the previous state of the
 504//		*entire text* and restores it.
 505// -----------------------------------------------------------------------------
 506
 507-(void)	restoreText: (NSString*)textToRestore
 508{
 509	[[self undoManager] disableUndoRegistration];
 510	[TEXTVIEW setString: textToRestore];
 511	[[self undoManager] enableUndoRegistration];
 512}
 513
 514
 515// -----------------------------------------------------------------------------
 516//	indentSelection:
 517//		Indent the selected lines by one more level (i.e. one more tab).
 518// -----------------------------------------------------------------------------
 519
 520-(IBAction) indentSelection: (id)sender
 521{
 522	[[self undoManager] beginUndoGrouping];
 523	NSString*	prevText = [[[[TEXTVIEW textStorage] string] copy] autorelease];
 524	[[self undoManager] registerUndoWithTarget: self selector: @selector(restoreText:) object: prevText];
 525	
 526	NSRange				selRange = [TEXTVIEW selectedRange],
 527    nuSelRange = selRange;
 528	unsigned			x;
 529	NSMutableString*	str = [[TEXTVIEW textStorage] mutableString];
 530	
 531	// Unselect any trailing returns so we don't indent the next line after a full-line selection.
 532	if( selRange.length > 1 && ([str characterAtIndex: selRange.location +selRange.length -1] == '\n'
 533                                || [str characterAtIndex: selRange.location +selRange.length -1] == '\r') )
 534		selRange.length--;
 535	
 536	for( x = selRange.location +selRange.length -1; x >= selRange.location; x-- )
 537	{
 538		if( [str characterAtIndex: x] == '\n'
 539           || [str characterAtIndex: x] == '\r' )
 540		{
 541			[str insertString: @"\t" atIndex: x+1];
 542			nuSelRange.length++;
 543		}
 544		
 545		if( x == 0 )
 546			break;
 547	}
 548	
 549	[str insertString: @"\t" atIndex: nuSelRange.location];
 550	nuSelRange.length++;
 551	[TEXTVIEW setSelectedRange: nuSelRange];
 552	[[self undoManager] endUndoGrouping];
 553}
 554
 555
 556// -----------------------------------------------------------------------------
 557//	unindentSelection:
 558//		Un-indent the selected lines by one level (i.e. remove one tab from each
 559//		line's start).
 560// -----------------------------------------------------------------------------
 561
 562-(IBAction) unindentSelection: (id)sender
 563{
 564	NSRange				selRange = [TEXTVIEW selectedRange],
 565    nuSelRange = selRange;
 566	unsigned			x, n;
 567	unsigned			lastIndex = selRange.location +selRange.length -1;
 568	NSMutableString*	str = [[TEXTVIEW textStorage] mutableString];
 569	
 570	// Unselect any trailing returns so we don't indent the next line after a full-line selection.
 571	if( selRange.length > 1 && ([str characterAtIndex: selRange.location +selRange.length -1] == '\n'
 572                                || [str characterAtIndex: selRange.location +selRange.length -1] == '\r') )
 573		selRange.length--;
 574	
 575	if( selRange.length == 0 )
 576		return;
 577	
 578	[[self undoManager] beginUndoGrouping];
 579	NSString*	prevText = [[[[TEXTVIEW textStorage] string] copy] autorelease];
 580	[[self undoManager] registerUndoWithTarget: self selector: @selector(restoreText:) object: prevText];
 581    
 582	for( x = lastIndex; x >= selRange.location; x-- )
 583	{
 584		if( [str characterAtIndex: x] == '\n'
 585           || [str characterAtIndex: x] == '\r' )
 586		{
 587			if( (x +1) <= lastIndex)
 588			{
 589				if( [str characterAtIndex: x+1] == '\t' )
 590				{
 591					[str deleteCharactersInRange: NSMakeRange(x+1,1)];
 592					nuSelRange.length--;
 593				}
 594				else
 595				{
 596					for( n = x+1; (n <= (x+4)) && (n <= lastIndex); n++ )
 597					{
 598						if( [str characterAtIndex: x+1] != ' ' )
 599							break;
 600						[str deleteCharactersInRange: NSMakeRange(x+1,1)];
 601						nuSelRange.length--;
 602					}
 603				}
 604			}
 605		}
 606		
 607		if( x == 0 )
 608			break;
 609	}
 610	
 611	if( [str characterAtIndex: nuSelRange.location] == '\t' )
 612	{
 613		[str deleteCharactersInRange: NSMakeRange(nuSelRange.location,1)];
 614		nuSelRange.length--;
 615	}
 616	else
 617	{
 618		for( n = 1; (n <= 4) && (n <= lastIndex); n++ )
 619		{
 620			if( [str characterAtIndex: nuSelRange.location] != ' ' )
 621				break;
 622			[str deleteCharactersInRange: NSMakeRange(nuSelRange.location,1)];
 623			nuSelRange.length--;
 624		}
 625	}
 626	
 627	[TEXTVIEW setSelectedRange: nuSelRange];
 628	[[self undoManager] endUndoGrouping];
 629}
 630
 631
 632/* -----------------------------------------------------------------------------
 633 toggleCommentForSelection:
 634 Add a comment to the start of this line/remove an existing comment.
 635 -------------------------------------------------------------------------- */
 636
 637-(IBAction)	toggleCommentForSelection: (id)sender
 638{
 639	NSRange				selRange = [TEXTVIEW selectedRange];
 640	unsigned			x;
 641	NSMutableString*	str = [[TEXTVIEW textStorage] mutableString];
 642	
 643	if( selRange.length == 0 )
 644		selRange.length++;
 645	
 646	// Are we at the end of a line?
 647	if ([str characterAtIndex: selRange.location] == '\n' ||
 648        [str characterAtIndex: selRange.location] == '\r') 
 649	{
 650		if( selRange.location > 0 )
 651		{
 652			selRange.location--;
 653			selRange.length++;
 654		}
 655	}
 656	
 657	// Move the selection to the start of a line
 658	while( selRange.location > 0 )
 659	{
 660		if( [str characterAtIndex: selRange.location] == '\n'
 661           || [str characterAtIndex: selRange.location] == '\r')
 662		{
 663			selRange.location++;
 664			selRange.length--;
 665			break;
 666		}
 667		selRange.location--;
 668		selRange.length++;
 669	}
 670    
 671	// Select up to the end of a line
 672	while ( (selRange.location +selRange.length) < [str length]  
 673           && !([str characterAtIndex:selRange.location+selRange.length-1] == '\n' 
 674                || [str characterAtIndex:selRange.location+selRange.length-1] == '\r') ) 
 675	{
 676		selRange.length++;
 677	}
 678	
 679	if (selRange.length == 0)
 680		return;
 681	
 682	[[self undoManager] beginUndoGrouping];
 683	NSString*	prevText = [[[[TEXTVIEW textStorage] string] copy] autorelease];
 684	[[self undoManager] registerUndoWithTarget: self selector: @selector(restoreText:) object: prevText];
 685	
 686	// Unselect any trailing returns so we don't comment the next line after a full-line selection.
 687	while( [str characterAtIndex: selRange.location +selRange.length -1] == '\n' ||
 688          [str characterAtIndex: selRange.location +selRange.length -1] == '\r'
 689          && selRange.length > 0 )
 690	{
 691		selRange.length--;
 692	}
 693	
 694	
 695	NSRange nuSelRange = selRange;
 696	
 697	NSString*	commentPrefix = [[self syntaxDefinitionDictionary] objectForKey: @"OneLineCommentPrefix"];
 698	if( !commentPrefix || [commentPrefix length] == 0 )
 699		commentPrefix = @"# ";
 700	NSInteger	commentPrefixLength = [commentPrefix length];
 701	NSString*	trimmedCommentPrefix = [commentPrefix stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
 702	if( !trimmedCommentPrefix || [trimmedCommentPrefix length] == 0 )	// Comments apparently *are* whitespace.
 703		trimmedCommentPrefix = commentPrefix;
 704	NSInteger	trimmedCommentPrefixLength = [trimmedCommentPrefix length];
 705	
 706	for( x = selRange.location +selRange.length -1; x >= selRange.location; x-- )
 707	{
 708		BOOL	hitEnd = (x == selRange.location);
 709		BOOL	hitLineBreak = [str characterAtIndex: x] == '\n' || [str characterAtIndex: x] == '\r';
 710		if( hitLineBreak || hitEnd )
 711		{
 712			NSUInteger	startOffs = x+1;
 713			if( hitEnd && !hitLineBreak )
 714				startOffs = x;
 715			NSInteger	possibleCommentLength = 0;
 716			if( commentPrefixLength <= (selRange.length +selRange.location -startOffs) )
 717				possibleCommentLength = commentPrefixLength;
 718			else if( trimmedCommentPrefixLength <= (selRange.length +selRange.location -startOffs) )
 719				possibleCommentLength = trimmedCommentPrefixLength;
 720			
 721			NSString	*	lineStart = [str substringWithRange: NSMakeRange( startOffs, possibleCommentLength )];
 722			BOOL			haveWhitespaceToo = [lineStart hasPrefix: commentPrefix];
 723			if( [lineStart hasPrefix: trimmedCommentPrefix] )
 724			{
 725				NSInteger	commentLength = haveWhitespaceToo ? commentPrefixLength : trimmedCommentPrefixLength;
 726				[str deleteCharactersInRange: NSMakeRange(startOffs, commentLength)];
 727				nuSelRange.length -= commentLength;
 728			}
 729			else
 730			{
 731				[str insertString: commentPrefix atIndex: startOffs];
 732				nuSelRange.length += commentPrefixLength;
 733			}
 734		}
 735		
 736		if( x == 0 )
 737			break;
 738	}
 739	
 740	[TEXTVIEW setSelectedRange: nuSelRange];
 741	[[self undoManager] endUndoGrouping];
 742	
 743}
 744
 745
 746/* -----------------------------------------------------------------------------
 747 validateMenuItem:
 748 Make sure check marks of the "Toggle auto syntax coloring" and "Maintain
 749 indentation" menu items are set up properly.
 750 -------------------------------------------------------------------------- */
 751
 752-(BOOL)	validateMenuItem:(NSMenuItem*)menuItem
 753{
 754	if( [menuItem action] == @selector(toggleAutoSyntaxColoring:) )
 755	{
 756		[menuItem setState: [self autoSyntaxColoring]];
 757		return YES;
 758	}
 759	else if( [menuItem action] == @selector(toggleMaintainIndentation:) )
 760	{
 761		[menuItem setState: [self maintainIndentation]];
 762		return YES;
 763	}
 764	else
 765		return [super validateMenuItem: menuItem];
 766}
 767
 768
 769/* -----------------------------------------------------------------------------
 770 recolorCompleteFile:
 771 IBAction to do a complete recolor of the whole friggin' document.
 772 This is called once after the document's been loaded and leaves some
 773 custom styles in the document which are used by recolorRange to properly
 774 perform recoloring of parts.
 775 -------------------------------------------------------------------------- */
 776
 777-(IBAction)	recolorCompleteFile: (id)sender
 778{
 779	NSRange		range = NSMakeRange( 0, [[TEXTVIEW textStorage] length] );
 780	[self recolorRange: range];
 781}
 782
 783
 784/* -----------------------------------------------------------------------------
 785 recolorRange:
 786 Try to apply syntax coloring to the text in our text view. This
 787 overwrites any styles the text may have had before. This function
 788 guarantees that it'll preserve the selection.
 789 
 790 Note that the order in which the different things are colorized is
 791 important. E.g. identifiers go first, followed by comments, since that
 792 way colors are removed from identifiers inside a comment and replaced
 793 with the comment color, etc. 
 794 
 795 The range passed in here is special, and may not include partial
 796 identifiers or the end of a comment. Make sure you include the entire
 797 multi-line comment etc. or it'll lose color.
 798 
 799 This calls oldRecolorRange to handle old-style syntax definitions.
 800 -------------------------------------------------------------------------- */
 801
 802-(void)		recolorRange: (NSRange)range
 803{
 804	if( syntaxColoringBusy )	// Prevent endless loop when recoloring's replacement of text causes processEditing to fire again.
 805		return;
 806	
 807	if( TEXTVIEW == nil || range.length == 0	// Don't like doing useless stuff.
 808       || recolorTimer )						// And don't like recoloring partially if a full recolorization is pending.
 809		return;
 810	
 811	@try
 812	{
 813		syntaxColoringBusy = YES;
 814		if( [delegate respondsToSelector: @selector(textViewControllerWillStartSyntaxRecoloring:)] )
 815			[delegate textViewControllerWillStartSyntaxRecoloring: self];
 816		
 817		// Kludge fix for case where we sometimes exceed text length:ra
 818		int diff = [[TEXTVIEW textStorage] length] -(range.location +range.length);
 819		if( diff < 0 )
 820			range.length += diff;
 821        
 822		// Get the text we'll be working with:
 823		NSMutableAttributedString*	vString = [[NSMutableAttributedString alloc] initWithString: [[[TEXTVIEW textStorage] string] substringWithRange: range]];
 824		[vString autorelease];
 825		
 826		// Load colors and fonts to use from preferences:
 827		// Load our dictionary which contains info on coloring this language:
 828		NSDictionary*				vSyntaxDefinition = [self syntaxDefinitionDictionary];
 829		NSEnumerator*				vComponentsEnny = [[vSyntaxDefinition objectForKey: @"Components"] objectEnumerator];
 830		
 831		if( vComponentsEnny == nil )	// No new-style list of components to colorize?
 832		{
 833			// @finally takes care of cleaning up syntaxColoringBusy etc. here.
 834			return;
 835		}
 836		
 837		// Loop over all available components:
 838		NSDictionary*				vCurrComponent = nil;
 839		NSDictionary*				vStyles = [self defaultTextAttributes];
 840		NSUserDefaults*				vPrefs = [NSUserDefaults standardUserDefaults];
 841        
 842        NSDictionary*		dStyles = [NSDictionary dictionaryWithObjectsAndKeys:
 843                                       [NSColor whiteColor], NSForegroundColorAttributeName,
 844                                       @"JSONStrings", TD_SYNTAX_COLORING_MODE_ATTR,
 845                                       nil];
 846        
 847        [vString addAttributes: dStyles range: NSMakeRange( 0, [vString length] )];
 848        
 849		while( (vCurrComponent = [vComponentsEnny nextObject]) )
 850		{
 851			NSString*   vComponentType = [vCurrComponent objectForKey: @"Type"];
 852			NSString*   vComponentName = [vCurrComponent objectForKey: @"Name"];
 853			NSString*   vColorKeyName = [@"SyntaxColoring:Color:" stringByAppendingString: vComponentName];
 854			NSColor*	vColor = [[vPrefs arrayForKey: vColorKeyName] colorValue];
 855			
 856			if( !vColor )
 857				vColor = [[vCurrComponent objectForKey: @"Color"] colorValue];
 858			
 859			if( [vComponentType isEqualToString: @"BlockComment"] )
 860			{
 861				[self colorCommentsFrom: [vCurrComponent objectForKey: @"Start"]
 862                                     to: [vCurrComponent objectForKey: @"End"] inString: vString
 863                              withColor: vColor andMode: vComponentName];
 864			}
 865			else if( [vComponentType isEqualToString: @"OneLineComment"] )
 866			{
 867				[self colorOneLineComment: [vCurrComponent objectForKey: @"Start"]
 868                                 inString: vString withColor: vColor andMode: vComponentName];
 869			}
 870			else if( [vComponentType isEqualToString: @"String"] )
 871			{
 872				[self colorStringsFrom: [vCurrComponent objectForKey: @"Start"]
 873                                    to: [vCurrComponent objectForKey: @"End"]
 874                              inString: vString withColor: vColor andMode: vComponentName
 875                         andEscapeChar: [vCurrComponent objectForKey: @"EscapeChar"]]; 
 876			}
 877			else if( [vComponentType isEqualToString: @"Tag"] )
 878			{
 879				[self colorTagFrom: [vCurrComponent objectForKey: @"Start"]
 880                                to: [vCurrComponent objectForKey: @"End"] inString: vString
 881                         withColor: vColor andMode: vComponentName
 882                      exceptIfMode: [vCurrComponent objectForKey: @"IgnoredComponent"]];
 883			}
 884			else if( [vComponentType isEqualToString: @"Keywords"] )
 885			{
 886				NSArray* vIdents = [vCurrComponent objectForKey: @"Keywords"];
 887				if( !vIdents )
 888					vIdents = [[NSUserDefaults standardUserDefaults] objectForKey: [@"SyntaxColoring:Keywords:" stringByAppendingString: vComponentName]];
 889				if( !vIdents && [vComponentName isEqualToString: @"UserIdentifiers"] )
 890					vIdents = [[NSUserDefaults standardUserDefaults] objectForKey: TD_USER_DEFINED_IDENTIFIERS];
 891				if( vIdents )
 892				{
 893					NSCharacterSet*		vIdentCharset = nil;
 894					NSString*			vCurrIdent = nil;
 895					NSString*			vCsStr = [vCurrComponent objectForKey: @"Charset"];
 896					if( vCsStr )
 897						vIdentCharset = [NSCharacterSet characterSetWithCharactersInString: vCsStr];
 898					
 899					NSEnumerator*	vItty = [vIdents objectEnumerator];
 900					while( vCurrIdent = [vItty nextObject] )
 901						[self colorIdentifier: vCurrIdent inString: vString withColor: vColor
 902                                      andMode: vComponentName charset: vIdentCharset];
 903				}
 904			}
 905		}
 906		
 907		// Replace the range with our recolored part:
 908		[vString addAttributes: vStyles range: NSMakeRange( 0, [vString length] )];
 909		[[TEXTVIEW textStorage] replaceCharactersInRange: range withAttributedString: vString];
 910	}
 911	@finally
 912	{
 913		if( [delegate respondsToSelector: @selector(textViewControllerDidFinishSyntaxRecoloring:)] )
 914			[delegate textViewControllerDidFinishSyntaxRecoloring: self];
 915		syntaxColoringBusy = NO;
 916		[self textView: TEXTVIEW willChangeSelectionFromCharacterRange: [TEXTVIEW selectedRange]
 917      toCharacterRange: [TEXTVIEW selectedRange]];
 918	}
 919}
 920
 921
 922/* -----------------------------------------------------------------------------
 923 textView:willChangeSelectionFromCharacterRange:toCharacterRange:
 924 Delegate method called when our selection changes. Updates our status
 925 display to indicate which characters are selected.
 926 -------------------------------------------------------------------------- */
 927
 928-(NSRange)  textView: (NSTextView*)theTextView willChangeSelectionFromCharacterRange: (NSRange)oldSelectedCharRange
 929    toCharacterRange: (NSRange)newSelectedCharRange
 930{
 931	unsigned		startCh = newSelectedCharRange.location,
 932    endCh = newSelectedCharRange.location +newSelectedCharRange.length;
 933	unsigned		lineNo = 0,
 934    lastLineStart = 0,
 935    x = 0;
 936	unsigned		startChLine = 0, endChLine = 0;
 937	unichar			lastBreakChar = 0;
 938	unsigned		lastBreakOffs = 0;
 939    
 940	// Calc line number:
 941	for( x = 0; (x < startCh) && (x < [[theTextView string] length]); x++ )
 942	{
 943		unichar		theCh = [[theTextView string] characterAtIndex: x];
 944		switch( theCh )
 945		{
 946			case '\n':
 947				if( lastBreakOffs == (x-1) && lastBreakChar == '\r' )   // LF in CRLF sequence? Treat this as a single line break.
 948				{
 949					lastBreakOffs = 0;
 950					lastBreakChar = 0;
 951					continue;
 952				}
 953				// Else fall through!
 954				
 955			case '\r':
 956				lineNo++;
 957				lastLineStart = x +1;
 958				lastBreakOffs = x;
 959				lastBreakChar = theCh;
 960				break;
 961		}
 962	}
 963	
 964	startChLine = (newSelectedCharRange.location -lastLineStart);
 965	endChLine = (newSelectedCharRange.location -lastLineStart) +newSelectedCharRange.length;
 966	
 967	// Let delegate know what to display:
 968	if( [delegate respondsToSelector: @selector(selectionInTextViewController:changedToStartCharacter:endCharacter:inLine:startCharacterInDocument:endCharacterInDocument:)] )
 969		[delegate selectionInTextViewController: self
 970                        changedToStartCharacter: startChLine endCharacter: endChLine
 971                                         inLine: lineNo startCharacterInDocument: startCh
 972                         endCharacterInDocument: endCh];
 973	
 974	return newSelectedCharRange;
 975}
 976
 977
 978/* -----------------------------------------------------------------------------
 979 syntaxDefinitionFilename:
 980 Like nibName, this should return the name of the syntax
 981 definition file to use. Advanced users may use this to allow different
 982 coloring to take place depending on the file extension by returning
 983 different file names here.
 984 
 985 Note that the ".plist" extension is automatically appended to the file
 986 name.
 987 -------------------------------------------------------------------------- */
 988
 989-(NSString*)	syntaxDefinitionFilename
 990{
 991	NSString*	syntaxDefFN = nil;
 992	if( [delegate respondsToSelector: @selector(syntaxDefinitionFilenameForTextViewController:)] )
 993		syntaxDefFN = [delegate syntaxDefinitionFilenameForTextViewController: self];
 994	
 995	if( !syntaxDefFN )
 996		syntaxDefFN = @"SyntaxDefinition";
 997	
 998	return syntaxDefFN;
 999}
1000
1001
1002/* -----------------------------------------------------------------------------
1003 syntaxDefinitionDictionary:
1004 This returns the syntax definition dictionary to use, which indicates
1005 what ranges of text to colorize. Advanced users may use this to allow
1006 different coloring to take place depending on the file extension by
1007 returning different dictionaries here.
1008 
1009 By default, this simply reads a dictionary from the .plist file
1010 indicated by -syntaxDefinitionFilename.
1011 -------------------------------------------------------------------------- */
1012
1013-(NSDictionary*)	syntaxDefinitionDictionary
1014{
1015	NSDictionary*	theDict = nil;
1016	
1017	if( [delegate respondsToSelector: @selector(syntaxDefinitionDictionaryForTextViewController:)] )
1018		theDict = [delegate syntaxDefinitionDictionaryForTextViewController: self];
1019	
1020	if( !theDict )
1021	{
1022		NSBundle*	theBundle = [self nibBundle];
1023		if( !theBundle )
1024			theBundle = [NSBundle bundleForClass: [self class]];	// Usually the main bundle, but be nice to plugins.
1025		theDict = [NSDictionary dictionaryWithContentsOfFile: [theBundle pathForResource: [self syntaxDefinitionFilename] ofType: @"plist"]];
1026	}
1027	
1028	return theDict;
1029}
1030
1031
1032/* -----------------------------------------------------------------------------
1033 colorStringsFrom:
1034 Apply syntax coloring to all strings. This is basically the same code
1035 as used for multi-line comments, except that it ignores the end
1036 character if it is preceded by a backslash.
1037 -------------------------------------------------------------------------- */
1038
1039-(void)	colorStringsFrom: (NSString*) startCh to: (NSString*) endCh inString: (NSMutableAttributedString*) s
1040               withColor: (NSColor*) col andMode:(NSString*)attr andEscapeChar: (NSString*)vStringEscapeCharacter
1041{
1042	NS_DURING
1043    NSScanner*			vScanner = [NSScanner scannerWithString: [s string]];
1044    NSDictionary*		vStyles = [NSDictionary dictionaryWithObjectsAndKeys:
1045                                   col, NSForegroundColorAttributeName,
1046                                   attr, TD_SYNTAX_COLORING_MODE_ATTR,
1047                                   nil];
1048    BOOL				vIsEndChar = NO;
1049    unichar				vEscChar = '\\';
1050    BOOL				vDelegateHandlesProgress = [delegate respondsToSelector: @selector(textViewControllerProgressedWhileSyntaxRecoloring:)];
1051    
1052    if( vStringEscapeCharacter )
1053    {
1054        if( [vStringEscapeCharacter length] != 0 )
1055            vEscChar = [vStringEscapeCharacter characterAtIndex: 0];
1056    }
1057    
1058    while( ![vScanner isAtEnd] )
1059    {
1060        int		vStartOffs,
1061        vEndOffs;
1062        vIsEndChar = NO;
1063        
1064        if( vDelegateHandlesProgress )
1065            [delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1066        
1067        // Look for start of string:
1068        [vScanner scanUpToString: startCh intoString: nil];
1069        vStartOffs = [vScanner scanLocation];
1070        if( ![vScanner scanString:startCh intoString:nil] )
1071            NS_VOIDRETURN;
1072        
1073        while( !vIsEndChar && ![vScanner isAtEnd] )	// Loop until we find end-of-string marker or our text to color is finished:
1074        {
1075            [vScanner scanUpToString: endCh intoString: nil];
1076            if( ([vStringEscapeCharacter length] == 0) || [[s string] characterAtIndex: ([vScanner scanLocation] -1)] != vEscChar )	// Backslash before the end marker? That means ignore the end marker.
1077                vIsEndChar = YES;	// A real one! Terminate loop.
1078            if( ![vScanner scanString:endCh intoString:nil] )	// But skip this char before that.
1079                return;
1080            
1081            if( vDelegateHandlesProgress )
1082                [delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1083        }
1084        
1085        vEndOffs = [vScanner scanLocation];
1086        
1087        // Now mess with the string's styles:
1088        [s setAttributes: vStyles range: NSMakeRange( vStartOffs, vEndOffs -vStartOffs )];
1089    }
1090	NS_HANDLER
1091    // Just ignore it, syntax coloring isn't that important.
1092	NS_ENDHANDLER
1093}
1094
1095
1096/* -----------------------------------------------------------------------------
1097 colorCommentsFrom:
1098 Colorize block-comments in the text view.
1099 
1100 REVISIONS:
1101 2004-05-18  witness Documented.
1102 -------------------------------------------------------------------------- */
1103
1104-(void)	colorCommentsFrom: (NSString*) startCh to: (NSString*) endCh inString: (NSMutableAttributedString*) s
1105                withColor: (NSColor*) col andMode:(NSString*)attr
1106{
1107	@try
1108	{
1109		NSScanner*			vScanner = [NSScanner scannerWithString: [s string]];
1110		NSDictionary*		vStyles = [NSDictionary dictionaryWithObjectsAndKeys:
1111                                       col, NSForegroundColorAttributeName,
1112                                       attr, TD_SYNTAX_COLORING_MODE_ATTR,
1113                                       nil];
1114		BOOL				vDelegateHandlesProgress = [delegate respondsToSelector: @selector(textViewControllerProgressedWhileSyntaxRecoloring:)];
1115		
1116		while( ![vScanner isAtEnd] )
1117		{
1118			int		vStartOffs,
1119            vEndOffs;
1120			
1121			// Look for start of multi-line comment:
1122			[vScanner scanUpToString: startCh intoString: nil];
1123			vStartOffs = [vScanner scanLocation];
1124			if( ![vScanner scanString:startCh intoString:nil] )
1125				return;
1126            
1127			// Look for associated end-of-comment marker:
1128			[vScanner scanUpToString: endCh intoString: nil];
1129			if( ![vScanner scanString: endCh intoString: nil] )
1130            /*return*/;  // Don't exit. If user forgot trailing marker, indicate this by "bleeding" until end of string.
1131			vEndOffs = [vScanner scanLocation];
1132			
1133			// Now mess with the string's styles:
1134			[s setAttributes: vStyles range: NSMakeRange( vStartOffs, vEndOffs -vStartOffs )];
1135			
1136			if( vDelegateHandlesProgress )
1137				[delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1138		}
1139	}
1140	@catch( ... )
1141	{
1142		// Just ignore it, syntax coloring isn't that important.
1143	}
1144}
1145
1146
1147/* -----------------------------------------------------------------------------
1148 colorOneLineComment:
1149 Colorize one-line-comments in the text view.
1150 
1151 REVISIONS:
1152 2004-05-18  witness Documented.
1153 -------------------------------------------------------------------------- */
1154
1155-(void)	colorOneLineComment: (NSString*) startCh inString: (NSMutableAttributedString*) s
1156                  withColor: (NSColor*) col andMode:(NSString*)attr
1157{
1158	@try
1159	{
1160		NSScanner*			vScanner = [NSScanner scannerWithString: [s string]];
1161		NSDictionary*		vStyles = [NSDictionary dictionaryWithObjectsAndKeys:
1162                                       col, NSForegroundColorAttributeName,
1163                                       attr, TD_SYNTAX_COLORING_MODE_ATTR,
1164                                       nil];
1165		BOOL				vDelegateHandlesProgress = [delegate respondsToSelector: @selector(textViewControllerProgressedWhileSyntaxRecoloring:)];
1166		
1167		while( ![vScanner isAtEnd] )
1168		{
1169			int		vStartOffs,
1170            vEndOffs;
1171			
1172			// Look for start of one-line comment:
1173			[vScanner scanUpToString: startCh intoString: nil];
1174			vStartOffs = [vScanner scanLocation];
1175			if( ![vScanner scanString:startCh intoString:nil] )
1176				return;
1177            
1178			// Look for associated line break:
1179			if( ![vScanner skipUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString: @"\n\r"]] )
1180				;
1181			
1182			vEndOffs = [vScanner scanLocation];
1183			
1184			// Now mess with the string's styles:
1185			[s setAttributes: vStyles range: NSMakeRange( vStartOffs, vEndOffs -vStartOffs )];
1186			
1187			if( vDelegateHandlesProgress )
1188				[delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1189		}
1190	}
1191	@catch( ... )
1192	{
1193		// Just ignore it, syntax coloring isn't that important.
1194	}
1195}
1196
1197
1198/* -----------------------------------------------------------------------------
1199 colorIdentifier:
1200 Colorize keywords in the text view.
1201 
1202 REVISIONS:
1203 2004-05-18  witness Documented.
1204 -------------------------------------------------------------------------- */
1205
1206-(void)	colorIdentifier: (NSString*) ident inString: (NSMutableAttributedString*) s
1207              withColor: (NSColor*) col andMode:(NSString*)attr charset: (NSCharacterSet*)cset
1208{
1209	@try
1210	{
1211		NSScanner*			vScanner = [NSScanner scannerWithString: [s string]];
1212		NSDictionary*		vStyles = [NSDictionary dictionaryWithObjectsAndKeys:
1213                                       col, NSForegroundColorAttributeName,
1214                                       attr, TD_SYNTAX_COLORING_MODE_ATTR,
1215                                       nil];
1216		int					vStartOffs = 0;
1217		BOOL				vDelegateHandlesProgress = [delegate respondsToSelector: @selector(textViewControllerProgressedWhileSyntaxRecoloring:)];
1218		
1219		// Skip any leading whitespace chars, somehow NSScanner doesn't do that:
1220		if( cset )
1221		{
1222			while( vStartOffs < [[s string] length] )
1223			{
1224				if( [cset characterIsMember: [[s string] characterAtIndex: vStartOffs]] )
1225					break;
1226				vStartOffs++;
1227			}
1228		}
1229		
1230		[vScanner setScanLocation: vStartOffs];
1231		
1232		while( ![vScanner isAtEnd] )
1233		{
1234			// Look for start of identifier:
1235			[vScanner scanUpToString: ident intoString: nil];
1236			vStartOffs = [vScanner scanLocation];
1237			if( ![vScanner scanString:ident intoString:nil] )
1238				return;
1239			
1240			if( vStartOffs > 0 )	// Check that we're not in the middle of an identifier:
1241			{
1242				// Alphanum character before identifier start?
1243				if( [cset characterIsMember: [[s string] characterAtIndex: (vStartOffs -1)]] )  // If charset is NIL, this evaluates to NO.
1244					continue;
1245			}
1246			
1247			if( (vStartOffs +[ident length] +1) < [s length] )
1248			{
1249				// Alphanum character following our identifier?
1250				if( [cset characterIsMember: [[s string] characterAtIndex: (vStartOffs +[ident length])]] )  // If charset is NIL, this evaluates to NO.
1251					continue;
1252			}
1253			
1254			// Now mess with the string's styles:
1255			[s setAttributes: vStyles range: NSMakeRange( vStartOffs, [ident length] )];
1256            
1257			if( vDelegateHandlesProgress )
1258				[delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1259		}
1260	}
1261	@catch( ... )
1262	{
1263		// Just ignore it, syntax coloring isn't that important.
1264	}
1265}
1266
1267
1268/* -----------------------------------------------------------------------------
1269 colorTagFrom:
1270 Colorize HTML tags or similar constructs in the text view.
1271 
1272 REVISIONS:
1273 2004-05-18  witness Documented.
1274 -------------------------------------------------------------------------- */
1275
1276-(void)	colorTagFrom: (NSString*) startCh to: (NSString*)endCh inString: (NSMutableAttributedString*) s
1277           withColor: (NSColor*) col andMode:(NSString*)attr exceptIfMode: (NSString*)ignoreAttr
1278{
1279	@try
1280	{
1281		NSScanner*			vScanner = [NSScanner scannerWithString: [s string]];
1282		NSDictionary*		vStyles = [NSDictionary dictionaryWithObjectsAndKeys:
1283                                       col, NSForegroundColorAttributeName,
1284                                       attr, TD_SYNTAX_COLORING_MODE_ATTR,
1285                                       nil];
1286		BOOL				vDelegateHandlesProgress = [delegate respondsToSelector: @selector(textViewControllerProgressedWhileSyntaxRecoloring:)];
1287		
1288		while( ![vScanner isAtEnd] )
1289		{
1290			int		vStartOffs,
1291            vEndOffs;
1292			
1293			// Look for start of one-line comment:
1294			[vScanner scanUpToString: startCh intoString: nil];
1295			vStartOffs = [vScanner scanLocation];
1296			if( vStartOffs >= [s length] )
1297				return;
1298			NSString*   scMode = [[s attributesAtIndex:vStartOffs effectiveRange: nil] objectForKey: TD_SYNTAX_COLORING_MODE_ATTR];
1299			if( ![vScanner scanString:startCh intoString:nil] )
1300				return;
1301			
1302			// If start lies in range of ignored style, don't colorize it:
1303			if( ignoreAttr != nil && [scMode isEqualToString: ignoreAttr] )
1304				continue;
1305            
1306			// Look for matching end marker:
1307			while( ![vScanner isAtEnd] )
1308			{
1309				// Scan up to the next occurence of the terminating sequence:
1310				(BOOL) [vScanner scanUpToString: endCh intoString:nil];
1311				
1312				// Now, if the mode of the end marker is not the mode we were told to ignore,
1313				//  we're finished now and we can exit the inner loop:
1314				vEndOffs = [vScanner scanLocation];
1315				if( vEndOffs < [s length] )
1316				{
1317					scMode = [[s attributesAtIndex:vEndOffs effectiveRange: nil] objectForKey: TD_SYNTAX_COLORING_MODE_ATTR];
1318					[vScanner scanString: endCh intoString: nil];   // Also skip the terminating sequence.
1319					if( ignoreAttr == nil || ![scMode isEqualToString: ignoreAttr] )
1320						break;
1321				}
1322				
1323				// Otherwise we keep going, look for the next occurence of endCh and hope it isn't in that style.
1324			}
1325			
1326			vEndOffs = [vScanner scanLocation];
1327			
1328			if( vDelegateHandlesProgress )
1329				[delegate textViewControllerProgressedWhileSyntaxRecoloring: self];
1330			
1331			// Now mess with the string's styles:
1332			[s setAttributes: vStyles range: NSMakeRange( vStartOffs, vEndOffs -vStartOffs )];
1333		}
1334	}
1335	@catch( ... )
1336	{
1337		// Just ignore it, syntax coloring isn't that important.
1338	}
1339}
1340
1341
1342/* -----------------------------------------------------------------------------
1343 defaultTextAttributes:
1344 Return the text attributes to use for the text in our text view.
1345 
1346 REVISIONS:
1347 2004-05-18  witness Documented.
1348 -------------------------------------------------------------------------- */
1349
1350-(NSDictionary*)	defaultTextAttributes
1351{
1352	return [NSDictionary dictionaryWithObjectsAndKeys: [NSFont userFixedPitchFontOfSize: 10.0], NSFontAttributeName, nil];
1353}
1354
1355@end