// // $Id$ // // SPTextViewAdditions.m // sequel-pro // // Created by Hans-Jörg Bibiko on April 05, 2009 // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // More info at @implementation NSTextView (SPTextViewAdditions) /* * Returns the range of the current word. * finds: [| := caret] |word wo|rd word| * If | is in between whitespaces nothing will be selected. */ - (NSRange)getRangeForCurrentWord { NSRange curRange = [self selectedRange]; if (curRange.length) return curRange; NSUInteger curLocation = curRange.location; [self moveWordLeft:self]; [self moveWordRightAndModifySelection:self]; NSUInteger newStartRange = [self selectedRange].location; NSUInteger newEndRange = newStartRange + [self selectedRange].length; // if current location does not intersect with found range // then caret is at the begin of a word -> change strategy if(curLocation < newStartRange || curLocation > newEndRange) { [self setSelectedRange:curRange]; [self moveWordRight:self]; [self moveWordLeftAndModifySelection:self]; newStartRange = [self selectedRange].location; newEndRange = newStartRange + [self selectedRange].length; } // how many space in front of the selection NSInteger bias = [self selectedRange].length - [[[[self string] substringWithRange:[self selectedRange]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]; [self setSelectedRange:NSMakeRange([self selectedRange].location+bias, [self selectedRange].length-bias)]; newStartRange += bias; newEndRange -= bias; // is caret inside the selection still? if(curLocation < newStartRange || curLocation > newEndRange || [[[self string] substringWithRange:[self selectedRange]] rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].location != NSNotFound) [self setSelectedRange:curRange]; NSRange wordRange = [self selectedRange]; [self setSelectedRange:curRange]; return(wordRange); } /* * Select current word. * finds: [| := caret] |word wo|rd word| * If | is in between whitespaces nothing will be selected. */ - (IBAction)selectCurrentWord:(id)sender { [self setSelectedRange:[self getRangeForCurrentWord]]; } /* * Select current line. */ - (IBAction)selectCurrentLine:(id)sender { NSRange lineRange = [[self string] lineRangeForRange:[self selectedRange]]; if(lineRange.location != NSNotFound && lineRange.length) [self setSelectedRange:lineRange]; else NSBeep(); } /* * */ - (IBAction)selectEnclosingBrackets:(id)sender { NSUInteger caretPosition = [self selectedRange].location; NSUInteger stringLength = [[self string] length]; unichar co, cc; if(caretPosition == 0 || caretPosition >= stringLength) return; NSInteger pcnt = 0; NSInteger bcnt = 0; NSInteger scnt = 0; NSInteger i; // look for the first non-balanced closing bracket for(i=caretPosition; i=0; i--) { if([[self string] characterAtIndex:i] == co) { if(!bracketCounter) { start = i; break; } bracketCounter--; } if([[self string] characterAtIndex:i] == cc) { bracketCounter++; } } if(start < 0 ) return; bracketCounter = 0; for(i=caretPosition; i [[self string] length]) { // caret is at the end of a text field // transpose last two characters [self moveLeftAndModifySelection:self]; [self moveLeftAndModifySelection:self]; workingRange = [self selectedRange]; } else if(curRange.location == 0) { // caret is at the beginning of the text field // do nothing workingRange.length = 0; } else { // caret is in between two characters // reverse adjacent characters NSRange twoCharRange = NSMakeRange(curRange.location-1, 2); [self setSelectedRange:twoCharRange]; workingRange = twoCharRange; } } @catch(id ae) { workingRange.length = 0; } // reverse string : TODO not yet combining diacritics safe! NSUInteger len = workingRange.length; if (len > 1) { NSMutableString *reversedStr = [NSMutableString stringWithCapacity:len]; while (len > 0) [reversedStr appendString: [NSString stringWithFormat:@"%C", [[self string] characterAtIndex:--len+workingRange.location]]]; [self insertText:reversedStr]; [self setSelectedRange:curRange]; } } /** * Inserts the preference's NULL value set by the user */ - (IBAction)insertNULLvalue:(id)sender { id prefs = [NSUserDefaults standardUserDefaults]; if([self respondsToSelector:@selector(insertText:)]) if([prefs objectForKey:SPNullValue] && [[prefs objectForKey:SPNullValue] length]) [self insertText:[prefs objectForKey:SPNullValue]]; else [self insertText:@"NULL"]; } /** * Move selected lines or current line one line up */ - (IBAction)moveSelectionLineUp:(id)sender; { NSRange currentSelection = [self selectedRange]; NSRange lineRange = [[self string] lineRangeForRange:currentSelection]; if(lineRange.location > 0) { NSRange beforeLineRange = [[self string] lineRangeForRange:NSMakeRange(lineRange.location-1, 0)]; NSRange insertPoint = NSMakeRange(beforeLineRange.location, 0); NSString *currentLine = [[self string] substringWithRange:lineRange]; BOOL lastLine = NO; if([currentLine characterAtIndex:[currentLine length]-1] != '\n') { currentLine = [NSString stringWithFormat:@"%@\n", currentLine]; lastLine = YES; } [self setSelectedRange:lineRange]; [self insertText:@""]; [self setSelectedRange:insertPoint]; [self insertText:currentLine]; if(lastLine) { [self setSelectedRange:NSMakeRange([[self string] length]-1,1)]; [self insertText:@""]; } if(currentSelection.length) insertPoint.length+=[currentLine length]; [self setSelectedRange:insertPoint]; } } /** * Move selected lines or current line one line down */ - (IBAction)moveSelectionLineDown:(id)sender { NSRange currentSelection = [self selectedRange]; NSRange lineRange = [[self string] lineRangeForRange:currentSelection]; if(NSMaxRange(lineRange) < [[self string] length]) { NSRange afterLineRange = [[self string] lineRangeForRange:NSMakeRange(NSMaxRange(lineRange)+1, 0)]; NSRange insertPoint = NSMakeRange(lineRange.location + afterLineRange.length, 0); NSString *currentLine = [[self string] substringWithRange:lineRange]; [self setSelectedRange:lineRange]; [self insertText:@""]; [self setSelectedRange:insertPoint]; if([[self string] characterAtIndex:insertPoint.location-1] != '\n') { [self insertText:@"\n"]; insertPoint.location++; currentLine = [currentLine substringToIndex:[currentLine length]-1]; } [self insertText:currentLine]; if(currentSelection.length) insertPoint.length+=[currentLine length]; [self setSelectedRange:insertPoint]; } } /** * Increase the textView's font size by 1 */ - (void)makeTextSizeLarger { NSFont *aFont = [self font]; BOOL editableStatus = [self isEditable]; [self setEditable:YES]; [self setFont:[[NSFontManager sharedFontManager] convertFont:aFont toSize:[aFont pointSize]+1]]; [self setEditable:editableStatus]; } /* * Decrease the textView's font size by 1 but not smaller than 4pt */ - (void)makeTextSizeSmaller { NSFont *aFont = [self font]; NSInteger newSize = ([aFont pointSize]-1 < 4) ? [aFont pointSize] : [aFont pointSize]-1; BOOL editableStatus = [self isEditable]; [self setEditable:YES]; [self setFont:[[NSFontManager sharedFontManager] convertFont:aFont toSize:newSize]]; [self setEditable:editableStatus]; } #pragma mark - #pragma mark multi-touch trackpad support /** * Trackpad two-finger zooming gesture for in/decreasing the font size */ - (void) magnifyWithEvent:(NSEvent *)anEvent { //Avoid font resizing for NSTextViews in SPCopyTable or NSTableView if([[[[self delegate] class] description] isEqualToString:@"SPCopyTable"] || [[[[self delegate] class] description] isEqualToString:@"NSTableView"]) return; if([anEvent deltaZ]>5.0) [self makeTextSizeLarger]; else if([anEvent deltaZ]<-5.0) [self makeTextSizeSmaller]; } @end