/* ** CodeEditorView.m ** ** Copyright (c) 2002, 2003 ** ** Author: Yen-Ju Chen ** Bjoern Giesler ** ** 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 */ #include "CodeEditorView.h" #include "RulesetManager.h" #include "CodeEditorViewPreference.h" #include "CEView+BlockMarking.h" #include "CEView+Indentation.h" #include "CEView+TextUtils.h" #include "CEView+Fontification.h" #include "CEView+Inspector.h" #include "NSAttributedString+FindAndReplace.h" #include "BundleLoader.h" #include "FontificationHandler.h" #include #include "CEViewTypesetter.h" #include "CEViewLayoutManager.h" #include @implementation CodeEditorView - (void) scrollSelectedRangeToTop: (id) sender { int line; NSRect rect; [self sizeToFit]; [self getCursorPosOverall: NULL inLine: NULL atLine: &line]; rect = [layoutManager lineFragmentRectForGlyphAtIndex: 0 effectiveRange: NULL]; rect.origin.y = (line-1) * rect.size.height; rect.size = [self visibleRect].size; //rect.size.height -= 2 * lineHeight; [self scrollRectToVisible: rect]; } - (void) showSideTextView { if (hasSideTextView) { [self setTextContainerInset: NSMakeSize(0,0)]; hasSideTextView = NO; } else { [self setTextContainerInset: NSMakeSize(40, 0)]; hasSideTextView = YES; } } - (BOOL) isDocumentEdited { return documentEdited; } - (void) setDocumentEdited: (BOOL) bool { documentEdited = bool; if (bool == YES) [[self window] setDocumentEdited: YES]; } - (void) fontification { if ([self supportLanguage: languageName]) { [self unmarkPreviouslyMarkedBlock]; fontifying = YES; [self fontifyAccordingToLanguage: languageName]; fontifying = NO; [self markBlockAroundCursor]; } needsFontification = NO; } /* Private method */ -(NSTextContainer *) buildUpTextNetwork: (NSSize)aSize { NSTextContainer *tc; NSLayoutManager *lm; NSTextStorage *ts; ts = [[NSTextStorage alloc] init]; lm = [[CEViewLayoutManager alloc] init]; [ts addLayoutManager: lm]; RELEASE(lm); tc = [[NSTextContainer alloc] initWithContainerSize: aSize]; [lm addTextContainer: tc]; RELEASE(tc); /* The situation at this point is as follows: textView (us) --RETAINs--> textStorage textStorage --RETAINs--> layoutManager layoutManager --RETAINs--> textContainer */ /* We keep a flag to remember that we are directly responsible for managing the text network. */ _tf.owns_text_network = YES; return tc; } - (id) initWithFrame: (NSRect) frame { Class bundleClass; NSString *bundleName; int i, count; NSTextContainer *textContainer; /* Use built-in text system */ textContainer = [self buildUpTextNetwork: frame.size]; self = [self initWithFrame: frame textContainer: textContainer]; markedStartRange.location = NSNotFound; markedEndRange.location = NSNotFound; languageName = nil; needsFontification = NO; hasBlockMarked = NO; lineHeight = 0; ASSIGN(normalFont, [[RulesetManager sharedRulesetManager] normalFont]); [self setFont: normalFont]; // [self setString: @""]; [self setSelectedRange: NSMakeRange(0,0)]; [self setRichText: NO]; [self setImportsGraphics: NO]; [self setUsesFontPanel: NO]; [self setUsesRuler: NO]; [self performSelector: @selector(periodicTask:) withObject: self afterDelay: .2]; [[self textStorage] setDelegate: self]; preference = [CodeEditorViewPreference sharedCodeEditorViewPreference]; hasSideTextView = NO; if ([preference displaySideView]) { [self showSideTextView]; } [[self textContainer] setContainerSize: NSMakeSize(1e7, 1e7)]; layoutManager = [self layoutManager]; /* Load language bundle */ bundleLanguages = [NSMutableArray new]; bundleNames = [[BundleLoader defaultLoader] allNamesOfBundles]; RETAIN(bundleNames); count = [bundleNames count]; for (i = 0; i < count; i++) { bundleName = [bundleNames objectAtIndex: i]; bundleClass = [[BundleLoader defaultLoader] classNameOf: bundleName]; [bundleLanguages addObject: [bundleClass language]]; } return self; } - (NSString *)languageName { if(!languageName) return @"Text"; return languageName; } - (void)setLanguageName: (NSString *) name { NSString *bundleName; unsigned int index; ASSIGN(languageName, name); needsFontification = YES; /* Get block handler */ index = [bundleLanguages indexOfObject: name]; if (index != NSNotFound) { bundleName = [[[BundleLoader defaultLoader] allNamesOfBundles] objectAtIndex: index]; fontificationHandlerClass = [[BundleLoader defaultLoader] classNameOf: bundleName]; blockHandlerClass = [fontificationHandlerClass blockHandlerClass]; indentationHandlerClass = [fontificationHandlerClass indentationHandlerClass]; methodHandlerClass = [fontificationHandlerClass methodHandlerClass]; } } - (void)drawRect: (NSRect) rect { NSPoint drawPoint; [super drawRect: rect]; if (hasSideTextView) { /* draw side text view */ unsigned int i, len; NSString *text; NSRect visibleRect; NSString *s; NSDictionary *attributes; text = [[self textStorage] string]; len = [text length]; visibleRect = [self visibleRect]; [[NSColor lightGrayColor] set]; [NSBezierPath fillRect: NSMakeRect(visibleRect.origin.x, visibleRect.origin.y, 40, visibleRect.size.height)]; if (len) { attributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSColor blackColor], NSForegroundColorAttributeName, normalFont , NSFontAttributeName, nil]; /* Draw marked area first */ if (hasSideMarked) { [[NSColor yellowColor] set]; [NSBezierPath fillRect: sideMarkedRect]; } /* FIXME: Assume all the line are the same height, which is set in typesetter. */ lineHeight = [layoutManager lineFragmentRectForGlyphAtIndex: 0 effectiveRange: NULL].size.height; { int start, end; float delta; visibleRect.origin.y -= 5; visibleRect.size.height += 10; start = (int)floor(NSMinY(visibleRect)/lineHeight); end = (int)floor(NSMaxY(visibleRect)/lineHeight)+1; delta = (lineHeight-[normalFont pointSize])/2; if (start < 0) start = 0; for (i = start; i < end+1; i++) { drawPoint = NSMakePoint(3, i*lineHeight+delta); s = [[NSString alloc] initWithFormat: @"%d", i+1]; [s drawAtPoint: drawPoint withAttributes: attributes]; RELEASE(s); } } } } } - (void) finishedTypingWord: (NSString *) word range: (NSRange) range { if ([preference autoFontificationEachLine]) { NSRange lineRange; NSColor *color; lineRange = [self rangeOfLineAtCursor]; /* Delete word */ if (NSMaxRange(range) > [[self textStorage] length]) range.length = [[self textStorage] length] - range.location; if ([self supportLanguage: languageName]) { [self unmarkPreviouslyMarkedBlock]; fontifying = YES; color = [[RulesetManager sharedRulesetManager] colorForType: @"Normal"]; [[self textStorage] addAttribute: NSFontAttributeName value: normalFont range: lineRange]; [[self textStorage] addAttribute: NSForegroundColorAttributeName value: color range: lineRange]; [self fontifyAccordingToLanguage: languageName inRange: lineRange]; fontifying = NO; [self markBlockAroundCursor]; } needsFontification = NO; } } - (void) keyDown: (NSEvent *) theEvent { unichar fnkey; BOOL cursorMotion; NSRange wordAtBeginRange; NSString *wordAtBegin, *wordAtEnd; NSString *tabString; unsigned point; userPressingKey = YES; if (hasBlockMarked) { [self unmarkPreviouslyMarkedBlock]; hasBlockMarked = NO; } wordAtBegin = [self wordAtCursor]; wordAtBeginRange = [self rangeOfWordAtCursor]; if([[theEvent characters] length]) fnkey = [[theEvent characters] characterAtIndex: 0]; else fnkey = (unichar)0; if((fnkey == NSLeftArrowFunctionKey) || (fnkey == NSRightArrowFunctionKey) || (fnkey == NSUpArrowFunctionKey) || (fnkey == NSDownArrowFunctionKey)) { cursorMotion = YES; [super keyDown: theEvent]; } else { cursorMotion = NO; [self setDocumentEdited: YES]; if([[theEvent characters] isEqualToString: @"\t"]) { if ((tabString = [preference tabString])) [self insertText: tabString]; else /* Auto Indentation */ { point = [self selectedRange].location; point += [self syntacticallyIndentLineAtIndex: point]; [self setSelectedRange: NSMakeRange(point, 0)]; } } else if([[theEvent characters] isEqualToString: @"\r"]) { // Return [super keyDown: theEvent]; if ([preference autoIndentation]) { point = [self selectedRange].location; point += [self syntacticallyIndentLineAtIndex: point]; [self setSelectedRange: NSMakeRange(point, 0)]; } } else [super keyDown: theEvent]; needsFontification = YES; } /* Mark delimiter */ wordAtEnd = [self wordAtCursor]; if ([preference autoMarkBlock]) { if ([blockHandlerClass isDelimiter: wordAtEnd]) [self markBlockAroundCursor]; } if (cursorMotion == NO) { if (wordAtBegin) { if([wordAtEnd rangeOfString: wordAtBegin].location == NSNotFound) [self finishedTypingWord: wordAtBegin range: wordAtBeginRange]; } } userPressingKey = NO; } - (void) markBlockAroundCursor { [self findAndMarkBlockAtCursor]; hasBlockMarked = YES; } - (void)periodicTask: (id) sender { static float sinceFontify; static float sincePeriodicTask; static int lastLines; sinceFontify += .2; sincePeriodicTask += .2; if(userPressingKey) { /* Skip this one */ sinceFontify = 0; sincePeriodicTask = 0; [self performSelector: @selector(periodicTask:) withObject: self afterDelay: .2]; return; } if(sinceFontify > [preference fontificationInterval]) { if([preference autoFontification] && needsFontification) { /* Not over threshold */ if ( !([preference autoFontificationThreshold] && ([preference fontificationThreshold] < lastLines)) ) { if ((documentEdited && [preference autoFontification]) || needsFontification) { [self fontification]; } needsFontification = NO; } } sinceFontify = 0; } /* Check lines every 10 seconds in order to get accurate lines * so that it can apply to threshold */ if (sincePeriodicTask > 10) { [self getNumberOfGlyphs: NULL lines: &lastLines]; } [self performSelector: @selector(periodicTask:) withObject: self afterDelay: .2]; } - (BOOL) searchString: (NSString *) string backward: (BOOL) backward caseInsensitive: (BOOL) caseInsensitive { NSRange cursor, range; cursor = [self selectedRange]; if (cursor.location == NSNotFound) cursor = NSMakeRange(0, [[self textStorage] length]); if (backward == NO) { range = [[self textStorage] rangeOfString: string inRange: NSMakeRange(cursor.location + cursor.length, [[self textStorage] length] - cursor.location - cursor.length) backward: backward caseInsensitive: caseInsensitive]; } else { range = [[self textStorage] rangeOfString: string inRange: NSMakeRange(0, cursor.location) backward: backward caseInsensitive: caseInsensitive]; } if (range.location == NSNotFound) return NO; [self setSelectedRange: range]; [self scrollRangeToVisible: range]; return YES; } - (void)textStorageDidProcessEditing: (NSNotification *) not { if(!fontifying) { /* [not object] is the whole attributed string */ [self setDocumentEdited: YES]; } } - (NSArray *) supportedLanguages { return bundleLanguages; } - (void) dealloc { RELEASE(languageName); RELEASE(normalFont); RELEASE(bundleLanguages); RELEASE(bundleNames); [super dealloc]; } @end