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