diff options
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 Binary files differindex 5cb43a2e..a9cb57b7 100644 --- a/Interfaces/English.lproj/Console.nib/keyedobjects.nib +++ b/Interfaces/English.lproj/Console.nib/keyedobjects.nib 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 Binary files differindex c44f2194..6df4919c 100644 --- a/Interfaces/English.lproj/DBView.nib/keyedobjects.nib +++ b/Interfaces/English.lproj/DBView.nib/keyedobjects.nib 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 Binary files differnew file mode 100644 index 00000000..9589c170 --- /dev/null +++ b/Resources/Images/button_clear.tiff diff --git a/Resources/Images/toolbar-preferences-favorites.png b/Resources/Images/toolbar-preferences-favorites.png Binary files differindex 8067e5c0..0ba89b88 100644 --- a/Resources/Images/toolbar-preferences-favorites.png +++ b/Resources/Images/toolbar-preferences-favorites.png diff --git a/Resources/Images/toolbar-preferences-network.png b/Resources/Images/toolbar-preferences-network.png Binary files differindex e07c3c1f..694a51d5 100644 --- a/Resources/Images/toolbar-preferences-network.png +++ b/Resources/Images/toolbar-preferences-network.png 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; }; |