//
// $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