aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Interfaces/English.lproj/Console.nib/classes.nib93
-rw-r--r--Interfaces/English.lproj/Console.nib/info.nib11
-rw-r--r--Interfaces/English.lproj/Console.nib/keyedobjects.nibbin11296 -> 14081 bytes
-rw-r--r--Interfaces/English.lproj/Credits.rtf3
-rw-r--r--Interfaces/English.lproj/DBView.nib/keyedobjects.nibbin188484 -> 188524 bytes
-rw-r--r--Interfaces/English.lproj/InfoPlist.strings2
-rw-r--r--Resources/Images/button_clear.tiffbin0 -> 6822 bytes
-rw-r--r--Resources/Images/toolbar-preferences-favorites.pngbin3748 -> 1996 bytes
-rw-r--r--Resources/Images/toolbar-preferences-network.pngbin1146 -> 1174 bytes
-rw-r--r--Resources/Info.plist4
-rw-r--r--Resources/PreferenceDefaults.plist4
-rwxr-xr-xScripts/package-application.sh4
-rw-r--r--Source/CMTextView.h1
-rw-r--r--Source/CMTextView.m60
-rw-r--r--Source/CustomQuery.h3
-rw-r--r--Source/CustomQuery.m111
-rw-r--r--Source/RegexKitLite.h130
-rw-r--r--Source/RegexKitLite.m354
-rw-r--r--Source/SPQueryConsole.h3
-rw-r--r--Source/SPQueryConsole.m16
-rw-r--r--Source/SPTextViewAdditions.h2
-rw-r--r--Source/SPTextViewAdditions.m6
-rw-r--r--Source/TableDump.m2
-rw-r--r--sequel-pro.xcodeproj/project.pbxproj22
24 files changed, 727 insertions, 104 deletions
diff --git a/Interfaces/English.lproj/Console.nib/classes.nib b/Interfaces/English.lproj/Console.nib/classes.nib
index 0328b33e..19ac3aff 100644
--- a/Interfaces/English.lproj/Console.nib/classes.nib
+++ b/Interfaces/English.lproj/Console.nib/classes.nib
@@ -1,63 +1,30 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>IBClasses</key>
- <array>
- <dict>
- <key>CLASS</key>
- <string>NSObject</string>
- <key>LANGUAGE</key>
- <string>ObjC</string>
- </dict>
- <dict>
- <key>CLASS</key>
- <string>NSWindow</string>
- <key>LANGUAGE</key>
- <string>ObjC</string>
- <key>SUPERCLASS</key>
- <string>NSResponder</string>
- </dict>
- <dict>
- <key>ACTIONS</key>
- <dict>
- <key>clearConsole</key>
- <string>id</string>
- <key>copy</key>
- <string>id</string>
- <key>saveConsoleAs</key>
- <string>id</string>
- <key>toggleShowSelectShowStatements</key>
- <string>id</string>
- <key>toggleShowTimeStamps</key>
- <string>id</string>
- </dict>
- <key>CLASS</key>
- <string>SPQueryConsole</string>
- <key>LANGUAGE</key>
- <string>ObjC</string>
- <key>OUTLETS</key>
- <dict>
- <key>clearConsoleButton</key>
- <string>NSButton</string>
- <key>consoleSearchField</key>
- <string>NSSearchField</string>
- <key>consoleTableView</key>
- <string>NSTableView</string>
- <key>includeTimeStampsButton</key>
- <string>NSButton</string>
- <key>progressIndicator</key>
- <string>NSProgressIndicator</string>
- <key>saveConsoleButton</key>
- <string>NSButton</string>
- <key>saveLogView</key>
- <string>NSView</string>
- </dict>
- <key>SUPERCLASS</key>
- <string>NSWindowController</string>
- </dict>
- </array>
- <key>IBVersion</key>
- <string>1</string>
-</dict>
-</plist>
+{
+ IBClasses = (
+ {CLASS = NSObject; LANGUAGE = ObjC; },
+ {CLASS = NSWindow; LANGUAGE = ObjC; SUPERCLASS = NSResponder; },
+ {
+ ACTIONS = {
+ clearConsole = id;
+ copy = id;
+ saveConsoleAs = id;
+ toggleShowSelectShowStatements = id;
+ toggleShowTimeStamps = id;
+ };
+ CLASS = SPQueryConsole;
+ LANGUAGE = ObjC;
+ OUTLETS = {
+ clearConsoleButton = NSButton;
+ consoleSearchField = NSSearchField;
+ consoleTableView = NSTableView;
+ includeTimeStampsButton = NSButton;
+ progressIndicator = NSProgressIndicator;
+ saveConsoleButton = NSButton;
+ saveLogView = NSView;
+ showSelectShowStatementsMenuItem = NSMenuItem;
+ showTimeStampsMenuItem = NSMenuItem;
+ };
+ SUPERCLASS = NSWindowController;
+ }
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/Interfaces/English.lproj/Console.nib/info.nib b/Interfaces/English.lproj/Console.nib/info.nib
index 2ca73e20..ce6631f8 100644
--- a/Interfaces/English.lproj/Console.nib/info.nib
+++ b/Interfaces/English.lproj/Console.nib/info.nib
@@ -1,20 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
+ <key>IBDocumentLocation</key>
+ <string>69 85 356 240 0 0 1024 746 </string>
<key>IBFramework Version</key>
- <string>677</string>
+ <string>489.0</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../../sequel-pro.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
- <integer>49</integer>
- <integer>7</integer>
+ <integer>3</integer>
</array>
<key>IBSystem Version</key>
- <string>9G55</string>
+ <string>8S165</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
diff --git a/Interfaces/English.lproj/Console.nib/keyedobjects.nib b/Interfaces/English.lproj/Console.nib/keyedobjects.nib
index 5cb43a2e..a9cb57b7 100644
--- a/Interfaces/English.lproj/Console.nib/keyedobjects.nib
+++ b/Interfaces/English.lproj/Console.nib/keyedobjects.nib
Binary files differ
diff --git a/Interfaces/English.lproj/Credits.rtf b/Interfaces/English.lproj/Credits.rtf
index 2f5301ee..a3758c25 100644
--- a/Interfaces/English.lproj/Credits.rtf
+++ b/Interfaces/English.lproj/Credits.rtf
@@ -16,7 +16,7 @@ Rowan Beentje \
{\field{\*\fldinst{HYPERLINK "http://marius.me.uk/"}}{\fldrslt \cf0 Marius Ursache}}\
Jakob Egger\
\pard\pardeftab720\qc
-{\field{\*\fldinst{HYPERLINK "http://www.google.com/url?q=http://www.bibiko.de/&ei=7XnmSe_aJpCc-gakkKTiBQ&sa=X&oi=spellmeleon_result&resnum=1&ct=result&usg=AFQjCNHkKis7UP61ILUxK7GxnUHortnn_A"}}{\fldrslt \cf3 \ul \ulc3 Hans-J\'f6rg Bibiko}}\
+{\field{\*\fldinst{HYPERLINK "http://www.bibiko.de/"}}{\fldrslt \cf3 \ul \ulc3 Hans-J\'f6rg Bibiko}}\
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\qc\pardirnatural
@@ -40,6 +40,7 @@ Carsten Bl\'fcm\
Andrea Salomoni\
Greg Hulands\
Paul Kim (NoodleLineNumberView)\
+Alex King\
\
\b Artwork
diff --git a/Interfaces/English.lproj/DBView.nib/keyedobjects.nib b/Interfaces/English.lproj/DBView.nib/keyedobjects.nib
index c44f2194..6df4919c 100644
--- a/Interfaces/English.lproj/DBView.nib/keyedobjects.nib
+++ b/Interfaces/English.lproj/DBView.nib/keyedobjects.nib
Binary files differ
diff --git a/Interfaces/English.lproj/InfoPlist.strings b/Interfaces/English.lproj/InfoPlist.strings
index 6048d98c..9e47ef32 100644
--- a/Interfaces/English.lproj/InfoPlist.strings
+++ b/Interfaces/English.lproj/InfoPlist.strings
@@ -1,4 +1,4 @@
/* Localized versions of Info.plist keys */
-CFBundleGetInfoString = "Sequel Pro version 0.9.5 Tiger Version, Copyright 2002-2009 Sequel Pro and CocoaMySQL team.";
+CFBundleGetInfoString = "Sequel Pro Tiger Version 1, Copyright 2002-2009 Sequel Pro and CocoaMySQL team.";
NSHumanReadableCopyright = "Copyright 2002-2009 Sequel Pro and CocoaMySQL team."; \ No newline at end of file
diff --git a/Resources/Images/button_clear.tiff b/Resources/Images/button_clear.tiff
new file mode 100644
index 00000000..9589c170
--- /dev/null
+++ b/Resources/Images/button_clear.tiff
Binary files differ
diff --git a/Resources/Images/toolbar-preferences-favorites.png b/Resources/Images/toolbar-preferences-favorites.png
index 8067e5c0..0ba89b88 100644
--- a/Resources/Images/toolbar-preferences-favorites.png
+++ b/Resources/Images/toolbar-preferences-favorites.png
Binary files differ
diff --git a/Resources/Images/toolbar-preferences-network.png b/Resources/Images/toolbar-preferences-network.png
index e07c3c1f..694a51d5 100644
--- a/Resources/Images/toolbar-preferences-network.png
+++ b/Resources/Images/toolbar-preferences-network.png
Binary files differ
diff --git a/Resources/Info.plist b/Resources/Info.plist
index 086a569b..c76970c4 100644
--- a/Resources/Info.plist
+++ b/Resources/Info.plist
@@ -42,13 +42,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>0.9.5</string>
+ <string>TigerV1</string>
<key>CFBundleVersion</key>
<string></string>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSHumanReadableCopyright</key>
- <string>0.9.5 (593)</string>
+ <string>Tiger v1 (634)</string>
<key>LSMinimumSystemVersion</key>
<string>10.4.0</string>
<key>NSMainNibFile</key>
diff --git a/Resources/PreferenceDefaults.plist b/Resources/PreferenceDefaults.plist
index 72f04178..77825e05 100644
--- a/Resources/PreferenceDefaults.plist
+++ b/Resources/PreferenceDefaults.plist
@@ -46,5 +46,9 @@
<true/>
<key>LastFavoriteIndex</key>
<integer>0</integer>
+ <key>ConsoleShowTimestamps</key>
+ <true/>
+ <key>ConsoleShowSelectsAndShows</key>
+ <true/>
</dict>
</plist>
diff --git a/Scripts/package-application.sh b/Scripts/package-application.sh
index efe63717..982ad4c8 100755
--- a/Scripts/package-application.sh
+++ b/Scripts/package-application.sh
@@ -20,7 +20,7 @@ fi
VERSION_NUMBER=`cat "${BUILT_PRODUCTS_DIR}/${TARGET_NAME}${WRAPPER_SUFFIX}/Contents/Info.plist" | tr -d "\n\t" | sed -e 's/.*<key>CFBundleShortVersionString<\/key><string>\([^<]*\)<\/string>.*/\1/'`
# Define target disk image name and temporary names
-DMG_VOLUME_NAME="Sequel Pro ${VERSION_NUMBER}"
+DMG_VOLUME_NAME="Sequel Pro Tiger ${VERSION_NUMBER}"
DMG_NAME="sequel-pro-${VERSION_NUMBER}"
DMG_BUILD_PATH="${BUILT_PRODUCTS_DIR}"
DISTTEMP="${DMG_BUILD_PATH}/disttemp"
@@ -45,7 +45,7 @@ cp -R "${BUILT_PRODUCTS_DIR}/${TARGET_NAME}${WRAPPER_SUFFIX}" "${DMG_BUILD_PATH}
hdiutil create -srcfolder "${DISTTEMP}" -volname "$DMG_VOLUME_NAME" -fs HFS+ -fsargs '-c c=64,a=16,e=16' -format UDRW "${DMG_BUILD_PATH}/${DMG_NAME}.temp.dmg" > /dev/null
# Compress the disk image
-hdiutil convert "${DMG_BUILD_PATH}/${DMG_NAME}.temp.dmg" -format UDZO -imagekey lib-level=9 -o "${DMG_BUILD_PATH}/${DMG_NAME}.dmg" > /dev/null
+hdiutil convert "${DMG_BUILD_PATH}/${DMG_NAME}.temp.dmg" -format UDBZ -o "${DMG_BUILD_PATH}/${DMG_NAME}.dmg" > /dev/null
# Remove temporary files and copies
rm -rf "${DISTTEMP}"
diff --git a/Source/CMTextView.h b/Source/CMTextView.h
index d4f0135a..70530392 100644
--- a/Source/CMTextView.h
+++ b/Source/CMTextView.h
@@ -52,5 +52,6 @@
- (BOOL) autopair;
- (void) setAutouppercaseKeywords:(BOOL)enableAutouppercaseKeywords;
- (BOOL) autouppercaseKeywords;
+- (void) selectLineNumber:(unsigned int)lineNumber ignoreLeadingNewLines:(BOOL)ignLeadingNewLines;
@end
diff --git a/Source/CMTextView.m b/Source/CMTextView.m
index 62f84629..42bd3fd9 100644
--- a/Source/CMTextView.m
+++ b/Source/CMTextView.m
@@ -22,7 +22,9 @@
// Or mail to <lorenz@textor.ch>
#import "CMTextView.h"
+#import "TableDocument.h"
#import "SPStringAdditions.h"
+#import "SPTextViewAdditions.h"
/*
* Include all the extern variables and prototypes required for flex (used for syntax highlighting)
@@ -41,7 +43,7 @@ YY_BUFFER_STATE yy_scan_string (const char *);
#define kSQLkeyword @"SQLkw" // attribute for found SQL keywords
#define kQuote @"Quote"
-#define MYSQL_DOC_SEARCH_URL @"http://search.mysql.com/search?q=%@"
+#define MYSQL_DOC_SEARCH_URL @"http://dev.mysql.com/doc/refman/%@/en/%@.html"
@implementation CMTextView
@@ -66,28 +68,41 @@ YY_BUFFER_STATE yy_scan_string (const char *);
}
/*
- * Open a search for the current selection on mysql.com
+ * Open the refman if available or a search for the current selection or current word on mysql.com
*/
- (void)lookupSelectionInDocumentation
{
+ // Get the major MySQL server version in the form of x.x, which is basically the first 3 characters of the returned version string
+ NSString *version = [[(TableDocument *)[[self window] delegate] mySQLVersion] substringToIndex:3];
+
// Get the current selection and encode it to be used in a URL
- NSString *keyword = [[[[self string] substringWithRange:[self selectedRange]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
-
+ NSString *keyword = [[[self string] substringWithRange:[self getRangeForCurrentWord]] lowercaseString];
+
+ // Remove whitespace and newlines
+ keyword = [keyword stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
+
+ // Remove whitespace and newlines within the keyword
+ NSMutableString *mutableKeyword = [keyword mutableCopy];
+ [mutableKeyword replaceOccurrencesOfString:@" " withString:@"" options:0 range:NSMakeRange(0, [mutableKeyword length])];
+ [mutableKeyword replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0, [mutableKeyword length])];
+ keyword = [NSString stringWithString:mutableKeyword];
+ [mutableKeyword release];
+
// Open MySQL Documentation search in browser using the terms
- NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:MYSQL_DOC_SEARCH_URL, keyword]];
+ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:MYSQL_DOC_SEARCH_URL, version, [keyword stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
[[NSWorkspace sharedWorkspace] openURL:url];
}
/*
- * Disable the lookup in documentation function when there is no selection.
+ * Disable the lookup in documentation function when getRangeForCurrentWord returns zero length.
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
// Enable or disable the lookup in documentation menu item depending on whether there is a
// selection and whether it is a reasonable length.
if ([menuItem action] == @selector(lookupSelectionInDocumentation)) {
- return (([self selectedRange].length) || ([self selectedRange].length > 256));
+ return (([self getRangeForCurrentWord].length) || ([self getRangeForCurrentWord].length > 256));
}
return YES;
@@ -183,6 +198,37 @@ YY_BUFFER_STATE yy_scan_string (const char *);
}
/*
+ * Selects the line lineNumber relatively to a selection (if given) and scrolls to it
+ */
+- (void) selectLineNumber:(unsigned int)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])];
+ int offset = 0;
+ if(ignLeadingNewLines) // ignore leading empty lines
+ {
+ int arrayCount = [lineRanges count];
+ int i;
+ for (i = 0; i < arrayCount; i++) {
+ if(NSRangeFromString([lineRanges objectAtIndex:i]).length > 0)
+ break;
+ offset++;
+ }
+ }
+ selRange = NSRangeFromString([lineRanges objectAtIndex:lineNumber-1+offset]);
+
+ // adjust selRange if a selection was given
+ if([self selectedRange].length)
+ selRange.location += [self selectedRange].location;
+ [self setSelectedRange:selRange];
+ [self scrollRangeToVisible:selRange];
+}
+
+/*
* Handle some keyDown events in order to provide autopairing functionality (if enabled).
*/
- (void) keyDown:(NSEvent *)theEvent
diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h
index c2eac75f..295bb7e2 100644
--- a/Source/CustomQuery.h
+++ b/Source/CustomQuery.h
@@ -28,6 +28,7 @@
#import "CMTextView.h"
#import "CMMCPConnection.h"
#import "CMMCPResult.h"
+#import "RegexKitLite.h"
@interface CustomQuery : NSObject {
@@ -79,7 +80,7 @@
// Query actions
- (void)performQueries:(NSArray *)queries;
-- (NSString *)queryAtPosition:(long)position;
+- (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind;
// Accessors
- (NSArray *)currentResult;
diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m
index 41170b38..189c2078 100644
--- a/Source/CustomQuery.m
+++ b/Source/CustomQuery.m
@@ -54,14 +54,24 @@
queries = [queryParser splitStringByCharacter:';'];
[queryParser release];
+ NSRange curRange = [textView selectedRange];
+ // Unselect a selection if given to avoid interferring with error highlighting
+ [textView setSelectedRange:NSMakeRange(curRange.location, 0)];
+
[self performQueries:queries];
+ // If no error was selected reconstruct a given selection
+ if([textView selectedRange].length == 0)
+ [textView setSelectedRange:curRange];
+
// Invoke textStorageDidProcessEditing: for syntax highlighting and auto-uppercase
- [textView setSelectedRange:NSMakeRange(0,0)];
+ NSRange oldRange = [textView selectedRange];
+ [textView setSelectedRange:NSMakeRange(oldRange.location,0)];
[textView insertText:@""];
+ [textView setSelectedRange:oldRange];
// Select the text of the query textView for re-editing
- [textView selectAll:self];
+ //[textView selectAll:self];
}
/*
@@ -77,7 +87,8 @@
// If the current selection is a single caret position, run the current query.
if (selectedRange.length == 0) {
- query = [self queryAtPosition:selectedRange.location];
+ BOOL doLookBehind = YES;
+ query = [self queryAtPosition:selectedRange.location lookBehind:&doLookBehind];
if (!query) {
NSBeep();
return;
@@ -93,7 +104,7 @@
// Invoke textStorageDidProcessEditing: for syntax highlighting and auto-uppercase
// and preserve the selection
- [textView setSelectedRange:NSMakeRange(0,0)];
+ [textView setSelectedRange:NSMakeRange(selectedRange.location,0)];
[textView insertText:@""];
[textView setSelectedRange:selectedRange];
@@ -426,8 +437,36 @@ sets the tableView columns corresponding to the mysql-result
}
[prefs setObject:menuItems forKey:@"queryHistory"];
+ // Error checking
if ( [errors length] ) {
+ // set the error text
[errorText setStringValue:errors];
+ // select the line x of the first error if error message contains "at line x"
+ NSError *err1 = NULL;
+ NSRange errorLineNumberRange = [errors rangeOfRegex:@"at line ([0-9]+)" options:RKLNoOptions inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1];
+ if(errorLineNumberRange.length) // if a line number was found
+ {
+ // Get the line number
+ unsigned int errorAtLine = [[errors substringWithRange:errorLineNumberRange] intValue];
+ [textView selectLineNumber:errorAtLine ignoreLeadingNewLines:YES];
+
+ // Check for near message
+ NSRange errorNearMessageRange = [errors rangeOfRegex:@"use near '(.*?)'" options:(RKLMultiline|RKLDotAll) inRange:NSMakeRange(0, [errors length]) capture:1 error:&err1];
+ if(errorNearMessageRange.length) // if a "near message" was found
+ {
+ // Get the line of the first error via the current selected line
+ NSRange lineRange = [[textView string] lineRangeForRange:NSMakeRange([textView selectedRange].location, 0)];
+ // Build the range to search for nearMessage (beginning from the error line to try to avoid mismatching)
+ NSRange theRange = NSMakeRange(lineRange.location, [[textView string] length]-lineRange.location);
+ // Get the range in textView of the near message
+ NSRange textNearMessageRange = [[[textView string] substringWithRange:theRange] rangeOfString:[errors substringWithRange:errorNearMessageRange] options:NSLiteralSearch];
+ // Correct the near message range
+ textNearMessageRange = NSMakeRange(textNearMessageRange.location+lineRange.location, textNearMessageRange.length);
+ // Select the near message and scroll to it
+ [textView setSelectedRange:textNearMessageRange];
+ [textView scrollRangeToVisible:textNearMessageRange];
+ }
+ }
} else {
[errorText setStringValue:NSLocalizedString(@"There were no errors.", @"text shown when query was successfull")];
}
@@ -521,13 +560,16 @@ sets the tableView columns corresponding to the mysql-result
* Retrieve the query at a position specified within the custom query
* text view. This will return nil if the position specified is beyond
* the available string or if an empty query would be returned.
+ * If lookBehind is set, returns the *previous* query, but only if the
+ * caret is after a query ending in a semicolon, on the same line, and
+ * separated only by whitespace.
*/
-- (NSString *)queryAtPosition:(long)position
+- (NSString *)queryAtPosition:(long)position lookBehind:(BOOL *)doLookBehind
{
SPSQLParser *customQueryParser;
NSArray *queries;
NSString *query = nil;
- int i, queryPosition = 0;
+ int i, queryPosition = 0, queryStartPosition;
// If the supplied position is negative or beyond the end of the string, return nil.
if (position < 0 || position > [[textView string] length])
@@ -541,13 +583,24 @@ sets the tableView columns corresponding to the mysql-result
// Walk along the array of queries to identify the current query - taking into account
// the extra semicolon at the end of each query
for (i = 0; i < [queries count]; i++ ) {
+ queryStartPosition = queryPosition;
queryPosition += [[queries objectAtIndex:i] length];
if (queryPosition >= position) {
+
+ // If lookbehind is enabled, determine whether it's valid
+ if (doLookBehind) {
+ if (i && [[[[textView string] substringWithRange:NSMakeRange(queryStartPosition, position - queryStartPosition)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0) {
+ query = [NSString stringWithString:[queries objectAtIndex:i-1]];
+ break;
+ }
+ *doLookBehind = NO;
+ }
query = [NSString stringWithString:[queries objectAtIndex:i]];
break;
}
queryPosition++;
}
+ if (doLookBehind && position == [[textView string] length] && !query) query = [queries lastObject];
[queries release];
@@ -980,6 +1033,7 @@ traps enter key and
if ([textView selectedRange].length == 0) {
int selectionPosition = [textView selectedRange].location;
int movedRangeStart, movedRangeLength;
+ BOOL updateQueryButtons = FALSE;
NSRange oldSelection;
// Retrieve the old selection position
@@ -991,19 +1045,48 @@ traps enter key and
// parsing overhead - which is cheap on small text strings but heavy of large queries.
movedRangeStart = (selectionPosition < oldSelection.location)?selectionPosition:oldSelection.location;
movedRangeLength = abs(selectionPosition - oldSelection.location);
- if (oldSelection.length > 0
- || movedRangeLength > 100
- || oldSelection.location > [[textView string] length]
- || [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound
- || (![runSelectionButton isEnabled] && selectionPosition > oldSelection.location
- && [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length])
- ) {
+ if (oldSelection.length > 0) updateQueryButtons = TRUE;
+ if (!updateQueryButtons && movedRangeLength > 100) updateQueryButtons = TRUE;
+ if (!updateQueryButtons && oldSelection.location > [[textView string] length]) updateQueryButtons = TRUE;
+ if (!updateQueryButtons && [[textView string] rangeOfString:@";" options:0 range:NSMakeRange(movedRangeStart, movedRangeLength)].location != NSNotFound) updateQueryButtons = TRUE;
+ if (!updateQueryButtons && ![runSelectionButton isEnabled] && selectionPosition > oldSelection.location
+ && [[[[textView string] substringWithRange:NSMakeRange(movedRangeStart, movedRangeLength)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) updateQueryButtons = TRUE;
+ if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")]) {
+ int charPosition;
+ unichar theChar;
+ for (charPosition = selectionPosition; charPosition > 0; charPosition--) {
+ theChar = [[textView string] characterAtIndex:charPosition-1];
+ if (theChar == ';') {
+ updateQueryButtons = TRUE;
+ break;
+ }
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:theChar]) break;
+ }
+ }
+ if (!updateQueryButtons && [[runSelectionButton title] isEqualToString:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")]) {
+ int charPosition;
+ unichar theChar;
+ for (charPosition = selectionPosition; charPosition > 0; charPosition--) {
+ theChar = [[textView string] characterAtIndex:charPosition-1];
+ if (theChar == ';') break;
+ if (![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:theChar]) {
+ updateQueryButtons = TRUE;
+ break;
+ }
+ }
+ }
+ if (updateQueryButtons) {
[runSelectionButton setTitle:NSLocalizedString(@"Run Current", @"Title of button to run current query in custom query view")];
[runSelectionMenuItem setTitle:NSLocalizedString(@"Run Current Query", @"Title of action menu item to run current query in custom query view")];
// If a valid query is present at the cursor position, enable the button
- if ([self queryAtPosition:selectionPosition]) {
+ BOOL isLookBehind = YES;
+ if ([self queryAtPosition:selectionPosition lookBehind:&isLookBehind]) {
+ if (isLookBehind) {
+ [runSelectionButton setTitle:NSLocalizedString(@"Run Previous", @"Title of button to run query just before text caret in custom query view")];
+ [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Previous Query", @"Title of action menu item to run query just before text caret in custom query view")];
+ }
[runSelectionButton setEnabled:YES];
[runSelectionMenuItem setEnabled:YES];
} else {
diff --git a/Source/RegexKitLite.h b/Source/RegexKitLite.h
new file mode 100644
index 00000000..0338f582
--- /dev/null
+++ b/Source/RegexKitLite.h
@@ -0,0 +1,130 @@
+//
+// RegexKitLite.h
+// http://regexkit.sourceforge.net/
+// Licensesd under the terms of the BSD License, as specified below.
+//
+
+/*
+ Copyright (c) 2008, John Engelhart
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the Zang Industries nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef __OBJC__
+
+#import <Foundation/NSObjCRuntime.h>
+#import <Foundation/NSRange.h>
+#import <Foundation/NSString.h>
+
+#endif // __OBJC__
+
+#include <limits.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// For Mac OS X < 10.5.
+#ifndef NSINTEGER_DEFINED
+#define NSINTEGER_DEFINED
+#ifdef __LP64__ || NS_BUILD_32_LIKE_64
+typedef long NSInteger;
+typedef unsigned long NSUInteger;
+#define NSIntegerMin LONG_MIN
+#define NSIntegerMax LONG_MAX
+#define NSUIntegerMax ULONG_MAX
+#else
+typedef int NSInteger;
+typedef unsigned int NSUInteger;
+#define NSIntegerMin INT_MIN
+#define NSIntegerMax INT_MAX
+#define NSUIntegerMax UINT_MAX
+#endif
+#endif // NSINTEGER_DEFINED
+
+#ifndef _REGEXKITLITE_H_
+#define _REGEXKITLITE_H_
+
+#ifdef __OBJC__
+
+@class NSError;
+
+// NSError error domains and user info keys.
+extern NSString * const RKLICURegexErrorDomain;
+
+extern NSString * const RKLICURegexErrorNameErrorKey;
+extern NSString * const RKLICURegexLineErrorKey;
+extern NSString * const RKLICURegexOffsetErrorKey;
+extern NSString * const RKLICURegexPreContextErrorKey;
+extern NSString * const RKLICURegexPostContextErrorKey;
+extern NSString * const RKLICURegexRegexErrorKey;
+extern NSString * const RKLICURegexRegexOptionsErrorKey;
+
+// These must be idential to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html
+enum {
+ RKLNoOptions = 0,
+ RKLCaseless = 2,
+ RKLComments = 4,
+ RKLDotAll = 32,
+ RKLMultiline = 8,
+ RKLUnicodeWordBoundaries = 256
+};
+typedef uint32_t RKLRegexOptions;
+
+@interface NSString (RegexKitLiteAdditions)
+
++ (void)clearStringCache;
+
++ (NSInteger)captureCountForRegex:(NSString *)regexString;
++ (NSInteger)captureCountForRegex:(NSString *)regexString options:(RKLRegexOptions)options error:(NSError **)error;
+
+- (BOOL)isMatchedByRegex:(NSString *)regexString;
+- (BOOL)isMatchedByRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error;
+
+- (NSRange)rangeOfRegex:(NSString *)regexString;
+- (NSRange)rangeOfRegex:(NSString *)regexString capture:(NSInteger)capture;
+- (NSRange)rangeOfRegex:(NSString *)regexString inRange:(NSRange)range;
+- (NSRange)rangeOfRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
+
+- (NSString *)stringByMatching:(NSString *)regexString;
+- (NSString *)stringByMatching:(NSString *)regexString capture:(NSInteger)capture;
+- (NSString *)stringByMatching:(NSString *)regexString inRange:(NSRange)range;
+- (NSString *)stringByMatching:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
+
+@end
+
+#endif // _REGEXKITLITE_H_
+
+#endif // __OBJC__
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
diff --git a/Source/RegexKitLite.m b/Source/RegexKitLite.m
new file mode 100644
index 00000000..9e77bd28
--- /dev/null
+++ b/Source/RegexKitLite.m
@@ -0,0 +1,354 @@
+//
+// RegexKitLite.m
+// http://regexkit.sourceforge.net/
+// Licensesd under the terms of the BSD License, as specified below.
+//
+
+/*
+ Copyright (c) 2008, John Engelhart
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the Zang Industries nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#import <CoreFoundation/CFBase.h>
+#import <CoreFoundation/CFString.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSError.h>
+#import <Foundation/NSException.h>
+#import <libkern/OSAtomic.h>
+#import <string.h>
+#import <stdlib.h>
+#import "RegexKitLite.h"
+
+#ifndef RKL_CACHE_SIZE
+#define RKL_CACHE_SIZE 23
+#endif
+
+#ifndef RKL_FIXED_LENGTH
+#define RKL_FIXED_LENGTH 2048
+#endif
+
+// Ugly macros to keep other parts clean.
+
+#define NSRangeInsideRange(inside, within) ({NSRange _inside = (inside), _within = (within); (((_inside.location - _within.location) <= _within.length) && ((NSMaxRange(_inside) - _within.location) <= _within.length));})
+#define NSEqualRanges(range1, range2) ({NSRange _r1 = (range1), _r2 = (range2); ((_r1.location == _r2.location) && (_r1.length == _r2.length));})
+#define NSMakeRange(loc, len) ((NSRange){(NSUInteger)(loc), (NSUInteger)(len)})
+#define CFMakeRange(loc, len) ((CFRange){(CFIndex)(loc), (CFIndex)(len)})
+#define NSMaxRange(r) ({NSRange _r = (r); _r.location + _r.length;})
+#define NSNotFoundRange ((NSRange){NSNotFound, 0})
+#define NSMaxiumRange ((NSRange){0, NSUIntegerMax})
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
+#define CFAutorelease(obj) ({CFTypeRef _obj = (obj); (_obj == NULL) ? NULL : [(id)CFMakeCollectable(_obj) autorelease]; })
+#else
+#define CFAutorelease(obj) ({CFTypeRef _obj = (obj); (_obj == NULL) ? NULL : [(id)(_obj) autorelease]; })
+#endif
+
+#define RKLMakeString(str, hash, len, uc) ((RKLString){(str), (hash), (len), (UniChar *)(uc)})
+#define RKLClearCacheSlotLastString(ce) ({ ce->last = RKLMakeString(NULL, 0, 0, NULL); ce->lastFindRange = NSNotFoundRange; ce->lastMatchRange = NSNotFoundRange; })
+#define RKLGetRangeForCapture(regex, status, capture, range) ({ range.location = (NSUInteger)uregex_start(regex, capture, &status); range.length = (NSUInteger)uregex_end(regex, capture, &status) - range.location; status; })
+#define RKLInternalException [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"An internal error occured at %@:%d", [NSString stringWithUTF8String:__FILE__], __LINE__] userInfo:NULL]
+
+// Exported symbols. Error domains, keys, etc.
+NSString * const RKLICURegexErrorDomain = @"RKLICURegexErrorDomain";
+
+NSString * const RKLICURegexErrorNameErrorKey = @"RKLICURegexErrorName";
+NSString * const RKLICURegexLineErrorKey = @"RKLICURegexLine";
+NSString * const RKLICURegexOffsetErrorKey = @"RKLICURegexOffset";
+NSString * const RKLICURegexPreContextErrorKey = @"RKLICURegexPreContext";
+NSString * const RKLICURegexPostContextErrorKey = @"RKLICURegexPostContext";
+NSString * const RKLICURegexRegexErrorKey = @"RKLICURegexRegex";
+NSString * const RKLICURegexRegexOptionsErrorKey = @"RKLICURegexRegexOptions";
+
+// Type / struct definitions
+
+typedef struct uregex uregex; // Opaque ICU regex type.
+
+#define U_PARSE_CONTEXT_LEN 16
+
+typedef struct UParseError {
+ int32_t line;
+ int32_t offset;
+ unichar preContext[U_PARSE_CONTEXT_LEN];
+ unichar postContext[U_PARSE_CONTEXT_LEN];
+} UParseError;
+
+typedef struct {
+ void *string; // Used ONLY for pointer equality tests! Never messaged!
+ CFHashCode hash;
+ NSUInteger length;
+ UniChar *uniChar;
+} RKLString;
+
+typedef struct {
+ NSString *regexString;
+ RKLRegexOptions regexOptions;
+ uregex *icu_regex;
+ NSInteger captureCount;
+
+ RKLString last;
+ NSRange lastFindRange;
+ NSRange lastMatchRange;
+} RKLCacheSlot;
+
+// ICU functions. See http://www.icu-project.org/apiref/icu4c/uregex_8h.html Tweaked slightly from the originals, but functionally identical.
+const char * u_errorName (int32_t status);
+int32_t u_strlen (const UniChar *s);
+void uregex_close (uregex *regexp);
+int32_t uregex_end (uregex *regexp, int32_t groupNum, int32_t *status);
+BOOL uregex_find (uregex *regexp, int32_t location, int32_t *status);
+BOOL uregex_findNext (uregex *regexp, int32_t *status);
+int32_t uregex_groupCount (uregex *regexp, int32_t *status);
+uregex * uregex_open (const UniChar *pattern, int32_t patternLength, RKLRegexOptions flags, UParseError *parseError, int32_t *status);
+void uregex_setText (uregex *regexp, const UniChar *text, int32_t textLength, int32_t *status);
+int32_t uregex_start (uregex *regexp, int32_t groupNum, int32_t *status);
+
+static RKLCacheSlot *getCachedRegex (NSString *regexString, RKLRegexOptions regexOptions, NSError **error);
+static NSError *RKLNSErrorForRegex (NSString *regexString, RKLRegexOptions regexOptions, UParseError *parseError, int status);
+
+// Compile unit local global variables
+static OSSpinLock cacheSpinLock = OS_SPINLOCK_INIT;
+static RKLCacheSlot RKLCache[RKL_CACHE_SIZE];
+static RKLCacheSlot *lastCacheSlot;
+static void *lastRegexString;
+static UniChar fixedUniChar[(RKL_FIXED_LENGTH * sizeof(UniChar))];
+static RKLString fixedString = {NULL, 0, 0, &fixedUniChar[0]};
+static RKLString dynamicString;
+
+// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
+// IMPORTANT! Should only be called with cacheSpinLock already locked!
+// ----------
+
+static RKLCacheSlot *getCachedRegex(NSString *regexString, RKLRegexOptions regexOptions, NSError **error) {
+ CFHashCode regexHash = CFHash(regexString);
+ RKLCacheSlot *cacheSlot = &RKLCache[regexHash % RKL_CACHE_SIZE]; // Retrieve the cache slot for this regex.
+ UParseError parseError = (UParseError){-1, -1, {0}, {0}};
+ UniChar *regexUniChar = NULL;
+ CFIndex regexLength = 0;
+ int32_t status = 0;
+
+ // Return the cached entry if it's a match, otherwise clear the slot and create a new ICU regex in its place.
+ if((cacheSlot->regexOptions == regexOptions) && (cacheSlot->icu_regex != NULL) && (cacheSlot->regexString != NULL) && (CFEqual(regexString, cacheSlot->regexString) == YES)) { lastCacheSlot = cacheSlot; lastRegexString = regexString; return(cacheSlot); }
+
+ RKLClearCacheSlotLastString(cacheSlot); // Clear any cached string state for this cache slot.
+ if(cacheSlot->regexString != NULL) { CFRelease(cacheSlot->regexString); cacheSlot->regexString = NULL; cacheSlot->regexOptions = 0; }
+ if(cacheSlot->icu_regex != NULL) { uregex_close(cacheSlot->icu_regex); cacheSlot->icu_regex = NULL; cacheSlot->captureCount = -1; }
+
+ cacheSlot->regexString = (NSString *)CFStringCreateCopy(NULL, (CFStringRef)regexString); // Get a cheap immutable copy.
+ cacheSlot->regexOptions = regexOptions;
+ regexLength = CFStringGetLength((CFStringRef)regexString); // In UTF16 code pairs.
+
+ // Try to quickly obtain the regex string in UTF16 format. Otherwise allocate enough space on the stack and convert to UTF16 using the stack buffer.
+ if((regexUniChar = (UniChar *)CFStringGetCharactersPtr((CFStringRef)regexString)) == NULL) {
+ if((regexUniChar = alloca(regexLength * sizeof(UniChar))) == NULL) { return(NULL); }
+ CFStringGetCharacters((CFStringRef)regexString, CFRangeMake(0, regexLength), regexUniChar);
+ }
+
+ // Create the ICU regex. If there is a problem, create a NSError if requested.
+ if(((cacheSlot->icu_regex = uregex_open(regexUniChar, (int32_t)regexLength, regexOptions, &parseError, &status)) == NULL) && (status > 0)) {
+ if(error != NULL) { *error = RKLNSErrorForRegex(regexString, regexOptions, &parseError, status); }
+ return(NULL);
+ }
+
+ cacheSlot->captureCount = (NSUInteger)uregex_groupCount(cacheSlot->icu_regex, &status);
+ lastCacheSlot = cacheSlot;
+ lastRegexString = regexString;
+
+ return(cacheSlot);
+}
+
+static NSError *RKLNSErrorForRegex(NSString *regexString, RKLRegexOptions regexOptions, UParseError *parseError, int status) {
+ NSNumber *regexOptionsNumber = [NSNumber numberWithInt:regexOptions];
+ NSNumber *lineNumber = [NSNumber numberWithInt:parseError->line];
+ NSNumber *offsetNumber = [NSNumber numberWithInt:parseError->offset];
+ NSString *preContextString = [NSString stringWithCharacters:&parseError->preContext[0] length:u_strlen(&parseError->preContext[0])];
+ NSString *postContextString = [NSString stringWithCharacters:&parseError->postContext[0] length:u_strlen(&parseError->postContext[0])];
+ NSString *errorNameString = [NSString stringWithUTF8String:u_errorName(status)];
+ NSString *reasonString = [NSString stringWithFormat:@"The error %@ occured at line %d, column %d: %@<<HERE>>%@", errorNameString, parseError->line, parseError->offset, preContextString, postContextString];
+
+ // If line == -1, parseError doesn't contain any useful information. Set lineNumber to NULL,
+ // which will stop adding objects to the dictionary at that point, ignoring everything after.
+ if(parseError->line == -1) { reasonString = [NSString stringWithFormat:@"The error %@ occured.", errorNameString]; lineNumber = NULL; }
+
+ return([NSError errorWithDomain:RKLICURegexErrorDomain code:(NSInteger)status userInfo:[NSDictionary dictionaryWithObjectsAndKeys: @"There was an error compiling the regular expression.", @"NSLocalizedDescription", reasonString, @"NSLocalizedFailureReason", regexString, RKLICURegexRegexErrorKey, regexOptionsNumber, RKLICURegexRegexOptionsErrorKey, lineNumber, RKLICURegexLineErrorKey, offsetNumber, RKLICURegexOffsetErrorKey, preContextString, RKLICURegexPreContextErrorKey, postContextString, RKLICURegexPostContextErrorKey, errorNameString, RKLICURegexErrorNameErrorKey, NULL]]);
+}
+
+@implementation NSString (RegexKitLiteAdditions)
+
++ (void)clearStringCache
+{
+ OSSpinLockLock(&cacheSpinLock);
+ fixedString = RKLMakeString(NULL, 0, 0, fixedString.uniChar);
+ dynamicString = RKLMakeString(NULL, 0, 0, reallocf(dynamicString.uniChar, 0));
+ NSUInteger x = 0;
+ for(x = 0; x < RKL_CACHE_SIZE; x++) { RKLClearCacheSlotLastString((&RKLCache[x])); }
+ OSSpinLockUnlock(&cacheSpinLock);
+}
+
++ (NSInteger)captureCountForRegex:(NSString *)regexString
+{
+ return([self captureCountForRegex:regexString options:RKLNoOptions error:NULL]);
+}
+
++ (NSInteger)captureCountForRegex:(NSString *)regexString options:(RKLRegexOptions)options error:(NSError **)error
+{
+ if(error != NULL) { *error = NULL; }
+ if(regexString == NULL) { [NSException raise:NSInvalidArgumentException format:@"The regular expression argument is NULL."]; }
+
+ RKLCacheSlot *cacheSlot = NULL;
+ NSInteger captureCount = -1;
+
+ OSSpinLockLock(&cacheSpinLock);
+ if((cacheSlot = getCachedRegex(regexString, options, error)) != NULL) { captureCount = cacheSlot->captureCount; }
+ OSSpinLockUnlock(&cacheSpinLock);
+
+ return(captureCount);
+}
+
+- (BOOL)isMatchedByRegex:(NSString *)regexString
+{
+ return([self isMatchedByRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange error:NULL]);
+}
+
+- (BOOL)isMatchedByRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error
+{
+ return(([self rangeOfRegex:regexString options:options inRange:range capture:0 error:error].location == NSNotFound) ? NO : YES);
+}
+
+- (NSString *)stringByMatching:(NSString *)regexString
+{
+ return([self stringByMatching:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:0 error:NULL]);
+}
+
+- (NSString *)stringByMatching:(NSString *)regexString capture:(NSInteger)capture
+{
+ return([self stringByMatching:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:capture error:NULL]);
+}
+
+- (NSString *)stringByMatching:(NSString *)regexString inRange:(NSRange)range
+{
+ return([self stringByMatching:regexString options:RKLNoOptions inRange:range capture:0 error:NULL]);
+}
+
+- (NSString *)stringByMatching:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error
+{
+ NSRange matchedRange = [self rangeOfRegex:regexString options:options inRange:range capture:capture error:error];
+ return((matchedRange.location == NSNotFound) ? NULL : CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length))));
+}
+
+- (NSRange)rangeOfRegex:(NSString *)regexString
+{
+ return([self rangeOfRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:0 error:NULL]);
+}
+
+- (NSRange)rangeOfRegex:(NSString *)regexString capture:(NSInteger)capture
+{
+ return([self rangeOfRegex:regexString options:RKLNoOptions inRange:NSMaxiumRange capture:capture error:NULL]);
+}
+
+- (NSRange)rangeOfRegex:(NSString *)regexString inRange:(NSRange)range
+{
+ return([self rangeOfRegex:regexString options:RKLNoOptions inRange:range capture:0 error:NULL]);
+}
+
+
+// IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity.
+// ----------
+
+- (NSRange)rangeOfRegex:(NSString *)regexString options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error
+{
+ if(error != NULL) { *error = NULL; }
+ if(regexString == NULL) { [NSException raise:NSInvalidArgumentException format:@"The regular expression argument is NULL."]; }
+
+ NSRange captureRange = NSNotFoundRange;
+ CFIndex stringLength = CFStringGetLength((CFStringRef)self); // In UTF16 code pairs.
+ RKLCacheSlot *cacheSlot = NULL;
+ NSException *exception = NULL;
+ int32_t status = 0;
+
+ if(range.length == NSUIntegerMax) { range.length = stringLength; } // For convenience.
+ if((NSUInteger)stringLength < NSMaxRange(range)) { [NSException raise:NSRangeException format:@"The search range exceeds the strings bounds."]; }
+
+ // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS!
+
+ OSSpinLockLock(&cacheSpinLock); // Grab the lock and get cache entry.
+ // Fast path the common case where this regex is the same one used last time.
+ // On a miss, do full lookup with getCachedRegex(), which compiles the regex if it's not in the cache.
+ if((lastCacheSlot != NULL) && (options == lastCacheSlot->regexOptions) && (CFEqual(regexString, lastCacheSlot->regexString) == YES)) { cacheSlot = lastCacheSlot; }
+ else if((cacheSlot = getCachedRegex(regexString, options, error)) == NULL) { goto exitNow; }
+ if(cacheSlot->icu_regex == NULL) { exception = RKLInternalException; goto exitNow; } // assertion check.
+
+ if((capture < 0) || (capture > cacheSlot->captureCount)) { exception = [NSException exceptionWithName:NSInvalidArgumentException reason:@"The capture argument is not valid." userInfo:NULL]; goto exitNow; }
+
+ RKLString selfString = RKLMakeString(self, CFHash(self), stringLength, CFStringGetCharactersPtr((CFStringRef)self));
+ // *string will point to the most approrpiate buffer. If selfString contains a valid uniChar pointer, that's used.
+ // Otherwise, use the strings length to determine if the fixed or dynamically sized conversion buffer should be used.
+ RKLString *string = (selfString.uniChar != NULL) ? &selfString : (stringLength < RKL_FIXED_LENGTH) ? &fixedString : &dynamicString;
+
+ // Check if this regex is already set to this string.
+ if((cacheSlot->last.uniChar == string->uniChar) && (cacheSlot->last.string == selfString.string) && (cacheSlot->last.hash == selfString.hash) && (cacheSlot->last.length == selfString.length) && (cacheSlot->last.string != NULL)) { goto alreadySetText; }
+
+ // If we didn't get direct UTF16 access, perform any required UTF16 conversions if the current buffer doesn't match this string.
+ if((string != &selfString) && ((string->string != self) || (string->length != selfString.length) || (string->hash != selfString.hash))) {
+ *string = RKLMakeString(self, selfString.hash, selfString.length, string->uniChar);
+ // If this is the dynamically sized buffer, resize the allocation to the correct size.
+ if((stringLength >= RKL_FIXED_LENGTH) && ((string->uniChar = reallocf(string->uniChar, (selfString.length * sizeof(UniChar)))) == NULL)) { goto exitNow; }
+ CFStringGetCharacters((CFStringRef)self, CFRangeMake(0, string->length), string->uniChar); // Convert to a UTF16 string.
+ }
+
+ RKLClearCacheSlotLastString(cacheSlot); // Clear the cached state for this regex.
+ if(string->uniChar == NULL) { exception = RKLInternalException; goto exitNow; } // assertion check.
+ uregex_setText(cacheSlot->icu_regex, string->uniChar, string->length, &status); // "set" the ICU regex to this string.
+ if(status != 0) { goto exitNow; }
+ cacheSlot->last = *string; // Cache the last string we set this regex to.
+
+ alreadySetText:
+ if((NSEqualRanges(range, cacheSlot->lastFindRange) == NO)) { // Perform a 'find' if the current range is different than the last find range.
+ // Using uregex_findNext can be a slight performance win.
+ BOOL useFindNext = (range.location == (NSMaxRange(cacheSlot->lastMatchRange) + ((cacheSlot->lastMatchRange.length == 0) ? 1 : 0))) ? YES : NO;
+
+ cacheSlot->lastFindRange = NSNotFoundRange; // Cleared the cached search/find range.
+ if(useFindNext == NO) { if((uregex_find (cacheSlot->icu_regex, range.location, &status) == NO) || (status != 0)) { goto exitNow; } }
+ else { if((uregex_findNext(cacheSlot->icu_regex, &status) == NO) || (status != 0)) { goto exitNow; } }
+
+ if(RKLGetRangeForCapture(cacheSlot->icu_regex, status, 0, cacheSlot->lastMatchRange) != 0) { goto exitNow; }
+ cacheSlot->lastFindRange = range; // Cache the successful search/find range.
+ }
+
+ if(NSRangeInsideRange(cacheSlot->lastMatchRange, range) == NO) { goto exitNow; } // If the regex matched outside the requested range, exit.
+ if(capture == 0) { captureRange = cacheSlot->lastMatchRange; } else { RKLGetRangeForCapture(cacheSlot->icu_regex, status, capture, captureRange); }
+
+ exitNow: // A bit of advice...
+ OSSpinLockUnlock(&cacheSpinLock); // Always... no, no... never... forget to unlock your locks.
+ if(exception != NULL) { [exception raise]; } // I think the young people enjoy it when I "get down" verbally, don't you?
+ if(status > 0) { [NSException raise:NSInternalInconsistencyException format:@"ICU regular expression error #%d, %s", status, u_errorName(status)]; }
+ return((status == 0) ? captureRange : NSNotFoundRange);
+}
+
+@end
diff --git a/Source/SPQueryConsole.h b/Source/SPQueryConsole.h
index 4a2326c6..6325fb0c 100644
--- a/Source/SPQueryConsole.h
+++ b/Source/SPQueryConsole.h
@@ -29,7 +29,8 @@
IBOutlet NSSearchField *consoleSearchField;
IBOutlet NSProgressIndicator *progressIndicator;
IBOutlet NSButton *includeTimeStampsButton, *saveConsoleButton, *clearConsoleButton;
-
+ IBOutlet NSMenuItem *showTimeStampsMenuItem, *showSelectShowStatementsMenuItem;
+
NSFont *consoleFont;
NSMutableArray *messagesFullSet, *messagesFilteredSet, *messagesVisibleSet;
BOOL showSelectStatementsAreDisabled;
diff --git a/Source/SPQueryConsole.m b/Source/SPQueryConsole.m
index ac96e51f..fbe98955 100644
--- a/Source/SPQueryConsole.m
+++ b/Source/SPQueryConsole.m
@@ -111,11 +111,18 @@ static SPQueryConsole *sharedQueryConsole = nil;
- (void)release { }
/**
- * Set the window's auto save name.
+ * Set the window's auto save name and initialise display
*/
- (void)awakeFromNib
{
[self setWindowFrameAutosaveName:CONSOLE_WINDOW_AUTO_SAVE_NAME];
+ if (![[NSUserDefaults standardUserDefaults] boolForKey:@"ConsoleShowTimestamps"]) {
+ uncollapsedDateColumnWidth = [[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] width];
+ [[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] setMinWidth:0.0];
+ [[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] setWidth: 0.0];
+ }
+ showSelectStatementsAreDisabled = ![[NSUserDefaults standardUserDefaults] boolForKey:@"ConsoleShowSelectsAndShows"];
+ [self _updateFilterState];
}
/**
@@ -201,7 +208,7 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (IBAction)toggleShowTimeStamps:(id)sender
{
- if ([sender intValue]) {
+ if ([sender state]) {
[[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] setMinWidth:50.0];
[[consoleTableView tableColumnWithIdentifier:TABLEVIEW_DATE_COLUMN_IDENTIFIER] setWidth:uncollapsedDateColumnWidth];
} else {
@@ -216,8 +223,9 @@ static SPQueryConsole *sharedQueryConsole = nil;
*/
- (IBAction)toggleShowSelectShowStatements:(id)sender
{
+
// Store the state of the toggle for later quick reference
- showSelectStatementsAreDisabled = ![sender intValue];
+ showSelectStatementsAreDisabled = ![sender state];
[self _updateFilterState];
}
@@ -485,6 +493,8 @@ static SPQueryConsole *sharedQueryConsole = nil;
&& [self _messageMatchesCurrentFilters:[consoleMessage message]])
{
[messagesFilteredSet addObject:[messagesFullSet lastObject]];
+ [saveConsoleButton setEnabled:YES];
+ [clearConsoleButton setEnabled:YES];
}
// Reload the table and scroll to the new message
diff --git a/Source/SPTextViewAdditions.h b/Source/SPTextViewAdditions.h
index 95075165..2fcbeeee 100644
--- a/Source/SPTextViewAdditions.h
+++ b/Source/SPTextViewAdditions.h
@@ -24,6 +24,8 @@
@interface NSTextView (SPTextViewAdditions)
+- (NSRange)getRangeForCurrentWord;
+
- (IBAction)selectCurrentWord:(id)sender;
- (IBAction)selectCurrentLine:(id)sender;
- (IBAction)doSelectionUpperCase:(id)sender;
diff --git a/Source/SPTextViewAdditions.m b/Source/SPTextViewAdditions.m
index 885e51df..ee957c78 100644
--- a/Source/SPTextViewAdditions.m
+++ b/Source/SPTextViewAdditions.m
@@ -31,8 +31,11 @@
*/
- (NSRange)getRangeForCurrentWord
{
-
NSRange curRange = [self selectedRange];
+
+ if (curRange.length)
+ return curRange;
+
unsigned long curLocation = curRange.location;
[self moveWordLeft:self];
@@ -57,7 +60,6 @@
[self setSelectedRange:curRange];
return(wordRange);
-
}
/*
diff --git a/Source/TableDump.m b/Source/TableDump.m
index 958969e9..afb8ebc2 100644
--- a/Source/TableDump.m
+++ b/Source/TableDump.m
@@ -391,11 +391,9 @@
// Load file into string. For SQL imports, try UTF8 file encoding before the current encoding.
if ([fileType isEqualToString:@"SQL"]) {
- NSLog(@"Reading as utf8");
dumpFile = [SPSQLParser stringWithContentsOfFile:filename
encoding:NSUTF8StringEncoding
error:&errorStr];
- NSLog(dumpFile);
if (errorStr) {
importSQLAsUTF8 = NO;
errorStr = nil;
diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj
index 0bc512bc..fb1100ef 100644
--- a/sequel-pro.xcodeproj/project.pbxproj
+++ b/sequel-pro.xcodeproj/project.pbxproj
@@ -108,6 +108,9 @@
584146D60F9AB15B00A34B47 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 584146BA0F9AAD6C00A34B47 /* Sparkle.framework */; };
584146D70F9AB15B00A34B47 /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 584146BB0F9AAD6C00A34B47 /* Growl.framework */; };
584146D80F9AB15B00A34B47 /* MCPKit_bundled.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 584146BC0F9AAD6C00A34B47 /* MCPKit_bundled.framework */; };
+ 58414B5E0FA343F200A34B47 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 58414B5C0FA343F200A34B47 /* RegexKitLite.m */; };
+ 58414B670FA3452000A34B47 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 58414B660FA3452000A34B47 /* libicucore.dylib */; };
+ 58414B6B0FA3459700A34B47 /* button_clear.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 58414B6A0FA3459700A34B47 /* button_clear.tiff */; };
58C56EF50F438E120035701E /* SPDataCellFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C56EF40F438E120035701E /* SPDataCellFormatter.m */; };
58FEF16D0F23D66600518E8E /* SPSQLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF16C0F23D66600518E8E /* SPSQLParser.m */; };
58FEF57E0F3B4E9700518E8E /* SPTableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF57D0F3B4E9700518E8E /* SPTableData.m */; };
@@ -287,6 +290,10 @@
584146BA0F9AAD6C00A34B47 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Sparkle.framework; sourceTree = "<group>"; };
584146BB0F9AAD6C00A34B47 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = Frameworks/Growl.framework; sourceTree = "<group>"; };
584146BC0F9AAD6C00A34B47 /* MCPKit_bundled.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MCPKit_bundled.framework; path = Frameworks/MCPKit_bundled.framework; sourceTree = "<group>"; };
+ 58414B5C0FA343F200A34B47 /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = "<group>"; };
+ 58414B5D0FA343F200A34B47 /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = "<group>"; };
+ 58414B660FA3452000A34B47 /* libicucore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libicucore.dylib; path = usr/lib/libicucore.dylib; sourceTree = SDKROOT; };
+ 58414B6A0FA3459700A34B47 /* button_clear.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = button_clear.tiff; sourceTree = "<group>"; };
58C56EF30F438E120035701E /* SPDataCellFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPDataCellFormatter.h; sourceTree = "<group>"; };
58C56EF40F438E120035701E /* SPDataCellFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataCellFormatter.m; sourceTree = "<group>"; };
58FEF16B0F23D66600518E8E /* SPSQLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLParser.h; sourceTree = "<group>"; };
@@ -307,6 +314,7 @@
584146D00F9AB00600A34B47 /* Growl.framework in Frameworks */,
584146D20F9AB01700A34B47 /* MCPKit_bundled.framework in Frameworks */,
584146D40F9AB02B00A34B47 /* Sparkle.framework in Frameworks */,
+ 58414B670FA3452000A34B47 /* libicucore.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -472,6 +480,7 @@
17E6416E0EF01F3B001BC333 /* Other */ = {
isa = PBXGroup;
children = (
+ 58414B5B0FA343DB00A34B47 /* RegexKitLite */,
17E6416F0EF01F4C001BC333 /* Keychain */,
17E641700EF01F52001BC333 /* MCPKit */,
58FEF15E0F23D60A00518E8E /* Parsing */,
@@ -536,6 +545,7 @@
584144B50F9A40BB00A34B47 /* button_add.tiff */,
584144B60F9A40BB00A34B47 /* button_bar_handle.tiff */,
584144B70F9A40BB00A34B47 /* button_bar_spacer.tiff */,
+ 58414B6A0FA3459700A34B47 /* button_clear.tiff */,
584144B80F9A40BB00A34B47 /* button_duplicate.tiff */,
584144B90F9A40BB00A34B47 /* button_edit_mode_selected.tiff */,
584144BA0F9A40BB00A34B47 /* button_edit_mode.tiff */,
@@ -614,6 +624,7 @@
1761FD9C0EF0486A00331368 /* Scripts */,
2A37F4C3FDCFA73011CA2CEA /* Frameworks */,
19C28FB0FE9D524F11CA2CBB /* Products */,
+ 58414B660FA3452000A34B47 /* libicucore.dylib */,
);
name = "sequel-pro";
sourceTree = "<group>";
@@ -642,6 +653,15 @@
name = "Category Additions";
sourceTree = "<group>";
};
+ 58414B5B0FA343DB00A34B47 /* RegexKitLite */ = {
+ isa = PBXGroup;
+ children = (
+ 58414B5D0FA343F200A34B47 /* RegexKitLite.h */,
+ 58414B5C0FA343F200A34B47 /* RegexKitLite.m */,
+ );
+ name = RegexKitLite;
+ sourceTree = "<group>";
+ };
58FEF15E0F23D60A00518E8E /* Parsing */ = {
isa = PBXGroup;
children = (
@@ -762,6 +782,7 @@
584146830F9AAC6200A34B47 /* DBView.nib in Resources */,
584146840F9AAC6200A34B47 /* MainMenu.nib in Resources */,
584146850F9AAC6200A34B47 /* Preferences.nib in Resources */,
+ 58414B6B0FA3459700A34B47 /* button_clear.tiff in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -823,6 +844,7 @@
584144590F9A34EC00A34B47 /* SPArrayAdditions.m in Sources */,
5841445A0F9A34EC00A34B47 /* SPTextViewAdditions.m in Sources */,
5841445B0F9A34EC00A34B47 /* SPWindowAdditions.m in Sources */,
+ 58414B5E0FA343F200A34B47 /* RegexKitLite.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};