From 6df5d2147ef4ac72760a8ab776ab8a4d489aebe9 Mon Sep 17 00:00:00 2001 From: Bibiko Date: Sun, 24 May 2009 20:26:28 +0000 Subject: =?UTF-8?q?=E2=80=A2=20improved=20narrow-down=20completion=20-=20a?= =?UTF-8?q?dded=20support=20for=20"in-quote=20completion"=20based=20on=20s?= =?UTF-8?q?uggestions=20of=20the=20spell=20checker=20dict=20and=20the=20cu?= =?UTF-8?q?rrent=20used=20words=20in=20the=20textView=20-=20still=20bound?= =?UTF-8?q?=20to=20F5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/CMTextView.h | 2 +- Source/CMTextView.m | 56 ++++++++++++++++++++------- Source/SPNarrowDownCompletion.h | 1 + Source/SPNarrowDownCompletion.m | 86 +++++++++++++++++++++++++++-------------- 4 files changed, 101 insertions(+), 44 deletions(-) diff --git a/Source/CMTextView.h b/Source/CMTextView.h index 723bde8f..0ada18ed 100644 --- a/Source/CMTextView.h +++ b/Source/CMTextView.h @@ -82,6 +82,6 @@ - (void) makeTextSizeSmaller; - (void) setConnection:(CMMCPConnection *)theConnection withVersion:(int)majorVersion; - (void) doCompletion; -- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord; +- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord dictMode:(BOOL)isDictMode; @end diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 7ab696db..f2b80a16 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -108,7 +108,21 @@ YY_BUFFER_STATE yy_scan_string (const char *); mySQLmajorVersion = majorVersion; } -- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord +/* + * Sort function (mainly used to sort the words in the textView) + */ +NSInteger alphabeticSort(id string1, id string2, void *reverse) +{ + return [string1 localizedCaseInsensitiveCompare:string2]; +} + +/* + * Return an array of NSDictionary containing the sorted strings representing + * the set of unique words, SQL keywords, user-defined funcs/procs, tables etc. + * NSDic key "display" := the displayed and to be inserted word + * NSDic key "image" := an image to be shown left from "display" (optional) + */ +- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord dictMode:(BOOL)isDictMode { NSMutableArray *compl = [[NSMutableArray alloc] initWithCapacity:32]; NSMutableArray *possibleCompletions = [[NSMutableArray alloc] initWithCapacity:32]; @@ -116,7 +130,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); unsigned i, insindex; insindex = 0; - if([mySQLConnection isConnected]) + if([mySQLConnection isConnected] && !isDictMode) { // Add table names to completions list MCPResult *queryResult = [mySQLConnection listTables]; @@ -164,24 +178,36 @@ YY_BUFFER_STATE yy_scan_string (const char *); } } + // If caret is not inside backticks add keywords and all words coming from the view. if([[self string] length] && ![[[self textStorage] attribute:kBTQuote atIndex:[self selectedRange].location-1 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,()\"'`-!;=+|?:~@"]; + NSCharacterSet *separators = [NSCharacterSet characterSetWithCharactersInString:@" \t\r\n,()[]{}\"'`-!;=+|?:~@"]; NSArray *textViewWords = [[self string] componentsSeparatedByCharactersInSet:separators]; - [possibleCompletions addObjectsFromArray:textViewWords]; + NSMutableArray *uniqueArray = [NSMutableArray array]; + NSString *s; + enumerate(textViewWords, s) + if(![uniqueArray containsObject:s]) + [uniqueArray addObject:s]; + + int reverseSort = NO; + NSArray *sortedArray = [[[uniqueArray mutableCopy] autorelease] sortedArrayUsingFunction:alphabeticSort context:&reverseSort]; + [possibleCompletions addObjectsFromArray:sortedArray]; } - [possibleCompletions addObjectsFromArray:[self keywords]]; } + + // Add predefined keywords + if(!isDictMode) + [possibleCompletions addObjectsFromArray:[self keywords]]; // Remove the current word [possibleCompletions removeObject:currentWord]; - // Build array of dictionaries as in: - // as in: [NSDictionary dictionaryWithObjectsAndKeys:@"foo", @"display", @"`foo`", @"insert", @"func-small", @"image", nil] + // Build array of dictionaries as e.g.: + // [NSDictionary dictionaryWithObjectsAndKeys:@"foo", @"display", @"`foo`", @"insert", @"func-small", @"image", nil] NSString* candidate; enumerate(possibleCompletions, candidate) { @@ -197,7 +223,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); - (void)doCompletion { - // No completion for a selection + // No completion for a selection (yet?) if ([self selectedRange].length > 0) return; // Refresh quote attributes @@ -206,22 +232,22 @@ YY_BUFFER_STATE yy_scan_string (const char *); // 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]; + // plus all unique words used in the textView + BOOL isDictMode = ([[[self textStorage] attribute:kQuote atIndex:[self getRangeForCurrentWord].location effectiveRange:nil] isEqualToString:kQuoteValue] ); NSString* filter = [[self string] substringWithRange:[self getRangeForCurrentWord]]; NSString* prefix = @""; - NSString* allow = @" "; + NSString* allow = @" _."; // additional chars which not close the popup BOOL caseInsensitive = YES; - SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:filter] + SPNarrowDownCompletion* completionPopUp = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:filter dictMode:isDictMode] alreadyTyped:filter staticPrefix:prefix additionalWordCharacters:allow caseSensitive:!caseInsensitive charRange:[self getRangeForCurrentWord] inView:self - dictMode:NO]; + dictMode:isDictMode]; //Get the NSPoint of the first character of the current word NSRange range = NSMakeRange([self getRangeForCurrentWord].location,0); @@ -231,6 +257,7 @@ YY_BUFFER_STATE yy_scan_string (const char *); NSPoint pos = [[self window] convertBaseToScreen: NSMakePoint(boundingRect.origin.x + boundingRect.size.width,boundingRect.origin.y + boundingRect.size.height)]; NSFont* font = [self font]; + // TODO: check if needed // if(filter) // pos.x -= [filter sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width; @@ -2293,9 +2320,12 @@ YY_BUFFER_STATE yy_scan_string (const char *); - (void)changeColor:(id)sender { [self setInsertionPointColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:@"CustomQueryEditorCaretColor"]]]; + // Remember the old selected range NSRange oldRange = [self selectedRange]; + // Invoke syntax highlighting [self setSelectedRange:NSMakeRange(oldRange.location,0)]; [self insertText:@""]; + // Reset old selected range [self setSelectedRange:oldRange]; } diff --git a/Source/SPNarrowDownCompletion.h b/Source/SPNarrowDownCompletion.h index 15176cf6..22657d15 100644 --- a/Source/SPNarrowDownCompletion.h +++ b/Source/SPNarrowDownCompletion.h @@ -53,6 +53,7 @@ BOOL dictMode; NSFont *tableFont; NSRange theCharRange; + NSArray *words; id theView; NSMutableCharacterSet* textualInputCharacters; diff --git a/Source/SPNarrowDownCompletion.m b/Source/SPNarrowDownCompletion.m index 10988241..4f54924e 100644 --- a/Source/SPNarrowDownCompletion.m +++ b/Source/SPNarrowDownCompletion.m @@ -119,7 +119,6 @@ { if(self = [self init]) { - suggestions = [someSuggestions retain]; if(aUserString) [mutablePrefix appendString:aUserString]; @@ -134,6 +133,13 @@ theCharRange = initRange; theView = aView; dictMode = mode; + + if(dictMode) { + words = [NSArray arrayWithArray:suggestions]; + } else { + suggestions = [someSuggestions retain]; + words = nil; + } } return self; } @@ -205,12 +211,15 @@ { NSImage* image = nil; NSString* imageName = nil; - imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"]; - if(imageName) - image = [NSImage imageNamed:imageName]; - [[aTableColumn dataCell] setImage:image]; + if(!dictMode) { + imageName = [[filtered objectAtIndex:rowIndex] objectForKey:@"image"]; + if(imageName) + image = [NSImage imageNamed:imageName]; + [[aTableColumn dataCell] setImage:image]; + return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"]; + } + return [filtered objectAtIndex:rowIndex]; - return [[filtered objectAtIndex:rowIndex] objectForKey:@"display"]; } // ==================== @@ -223,16 +232,24 @@ NSArray* newFiltered; if([mutablePrefix length] > 0) { - 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]; + 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]; + } } else { - newFiltered = suggestions; + if(dictMode) + newFiltered = nil; + else + newFiltered = suggestions; } NSPoint old = NSMakePoint([self frame].origin.x, [self frame].origin.y + [self frame].size.height); @@ -247,7 +264,10 @@ { for(i=0; i<[newFiltered count]; i++) { - item = [[newFiltered objectAtIndex:i] objectForKey:@"display"]; + if(dictMode) + item = [newFiltered objectAtIndex:i]; + else + item = [[newFiltered objectAtIndex:i] objectForKey:@"display"]; if([item length]>maxLen) maxLen = [item length]; } @@ -267,7 +287,7 @@ // 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,maxWidth,newHeight) display:YES]; + [self setFrame:NSMakeRect(old.x, old.y-newHeight, maxWidth, newHeight) display:YES]; [filtered release]; filtered = [newFiltered retain]; [theTableView reloadData]; @@ -401,7 +421,11 @@ return; id cur = [filtered objectAtIndex:row]; - NSString* curMatch = [cur objectForKey:@"match"] ?: [cur objectForKey:@"display"]; + NSString* curMatch; + if(dictMode) + curMatch = [NSString stringWithString:cur]; + else + curMatch = [cur objectForKey:@"match"] ?: [cur objectForKey:@"display"]; if([[self filterString] length] + 1 < [curMatch length]) { NSString* prefix = [curMatch substringToIndex:[[self filterString] length] + 1]; @@ -409,7 +433,11 @@ for(int i = row; i < [filtered count]; ++i) { id candidate = [filtered objectAtIndex:i]; - NSString* candidateMatch = [candidate objectForKey:@"match"] ?: [candidate objectForKey:@"display"]; + NSString* candidateMatch; + if(dictMode) + candidateMatch = [filtered objectAtIndex:i]; + else + candidateMatch = [candidate objectForKey:@"match"] ?: [candidate objectForKey:@"display"]; if([candidateMatch hasPrefix:prefix]) [candidates addObject:candidateMatch]; } @@ -421,10 +449,10 @@ if([[self filterString] length] < [commonPrefix length]) { - // NSString* toInsert = [commonPrefix substringFromIndex:[[self filterString] length]]; - // [mutablePrefix appendString:toInsert]; - // [self insert_text:toInsert]; [self insert_text:commonPrefix]; + NSString* toInsert = [commonPrefix substringFromIndex:[[self filterString] length]]; + [mutablePrefix appendString:toInsert]; + theCharRange = NSMakeRange(theCharRange.location,[commonPrefix length]); [self filter]; } } @@ -446,16 +474,14 @@ if([theTableView selectedRow] == -1) return; - NSMutableDictionary* selectedItem = [[[filtered objectAtIndex:[theTableView selectedRow]] mutableCopy] autorelease]; - - NSString* candidateMatch = [selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"]; - if([[self filterString] length] < [candidateMatch length]) - // [self insert_text:[candidateMatch substringFromIndex:[[self filterString] length]]]; - [self insert_text:candidateMatch]; - - // NSString* toInsert = [selectedItem objectForKey:@"insert"]; - // [self insert_text:toInsert]; - + if(dictMode){ + [self insert_text:[[[filtered objectAtIndex:[theTableView selectedRow]] mutableCopy] autorelease]]; + } else { + NSMutableDictionary* selectedItem = [[[filtered objectAtIndex:[theTableView selectedRow]] mutableCopy] autorelease]; + NSString* candidateMatch = [selectedItem objectForKey:@"match"] ?: [selectedItem objectForKey:@"display"]; + if([[self filterString] length] < [candidateMatch length]) + [self insert_text:candidateMatch]; + } closeMe = YES; } @end -- cgit v1.2.3