diff options
Diffstat (limited to 'Source/CMTextView.m')
-rw-r--r-- | Source/CMTextView.m | 293 |
1 files changed, 205 insertions, 88 deletions
diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 982cd9c6..85f4f8bf 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -37,18 +37,21 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE; void yy_switch_to_buffer(YY_BUFFER_STATE); YY_BUFFER_STATE yy_scan_string (const char *); -#define kAPlinked @"Linked" // attribute for a via auto-pair inserted char -#define kAPval @"linked" -#define kWQquoted @"Quoted" // set via lex to indicate a quoted string -#define kSQLkeyword @"SQLkw" // attribute for found SQL keywords -#define kQuote @"Quote" -#define kQuoteValue @"isQuoted" -#define kValue @"dummy" -#define kBTQuote @"BTQuote" -#define kBTQuoteValue @"isBTQuoted" +#define kAPlinked @"Linked" // attribute for a via auto-pair inserted char +#define kAPval @"linked" +#define kLEXToken @"Quoted" // set via lex to indicate a quoted string +#define kLEXTokenValue @"isMarked" +#define kSQLkeyword @"SQLkw" // attribute for found SQL keywords +#define kQuote @"Quote" +#define kQuoteValue @"isQuoted" +#define kValue @"dummy" +#define kBTQuote @"BTQuote" +#define kBTQuoteValue @"isBTQuoted" #define SP_CQ_SEARCH_IN_MYSQL_HELP_MENU_ITEM_TAG 1000 +#define SP_SYNTAX_HILITE_BIAS 2000 + #define MYSQL_DOC_SEARCH_URL @"http://dev.mysql.com/doc/refman/%@/en/%@.html" @@ -115,7 +118,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); /* * Checks if the char after the current caret position/selection matches a supplied attribute */ -- (BOOL) isNextCharMarkedBy:(id)attribute +- (BOOL) isNextCharMarkedBy:(id)attribute withValue:(id)aValue { unsigned int caretPosition = [self selectedRange].location; @@ -123,7 +126,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); if (caretPosition >= [[self string] length]) return NO; // Perform the check - if ([[self textStorage] attribute:attribute atIndex:caretPosition effectiveRange:nil]) + if ([[[self textStorage] attribute:attribute atIndex:caretPosition effectiveRange:nil] isEqualToString:aValue]) return YES; return NO; @@ -154,7 +157,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); // Check that the pairing character exists after the caret, and is tagged with the link attribute if (matchingChar == [[self string] characterAtIndex:caretPosition] - && [[self textStorage] attribute:kAPlinked atIndex:caretPosition effectiveRange:nil]) { + && [[[self textStorage] attribute:kAPlinked atIndex:caretPosition effectiveRange:nil] isEqualToString:kAPval]) { return YES; } @@ -233,6 +236,25 @@ YY_BUFFER_STATE yy_scan_string (const char *); [self scrollRangeToVisible:selRange]; } +/* + * Used for autoHelp update if the user changed the caret position by using the mouse. + */ +- (void) mouseDown:(NSEvent *)theEvent +{ + + // Cancel autoHelp timer + if([prefs boolForKey:@"CustomQueryAutohelp"]) + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(autoHelp) + object:nil]; + + [super mouseDown:theEvent]; + + // Start autoHelp timer + if([prefs boolForKey:@"CustomQueryAutohelp"]) + [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[[prefs valueForKey:@"CustomQueryAutohelpDelay"] retain] floatValue]]; + +} /* * Handle some keyDown events in order to provide autopairing functionality (if enabled). @@ -240,7 +262,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); - (void) keyDown:(NSEvent *)theEvent { - if(autohelpEnabled) // cancel autoHelp request + if([prefs boolForKey:@"CustomQueryAutohelp"]) // cancel autoHelp request [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(autoHelp) object:nil]; @@ -262,7 +284,6 @@ YY_BUFFER_STATE yy_scan_string (const char *); unichar insertedCharacter = [characters characterAtIndex:0]; long curFlags = ([theEvent modifierFlags] & allFlags); - // Note: switch(insertedCharacter) {} does not work instead use charactersIgnoringModifiers if([charactersIgnMod isEqualToString:@"c"]) // ^C copy as RTF if(curFlags==(NSControlKeyMask)) @@ -279,7 +300,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); } // Only process for character autopairing if autopairing is enabled and a single character is being added. - if (autopairEnabled && characters && [characters length] == 1) { + if ([prefs boolForKey:@"CustomQueryAutopair"] && characters && [characters length] == 1) { delBackwardsWasPressed = NO; @@ -295,10 +316,10 @@ YY_BUFFER_STATE yy_scan_string (const char *); (insertedCharacter == '\'' || insertedCharacter == '"' || insertedCharacter == '`') // And if the next char marked as linked auto-pair - && [self isNextCharMarkedBy:kAPlinked] + && [self isNextCharMarkedBy:kAPlinked withValue:kAPval] // And we are inside a quoted string - && [self isNextCharMarkedBy:kWQquoted] + && [self isNextCharMarkedBy:kLEXToken withValue:kLEXTokenValue] // And there is no selection, just the text caret && ![self selectedRange].length @@ -320,7 +341,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); // There is one exception to this - if the caret is before a linked pair character, // processing continues in order to check whether the next character should be jumped // over; e.g. [| := caret]: "foo|" and press " => only caret will be moved "foo"| - if(![self isNextCharMarkedBy:kAPlinked] && [self isNextCharMarkedBy:kWQquoted] && ![self selectedRange].length) { + if(![self isNextCharMarkedBy:kAPlinked withValue:kAPval] && [self isNextCharMarkedBy:kLEXToken withValue:kLEXTokenValue] && ![self selectedRange].length) { [super keyDown:theEvent]; return; } @@ -360,7 +381,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); if (skipTypedLinkedCharacter) { currentRange = [self selectedRange]; if (currentRange.location != NSNotFound && currentRange.length == 0) { - if ([self isNextCharMarkedBy:kAPlinked]) { + if ([self isNextCharMarkedBy:kAPlinked withValue:kAPval]) { if ([[[self textStorage] string] characterAtIndex:currentRange.location] == insertedCharacter) { currentRange.length = 1; [self setSelectedRange:currentRange]; @@ -405,29 +426,12 @@ YY_BUFFER_STATE yy_scan_string (const char *); // The default action is to perform the normal key-down action. [super keyDown:theEvent]; - if(autohelpEnabled) - [self performSelector:@selector(autoHelp) withObject:nil afterDelay:1]; + if([prefs boolForKey:@"CustomQueryAutohelp"]) + [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[[prefs valueForKey:@"CustomQueryAutohelpDelay"] retain] floatValue]]; } -/* - * Notify autoHelp if user changed the insertion point - */ -- (void)mouseDown:(NSEvent *)theEvent -{ - - if(autohelpEnabled) // cancel autoHelp request - [NSObject cancelPreviousPerformRequestsWithTarget:self - selector:@selector(autoHelp) - object:nil]; - - [super mouseDown:theEvent]; - if(autohelpEnabled) - [self performSelector:@selector(autoHelp) withObject:nil afterDelay:1]; - -} - - (void) deleteBackward:(id)sender { @@ -456,7 +460,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); // Handle newlines, adding any indentation found on the current line to the new line - ignoring the enter key if appropriate if (aSelector == @selector(insertNewline:) - && autoindentEnabled + && [prefs boolForKey:@"CustomQueryAutoindent"] && (!autoindentIgnoresEnter || [[NSApp currentEvent] keyCode] != 0x4C)) { NSString *textViewString = [[self textStorage] string]; @@ -629,7 +633,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); // Check if the caret is inside quotes "" or ''; if so // return the normal word suggestion due to the spelling's settings - if(!sqlStringIsTooLarge && [[[self textStorage] attribute:kQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kQuoteValue] ) + if([[[self textStorage] attribute:kQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kQuoteValue] ) return [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0,charRange.length) inString:[[self string] substringWithRange:charRange] language:nil inSpellDocumentWithTag:0]; @@ -665,12 +669,15 @@ YY_BUFFER_STATE yy_scan_string (const char *); } // If caret is not inside backticks add keywords and all words coming from the view. - if(!sqlStringIsTooLarge && ![[[self textStorage] attribute:kBTQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kBTQuoteValue] ) + if(![[[self textStorage] attribute:kBTQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kBTQuoteValue] ) { - NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()\"'`-!;=+|?:~"]; - NSArray *textViewWords = [[self string] componentsSeparatedByCharactersInSet:separators]; - - [possibleCompletions addObjectsFromArray:textViewWords]; + // Only parse for words if text size is less than 6MB + if([[self string] length]<6000000) + { + NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()\"'`-!;=+|?:~"]; + NSArray *textViewWords = [[self string] componentsSeparatedByCharactersInSet:separators]; + [possibleCompletions addObjectsFromArray:textViewWords]; + } [possibleCompletions addObjectsFromArray:[self keywords]]; } @@ -1762,32 +1769,58 @@ SYNTAX HIGHLIGHTING! autohelpEnabled = NO; delBackwardsWasPressed = NO; - // commentColor = [NSColor colorWithDeviceRed:0.000 green:0.455 blue:0.000 alpha:1.000]; - // quoteColor = [NSColor colorWithDeviceRed:0.769 green:0.102 blue:0.086 alpha:1.000]; - // keywordColor = [NSColor colorWithDeviceRed:0.200 green:0.250 blue:1.000 alpha:1.000]; - // backtickColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.658 alpha:1.000]; - // numericColor = [NSColor colorWithDeviceRed:0.506 green:0.263 blue:0.0 alpha:1.000]; - // variableColor = [NSColor colorWithDeviceRed:0.5 green:0.5 blue:0.5 alpha:1.000]; - lineNumberView = [[NoodleLineNumberView alloc] initWithScrollView:scrollView]; [scrollView setVerticalRulerView:lineNumberView]; [scrollView setHasHorizontalRuler:NO]; [scrollView setHasVerticalRuler:YES]; [scrollView setRulersVisible:YES]; + + // disabled to get the current text range in textView safer + [[self layoutManager] setBackgroundLayoutEnabled:NO]; + + // add NSViewBoundsDidChangeNotification to scrollView + [[scrollView contentView] setPostsBoundsChangedNotifications:YES]; + NSNotificationCenter *aNotificationCenter = [NSNotificationCenter defaultCenter]; + [aNotificationCenter addObserver:self selector:@selector(boundsDidChangeNotification:) name:@"NSViewBoundsDidChangeNotification" object:[scrollView contentView]]; + + prefs = [[NSUserDefaults standardUserDefaults] retain]; } +/* + * Scrollview delegate after the textView's view port was changed. + * Manily used to update the syntax highlighting for a large text size. + */ +- (void) boundsDidChangeNotification:(NSNotification *)notification +{ + // Invoke syntax highlighting if text view port was changed for large text + if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) + { + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doSyntaxHighlighting) + object:nil]; + + if(![[self textStorage] changeInLength]) + [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.4]; + } + +} +/* + * If enabled it shows the MySQL Help for the current word (not inside quotes) or for the selection + * after an adjustable delay if the textView is idle, i.e. no user interaction. + */ - (void)autoHelp { - if(!autohelpEnabled) return; + if(![prefs boolForKey:@"CustomQueryAutohelp"]) return; + // If selection show Help for it if([self selectedRange].length) { [[[[self window] delegate] valueForKeyPath:@"customQueryInstance"] performSelector:@selector(showHelpForCurrentWord:) withObject:self afterDelay:0.1]; return; } - + // Otherwise show Help if caret is not inside quotes long cursorPosition = [self selectedRange].location; if (cursorPosition >= [[self string] length]) cursorPosition--; if(cursorPosition > -1 && (![[self textStorage] attribute:kQuote atIndex:cursorPosition effectiveRange:nil]||[[self textStorage] attribute:kSQLkeyword atIndex:cursorPosition effectiveRange:nil])) @@ -1795,33 +1828,102 @@ SYNTAX HIGHLIGHTING! } -- (void)doSyntaxHighlighting:(NSTextStorage*)textStore +/* + * Syntax Highlighting. + * + * (The main bottleneck is the [NSTextStorage addAttribute:value:range:] method - the parsing itself is really fast!) + * Some sample code from Andrew Choi ( http://members.shaw.ca/akochoi-old/blog/2003/11-09/index.html#3 ) has been reused. + */ +- (void)doSyntaxHighlighting { + + NSTextStorage *textStore = [self textStorage]; + NSRange textRange; + + // If text larger than SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING + // do highlighting partly (max SP_SYNTAX_HILITE_BIAS*2). + // The approach is to take the middle position of the current view port + // and highlight only ±SP_SYNTAX_HILITE_BIAS of that middle position + // considering of line starts resp. ends + if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) + { + + // Cancel all doSyntaxHighlighting requests + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doSyntaxHighlighting) + object:nil]; + + // Get the text range currently displayed in the view port + NSRect visibleRect = [[[self enclosingScrollView] contentView] documentVisibleRect]; + NSRange visibleRange = [[self layoutManager] glyphRangeForBoundingRectWithoutAdditionalLayout:visibleRect inTextContainer:[self textContainer]]; + if(!visibleRange.length) return; + + // Take roughly the middle position in the current view port + int curPos = visibleRange.location+(int)(visibleRange.length/2); + + int strlength = [[self string] length]; + + // get the last line to parse due to SP_SYNTAX_HILITE_BIAS + int end = curPos + SP_SYNTAX_HILITE_BIAS; + if (end > strlength ) + { + end = strlength; + } else { + while(end < strlength) + { + if([[self string] characterAtIndex:end]=='\n') + break; + end++; + } + } + + // get the first line to parse due to SP_SYNTAX_HILITE_BIAS + int start = end - (SP_SYNTAX_HILITE_BIAS*2); + if (start > 0) + while(start>-1) + { + if([[self string] characterAtIndex:start]=='\n') + break; + start--; + } + else + start = 0; + + + textRange = NSMakeRange(start, end-start); + // only to be sure that nothing went wrongly + textRange = NSIntersectionRange(textRange, NSMakeRange(0, [textStore length])); + if (!textRange.length) + return; + } else { + // If text size is less SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING + // process syntax highlighting for the entire text view buffer + textRange = NSMakeRange(0,[[self string] length]); + } + NSColor *tokenColor; - NSColor *commentColor = [NSColor colorWithDeviceRed:0.000 green:0.455 blue:0.000 alpha:1.000]; - NSColor *quoteColor = [NSColor colorWithDeviceRed:0.769 green:0.102 blue:0.086 alpha:1.000]; - NSColor *keywordColor = [NSColor colorWithDeviceRed:0.200 green:0.250 blue:1.000 alpha:1.000]; - NSColor *backtickColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.658 alpha:1.000]; - NSColor *numericColor = [NSColor colorWithDeviceRed:0.506 green:0.263 blue:0.0 alpha:1.000]; - NSColor *variableColor = [NSColor colorWithDeviceRed:0.5 green:0.5 blue:0.5 alpha:1.000]; + + NSColor *commentColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorCommentColor"]] retain];//[NSColor colorWithDeviceRed:0.000 green:0.455 blue:0.000 alpha:1.000]; + NSColor *quoteColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorQuoteColor"]] retain];//[NSColor colorWithDeviceRed:0.769 green:0.102 blue:0.086 alpha:1.000]; + NSColor *keywordColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorSQLKeywordColor"]] retain];//[NSColor colorWithDeviceRed:0.200 green:0.250 blue:1.000 alpha:1.000]; + NSColor *backtickColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorBacktickColor"]] retain];//[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.658 alpha:1.000]; + NSColor *numericColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorNumericColor"]] retain];//[NSColor colorWithDeviceRed:0.506 green:0.263 blue:0.0 alpha:1.000]; + NSColor *variableColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorVariableColor"]] retain];//[NSColor colorWithDeviceRed:0.5 green:0.5 blue:0.5 alpha:1.000]; + NSColor *textColor = [[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorTextColor"]] retain];//[NSColor colorWithDeviceRed:0.5 green:0.5 blue:0.5 alpha:1.000]; - int token; - NSRange textRange, tokenRange; + BOOL autouppercaseKeywords = [prefs boolForKey:@"CustomQueryAutouppercaseKeywords"]; - textRange = NSMakeRange(0, [textStore length]); + unsigned long tokenEnd, token; + NSRange tokenRange; //first remove the old colors and kQuote [textStore removeAttribute:NSForegroundColorAttributeName range:textRange]; [textStore removeAttribute:kQuote range:textRange]; - - //don't color texts longer than about 20KB. would be too slow - sqlStringIsTooLarge = (textRange.length > 20000); - if(sqlStringIsTooLarge) return; - + [textStore removeAttribute:kLEXToken range:textRange]; //initialise flex - yyuoffset = 0; yyuleng = 0; - yy_switch_to_buffer(yy_scan_string([[textStore string] UTF8String])); + yyuoffset = textRange.location; yyuleng = 0; + yy_switch_to_buffer(yy_scan_string([[[self string] substringWithRange:textRange] UTF8String])); //now loop through all the tokens while (token=yylex()){ @@ -1845,8 +1947,11 @@ SYNTAX HIGHLIGHTING! case SPT_VARIABLE: tokenColor = variableColor; break; - default: + case SPT_WHITESPACE: tokenColor = nil; + break; + default: + tokenColor = textColor; } if (!tokenColor) continue; @@ -1859,19 +1964,20 @@ SYNTAX HIGHLIGHTING! if (!tokenRange.length) continue; // If the current token is marked as SQL keyword, uppercase it if required. - unsigned long tokenEnd = tokenRange.location+tokenRange.length-1; + tokenEnd = tokenRange.location+tokenRange.length-1; // Check the end of the token - if (autouppercaseKeywordsEnabled && !delBackwardsWasPressed + if (autouppercaseKeywords && !delBackwardsWasPressed && [[self textStorage] attribute:kSQLkeyword atIndex:tokenEnd effectiveRange:nil]) // check if next char is not a kSQLkeyword or current kSQLkeyword is at the end; // if so then upper case keyword if not already done // @try catch() for catching valid index esp. after deleteBackward: { + NSString* curTokenString = [[self string] substringWithRange:tokenRange]; BOOL doIt = NO; @try { - doIt = ![[self textStorage] attribute:kSQLkeyword atIndex:tokenEnd+1 effectiveRange:nil]; + doIt = ![[self textStorage] attribute:kSQLkeyword atIndex:tokenEnd+1 effectiveRange:nil]; } @catch(id ae) { doIt = YES; } if(doIt && ![[curTokenString uppercaseString] isEqualToString:curTokenString]) @@ -1889,10 +1995,11 @@ SYNTAX HIGHLIGHTING! // Add an attribute to be used in the auto-pairing (keyDown:) // to disable auto-pairing if caret is inside of any token found by lex. // For discussion: maybe change it later (only for quotes not keywords?) - [textStore addAttribute: kWQquoted - value: kValue + if(token < 6) + [textStore addAttribute: kLEXToken + value: kLEXTokenValue range: tokenRange ]; - + // Mark each SQL keyword for auto-uppercasing and do it for the next textStorageDidProcessEditing: event. // Performing it one token later allows words which start as reserved keywords to be entered. @@ -1906,34 +2013,44 @@ SYNTAX HIGHLIGHTING! [textStore addAttribute: kQuote value: kQuoteValue range: tokenRange ]; + //distinguish backtick quoted word for completion else if(token == SPT_BACKTICK_QUOTED_TEXT) [textStore addAttribute: kBTQuote value: kBTQuoteValue range: tokenRange ]; + } } /* - * Performs syntax highlighting. - * This method recolors the entire text on every keypress. For performance reasons, this function does - * nothing if the text is more than 20 KB. - * - * The main bottleneck is the [NSTextStorage addAttribute:value:range:] method - the parsing itself is really fast! - * - * Some sample code from Andrew Choi ( http://members.shaw.ca/akochoi-old/blog/2003/11-09/index.html#3 ) has been reused. + * Performs syntax highlighting after a text change. */ - (void)textStorageDidProcessEditing:(NSNotification *)notification { - NSTextStorage *textStore = [notification object]; //make sure that the notification is from the correct textStorage object if (textStore!=[self textStorage]) return; - [self doSyntaxHighlighting:textStore]; - + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(doSyntaxHighlighting) + object:nil]; + + [self doSyntaxHighlighting]; + +} + +/* + * Show only setable modes in the font panel + */ +- (unsigned int)validModesForFontPanel:(NSFontPanel *)fontPanel +{ + return (NSFontPanelFaceModeMask | NSFontPanelSizeModeMask); } + + @end + |