diff options
-rw-r--r-- | Source/CustomQuery.h | 2 | ||||
-rw-r--r-- | Source/CustomQuery.m | 312 |
2 files changed, 100 insertions, 214 deletions
diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h index 9c4e1a1e..68fdbf49 100644 --- a/Source/CustomQuery.h +++ b/Source/CustomQuery.h @@ -117,7 +117,7 @@ // Query actions - (void)performQueries:(NSArray *)queries; - (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind; -- (NSRange)queryTextRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind; +- (NSRange)queryRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind; - (NSRange)queryTextRangeForQuery:(int)anIndex startPosition:(long)position; // Accessors diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index 49b2f64a..cbb9a38c 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -515,8 +515,7 @@ // set the error text [errorText setStringValue:errors]; // select the line x of the first error if error message contains "at line x" - NSError *err1 = NULL; - NSRange errorLineNumberRange = [errors rangeOfRegex:@"([0-9]+)$" options:RKLNoOptions inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; + NSRange errorLineNumberRange = [errors rangeOfRegex:@"([0-9]+)$" options:RKLNoOptions inRange:NSMakeRange(0, [errors length]) capture:1 error:nil]; if(errorLineNumberRange.length) // if a line number was found { // Get the line number @@ -524,7 +523,7 @@ [textView selectLineNumber:errorAtLine ignoreLeadingNewLines:YES]; // Check for near message - NSRange errorNearMessageRange = [errors rangeOfRegex:@" '(.*?)' " options:(RKLMultiline|RKLDotAll) inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1]; + NSRange errorNearMessageRange = [errors rangeOfRegex:@" '(.*?)' " options:(RKLMultiline|RKLDotAll) inRange:NSMakeRange(0, [errors length]) capture:1 error:nil]; if(errorNearMessageRange.length) // if a "near message" was found { // Build the range to search for nearMessage (beginning from queryStartPosition to try to avoid mismatching) @@ -543,7 +542,7 @@ if(firstErrorOccuredInQuery == -1) // for current or previous query { BOOL isLookBehind = YES; - queryRange = [self queryTextRangeAtPosition:[textView selectedRange].location lookBehind:&isLookBehind]; + queryRange = [self queryRangeAtPosition:[textView selectedRange].location lookBehind:&isLookBehind]; [textView setSelectedRange:queryRange]; } else { // select the query for which the first error was detected @@ -646,144 +645,14 @@ * Retrieve the range of the query at a position specified * within the custom query text view. */ -- (NSRange)queryTextRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind -{ - SPSQLParser *customQueryParser; - NSArray *queries; - NSString *query = nil; - int i, j, lastQueryStartPosition, queryPosition = 0; - - // If the supplied position is negative or beyond the end of the string, return nil. - if (position < 0 || position > [[textView string] length]) - return NSMakeRange(NSNotFound,0); - - // Split the current text into queries - customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]]; - queries = [[NSArray alloc] initWithArray:[customQueryParser splitStringByCharacter:';']]; - [customQueryParser release]; - - // Walk along the array of queries to identify the current query - taking into account - // the extra semicolon at the end of each query - for (i = 0; i < [queries count]; i++ ) { - lastQueryStartPosition = queryStartPosition; - queryStartPosition = queryPosition; - queryPosition += [[queries objectAtIndex:i] length]; - if (queryPosition >= position) { - - // If lookbehind is enabled, check whether the current position could be considered to - // be within the previous query. A position just after a semicolon is always considered - // to be within the previous query; otherwise, if there is only whitespace *and newlines* - // before the next character, also consider the position to belong to the previous query. - if (*doLookBehind) { - BOOL positionAssociatedWithPreviousQuery = NO; - NSCharacterSet *newlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - - // If the caret is at the very start of the string, always associate - if (position == queryStartPosition) positionAssociatedWithPreviousQuery = YES; - - // Otherwise associate if only whitespace since previous, and a newline before next. - if (!positionAssociatedWithPreviousQuery) { - NSString *stringToPrevious = [[textView string] substringWithRange:NSMakeRange(queryStartPosition, position - queryStartPosition)]; - NSString *stringToEnd = [[textView string] substringWithRange:NSMakeRange(position, queryPosition - position)]; - NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet]; - if (![[stringToPrevious stringByTrimmingCharactersInSet:newlineSet] length]) { - for (j = 0; j < [stringToEnd length]; j++) { - if ([whitespaceSet characterIsMember:[stringToEnd characterAtIndex:j]]) continue; - if ([newlineSet characterIsMember:[stringToEnd characterAtIndex:j]]) { - positionAssociatedWithPreviousQuery = YES; - } - break; - } - } - } - - // If there is a previous query and the position should and can be associated with it, do so. - if (i && positionAssociatedWithPreviousQuery && [[[queries objectAtIndex:i-1] stringByTrimmingCharactersInSet:newlineSet] length]) { - query = [NSString stringWithString:[queries objectAtIndex:i-1]]; - queryStartPosition = lastQueryStartPosition; - break; - } - - // Lookbehind failed - set the pointer to NO so the parent knows. - *doLookBehind = NO; - } - - query = [NSString stringWithString:[queries objectAtIndex:i]]; - break; - } - queryPosition++; - } - - // For lookbehinds catch position at the very end of a string ending in a semicolon - if (*doLookBehind && position == [[textView string] length] && !query) - { - query = [queries lastObject]; - } - - if(queryStartPosition < 0) queryStartPosition = 0; - - [queries release]; - - // Remove all leading white spaces - NSError *err; - int offset = [query rangeOfRegex:@"^(\\s*)" options:RKLNoOptions inRange:NSMakeRange(0, [query length]) capture:1 error:&err].length; - - return NSMakeRange(queryStartPosition+offset, [query length]-offset); -} - -/* - * Retrieve the range of the query for the passed index seen from a start position - * specified within the custom query text view. - */ -- (NSRange)queryTextRangeForQuery:(int)anIndex startPosition:(long)position -{ - SPSQLParser *customQueryParser; - NSArray *queries; - - // If the supplied position is negative or beyond the end of the string, return nil. - if (position < 0 || position > [[textView string] length]) - return NSMakeRange(NSNotFound,0); - - // Split the current text into ranges of queries - customQueryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:NSMakeRange(position, [[textView string] length]-position)]]; - queries = [[NSArray alloc] initWithArray:[customQueryParser splitSqlStringIntoRangesByCharacter:';']]; - [customQueryParser release]; - - // Check for a valid index - anIndex--; - if(anIndex < 0 || anIndex >= [queries count]) - { - [queries release]; - return NSMakeRange(NSNotFound, 0); - } - - NSRange theQueryRange = [[queries objectAtIndex:anIndex] rangeValue]; - NSString *theQueryString = [[textView string] substringWithRange:theQueryRange]; - - [queries release]; - - // Remove all leading white spaces - int offset = [theQueryString rangeOfRegex:@"^(\\s*)"].length; - theQueryRange.location += offset; - theQueryRange.length -= offset; - return theQueryRange; -} - -/* - * Retrieve the query at a position specified within the custom query - * text view. This will return nil if the position specified is beyond - * the available string or if an empty query would be returned. - * If lookBehind is set, returns the *previous* query, but only if the - * caret should be associated with the previous query based on whitespace. - */ -- (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind +- (NSRange)queryRangeAtPosition:(long)position lookBehind:(BOOL *)doLookBehind { SPSQLParser *customQueryParser; NSArray *queries; NSString *query = nil; NSRange queryRange; - long i, j, lastQueryStartPosition, queryPosition = 0; + long i, j, queryPosition = 0; long queryCount; NSCharacterSet *whitespaceAndNewlineSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; @@ -791,9 +660,9 @@ // If the supplied position is negative or beyond the end of the string, return nil. if (position < 0 || position > [[textView string] length]) - return nil; + return NSMakeRange(NSNotFound, 0); - // Split the current text into queries and ranges + // Split the current text into ranges of queries // only if the textView was really changed, otherwise use the cache if([[textView textStorage] editedMask] != 0) { customQueryParser = [[SPSQLParser alloc] initWithString:[textView string]]; @@ -812,10 +681,9 @@ // the extra semicolon at the end of each query for (i = 0; i < queryCount; i++ ) { - lastQueryStartPosition = queryStartPosition; queryRange = [[queries objectAtIndex:i] rangeValue]; - queryStartPosition = queryRange.location; queryPosition = NSMaxRange(queryRange); + queryStartPosition = queryRange.location; if (queryPosition >= position) { @@ -851,7 +719,6 @@ // If there is a previous query and the position should be associated with it, do so. if (i && positionAssociatedWithPreviousQuery && [[[[textView string] substringWithRange:[[queries objectAtIndex:i-1] rangeValue]] stringByTrimmingCharactersInSet:whitespaceAndNewlineSet] length]) { - queryStartPosition = lastQueryStartPosition; queryRange = [[queries objectAtIndex:i-1] rangeValue]; break; } @@ -872,14 +739,9 @@ [queries release]; - // Remove all background color attributes - NSRange textRange = NSMakeRange(0,[[textView string] length]); - [[textView textStorage] removeAttribute:NSBackgroundColorAttributeName range:textRange]; - - queryRange = NSIntersectionRange(queryRange, textRange); + queryRange = NSIntersectionRange(queryRange, NSMakeRange(0, [[textView string] length])); if (!queryRange.length) { - currentQueryRange = NSMakeRange(0,0); - return nil; + return NSMakeRange(NSNotFound, 0); } query = [[textView string] substringWithRange:queryRange]; @@ -894,18 +756,66 @@ // Ensure the string isn't empty. // (We could also strip comments for this check, but that prevents use of conditional comments) if(queryRange.length < 1 || queryRange.length > [query length]) { - currentQueryRange = NSMakeRange(0,0); - return nil; + return NSMakeRange(NSNotFound, 0); } - [[textView textStorage] addAttribute: NSBackgroundColorAttributeName - value: [NSColor colorWithDeviceRed:0.95 green:0.95 blue:0.95 alpha:1] - range: queryRange ]; + // Return the located query range + return queryRange; +} + +/* + * Retrieve the range of the query for the passed index seen from a start position + * specified within the custom query text view. + */ +- (NSRange)queryTextRangeForQuery:(int)anIndex startPosition:(long)position +{ + SPSQLParser *customQueryParser; + NSArray *queries; + + // If the supplied position is negative or beyond the end of the string, return nil. + if (position < 0 || position > [[textView string] length]) + return NSMakeRange(NSNotFound,0); + + // Split the current text into ranges of queries + customQueryParser = [[SPSQLParser alloc] initWithString:[[textView string] substringWithRange:NSMakeRange(position, [[textView string] length]-position)]]; + queries = [[NSArray alloc] initWithArray:[customQueryParser splitSqlStringIntoRangesByCharacter:';']]; + [customQueryParser release]; + + // Check for a valid index + anIndex--; + if(anIndex < 0 || anIndex >= [queries count]) + { + [queries release]; + return NSMakeRange(NSNotFound, 0); + } + + NSRange theQueryRange = [[queries objectAtIndex:anIndex] rangeValue]; + NSString *theQueryString = [[textView string] substringWithRange:theQueryRange]; + + [queries release]; + + // Remove all leading white spaces + int offset = [theQueryString rangeOfRegex:@"^(\\s*)"].length; + theQueryRange.location += offset; + theQueryRange.length -= offset; + return theQueryRange; +} - currentQueryRange = queryRange; +/* + * Retrieve the query at a position specified within the custom query + * text view. This will return nil if the position specified is beyond + * the available string or if an empty query would be returned. + * If lookBehind is set, returns the *previous* query, but only if the + * caret should be associated with the previous query based on whitespace. + */ +- (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind +{ - // Return the located string - return query; + BOOL lookBehind = *doLookBehind; + NSRange queryRange = [self queryRangeAtPosition:position lookBehind:&lookBehind]; + *doLookBehind = lookBehind; + + return (queryRange.length) ? [[textView string] substringWithRange:queryRange] : nil; } @@ -1326,8 +1236,26 @@ // Ensure that the notification is from the custom query text view if ( [aNotification object] != textView ) return; + // Remove all background color attributes + [[textView textStorage] removeAttribute:NSBackgroundColorAttributeName range:NSMakeRange(0,[[textView string] length])]; + + BOOL isLookBehind = YES; + NSRange currentSelection = [textView selectedRange]; + long caretPosition = currentSelection.location; + NSRange qRange = [self queryRangeAtPosition:caretPosition lookBehind:&isLookBehind]; + + // Highlight by setting a background color the current query + if(qRange.length) { + [[textView textStorage] addAttribute: NSBackgroundColorAttributeName + value: [NSColor colorWithDeviceRed:0.95 green:0.95 blue:0.95 alpha:1] + range: qRange ]; + currentQueryRange = qRange; + } else { + currentQueryRange = NSMakeRange(0, 0); + } + // If no text is selected, disable the button and action menu. - if ( [textView selectedRange].location == NSNotFound ) { + if ( caretPosition == NSNotFound ) { [runSelectionButton setEnabled:NO]; [runSelectionMenuItem setEnabled:NO]; return; @@ -1335,64 +1263,22 @@ // If the current selection is a single caret position, update the button based on // whether the caret is inside a valid query. - if ([textView selectedRange].length == 0) { - int selectionPosition = [textView selectedRange].location; - // int movedRangeStart, movedRangeLength; - BOOL updateQueryButtons = TRUE; - // NSRange oldSelection; - // NSCharacterSet *whitespaceAndNewlineCharset = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - - // Retrieve the old selection position - // [[[aNotification userInfo] objectForKey:@"NSOldSelectedCharacterRange"] getValue:&oldSelection]; - - // Only process the query text if the selection previously had length, or moved more than 100 characters, - // or the intervening space contained a semicolon, or typing has been performed with no current query. - // This adds more checks to every keypress, but ensures the majority of the actions don't incur a - // parsing overhead - which is cheap on small text strings but heavy of large queries. - // movedRangeStart = (selectionPosition < oldSelection.location)?selectionPosition:oldSelection.location; - // movedRangeLength = abs(selectionPosition - oldSelection.location); - // updateQueryButtons = TRUE; - // if (oldSelection.length > 0) updateQueryButtons = TRUE; - // if (!updateQueryButtons && movedRangeLength > 100) updateQueryButtons = TRUE; - // if (!updateQueryButtons && oldSelection.location > [[textView string] length]) updateQueryButtons = TRUE; - // if (!updateQueryButtons && [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound) updateQueryButtons = TRUE; - // if (!updateQueryButtons && ![runSelectionButton isEnabled] && selectionPosition > oldSelection.location - // && [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:whitespaceAndNewlineCharset] length]) updateQueryButtons = TRUE; - // if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]) { - // int charPosition; - // unichar theChar; - // for (charPosition = selectionPosition; charPosition > 0; charPosition--) { - // theChar = [[textView string] characterAtIndex:charPosition-1]; - // if (theChar == ';') { - // updateQueryButtons = TRUE; - // break; - // } - // if (![whitespaceAndNewlineCharset characterIsMember:theChar]) break; - // } - // } - // if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]) { - // updateQueryButtons = TRUE; - // } - - if (updateQueryButtons) { - [runSelectionButton setTitle:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]; - [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Current Query", @"Title of action menu item to run current query in custom query view")]; - - // If a valid query is present at the cursor position, enable the button - BOOL isLookBehind = YES; - if ([self queryAtPosition:selectionPosition lookBehind:&isLookBehind]) { - if (isLookBehind) { - [runSelectionButton setTitle:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]; - [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Previous Query", @"Title of action menu item to run query just before text caret in custom query view")]; - } - [runSelectionButton setEnabled:YES]; - [runSelectionMenuItem setEnabled:YES]; - } else { - [runSelectionButton setEnabled:NO]; - [runSelectionMenuItem setEnabled:NO]; + if (!currentSelection.length) { + [runSelectionButton setTitle:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]; + [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Current Query", @"Title of action menu item to run current query in custom query view")]; + + // If a valid query is present at the cursor position, enable the button + if (qRange.length) { + if (isLookBehind) { + [runSelectionButton setTitle:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]; + [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Previous Query", @"Title of action menu item to run query just before text caret in custom query view")]; } + [runSelectionButton setEnabled:YES]; + [runSelectionMenuItem setEnabled:YES]; + } else { + [runSelectionButton setEnabled:NO]; + [runSelectionMenuItem setEnabled:NO]; } - // For selection ranges, enable the button. } else { [runSelectionButton setTitle:NSLocalizedString(@"Run Selection", @"Title of button to run selected text in custom query view")]; |