From ab560b63b13d9e74e3f1082287dcefe04322cdba Mon Sep 17 00:00:00 2001 From: Bibiko Date: Mon, 1 Feb 2010 23:07:24 +0000 Subject: =?UTF-8?q?=E2=80=A2=20sped=20up=20Query=20Editor=20for=20larger?= =?UTF-8?q?=20text=20-=20rewrote=20[NoodleLineNumber=20requiredThickness]?= =?UTF-8?q?=20completely=20to=20avoid=20stack=20overflow=20for=20larger=20?= =?UTF-8?q?text=20due=20to=20[NSMutableString=20string]=20plus=20appendStr?= =?UTF-8?q?ing=20all=20the=20time=20without=20releasing=20it=20in=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/CMTextView.h | 8 ++ Source/CMTextView.m | 45 ++++++----- Source/NoodleLineNumberView.m | 179 +++++++++++++++++++++--------------------- 3 files changed, 123 insertions(+), 109 deletions(-) (limited to 'Source') diff --git a/Source/CMTextView.h b/Source/CMTextView.h index ea0b36cc..a7c0ccea 100644 --- a/Source/CMTextView.h +++ b/Source/CMTextView.h @@ -38,6 +38,13 @@ static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAtt SPMutableAttributedStringAddAttributeValueRange(self, @selector(addAttribute:value:range:), aStr, aValue, aRange); return; } +static inline id NSMutableAttributedStringAttributeAtIndex (NSMutableAttributedString* self, NSString* aStr, NSUInteger index, NSRangePointer range) { + typedef id (*SPMutableAttributedStringAttributeAtIndexMethodPtr)(NSMutableAttributedString*, SEL, NSString*, NSUInteger, NSRangePointer); + static SPMutableAttributedStringAttributeAtIndexMethodPtr SPMutableAttributedStringAttributeAtIndex; + if (!SPMutableAttributedStringAttributeAtIndex) SPMutableAttributedStringAttributeAtIndex = (SPMutableAttributedStringAttributeAtIndexMethodPtr)[self methodForSelector:@selector(attribute:atIndex:effectiveRange:)]; + id r = SPMutableAttributedStringAttributeAtIndex(self, @selector(attribute:atIndex:effectiveRange:), aStr, index, range); + return r; +} @interface CMTextView : NSTextView { BOOL autoindentEnabled; @@ -49,6 +56,7 @@ static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAtt NoodleLineNumberView *lineNumberView; BOOL startListeningToBoundChanges; + BOOL textBufferSizeIncreased; NSString *showMySQLHelpFor; diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 5ba0f3e9..f7b1663f 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -52,10 +52,10 @@ YY_BUFFER_STATE yy_scan_string (const 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 kSQLkeyword @"s" // attribute for found SQL keywords #define kQuote @"Quote" #define kQuoteValue @"isQuoted" -#define kValue @"dummy" +#define kValue @"x" #define kBTQuote @"BTQuote" #define kBTQuoteValue @"isBTQuoted" @@ -117,6 +117,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) autohelpEnabled = NO; delBackwardsWasPressed = NO; startListeningToBoundChanges = NO; + textBufferSizeIncreased = NO; snippetControlCounter = -1; lineNumberView = [[NoodleLineNumberView alloc] initWithScrollView:scrollView]; @@ -791,7 +792,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) NSRange r = NSMakeRange(0, [[self string] length]); // Remove all colors before printing for large text buffer - if(r.length > SP_SYNTAX_HILITE_BIAS) { + if(r.length > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) { // Cancel all doSyntaxHighlighting requests [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doSyntaxHighlighting) @@ -2882,55 +2883,54 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) // If the current token is marked as SQL keyword, uppercase it if required. tokenEnd = tokenRange.location+tokenRange.length-1; // Check the end of the token - if (allowToCheckForUpperCase && autouppercaseKeywords && !delBackwardsWasPressed - && [[textStore attribute:kSQLkeyword atIndex:tokenEnd effectiveRange:nil] isEqualToString:kValue]) + if (textBufferSizeIncreased && allowToCheckForUpperCase && autouppercaseKeywords && !delBackwardsWasPressed + && [(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword, tokenEnd, nil) length]) // 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 = [selfstr substringWithRange:tokenRange]; - NSString* upperCaseCurTokenString = [curTokenString uppercaseString]; BOOL doIt = NO; @try { - doIt = ![[textStore attribute:kSQLkeyword atIndex:tokenEnd+1 effectiveRange:nil] isEqualToString:kValue]; + doIt = ![(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword,tokenEnd+1,nil) length]; } @catch(id ae) { doIt = NO; } - - if(doIt && ![upperCaseCurTokenString isEqualToString:curTokenString]) + + if(doIt) { // Register it for undo works only partly for now, at least the uppercased keyword will be selected [self shouldChangeTextInRange:tokenRange replacementString:curTokenString]; - [self replaceCharactersInRange:tokenRange withString:upperCaseCurTokenString]; + [self replaceCharactersInRange:tokenRange withString:[curTokenString uppercaseString]]; } } - + NSMutableAttributedStringAddAttributeValueRange(textStore, NSForegroundColorAttributeName, tokenColor, tokenRange); - + if(!allowToCheckForUpperCase) continue; - + // 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?) if(token < 6) NSMutableAttributedStringAddAttributeValueRange(textStore, kLEXToken, kLEXTokenValue, 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. if(token == SPT_RESERVED_WORD) NSMutableAttributedStringAddAttributeValueRange(textStore, kSQLkeyword, kValue, tokenRange); - + // Add an attribute to be used to distinguish quotes from keywords etc. // used e.g. in completion suggestions else if(token < 4) NSMutableAttributedStringAddAttributeValueRange(textStore, kQuote, kQuoteValue, tokenRange); - + //distinguish backtick quoted word for completion else if(token == SPT_BACKTICK_QUOTED_TEXT) NSMutableAttributedStringAddAttributeValueRange(textStore, kBTQuote, kBTQuoteValue, tokenRange); } - + } - (void)drawRect:(NSRect)rect { @@ -3236,9 +3236,16 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } } } + if([[self textStorage] changeInLength] > 0) + textBufferSizeIncreased = YES; + else + textBufferSizeIncreased = NO; - [self doSyntaxHighlighting]; + if([[self textStorage] changeInLength] < SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) + [self doSyntaxHighlighting]; + } else { + textBufferSizeIncreased = NO; } startListeningToBoundChanges = YES; diff --git a/Source/NoodleLineNumberView.m b/Source/NoodleLineNumberView.m index 1ba6e2f1..9be65981 100644 --- a/Source/NoodleLineNumberView.m +++ b/Source/NoodleLineNumberView.m @@ -50,13 +50,13 @@ - (id)initWithScrollView:(NSScrollView *)aScrollView { - if ((self = [super initWithScrollView:aScrollView orientation:NSVerticalRuler]) != nil) - { - [self setClientView:[aScrollView documentView]]; + if ((self = [super initWithScrollView:aScrollView orientation:NSVerticalRuler]) != nil) + { + [self setClientView:[aScrollView documentView]]; lineIndices = nil; } - - return self; + + return self; } - (void)awakeFromNib @@ -66,21 +66,21 @@ - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (lineIndices) [lineIndices release]; - [font release]; - - [super dealloc]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (lineIndices) [lineIndices release]; + [font release]; + + [super dealloc]; } - (void)setFont:(NSFont *)aFont { - if (font != aFont) - { - [font autorelease]; + if (font != aFont) + { + [font autorelease]; font = [aFont retain]; - } + } } - (NSFont *)font @@ -144,21 +144,21 @@ - (void)setClientView:(NSView *)aView { - id oldClientView; - + id oldClientView; + oldClientView = [self clientView]; - - if ((oldClientView != aView) && [oldClientView isKindOfClass:[NSTextView class]]) - { + + if ((oldClientView != aView) && [oldClientView isKindOfClass:[NSTextView class]]) + { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)oldClientView textStorage]]; - } - [super setClientView:aView]; - if ((aView != nil) && [aView isKindOfClass:[NSTextView class]]) - { + } + [super setClientView:aView]; + if ((aView != nil) && [aView isKindOfClass:[NSTextView class]]) + { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextStorageDidProcessEditingNotification object:[(NSTextView *)aView textStorage]]; [self invalidateLineIndices]; - } + } } - (NSMutableArray *)lineIndices @@ -179,8 +179,8 @@ { // Invalidate the line indices. They will be recalculated and recached on demand. [self invalidateLineIndices]; - - [self setNeedsDisplay:YES]; + + [self setNeedsDisplay:YES]; } - (NSUInteger)lineNumberForLocation:(CGFloat)location @@ -231,60 +231,60 @@ - (void)calculateLines { - id view; + id view; - view = [self clientView]; - - if ([view isKindOfClass:[NSTextView class]]) - { - NSUInteger index, numberOfLines, stringLength, lineEnd, contentEnd; - NSString *text; - CGFloat oldThickness, newThickness; - - text = [view string]; - stringLength = [text length]; - // Switch off line numbering if text larger than 6MB - // for performance reasons. - // TODO improve performance maybe via threading - if(stringLength>6000000) - return; - if (lineIndices) [lineIndices release]; - lineIndices = [[NSMutableArray alloc] init]; - - index = 0; - numberOfLines = 0; - - do - { - [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]]; - - index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]); - numberOfLines++; - } - while (index < stringLength); + view = [self clientView]; - // Check if text ends with a new line. - [text getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange([[lineIndices lastObject] unsignedIntegerValue], 0)]; - if (contentEnd < lineEnd) - { - [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]]; - } + if ([view isKindOfClass:[NSTextView class]]) + { + NSUInteger index, numberOfLines, stringLength, lineEnd, contentEnd; + NSString *text; + CGFloat oldThickness, newThickness; + + text = [view string]; + stringLength = [text length]; + // Switch off line numbering if text larger than 6MB + // for performance reasons. + // TODO improve performance maybe via threading + if(stringLength>6000000) + return; + if (lineIndices) [lineIndices release]; + lineIndices = [[NSMutableArray alloc] init]; + + index = 0; + numberOfLines = 0; + + do + { + [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]]; - oldThickness = [self ruleThickness]; - newThickness = [self requiredThickness]; - if (fabs(oldThickness - newThickness) > 1) - { + index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]); + numberOfLines++; + } + while (index < stringLength); + + // Check if text ends with a new line. + [text getLineStart:NULL end:&lineEnd contentsEnd:&contentEnd forRange:NSMakeRange([[lineIndices lastObject] unsignedIntegerValue], 0)]; + if (contentEnd < lineEnd) + { + [lineIndices addObject:[NSNumber numberWithUnsignedInteger:index]]; + } + + oldThickness = [self ruleThickness]; + newThickness = [self requiredThickness]; + if (fabs(oldThickness - newThickness) > 1) + { NSInvocation *invocation; - + // Not a good idea to resize the view during calculations (which can happen during // display). Do a delayed perform (using NSInvocation since arg is a float). invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(setRuleThickness:)]]; [invocation setSelector:@selector(setRuleThickness:)]; [invocation setTarget:self]; [invocation setArgument:&newThickness atIndex:2]; - + [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0.0]; - } + } } } @@ -330,26 +330,25 @@ - (CGFloat)requiredThickness { - NSUInteger lineCount, digits, i; - NSMutableString *sampleString; - NSSize stringSize; - - lineCount = [[self lineIndices] count]; - digits = (NSUInteger)log10(lineCount) + 1; - sampleString = [NSMutableString string]; - for (i = 0; i < digits; i++) - { - // Use "8" since it is one of the fatter numbers. Anything but "1" - // will probably be ok here. I could be pedantic and actually find the fattest - // number for the current font but nah. - [sampleString appendString:@"8"]; - } - - stringSize = [sampleString sizeWithAttributes:[self textAttributes]]; - - // Round up the value. There is a bug on 10.4 where the display gets all wonky when scrolling if you don't - // return an integral value here. - return ceil(MAX(DEFAULT_THICKNESS, stringSize.width + RULER_MARGIN * 2)); + NSUInteger lineCount = [[self lineIndices] count]; + if(lineCount < 10) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 100) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 1000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 10000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 100000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 1000000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 10000000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"8888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else if(lineCount < 100000000) + return ceil(MAX(DEFAULT_THICKNESS, [[NSString stringWithString:@"88888888"] sizeWithAttributes:[self textAttributes]].width + RULER_MARGIN * 2)); + else + return 100; } - (void)drawHashMarksAndLabelsInRect:(NSRect)aRect -- cgit v1.2.3