aboutsummaryrefslogtreecommitdiffstats
path: root/Source/CMTextView.m
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-05-27 17:35:20 +0000
committerstuconnolly <stuart02@gmail.com>2010-05-27 17:35:20 +0000
commitc06ad0189beb544710b6d47b2978a5614d036cdc (patch)
tree421a234a435e7fb8ca4bb6983e285e333457698d /Source/CMTextView.m
parent60ff002c794bc409c6c4db208cfbe1fe1c9fbe9f (diff)
downloadsequelpro-c06ad0189beb544710b6d47b2978a5614d036cdc.tar.gz
sequelpro-c06ad0189beb544710b6d47b2978a5614d036cdc.tar.bz2
sequelpro-c06ad0189beb544710b6d47b2978a5614d036cdc.zip
Rename CMTextView to SPTextView.
Diffstat (limited to 'Source/CMTextView.m')
-rw-r--r--Source/CMTextView.m3319
1 files changed, 0 insertions, 3319 deletions
diff --git a/Source/CMTextView.m b/Source/CMTextView.m
deleted file mode 100644
index 55c759a0..00000000
--- a/Source/CMTextView.m
+++ /dev/null
@@ -1,3319 +0,0 @@
-//
-// $Id$
-//
-// CMTextView.m
-// sequel-pro
-//
-// Created by Carsten Blüm.
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//
-// More info at <http://code.google.com/p/sequel-pro/>
-
-#import "CMTextView.h"
-#import "CustomQuery.h"
-#import "TableDocument.h"
-#import "SPStringAdditions.h"
-#import "SPArrayAdditions.h"
-#import "SPTextViewAdditions.h"
-#import "SPNarrowDownCompletion.h"
-#import "SPConstants.h"
-#import "SPQueryController.h"
-#import "SPTooltip.h"
-#import "TablesList.h"
-#import "SPNavigatorController.h"
-#import "SPAlertSheets.h"
-
-#pragma mark -
-#pragma mark lex init
-
-/*
- * Include all the extern variables and prototypes required for flex (used for syntax highlighting)
- */
-#import "SPEditorTokens.h"
-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 *);
-
-#pragma mark -
-#pragma mark attribute definition
-
-#define kAPlinked @"Linked" // attribute for a via auto-pair inserted char
-#define kAPval @"linked"
-#define kLEXToken @"Quoted" // set via lex to indicate a quoted string
-#define kLEXTokenValue @"isMarked"
-#define kSQLkeyword @"s" // attribute for found SQL keywords
-#define kQuote @"Quote"
-#define kQuoteValue @"isQuoted"
-#define kValue @"x"
-#define kBTQuote @"BTQuote"
-#define kBTQuoteValue @"isBTQuoted"
-
-#pragma mark -
-#pragma mark constant definitions
-
-#define SP_CQ_SEARCH_IN_MYSQL_HELP_MENU_ITEM_TAG 1000
-#define SP_CQ_COPY_AS_RTF_MENU_ITEM_TAG 1001
-#define SP_CQ_SELECT_CURRENT_QUERY_MENU_ITEM_TAG 1002
-
-#define SP_SYNTAX_HILITE_BIAS 2000
-#define SP_MAX_TEXT_SIZE_FOR_SYNTAX_HIGHLIGHTING 20000000
-
-#define MYSQL_DOC_SEARCH_URL @"http://dev.mysql.com/doc/refman/%@/en/%@.html"
-
-#pragma mark -
-
-// some helper functions for handling rectangles and points
-// needed in roundedBezierPathAroundRange:
-static inline CGFloat SPRectTop(NSRect rectangle) { return rectangle.origin.y; }
-static inline CGFloat SPRectBottom(NSRect rectangle) { return rectangle.origin.y+rectangle.size.height; }
-static inline CGFloat SPRectLeft(NSRect rectangle) { return rectangle.origin.x; }
-static inline CGFloat SPRectRight(NSRect rectangle) { return rectangle.origin.x+rectangle.size.width; }
-static inline CGFloat SPPointDistance(NSPoint a, NSPoint b) { return sqrt( (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) ); }
-static inline NSPoint SPPointOnLine(NSPoint a, NSPoint b, CGFloat t) { return NSMakePoint(a.x*(1.-t) + b.x*t, a.y*(1.-t) + b.y*t); }
-
-@implementation CMTextView
-
-@synthesize queryHiliteColor;
-@synthesize queryEditorBackgroundColor;
-@synthesize commentColor;
-@synthesize quoteColor;
-@synthesize keywordColor;
-@synthesize backtickColor;
-@synthesize numericColor;
-@synthesize variableColor;
-@synthesize otherTextColor;
-@synthesize queryRange;
-@synthesize shouldHiliteQuery;
-@synthesize completionIsOpen;
-@synthesize completionWasReinvokedAutomatically;
-
-/*
- * Sort function (mainly used to sort the words in the textView)
- */
-NSInteger alphabeticSort(id string1, id string2, void *reverse)
-{
- return [string1 localizedCaseInsensitiveCompare:string2];
-}
-
-- (void) awakeFromNib
-{
-
- prefs = [[NSUserDefaults standardUserDefaults] retain];
- [self setFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]];
-
- // Set self as delegate for the textView's textStorage to enable syntax highlighting,
- [[self textStorage] setDelegate:self];
-
- // Set defaults for general usage
- autoindentEnabled = NO;
- autopairEnabled = YES;
- autoindentIgnoresEnter = NO;
- autouppercaseKeywordsEnabled = NO;
- autohelpEnabled = NO;
- delBackwardsWasPressed = NO;
- startListeningToBoundChanges = NO;
- textBufferSizeIncreased = NO;
- snippetControlCounter = -1;
- mirroredCounter = -1;
- completionPopup = nil;
- completionIsOpen = NO;
- isProcessingMirroredSnippets = NO;
- completionWasRefreshed = NO;
-
- lineNumberView = [[NoodleLineNumberView alloc] initWithScrollView:scrollView];
- [scrollView setVerticalRulerView:lineNumberView];
- [scrollView setHasHorizontalRuler:NO];
- [scrollView setHasVerticalRuler:YES];
- [scrollView setRulersVisible:YES];
- [self setAllowsDocumentBackgroundColorChange:YES];
- [self setContinuousSpellCheckingEnabled:NO];
- [self setAutoindent:[prefs boolForKey:SPCustomQueryAutoIndent]];
- [self setAutoindentIgnoresEnter:YES];
- [self setAutopair:[prefs boolForKey:SPCustomQueryAutoPairCharacters]];
- [self setAutohelp:[prefs boolForKey:SPCustomQueryUpdateAutoHelp]];
- [self setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]];
- [self setCompletionWasReinvokedAutomatically:NO];
-
-
- // Re-define tab stops for a better editing
- [self setTabStops];
-
- // disabled to get the current text range in textView safer
- [[self layoutManager] setBackgroundLayoutEnabled:NO];
-
- // add NSViewBoundsDidChangeNotification to scrollView
- [[scrollView contentView] setPostsBoundsChangedNotifications:YES];
- NSNotificationCenter *aNotificationCenter = [NSNotificationCenter defaultCenter];
- [aNotificationCenter addObserver:self selector:@selector(boundsDidChangeNotification:) name:@"NSViewBoundsDidChangeNotification" object:[scrollView contentView]];
-
- [self setQueryHiliteColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorHighlightQueryColor]]];
- [self setQueryEditorBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBackgroundColor]]];
- [self setCommentColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorCommentColor]]];
- [self setQuoteColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorQuoteColor]]];
- [self setKeywordColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorSQLKeywordColor]]];
- [self setBacktickColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorBacktickColor]]];
- [self setNumericColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorNumericColor]]];
- [self setVariableColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorVariableColor]]];
- [self setOtherTextColor:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorTextColor]]];
- [self setTextColor:[self otherTextColor]];
- [self setInsertionPointColor:[self otherTextColor]];
- [self setShouldHiliteQuery:[prefs boolForKey:SPCustomQueryHighlightCurrentQuery]];
-
- // Register observers for the when editor background colors preference changes
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorFont options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorBackgroundColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorHighlightQueryColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryHighlightCurrentQuery options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorCommentColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorQuoteColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorSQLKeywordColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorBacktickColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorNumericColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorVariableColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorTextColor options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryEditorTabStopWidth options:NSKeyValueObservingOptionNew context:NULL];
- [prefs addObserver:self forKeyPath:SPCustomQueryAutoUppercaseKeywords options:NSKeyValueObservingOptionNew context:NULL];
-
-}
-
-- (void) setConnection:(MCPConnection *)theConnection withVersion:(NSInteger)majorVersion
-{
- mySQLConnection = theConnection;
- mySQLmajorVersion = majorVersion;
-}
-
-/**
- * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface.
- */
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- if ([keyPath isEqualToString:SPCustomQueryEditorBackgroundColor]) {
- [self setQueryEditorBackgroundColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- [self setNeedsDisplay:YES];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorFont]) {
- [self setFont:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- [self setNeedsDisplay:YES];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorHighlightQueryColor]) {
- [self setQueryHiliteColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- [self setNeedsDisplay:YES];
- } else if ([keyPath isEqualToString:SPCustomQueryHighlightCurrentQuery]) {
- [self setShouldHiliteQuery:[[change objectForKey:NSKeyValueChangeNewKey] boolValue]];
- [self setNeedsDisplay:YES];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorCommentColor]) {
- [self setCommentColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorQuoteColor]) {
- [self setQuoteColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorSQLKeywordColor]) {
- [self setKeywordColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorBacktickColor]) {
- [self setBacktickColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorNumericColor]) {
- [self setNumericColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorVariableColor]) {
- [self setVariableColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorTextColor]) {
- [self setOtherTextColor:[NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]];
- [self setTextColor:[self otherTextColor]];
- if([[self string] length]<100000 && [self isEditable])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.1];
- } else if ([keyPath isEqualToString:SPCustomQueryEditorTabStopWidth]) {
- [self setTabStops];
- } else if ([keyPath isEqualToString:SPCustomQueryAutoUppercaseKeywords]) {
- [self setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]];
- }
-}
-
-/*
- * 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)
- *
- * [NSDictionary dictionaryWithObjectsAndKeys:@"foo", @"display", @"`foo`", @"match", @"func-small", @"image", nil]
- */
-- (NSArray *)suggestionsForSQLCompletionWith:(NSString *)currentWord dictMode:(BOOL)isDictMode browseMode:(BOOL)dbBrowseMode withTableName:(NSString*)aTableName withDbName:(NSString*)aDbName
-{
-
- NSMutableArray *possibleCompletions = [[NSMutableArray alloc] initWithCapacity:32];
- if(currentWord == nil) currentWord = [NSString stringWithString:@""];
- // If caret is not inside backticks add keywords and all words coming from the view.
- if(!dbBrowseMode)
- {
- // Only parse for words if text size is less than 6MB
- if([[self string] length] && [[self string] length]<6000000)
- {
- NSMutableSet *uniqueArray = [NSMutableSet setWithCapacity:5];
-
- for(id w in [[self textStorage] words])
- [uniqueArray addObject:[w string]];
- // Remove current word from list
-
- [uniqueArray removeObject:currentWord];
-
- NSInteger reverseSort = NO;
-
- for(id w in [[uniqueArray allObjects] sortedArrayUsingFunction:alphabeticSort context:&reverseSort])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"dummy-small", @"image", nil]];
-
- }
-
- if(!isDictMode) {
- // Add predefined keywords
- NSArray *keywordList = [[NSArray arrayWithArray:[[SPQueryController sharedQueryController] keywordList]] retain];
- for(id s in keywordList)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:s, @"display", @"dummy-small", @"image", nil]];
-
- // Add predefined functions
- NSArray *functionList = [[NSArray arrayWithArray:[[SPQueryController sharedQueryController] functionList]] retain];
- for(id s in functionList)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:s, @"display", @"func-small", @"image", nil]];
-
- [functionList release];
- [keywordList release];
- }
-
- }
-
- if(!isDictMode && [mySQLConnection isConnected])
- {
- // Add structural db/table/field data to completions list or fallback to gathering TablesList data
-
- NSString* connectionID;
- if(tableDocumentInstance)
- connectionID = [tableDocumentInstance connectionID];
- else
- connectionID = @"_";
-
- // Try to get structure data
- NSDictionary *dbs = [NSDictionary dictionaryWithDictionary:[[SPNavigatorController sharedNavigatorController] dbStructureForConnection:connectionID]];
-
- if(dbs != nil && [dbs isKindOfClass:[NSDictionary class]] && [dbs count]) {
- NSMutableArray *allDbs = [NSMutableArray array];
- [allDbs addObjectsFromArray:[dbs allKeys]];
-
- NSSortDescriptor *desc = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
- NSMutableArray *sortedDbs = [NSMutableArray array];
- [sortedDbs addObjectsFromArray:[allDbs sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]]];
-
- NSString *currentDb = nil;
- NSString *currentTable = nil;
-
- if (tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, [tablesListInstance selectedDatabase]];
- if (tablesListInstance && [tablesListInstance tableName])
- currentTable = [tablesListInstance tableName];
-
- // Put current selected db at the top
- if(aTableName == nil && aDbName == nil && [tablesListInstance selectedDatabase]) {
- currentDb = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, [tablesListInstance selectedDatabase]];
- [sortedDbs removeObject:currentDb];
- [sortedDbs insertObject:currentDb atIndex:0];
- }
-
- NSString* aTableName_id;
- NSString* aDbName_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, aDbName];
- if(aDbName && aTableName)
- aTableName_id = [NSString stringWithFormat:@"%@%@%@", aDbName_id, SPUniqueSchemaDelimiter, aTableName];
- else
- aTableName_id = [NSString stringWithFormat:@"%@%@%@", currentDb, SPUniqueSchemaDelimiter, aTableName];
-
-
- // Put information_schema and/or mysql db at the end if not selected
- NSString* mysql_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"mysql"];
- NSString* inf_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, @"information_schema"];
- if(currentDb && ![currentDb isEqualToString:mysql_id] && [sortedDbs containsObject:mysql_id]) {
- [sortedDbs removeObject:mysql_id];
- [sortedDbs addObject:mysql_id];
- }
- if(currentDb && ![currentDb isEqualToString:inf_id] && [sortedDbs containsObject:inf_id]) {
- [sortedDbs removeObject:inf_id];
- [sortedDbs addObject:inf_id];
- }
-
- BOOL aTableNameExists = NO;
- if(!aDbName) {
-
- // Try to suggest only items which are uniquely valid for the parsed string
- NSArray *uniqueSchema = [[SPNavigatorController sharedNavigatorController] getUniqueDbIdentifierFor:[aTableName lowercaseString] andConnection:[[[self delegate] valueForKeyPath:@"tableDocumentInstance"] connectionID]];
- NSInteger uniqueSchemaKind = [[uniqueSchema objectAtIndex:0] intValue];
-
- // 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] isKindOfClass:[NSDictionary class]]
- && [[dbs objectForKey:currentDb] objectForKey:[NSString stringWithFormat:@"%@%@%@", currentDb, SPUniqueSchemaDelimiter, [uniqueSchema objectAtIndex:1]]]
- && uniqueSchemaKind == 2) {
- aTableNameExists = YES;
- aTableName = [uniqueSchema objectAtIndex:1];
- aTableName_id = [NSString stringWithFormat:@"%@%@%@", currentDb, SPUniqueSchemaDelimiter, aTableName];
- aDbName_id = [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_id = [NSString stringWithFormat:@"%@%@%@", connectionID, SPUniqueSchemaDelimiter, [uniqueSchema objectAtIndex:1]];
- aTableNameExists = NO;
- }
-
- } else if (aDbName && [aDbName length]) {
- if(aTableName && [aTableName length]
- && [dbs objectForKey:aDbName_id] && [[dbs objectForKey:aDbName_id] isKindOfClass:[NSDictionary class]]
- && [[dbs objectForKey:aDbName_id] objectForKey:[NSString stringWithFormat:@"%@%@%@", aDbName_id, SPUniqueSchemaDelimiter, aTableName]]) {
- aTableNameExists = YES;
- }
- }
-
- // If aDbName exist show only those table
- if([allDbs containsObject:aDbName_id]) {
- [sortedDbs removeAllObjects];
- [sortedDbs addObject:aDbName_id];
- }
-
- for(id db in sortedDbs) {
-
- NSArray *allTables;
- if([[dbs objectForKey:db] isKindOfClass:[NSDictionary class]])
- allTables = [[dbs objectForKey:db] allKeys];
- else {
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[[[dbs objectForKey:db] description] componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"database-small", @"image", @"", @"isRef", nil]];
- continue;
- }
-
- NSString *dbpath = [db substringFromIndex:[db rangeOfString:SPUniqueSchemaDelimiter].location];
-
- NSMutableArray *sortedTables = [NSMutableArray array];
- if(aTableNameExists) {
- [sortedTables addObject:aTableName_id];
- } else {
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[db componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"database-small", @"image", @"", @"isRef", nil]];
- [sortedTables addObjectsFromArray:[allTables sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]]];
- if([sortedTables count] > 1 && [sortedTables containsObject:[NSString stringWithFormat:@"%@%@%@", db, SPUniqueSchemaDelimiter, currentTable]]) {
- [sortedTables removeObject:[NSString stringWithFormat:@"%@%@%@", db, SPUniqueSchemaDelimiter, currentTable]];
- [sortedTables insertObject:[NSString stringWithFormat:@"%@%@%@", db, SPUniqueSchemaDelimiter, currentTable] atIndex:0];
- }
- }
- for(id table in sortedTables) {
- NSDictionary *theTable = [[dbs objectForKey:db] objectForKey:table];
- NSString *tablepath = [table substringFromIndex:[table rangeOfString:SPUniqueSchemaDelimiter].location];
- NSArray *allFields = [theTable allKeys];
- NSInteger structtype = [[theTable objectForKey:@" struct_type "] intValue];
- BOOL breakFlag = NO;
- if(!aTableNameExists)
- switch(structtype) {
- case 0:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[table componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"table-small-square", @"image", tablepath, @"path", @"", @"isRef", nil]];
- break;
- case 1:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[table componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"table-view-small-square", @"image", tablepath, @"path", @"", @"isRef", nil]];
- break;
- case 2:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[table componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"proc-small", @"image", tablepath, @"path", @"", @"isRef", nil]];
- breakFlag = YES;
- break;
- case 3:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[[table componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display", @"func-small", @"image", tablepath, @"path", @"", @"isRef", nil]];
- breakFlag = YES;
- break;
- }
- if(!breakFlag) {
- NSArray *sortedFields = [allFields sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]];
- for(id field in sortedFields) {
- if(![field hasPrefix:@" "]) {
- NSString *fieldpath = [field substringFromIndex:[field rangeOfString:SPUniqueSchemaDelimiter].location];
- NSArray *def = [theTable objectForKey:field];
- NSString *typ = [NSString stringWithFormat:@"%@ %@ %@", [def objectAtIndex:0], [def objectAtIndex:3], [def objectAtIndex:5]];
- // Check if type definition contains a , if so replace the bracket content by … and add
- // the bracket content as "list" key to prevend the token field to split them by ,
- if(typ && [typ rangeOfString:@","].length) {
- NSString *t = [typ stringByReplacingOccurrencesOfRegex:@"\\(.*?\\)" withString:@"(…)"];
- NSString *lst = [typ stringByMatching:@"\\(([^\\)]*?)\\)" capture:1L];
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
- [[field componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display",
- @"field-small-square", @"image",
- fieldpath, @"path",
- t, @"type",
- lst, @"list",
- @"", @"isRef",
- nil]];
- } else {
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
- [[field componentsSeparatedByString:SPUniqueSchemaDelimiter] lastObject], @"display",
- @"field-small-square", @"image",
- fieldpath, @"path",
- typ, @"type",
- @"", @"isRef",
- nil]];
- }
- }
- }
- }
- }
- }
- if(desc) [desc release];
- } else {
-
- // [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 [tablesListInstance allDatabaseNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"database-small", @"image", @"", @"isRef", nil]];
-
- // Add all system database names to completions list
- for (id obj in [tablesListInstance allSystemDatabaseNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"database-small", @"image", @"", @"isRef", nil]];
-
- // Add table names to completions list
- for (id obj in [tablesListInstance allTableNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"table-small-square", @"image", @"", @"isRef", nil]];
-
- // Add view names to completions list
- for (id obj in [tablesListInstance allViewNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"table-view-small-square", @"image", @"", @"isRef", nil]];
-
- // Add field names to completions list for currently selected table
- if ([tableDocumentInstance table] != nil)
- for (id obj in [[tableDocumentInstance valueForKeyPath:@"tableDataInstance"] valueForKey:@"columnNames"])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"field-small-square", @"image", @"", @"isRef", nil]];
-
- // Add proc/func only for MySQL version 5 or higher
- if(mySQLmajorVersion > 4) {
- // Add all procedures to completions list for currently selected table
- for (id obj in [tablesListInstance allProcedureNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"proc-small", @"image", @"", @"isRef", nil]];
-
- // Add all function to completions list for currently selected table
- for (id obj in [tablesListInstance allFunctionNames])
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:obj, @"display", @"func-small", @"image", @"", @"isRef", nil]];
- }
- }
- }
-
- return [possibleCompletions autorelease];
-
-}
-
-- (void) doAutoCompletion
-{
- if(completionIsOpen || !self || ![self delegate]) return;
-
- // Cancel autocompletion trigger
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
-
- NSRange r = [self selectedRange];
-
- if(![self delegate] || ![[self delegate] isKindOfClass:[CustomQuery class]] || r.length || snippetControlCounter > -1) return;
-
- if(r.location) {
- NSCharacterSet *ignoreCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\"'`;,()[]{}=+/<> \t\n\r"];
-
- // Check the previous character and don't autocomplete if the character is whitespace or certain types of punctuation
- if ([ignoreCharacterSet characterIsMember:[[self string] characterAtIndex:r.location - 1]]) return;
-
- // Suppress auto-completion if the window isn't active anymore
- if ([[NSApp keyWindow] firstResponder] != self) return;
-
- // Trigger the completion
- [self doCompletionByUsingSpellChecker:NO fuzzyMode:NO autoCompleteMode:YES];
- }
-
-}
-
-- (void) refreshCompletion
-{
- if(completionWasRefreshed) return;
- completionWasRefreshed = YES;
- [self doCompletionByUsingSpellChecker:NO fuzzyMode:completionFuzzyMode autoCompleteMode:NO];
-}
-
-- (void) doCompletionByUsingSpellChecker:(BOOL)isDictMode fuzzyMode:(BOOL)fuzzySearch autoCompleteMode:(BOOL)autoCompleteMode
-{
-
- // Cancel autocompletion trigger
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- if(![self isEditable] || (completionIsOpen && !completionWasReinvokedAutomatically)) {
- return;
- }
-
- [self breakUndoCoalescing];
-
- // Remember state for refreshCompletion
- completionFuzzyMode = fuzzySearch;
-
- NSUInteger caretPos = NSMaxRange([self selectedRange]);
-
- BOOL caretMovedLeft = NO;
-
- // Check if caret is located after a ` - if so move caret inside
- if(!autoCompleteMode && [[self string] length] && caretPos > 0 && [[self string] characterAtIndex:caretPos-1] == '`') {
- if([[self string] length] > caretPos && [[self string] characterAtIndex:caretPos] == '`') {
- ;
- } else {
- caretPos--;
- caretMovedLeft = YES;
- [self setSelectedRange:NSMakeRange(caretPos, 0)];
- }
- }
-
- NSString* filter;
- NSString* dbName = nil;
- NSString* tableName = nil;
- NSRange completionRange = [self getRangeForCurrentWord];
- NSRange parseRange = completionRange;
- NSString* currentWord = [[self string] substringWithRange:completionRange];
- NSString* prefix = @"";
- NSString *currentDb = nil;
-
- // Break for long stuff
- if(completionRange.length>100000) return;
-
- NSString* allow; // additional chars which won't close the suggestion list window
- if(isDictMode)
- allow= @"_";
- else
- allow= @"_. ";
-
- BOOL dbBrowseMode = NO;
- NSInteger backtickMode = 0; // 0 none, 1 rigth only, 2 left only, 3 both
- BOOL caseInsensitive = YES;
-
- // Remove that attribute to suppress auto-uppercasing of certain keyword combinations
- if(![self selectedRange].length && [self selectedRange].location)
- [[self textStorage] removeAttribute:kSQLkeyword range:completionRange];
-
- [self setSelectedRange:NSMakeRange(caretPos, 0)];
-
- if(!isDictMode) {
-
- // Parse for leading db.table.field infos
-
- if(tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [tablesListInstance selectedDatabase];
- else
- currentDb = @"";
-
- BOOL caretIsInsideBackticks = NO;
-
- // Is the caret inside backticks
- // Do not using attribute:atIndex: since it could return wrong results due to editing.
- // This approach counts the number of ` up to the beginning of the current line from caret position
- NSRange lineHeadRange = [[self string] lineRangeForRange:NSMakeRange(caretPos, 0)];
- NSString *lineHead = [[self string] substringWithRange:NSMakeRange(lineHeadRange.location, caretPos - lineHeadRange.location)];
- for(NSUInteger i=0; i<[lineHead length]; i++)
- if([lineHead characterAtIndex:i]=='`') caretIsInsideBackticks = !caretIsInsideBackticks;
-
- NSCharacterSet *whiteSpaceCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
- NSUInteger start = caretPos;
- NSInteger backticksCounter = (caretIsInsideBackticks) ? 1 : 0;
- 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]) {
- start++;
- break;
- }
- if(currentCharacter == '.' && !(backticksCounter%2)) {
- pointCounter++;
- switch(pointCounter) {
- case 1:
- firstPoint = start;
- break;
- case 2:
- secondPoint = start;
- break;
- default:
- doParsing = NO;
- start++;
- }
- }
- if(doParsing && currentCharacter == '`') {
- backticksCounter++;
- if(!(backticksCounter%2) && start > 0) {
- currentCharacter = [[self string] characterAtIndex:start-1];
- if(currentCharacter != '`' && currentCharacter != '.') break;
- if(currentCharacter == '`') { // ignore ``
- backticksCounter++;
- start--;
- }
- }
- }
- }
-
- dbBrowseMode = (pointCounter || backticksCounter);
-
- if(dbBrowseMode) {
- parseRange = NSMakeRange(start, caretPos-start);
-
- // Break for long stuff
- if(parseRange.length>100000) return;
-
- NSString *parsedString = [[self string] substringWithRange:parseRange];
-
- // Check if parsed string is wrapped by ``
- 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;
- }
- }
-
- // Normalize point positions
- firstPoint-=start;
- secondPoint-=start;
-
- if(secondPoint>0) {
- dbName = [[[parsedString substringWithRange:NSMakeRange(0, secondPoint)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- tableName = [[[parsedString substringWithRange:NSMakeRange(secondPoint+1,firstPoint-secondPoint-1)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- filter = [[[parsedString substringWithRange:NSMakeRange(firstPoint+1,[parsedString length]-firstPoint-1)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- } else if(firstPoint>0) {
- tableName = [[[parsedString substringWithRange:NSMakeRange(0, firstPoint)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- filter = [[[parsedString substringWithRange:NSMakeRange(firstPoint+1,[parsedString length]-firstPoint-1)] stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- } else {
- filter = [[parsedString stringByReplacingOccurrencesOfString:@"``" withString:@"`"] stringByReplacingOccurrencesOfRegex:@"^`|`$" withString:@""];
- }
-
- // 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];
- }
-
- if(fuzzySearch) {
- filter = [[NSString stringWithString:[[self string] substringWithRange:parseRange]] stringByReplacingOccurrencesOfString:@"`" withString:@""];
- completionRange = parseRange;
- }
-
- } else {
- filter = [NSString stringWithString:currentWord];
- }
- } else {
- filter = [NSString stringWithString:currentWord];
- }
-
- // Cancel autocompletion trigger again if user typed something in while parsing
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- if (completionIsOpen) [completionPopup close], completionPopup = nil;
- completionIsOpen = YES;
- completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:[self suggestionsForSQLCompletionWith:currentWord dictMode:isDictMode browseMode:dbBrowseMode withTableName:tableName withDbName:dbName]
- alreadyTyped:filter
- staticPrefix:prefix
- additionalWordCharacters:allow
- caseSensitive:!caseInsensitive
- charRange:completionRange
- parseRange:parseRange
- inView:self
- dictMode:isDictMode
- dbMode:dbBrowseMode
- tabTriggerMode:[self isSnippetMode]
- fuzzySearch:fuzzySearch
- backtickMode:backtickMode
- withDbName:dbName
- withTableName:tableName
- selectedDb:currentDb
- caretMovedLeft:caretMovedLeft
- autoComplete:autoCompleteMode
- oneColumn:isDictMode
- isQueryingDBStructure:[mySQLConnection isQueryingDatabaseStructure]];
-
- completionParseRangeLocation = parseRange.location;
-
- //Get the NSPoint of the first character of the current word
- NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(completionRange.location,0) 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;
-
- // Adjust list location to be under the current word or insertion point
- pos.y -= [[self font] pointSize]*1.25;
-
- [completionPopup setCaretPos:pos];
- [completionPopup orderFront:self];
- [completionPopup insertCommonPrefix];
-
-}
-
-
-/*
- * Returns the associated line number for a character position inside of the CMTextView
- */
-- (NSUInteger) getLineNumberForCharacterIndex:(NSUInteger)anIndex
-{
- return [lineNumberView lineNumberForCharacterIndex:anIndex inText:[self string]]+1;
-}
-
-/*
- * Checks if the char after the current caret position/selection matches a supplied attribute
- */
-- (BOOL) isNextCharMarkedBy:(id)attribute withValue:(id)aValue
-{
- NSUInteger caretPosition = [self selectedRange].location;
-
- // Perform bounds checking
- if (caretPosition >= [[self string] length]) return NO;
-
- // Perform the check
- if ([[[self textStorage] attribute:attribute atIndex:caretPosition effectiveRange:nil] isEqualToString:aValue])
- return YES;
-
- return NO;
-}
-
-/*
- * Checks if the caret adjoins to an alphanumeric char |word or word| or wo|rd
- * Exception for word| and char is a “(” to allow e.g. auto-pairing () for functions
- */
-- (BOOL) isCaretAdjacentToAlphanumCharWithInsertionOf:(unichar)aChar
-{
- NSUInteger caretPosition = [self selectedRange].location;
- NSCharacterSet *alphanum = [NSCharacterSet alphanumericCharacterSet];
- BOOL leftIsAlphanum = NO;
- BOOL rightIsAlphanum = NO;
- BOOL charIsOpenBracket = (aChar == '(');
- NSUInteger bufferLength = [[self string] length];
-
- if(!bufferLength) return NO;
-
- // Check previous/next character for being alphanum
- // @try block for bounds checking
- @try
- {
- if(caretPosition==0)
- leftIsAlphanum = NO;
- else
- leftIsAlphanum = [alphanum characterIsMember:[[self string] characterAtIndex:caretPosition-1]] && !charIsOpenBracket;
- } @catch(id ae) { }
- @try {
- if(caretPosition >= bufferLength)
- rightIsAlphanum = NO;
- else
- rightIsAlphanum= [alphanum characterIsMember:[[self string] characterAtIndex:caretPosition]];
-
- } @catch(id ae) { }
-
- return (leftIsAlphanum ^ rightIsAlphanum || leftIsAlphanum && rightIsAlphanum);
-}
-
-/*
- * Checks if the caret is wrapped by auto-paired characters.
- * e.g. [| := caret]: "|"
- */
-- (BOOL) areAdjacentCharsLinked
-{
- NSUInteger caretPosition = [self selectedRange].location;
- unichar leftChar, matchingChar;
-
- // Perform bounds checking
- if ([self selectedRange].length) return NO;
- if (caretPosition < 1) return NO;
- if (caretPosition >= [[self string] length]) return NO;
-
- // Check the character to the left of the cursor and set the pairing character if appropriate
- leftChar = [[self string] characterAtIndex:caretPosition - 1];
- if (leftChar == '(')
- matchingChar = ')';
- else if (leftChar == '"' || leftChar == '`' || leftChar == '\'')
- matchingChar = leftChar;
- else if (leftChar == '{')
- matchingChar = '}';
- else
- return NO;
-
- // Check that the pairing character exists after the caret, and is tagged with the link attribute
- if (matchingChar == [[self string] characterAtIndex:caretPosition]
- && [[[self textStorage] attribute:kAPlinked atIndex:caretPosition effectiveRange:nil] isEqualToString:kAPval]) {
- return YES;
- }
-
- return NO;
-}
-
-#pragma mark -
-#pragma mark user actions
-
-- (IBAction)printDocument:(id)sender
-{
-
- // If Extended Table Info tab is active delegate the print call to the SPPrintController
- // if the user doesn't select anything in self
- if([[[[self delegate] class] description] isEqualToString:@"SPExtendedTableInfo"] && ![self selectedRange].length) {
- [[[self delegate] valueForKeyPath:@"tableDocumentInstance"] printDocument:sender];
- return;
- }
-
- // This will scale the view to fit the page without centering it.
- [[NSPrintInfo sharedPrintInfo] setHorizontalPagination:NSFitPagination];
- [[NSPrintInfo sharedPrintInfo] setHorizontallyCentered:NO];
- [[NSPrintInfo sharedPrintInfo] setVerticallyCentered:NO];
-
- NSRange r = NSMakeRange(0, [[self string] length]);
-
- // Remove all colors before printing for large text buffer
- if(r.length > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING) {
- // Cancel all doSyntaxHighlighting requests
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doSyntaxHighlighting)
- object:nil];
- [[self textStorage] removeAttribute:NSForegroundColorAttributeName range:r];
- [[self textStorage] removeAttribute:kLEXToken range:r];
- [[self textStorage] ensureAttributesAreFixedInRange:r];
-
- }
- [[self textStorage] ensureAttributesAreFixedInRange:r];
-
- // Setup the print operation with the print info and view
- NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:self printInfo:[NSPrintInfo sharedPrintInfo]];
-
- // Order out print sheet
- [printOperation runOperationModalForWindow:[self window] delegate:nil didRunSelector:NULL contextInfo:NULL];
-
-}
-
-- (void)printOperationDidRun:(NSPrintOperation *)printOperation success:(BOOL)success contextInfo:(void *)contextInfo
-{
- // Refresh syntax highlighting
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.01];
-}
-
-/*
- * Search for the current selection or current word in the MySQL Help
- */
-- (IBAction) showMySQLHelpForCurrentWord:(id)sender
-{
- [customQueryInstance showHelpForCurrentWord:self];
-}
-
-/*
- * If the textview has a selection, wrap it with the supplied prefix and suffix strings;
- * return whether or not any wrap was performed.
- */
-- (BOOL) wrapSelectionWithPrefix:(NSString *)prefix suffix:(NSString *)suffix
-{
-
- NSRange currentRange = [self selectedRange];
-
- // Only proceed if a selection is active
- if (currentRange.length == 0 || ![self isEditable])
- return NO;
-
- NSString *selString = [[self string] substringWithRange:currentRange];
-
- // Replace the current selection with the selected string wrapped in prefix and suffix
- [self insertText:[NSString stringWithFormat:@"%@%@%@", prefix, selString, suffix]];
-
- // Re-select original selection
- NSRange innerSelectionRange = NSMakeRange(currentRange.location+1, [selString length]);
- [self setSelectedRange:innerSelectionRange];
-
- // If autopair is enabled mark last autopair character as autopair-linked
- if([prefs boolForKey:SPCustomQueryAutoPairCharacters])
- [[self textStorage] addAttribute:kAPlinked value:kAPval range:NSMakeRange(NSMaxRange(innerSelectionRange), 1)];
-
- return YES;
-}
-
-/*
- * Copy selected text chunk as RTF to preserve syntax highlighting
- */
-- (void) copyAsRTF
-{
-
- NSPasteboard *pb = [NSPasteboard generalPasteboard];
- NSTextStorage *textStorage = [self textStorage];
- NSData *rtf = [textStorage RTFFromRange:[self selectedRange]
- documentAttributes:nil];
-
- if (rtf)
- {
- [pb declareTypes:[NSArray arrayWithObject:NSRTFPboardType] owner:self];
- [pb setData:rtf forType:NSRTFPboardType];
- }
-}
-
-- (void) selectCurrentQuery
-{
- if([self isEditable])
- [customQueryInstance selectCurrentQuery];
-}
-
-/*
- * Selects the line lineNumber relatively to a selection (if given) and scrolls to it
- */
-- (void) selectLineNumber:(NSUInteger)lineNumber ignoreLeadingNewLines:(BOOL)ignLeadingNewLines
-{
- NSRange selRange;
- NSArray *lineRanges;
- if([self selectedRange].length)
- lineRanges = [[[self string] substringWithRange:[self selectedRange]] lineRangesForRange:NSMakeRange(0, [self selectedRange].length)];
- else
- lineRanges = [[self string] lineRangesForRange:NSMakeRange(0, [[self string] length])];
-
- if(ignLeadingNewLines) // ignore leading empty lines
- {
- NSUInteger arrayCount = [lineRanges count];
- NSUInteger i;
- for (i = 0; i < arrayCount; i++) {
- if(NSRangeFromString([lineRanges objectAtIndex:i]).length > 0)
- break;
- lineNumber++;
- }
- }
-
- // Safety-check the line number
- if (lineNumber > [lineRanges count]) lineNumber = [lineRanges count];
- if (lineNumber < 1) lineNumber = 1;
-
- // Grab the range to select
- selRange = NSRangeFromString([lineRanges objectAtIndex:lineNumber-1]);
-
- // adjust selRange if a selection was given
- if([self selectedRange].length)
- selRange.location += [self selectedRange].location;
- [self setSelectedRange:selRange];
- [self scrollRangeToVisible:selRange];
-}
-
-/*
- * Shifts the selection, if any, rightwards by indenting any selected lines with one tab.
- * If the caret is within a line, the selection is not changed after the index; if the selection
- * has length, all lines crossed by the length are indented and fully selected.
- * Returns whether or not an indentation was performed.
- */
-- (BOOL) shiftSelectionRight
-{
- NSString *textViewString = [[self textStorage] string];
- NSRange currentLineRange;
- NSArray *lineRanges;
- NSString *tabString = @"\t";
- NSUInteger i, indentedLinesLength = 0;
-
- if ([self selectedRange].location == NSNotFound || ![self isEditable]) return NO;
-
- // Indent the currently selected line if the caret is within a single line
- if ([self selectedRange].length == 0) {
-
- // Extract the current line range based on the text caret
- currentLineRange = [textViewString lineRangeForRange:[self selectedRange]];
-
- // Register the indent for undo
- [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 0) replacementString:tabString];
-
- // Insert the new tab
- [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 0) withString:tabString];
-
- return YES;
- }
-
- // Otherwise, the selection has a length - get an array of current line ranges for the specified selection
- lineRanges = [textViewString lineRangesForRange:[self selectedRange]];
-
- // Loop through the ranges, storing a count of the overall length.
- for (i = 0; i < [lineRanges count]; i++) {
- currentLineRange = NSRangeFromString([lineRanges objectAtIndex:i]);
- indentedLinesLength += currentLineRange.length + 1;
-
- // Register the indent for undo
- [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location+i, 0) replacementString:tabString];
-
- // Insert the new tab
- [self replaceCharactersInRange:NSMakeRange(currentLineRange.location+i, 0) withString:tabString];
- }
-
- // Select the entirety of the new range
- [self setSelectedRange:NSMakeRange(NSRangeFromString([lineRanges objectAtIndex:0]).location, indentedLinesLength)];
-
- return YES;
-}
-
-
-/*
- * Shifts the selection, if any, leftwards by un-indenting any selected lines by one tab if possible.
- * If the caret is within a line, the selection is not changed after the undent; if the selection has
- * length, all lines crossed by the length are un-indented and fully selected.
- * Returns whether or not an indentation was performed.
- */
-- (BOOL) shiftSelectionLeft
-{
- NSString *textViewString = [[self textStorage] string];
- NSRange currentLineRange;
- NSArray *lineRanges;
- NSUInteger i, unindentedLines = 0, unindentedLinesLength = 0;
-
- if ([self selectedRange].location == NSNotFound) return NO;
-
- // Undent the currently selected line if the caret is within a single line
- if ([self selectedRange].length == 0) {
-
- // Extract the current line range based on the text caret
- currentLineRange = [textViewString lineRangeForRange:[self selectedRange]];
-
- // Ensure that the line has length and that the first character is a tab
- if (currentLineRange.length < 1
- || [textViewString characterAtIndex:currentLineRange.location] != '\t')
- return NO;
-
- // Register the undent for undo
- [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 1) replacementString:@""];
-
- // Remove the tab
- [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 1) withString:@""];
-
- return YES;
- }
-
- // Otherwise, the selection has a length - get an array of current line ranges for the specified selection
- lineRanges = [textViewString lineRangesForRange:[self selectedRange]];
-
- // Loop through the ranges, storing a count of the total lines changed and the new length.
- for (i = 0; i < [lineRanges count]; i++) {
- currentLineRange = NSRangeFromString([lineRanges objectAtIndex:i]);
- unindentedLinesLength += currentLineRange.length;
-
- // Ensure that the line has length and that the first character is a tab
- if (currentLineRange.length < 1
- || [textViewString characterAtIndex:currentLineRange.location-unindentedLines] != '\t')
- continue;
-
- // Register the undent for undo
- [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location-unindentedLines, 1) replacementString:@""];
-
- // Remove the tab
- [self replaceCharactersInRange:NSMakeRange(currentLineRange.location-unindentedLines, 1) withString:@""];
-
- // As a line has been unindented, modify counts and lengths
- unindentedLines++;
- unindentedLinesLength--;
- }
-
- // If a change was made, select the entirety of the new range and return success
- if (unindentedLines) {
- [self setSelectedRange:NSMakeRange(NSRangeFromString([lineRanges objectAtIndex:0]).location, unindentedLinesLength)];
- return YES;
- }
-
- return NO;
-}
-
-#pragma mark -
-#pragma mark snippet handler
-
-/*
- * Reset snippet controller variables to end a snippet session
- */
-- (void)endSnippetSession
-{
- snippetControlCounter = -1;
- currentSnippetIndex = -1;
- snippetControlMax = -1;
- mirroredCounter = -1;
- snippetWasJustInserted = NO;
-}
-
-/*
- * Shows pre-defined completion list
- */
-- (void)showCompletionListFor:(NSString*)kind atRange:(NSRange)aRange fuzzySearch:(BOOL)fuzzySearchMode
-{
-
- // Cancel auto-completion timer
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- NSMutableArray *possibleCompletions = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
-
- NSString *connectionID;
- if(tableDocumentInstance)
- connectionID = [tableDocumentInstance connectionID];
- else
- connectionID = @"_";
-
- NSArray *arr = nil;
- if([kind isEqualToString:@"$SP_ASLIST_ALL_TABLES"]) {
- NSString *currentDb = nil;
-
- if (tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [tablesListInstance selectedDatabase];
-
- NSDictionary *dbs = [NSDictionary dictionaryWithDictionary:[[mySQLConnection getDbStructure] objectForKey:connectionID]];
-
- if(currentDb != nil && dbs != nil && [dbs count] && [dbs objectForKey:currentDb]) {
- NSArray *allTables = [[dbs objectForKey:currentDb] allKeys];
- NSSortDescriptor *desc = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
- NSArray *sortedTables = [allTables sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]];
- [desc release];
- for(id table in sortedTables) {
- NSDictionary * theTable = [[dbs objectForKey:currentDb] objectForKey:table];
- NSInteger structtype = [[theTable objectForKey:@" struct_type "] intValue];
- switch(structtype) {
- case 0:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"table-small-square", @"image", currentDb, @"path", @"", @"isRef", nil]];
- break;
- case 1:
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:table, @"display", @"table-view-small-square", @"image", currentDb, @"path", @"", @"isRef", nil]];
- break;
- }
- }
- } else {
- arr = [NSArray arrayWithArray:[[[self delegate] valueForKeyPath:@"tablesListInstance"] allTableAndViewNames]];
- if(arr == nil) {
- arr = [NSArray array];
- }
- for(id w in arr)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"table-small-square", @"image", @"", @"isRef", nil]];
- }
- }
- else if([kind isEqualToString:@"$SP_ASLIST_ALL_DATABASES"]) {
- arr = [NSArray arrayWithArray:[[[self delegate] valueForKeyPath:@"tablesListInstance"] allDatabaseNames]];
- if(arr == nil) {
- arr = [NSArray array];
- }
- for(id w in arr)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"database-small", @"image", @"", @"isRef", nil]];
- arr = [NSArray arrayWithArray:[[[self delegate] valueForKeyPath:@"tablesListInstance"] allSystemDatabaseNames]];
- if(arr == nil) {
- arr = [NSArray array];
- }
- for(id w in arr)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"database-small", @"image", @"", @"isRef", nil]];
- }
- else if([kind isEqualToString:@"$SP_ASLIST_ALL_FIELDS"]) {
-
- NSString *currentDb = nil;
- NSString *currentTable = nil;
-
- if (tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [tablesListInstance selectedDatabase];
- if (tablesListInstance && [tablesListInstance tableName])
- currentTable = [tablesListInstance tableName];
-
- NSDictionary *dbs = [NSDictionary dictionaryWithDictionary:[[mySQLConnection getDbStructure] objectForKey:connectionID]];
- if(currentDb != nil && currentTable != nil && dbs != nil && [dbs count] && [dbs objectForKey:currentDb] && [[dbs objectForKey:currentDb] objectForKey:currentTable]) {
- NSDictionary * theTable = [[dbs objectForKey:currentDb] objectForKey:currentTable];
- NSArray *allFields = [theTable allKeys];
- NSSortDescriptor *desc = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES selector:@selector(localizedCompare:)];
- NSArray *sortedFields = [allFields sortedArrayUsingDescriptors:[NSArray arrayWithObject:desc]];
- [desc release];
- for(id field in sortedFields) {
- if(![field hasPrefix:@" "]) {
- NSArray *def = [theTable objectForKey:field];
- NSString *typ = [NSString stringWithFormat:@"%@ %@ %@", [def objectAtIndex:0], [def objectAtIndex:1], [def objectAtIndex:2]];
- // Check if type definition contains a , if so replace the bracket content by … and add
- // the bracket content as "list" key to prevend the token field to split them by ,
- if(typ && [typ rangeOfString:@","].length) {
- NSString *t = [typ stringByReplacingOccurrencesOfRegex:@"\\(.*?\\)" withString:@"(…)"];
- NSString *lst = [typ stringByMatching:@"\\(([^\\)]*?)\\)" capture:1L];
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
- field, @"display",
- @"field-small-square", @"image",
- [NSString stringWithFormat:@"%@@%%@",currentTable,currentDb], @"path", SPUniqueSchemaDelimiter,
- t, @"type",
- lst, @"list",
- @"", @"isRef",
- nil]];
- } else {
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
- field, @"display",
- @"field-small-square", @"image",
- [NSString stringWithFormat:@"%@%@%@",currentTable,currentDb], @"path", SPUniqueSchemaDelimiter,
- typ, @"type",
- @"", @"isRef",
- nil]];
- }
- }
- }
- } else {
- arr = [NSArray arrayWithArray:[[tableDocumentInstance valueForKeyPath:@"tableDataInstance"] valueForKey:@"columnNames"]];
- if(arr == nil) {
- arr = [NSArray array];
- }
- for(id w in arr)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"field-small-square", @"image", @"", @"isRef", nil]];
- }
- }
- else {
- NSLog(@"“%@” is not a valid completion list", kind);
- NSBeep();
- return;
- }
-
- if (completionIsOpen) [completionPopup close], completionPopup = nil;
- completionIsOpen = YES;
- completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
- alreadyTyped:@""
- staticPrefix:@""
- additionalWordCharacters:@"_."
- caseSensitive:NO
- charRange:aRange
- parseRange:aRange
- inView:self
- dictMode:NO
- dbMode:YES
- tabTriggerMode:[self isSnippetMode]
- fuzzySearch:fuzzySearchMode
- backtickMode:NO
- withDbName:@""
- withTableName:@""
- selectedDb:@""
- caretMovedLeft:NO
- autoComplete:NO
- oneColumn:NO
- isQueryingDBStructure:NO];
-
- //Get the NSPoint of the first character of the current word
- NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(aRange.location,0) 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)];
- // Adjust list location to be under the current word or insertion point
- pos.y -= [[self font] pointSize]*1.25;
- [completionPopup setCaretPos:pos];
- [completionPopup orderFront:self];
-
-}
-
-/*
- * Update all mirrored snippets and adjust any involved instances
- */
-- (void)processMirroredSnippets
-{
- if(mirroredCounter > -1) {
-
- isProcessingMirroredSnippets = YES;
-
- NSInteger i, j, k, deltaLength;
- NSRange mirroredRange;
-
- // Go through each defined mirrored snippet and update it
- for(i=0; i<=mirroredCounter; i++) {
- if(snippetMirroredControlArray[i][0] == currentSnippetIndex) {
-
- deltaLength = snippetControlArray[currentSnippetIndex][1]-snippetMirroredControlArray[i][2];
-
- mirroredRange = NSMakeRange(snippetMirroredControlArray[i][1], snippetMirroredControlArray[i][2]);
- NSString *mirroredString = nil;
-
- // For safety reasons
- @try{
- mirroredString = [[self string] substringWithRange:NSMakeRange(snippetControlArray[currentSnippetIndex][0], snippetControlArray[currentSnippetIndex][1])];
- }
- @catch(id ae) {
- NSLog(@"Error while parsing for mirrored snippets. %@", [ae description]);
- NSBeep();
- [self endSnippetSession];
- return;
- }
-
- // Register for undo
- [self shouldChangeTextInRange:mirroredRange replacementString:mirroredString];
-
- [self replaceCharactersInRange:mirroredRange withString:mirroredString];
- snippetMirroredControlArray[i][2] = snippetControlArray[currentSnippetIndex][1];
-
- // If a completion list is open adjust the theCharRange and theParseRange if a mirrored snippet
- // was updated which is located before the initial position
- if(completionIsOpen && snippetMirroredControlArray[i][1] < completionParseRangeLocation)
- [completionPopup adjustWorkingRangeByDelta:deltaLength];
-
- // Adjust all other snippets accordingly
- for(j=0; j<=snippetControlMax; j++) {
- if(snippetControlArray[j][0] > -1) {
- if(snippetControlArray[j][0]+snippetControlArray[j][1]>=snippetMirroredControlArray[i][1]) {
- snippetControlArray[j][0] += deltaLength;
- }
- }
- }
- // Adjust all mirrored snippets accordingly
- for(k=0; k<=mirroredCounter; k++) {
- if(i != k) {
- if(snippetMirroredControlArray[k][1] > snippetMirroredControlArray[i][1]) {
- snippetMirroredControlArray[k][1] += deltaLength;
- }
- }
- }
- }
- }
-
- isProcessingMirroredSnippets = NO;
- [self didChangeText];
-
- }
-}
-
-
-/*
- * Selects the current snippet defined by “currentSnippetIndex”
- */
-- (void)selectCurrentSnippet
-{
- if( snippetControlCounter > -1
- && currentSnippetIndex >= 0
- && currentSnippetIndex <= snippetControlMax
- )
- {
-
- [self breakUndoCoalescing];
-
- // Place the caret at the end of the query favorite snippet
- // and finish snippet editing
- if(currentSnippetIndex == snippetControlMax) {
- [self setSelectedRange:NSMakeRange(snippetControlArray[snippetControlMax][0] + snippetControlArray[snippetControlMax][1], 0)];
- [self endSnippetSession];
- return;
- }
-
- if(currentSnippetIndex >= 0 && currentSnippetIndex < 20) {
- if(snippetControlArray[currentSnippetIndex][2] == 0) {
-
- NSRange r1 = NSMakeRange(snippetControlArray[currentSnippetIndex][0], snippetControlArray[currentSnippetIndex][1]);
-
- NSRange r2;
- // Ensure the selection for nested snippets if it is at very end of the text buffer
- // because NSIntersectionRange returns {0, 0} in such a case
- if(r1.location == [[self string] length])
- r2 = NSMakeRange([[self string] length], 0);
- else
- r2 = NSIntersectionRange(NSMakeRange(0,[[self string] length]), r1);
-
- if(r1.location == r2.location && r1.length == r2.length) {
- [self setSelectedRange:r2];
- NSString *snip = [[self string] substringWithRange:r2];
-
- if([snip length] > 2 && [snip hasPrefix:@"¦"] && [snip hasSuffix:@"¦"]) {
- BOOL fuzzySearchMode = ([snip hasPrefix:@"¦¦"] && [snip hasSuffix:@"¦¦"]) ? YES : NO;
- NSInteger offset = (fuzzySearchMode) ? 2 : 1;
- NSRange insertRange = NSMakeRange(r2.location,0);
- NSString *newSnip = [snip substringWithRange:NSMakeRange(1*offset,[snip length]-(2*offset))];
- if([newSnip hasPrefix:@"$SP_ASLIST_"]) {
- [self showCompletionListFor:newSnip atRange:NSMakeRange(r2.location, 0) fuzzySearch:fuzzySearchMode];
- return;
- } else {
- NSArray *list = [[snip substringWithRange:NSMakeRange(1*offset,[snip length]-(2*offset))] componentsSeparatedByString:@"¦"];
- NSMutableArray *possibleCompletions = [[[NSMutableArray alloc] initWithCapacity:[list count]] autorelease];
- for(id w in list)
- [possibleCompletions addObject:[NSDictionary dictionaryWithObjectsAndKeys:w, @"display", @"dummy-small", @"image", nil]];
-
- if (completionIsOpen) [completionPopup close], completionPopup = nil;
- completionIsOpen = YES;
- completionPopup = [[SPNarrowDownCompletion alloc] initWithItems:possibleCompletions
- alreadyTyped:@""
- staticPrefix:@""
- additionalWordCharacters:@"_."
- caseSensitive:NO
- charRange:insertRange
- parseRange:insertRange
- inView:self
- dictMode:NO
- dbMode:NO
- tabTriggerMode:[self isSnippetMode]
- fuzzySearch:fuzzySearchMode
- backtickMode:NO
- withDbName:@""
- withTableName:@""
- selectedDb:@""
- caretMovedLeft:NO
- autoComplete:NO
- oneColumn:YES
- isQueryingDBStructure:NO];
-
- //Get the NSPoint of the first character of the current word
- NSRange glyphRange = [[self layoutManager] glyphRangeForCharacterRange:NSMakeRange(r2.location,0) 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)];
- // Adjust list location to be under the current word or insertion point
- pos.y -= [[self font] pointSize]*1.25;
- [completionPopup setCaretPos:pos];
- [completionPopup orderFront:self];
- }
- }
- } else {
- [self endSnippetSession];
- }
- }
- } else { // for safety reasons
- [self endSnippetSession];
- }
- } else { // for safety reasons
- [self endSnippetSession];
- }
-}
-
-/*
- * Inserts a chosen query favorite and initialze a snippet session if user defined any
- */
-- (void)insertAsSnippet:(NSString*)theSnippet atRange:(NSRange)targetRange
-{
-
- // Do not allow the insertion of a query favorite if snippets are active
- if(snippetControlCounter > -1) {
- NSBeep();
- return;
- }
-
- NSInteger i, j;
- mirroredCounter = -1;
-
- // reset snippet array
- for(i=0; i<20; i++) {
- snippetControlArray[i][0] = -1; // snippet location
- snippetControlArray[i][1] = -1; // snippet length
- snippetControlArray[i][2] = -1; // snippet task : -1 not valid, 0 select snippet
- snippetMirroredControlArray[i][0] = -1; // mirrored snippet index
- snippetMirroredControlArray[i][1] = -1; // mirrored snippet location
- snippetMirroredControlArray[i][2] = -1; // mirrored snippet length
- }
-
- if(theSnippet == nil || ![theSnippet length]) return;
-
- NSMutableString *snip = [[NSMutableString alloc] initWithCapacity:[theSnippet length]];
-
- @try{
- NSString *re = @"(?s)(?<!\\\\)\\$\\{(1?\\d):(.{0}|[^{}]*?[^\\\\])\\}";
- NSString *mirror_re = @"(?<!\\\\)\\$(1?\\d)(?=\\D)";
-
- if(targetRange.length)
- targetRange = NSIntersectionRange(NSMakeRange(0,[[self string] length]), targetRange);
- [snip setString:theSnippet];
-
- if (snip == nil) return;
- if (![snip length]) {
- [snip release];
- return;
- }
-
- // Replace `${x:…}` by ${x:`…`} for convience
- [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];
-
- // Check for snippet number 19 (to simplify regexp)
- if(snipCnt>18 || snipCnt<0) {
- NSLog(@"Only snippets in the range of 0…18 allowed.");
- [self endSnippetSession];
- break;
- }
-
- // Remember the maximal snippet number defined by user
- if(snipCnt>snippetControlMax)
- snippetControlMax = snipCnt;
-
- // Replace internal variables
- NSMutableString *theHintString = [[NSMutableString alloc] initWithCapacity:hintRange.length];
- [theHintString setString:[snip substringWithRange:hintRange]];
- if([theHintString isMatchedByRegex:@"(?<!\\\\)\\$SP_"]) {
- NSRange r;
- NSString *currentTable = nil;
- if (tablesListInstance && [tablesListInstance tableName])
- currentTable = [tablesListInstance tableName];
- NSString *currentDb = nil;
- if (tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [tablesListInstance selectedDatabase];
-
- while([theHintString isMatchedByRegex:@"(?<!\\\\)\\$SP_SELECTED_TABLES"]) {
- r = [theHintString rangeOfRegex:@"(?<!\\\\)\\$SP_SELECTED_TABLES"];
- if(r.length) {
- NSArray *selTables = [[[self delegate] valueForKeyPath:@"tablesListInstance"] selectedTableNames];
- if([selTables count])
- [theHintString replaceCharactersInRange:r withString:[selTables componentsJoinedAndBacktickQuoted]];
- else
- [theHintString replaceCharactersInRange:r withString:@"$SP_SELECTED_TABLE"];
- }
- [theHintString flushCachedRegexData];
- }
-
- while([theHintString isMatchedByRegex:@"(?<!\\\\)\\$SP_SELECTED_TABLE"]) {
- r = [theHintString rangeOfRegex:@"(?<!\\\\)\\$SP_SELECTED_TABLE"];
- if(r.length) {
- if(currentTable && [currentTable length])
- [theHintString replaceCharactersInRange:r withString:[currentTable backtickQuotedString]];
- else
- [theHintString replaceCharactersInRange:r withString:@"<table>"];
- }
- [theHintString flushCachedRegexData];
- }
-
- while([theHintString isMatchedByRegex:@"(?<!\\\\)\\$SP_SELECTED_DATABASE"]) {
- r = [theHintString rangeOfRegex:@"(?<!\\\\)\\$SP_SELECTED_DATABASE"];
- if(r.length) {
- if(currentDb && [currentDb length])
- [theHintString replaceCharactersInRange:r withString:[currentDb backtickQuotedString]];
- else
- [theHintString replaceCharactersInRange:r withString:@"<database>"];
- }
- [theHintString flushCachedRegexData];
- }
- }
-
- // Handle escaped characters
- [theHintString replaceOccurrencesOfRegex:@"\\\\(\\$\\(|\\}|\\$SP_)" withString:@"$1"];
- [theHintString flushCachedRegexData];
-
- // If inside the snippet hint $(…) is defined run … as BASH command
- // 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];
-
- // Store found snippet range
- snippetControlArray[snipCnt][0] = snipRange.location + targetRange.location;
- snippetControlArray[snipCnt][1] = [theHintString length];
- snippetControlArray[snipCnt][2] = 0;
-
- [theHintString release];
-
- // Adjust successive snippets
- for(i=0; i<20; i++)
- if(snippetControlArray[i][0] > -1 && i != snipCnt && snippetControlArray[i][0] > snippetControlArray[snipCnt][0])
- snippetControlArray[i][0] -= 3+((snipCnt>9)?2:1);
-
- }
-
- // Parse for mirrored snippets
- while([snip isMatchedByRegex:mirror_re]) {
- mirroredCounter++;
- if(mirroredCounter > 19) {
- NSLog(@"Only 20 mirrored snippet placeholders allowed.");
- NSBeep();
- break;
- } else {
-
- NSRange snipRange = [snip rangeOfRegex:mirror_re capture:0L];
- NSInteger snipCnt = [[snip substringWithRange:[snip rangeOfRegex:mirror_re capture:1L]] intValue];
-
- // Check for snippet number 19 (to simplify regexp)
- if(snipCnt>18 || snipCnt<0) {
- NSLog(@"Only snippets in the range of 0…18 allowed.");
- [self endSnippetSession];
- break;
- }
-
- [snip replaceCharactersInRange:snipRange withString:@""];
- [snip flushCachedRegexData];
-
- // Store found mirrored snippet range
- snippetMirroredControlArray[mirroredCounter][0] = snipCnt;
- snippetMirroredControlArray[mirroredCounter][1] = snipRange.location + targetRange.location;
- snippetMirroredControlArray[mirroredCounter][2] = 0;
-
- // Adjust successive snippets
- for(i=0; i<20; i++)
- if(snippetControlArray[i][0] > -1 && snippetControlArray[i][0] > snippetMirroredControlArray[mirroredCounter][1])
- snippetControlArray[i][0] -= 1+((snipCnt>9)?2:1);
-
- [snip flushCachedRegexData];
- }
- }
- // Preset mirrored snippets with according snippet content
- if(mirroredCounter > -1) {
- for(i=0; i<=mirroredCounter; i++) {
- if(snippetControlArray[snippetMirroredControlArray[i][0]][0] > -1 && snippetControlArray[snippetMirroredControlArray[i][0]][1] > 0) {
- [snip replaceCharactersInRange:NSMakeRange(snippetMirroredControlArray[i][1]-targetRange.location, snippetMirroredControlArray[i][2])
- withString:[snip substringWithRange:NSMakeRange(snippetControlArray[snippetMirroredControlArray[i][0]][0]-targetRange.location, snippetControlArray[snippetMirroredControlArray[i][0]][1])]];
- snippetMirroredControlArray[i][2] = snippetControlArray[snippetMirroredControlArray[i][0]][1];
- }
- // Adjust successive snippets
- for(j=0; j<20; j++)
- if(snippetControlArray[j][0] > -1 && snippetControlArray[j][0] > snippetMirroredControlArray[i][1])
- snippetControlArray[j][0] += snippetControlArray[snippetMirroredControlArray[i][0]][1];
- // Adjust successive mirrored snippets
- for(j=0; j<=mirroredCounter; j++)
- if(snippetMirroredControlArray[j][1] > snippetMirroredControlArray[i][1])
- snippetMirroredControlArray[j][1] += snippetControlArray[snippetMirroredControlArray[i][0]][1];
- }
- }
-
- if(snippetControlCounter > -1) {
- // Store the end for tab out
- snippetControlMax++;
- snippetControlArray[snippetControlMax][0] = targetRange.location + [snip length];
- snippetControlArray[snippetControlMax][1] = 0;
- snippetControlArray[snippetControlMax][2] = 0;
- }
-
- // 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]--;
- // Adjust mirrored snippets
- if(mirroredCounter > -1)
- for(i=0; i<=mirroredCounter; i++)
- if(snippetMirroredControlArray[i][0] > -1 && snippetMirroredControlArray[i][1] > loc)
- snippetMirroredControlArray[i][1]--;
- }
-
- // Insert favorite query by selecting the tab trigger if any
- [self setSelectedRange:targetRange];
-
- // Registering for undo
- [self breakUndoCoalescing];
- [self insertText:snip];
-
- // If autopair is enabled check whether snip begins with ( and ends with ), if so mark ) as pair-linked
- if([prefs boolForKey:SPCustomQueryAutoPairCharacters] && ([snip hasPrefix:@"("] && [snip hasSuffix:@")"] || ([snip hasPrefix:@"`"] && [snip hasSuffix:@"`"]) || ([snip hasPrefix:@"'"] && [snip hasSuffix:@"'"]) || ([snip hasPrefix:@"\""] && [snip hasSuffix:@"\""])))
- [[self textStorage] addAttribute:kAPlinked value:kAPval range:NSMakeRange([self selectedRange].location - 1, 1)];
-
- // Any snippets defined?
- if(snippetControlCounter > -1) {
- // Find and select first defined snippet
- currentSnippetIndex = 0;
- // Look for next defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g.
- while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20)
- currentSnippetIndex++;
- [self selectCurrentSnippet];
- }
-
- snippetWasJustInserted = NO;
- }
- @catch(id ae) { // For safety reasons catch exceptions
- NSLog(@"Snippet Error: %@", [ae description]);
- [self endSnippetSession];
- snippetWasJustInserted = NO;
- }
-
- if(snip)[snip release];
-
-}
-
-/*
- * 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/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] || [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;
- if (([event modifierFlags] & NSCommandKeyMask) && key == '.') {
- [bashTask terminate];
- userTerminated = YES;
- break;
- }
- } else {
- [NSApp sendEvent:event];
- }
- }
-
- [bashTask waitUntilExit];
-
- if(userTerminated) {
- if(bashTask) [bashTask release];
- NSBeep();
- NSLog(@"“%@” was terminated by user.", command);
- return @"";
- }
-
- // If return from bash re-activate Sequel Pro
- [NSApp activateIgnoringOtherApps:YES];
-
- NSInteger status = [bashTask terminationStatus];
- NSData *outdata = [stdout_file readDataToEndOfFile];
- NSData *errdata = [stderr_file readDataToEndOfFile];
-
- if(outdata != nil) {
- NSString *stdout = [[NSString alloc] initWithData:outdata encoding:NSUTF8StringEncoding];
- NSString *error = [[[NSString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease];
- if(bashTask) [bashTask release];
- if(stdout != nil) {
- if (status == 0) {
- return [stdout autorelease];
- } else {
- NSString *error = [[[NSString alloc] initWithData:errdata encoding:NSUTF8StringEncoding] autorelease];
- SPBeginAlertSheet(NSLocalizedString(@"BASH Error", @"bash error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [self window], self, nil, nil,
- [NSString stringWithFormat:@"%@ “%@”:\n%@", NSLocalizedString(@"Error for", @"error for message"), command, [error description]]);
- [stdout release];
- NSBeep();
- return @"";
- }
- } else {
- NSLog(@"Couldn't read return string from “%@” by using UTF-8 encoding.", command);
- NSBeep();
- }
- } else {
- if(bashTask) [bashTask release];
- NSLog(@"Couldn't read data from command “%@”.", command);
- NSBeep();
- return @"";
- }
-
-}
-
-/*
- * Checks whether the current caret position in inside of a defined snippet range
- */
-- (BOOL)checkForCaretInsideSnippet
-{
-
- if(snippetWasJustInserted) return YES;
-
- BOOL isCaretInsideASnippet = NO;
-
- if(snippetControlCounter < 0 || currentSnippetIndex == snippetControlMax) {
- [self endSnippetSession];
- return NO;
- }
-
- [[self textStorage] ensureAttributesAreFixedInRange:[self selectedRange]];
- NSUInteger caretPos = [self selectedRange].location;
- NSInteger i, j;
- NSInteger foundSnippetIndices[20]; // array to hold nested snippets
-
- j = -1;
-
- // Go through all snippet ranges and check whether the caret is inside of the
- // current snippet range. Remember matches
- // in foundSnippetIndices array to test for nested snippets.
- for(i=0; i<=snippetControlMax; i++) {
- j++;
- foundSnippetIndices[j] = 0;
- if(snippetControlArray[i][0] != -1
- && caretPos >= snippetControlArray[i][0]
- && caretPos <= snippetControlArray[i][0] + snippetControlArray[i][1]) {
-
- foundSnippetIndices[j] = 1;
- if(i == currentSnippetIndex)
- isCaretInsideASnippet = YES;
-
- }
- }
- // If caret is not inside the current snippet range check if caret is inside of
- // another defined snippet; if so set currentSnippetIndex to it (this allows to use the
- // mouse to activate another snippet). If the caret is inside of overlapped snippets (nested)
- // then select this snippet which has the smallest length.
- if(!isCaretInsideASnippet && foundSnippetIndices[currentSnippetIndex] == 1) {
- isCaretInsideASnippet = YES;
- } else if(![self selectedRange].length) {
- NSInteger index = -1;
- NSInteger smallestLength = -1;
- for(i=0; i<snippetControlMax; i++) {
- if(foundSnippetIndices[i] == 1) {
- if(index == -1) {
- index = i;
- smallestLength = snippetControlArray[i][1];
- } else {
- if(smallestLength > snippetControlArray[i][1]) {
- index = i;
- smallestLength = snippetControlArray[i][1];
- }
- }
- }
- }
- // Reset the active snippet
- if(index > -1 && smallestLength > -1) {
- currentSnippetIndex = index;
- isCaretInsideASnippet = YES;
- }
- }
- return isCaretInsideASnippet;
-
-}
-
-/*
- * Return YES if user interacts with snippets (is needed mainly for suppressing
- * the highlighting of the current query)
- */
-- (BOOL)isSnippetMode
-{
- return (snippetControlCounter > -1) ? YES : NO;
-}
-
-#pragma mark -
-#pragma mark event management
-
-/*
- * Used for autoHelp update if the user changed the caret position by using the mouse.
- */
-- (void) mouseDown:(NSEvent *)theEvent
-{
-
- // Cancel autoHelp timer
- if([prefs boolForKey:SPCustomQueryUpdateAutoHelp])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(autoHelp)
- object:nil];
-
- // Cancel auto-completion timer
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- [super mouseDown:theEvent];
-
- // Start autoHelp timer
- if([prefs boolForKey:SPCustomQueryUpdateAutoHelp])
- [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoHelpDelay] doubleValue]];
-
-}
-
-/*
- * Handle some keyDown events in order to provide autopairing functionality (if enabled).
- */
-- (void) keyDown:(NSEvent *)theEvent
-{
-
- if([prefs boolForKey:SPCustomQueryUpdateAutoHelp]) {// restart autoHelp timer
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(autoHelp)
- object:nil];
- [self performSelector:@selector(autoHelp) withObject:nil
- afterDelay:[[prefs valueForKey:SPCustomQueryAutoHelpDelay] doubleValue]];
- }
-
- // Cancel auto-completion timer
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
-
- long allFlags = (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask);
-
- // Check if user pressed ⌥ to allow composing of accented characters.
- // e.g. for US keyboard "⌥u a" to insert ä
- // or for non-US keyboards to allow to enter dead keys
- // e.g. for German keyboard ` is a dead key, press space to enter `
- if (([theEvent modifierFlags] & allFlags) == NSAlternateKeyMask || [[theEvent characters] length] == 0)
- {
- [super keyDown: theEvent];
- return;
- }
-
- NSString *characters = [theEvent characters];
- NSString *charactersIgnMod = [theEvent charactersIgnoringModifiers];
- unichar insertedCharacter = [characters characterAtIndex:0];
- long curFlags = ([theEvent modifierFlags] & allFlags);
-
- if ([theEvent keyCode] == 53 && [self isEditable]){ // ESC key for internal completion
-
- [self setCompletionWasReinvokedAutomatically:NO];
- completionWasRefreshed = NO;
- // Cancel autocompletion trigger
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- if(curFlags==(NSControlKeyMask))
- [self doCompletionByUsingSpellChecker:NO fuzzyMode:YES autoCompleteMode:NO];
- else
- [self doCompletionByUsingSpellChecker:NO fuzzyMode:NO autoCompleteMode:NO];
- return;
- }
- if (insertedCharacter == NSF5FunctionKey && [self isEditable]){ // F5 for completion based on spell checker
- [self setCompletionWasReinvokedAutomatically:NO];
- [self doCompletionByUsingSpellChecker:YES fuzzyMode:NO autoCompleteMode:NO];
- return;
- }
-
- // Check for {SHIFT}TAB to try to insert query favorite via TAB trigger if CMTextView belongs to CustomQuery
- if ([theEvent keyCode] == 48 && [self isEditable] && [[self delegate] isKindOfClass:[CustomQuery class]]){
- NSRange targetRange = [self getRangeForCurrentWord];
- NSString *tabTrigger = [[self string] substringWithRange:targetRange];
-
- // Is TAB trigger active change selection according to {SHIFT}TAB
- if(snippetControlCounter > -1){
-
- if(curFlags==(NSShiftKeyMask)) { // select previous snippet
-
- currentSnippetIndex--;
-
- // Look for previous defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g.
- while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex > -2)
- currentSnippetIndex--;
-
- if(currentSnippetIndex < 0) {
- currentSnippetIndex = 0;
- while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20)
- currentSnippetIndex++;
- NSBeep();
- }
-
- [self selectCurrentSnippet];
- return;
-
- } else { // select next snippet
-
- currentSnippetIndex++;
-
- // Look for next defined snippet since snippet numbers must not serial like 1, 5, and 12 e.g.
- while(snippetControlArray[currentSnippetIndex][0] == -1 && currentSnippetIndex < 20)
- currentSnippetIndex++;
-
- if(currentSnippetIndex > snippetControlMax) { // for safety reasons
- [self endSnippetSession];
- } else {
- [self selectCurrentSnippet];
- return;
- }
- }
-
- [self endSnippetSession];
-
- }
-
- // Check if tab trigger is defined; if so insert it, otherwise pass through event
- if(snippetControlCounter < 0 && [tableDocumentInstance fileURL]) {
- NSArray *snippets = [[SPQueryController sharedQueryController] queryFavoritesForFileURL:[tableDocumentInstance fileURL] andTabTrigger:tabTrigger includeGlobals:YES];
- if([snippets count] > 0 && [(NSString*)[(NSDictionary*)[snippets objectAtIndex:0] objectForKey:@"query"] length]) {
- [self insertAsSnippet:[(NSDictionary*)[snippets objectAtIndex:0] objectForKey:@"query"] atRange:targetRange];
- return;
- }
- }
- }
-
- // Note: switch(insertedCharacter) {} does not work instead use charactersIgnoringModifiers
- if([charactersIgnMod isEqualToString:@"c"]) // ^C copy as RTF
- if(curFlags==(NSControlKeyMask))
- {
- [self copyAsRTF];
- return;
- }
- if([charactersIgnMod isEqualToString:@"h"]) // ^H show MySQL Help
- if(curFlags==(NSControlKeyMask))
- {
- [self showMySQLHelpForCurrentWord:self];
- return;
- }
- if([charactersIgnMod isEqualToString:@"y"]) // ^Y select current query
- if(curFlags==(NSControlKeyMask))
- {
- [self selectCurrentQuery];
- return;
- }
- if(curFlags & NSCommandKeyMask) {
- if([charactersIgnMod isEqualToString:@"+"] || [charactersIgnMod isEqualToString:@"="]) // increase text size by 1; ⌘+, ⌘=, and ⌘ numpad +
- {
- [self makeTextSizeLarger];
- return;
- }
- if([charactersIgnMod isEqualToString:@"-"]) // decrease text size by 1; ⌘- and numpad -
- {
- [self makeTextSizeSmaller];
- return;
- }
- if([charactersIgnMod isEqualToString:@"0"]) { // reset font to default
- BOOL editableStatus = [self isEditable];
- [self setEditable:YES];
- [self setFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]];
- [self setEditable:editableStatus];
- return;
- }
- }
-
- // Only process for character autopairing if autopairing is enabled and a single character is being added.
- if ([prefs boolForKey:SPCustomQueryAutoPairCharacters] && characters && [characters length] == 1) {
-
- delBackwardsWasPressed = NO;
-
- NSString *matchingCharacter = nil;
- BOOL processAutopair = NO, skipTypedLinkedCharacter = NO;
- NSRange currentRange;
-
- // When a quote character is being inserted into a string quoted with other
- // quote characters, or if it's the same character but is escaped, don't
- // automatically match it.
- if(
- // Only for " ` or ' quote characters
- (insertedCharacter == '\'' || insertedCharacter == '"' || insertedCharacter == '`')
-
- // And if the next char marked as linked auto-pair
- && [self isNextCharMarkedBy:kAPlinked withValue:kAPval]
-
- // And we are inside a quoted string
- && [self isNextCharMarkedBy:kLEXToken withValue:kLEXTokenValue]
-
- // And there is no selection, just the text caret
- && ![self selectedRange].length
-
- && (
- // And the user is inserting an escaped string
- [[self string] characterAtIndex:[self selectedRange].location-1] == '\\'
-
- // Or the user is inserting a character not matching the characters used to quote this string
- || [[self string] characterAtIndex:[self selectedRange].location] != insertedCharacter
- )
- )
- {
- [super keyDown: theEvent];
- return;
- }
-
- // If the caret is inside a text string, without any selection, and not adjoined to an alphanumeric char
- // (exception for '(' ) skip autopairing.
- // There is one exception to this - if the caret is before a linked pair character,
- // processing continues in order to check whether the next character should be jumped
- // over; e.g. [| := caret]: "foo|" and press " => only caret will be moved "foo"|
- if( ([self isCaretAdjacentToAlphanumCharWithInsertionOf:insertedCharacter] && ![self isNextCharMarkedBy:kAPlinked withValue:kAPval] && ![self selectedRange].length)
- || (![self isNextCharMarkedBy:kAPlinked withValue:kAPval] && [self isNextCharMarkedBy:kLEXToken withValue:kLEXTokenValue] && ![self selectedRange].length)) {
- [super keyDown:theEvent];
- return;
- }
-
- // Check whether the submitted character should trigger autopair processing.
- switch (insertedCharacter)
- {
- case '(':
- matchingCharacter = @")";
- processAutopair = YES;
- break;
- case '"':
- matchingCharacter = @"\"";
- processAutopair = YES;
- skipTypedLinkedCharacter = YES;
- break;
- case '`':
- matchingCharacter = @"`";
- processAutopair = YES;
- skipTypedLinkedCharacter = YES;
- break;
- case '\'':
- matchingCharacter = @"'";
- processAutopair = YES;
- skipTypedLinkedCharacter = YES;
- break;
- case ')':
- skipTypedLinkedCharacter = YES;
- break;
- case '{':
- matchingCharacter = @"}";
- processAutopair = YES;
- break;
- case '}':
- skipTypedLinkedCharacter = YES;
- break;
- }
-
- // Check to see whether the next character should be compared to the typed character;
- // if it matches the typed character, and is marked with the is-linked-pair attribute,
- // select the next character and replace it with the typed character. This allows
- // a normally quoted string to be typed in full, with the autopair appearing as a hint and
- // then being automatically replaced when the user types it.
- if (skipTypedLinkedCharacter) {
- currentRange = [self selectedRange];
- if (currentRange.location != NSNotFound && currentRange.length == 0) {
- if ([self isNextCharMarkedBy:kAPlinked withValue:kAPval]) {
- if ([[[self textStorage] string] characterAtIndex:currentRange.location] == insertedCharacter) {
- currentRange.length = 1;
- [self setSelectedRange:currentRange];
- processAutopair = NO;
- }
- }
- }
- }
-
- // If an appropriate character has been typed, and a matching character has been set,
- // some form of autopairing is required.
- if (processAutopair && matchingCharacter) {
-
- // Check to see whether several characters are selected, and if so, wrap them with
- // the auto-paired characters. This returns false if the selection has zero length.
- if ([self wrapSelectionWithPrefix:characters suffix:matchingCharacter])
- return;
-
- // Otherwise, start by inserting the original character - the first half of the autopair.
- [super keyDown:theEvent];
-
- // Then process the second half of the autopair - the matching character.
- currentRange = [self selectedRange];
- if (currentRange.location != NSNotFound) {
- NSTextStorage *textStorage = [self textStorage];
-
- // Register the auto-pairing for undo
- [self shouldChangeTextInRange:currentRange replacementString:matchingCharacter];
-
- // Insert the matching character and give it the is-linked-pair-character attribute
- [self replaceCharactersInRange:currentRange withString:matchingCharacter];
- currentRange.length = 1;
- [textStorage addAttribute:kAPlinked value:kAPval range:currentRange];
-
- // Restore the original selection.
- currentRange.length=0;
- [self setSelectedRange:currentRange];
-
- [self didChangeText];
- }
- return;
- }
- }
-
- // break down the undo grouping level for better undo behavior
- [self breakUndoCoalescing];
- // The default action is to perform the normal key-down action.
- [super keyDown:theEvent];
-
-}
-
-
-- (void) deleteBackward:(id)sender
-{
-
- // If the caret is currently inside a marked auto-pair, delete the characters on both sides
- // of the caret.
- NSRange currentRange = [self selectedRange];
- if (currentRange.length == 0 && currentRange.location > 0 && [self areAdjacentCharsLinked])
- [self setSelectedRange:NSMakeRange(currentRange.location - 1,2)];
-
- // Avoid auto-uppercasing if resulting word would be a SQL keyword;
- // e.g. type inta| and deleteBackward:
- delBackwardsWasPressed = YES;
-
- [super deleteBackward:sender];
-
-}
-
-/*
- * Handle special commands - see NSResponder.h for a sample list.
- * This subclass currently handles insertNewline: in order to preserve indentation
- * when adding newlines.
- */
-- (void) doCommandBySelector:(SEL)aSelector
-{
-
- // Handle newlines, adding any indentation found on the current line to the new line - ignoring the enter key if appropriate
- if (aSelector == @selector(insertNewline:)
- && [prefs boolForKey:SPCustomQueryAutoIndent]
- && (!autoindentIgnoresEnter || [[NSApp currentEvent] keyCode] != 0x4C))
- {
- NSString *textViewString = [[self textStorage] string];
- NSString *currentLine, *indentString = nil;
- NSScanner *whitespaceScanner;
- NSRange currentLineRange;
- NSUInteger lineCursorLocation;
-
- // Extract the current line based on the text caret or selection start position
- currentLineRange = [textViewString lineRangeForRange:NSMakeRange([self selectedRange].location, 0)];
- currentLine = [[NSString alloc] initWithString:[textViewString substringWithRange:currentLineRange]];
- lineCursorLocation = [self selectedRange].location - currentLineRange.location;
-
- // Scan all indentation characters on the line into a string
- whitespaceScanner = [[NSScanner alloc] initWithString:currentLine];
- [whitespaceScanner setCharactersToBeSkipped:nil];
- [whitespaceScanner scanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:&indentString];
- [whitespaceScanner release];
- [currentLine release];
-
- // Always add the newline, whether or not we want to indent the next line
- [self insertNewline:self];
-
- // Replicate the indentation on the previous line if one was found.
- if (indentString) {
- if (lineCursorLocation < [indentString length]) {
- [self insertText:[indentString substringWithRange:NSMakeRange(0, lineCursorLocation)]];
- } else {
- [self insertText:indentString];
- }
- }
-
- // Return to avoid the original implementation, preventing double linebreaks
- return;
- }
- [super doCommandBySelector:aSelector];
-}
-
-/*
- * Set whether this text view should apply the indentation on the current line to new lines.
- */
-- (void)setAutoindent:(BOOL)enableAutoindent
-{
- autoindentEnabled = enableAutoindent;
-}
-
-/*
- * Retrieve whether this text view applies indentation on the current line to new lines.
- */
-- (BOOL)autoindent
-{
- return autoindentEnabled;
-}
-
-/*
- * Set whether this text view should not autoindent when the Enter key is used, as opposed
- * to the return key. Also catches function-return.
- */
-- (void)setAutoindentIgnoresEnter:(BOOL)enableAutoindentIgnoresEnter
-{
- autoindentIgnoresEnter = enableAutoindentIgnoresEnter;
-}
-
-/*
- * Retrieve whether this text view should not autoindent when the Enter key is used.
- */
-- (BOOL)autoindentIgnoresEnter
-{
- return autoindentIgnoresEnter;
-}
-
-/*
- * Set whether this text view should automatically create the matching closing char for ", ', ` and ( chars.
- */
-- (void)setAutopair:(BOOL)enableAutopair
-{
- autopairEnabled = enableAutopair;
-}
-
-/*
- * Retrieve whether this text view automatically creates the matching closing char for ", ', ` and ( chars.
- */
-- (BOOL)autopair
-{
- return autopairEnabled;
-}
-
-/*
- * Set whether MySQL Help should be automatically invoked while typing.
- */
-- (void)setAutohelp:(BOOL)enableAutohelp
-{
- autohelpEnabled = enableAutohelp;
-}
-
-/*
- * Retrieve whether MySQL Help should be automatically invoked while typing.
- */
-- (BOOL)autohelp
-{
- return autohelpEnabled;
-}
-
-/*
- * Set whether SQL keywords should be automatically uppercased.
- */
-- (void)setAutouppercaseKeywords:(BOOL)enableAutouppercaseKeywords
-{
- autouppercaseKeywordsEnabled = enableAutouppercaseKeywords;
-}
-
-/*
- * Retrieve whether SQL keywords should be automatically uppercased.
- */
-- (BOOL)autouppercaseKeywords
-{
- return autouppercaseKeywordsEnabled;
-}
-
-
-/*
- * If enabled it shows the MySQL Help for the current word (not inside quotes) or for the selection
- * after an adjustable delay if the textView is idle, i.e. no user interaction.
- */
-- (void)autoHelp
-{
-
- if(![prefs boolForKey:SPCustomQueryUpdateAutoHelp]) return;
-
- // If selection show Help for it
- if([self selectedRange].length)
- {
- [customQueryInstance performSelector:@selector(showAutoHelpForCurrentWord:) withObject:self afterDelay:0.1];
- return;
- }
- // Otherwise show Help if caret is not inside quotes
- NSUInteger cursorPosition = [self selectedRange].location;
- if (cursorPosition >= [[self string] length]) cursorPosition--;
- if(cursorPosition > -1 && (![[self textStorage] attribute:kQuote atIndex:cursorPosition effectiveRange:nil]||[[self textStorage] attribute:kSQLkeyword atIndex:cursorPosition effectiveRange:nil]))
- [customQueryInstance performSelector:@selector(showAutoHelpForCurrentWord:) withObject:self afterDelay:0.1];
-
-}
-
-/*
- * Syntax Highlighting.
- *
- * (The main bottleneck is the [NSTextStorage addAttribute:value:range:] method - the parsing itself is really fast!)
- * Some sample code from Andrew Choi ( http://members.shaw.ca/akochoi-old/blog/2003/11-09/index.html#3 ) has been reused.
- */
-- (void)doSyntaxHighlighting
-{
-
- NSTextStorage *textStore = [self textStorage];
- NSString *selfstr = [self string];
- NSUInteger strlength = [selfstr length];
-
- if(strlength > SP_MAX_TEXT_SIZE_FOR_SYNTAX_HIGHLIGHTING) return;
-
- NSRange textRange;
-
- // If text larger than SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING
- // do highlighting partly (max SP_SYNTAX_HILITE_BIAS*2).
- // The approach is to take the middle position of the current view port
- // and highlight only ±SP_SYNTAX_HILITE_BIAS of that middle position
- // considering of line starts resp. ends
- if(strlength > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING)
- {
-
- // Get the text range currently displayed in the view port
- NSRect visibleRect = [[[self enclosingScrollView] contentView] documentVisibleRect];
- NSRange visibleRange = [[self layoutManager] glyphRangeForBoundingRectWithoutAdditionalLayout:visibleRect inTextContainer:[self textContainer]];
-
- if(!visibleRange.length) return;
-
- // Take roughly the middle position in the current view port
- NSUInteger curPos = visibleRange.location+(NSUInteger)(visibleRange.length/2);
-
- // get the last line to parse due to SP_SYNTAX_HILITE_BIAS
- // but look for only SP_SYNTAX_HILITE_BIAS chars forwards
- NSUInteger end = curPos + SP_SYNTAX_HILITE_BIAS;
- NSInteger lengthChecker = SP_SYNTAX_HILITE_BIAS;
- if (end > strlength ) {
- end = strlength;
- } else {
- while(end < strlength && lengthChecker > 0) {
- if([selfstr characterAtIndex:end]=='\n')
- break;
- end++;
- lengthChecker--;
- }
- }
- if(lengthChecker <= 0)
- end = curPos + SP_SYNTAX_HILITE_BIAS;
-
- // get the first line to parse due to SP_SYNTAX_HILITE_BIAS
- // but look for only SP_SYNTAX_HILITE_BIAS chars backwards
- NSUInteger start, start_temp;
- if(end <= (SP_SYNTAX_HILITE_BIAS*2))
- start = 0;
- else
- start = end - (SP_SYNTAX_HILITE_BIAS*2);
-
- start_temp = start;
- lengthChecker = SP_SYNTAX_HILITE_BIAS;
- if (start > 0)
- while(start>0 && lengthChecker > 0) {
- if([selfstr characterAtIndex:start]=='\n')
- break;
- start--;
- lengthChecker--;
- }
- if(lengthChecker <= 0)
- start = start_temp;
-
- textRange = NSMakeRange(start, end-start);
-
- // only to be sure that nothing went wrongly
- textRange = NSIntersectionRange(textRange, NSMakeRange(0, [textStore length]));
-
- if (!textRange.length)
- return;
-
- } else {
- // If text size is less SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING
- // process syntax highlighting for the entire text view buffer
- textRange = NSMakeRange(0,strlength);
- }
-
- NSColor *tokenColor;
-
- size_t tokenEnd, token;
- NSRange tokenRange;
-
- // first remove the old colors and kQuote
- [textStore removeAttribute:NSForegroundColorAttributeName range:textRange];
- // mainly for suppressing auto-pairing in
- [textStore removeAttribute:kLEXToken range:textRange];
-
- // initialise flex
- yyuoffset = textRange.location; yyuleng = 0;
- yy_switch_to_buffer(yy_scan_string(NSStringUTF8String([selfstr substringWithRange:textRange])));
-
- // NO if lexer doesn't find a token to suppress auto-uppercasing
- // and continue earlier.
- BOOL allowToCheckForUpperCase;
-
- // now loop through all the tokens
- while (token=yylex()){
-
- allowToCheckForUpperCase = YES;
-
- switch (token) {
- case SPT_SINGLE_QUOTED_TEXT:
- case SPT_DOUBLE_QUOTED_TEXT:
- tokenColor = quoteColor;
- break;
- case SPT_BACKTICK_QUOTED_TEXT:
- tokenColor = backtickColor;
- break;
- case SPT_RESERVED_WORD:
- tokenColor = keywordColor;
- break;
- case SPT_NUMERIC:
- tokenColor = numericColor;
- break;
- case SPT_COMMENT:
- tokenColor = commentColor;
- break;
- case SPT_VARIABLE:
- tokenColor = variableColor;
- break;
- case SPT_WHITESPACE:
- continue;
- break;
- default:
- tokenColor = otherTextColor;
- allowToCheckForUpperCase = NO;
- }
-
- tokenRange = NSMakeRange(yyuoffset, yyuleng);
-
- // make sure that tokenRange is valid (and therefore within textRange)
- // otherwise a bug in the lex code could cause the the TextView to crash
- // NOTE Disabled for testing purposes for speed it up
- tokenRange = NSIntersectionRange(tokenRange, textRange);
- if (!tokenRange.length) continue;
-
- // 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 && 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
- // @try catch() for catching valid index esp. after deleteBackward:
- {
-
- NSString* curTokenString = [selfstr substringWithRange:tokenRange];
- BOOL doIt = NO;
- @try
- {
- doIt = ![(NSString*)NSMutableAttributedStringAttributeAtIndex(textStore, kSQLkeyword,tokenEnd+1,nil) length];
- } @catch(id ae) { doIt = NO; }
-
- if(doIt)
- {
- // Register it for undo works only partly for now, at least the uppercased keyword will be selected
- [self shouldChangeTextInRange:tokenRange replacementString:curTokenString];
- [self replaceCharactersInRange:tokenRange withString:[curTokenString uppercaseString]];
- }
- }
-
- NSMutableAttributedStringAddAttributeValueRange(textStore, NSForegroundColorAttributeName, tokenColor, tokenRange);
-
- if(!allowToCheckForUpperCase) continue;
-
- // Add an attribute to be used in the auto-pairing (keyDown:)
- // to disable auto-pairing if caret is inside of any token found by lex.
- // For discussion: maybe change it later (only for quotes not keywords?)
- if(token < 6)
- NSMutableAttributedStringAddAttributeValueRange(textStore, kLEXToken, kLEXTokenValue, tokenRange);
-
- // Mark each SQL keyword for auto-uppercasing and do it for the next textStorageDidProcessEditing: event.
- // Performing it one token later allows words which start as reserved keywords to be entered.
- if(token == SPT_RESERVED_WORD)
- NSMutableAttributedStringAddAttributeValueRange(textStore, kSQLkeyword, kValue, tokenRange);
-
- // Add an attribute to be used to distinguish quotes from keywords etc.
- // used e.g. in completion suggestions
- else if(token < 4)
- NSMutableAttributedStringAddAttributeValueRange(textStore, kQuote, kQuoteValue, tokenRange);
-
- //distinguish backtick quoted word for completion
- else if(token == SPT_BACKTICK_QUOTED_TEXT)
- NSMutableAttributedStringAddAttributeValueRange(textStore, kBTQuote, kBTQuoteValue, tokenRange);
-
- }
-
-}
-
-- (void) setTabStops
-{
- NSFont *tvFont = [self font];
- NSInteger i;
- NSTextTab *aTab;
- NSMutableArray *myArrayOfTabs;
- NSMutableParagraphStyle *paragraphStyle;
-
- BOOL oldEditableStatus = [self isEditable];
- [self setEditable:YES];
-
- NSInteger tabStopWidth = [prefs integerForKey:SPCustomQueryEditorTabStopWidth];
- if(tabStopWidth < 1) tabStopWidth = 1;
-
- float tabWidth = NSSizeToCGSize([[NSString stringWithString:@" "] sizeWithAttributes:[NSDictionary dictionaryWithObject:tvFont forKey:NSFontAttributeName]]).width;
- tabWidth = (float)tabStopWidth * tabWidth;
-
- NSInteger numberOfTabs = 256/tabStopWidth;
- myArrayOfTabs = [NSMutableArray arrayWithCapacity:numberOfTabs];
- aTab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:tabWidth];
- [myArrayOfTabs addObject:aTab];
- [aTab release];
- for(i=1; i<numberOfTabs; i++) {
- aTab = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:tabWidth + ((float)i * tabWidth)];
- [myArrayOfTabs addObject:aTab];
- [aTab release];
- }
- paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
- [paragraphStyle setTabStops:myArrayOfTabs];
- // Soft wrapped lines are indented slightly
- [paragraphStyle setHeadIndent:4.0];
-
- NSMutableDictionary *textAttributes = [[[NSMutableDictionary alloc] initWithCapacity:1] autorelease];
- [textAttributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
-
- NSRange range = NSMakeRange(0, [[self textStorage] length]);
- if ([self shouldChangeTextInRange:range replacementString:nil]) {
- [[self textStorage] setAttributes:textAttributes range: range];
- [self didChangeText];
- }
- [self setTypingAttributes:textAttributes];
- [self setDefaultParagraphStyle:paragraphStyle];
- [self setFont:tvFont];
-
- [self setEditable:oldEditableStatus];
-
- [paragraphStyle release];
-}
-
-- (void)drawRect:(NSRect)rect {
-
-
- // Draw background only for screen display but not while printing
- if([NSGraphicsContext currentContextDrawingToScreen]) {
-
- // Draw textview's background since due to the snippet highlighting we're responsible for it.
- [[self queryEditorBackgroundColor] setFill];
- NSRectFill(rect);
-
- if([[self delegate] isKindOfClass:[CustomQuery class]]) {
-
- // Highlightes the current query if set in the Pref and no snippet session
- // and if nothing is selected in the text view
- if ([self shouldHiliteQuery] && snippetControlCounter<=-1 && ![self selectedRange].length && [[self string] length] < SP_MAX_TEXT_SIZE_FOR_SYNTAX_HIGHLIGHTING) {
- NSUInteger rectCount;
- [[self textStorage] ensureAttributesAreFixedInRange:[self queryRange]];
- NSRectArray queryRects = [[self layoutManager] rectArrayForCharacterRange: [self queryRange]
- withinSelectedCharacterRange: [self queryRange]
- inTextContainer: [self textContainer]
- rectCount: &rectCount ];
- [[self queryHiliteColor] setFill];
- NSRectFillList(queryRects, rectCount);
- }
-
- // Highlight snippets coming from the Query Favorite text macro
- if(snippetControlCounter > -1) {
- // Is the caret still inside a snippet
- if([self checkForCaretInsideSnippet]) {
- for(NSUInteger i=0; i<snippetControlMax; i++) {
- if(snippetControlArray[i][0] > -1) {
- // choose the colors for the snippet parts
- if(i == currentSnippetIndex) {
- [[NSColor colorWithCalibratedRed:1.0 green:0.6 blue:0.0 alpha:0.4] setFill];
- [[NSColor colorWithCalibratedRed:1.0 green:0.6 blue:0.0 alpha:0.8] setStroke];
- } else {
- [[NSColor colorWithCalibratedRed:1.0 green:0.8 blue:0.2 alpha:0.2] setFill];
- [[NSColor colorWithCalibratedRed:1.0 green:0.8 blue:0.2 alpha:0.5] setStroke];
- }
- NSBezierPath *snippetPath = [self roundedBezierPathAroundRange: NSMakeRange(snippetControlArray[i][0],snippetControlArray[i][1]) ];
- [snippetPath fill];
- [snippetPath stroke];
- }
- }
- } else {
- [self endSnippetSession];
- }
- }
-
- }
- }
-
- [super drawRect:rect];
-}
-
-- (NSBezierPath*)roundedBezierPathAroundRange:(NSRange)aRange
-{
- // parameters for snippet highlighting
- CGFloat kappa = 0.5522847498; // magic number from http://www.whizkidtech.redprince.net/bezier/circle/
- CGFloat radius = 6;
- CGFloat horzInset = -3;
- CGFloat vertInset = 0.3;
- BOOL connectDisconnectedPartsWithLine = NO;
-
- NSBezierPath *funkyPath = [NSBezierPath bezierPath];
- NSUInteger rectCount;
- NSRectArray rects = [[self layoutManager] rectArrayForCharacterRange: aRange
- withinSelectedCharacterRange: aRange
- inTextContainer: [self textContainer]
- rectCount: &rectCount ];
- if (rectCount>2 || (rectCount>1 && (SPRectRight(rects[1]) >= SPRectLeft(rects[0]) || connectDisconnectedPartsWithLine))) {
- // highlight complicated multiline snippet
- NSRect lineRects[4];
- lineRects[0] = rects[0];
- lineRects[1] = rects[1];
- lineRects[2] = rects[rectCount-2];
- lineRects[3] = rects[rectCount-1];
- for(int j=0;j<4;j++) lineRects[j] = NSInsetRect(lineRects[j], horzInset, vertInset);
- NSPoint vertices[8];
- vertices[0] = NSMakePoint( SPRectLeft(lineRects[0]), SPRectTop(lineRects[0]) ); // point a
- vertices[1] = NSMakePoint( SPRectRight(lineRects[0]), SPRectTop(lineRects[0]) ); // point b
- vertices[2] = NSMakePoint( SPRectRight(lineRects[2]), SPRectBottom(lineRects[2]) ); // point c
- vertices[3] = NSMakePoint( SPRectRight(lineRects[3]), SPRectBottom(lineRects[2]) ); // point d
- vertices[4] = NSMakePoint( SPRectRight(lineRects[3]), SPRectBottom(lineRects[3]) ); // point e
- vertices[5] = NSMakePoint( SPRectLeft(lineRects[3]), SPRectBottom(lineRects[3]) ); // point f
- vertices[6] = NSMakePoint( SPRectLeft(lineRects[1]), SPRectTop(lineRects[1]) ); // point g
- vertices[7] = NSMakePoint( SPRectLeft(lineRects[0]), SPRectTop(lineRects[1]) ); // point h
-
- for (NSUInteger j=0; j<8; j++) {
- NSPoint curr = vertices[j];
- NSPoint prev = vertices[(j+8-1)%8];
- NSPoint next = vertices[(j+1)%8];
-
- CGFloat s = radius/SPPointDistance(prev, curr);
- if (s>0.5) s = 0.5;
- CGFloat t = radius/SPPointDistance(curr, next);
- if (t>0.5) t = 0.5;
-
- NSPoint a = SPPointOnLine(curr, prev, 0.5);
- NSPoint b = SPPointOnLine(curr, prev, s);
- NSPoint c = curr;
- NSPoint d = SPPointOnLine(curr, next, t);
- NSPoint e = SPPointOnLine(curr, next, 0.5);
-
- if (j==0) [funkyPath moveToPoint:a];
- [funkyPath lineToPoint: b];
- [funkyPath curveToPoint:d controlPoint1:SPPointOnLine(b, c, kappa) controlPoint2:SPPointOnLine(d, c, kappa)];
- [funkyPath lineToPoint: e];
- }
- } else {
- //highlight disconnected snippet parts (or single line snippet)
- for (NSUInteger j=0; j<rectCount; j++) {
- NSRect rect = rects[j];
- rect = NSInsetRect(rect, horzInset, vertInset);
- [funkyPath appendBezierPathWithRoundedRect:rect xRadius:radius yRadius:radius];
- }
- }
- return funkyPath;
-}
-
-
-#pragma mark -
-#pragma mark context menu
-
-/*
- * Add a menu item to context menu for looking up mysql documentation.
- */
-- (NSMenu *)menuForEvent:(NSEvent *)event
-{
- // Set title of the menu item
- if([self selectedRange].length)
- showMySQLHelpFor = NSLocalizedString(@"MySQL Help for Selection", @"MySQL Help for Selection");
- else
- showMySQLHelpFor = NSLocalizedString(@"MySQL Help for Word", @"MySQL Help for Word");
-
- // Add the menu items for
- // - MySQL Help for Word/Selection
- // - Copy as RTF
- // - Select Active Query
- // if it doesn't yet exist
- NSMenu *menu = [[self class] defaultMenu];
-
- if ([[[self class] defaultMenu] itemWithTag:SP_CQ_SEARCH_IN_MYSQL_HELP_MENU_ITEM_TAG] == nil)
- {
- [menu insertItem:[NSMenuItem separatorItem] atIndex:3];
- NSMenuItem *showMySQLHelpForMenuItem = [[NSMenuItem alloc] initWithTitle:showMySQLHelpFor action:@selector(showMySQLHelpForCurrentWord:) keyEquivalent:@"h"];
- [showMySQLHelpForMenuItem setTag:SP_CQ_SEARCH_IN_MYSQL_HELP_MENU_ITEM_TAG];
- [showMySQLHelpForMenuItem setKeyEquivalentModifierMask:NSControlKeyMask];
- [menu insertItem:showMySQLHelpForMenuItem atIndex:4];
- [showMySQLHelpForMenuItem release];
- } else {
- [[menu itemWithTag:SP_CQ_SEARCH_IN_MYSQL_HELP_MENU_ITEM_TAG] setTitle:showMySQLHelpFor];
- }
- if ([[[self class] defaultMenu] itemWithTag:SP_CQ_COPY_AS_RTF_MENU_ITEM_TAG] == nil)
- {
- NSMenuItem *copyAsRTFMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Copy as RTF", @"Copy as RTF") action:@selector(copyAsRTF) keyEquivalent:@"c"];
- [copyAsRTFMenuItem setTag:SP_CQ_COPY_AS_RTF_MENU_ITEM_TAG];
- [copyAsRTFMenuItem setKeyEquivalentModifierMask:NSControlKeyMask];
- [menu insertItem:copyAsRTFMenuItem atIndex:2];
- [copyAsRTFMenuItem release];
- }
- if ([[[self class] defaultMenu] itemWithTag:SP_CQ_SELECT_CURRENT_QUERY_MENU_ITEM_TAG] == nil)
- {
- NSMenuItem *selectCurrentQueryMenuItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Select Active Query", @"Select Active Query") action:@selector(selectCurrentQuery) keyEquivalent:@"y"];
- [selectCurrentQueryMenuItem setTag:SP_CQ_SELECT_CURRENT_QUERY_MENU_ITEM_TAG];
- [selectCurrentQueryMenuItem setKeyEquivalentModifierMask:NSControlKeyMask];
- [menu insertItem:selectCurrentQueryMenuItem atIndex:4];
- [selectCurrentQueryMenuItem release];
- }
- // Hide "Select Active Query" if self is not editable
- [[menu itemAtIndex:4] setHidden:![self isEditable]];
-
- if(customQueryInstance) {
- [[menu itemAtIndex:5] setHidden:NO];
- [[menu itemAtIndex:6] setHidden:NO];
- } else {
- [[menu itemAtIndex:5] setHidden:YES];
- [[menu itemAtIndex:6] setHidden:YES];
- }
-
- return menu;
-}
-
-/*
- * Disable the search in the MySQL help function when getRangeForCurrentWord returns zero length.
- */
-- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
- // Enable or disable the search in the MySQL help menu item depending on whether there is a
- // selection and whether it is a reasonable length.
- if ([menuItem action] == @selector(showMySQLHelpForCurrentWord:)) {
- NSUInteger stringSize = [self getRangeForCurrentWord].length;
- return (stringSize || stringSize > 64);
- }
- // Enable Copy as RTF if something is selected
- if ([menuItem action] == @selector(copyAsRTF)) {
- return ([self selectedRange].length>0);
- }
- // Validate Select Active Query
- if ([menuItem action] == @selector(selectCurrentQuery)) {
- return ([self isEditable] && [[self delegate] isKindOfClass:[CustomQuery class]]);
- }
- // Disable "Copy with Column Names" and "Copy as SQL INSERT"
- // in the main menu
- if ( [menuItem tag] == MENU_EDIT_COPY_WITH_COLUMN
- || [menuItem tag] == MENU_EDIT_COPY_AS_SQL ) {
- return NO;
- }
-
- return YES;
-}
-
-
-#pragma mark -
-#pragma mark delegates
-
-/*
- * Scrollview delegate after the textView's view port was changed.
- * Manily used to update the syntax highlighting for a large text size.
- */
-- (void) boundsDidChangeNotification:(NSNotification *)notification
-{
- // Invoke syntax highlighting if text view port was changed for large text
- if(startListeningToBoundChanges && [[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING)
- {
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doSyntaxHighlighting)
- object:nil];
-
- if(![[self textStorage] changeInLength])
- [self performSelector:@selector(doSyntaxHighlighting) withObject:nil afterDelay:0.4];
- }
-
-}
-
-/*
- * Performs syntax highlighting, re-init autohelp, and re-calculation of snippets after a text change
- */
-- (void)textStorageDidProcessEditing:(NSNotification *)notification
-{
-
- NSTextStorage *textStore = [notification object];
-
- // Make sure that the notification is from the correct textStorage object
- if (textStore!=[self textStorage]) return;
-
- // Cancel autocompletion trigger
- if([prefs boolForKey:SPCustomQueryAutoComplete])
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doAutoCompletion)
- object:nil];
-
- NSInteger editedMask = [textStore editedMask];
-
- // Start autohelp only if the user really changed the text (not e.g. for setting a background color)
- if([prefs boolForKey:SPCustomQueryUpdateAutoHelp] && editedMask != 1) {
- [self performSelector:@selector(autoHelp) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoHelpDelay] doubleValue]];
- }
-
- // Start autocompletion if enabled
- if([[NSApp keyWindow] firstResponder] == self && [prefs boolForKey:SPCustomQueryAutoComplete] && !completionIsOpen && editedMask != 1 && [textStore editedRange].length)
- [self performSelector:@selector(doAutoCompletion) withObject:nil afterDelay:[[prefs valueForKey:SPCustomQueryAutoCompleteDelay] doubleValue]];
-
- // Cancel calling doSyntaxHighlighting for large text
- if([[self string] length] > SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING)
- [NSObject cancelPreviousPerformRequestsWithTarget:self
- selector:@selector(doSyntaxHighlighting)
- object:nil];
-
- // Do syntax highlighting/re-calculate snippet ranges only if the user really changed the text
- if(editedMask != 1) {
-
- // Re-calculate snippet ranges if snippet session is active
- if(snippetControlCounter > -1 && !snippetWasJustInserted && !isProcessingMirroredSnippets) {
- // Remove any fully nested snippets relative to the current snippet which was edited
- NSUInteger currentSnippetLocation = snippetControlArray[currentSnippetIndex][0];
- NSUInteger currentSnippetMaxRange = snippetControlArray[currentSnippetIndex][0] + snippetControlArray[currentSnippetIndex][1];
- NSInteger i;
- for(i=0; i<snippetControlMax; i++) {
- if(snippetControlArray[i][0] > -1
- && i != currentSnippetIndex
- && snippetControlArray[i][0] >= currentSnippetLocation
- && snippetControlArray[i][0] <= currentSnippetMaxRange
- && snippetControlArray[i][0] + snippetControlArray[i][1] >= currentSnippetLocation
- && snippetControlArray[i][0] + snippetControlArray[i][1] <= currentSnippetMaxRange
- ) {
- snippetControlArray[i][0] = -1;
- snippetControlArray[i][1] = -1;
- snippetControlArray[i][2] = -1;
- }
- }
-
- NSUInteger editStartPosition = [textStore editedRange].location;
- NSUInteger changeInLength = [textStore changeInLength];
-
- // Adjust length change to current snippet
- snippetControlArray[currentSnippetIndex][1] += changeInLength;
- // If length < 0 break snippet input
- if(snippetControlArray[currentSnippetIndex][1] < 0) {
- [self endSnippetSession];
- } else {
- // Adjust start position of snippets after caret position
- for(i=0; i<=snippetControlMax; i++) {
- if(snippetControlArray[i][0] > -1 && i != currentSnippetIndex) {
- if(editStartPosition < snippetControlArray[i][0]) {
- snippetControlArray[i][0] += changeInLength;
- } else if(editStartPosition >= snippetControlArray[i][0] && editStartPosition <= snippetControlArray[i][0] + snippetControlArray[i][1]) {
- snippetControlArray[i][1] += changeInLength;
- }
- }
- }
- // Adjust start position of mirrored snippets after caret position
- if(mirroredCounter > -1)
- for(i=0; i<=mirroredCounter; i++) {
- if(editStartPosition < snippetMirroredControlArray[i][1]) {
- snippetMirroredControlArray[i][1] += changeInLength;
- }
- }
- }
-
- if(mirroredCounter > -1 && snippetControlCounter > -1) {
- [self performSelector:@selector(processMirroredSnippets) withObject:nil afterDelay:0.0];
- }
-
-
- }
- if([[self textStorage] changeInLength] > 0)
- textBufferSizeIncreased = YES;
- else
- textBufferSizeIncreased = NO;
-
- if([[self textStorage] changeInLength] < SP_TEXT_SIZE_TRIGGER_FOR_PARTLY_PARSING)
- [self doSyntaxHighlighting];
-
- } else {
- textBufferSizeIncreased = NO;
- }
-
- startListeningToBoundChanges = YES;
-
-}
-
-/*
- * Set font panel's valid modes
- */
-- (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel
-{
- return (NSFontPanelSizeModeMask|NSFontPanelCollectionModeMask);
-}
-
-#pragma mark -
-#pragma mark drag&drop
-
-///////////////////////////
-// Dragging methods
-///////////////////////////
-
-/*
- * Insert the content of a dragged file path or if ⌘ is pressed
- * while dragging insert the file path
- */
-- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
-{
- NSPasteboard *pboard = [sender draggingPasteboard];
-
- if ( [[pboard types] containsObject:NSFilenamesPboardType] && [[pboard types] containsObject:@"CorePasteboardFlavorType 0x54455854"])
- return [super performDragOperation:sender];
-
- if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
- NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
-
- // Only one file path is allowed
- if([files count] > 1) {
- NSLog(@"%@", NSLocalizedString(@"Only one dragged item allowed.",@"Only one dragged item allowed."));
- return YES;
- }
-
- NSString *filepath = [[pboard propertyListForType:NSFilenamesPboardType] objectAtIndex:0];
- // if (([filenamesAttributes fileHFSTypeCode] == 'clpt' && [filenamesAttributes fileHFSCreatorCode] == 'MACS') || [[filename pathExtension] isEqualToString:@"textClipping"] == YES) {
- //
- // }
-
-
- // Set the new insertion point
- NSPoint draggingLocation = [sender draggingLocation];
- draggingLocation = [self convertPoint:draggingLocation fromView:nil];
- NSUInteger characterIndex = [self characterIndexOfPoint:draggingLocation];
- [self setSelectedRange:NSMakeRange(characterIndex,0)];
-
- // Check if user pressed ⌘ while dragging for inserting only the file path
- if([sender draggingSourceOperationMask] == 4)
- {
- [self insertText:filepath];
- return YES;
- }
-
- // Check size and NSFileType
- NSDictionary *attr = [[NSFileManager defaultManager] fileAttributesAtPath:filepath traverseLink:YES];
- if(attr)
- {
- NSNumber *filesize = [attr objectForKey:NSFileSize];
- NSString *filetype = [attr objectForKey:NSFileType];
- if(filetype == NSFileTypeRegular && filesize)
- {
- // Ask for confirmation if file content is larger than 1MB
- if([filesize unsignedLongValue] > 1000000)
- {
- NSAlert *alert = [[NSAlert alloc] init];
- [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK button")];
- [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")];
- [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Do you really want to proceed with %@ of data?", @"message of panel asking for confirmation for inserting large text from dragging action"),
- [NSString stringForByteSize:[filesize longLongValue]]]];
- [alert setHelpAnchor:filepath];
- [alert setMessageText:NSLocalizedString(@"Warning",@"warning")];
- [alert setAlertStyle:NSWarningAlertStyle];
- [alert beginSheetModalForWindow:[self window]
- modalDelegate:self
- didEndSelector:@selector(dragAlertSheetDidEnd:returnCode:contextInfo:)
- contextInfo:nil];
- [alert release];
-
- } else
- [self insertFileContentOfFile:filepath];
- }
- }
- return YES;
- }
-
- // Insert selected items coming from the Navigator
- if ( [[pboard types] containsObject:@"SPDragFromNavigatorPboardType"] ) {
- NSPoint draggingLocation = [sender draggingLocation];
- draggingLocation = [self convertPoint:draggingLocation fromView:nil];
- NSUInteger characterIndex = [self characterIndexOfPoint:draggingLocation];
- [self setSelectedRange:NSMakeRange(characterIndex,0)];
-
- NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:[pboard dataForType:@"SPDragFromNavigatorPboardType"]] autorelease];
- NSArray *draggedItems = [[NSArray alloc] initWithArray:(NSArray *)[unarchiver decodeObjectForKey:@"itemdata"]];
- [unarchiver finishDecoding];
-
- NSMutableString *dragString = [NSMutableString string];
- NSMutableString *aPath = [NSMutableString string];
-
- NSString *currentDb = nil;
- NSString *currentTable = nil;
-
- if (tablesListInstance && [tablesListInstance selectedDatabase])
- currentDb = [tablesListInstance selectedDatabase];
- if (tablesListInstance && [tablesListInstance tableName])
- currentTable = [tablesListInstance tableName];
-
- if(!currentDb) currentDb = @"";
- if(!currentTable) currentTable = @"";
-
- for(NSString* item in draggedItems) {
- if([dragString length]) [dragString appendString:@", "];
- [aPath setString:item];
- // Insert path relative to the current selected db and table if any
- [aPath replaceOccurrencesOfRegex:[NSString stringWithFormat:@"^%@%@", currentDb, SPUniqueSchemaDelimiter] withString:@""];
- [aPath replaceOccurrencesOfRegex:[NSString stringWithFormat:@"^%@%@", currentTable, SPUniqueSchemaDelimiter] withString:@""];
- [dragString appendString:[[aPath componentsSeparatedByString:SPUniqueSchemaDelimiter] componentsJoinedByPeriodAndBacktickQuoted]];
- }
- [self breakUndoCoalescing];
- [self insertText:dragString];
- if (draggedItems) [draggedItems release];
- return YES;
- }
-
- return [super performDragOperation:sender];
-}
-
-/*
- * Confirmation sheetDidEnd method
- */
-- (void)dragAlertSheetDidEnd:(NSAlert *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
-{
-
- [[sheet window] orderOut:nil];
- if ( returnCode == NSAlertFirstButtonReturn )
- [self insertFileContentOfFile:[sheet helpAnchor]];
-
-}
-/*
- * Convert a NSPoint, usually the mouse location, to
- * a character index of the text view.
- */
-- (NSUInteger)characterIndexOfPoint:(NSPoint)aPoint
-{
- NSUInteger glyphIndex;
- NSLayoutManager *layoutManager = [self layoutManager];
- CGFloat fraction;
- NSRange range;
-
- range = [layoutManager glyphRangeForTextContainer:[self textContainer]];
- glyphIndex = [layoutManager glyphIndexForPoint:aPoint
- inTextContainer:[self textContainer]
- fractionOfDistanceThroughGlyph:&fraction];
- if( fraction > 0.5 ) glyphIndex++;
-
- if( glyphIndex == NSMaxRange(range) )
- return [[self textStorage] length];
- else
- return [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
-
-}
-
-/*
- * Insert content of a plain text file for a given path.
- * In addition it tries to figure out the file's text encoding heuristically.
- */
-- (void)insertFileContentOfFile:(NSString *)aPath
-{
-
- NSError *err = nil;
- NSStringEncoding enc;
- NSString *content = nil;
-
- // Make usage of the UNIX command "file" to get an info
- // about file type and encoding.
- NSTask *task=[[NSTask alloc] init];
- NSPipe *pipe=[[NSPipe alloc] init];
- NSFileHandle *handle;
- NSString *result;
- [task setLaunchPath:@"/usr/bin/file"];
- [task setArguments:[NSArray arrayWithObjects:aPath, @"-Ib", nil]];
- [task setStandardOutput:pipe];
- handle=[pipe fileHandleForReading];
- [task launch];
- result=[[NSString alloc] initWithData:[handle readDataToEndOfFile]
- encoding:NSASCIIStringEncoding];
-
- [pipe release];
- [task release];
-
- // UTF16/32 files are detected as application/octet-stream resp. audio/mpeg
- if( [result hasPrefix:@"text/plain"]
- || [[[aPath pathExtension] lowercaseString] isEqualToString:@"sql"]
- || [[[aPath pathExtension] lowercaseString] isEqualToString:@"txt"]
- || [result hasPrefix:@"audio/mpeg"]
- || [result hasPrefix:@"application/octet-stream"]
- )
- {
- // if UTF16/32 cocoa will try to find the correct encoding
- if([result hasPrefix:@"application/octet-stream"] || [result hasPrefix:@"audio/mpeg"] || [result rangeOfString:@"utf-16"].length)
- enc = 0;
- else if([result rangeOfString:@"utf-8"].length)
- enc = NSUTF8StringEncoding;
- else if([result rangeOfString:@"iso-8859-1"].length)
- enc = NSISOLatin1StringEncoding;
- else if([result rangeOfString:@"us-ascii"].length)
- enc = NSASCIIStringEncoding;
- else
- enc = 0;
-
- if(enc == 0) // cocoa tries to detect the encoding
- content = [NSString stringWithContentsOfFile:aPath usedEncoding:&enc error:&err];
- else
- content = [NSString stringWithContentsOfFile:aPath encoding:enc error:&err];
-
- if(content)
- {
- [self insertText:content];
- [result release];
- // [self insertText:@""]; // Invoke keyword uppercasing
- return;
- }
- // If UNIX "file" failed try cocoa's encoding detection
- content = [NSString stringWithContentsOfFile:aPath encoding:enc error:&err];
- if(content)
- {
- [self insertText:content];
- [result release];
- // [self insertText:@""]; // Invoke keyword uppercasing
- return;
- }
- }
-
- [result release];
-
- NSLog(@"%@ ‘%@’.", NSLocalizedString(@"Couldn't read the file content of", @"Couldn't read the file content of"), aPath);
-}
-
-- (void)changeFont:(id)sender
-{
- if (prefs && [self font] != nil) {
- [prefs setObject:[NSArchiver archivedDataWithRootObject:[self font]] forKey:SPCustomQueryEditorFont];
- NSFont *nf = [[NSFontPanel sharedFontPanel] panelConvertFont:[NSUnarchiver unarchiveObjectWithData:[prefs dataForKey:SPCustomQueryEditorFont]]];
- BOOL oldEditable = [self isEditable];
- [self setEditable:YES];
- [self setFont:nf];
- [self setEditable:oldEditable];
- [self setNeedsDisplay:YES];
- [prefs setObject:[NSArchiver archivedDataWithRootObject:nf] forKey:SPCustomQueryEditorFont];
- }
-}
-
-- (void) dealloc
-{
-
- // Cancel any deferred calls
- [NSObject cancelPreviousPerformRequestsWithTarget:self];
-
- // Remove observers
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorFont];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorBackgroundColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorHighlightQueryColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryHighlightCurrentQuery];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorCommentColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorQuoteColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorSQLKeywordColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorBacktickColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorNumericColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorVariableColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorTextColor];
- [prefs removeObserver:self forKeyPath:SPCustomQueryEditorTabStopWidth];
- [prefs removeObserver:self forKeyPath:SPCustomQueryAutoUppercaseKeywords];
-
- if (completionIsOpen) [completionPopup close], completionIsOpen = NO;
- [prefs release];
- [lineNumberView release];
- if(queryHiliteColor) [queryHiliteColor release];
- if(queryEditorBackgroundColor) [queryEditorBackgroundColor release];
- if(commentColor) [commentColor release];
- if(quoteColor) [quoteColor release];
- if(keywordColor) [keywordColor release];
- if(backtickColor) [backtickColor release];
- if(numericColor) [numericColor release];
- if(variableColor) [variableColor release];
- if(otherTextColor) [otherTextColor release];
- [super dealloc];
-}
-
-@end