diff options
Diffstat (limited to 'Source/SPDatabaseDocument.m')
-rw-r--r-- | Source/SPDatabaseDocument.m | 364 |
1 files changed, 338 insertions, 26 deletions
diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index 86588f88..096974ba 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -56,6 +56,19 @@ #import "SPDatabaseRename.h" #import "SPServerSupport.h" #import "SPTooltip.h" +#import "SPEditorTokens.h" + +#pragma mark lex init + +/* +* Include all the extern variables and prototypes required for flex (used for syntax highlighting) +*/ +extern NSUInteger yylex(); +extern NSUInteger yyuoffset, yyuleng; +typedef struct yy_buffer_state *YY_BUFFER_STATE; +void yy_switch_to_buffer(YY_BUFFER_STATE); +YY_BUFFER_STATE yy_scan_string (const char *); + @interface SPDatabaseDocument (PrivateAPI) @@ -4543,20 +4556,126 @@ #pragma mark - #pragma mark Scheme scripting methods +/** + * Return an HTML formatted string representing the passed SQL string syntax highlighted + */ +- (NSString*)doSQLSyntaxHighlightForString:(NSString*)sqlText cssLike:(BOOL)cssLike +{ + + NSMutableString *sqlHTML = [[[NSMutableString alloc] initWithCapacity:[sqlText length]] autorelease]; + + NSRange textRange = NSMakeRange(0, [sqlText length]); + NSString *tokenColor; + NSString *cssId; + size_t token; + NSRange tokenRange; + + // initialise flex + yyuoffset = 0; yyuleng = 0; + yy_switch_to_buffer(yy_scan_string([sqlText UTF8String])); + BOOL skipFontTag; + + while (token=yylex()){ + skipFontTag = NO; + switch (token) { + case SPT_SINGLE_QUOTED_TEXT: + case SPT_DOUBLE_QUOTED_TEXT: + tokenColor = @"#A7221C"; + cssId = @"sp_sql_quoted"; + break; + case SPT_BACKTICK_QUOTED_TEXT: + tokenColor = @"#001892"; + cssId = @"sp_sql_backtick"; + break; + case SPT_RESERVED_WORD: + tokenColor = @"#0041F6"; + cssId = @"sp_sql_keyword"; + break; + case SPT_NUMERIC: + tokenColor = @"#67350F"; + cssId = @"sp_sql_numeric"; + break; + case SPT_COMMENT: + tokenColor = @"#265C10"; + cssId = @"sp_sql_comment"; + break; + case SPT_VARIABLE: + tokenColor = @"#6C6C6C"; + cssId = @"sp_sql_variable"; + break; + case SPT_WHITESPACE: + skipFontTag = YES; + cssId = @""; + break; + default: + skipFontTag = YES; + cssId = @""; + } + + tokenRange = NSMakeRange(yyuoffset, yyuleng); + + if(skipFontTag) + [sqlHTML appendString:[[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + else { + if(cssLike) + [sqlHTML appendFormat:@"<span class=\"%@\">%@</span>", cssId, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + else + [sqlHTML appendFormat:@"<font color=%@>%@</font>", tokenColor, [[sqlText substringWithRange:tokenRange] HTMLEscapeString]]; + } + + } + + // Wrap lines, and replace tabs with spaces + [sqlHTML replaceOccurrencesOfString:@"\n" withString:@"<br>" options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + [sqlHTML replaceOccurrencesOfString:@"\t" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [sqlHTML length])]; + + if(sqlHTML) + return sqlHTML; + else + return @""; + +} + +/** + * Called by handleSchemeCommand: to break a while loop + */ +- (void)setTimeout +{ + _workingTimeout = YES; +} + +/** + * Process passed URL scheme command and wait (timeouted) for the document if it's busy or not yet connected + */ - (void)handleSchemeCommand:(NSDictionary*)commandDict { + if(!commandDict) return; + NSArray *params = [commandDict objectForKey:@"parameter"]; - if(![params count]) return; + if(![params count]) { + NSLog(@"No URL scheme command passed"); + NSBeep(); + return; + } NSString *command = [params objectAtIndex:0]; NSString *docProcessID = [self processID]; if(!docProcessID) docProcessID = @""; - // Authenticate command - if(![docProcessID isEqualToString:[commandDict objectForKey:@"id"]]) { - [SPTooltip showWithObject:NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated") atLocation:[NSApp mouseLocation]]; - return; + // Wait for self + _workingTimeout = NO; + // the following while loop waits maximal 5secs + [self performSelector:@selector(setTimeout) withObject:nil afterDelay:5.0]; + while (_isWorkingLevel || !_isConnected) { + if(_workingTimeout) break; + // Do not block self + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if(event) [NSApp sendEvent:event]; + } if([command isEqualToString:@"SelectDocumentView"]) { @@ -4599,6 +4718,74 @@ return; } + if([command isEqualToString:@"SelectDatabase"]) { + if([params count] > 1) { + NSString *dbName = [params objectAtIndex:1]; + NSString *tableName = nil; + if([dbName length]) { + if([params count] == 3) { + tableName = [params objectAtIndex:2]; + } + [self selectDatabase:dbName item:tableName]; + } + } + return; + } + + if([command isEqualToString:@"SyntaxHighlighting"]) { + + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir; + + NSString *queryFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, docProcessID]; + NSString *resultFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, docProcessID]; + NSString *metaFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, docProcessID]; + NSString *statusFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, docProcessID]; + + NSError *inError = nil; + NSString *query = [NSString stringWithContentsOfFile:queryFileName encoding:NSUTF8StringEncoding error:inError]; + NSString *result = @""; + NSString *status = @"0"; + + if([fm fileExistsAtPath:queryFileName isDirectory:&isDir] && !isDir) { + + if(inError == nil && query && [query length]) { + if([params count] > 1) { + if([[params lastObject] isEqualToString:@"html"]) + result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:NO]]; + else if([[params lastObject] isEqualToString:@"htmlcss"]) + result = [NSString stringWithString:[self doSQLSyntaxHighlightForString:query cssLike:YES]]; + } + } + } + + [fm removeItemAtPath:queryFileName error:nil]; + [fm removeItemAtPath:resultFileName error:nil]; + [fm removeItemAtPath:metaFileName error:nil]; + [fm removeItemAtPath:statusFileName error:nil]; + + if(![result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) + status = @"1"; + + // write status file as notification that query was finished + BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; + if(!succeed) { + NSBeep(); + SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + } + return; + } + + // ==== the following commands need an authentication for safety reasons + + // Authenticate command + if(![docProcessID isEqualToString:[commandDict objectForKey:@"id"]]) { + SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + NSLocalizedString(@"URL scheme command couldn't authenticated", @"URL scheme command couldn't authenticated")); + return; + } + if([command isEqualToString:@"SelectTableRows"]) { if([params count] > 1 && [[[NSApp mainWindow] firstResponder] respondsToSelector:@selector(selectTableRows:)]) { [[[NSApp mainWindow] firstResponder] selectTableRows:[params subarrayWithRange:NSMakeRange(1, [params count]-1)]]; @@ -4616,21 +4803,6 @@ return; } - if([command isEqualToString:@"SelectDatabase"]) { - if (_isWorkingLevel) return; - if([params count] > 1) { - NSString *dbName = [params objectAtIndex:1]; - NSString *tableName = nil; - if([dbName length]) { - if([params count] == 3) { - tableName = [params objectAtIndex:2]; - } - [self selectDatabase:dbName item:tableName]; - } - } - return; - } - if([command isEqualToString:@"ReloadContentTableWithWHEREClause"]) { NSString *queryFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, docProcessID]; NSFileManager *fm = [NSFileManager defaultManager]; @@ -4646,11 +4818,148 @@ return; } - if([command isEqualToString:@"ExecuteQuery"]) { - // Bail if document is busy - if (_isWorkingLevel) - [SPTooltip showWithObject:NSLocalizedString(@"Connection window is busy. URL scheme command bailed", @"Connection window is busy. URL scheme command bailed") atLocation:[NSApp mouseLocation]]; + if([command isEqualToString:@"CreateSyntaxForTables"]) { + + if([params count] > 1) { + + NSString *queryFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryInputPathHeader, docProcessID]; + NSString *resultFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultPathHeader, docProcessID]; + NSString *metaFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultMetaPathHeader, docProcessID]; + NSString *statusFileName = [NSString stringWithFormat:@"%@%@", SPURLSchemeQueryResultStatusPathHeader, docProcessID]; + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *status = @"0"; + BOOL isDir; + BOOL userTerminated = NO; + BOOL doSyntaxHighlighting = NO; + BOOL doSyntaxHighlightingViaCSS = NO; + + if([[params lastObject] hasPrefix:@"html"]) { + doSyntaxHighlighting = YES; + if([[params lastObject] hasSuffix:@"css"]) { + doSyntaxHighlightingViaCSS = YES; + } + } + + if(doSyntaxHighlighting && [params count] < 3) return; + + BOOL changeEncoding = ![[mySQLConnection encoding] isEqualToString:@"utf8"]; + + NSArray *items = [params subarrayWithRange:NSMakeRange(1, [params count]-( (doSyntaxHighlighting) ? 2 : 1) )]; + NSArray *availableItems = [tablesListInstance tables]; + NSArray *availableItemTypes = [tablesListInstance tableTypes]; + NSMutableString *result = [NSMutableString string]; + + for(NSString* item in items) { + + NSEvent* event = [NSApp currentEvent]; + if ([event type] == NSKeyDown) { + unichar key = [[event characters] length] == 1 ? [[event characters] characterAtIndex:0] : 0; + if (([event modifierFlags] & NSCommandKeyMask) && key == '.') { + userTerminated = YES; + break; + } + } + + NSInteger itemType = SPTableTypeNone; + NSString *itemTypeStr = @"TABLE"; + NSInteger i; + NSInteger queryCol = 1; + + // Loop through the unfiltered tables/views to find the desired item + for (i = 0; i < [availableItems count]; i++) { + itemType = [[availableItemTypes objectAtIndex:i] integerValue]; + if (itemType == SPTableTypeNone) continue; + if ([[availableItems objectAtIndex:i] isEqualToString:item]) { + break; + } + } + // If no match found, continue + if (itemType == SPTableTypeNone) continue; + + switch(itemType) { + case SPTableTypeTable: + case SPTableTypeView: + itemTypeStr = @"TABLE"; + break; + case SPTableTypeProc: + itemTypeStr = @"PROCEDURE"; + queryCol = 2; + break; + case SPTableTypeFunc: + itemTypeStr = @"FUNCTION"; + queryCol = 2; + break; + } + + // Ensure that queries are made in UTF8 + if (changeEncoding) { + [mySQLConnection storeEncodingForRestoration]; + [mySQLConnection setEncoding:@"utf8"]; + } + + // Get create syntax + MCPResult *queryResult = [mySQLConnection queryString:[NSString stringWithFormat:@"SHOW CREATE %@ %@", + itemTypeStr, + [item backtickQuotedString] + ]]; + [queryResult setReturnDataAsStrings:YES]; + + if (changeEncoding) [mySQLConnection restoreStoredEncoding]; + + if ( ![queryResult numOfRows] ) { + //error while getting table structure + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"Couldn't get create syntax.\nMySQL said: %@", @"message of panel when table information cannot be retrieved"), [mySQLConnection getLastErrorMessage]]); + + status = @"1"; + + } else { + NSString *syntaxString = [[queryResult fetchRowAsArray] objectAtIndex:queryCol]; + + // A NULL value indicates that the user does not have permission to view the syntax + if ([syntaxString isNSNull]) { + [[NSAlert alertWithMessageText:NSLocalizedString(@"Permission Denied", @"Permission Denied") + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil otherButton:nil + informativeTextWithFormat:NSLocalizedString(@"The creation syntax could not be retrieved due to a permissions error.\n\nPlease check your user permissions with an administrator.", @"Create syntax permission denied detail")] + beginSheetModalForWindow:[NSApp mainWindow] + modalDelegate:self didEndSelector:NULL contextInfo:NULL]; + + return; + } + if(doSyntaxHighlighting) { + [result appendFormat:@"%@<br>", [self doSQLSyntaxHighlightForString:[syntaxString createViewSyntaxPrettifier] cssLike:doSyntaxHighlightingViaCSS]]; + } else { + [result appendFormat:@"%@\n", [syntaxString createViewSyntaxPrettifier]]; + } + } + } + + [fm removeItemAtPath:queryFileName error:nil]; + [fm removeItemAtPath:resultFileName error:nil]; + [fm removeItemAtPath:metaFileName error:nil]; + [fm removeItemAtPath:statusFileName error:nil]; + + if(userTerminated) + status = @"1"; + + if(![result writeToFile:resultFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]) + status = @"1"; + + // write status file as notification that query was finished + BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; + if(!succeed) { + NSBeep(); + SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); + } + + } + return; + } + + if([command isEqualToString:@"ExecuteQuery"]) { NSString *outputFormat = @"tab"; if([params count] == 2) @@ -4815,13 +5124,16 @@ BOOL succeed = [status writeToFile:statusFileName atomically:YES encoding:NSUTF8StringEncoding error:nil]; if(!succeed) { NSBeep(); - SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil, + SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, NSLocalizedString(@"Status file for sequelpro url scheme command couldn't be written!", @"status file for sequelpro url scheme command couldn't be written error message")); } return; } - NSLog(@"received: %@", commandDict); + SPBeginAlertSheet(NSLocalizedString(@"Remote Error", @"remote error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self parentWindow], self, nil, nil, + [NSString stringWithFormat:NSLocalizedString(@"URL scheme command “%@” unsupported", @"URL scheme command “%@” unsupported"), command]); + + } - (void)registerActivity:(NSDictionary*)commandDict |