diff options
author | rowanbeentje <rowan@beent.je> | 2009-04-02 01:23:26 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-04-02 01:23:26 +0000 |
commit | 00910549fefaaa0954aa080a3ec1be5a4746062b (patch) | |
tree | 896bc68f6426e58be635ef0238aad7b045057651 /Source | |
parent | 8fd7a25952b3d5317a22e14c83af06b56c32710a (diff) | |
download | sequelpro-00910549fefaaa0954aa080a3ec1be5a4746062b.tar.gz sequelpro-00910549fefaaa0954aa080a3ec1be5a4746062b.tar.bz2 sequelpro-00910549fefaaa0954aa080a3ec1be5a4746062b.zip |
- Add a new "gear" action menu underneath the custom query view, including a number of items:
- Add menu commands for "Run All" and "Run Selected", with additional keyboard shortcuts - cmd-R for Run all, addressing #137
- Add menu commands for indenting text, outdenting text, and to show autocompletion is available
- Add menu commands to toggle autopairing and autoindenting
- Also hidden menu commands for history navigation and clearing, not hooked in yet (see #207)
- Add a new method to our string additions: lineRangesForRange
- Add "shift right" (indent) and "shift left" (outdent) support to CMTextView, including for multiple lines
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CMTextView.h | 18 | ||||
-rw-r--r-- | Source/CMTextView.m | 134 | ||||
-rw-r--r-- | Source/CustomQuery.h | 7 | ||||
-rw-r--r-- | Source/CustomQuery.m | 53 | ||||
-rw-r--r-- | Source/SPStringAdditions.h | 1 | ||||
-rw-r--r-- | Source/SPStringAdditions.m | 33 |
6 files changed, 233 insertions, 13 deletions
diff --git a/Source/CMTextView.h b/Source/CMTextView.h index 761e1189..4dadb33d 100644 --- a/Source/CMTextView.h +++ b/Source/CMTextView.h @@ -32,13 +32,15 @@ - (BOOL) isNextCharMarkedBy:(id)attribute; - (BOOL) areAdjacentCharsLinked; - (BOOL) wrapSelectionWithPrefix:(NSString *)prefix suffix:(NSString *)suffix; -- (NSArray *)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index; -- (NSArray *)keywords; -- (void)setAutoindent:(BOOL)enableAutoindent; -- (BOOL)autoindent; -- (void)setAutoindentIgnoresEnter:(BOOL)enableAutoindentIgnoresEnter; -- (BOOL)autoindentIgnoresEnter; -- (void)setAutopair:(BOOL)enableAutopair; -- (BOOL)autopair; +- (BOOL) shiftSelectionRight; +- (BOOL) shiftSelectionLeft; +- (NSArray *) completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index; +- (NSArray *) keywords; +- (void) setAutoindent:(BOOL)enableAutoindent; +- (BOOL) autoindent; +- (void) setAutoindentIgnoresEnter:(BOOL)enableAutoindentIgnoresEnter; +- (BOOL) autoindentIgnoresEnter; +- (void) setAutopair:(BOOL)enableAutopair; +- (BOOL) autopair; @end diff --git a/Source/CMTextView.m b/Source/CMTextView.m index 2532cf1e..cfd42884 100644 --- a/Source/CMTextView.m +++ b/Source/CMTextView.m @@ -252,15 +252,18 @@ YY_BUFFER_STATE yy_scan_string (const char *); { // Handle newlines, adding any indentation found on the current line to the new line - ignoring the enter key if appropriate - if (aSelector == @selector(insertNewline:) && (!autoindentIgnoresEnter || [[NSApp currentEvent] keyCode] != 0x4C)) { + if (aSelector == @selector(insertNewline:) + && autoindentEnabled + && (!autoindentIgnoresEnter || [[NSApp currentEvent] keyCode] != 0x4C)) + { NSString *textViewString = [[self textStorage] string]; NSString *currentLine, *indentString = nil; NSScanner *whitespaceScanner; - NSUInteger lineStart, lineEnd; + NSRange currentLineRange; // Extract the current line based on the text caret or selection start position - [textViewString getLineStart:&lineStart end:NULL contentsEnd:&lineEnd forRange:NSMakeRange([self selectedRange].location, 0)]; - currentLine = [[NSString alloc] initWithString:[textViewString substringWithRange:NSMakeRange(lineStart, lineEnd - lineStart)]]; + currentLineRange = [textViewString lineRangeForRange:NSMakeRange([self selectedRange].location, 0)]; + currentLine = [[NSString alloc] initWithString:[textViewString substringWithRange:currentLineRange]]; // Scan all indentation characters on the line into a string whitespaceScanner = [[NSScanner alloc] initWithString:currentLine]; @@ -283,6 +286,129 @@ YY_BUFFER_STATE yy_scan_string (const char *); /* + * Shifts the selection, if any, rightwards by indenting any selected lines with one tab. + * If the caret is within a line, the selection is not changed after the index; if the selection + * has length, all lines crossed by the length are indented and fully selected. + * Returns whether or not an indentation was performed. + */ +- (BOOL) shiftSelectionRight +{ + NSString *textViewString = [[self textStorage] string]; + NSRange currentLineRange; + NSArray *lineRanges; + NSString *tabString = @"\t"; + int i, indentedLinesLength = 0; + + if ([self selectedRange].location == NSNotFound) return NO; + + // Indent the currently selected line if the caret is within a single line + if ([self selectedRange].length == 0) { + NSRange currentLineRange; + + // Extract the current line range based on the text caret + currentLineRange = [textViewString lineRangeForRange:[self selectedRange]]; + + // Register the indent for undo + [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 0) replacementString:tabString]; + + // Insert the new tab + [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 0) withString:tabString]; + + return YES; + } + + // Otherwise, the selection has a length - get an array of current line ranges for the specified selection + lineRanges = [textViewString lineRangesForRange:[self selectedRange]]; + + // Loop through the ranges, storing a count of the overall length. + for (i = 0; i < [lineRanges count]; i++) { + currentLineRange = NSRangeFromString([lineRanges objectAtIndex:i]); + indentedLinesLength += currentLineRange.length + 1; + + // Register the indent for undo + [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location+i, 0) replacementString:tabString]; + + // Insert the new tab + [self replaceCharactersInRange:NSMakeRange(currentLineRange.location+i, 0) withString:tabString]; + } + + // Select the entirety of the new range + [self setSelectedRange:NSMakeRange(NSRangeFromString([lineRanges objectAtIndex:0]).location, indentedLinesLength)]; + + return YES; +} + + +/* + * Shifts the selection, if any, leftwards by un-indenting any selected lines by one tab if possible. + * If the caret is within a line, the selection is not changed after the undent; if the selection has + * length, all lines crossed by the length are un-indented and fully selected. + * Returns whether or not an indentation was performed. + */ +- (BOOL) shiftSelectionLeft +{ + NSString *textViewString = [[self textStorage] string]; + NSRange currentLineRange; + NSArray *lineRanges; + int i, unindentedLines = 0, unindentedLinesLength = 0; + + if ([self selectedRange].location == NSNotFound) return NO; + + // Undent the currently selected line if the caret is within a single line + if ([self selectedRange].length == 0) { + NSRange currentLineRange; + + // Extract the current line range based on the text caret + currentLineRange = [textViewString lineRangeForRange:[self selectedRange]]; + + // Ensure that the line has length and that the first character is a tab + if (currentLineRange.length < 1 + || [textViewString characterAtIndex:currentLineRange.location] != '\t') + return NO; + + // Register the undent for undo + [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location, 1) replacementString:@""]; + + // Remove the tab + [self replaceCharactersInRange:NSMakeRange(currentLineRange.location, 1) withString:@""]; + + return YES; + } + + // Otherwise, the selection has a length - get an array of current line ranges for the specified selection + lineRanges = [textViewString lineRangesForRange:[self selectedRange]]; + + // Loop through the ranges, storing a count of the total lines changed and the new length. + for (i = 0; i < [lineRanges count]; i++) { + currentLineRange = NSRangeFromString([lineRanges objectAtIndex:i]); + unindentedLinesLength += currentLineRange.length; + + // Ensure that the line has length and that the first character is a tab + if (currentLineRange.length < 1 + || [textViewString characterAtIndex:currentLineRange.location-unindentedLines] != '\t') + continue; + + // Register the undent for undo + [self shouldChangeTextInRange:NSMakeRange(currentLineRange.location-unindentedLines, 1) replacementString:@""]; + + // Remove the tab + [self replaceCharactersInRange:NSMakeRange(currentLineRange.location-unindentedLines, 1) withString:@""]; + + // As a line has been unindented, modify counts and lengths + unindentedLines++; + unindentedLinesLength--; + } + + // If a change was made, select the entirety of the new range and return success + if (unindentedLines) { + [self setSelectedRange:NSMakeRange(NSRangeFromString([lineRanges objectAtIndex:0]).location, unindentedLinesLength)]; + return YES; + } + + return NO; +} + +/* * Handle autocompletion, returning a list of suggested completions for the supplied character range. */ - (NSArray *)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index diff --git a/Source/CustomQuery.h b/Source/CustomQuery.h index 1abc5e57..908dd594 100644 --- a/Source/CustomQuery.h +++ b/Source/CustomQuery.h @@ -46,6 +46,12 @@ IBOutlet id copyQueryFavoriteButton; IBOutlet id runSelectionButton; IBOutlet id runAllButton; + IBOutlet NSMenuItem *runSelectionMenuItem; + IBOutlet NSMenuItem *shiftLeftMenuItem; + IBOutlet NSMenuItem *shiftRightMenuItem; + IBOutlet NSMenuItem *completionListMenuItem; + IBOutlet NSMenuItem *autoindentMenuItem; + IBOutlet NSMenuItem *autopairMenuItem; NSArray *queryResult; NSUserDefaults *prefs; @@ -60,6 +66,7 @@ - (IBAction)chooseQueryFavorite:(id)sender; - (IBAction)chooseQueryHistory:(id)sender; - (IBAction)closeSheet:(id)sender; +- (IBAction)gearMenuItemSelected:(id)sender; // queryFavoritesSheet methods - (IBAction)addQueryFavorite:(id)sender; diff --git a/Source/CustomQuery.m b/Source/CustomQuery.m index 115c4d36..baa81170 100644 --- a/Source/CustomQuery.m +++ b/Source/CustomQuery.m @@ -155,6 +155,49 @@ closes the sheet } +/* + * Perform simple actions (which don't require their own method), triggered by selecting the appropriate menu item + * in the "gear" action menu displayed beneath the cusotm query view. + */ +- (IBAction)gearMenuItemSelected:(id)sender +{ + + // "Shift Right" menu item - indent the selection with an additional tab. + if (sender == shiftRightMenuItem) { + [textView shiftSelectionRight]; + } + + // "Shift Left" menu item - un-indent the selection by one tab if possible. + if (sender == shiftLeftMenuItem) { + [textView shiftSelectionLeft]; + } + + // "Completion List" menu item - used to autocomplete. Uses a different shortcut to avoid the menu button flickering + // on normal autocomplete usage. + if (sender == completionListMenuItem) { + [textView complete:self]; + } + + // "Indent new lines" toggle + if (sender == autoindentMenuItem) { + BOOL enableAutoindent = ([autoindentMenuItem state] == NSOffState); + [prefs setBool:enableAutoindent forKey:@"CustomQueryAutoindent"]; + [prefs synchronize]; + [autoindentMenuItem setState:enableAutoindent?NSOnState:NSOffState]; + [textView setAutoindent:enableAutoindent]; + } + + // "Auto-pair characters" toggle + if (sender == autopairMenuItem) { + BOOL enableAutopair = ([autopairMenuItem state] == NSOffState); + [prefs setBool:enableAutopair forKey:@"CustomQueryAutopair"]; + [prefs synchronize]; + [autopairMenuItem setState:enableAutopair?NSOnState:NSOffState]; + [textView setAutopair:enableAutopair]; + } +} + + #pragma mark - #pragma mark queryFavoritesSheet methods @@ -549,8 +592,10 @@ sets the connection (received from TableDocument) and makes things that have to [textView setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; } [textView setContinuousSpellCheckingEnabled:NO]; + [autoindentMenuItem setState:([prefs boolForKey:@"CustomQueryAutoindent"]?NSOnState:NSOffState)]; [textView setAutoindent:[prefs boolForKey:@"CustomQueryAutoindent"]]; [textView setAutoindentIgnoresEnter:YES]; + [autopairMenuItem setState:([prefs boolForKey:@"CustomQueryAutopair"]?NSOnState:NSOffState)]; [textView setAutopair:[prefs boolForKey:@"CustomQueryAutopair"]]; [queryFavoritesView registerForDraggedTypes:[NSArray arrayWithObjects:@"SequelProPasteboard", nil]]; while ( (column = [enumerator nextObject]) ) @@ -891,9 +936,10 @@ traps enter key and // Ensure that the notification is from the custom query text view if ( [aNotification object] != textView ) return; - // If no text is selected, disable the button. + // If no text is selected, disable the button and action menu. if ( [textView selectedRange].location == NSNotFound ) { [runSelectionButton setEnabled:NO]; + [runSelectionMenuItem setEnabled:NO]; return; } @@ -922,12 +968,15 @@ traps enter key and ) { [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]) { [runSelectionButton setEnabled:YES]; + [runSelectionMenuItem setEnabled:YES]; } else { [runSelectionButton setEnabled:NO]; + [runSelectionMenuItem setEnabled:NO]; } } @@ -935,6 +984,8 @@ traps enter key and } else { [runSelectionButton setTitle:NSLocalizedString(@"Run Selection", @"Title of button to run selected text in custom query view")]; [runSelectionButton setEnabled:YES]; + [runSelectionMenuItem setTitle:NSLocalizedString(@"Run Selected Text", @"Title of action menu item to run selected text in custom query view")]; + [runSelectionMenuItem setEnabled:YES]; } } diff --git a/Source/SPStringAdditions.h b/Source/SPStringAdditions.h index be999ba4..1d706666 100644 --- a/Source/SPStringAdditions.h +++ b/Source/SPStringAdditions.h @@ -28,6 +28,7 @@ + (NSString *)stringForTimeInterval:(float)timeInterval; - (NSString *)backtickQuotedString; +- (NSArray *)lineRangesForRange:(NSRange)aRange; #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 - (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)set; diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m index 74c98b4f..2c719f6c 100644 --- a/Source/SPStringAdditions.m +++ b/Source/SPStringAdditions.m @@ -136,6 +136,39 @@ return quotedString; } +// ------------------------------------------------------------------------------- +// lineRangesForRange +// +// Returns an array of serialised NSRanges, each representing a line within the string +// which is at least partially covered by the NSRange supplied. +// Each line includes the line termination character(s) for the line. As per +// lineRangeForRange, lines are split by CR, LF, CRLF, U+2028 (Unicode line separator), +// or U+2029 (Unicode paragraph separator). +// ------------------------------------------------------------------------------- +- (NSArray *)lineRangesForRange:(NSRange)aRange +{ + NSMutableArray *lineRangesArray = [NSMutableArray array]; + NSRange currentLineRange; + + // Check that the range supplied is valid - if not return an empty array. + if (aRange.location == NSNotFound || aRange.location + aRange.length > [self length]) + return lineRangesArray; + + // Get the range of the first string covered by the specified range, and add it to the array + currentLineRange = [self lineRangeForRange:NSMakeRange(aRange.location, 0)]; + [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; + + // Loop through until the line end matches or surpasses the end of the specified range + while (currentLineRange.location + currentLineRange.length < aRange.location + aRange.length) { + currentLineRange = [self lineRangeForRange:NSMakeRange(currentLineRange.location + currentLineRange.length, 0)]; + [lineRangesArray addObject:NSStringFromRange(currentLineRange)]; + } + + // Return the constructed array of ranges + return lineRangesArray; +} + + #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 /* * componentsSeparatedByCharactersInSet: |