diff options
-rw-r--r-- | Source/CMTextView.m | 112 | ||||
-rw-r--r-- | Source/SPEditorTokens.l | 20 | ||||
-rw-r--r-- | Source/SPQueryController.m | 3 | ||||
-rw-r--r-- | Source/SPQueryFavoriteManager.h | 3 |
4 files changed, 90 insertions, 48 deletions
diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 474b0af9..0caa12c0 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -31,6 +31,7 @@ #import "SPNarrowDownCompletion.h" #import "SPConstants.h" #import "SPQueryController.h" +#import "SPTooltip.h" #pragma mark - #pragma mark lex init @@ -115,10 +116,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) [[self textStorage] setDelegate:self]; // Set defaults for general usage - autoindentEnabled = YES; + autoindentEnabled = NO; autopairEnabled = YES; autoindentIgnoresEnter = NO; - autouppercaseKeywordsEnabled = YES; + autouppercaseKeywordsEnabled = NO; autohelpEnabled = NO; delBackwardsWasPressed = NO; startListeningToBoundChanges = NO; @@ -1112,6 +1113,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) - (void)insertFavoriteAsSnippet:(NSString*)theSnippet atRange:(NSRange)targetRange { + // Do not allow the insertion of a query favorite if snippets are active if(snippetControlCounter > -1) { NSBeep(); return; @@ -1130,7 +1132,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) NSMutableString *snip = [[NSMutableString alloc] initWithCapacity:[theSnippet length]]; @try{ - NSString *re = @"(?<!\\\\)\\$\\{(1?\\d):([^\\{\\}]*)\\}"; + NSString *re = @"(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|.*?[^\\\\])\\}"; if(targetRange.length) targetRange = NSIntersectionRange(NSMakeRange(0,[[self string] length]), targetRange); @@ -1139,16 +1141,20 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) if(snip == nil || ![snip length]) return; // Replace `${x:…}` by ${x:`…`} for convience - [snip replaceOccurrencesOfRegex:@"`\\$\\{(1?\\d):([^\\{\\}]*)\\}`" withString:@"${$1:`$2`}"]; + [snip replaceOccurrencesOfRegex:@"`(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|.*?[^\\\\])\\}`" withString:@"${$1:`$2`}"]; [snip flushCachedRegexData]; snippetControlCounter = -1; snippetControlMax = -1; currentSnippetIndex = -1; + // Suppress snippet range calculation in [self textStorageDidProcessEditing] while initial insertion + snippetWasJustInserted = YES; + while([snip isMatchedByRegex:re]) { [snip flushCachedRegexData]; snippetControlCounter++; + NSRange snipRange = [snip rangeOfRegex:re capture:0L]; NSInteger snipCnt = [[snip substringWithRange:[snip rangeOfRegex:re capture:1L]] intValue]; NSRange hintRange = [snip rangeOfRegex:re capture:2L]; @@ -1199,16 +1205,23 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } } + // Handle escaped characters + [theHintString replaceOccurrencesOfRegex:@"\\\\(\\$\\(|\\}|\\$SP_)" withString:@"$1"]; + [theHintString flushCachedRegexData]; + // If inside the snippet hint $(…) is defined run … as BASH command - // and replace the snippet hint by the return string of that command - if([theHintString isMatchedByRegex:@"(?s)(?<!\\\\)\\s*\\$\\(.*\\)\\s*"]) { - NSRange r = [theHintString rangeOfRegex:@"(?s)(?<!\\\\)\\s*\\$\\((.*)\\)\\s*" capture:1L]; - if(r.length) - [theHintString setString:[self runBashCommand:[theHintString substringWithRange:r]]]; - else - [theHintString setString:@""]; + // and replace $(…) by the return string of that command. Please note + // only one $(…) statement is allowed within one ${…} snippet environment. + NSRange tagRange = [theHintString rangeOfRegex:@"(?s)(?<!\\\\)\\$\\((.*)\\)"]; + if(tagRange.length) { [theHintString flushCachedRegexData]; + NSRange cmdRange = [theHintString rangeOfRegex:@"(?s)(?<!\\\\)\\$\\(\\s*(.*)\\s*\\)" capture:1L]; + if(cmdRange.length) + [theHintString replaceCharactersInRange:tagRange withString:[self runBashCommand:[theHintString substringWithRange:cmdRange]]]; + else + [theHintString replaceCharactersInRange:tagRange withString:@""]; } + [theHintString flushCachedRegexData]; [snip replaceCharactersInRange:snipRange withString:theHintString]; [snip flushCachedRegexData]; @@ -1235,16 +1248,22 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) snippetControlArray[snippetControlMax][2] = 0; } - // Registering for undo - [self breakUndoCoalescing]; - - // Insert favorite query as snippet if any - // if(targetRange.length) - [self setSelectedRange:targetRange]; + // unescape escaped snippets and re-adjust successive snippet locations : \${1:a} → ${1:a} + NSString *ure = @"(?s)\\\\\\$\\{(1?\\d):(.{0}|.*?[^\\\\])\\}"; + while([snip isMatchedByRegex:ure]) { + NSRange escapeRange = [snip rangeOfRegex:ure capture:0L]; + [snip replaceCharactersInRange:escapeRange withString:[snip substringWithRange:NSMakeRange(escapeRange.location+1,escapeRange.length-1)]]; + NSUInteger loc = escapeRange.location + targetRange.location; + [snip flushCachedRegexData]; + for(i=0; i<=snippetControlMax; i++) + if(snippetControlArray[i][0] > -1 && snippetControlArray[i][0] > loc) + snippetControlArray[i][0]--; + } - // Suppress snippet range calculation in [self textStorageDidProcessEditing] while initial insertion - snippetWasJustInserted = YES; + // Insert favorite query by selecting the tab trigger if any + [self setSelectedRange:targetRange]; + // Registering for undo [self breakUndoCoalescing]; [self insertText:snip]; @@ -1271,32 +1290,34 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } /* - * Run 'command' as BASH script and return the result. + * Run 'command' as BASH command(s) and return the result. * This task can be interrupted by pressing ⌘. */ - (NSString *)runBashCommand:(NSString *)command { - BOOL userTerminated = NO; NSTask *bashTask = [[NSTask alloc] init]; - [bashTask setLaunchPath: @"/bin/sh"]; + [bashTask setLaunchPath: @"/bin/bash"]; [bashTask setArguments:[NSArray arrayWithObjects: @"-c", command, nil]]; NSPipe *stdout_pipe = [NSPipe pipe]; [bashTask setStandardOutput:stdout_pipe]; - NSFileHandle *stdout_file = [stdout_pipe fileHandleForReading]; + NSPipe *stderr_pipe = [NSPipe pipe]; + [bashTask setStandardError:stderr_pipe]; + NSFileHandle *stderr_file = [stderr_pipe fileHandleForReading]; [bashTask launch]; // Listen to ⌘. to terminate while(1) { - if(![bashTask isRunning]) break; - NSEvent* event = [NSApp nextEventMatchingMask:NSKeyDownMask - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; + if(![bashTask isRunning] || [bashTask processIdentifier] == 0) break; + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + usleep(10000); if(!event) continue; if ([event type] == NSKeyDown) { unichar key = [[event characters] length] == 1 ? [[event characters] characterAtIndex:0] : 0; @@ -1305,9 +1326,13 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) userTerminated = YES; break; } + } else { + [NSApp sendEvent:event]; } } + [bashTask waitUntilExit]; + if(userTerminated) { if(bashTask) [bashTask release]; NSBeep(); @@ -1315,25 +1340,33 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) return @""; } - [bashTask waitUntilExit]; + // If return from bash re-activate Sequel Pro + [NSApp activateIgnoringOtherApps:YES]; + NSInteger status = [bashTask terminationStatus]; - NSData *data = [stdout_file readDataToEndOfFile]; + NSData *outdata = [stdout_file readDataToEndOfFile]; + NSData *errdata = [stderr_file readDataToEndOfFile]; - if(data != nil) { - NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if(outdata != nil) { + NSString *stdout = [[[NSString alloc] initWithData:outdata encoding:NSUTF8StringEncoding] description]; + NSString *error = [[[NSString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] description]; if(bashTask) [bashTask release]; - if(string != nil) { + if(stdout != nil) { if (status == 0) { - return [string autorelease]; + return [stdout autorelease]; } else { - NSLog(@"Error for “%@”", command); - [string release]; + NSString *error = [[[NSString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] description]; + SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, nil, + [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), command, [error description]]); + [stdout release]; + [error release]; NSBeep(); return @""; } } else { - if(string) [string release]; + if(stdout) [stdout release]; NSLog(@"Couldn't read return string from “%@” by using UTF-8 encoding.", command); + NSBeep(); } } else { if(bashTask) [bashTask release]; @@ -1343,6 +1376,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) } } + /* * Checks whether the current caret position in inside of a defined snippet range */ @@ -2960,8 +2994,6 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse) NSColor *tokenColor; - BOOL autouppercaseKeywords = [prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]; - size_t tokenEnd, token; NSRange tokenRange; @@ -3022,7 +3054,7 @@ 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 (textBufferSizeIncreased && allowToCheckForUpperCase && autouppercaseKeywords && !delBackwardsWasPressed + if (textBufferSizeIncreased && allowToCheckForUpperCase && autouppercaseKeywordsEnabled && !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 diff --git a/Source/SPEditorTokens.l b/Source/SPEditorTokens.l index b3daa531..b0c2dee3 100644 --- a/Source/SPEditorTokens.l +++ b/Source/SPEditorTokens.l @@ -61,24 +61,32 @@ keywords (X(OR|509|A)|S(MALLINT|SL|H(OW({s}(E(NGINE(S)?|RRORS)|M(ASTER|UTEX)|BIN %x comment +%x snippet %x equation %x varequation %% + + +\$\{1?[0-9]: { BEGIN(snippet); return SPT_OTHER; } +<snippet>\\\} { return SPT_OTHER; } +<snippet>\} { BEGIN(INITIAL); return SPT_OTHER; } + + \"([^"\\]|\\(.|[\n\r]))*\"? { return SPT_DOUBLE_QUOTED_TEXT; } /* double quoted strings */ '([^'\\]|\\(.|[\n\r]))*'? { return SPT_SINGLE_QUOTED_TEXT; } /* single quoted strings */ -`[^`]*`? { return SPT_BACKTICK_QUOTED_TEXT; } /* backtick quoted string */ +`[^`]*`? { return SPT_BACKTICK_QUOTED_TEXT; } /* backtick quoted string */ -"/*" { BEGIN(comment); return SPT_COMMENT; } /* beginning of a c style comment */ -<comment>[^*]* { return SPT_COMMENT; } /* anything except * in a c cmnt */ -<comment>"*"+ { return SPT_COMMENT; } /* a range of * */ -<comment>"*"+"/" { BEGIN(INITIAL); return SPT_COMMENT; } /* a range of * with trailing / +"/*" { BEGIN(comment); return SPT_COMMENT; } /* beginning of a c style comment */ +<comment>[^*]* { return SPT_COMMENT; } /* anything except * in a c cmnt */ +<comment>"*"+ { return SPT_COMMENT; } /* a range of * */ +<comment>"*"+"/" { BEGIN(INITIAL); return SPT_COMMENT; } /* a range of * with trailing / Thanks to John Dickinson for publishing this method of parsing C comments on http://www.stillhq.com/pdfdb/000561/data.pdf */ #[^\n\r]*(\n|\r)? | /* # Comments */ ---[ \t][^\n\r]*(\n|\r)? { return SPT_COMMENT; } /* -- Comments */ +--[ \t][^\n\r]*(\n|\r)? { return SPT_COMMENT; } /* -- Comments */ {variable}/{ops} { BEGIN(varequation); return SPT_VARIABLE; }/* SQL variables before operator*/ <varequation>{ops} { BEGIN(INITIAL); return SPT_OTHER; } diff --git a/Source/SPQueryController.m b/Source/SPQueryController.m index 2eddf018..e19a49e5 100644 --- a/Source/SPQueryController.m +++ b/Source/SPQueryController.m @@ -27,6 +27,7 @@ #import "SPConsoleMessage.h" #import "SPArrayAdditions.h" #import "SPConstants.h" +#import "CustomQuery.h" #define MESSAGE_TRUNCATE_CHARACTER_LENGTH 256 #define MESSAGE_TIME_STAMP_FORMAT @"%H:%M:%S" @@ -655,7 +656,7 @@ static SPQueryController *sharedQueryController = nil; if([historyContainer objectForKey:[fileURL absoluteString]]) { NSMutableArray *returnArray = [NSMutableArray arrayWithCapacity:[[historyContainer objectForKey:[fileURL absoluteString]] count]]; NSMenuItem *historyMenuItem; - for(id history in [historyContainer objectForKey:[fileURL absoluteString]]) { + for(NSString* history in [historyContainer objectForKey:[fileURL absoluteString]]) { historyMenuItem = [[[NSMenuItem alloc] initWithTitle:([history length] > 64) ? [NSString stringWithFormat:@"%@…", [history substringToIndex:63]] : history action:NULL keyEquivalent:@""] autorelease]; diff --git a/Source/SPQueryFavoriteManager.h b/Source/SPQueryFavoriteManager.h index 2a9c3c56..9e207356 100644 --- a/Source/SPQueryFavoriteManager.h +++ b/Source/SPQueryFavoriteManager.h @@ -24,6 +24,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import <Cocoa/Cocoa.h> +#import "CMTextView.h" @class BWAnchoredButtonBar; @@ -42,7 +43,7 @@ IBOutlet NSTableView *favoritesTableView; IBOutlet NSTextField *favoriteNameTextField; IBOutlet NSTextField *favoriteTabTriggerTextField; - IBOutlet NSTextView *favoriteQueryTextView; + IBOutlet CMTextView *favoriteQueryTextView; IBOutlet NSButton *removeButton; IBOutlet BWAnchoredButtonBar *splitViewButtonBar; |