1/* 2 PCEditorView.m 3 4 Implementation of the PCEditorView class for the 5 ProjectManager application. 6 7 Copyright (C) 2005-2014 Free Software Foundation 8 Saso Kiselkov 9 Serg Stoyan 10 Riccardo Mottola 11 12 This program is free software; you can redistribute it and/or modify 13 it under the terms of the GNU General Public License as published by 14 the Free Software Foundation; either version 2 of the License, or 15 (at your option) any later version. 16 17 This program is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU General Public License for more details. 21 22 You should have received a copy of the GNU General Public License 23 along with this program; if not, write to the Free Software 24 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 25*/ 26 27#import "PCEditorView.h" 28 29#import <Foundation/NSString.h> 30#import <Foundation/NSUserDefaults.h> 31#import <Foundation/NSArchiver.h> 32 33#import <AppKit/PSOperators.h> 34#import <AppKit/NSColor.h> 35#import <AppKit/NSEvent.h> 36#import <AppKit/NSWindow.h> 37#import <AppKit/NSCursor.h> 38#import <AppKit/NSLayoutManager.h> 39#import <AppKit/NSFont.h> 40 41#import <ctype.h> 42 43#import "PCEditor.h" 44#import "SyntaxHighlighter.h" 45#import "LineJumper.h" 46 47static inline float my_abs(float aValue) 48{ 49 if (aValue >= 0) 50 { 51 return aValue; 52 } 53 else 54 { 55 return -aValue; 56 } 57} 58 59/** 60 * Computes the indenting offset of the last line before the passed 61 * start offset containg text in the passed string, e.g. 62 * 63 * ComputeIndentingOffset(@" Hello World", 12) = 2 64 * ComputeIndentingOffset(@" Try this one out\n" 65 * @" ", 27) = 4 66 * 67 * @argument string The string in which to do the computation. 68 * @argument start The start offset from which to start looking backwards. 69 * @return The ammount of spaces the last line containing text is offset 70 * from it's start. 71 */ 72static int ComputeIndentingOffset(NSString * string, unsigned int start) 73{ 74 SEL sel = @selector(characterAtIndex:); 75 unichar (* charAtIndex)(NSString *, SEL, unsigned int) = 76 (unichar (*)(NSString *, SEL, unsigned int)) 77 [string methodForSelector: sel]; 78 unichar c; 79 int firstCharOffset = -1; 80 int offset; 81 int startOffsetFromLineStart = -1; 82 83 for (offset = start - 1; offset >= 0; offset--) 84 { 85 c = charAtIndex(string, sel, offset); 86 87 if (c == '\n') 88 { 89 if (startOffsetFromLineStart < 0) 90 { 91 startOffsetFromLineStart = start - offset - 1; 92 } 93 94 if (firstCharOffset >= 0) 95 { 96 firstCharOffset = firstCharOffset - offset - 1; 97 break; 98 } 99 } 100 else if (!isspace(c)) 101 { 102 firstCharOffset = offset; 103 } 104 } 105 106 return firstCharOffset >= 0 ? firstCharOffset : 0; 107 108/* if (firstCharOffset >= 0) 109 { 110 // if the indenting of the current line is lower than the indenting 111 // of the previous actual line, we return the lower indenting 112 if (startOffsetFromLineStart >= 0 && 113 startOffsetFromLineStart < firstCharOffset) 114 { 115 return startOffsetFromLineStart; 116 } 117 // otherwise we return the actual indenting, so that any excess 118 // space is trimmed and the lines are aligned according the last 119 // indenting level 120 else 121 { 122 return firstCharOffset; 123 } 124 } 125 else 126 { 127 return 0; 128 }*/ 129} 130 131@interface PCEditorView (Private) 132 133- (void)insertSpaceFillAlignedAtTabsOfSize:(unsigned int)tabSize; 134- (void)performIndentation; 135 136@end 137 138@implementation PCEditorView (Private) 139 140/** 141 * Makes the receiver insert as many spaces at the current insertion 142 * location as are required to reach the nearest tab-character boundary. 143 * 144 * @argument tabSize Specifies how many spaces represent one tab. 145 */ 146- (void)insertSpaceFillAlignedAtTabsOfSize:(unsigned int)tabSize 147{ 148 char buf[tabSize]; 149 NSString * string = [self string]; 150 unsigned int lineLength; 151 SEL sel = @selector(characterAtIndex:); 152 unichar (* charAtIndex)(NSString*, SEL, unsigned int) = 153 (unichar (*)(NSString*, SEL, unsigned int)) 154 [string methodForSelector: sel]; 155 int i; 156 int skip; 157 158 // computes the length of the current line 159 for (i = [self selectedRange].location - 1, lineLength = 0; 160 i >= 0; 161 i--, lineLength++) 162 { 163 if (charAtIndex(string, sel, i) == '\n') 164 { 165 break; 166 } 167 } 168 169 skip = tabSize - (lineLength % tabSize); 170 if (skip == 0) 171 { 172 skip = tabSize; 173 } 174 175 memset(buf, ' ', skip); 176 [super insertText: [NSString stringWithCString: buf length: skip]]; 177} 178 179// Go backward to first '\n' char or start of file 180- (int)lineStartIndexForIndex:(int)index forString:(NSString *)string 181{ 182 int line_start; 183 184 // Get line start index moving from index backwards 185 for (line_start = index;line_start > 0;line_start--) 186 { 187 if ([string characterAtIndex:line_start] == '\n' && 188 line_start != index) 189 { 190 line_start++; 191 break; 192 } 193 } 194 195 NSLog(@"index: %i start: %i", index, line_start); 196 197 return line_start > index ? index : line_start; 198} 199 200- (int)lineEndIndexForIndex:(int)index forString:(NSString *)string 201{ 202 int line_end; 203 int string_length = [string length]; 204 205 // Get line start index moving from index backwards 206 for (line_end = index;line_end < string_length;line_end++) 207 { 208 if ([string characterAtIndex:line_end] == '\n') 209 { 210 break; 211 } 212 } 213 214 NSLog(@"index: %i end: %i", index, line_end); 215 216 return line_end < string_length ? line_end : string_length; 217} 218 219- (int)previousLineStartIndexForIndex:(int)index forString:(NSString *)string 220{ 221 int cur_line_start; 222 int prev_line_start; 223 224 cur_line_start = [self lineStartIndexForIndex:index forString:string]; 225 prev_line_start = [self lineStartIndexForIndex:cur_line_start-1 226 forString:string]; 227 228 NSLog(@"index: %i prev_start: %i", index, prev_line_start); 229 230 return prev_line_start; 231} 232 233- (int)nextLineStartIndexForIndex:(int)index forString:(NSString *)string 234{ 235 int cur_line_end; 236 int next_line_start; 237 int string_length = [string length]; 238 239 cur_line_end = [self lineEndIndexForIndex:index forString:string]; 240 next_line_start = cur_line_end + 1; 241 242 if (next_line_start < string_length) 243 { 244 return next_line_start; 245 } 246 else 247 { 248 return string_length; 249 } 250} 251 252- (unichar)firstCharOfLineForIndex:(int)index forString:(NSString *)string 253{ 254 int line_start = [self lineStartIndexForIndex:index forString:string]; 255 int i; 256 unichar c; 257 258 // Get leading whitespaces range 259 for (i = line_start; i >= 0; i++) 260 { 261 c = [string characterAtIndex:i]; 262 if (!isspace(c)) 263 { 264 break; 265 } 266 } 267 268 fprintf(stderr, "First char: %c\n", c); 269 270 return c; 271} 272 273- (unichar)firstCharOfPrevLineForIndex:(int)index forString:(NSString *)string 274{ 275 int line_start = [self previousLineStartIndexForIndex:index 276 forString:string]; 277 278 return [self firstCharOfLineForIndex:line_start forString:string]; 279} 280 281- (unichar)firstCharOfNextLineForIndex:(int)index 282{ 283 return 0; 284} 285 286- (void)performIndentation 287{ 288 NSString *string = [self string]; 289 int location; 290 int line_start; 291 int offset; 292 unichar c, plfc, clfc; 293 NSRange wsRange; 294 NSMutableString *indentString; 295 NSCharacterSet *wsCharSet = [NSCharacterSet whitespaceCharacterSet]; 296 int i; 297// int point; 298 299 location = [self selectedRange].location; 300 301// point = [self nextLineStartIndexForIndex:location forString:string]; 302// [self setSelectedRange:NSMakeRange(point, 0)]; 303 304 clfc = [self firstCharOfLineForIndex:location forString:string]; 305 plfc = [self firstCharOfPrevLineForIndex:location forString:string]; 306 307 // Get leading whitespaces range 308 line_start = [self lineStartIndexForIndex:location forString:string]; 309 for (offset = line_start; offset >= 0; offset++) 310 { 311 c = [string characterAtIndex:offset]; 312 if (![wsCharSet characterIsMember:c]) 313 { 314 wsRange = NSMakeRange(line_start, offset-line_start); 315 break; 316 } 317 } 318 319 // Get indent 320 line_start = [self previousLineStartIndexForIndex:location forString:string]; 321 for (offset = line_start; offset >= 0; offset++) 322 { 323 c = [string characterAtIndex:offset]; 324 if (![wsCharSet characterIsMember:c]) 325 { 326 offset = offset - line_start; 327 NSLog(@"offset: %i", offset); 328 break; 329 } 330 } 331 332 NSLog (@"clfc: %c plfc: %c", clfc, plfc); 333 if (plfc == '{' || clfc == '{') 334 { 335 offset += 2; 336 } 337 else if (clfc == '}' && plfc != '{') 338 { 339 offset -= 2; 340 } 341 342 // Get offset from BOL of previous line 343// offset = ComputeIndentingOffset([self string], line_start-1); 344 NSLog(@"Indent offset: %i", offset); 345 346 // Replace current line whitespaces with new ones 347 indentString = [[NSMutableString alloc] initWithString:@""]; 348 for (i = offset; i > 0; i--) 349 { 350 [indentString appendString:@" "]; 351 } 352 353 if ([self shouldChangeTextInRange:wsRange 354 replacementString:indentString]) 355 [[self textStorage] replaceCharactersInRange:wsRange 356 withString:indentString]; 357 358/* if (location > line_start + offset) 359 { 360 point = location - offset; 361 } 362 else 363 { 364 point = location; 365 } 366 [self setSelectedRange:NSMakeRange(point, 0)];*/ 367 368 [indentString release]; 369} 370 371@end 372 373@implementation PCEditorView 374 375+ (NSFont *)defaultEditorFont 376{ 377 NSUserDefaults *df = [NSUserDefaults standardUserDefaults]; 378 NSString *fontName; 379 float fontSize; 380 NSFont *font = nil; 381 382 fontName = [df objectForKey:@"EditorFont"]; 383 fontSize = [df floatForKey:@"EditorFontSize"]; 384 385 if (fontName != nil) 386 { 387 font = [NSFont fontWithName:fontName size:fontSize]; 388 } 389 if (font == nil) 390 { 391 font = [NSFont userFixedPitchFontOfSize:fontSize]; 392 } 393 394 return font; 395} 396 397+ (NSFont *)defaultEditorBoldFont 398{ 399 NSFont *font = [self defaultEditorFont]; 400 401 return [[NSFontManager sharedFontManager] convertFont:font 402 toHaveTrait:NSBoldFontMask]; 403} 404 405+ (NSFont *)defaultEditorItalicFont 406{ 407 NSFont *font = [self defaultEditorFont]; 408 409 return [[NSFontManager sharedFontManager] convertFont:font 410 toHaveTrait:NSItalicFontMask]; 411} 412 413+ (NSFont *)defaultEditorBoldItalicFont 414{ 415 NSFont *font = [self defaultEditorFont]; 416 417 return [[NSFontManager sharedFontManager] convertFont:font 418 toHaveTrait:NSBoldFontMask | 419 NSItalicFontMask]; 420} 421 422// --- 423- (BOOL)becomeFirstResponder 424{ 425 return [editor becomeFirstResponder:self]; 426} 427 428- (BOOL)resignFirstResponder 429{ 430 return [editor resignFirstResponder:self]; 431} 432 433- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent 434{ 435 return YES; 436} 437// --- 438 439- (void)dealloc 440{ 441 TEST_RELEASE(highlighter); 442 443 [super dealloc]; 444} 445 446- (void)setEditor:(PCEditor *)anEditor 447{ 448 editor = anEditor; 449} 450 451- (PCEditor *)editor 452{ 453 return editor; 454} 455 456- (void)awakeFromNib 457{ 458/* NSData * data; 459 NSUserDefaults * df = [NSUserDefaults standardUserDefaults]; 460 461 drawCrosshairs = [df boolForKey: @"DrawCrosshairs"]; 462 if (drawCrosshairs) 463 { 464 if ((data = [df dataForKey: @"CrosshairColor"]) == nil || 465 (crosshairColor = [NSUnarchiver unarchiveObjectWithData: data]) == nil) 466 { 467 crosshairColor = [NSColor lightGrayColor]; 468 } 469 [crosshairColor retain]; 470 } 471 472 guides = [NSMutableArray new];*/ 473} 474 475- (void)drawRect:(NSRect)r 476{ 477 NSRange drawnRange; 478 479 if (highlighter) 480 { 481 drawnRange = [[self layoutManager] 482 glyphRangeForBoundingRect:r inTextContainer:[self textContainer]]; 483 [highlighter highlightRange:drawnRange]; 484 } 485 486 [super drawRect:r]; 487} 488 489- (void)createSyntaxHighlighterForFileType:(NSString *)fileType 490{ 491 ASSIGN(highlighter, 492 [[[SyntaxHighlighter alloc] initWithFileType:fileType 493 textStorage:[self textStorage]] 494 autorelease]); 495} 496 497- (void)insertText:text 498{ 499 /* NOTE: On Windows we ensure to get a string in UTF-8 encoding. The problem 500 * is the highlighter that don't use a consistent codification causing a 501 * problem on Windows platform. Anyway, the plugin for Gemas editor works 502 * better and don't show this problem. 503 */ 504 if ([text isKindOfClass:[NSString class]]) 505 { 506 NSString * string = text; 507 508 if ([text characterAtIndex:0] == 27) 509 { 510 NSLog(@"ESC key pressed. Ignoring it"); 511 return; 512 } 513 514 if ([string isEqualToString:@"\n"]) 515 { 516/* if ([[NSUserDefaults standardUserDefaults] 517 boolForKey:@"ReturnDoesAutoindent"]) 518 {*/ 519 int location = [self selectedRange].location; 520 int offset = ComputeIndentingOffset([self string], location); 521 char *buf; 522 523 buf = (char *) malloc((offset + 2) * sizeof(unichar)); 524 buf[0] = '\n'; 525 memset(&buf[1], ' ', offset); 526 buf[offset+1] = '\0'; 527 528#ifdef WIN32 529 [super insertText:[NSString stringWithCString: buf 530 encoding: NSUTF8StringEncoding]]; 531#else 532 [super insertText:[NSString stringWithCString:buf]]; 533#endif 534 free(buf); 535/* } 536 else 537 { 538 [super insertText:text]; 539 }*/ 540 } 541 else if ([string isEqualToString:@"\t"]) 542 { 543 [self performIndentation]; 544/* switch ([[NSUserDefaults standardUserDefaults] 545 integerForKey:@"TabConversion"]) 546 { 547 case 0: // no conversion 548 [super insertText:text]; 549 break; 550 case 1: // 2 spaces 551 [super insertText:@" "]; 552 break; 553 case 2: // 4 spaces 554 [super insertText:@" "]; 555 break; 556 case 3: // 8 spaces 557 [super insertText:@" "]; 558 break; 559 case 4: // aligned to tab boundaries of 2 spaces long tabs 560 [self insertSpaceFillAlignedAtTabsOfSize:2]; 561 break; 562 case 5: // aligned to tab boundaries of 4 spaces long tabs 563 [self insertSpaceFillAlignedAtTabsOfSize:4]; 564 break; 565 case 6: // aligned to tab boundaries of 8 spaces long tabs 566 [self insertSpaceFillAlignedAtTabsOfSize:8]; 567 break; 568 }*/ 569 } 570 else 571 { 572#ifdef WIN32 573 [super insertText: [NSString stringWithCString: [text UTF8String]]]; 574#else 575 [super insertText: text]; 576#endif 577 } 578 } 579 else 580 { 581#ifdef WIN32 582 [super insertText: [NSString stringWithCString: [text UTF8String]]]; 583#else 584 [super insertText: text]; 585#endif 586 } 587} 588 589/* This extra change tracking is required in order to inform the document 590 * that the text is changing _before_ it actually changes. This is required 591 * so that the document can un-highlight any highlit characters before the 592 * change occurs and after the change recompute any new highlighting. 593 */ 594- (void)keyDown:(NSEvent *)ev 595{ 596 [editor editorTextViewWillPressKey:self]; 597 [super keyDown:ev]; 598 [editor editorTextViewDidPressKey:self]; 599} 600 601- (void)paste:sender 602{ 603 [editor editorTextViewWillPressKey:self]; 604 [super paste:sender]; 605 [editor editorTextViewDidPressKey:self]; 606} 607 608- (void)mouseDown:(NSEvent *)ev 609{ 610 [editor editorTextViewWillPressKey:self]; 611 [super mouseDown:ev]; 612 [editor editorTextViewDidPressKey:self]; 613} 614 615- (NSRect)selectionRect 616{ 617 return _insertionPointRect; 618} 619 620- (BOOL)usesFindPanel 621{ 622 return YES; 623} 624 625- (void)performGoToLinePanelAction:(id)sender 626{ 627 LineJumper *lj; 628 629 lj = [LineJumper sharedInstance]; 630 [lj orderFrontLinePanel:self]; 631} 632 633- (void)goToLineNumber:(NSUInteger)lineNumber 634{ 635 NSUInteger offset; 636 NSUInteger i; 637 NSString *line; 638 NSEnumerator *e; 639 NSArray *lines; 640 NSRange range; 641 642 lines = [[self string] componentsSeparatedByString: @"\n"]; 643 e = [lines objectEnumerator]; 644 645 for (offset = 0, i = 1; 646 (line = [e nextObject]) != nil && i < lineNumber; 647 i++, offset += [line length] + 1); 648 649 if (line != nil) 650 { 651 range = NSMakeRange(offset, [line length]); 652 } 653 else 654 { 655 range = NSMakeRange([[self string] length], 0); 656 } 657 [self setSelectedRange:range]; 658 [self scrollRangeToVisible:range]; 659} 660 661@end 662