aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/CMTextView.h4
-rw-r--r--Source/CMTextView.m301
-rw-r--r--Source/SPNarrowDownCompletion.h5
-rw-r--r--Source/SPNarrowDownCompletion.m284
4 files changed, 336 insertions, 258 deletions
diff --git a/Source/CMTextView.h b/Source/CMTextView.h
index cfabd3ef..80515928 100644
--- a/Source/CMTextView.h
+++ b/Source/CMTextView.h
@@ -69,7 +69,7 @@ static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAtt
- (BOOL) wrapSelectionWithPrefix:(NSString *)prefix suffix:(NSString *)suffix;
- (BOOL) shiftSelectionRight;
- (BOOL) shiftSelectionLeft;
-- (NSArray *) completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index;
+// - (NSArray *) completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index;
- (NSArray *) keywords;
- (NSArray *) functions;
- (void) setAutoindent:(BOOL)enableAutoindent;
@@ -87,7 +87,7 @@ static inline void NSMutableAttributedStringAddAttributeValueRange (NSMutableAtt
- (void) autoHelp;
- (void) doSyntaxHighlighting;
- (void) setConnection:(MCPConnection *)theConnection withVersion:(NSInteger)majorVersion;
-- (void) doCompletion;
+- (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode;
- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord dictMode:(BOOL)isDictMode browseMode:(BOOL)dbBrowseMode withTableName:(NSString*)aTableName withDbName:(NSString*)aDbName;
- (void) selectCurrentQuery;
diff --git a/Source/CMTextView.m b/Source/CMTextView.m
index c3b27987..3b0cab17 100644
--- a/Source/CMTextView.m
+++ b/Source/CMTextView.m
@@ -166,18 +166,13 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSMutableArray *possibleCompletions = [[NSMutableArray alloc] initWithCapacity:32];
- if(isDictMode) {
- for (id w in [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0,[currentWord length]) inString:currentWord language:nil inSpellDocumentWithTag:0])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"dummy-small", @"image", nil]];
- }
-
// If caret is not inside backticks add keywords and all words coming from the view.
- if([[self string] length] && !dbBrowseMode)
+ if(!dbBrowseMode)
{
// Only parse for words if text size is less than 6MB
- if([[self string] length]<6000000)
+ if([[self string] length] && [[self string] length]<6000000)
{
- NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()[]{}\"'`-!;=+|?:~@"];
+ NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()[]{}\"'`-!;=+|?:~@."];
NSMutableArray *uniqueArray = [NSMutableArray array];
[uniqueArray addObjectsFromArray:[[NSSet setWithArray:[[self string] componentsSeparatedByCharactersInSet:separators]] allObjects]];
@@ -222,28 +217,41 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
if ([[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"tableName"] != nil)
currentTable = [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"tableName"];
+ // Put current selected db at the top
if(aTableName == nil && aDbName == nil && [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"selectedDatabase"]) {
- // Put current selected db at the top
currentDb = [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"selectedDatabase"];
[sortedDbs removeObject:currentDb];
[sortedDbs insertObject:currentDb atIndex:0];
}
- // Put information_schema and mysql db at the end if not chosen
- if(currentDb && ![currentDb isEqualToString:@"mysql"]) {
+
+ // Put information_schema and/or mysql db at the end if not selected
+ if(currentDb && ![currentDb isEqualToString:@"mysql"] && [sortedDbs containsObject:@"mysql"]) {
[sortedDbs removeObject:@"mysql"];
[sortedDbs addObject:@"mysql"];
}
- if(currentDb && ![currentDb isEqualToString:@"information_schema"]) {
+ if(currentDb && ![currentDb isEqualToString:@"information_schema"] && [sortedDbs containsObject:@"information_schema"]) {
[sortedDbs removeObject:@"information_schema"];
[sortedDbs addObject:@"information_schema"];
}
BOOL aTableNameExists = NO;
if(!aDbName) {
- if(aTableName && [aTableName length] && [dbs objectForKey:currentDb] && [[dbs objectForKey:currentDb] objectForKey:aTableName]) {
+ // Try to suggest only items which are uniquely valid for the parsed string
+
+ NSInteger uniqueSchemaKind = [mySQLConnection getUniqueDbIndentifierFor:[aTableName lowercaseString]];
+
+ // If no db name but table name check if table name is a valid name in the current selected db
+ if(aTableName && [aTableName length] && [dbs objectForKey:currentDb] && [[dbs objectForKey:currentDb] objectForKey:aTableName] && uniqueSchemaKind == 2) {
aTableNameExists = YES;
aDbName = [NSString stringWithString:currentDb];
}
+
+ // If no db name but table name check if table name is a valid db name
+ if(!aTableNameExists && aTableName && [aTableName length] && uniqueSchemaKind == 1) {
+ aDbName = [NSString stringWithString:aTableName];
+ aTableNameExists = NO;
+ }
+
} else if (aDbName && [aDbName length]) {
if(aTableName && [aTableName length] && [dbs objectForKey:aDbName] && [[dbs objectForKey:aDbName] objectForKey:aTableName]) {
aTableNameExists = YES;
@@ -264,12 +272,10 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
} else {
[possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:db, @"display", @"database-small", @"image", @"", @"isRef", nil]];
[sortedTables addObjectsFromArray:[allTables sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]]];
- // if(aDbName == nil && aTableName) {
- if([sortedTables count] > 1 && [sortedTables containsObject:currentTable]) {
- [sortedTables removeObject:currentTable];
- [sortedTables insertObject:currentTable atIndex:0];
- }
- // }
+ if([sortedTables count] > 1 && [sortedTables containsObject:currentTable]) {
+ [sortedTables removeObject:currentTable];
+ [sortedTables insertObject:currentTable atIndex:0];
+ }
}
for(id table in sortedTables) {
NSDictionary * theTable = [[dbs objectForKey:db] objectForKey:table];
@@ -285,11 +291,11 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
[possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"table-view-small-square", @"image", db, @"path", @"", @"isRef", nil]];
break;
case 2:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"proc-small", @"image", db, @"path", @"", @"isRef", nil]];
+ [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"proc-small", @"image", db, @"path", nil]];
breakFlag = YES;
break;
case 3:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"func-small", @"image", db, @"path", @"", @"isRef", nil]];
+ [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"func-small", @"image", db, @"path", nil]];
breakFlag = YES;
break;
}
@@ -306,7 +312,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
} else {
// Fallback for MySQL < 5 and if the data gathering is in progress
if(mySQLmajorVersion > 4)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"fetching table data…", @"fetching table data for completion in progress message"), @"path", nil]];
+ [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"fetching table data…", @"fetching table data for completion in progress message"), @"path", @"", @"noCompletion", nil]];
// Add all database names to completions list
for (id obj in [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allDatabaseNames"])
@@ -354,7 +360,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
}
-- (void)doCompletion
+- (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode
{
// No completion for a selection (yet?) and if caret positiopn == 0
@@ -367,26 +373,28 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSRange parseRange = completionRange;
NSString* currentWord = [[self string] substringWithRange:completionRange];
NSString* prefix = @"";
- NSString* allow = @"_. "; // additional chars which not close the popup
NSString *currentDb = nil;
+ NSString* allow; // additional chars which not close the popup
+ if(isDictMode)
+ allow= @"_";
+ else
+ allow= @"_. ";
+
+
BOOL dbBrowseMode = NO;
NSInteger backtickMode = 0; // 0 none, 1 rigth only, 2 left only, 3 both
BOOL caseInsensitive = YES;
- currentDb = [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"selectedDatabase"];
- if(!currentDb) currentDb = @"";
-
- // Check if the caret is inside quotes "" or ''; if so
- // return the normal word suggestion due to the spelling's settings
- // plus all unique words used in the textView
- BOOL isDictMode = NO;
- if(completionRange.length)
- isDictMode = ([[[self textStorage] attribute:kQuote atIndex:completionRange.location effectiveRange:nil] isEqualToString:kQuoteValue] );
-
-
if(!isDictMode) {
+ // Parse for leading db.table.field infos
+
+ if([[[self window] delegate] isKindOfClass:[TableDocument class]] && [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"selectedDatabase"])
+ currentDb = [[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKeyPath:@"selectedDatabase"];
+ else
+ currentDb = @"";
+
NSInteger caretPos = [self selectedRange].location;
BOOL caretIsInsideBackticks = NO;
@@ -401,11 +409,15 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSCharacterSet *whiteSpaceCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSInteger start = caretPos;
NSInteger backticksCounter = (caretIsInsideBackticks) ? 1 : 0;
- NSInteger pointCounter = 0;
- NSInteger firstPoint = 0;
- NSInteger secondPoint = 0;
- BOOL doParsing = YES;
+ NSInteger pointCounter = 0;
+ NSInteger firstPoint = 0;
+ NSInteger secondPoint = 0;
+ BOOL rightBacktick = NO;
+ BOOL leftBacktick = NO;
+ BOOL doParsing = YES;
+
unichar currentCharacter;
+
while(start > 0 && doParsing) {
currentCharacter = [[self string] characterAtIndex:--start];
if(!(backticksCounter%2) && [whiteSpaceCharSet characterIsMember:currentCharacter]) {
@@ -446,11 +458,15 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
NSString *parsedString = [[self string] substringWithRange:parseRange];
// Check if parsed string is wrapped by ``
- if([parsedString hasPrefix:@"`"]) backtickMode+=1;
+ if([parsedString hasPrefix:@"`"]) {
+ backtickMode+=1;
+ leftBacktick = YES;
+ }
if([[self string] length] > parseRange.location+parseRange.length) {
if([[self string] characterAtIndex:parseRange.location+parseRange.length] == '`') {
backtickMode+=2;
parseRange.length++; // adjust parse string for right `
+ rightBacktick = YES;
}
}
@@ -468,8 +484,27 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
} else {
filter = [[parsedString stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
}
- if(![filter length])
- completionRange = NSMakeRange(parseRange.location+parseRange.length,0);
+
+ // Adjust completion range
+ if(firstPoint>0) {
+ completionRange = NSMakeRange(firstPoint+1+start,[parsedString length]-firstPoint-1);
+ }
+ else if([filter length] && leftBacktick) {
+ completionRange = NSMakeRange(completionRange.location-1,completionRange.length+1);
+ }
+ if(rightBacktick)
+ completionRange.length++;
+
+ // Check leading . since .tableName == <currentDB>.tableName etc.
+ if([filter hasPrefix:@".`"]) {
+ filter = [filter substringFromIndex:2];
+ completionRange = NSMakeRange(completionRange.location-1,completionRange.length+1);
+ } else if([filter hasPrefix:@"."]) {
+ filter = [filter substringFromIndex:1];
+ } else if([tableName hasPrefix:@".`"]) {
+ tableName = [tableName substringFromIndex:2];
+ }
+
} else {
filter = [NSString stringWithString:currentWord];
}
@@ -493,12 +528,11 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
selectedDb:currentDb];
//Get the NSPoint of the first character of the current word
- NSRange range = NSMakeRange(completionRange.location,0);
- NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:range actualCharacterRange:NULL];
+ NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(completionRange.location,1) actualCharacterRange:NULL];
NSRect boundingRect = [[self layoutManager] boundingRectForGlyphRange:glyphRange inTextContainer:[self textContainer]];
boundingRect = [self convertRect: boundingRect toView: NULL];
NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)];
-
+
// TODO: check if needed
// if(filter)
// pos.x -= [filter sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width;
@@ -737,14 +771,14 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
long curFlags = ([theEvent modifierFlags] & allFlags);
if ([theEvent keyCode] == 53){ // ESC key for internal completion
- [super keyDown: theEvent];
+ [self doCompletionByUsingSpellChecker:NO];
// Remove that attribute to suppress auto-uppercasing of certain keyword combinations
if(![self selectedRange].length && [self selectedRange].location)
- [[self textStorage] removeAttribute:kSQLkeyword range:NSMakeRange([self selectedRange].location-1,1)];
+ [[self textStorage] removeAttribute:kSQLkeyword range:[self getRangeForCurrentWord]];
return;
}
- if (insertedCharacter == NSF5FunctionKey){ // F5 for cocoa completion
- [self doCompletion];
+ if (insertedCharacter == NSF5FunctionKey){ // F5 for completion based on spell checker
+ [self doCompletionByUsingSpellChecker:YES];
// Remove that attribute to suppress auto-uppercasing of certain keyword combinations
if(![self selectedRange].length && [self selectedRange].location)
[[self textStorage] removeAttribute:kSQLkeyword range:[self getRangeForCurrentWord]];
@@ -1111,90 +1145,90 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
/*
* Handle autocompletion, returning a list of suggested completions for the supplied character range.
*/
-- (NSArray *)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
-{
-
- if (!charRange.length) return nil;
-
- // Refresh quote attributes
- [[self textStorage] removeAttribute:kQuote range:NSMakeRange(0,[[self string] length])];
- [self insertText:@""];
-
-
- // Check if the caret is inside quotes "" or ''; if so
- // return the normal word suggestion due to the spelling's settings
- 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];
-
-
- NSMutableArray *compl = [[NSMutableArray alloc] initWithCapacity:32];
- NSMutableArray *possibleCompletions = [[NSMutableArray alloc] initWithCapacity:32];
-
- NSString *partialString = [[self string] substringWithRange:charRange];
- NSUInteger partialLength = [partialString length];
-
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF beginswith[cd] %@ AND length > %lu", partialString, (unsigned long)partialLength];
- NSArray *matchingCompletions;
-
- NSUInteger i, insindex;
- insindex = 0;
-
-
- if([mySQLConnection isConnected])
- {
-
- // Add all database names to completions list
- [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allDatabaseNames"]];
-
- // Add table names to completions list
- [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allTableAndViewNames"]];
-
- // Add field names to completions list for currently selected table
- if ([[[self window] delegate] table] != nil)
- [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tableDataInstance"] valueForKey:@"columnNames"]];
-
- // Add proc/func only for MySQL version 5 or higher
- if(mySQLmajorVersion > 4) {
- [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allProcedureNames"]];
- [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allFunctionNames"]];
- }
-
- }
- // If caret is not inside backticks add keywords and all words coming from the view.
- if(![[[self textStorage] attribute:kBTQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kBTQuoteValue] )
- {
- // Only parse for words if text size is less than 6MB
- if([[self string] length]<6000000)
- {
- NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()[]{}\"'`-!;=+|?:~@"];
- NSMutableArray *uniqueArray = [NSMutableArray array];
- [uniqueArray addObjectsFromArray:[[NSSet setWithArray:[[self string] componentsSeparatedByCharactersInSet:separators]] allObjects]];
- [possibleCompletions addObjectsFromArray:uniqueArray];
- }
-
- [possibleCompletions addObjectsFromArray:[self keywords]];
- [possibleCompletions addObjectsFromArray:[self functions]];
- }
-
- // Check for possible completions
- matchingCompletions = [[possibleCompletions filteredArrayUsingPredicate:predicate] sortedArrayUsingSelector:@selector(compare:)];
-
- for (i = 0; i < [matchingCompletions count]; i++)
- {
- NSString* obj = NSArrayObjectAtIndex(matchingCompletions, i);
- if(![compl containsObject:obj])
- if ([partialString isEqualToString:[obj substringToIndex:partialLength]])
- // Matches case --> Insert at beginning of completion list
- [compl insertObject:obj atIndex:insindex++];
- else
- // Not matching case --> Insert at end of completion list
- [compl addObject:obj];
- }
-
- [possibleCompletions release];
-
- return [compl autorelease];
-}
+// - (NSArray *)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
+// {
+//
+// if (!charRange.length) return nil;
+//
+// // Refresh quote attributes
+// [[self textStorage] removeAttribute:kQuote range:NSMakeRange(0,[[self string] length])];
+// [self insertText:@""];
+//
+//
+// // Check if the caret is inside quotes "" or ''; if so
+// // return the normal word suggestion due to the spelling's settings
+// 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];
+//
+//
+// NSMutableArray *compl = [[NSMutableArray alloc] initWithCapacity:32];
+// NSMutableArray *possibleCompletions = [[NSMutableArray alloc] initWithCapacity:32];
+//
+// NSString *partialString = [[self string] substringWithRange:charRange];
+// NSUInteger partialLength = [partialString length];
+//
+// NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF beginswith[cd] %@ AND length > %lu", partialString, (unsigned long)partialLength];
+// NSArray *matchingCompletions;
+//
+// NSUInteger i, insindex;
+// insindex = 0;
+//
+//
+// if([mySQLConnection isConnected])
+// {
+//
+// // Add all database names to completions list
+// [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allDatabaseNames"]];
+//
+// // Add table names to completions list
+// [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allTableAndViewNames"]];
+//
+// // Add field names to completions list for currently selected table
+// if ([[[self window] delegate] table] != nil)
+// [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tableDataInstance"] valueForKey:@"columnNames"]];
+//
+// // Add proc/func only for MySQL version 5 or higher
+// if(mySQLmajorVersion > 4) {
+// [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allProcedureNames"]];
+// [possibleCompletions addObjectsFromArray:[[[[self window] delegate] valueForKeyPath:@"tablesListInstance"] valueForKey:@"allFunctionNames"]];
+// }
+//
+// }
+// // If caret is not inside backticks add keywords and all words coming from the view.
+// if(![[[self textStorage] attribute:kBTQuote atIndex:charRange.location effectiveRange:nil] isEqualToString:kBTQuoteValue] )
+// {
+// // Only parse for words if text size is less than 6MB
+// if([[self string] length]<6000000)
+// {
+// NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()[]{}\"'`-!;=+|?:~@"];
+// NSMutableArray *uniqueArray = [NSMutableArray array];
+// [uniqueArray addObjectsFromArray:[[NSSet setWithArray:[[self string] componentsSeparatedByCharactersInSet:separators]] allObjects]];
+// [possibleCompletions addObjectsFromArray:uniqueArray];
+// }
+//
+// [possibleCompletions addObjectsFromArray:[self keywords]];
+// [possibleCompletions addObjectsFromArray:[self functions]];
+// }
+//
+// // Check for possible completions
+// matchingCompletions = [[possibleCompletions filteredArrayUsingPredicate:predicate] sortedArrayUsingSelector:@selector(compare:)];
+//
+// for (i = 0; i < [matchingCompletions count]; i++)
+// {
+// NSString* obj = NSArrayObjectAtIndex(matchingCompletions, i);
+// if(![compl containsObject:obj])
+// if ([partialString isEqualToString:[obj substringToIndex:partialLength]])
+// // Matches case --> Insert at beginning of completion list
+// [compl insertObject:obj atIndex:insindex++];
+// else
+// // Not matching case --> Insert at end of completion list
+// [compl addObject:obj];
+// }
+//
+// [possibleCompletions release];
+//
+// return [compl autorelease];
+// }
/*
@@ -1579,6 +1613,7 @@ NSInteger alphabeticSort(id string1, id string2, void *reverse)
@"OPTIONS",
@"OR",
@"ORDER",
+ @"ORDER BY",
@"OUT",
@"OUTER",
@"OUTFILE",
diff --git a/Source/SPNarrowDownCompletion.h b/Source/SPNarrowDownCompletion.h
index 88e76567..d442b00f 100644
--- a/Source/SPNarrowDownCompletion.h
+++ b/Source/SPNarrowDownCompletion.h
@@ -44,13 +44,16 @@
BOOL caseSensitive;
BOOL dictMode;
BOOL dbStructureMode;
+ BOOL noFilterString;
NSInteger backtickMode;
NSFont *tableFont;
NSRange theCharRange;
NSRange theParseRange;
- NSArray *words;
+ NSString *theDbName;
id theView;
+ NSInteger maxWindowWidth;
+
NSMutableCharacterSet* textualInputCharacters;
}
diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m
index 82564567..dbc03b38 100644
--- a/Source/SPNarrowDownCompletion.m
+++ b/Source/SPNarrowDownCompletion.m
@@ -99,13 +99,16 @@
// =============================
- (id)init
{
- if(self = [super initWithContentRect:NSMakeRect(0,0,450,0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])
+
+ maxWindowWidth = 450;
+
+ if(self = [super initWithContentRect:NSMakeRect(0,0,maxWindowWidth,0) styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])
{
mutablePrefix = [NSMutableString new];
textualInputCharacters = [[NSMutableCharacterSet alphanumericCharacterSet] retain];
caseSensitive = YES;
filtered = nil;
-
+
tableFont = [NSUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] dataForKey:SPCustomQueryEditorFont]];
[self setupInterface];
}
@@ -134,10 +137,8 @@
if(self = [self init])
{
- BOOL filterStringIsBacktick = ([aUserString isEqualToString:@"`"]) ? YES : NO;
-
- // Set filter string - if aUserString == ` user invoked it via `|` ie show all db/tables/fields etc.
- if(aUserString && !filterStringIsBacktick)
+ // Set filter string
+ if(aUserString)
[mutablePrefix appendString:aUserString];
dbStructureMode = theDbMode;
@@ -150,24 +151,22 @@
caseSensitive = isCaseSensitive;
theCharRange = initRange;
-
- if(filterStringIsBacktick) {
- theCharRange.length = 0;
- theCharRange.location++;
- }
+ noFilterString = ([aUserString length]) ? NO : YES;
theParseRange = parseRange;
theView = aView;
dictMode = mode;
- if(!dictMode) {
- suggestions = [someSuggestions retain];
- words = nil;
- }
+ suggestions = [someSuggestions retain];
+
+ [[theTableView tableColumnWithIdentifier:@"image"] setWidth:((dictMode) ? 0 : 20)];
+ [[theTableView tableColumnWithIdentifier:@"name"] setWidth:((dictMode) ? 440 : 180)];
currentDb = selectedDb;
+ theDbName = dbName;
+
if(someAdditionalWordCharacters)
[textualInputCharacters addCharactersInString:someAdditionalWordCharacters];
@@ -178,24 +177,25 @@
- (void)setCaretPos:(NSPoint)aPos
{
caretPos = aPos;
- isAbove = NO;
-
+
NSRect mainScreen = [self rectOfMainScreen];
-
+
NSInteger offx = (caretPos.x/mainScreen.size.width) + 1;
+
if((caretPos.x + [self frame].size.width) > (mainScreen.size.width*offx))
- caretPos.x = caretPos.x - [self frame].size.width;
-
- if(caretPos.y>=0 && caretPos.y<[self frame].size.height)
+ caretPos.x = (mainScreen.size.width*offx) - [self frame].size.width - 5;
+
+ if(caretPos.y >= 0 && caretPos.y < [self frame].size.height)
{
- caretPos.y = caretPos.y + ([self frame].size.height + [tableFont pointSize]*1.5);
+ caretPos.y += [self frame].size.height + ([tableFont pointSize]*1.5);
isAbove = YES;
}
- if(caretPos.y<0 && (mainScreen.size.height-[self frame].size.height)<(caretPos.y*-1))
+ if(caretPos.y < 0 && (mainScreen.size.height-[self frame].size.height) < (caretPos.y*-1))
{
- caretPos.y = caretPos.y + ([self frame].size.height + [tableFont pointSize]*1.5);
+ caretPos.y += [self frame].size.height + ([tableFont pointSize]*1.5);
isAbove = YES;
}
+
[self setFrameTopLeftPoint:caretPos];
}
@@ -208,7 +208,7 @@
[self setAlphaValue:0.9];
NSScrollView* scrollView = [[[NSScrollView alloc] initWithFrame:NSZeroRect] autorelease];
- // [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[scrollView setAutohidesScrollers:YES];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:NO];
@@ -219,30 +219,31 @@
[theTableView setFocusRingType:NSFocusRingTypeNone];
[theTableView setAllowsEmptySelection:NO];
[theTableView setHeaderView:nil];
- // [theTableView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList];
+ [theTableView setDelegate:self];
NSTableColumn *column0 = [[[NSTableColumn alloc] initWithIdentifier:@"image"] autorelease];
[column0 setDataCell:[[ImageAndTextCell new] autorelease]];
[column0 setEditable:NO];
[theTableView addTableColumn:column0];
+ [column0 setMinWidth:0];
[column0 setWidth:20];
+
NSTableColumn *column1 = [[[NSTableColumn alloc] initWithIdentifier:@"name"] autorelease];
[column1 setEditable:NO];
- // [[column1 dataCell] setFont:[NSFont systemFontOfSize:12]];
[theTableView addTableColumn:column1];
- [column1 setWidth:180];
+ [column1 setWidth:170];
+
NSTableColumn *column2 = [[[NSTableColumn alloc] initWithIdentifier:@"type"] autorelease];
[column2 setEditable:NO];
- // [[column2 dataCell] setFont:[NSFont systemFontOfSize:11]];
[[column2 dataCell] setTextColor:[NSColor darkGrayColor]];
[theTableView addTableColumn:column2];
- [column2 setWidth:120];
+ [column2 setWidth:145];
+
NSTableColumn *column3 = [[[NSTableColumn alloc] initWithIdentifier:@"path"] autorelease];
[column3 setEditable:NO];
- // [[column3 dataCell] setFont:[NSFont systemFontOfSize:11]];
[[column3 dataCell] setTextColor:[NSColor darkGrayColor]];
[theTableView addTableColumn:column3];
- [column3 setWidth:130];
+ [column3 setWidth:95];
[theTableView setDataSource:self];
[scrollView setDocumentView:theTableView];
@@ -258,10 +259,43 @@
return [filtered count];
}
+// ------- nstokenfield delegates -- does not work for menus due to the click event does not reach it -- why???
+// - (NSMenu *)tokenFieldCell:(NSTokenFieldCell *)tokenFieldCell menuForRepresentedObject:(id)representedObject
+// {
+// NSMenu *tokenMenu = [[[NSMenu alloc] init] autorelease];
+//
+// if (!representedObject)
+// return nil;
+//
+// NSMenuItem *artistItem = [[[NSMenuItem alloc] init] autorelease];
+// [artistItem setTitle:@"aaa"];
+// [tokenMenu addItem:artistItem];
+//
+// NSMenuItem *albumItem = [[[NSMenuItem alloc] init] autorelease];
+// [albumItem setTitle:@"ksajdhkjas"];
+// [tokenMenu addItem:albumItem];
+//
+//
+// // NSMenuItem *mItem = [[[NSMenuItem alloc] initWithTitle:@"Show Album Art" action:@selector(showAlbumArt:) keyEquivalent:@""] autorelease];
+// // [mItem setTarget:self];
+// // [mItem setRepresentedObject:representedObject];
+// // [tokenMenu addItem:mItem];
+//
+// return tokenMenu;
+// }
+// - (BOOL)tokenFieldCell:(NSTokenFieldCell *)tokenFieldCell hasMenuForRepresentedObject:(id)representedObject
+// {
+// return YES;
+// }
+// - (NSString *)tokenFieldCell:(NSTokenFieldCell *)tokenFieldCell displayStringForRepresentedObject:(id)representedObject
+// {
+// return representedObject;
+// }
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSImage* image = nil;
NSString* imageName = nil;
+
if([[aTableColumn identifier] isEqualToString:@"image"]) {
if(!dictMode) {
imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"];
@@ -270,21 +304,48 @@
[[aTableColumn dataCell] setImage:image];
}
return @"";
+
} else if([[aTableColumn identifier] isEqualToString:@"name"]) {
- return (dictMode) ? [filtered objectAtIndex:rowIndex] : [[filtered objectAtIndex:rowIndex] objectForKey:@"display"];
+ return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"];
+
} else if([[aTableColumn identifier] isEqualToString:@"type"]) {
if(dictMode) {
return @"";
} else {
- [[aTableColumn dataCell] setTextColor:([aTableView selectedRow] == rowIndex)?[NSColor whiteColor]:[NSColor darkGrayColor]];
- return ([[filtered objectAtIndex:rowIndex] objectForKey:@"type"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"type"] : @"";
+ // [[aTableColumn dataCell] setTextColor:([aTableView selectedRow] == rowIndex)?[NSColor whiteColor]:[NSColor darkGrayColor]];
+ // return ([[filtered objectAtIndex:rowIndex] objectForKey:@"type"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"type"] : @"";
+ NSTokenFieldCell *b = [[[NSTokenFieldCell alloc] initTextCell:([[filtered objectAtIndex:rowIndex] objectForKey:@"type"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"type"] : @""] autorelease];
+ [b setEditable:NO];
+ [b setFont:[NSFont systemFontOfSize:11]];
+ [b setDelegate:self];
+ return b;
}
+
} else if ([[aTableColumn identifier] isEqualToString:@"path"]) {
if(dictMode) {
return @"";
} else {
- [[aTableColumn dataCell] setTextColor:([aTableView selectedRow] == rowIndex)?[NSColor whiteColor]:[NSColor darkGrayColor]];
- return ([[filtered objectAtIndex:rowIndex] objectForKey:@"path"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"path"] : @"";
+ // [[aTableColumn dataCell] setTextColor:([aTableView selectedRow] == rowIndex)?[NSColor whiteColor]:[NSColor darkGrayColor]];
+ // return ([[filtered objectAtIndex:rowIndex] objectForKey:@"path"]) ? [[filtered objectAtIndex:rowIndex] objectForKey:@"path"] : @"";
+ if([[filtered objectAtIndex:rowIndex] objectForKey:@"path"]) {
+ NSPopUpButtonCell *b = [[NSPopUpButtonCell new] autorelease];
+ [b setPullsDown:NO];
+ [b setAltersStateOfSelectedItem:NO];
+ [b setControlSize:NSMiniControlSize];
+ NSMenu *m = [[NSMenu alloc] init];
+ for(id p in [[[filtered objectAtIndex:rowIndex] objectForKey:@"path"] componentsSeparatedByString:@"⇠"])
+ [m addItemWithTitle:p action:NULL keyEquivalent:@""];
+ [b setMenu:m];
+ [m release];
+ [b setPreferredEdge:NSMinXEdge];
+ [b setArrowPosition:([m numberOfItems]>1) ? NSPopUpArrowAtCenter : NSPopUpNoArrow];
+ [b setFont:[NSFont systemFontOfSize:11]];
+ [b setBordered:NO];
+ [aTableColumn setDataCell:b];
+ } else {
+ [aTableColumn setDataCell:[[NSTextFieldCell new] autorelease]];
+ }
+ return @"";
}
}
return [filtered objectAtIndex:rowIndex];
@@ -295,77 +356,49 @@
// ====================
- (void)filter
{
- // NSRect mainScreen = [self rectOfMainScreen];
- NSArray* newFiltered;
+ NSMutableArray* newFiltered = [[NSMutableArray alloc] initWithCapacity:5];
if([mutablePrefix length] > 0)
{
- if(dictMode) {
- newFiltered = [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0,[[self filterString] length]) inString:[self filterString] language:nil inSpellDocumentWithTag:0];
- } else {
- NSPredicate* predicate;
- if(caseSensitive)
- predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH %@ OR (match == NULL AND display BEGINSWITH %@)", [self filterString], [self filterString]];
- else
- predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH[c] %@ OR (match == NULL AND display BEGINSWITH[c] %@)", [self filterString], [self filterString]];
- newFiltered = [suggestions filteredArrayUsingPredicate:predicate];
- }
+ NSPredicate* predicate;
+ if(caseSensitive)
+ predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH %@ OR (match == NULL AND display BEGINSWITH %@)", [self filterString], [self filterString]];
+ else
+ predicate = [NSPredicate predicateWithFormat:@"match BEGINSWITH[c] %@ OR (match == NULL AND display BEGINSWITH[c] %@)", [self filterString], [self filterString]];
+ [newFiltered addObjectsFromArray:[suggestions filteredArrayUsingPredicate:predicate]];
+ if(dictMode)
+ for(id w in [[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:NSMakeRange(0,[[self filterString] length]) inString:[self filterString] language:nil inSpellDocumentWithTag:0])
+ [newFiltered addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", nil]];
}
else
{
- if(dictMode)
- newFiltered = nil;
- else
- newFiltered = suggestions;
+ if(!dictMode)
+ [newFiltered addObjectsFromArray:suggestions];
}
+
+ if(![newFiltered count])
+ [newFiltered addObject:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"No completions found", @"no completions found message"), @"display", @"", @"noCompletion", nil]];
+
NSPoint old = NSMakePoint([self frame].origin.x, [self frame].origin.y + [self frame].size.height);
-
+
NSInteger displayedRows = [newFiltered count] < SP_NARROWDOWNLIST_MAX_ROWS ? [newFiltered count] : SP_NARROWDOWNLIST_MAX_ROWS;
- CGFloat newHeight = ([theTableView rowHeight] + [theTableView intercellSpacing].height) * displayedRows;
-
- // CGFloat maxLen = 1;
- // NSString* item;
- // NSInteger i;
- // BOOL spaceInSuggestion = NO;
- // [textualInputCharacters removeCharactersInString:@" "];
- // CGFloat maxWidth = [self frame].size.width;
- // if([newFiltered count]>0)
- // {
- // for(i=0; i<[newFiltered count]; i++)
- // {
- // if(dictMode)
- // item = NSArrayObjectAtIndex(newFiltered, i);
- // else
- // item = [NSArrayObjectAtIndex(newFiltered, i) objectForKey:@"display"];
- // // If space in suggestion add space to allowed input chars
- // if(!spaceInSuggestion && [item rangeOfString:@" "].length) {
- // [textualInputCharacters addCharactersInString:@" "];
- // spaceInSuggestion = YES;
- // }
- //
- // if([item length]>maxLen)
- // maxLen = [item length];
- // }
- // maxWidth = maxLen*16;
- // maxWidth = (maxWidth>340) ? 340 : maxWidth;
- // maxWidth = (maxWidth<20) ? 20 : maxWidth;
- // }
- // if(caretPos.y>=0 && (isAbove || caretPos.y<newHeight))
- // {
- // isAbove = YES;
- // old.y = caretPos.y + (newHeight + [tableFont pointSize]*1.5);
- // }
- // if(caretPos.y<0 && (isAbove || (mainScreen.size.height-newHeight)<(caretPos.y*-1)))
- // {
- // old.y = caretPos.y + (newHeight + [tableFont pointSize]*1.5);
- // }
-
+ CGFloat newHeight = ([theTableView rowHeight] + [theTableView intercellSpacing].height) * ((displayedRows) ? displayedRows : 1);
+
+ if(caretPos.y >= 0 && (isAbove || caretPos.y < newHeight))
+ {
+ isAbove = YES;
+ old.y = caretPos.y + newHeight + ([tableFont pointSize]*1.5);
+ }
+ if(caretPos.y < 0 && (isAbove || ([self rectOfMainScreen].size.height-newHeight) < (caretPos.y*-1)))
+ old.y = caretPos.y + newHeight + ([tableFont pointSize]*1.5);
+
// newHeight is currently the new height for theTableView, but we need to resize the whole window
// so here we use the difference in height to find the new height for the window
- // newHeight = [[self contentView] frame].size.height + (newHeight - [theTableView frame].size.height);
- [self setFrame:NSMakeRect(old.x, old.y-newHeight, 450, newHeight) display:YES];
+ [self setFrame:NSMakeRect(old.x, old.y-newHeight, maxWindowWidth, newHeight) display:YES];
+
if (filtered) [filtered release];
filtered = [newFiltered retain];
+ [newFiltered release];
[theTableView reloadData];
}
@@ -498,6 +531,7 @@
}
}
[self close];
+ usleep(70); // tiny delay to suppress while continously pressing of ESC overlapping
}
// ==================
@@ -511,10 +545,7 @@
id cur = [filtered objectAtIndex:row];
NSString* curMatch;
- if(dictMode)
- curMatch = [NSString stringWithString:cur];
- else
- curMatch = [cur objectForKey:@"match"] ?: [cur objectForKey:@"display"];
+ curMatch = [cur objectForKey:@"match"] ?: [cur objectForKey:@"display"];
if([[self filterString] length] + 1 < [curMatch length])
{
NSString* prefix = [curMatch substringToIndex:[[self filterString] length] + 1];
@@ -523,10 +554,7 @@
{
id candidate = [filtered objectAtIndex:i];
NSString* candidateMatch;
- if(dictMode)
- candidateMatch = [filtered objectAtIndex:i];
- else
- candidateMatch = [candidate objectForKey:@"match"] ?: [candidate objectForKey:@"display"];
+ candidateMatch = [candidate objectForKey:@"match"] ?: [candidate objectForKey:@"display"];
if([candidateMatch hasPrefix:prefix])
[candidates addObject:candidateMatch];
}
@@ -557,7 +585,7 @@
{
[theView setSelectedRange:theCharRange];
[theView insertText:aString];
- // If completion was invoked inside backticks move caret out of the backticks
+ // If completion string contains backticks move caret out of the backticks
if(backtickMode)
[theView performSelector:@selector(moveRight:)];
}
@@ -567,30 +595,42 @@
if([theTableView selectedRow] == -1)
return;
+ NSDictionary* selectedItem = [filtered objectAtIndex:[theTableView selectedRow]];
+
+ if([selectedItem objectForKey:@"noCompletion"]) {
+ return;
+ }
+
if(dictMode){
- [self insert_text:[[[filtered objectAtIndex:[theTableView selectedRow]] mutableCopy] autorelease]];
+ [self insert_text:[selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"]];
} else {
- NSMutableDictionary* selectedItem = [[[filtered objectAtIndex:[theTableView selectedRow]] mutableCopy] autorelease];
NSString* candidateMatch = [selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"];
- if( [[NSApp currentEvent] modifierFlags] & (NSShiftKeyMask)) {
- if([[selectedItem objectForKey:@"path"] length]) {
- NSString *path = [NSString stringWithFormat:@"%@.%@",
- [[[[[selectedItem objectForKey:@"path"] componentsSeparatedByString:@"⇠"] reverseObjectEnumerator] allObjects] componentsJoinedByPeriodAndBacktickQuoted],
- [candidateMatch backtickQuotedString]];
-
- // Check if path's db name is the current selected db name
- NSRange r = [path rangeOfString:[currentDb backtickQuotedString] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[currentDb backtickQuotedString] length])];
- theCharRange = theParseRange;
- backtickMode = 0; // suppress move the caret one step rightwards
- if(path && [path length] && r.length) {
- [self insert_text:[path substringFromIndex:r.length+1]];
+ if([selectedItem objectForKey:@"isRef"]
+ && ([[NSApp currentEvent] modifierFlags] & (NSShiftKeyMask))
+ && [[selectedItem objectForKey:@"path"] length]) {
+ NSString *path = [NSString stringWithFormat:@"%@.%@",
+ [[[[[selectedItem objectForKey:@"path"] componentsSeparatedByString:@"⇠"] reverseObjectEnumerator] allObjects] componentsJoinedByPeriodAndBacktickQuoted],
+ [candidateMatch backtickQuotedString]];
+
+ // Check if path's db name is the current selected db name
+ NSRange r = [path rangeOfString:[currentDb backtickQuotedString] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[currentDb backtickQuotedString] length])];
+ theCharRange = theParseRange;
+ backtickMode = 0; // suppress move the caret one step rightwards
+ if(path && [path length] && r.length) {
+ [self insert_text:[path substringFromIndex:r.length+1]];
+ } else {
+ [self insert_text:path];
+ }
+ } else {
+ if([[self filterString] length] < [candidateMatch length]) {
+ // Is completion string a schema name for current connection
+ if([selectedItem objectForKey:@"isRef"]) {
+ backtickMode = 0; // suppress move the caret one step rightwards
+ [self insert_text:[candidateMatch backtickQuotedString]];
} else {
- [self insert_text:path];
+ [self insert_text:candidateMatch];
}
}
- } else {
- if([[self filterString] length] < [candidateMatch length])
- [self insert_text:candidateMatch];
}
}
closeMe = YES;