apps-projectcenter/Modules/Editors/ProjectCenter/PCEditorView.m

665 lines
17 KiB
Objective-C

/*
PCEditorView.m
Implementation of the PCEditorView class for the
ProjectManager application.
Copyright (C) 2005-2020 Free Software Foundation
Saso Kiselkov
Serg Stoyan
Riccardo Mottola
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#import "PCEditorView.h"
#import <Foundation/NSString.h>
#import <Foundation/NSUserDefaults.h>
#import <Foundation/NSArchiver.h>
#import <AppKit/PSOperators.h>
#import <AppKit/NSColor.h>
#import <AppKit/NSEvent.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSCursor.h>
#import <AppKit/NSLayoutManager.h>
#import <AppKit/NSFont.h>
#import <ctype.h>
#import <ProjectCenter/PCProjectManager.h>
#import "PCEditor.h"
#import "SyntaxHighlighter.h"
#import "LineJumper.h"
#import "Modules/Preferences/EditorFSC/PCEditorFSCPrefs.h"
/**
* Computes the indenting offset of the last line before the passed
* start offset containg text in the passed string, e.g.
*
* ComputeIndentingOffset(@" Hello World", 12) = 2
* ComputeIndentingOffset(@" Try this one out\n"
* @" ", 27) = 4
*
* @argument string The string in which to do the computation.
* @argument start The start offset from which to start looking backwards.
* @return The ammount of spaces the last line containing text is offset
* from it's start.
*/
static int ComputeIndentingOffset(NSString * string, unsigned int start)
{
SEL sel = @selector(characterAtIndex:);
unichar (* charAtIndex)(NSString *, SEL, unsigned int) =
(unichar (*)(NSString *, SEL, unsigned int))
[string methodForSelector: sel];
unichar c;
int firstCharOffset = -1;
int offset;
int startOffsetFromLineStart = -1;
for (offset = start - 1; offset >= 0; offset--)
{
c = charAtIndex(string, sel, offset);
if (c == '\n')
{
if (startOffsetFromLineStart < 0)
{
startOffsetFromLineStart = start - offset - 1;
}
if (firstCharOffset >= 0)
{
firstCharOffset = firstCharOffset - offset - 1;
break;
}
}
else if (!isspace(c))
{
firstCharOffset = offset;
}
}
return firstCharOffset >= 0 ? firstCharOffset : 0;
/* if (firstCharOffset >= 0)
{
// if the indenting of the current line is lower than the indenting
// of the previous actual line, we return the lower indenting
if (startOffsetFromLineStart >= 0 &&
startOffsetFromLineStart < firstCharOffset)
{
return startOffsetFromLineStart;
}
// otherwise we return the actual indenting, so that any excess
// space is trimmed and the lines are aligned according the last
// indenting level
else
{
return firstCharOffset;
}
}
else
{
return 0;
}*/
}
@interface PCEditorView (Private)
- (void)insertSpaceFillAlignedAtTabsOfSize:(unsigned int)tabSize;
- (void)performIndentation;
@end
@implementation PCEditorView (Private)
/**
* Makes the receiver insert as many spaces at the current insertion
* location as are required to reach the nearest tab-character boundary.
*
* @argument tabSize Specifies how many spaces represent one tab.
*/
- (void)insertSpaceFillAlignedAtTabsOfSize:(unsigned int)tabSize
{
char buf[tabSize];
NSString * string = [self string];
unsigned int lineLength;
SEL sel = @selector(characterAtIndex:);
unichar (* charAtIndex)(NSString*, SEL, unsigned int) =
(unichar (*)(NSString*, SEL, unsigned int))
[string methodForSelector: sel];
int i;
int skip;
// computes the length of the current line
for (i = [self selectedRange].location - 1, lineLength = 0;
i >= 0;
i--, lineLength++)
{
if (charAtIndex(string, sel, i) == '\n')
{
break;
}
}
skip = tabSize - (lineLength % tabSize);
if (skip == 0)
{
skip = tabSize;
}
memset(buf, ' ', skip);
[super insertText: [NSString stringWithCString: buf length: skip]];
}
// Go backward to first '\n' char or start of file
- (NSInteger)lineStartIndexForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger line_start;
// Get line start index moving from index backwards
for (line_start = index;line_start > 0;line_start--)
{
if ([string characterAtIndex:line_start] == '\n' &&
line_start != index)
{
line_start++;
break;
}
}
NSLog(@"index: %li start: %li", index, line_start);
return line_start > index ? index : line_start;
}
- (NSInteger)lineEndIndexForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger line_end;
NSInteger string_length = [string length];
// Get line start index moving from index backwards
for (line_end = index;line_end < string_length;line_end++)
{
if ([string characterAtIndex:line_end] == '\n')
{
break;
}
}
NSLog(@"index: %li end: %li", (long)index, (long)line_end);
return line_end < string_length ? line_end : string_length;
}
- (NSInteger)previousLineStartIndexForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger cur_line_start;
NSInteger prev_line_start;
cur_line_start = [self lineStartIndexForIndex:index forString:string];
prev_line_start = [self lineStartIndexForIndex:cur_line_start-1
forString:string];
NSLog(@"index: %li prev_start: %li", (long)index, (long)prev_line_start);
return prev_line_start;
}
- (NSInteger)nextLineStartIndexForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger cur_line_end;
NSInteger next_line_start;
NSInteger string_length = [string length];
cur_line_end = [self lineEndIndexForIndex:index forString:string];
next_line_start = cur_line_end + 1;
if (next_line_start < string_length)
{
return next_line_start;
}
else
{
return string_length;
}
}
- (unichar)firstCharOfLineForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger line_start = [self lineStartIndexForIndex:index forString:string];
NSInteger i;
unichar c;
c = 0;
// Get leading whitespaces range
for (i = line_start; i >= 0; i++)
{
c = [string characterAtIndex:i];
if (!isspace(c))
{
break;
}
}
fprintf(stderr, "First char: %c\n", c);
return c;
}
- (unichar)firstCharOfPrevLineForIndex:(NSInteger)index forString:(NSString *)string
{
NSInteger line_start = [self previousLineStartIndexForIndex:index
forString:string];
return [self firstCharOfLineForIndex:line_start forString:string];
}
- (void)performIndentation
{
NSString *string = [self string];
NSInteger location;
NSInteger line_start;
NSInteger offset;
unichar c, plfc, clfc;
NSRange wsRange;
NSMutableString *indentString;
NSCharacterSet *wsCharSet = [NSCharacterSet whitespaceCharacterSet];
NSInteger i;
// int point;
location = [self selectedRange].location;
// point = [self nextLineStartIndexForIndex:location forString:string];
// [self setSelectedRange:NSMakeRange(point, 0)];
clfc = [self firstCharOfLineForIndex:location forString:string];
plfc = [self firstCharOfPrevLineForIndex:location forString:string];
// Get leading whitespaces range
line_start = [self lineStartIndexForIndex:location forString:string];
for (offset = line_start; offset >= 0; offset++)
{
c = [string characterAtIndex:offset];
if (![wsCharSet characterIsMember:c])
{
wsRange = NSMakeRange(line_start, offset-line_start);
break;
}
}
// Get indent
line_start = [self previousLineStartIndexForIndex:location forString:string];
for (offset = line_start; offset >= 0; offset++)
{
c = [string characterAtIndex:offset];
if (![wsCharSet characterIsMember:c])
{
offset = offset - line_start;
NSLog(@"offset: %li", offset);
break;
}
}
NSLog (@"clfc: %c plfc: %c", clfc, plfc);
if (plfc == '{' || clfc == '{')
{
offset += 2;
}
else if (clfc == '}' && plfc != '{')
{
offset -= 2;
}
// Get offset from BOL of previous line
// offset = ComputeIndentingOffset([self string], line_start-1);
NSLog(@"Indent offset: %li", offset);
// Replace current line whitespaces with new ones
indentString = [[NSMutableString alloc] initWithString:@""];
for (i = offset; i > 0; i--)
{
[indentString appendString:@" "];
}
if ([self shouldChangeTextInRange:wsRange
replacementString:indentString])
[[self textStorage] replaceCharactersInRange:wsRange
withString:indentString];
/* if (location > line_start + offset)
{
point = location - offset;
}
else
{
point = location;
}
[self setSelectedRange:NSMakeRange(point, 0)];*/
[indentString release];
}
@end
@implementation PCEditorView
+ (NSFont *)defaultEditorFont
{
NSFont *font = nil;
font = [NSFont userFixedPitchFontOfSize:0];
return font;
}
+ (NSFont *)defaultEditorBoldFont
{
NSFont *font = [self defaultEditorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSBoldFontMask];
}
+ (NSFont *)defaultEditorItalicFont
{
NSFont *font = [self defaultEditorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSItalicFontMask];
}
+ (NSFont *)defaultEditorBoldItalicFont
{
NSFont *font = [self defaultEditorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSBoldFontMask |
NSItalicFontMask];
}
- (NSFont *)editorFont
{
id <PCPreferences> prefs;
NSString *fontName;
CGFloat fontSize;
NSFont *font = nil;
prefs = [[[editor editorManager] projectManager] prefController];
fontName = [prefs stringForKey:EditorTextFont];
fontSize = [prefs floatForKey:EditorTextFontSize];
font = [NSFont fontWithName:fontName size:fontSize];
if (font == nil)
font = [NSFont userFixedPitchFontOfSize:0];
return font;
}
- (NSFont *)editorBoldFont
{
NSFont *font = [self editorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSBoldFontMask];
}
- (NSFont *)editorItalicFont
{
NSFont *font = [self editorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSItalicFontMask];
}
- (NSFont *)editorBoldItalicFont
{
NSFont *font = [self editorFont];
return [[NSFontManager sharedFontManager] convertFont:font
toHaveTrait:NSBoldFontMask |
NSItalicFontMask];
}
// ---
- (BOOL)becomeFirstResponder
{
return [editor becomeFirstResponder:self];
}
- (BOOL)resignFirstResponder
{
return [editor resignFirstResponder:self];
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
return YES;
}
// ---
- (void)dealloc
{
TEST_RELEASE(highlighter);
[super dealloc];
}
- (void)setEditor:(NSObject <CodeEditor> *)anEditor
{
editor = (PCEditor *)anEditor;
}
- (NSObject <CodeEditor> *)editor
{
return editor;
}
- (void)drawRect:(NSRect)r
{
if (highlighter)
{
NSRange drawnRange;
drawnRange = [[self layoutManager]
glyphRangeForBoundingRect:r inTextContainer:[self textContainer]];
drawnRange = [[self layoutManager] characterRangeForGlyphRange:drawnRange
actualGlyphRange:NULL];
[highlighter highlightRange:drawnRange];
}
[super drawRect:r];
}
- (void)createSyntaxHighlighterForFileType:(NSString *)fileType
{
ASSIGN(highlighter,
[[[SyntaxHighlighter alloc] initWithFileType:fileType
textStorage:[self textStorage]]
autorelease]);
[highlighter setNormalFont: [self editorFont]];
[highlighter setBoldFont: [self editorBoldFont]];
[highlighter setItalicFont: [self editorItalicFont]];
[highlighter setBoldItalicFont: [self editorBoldItalicFont]];
}
- (void)insertText:text
{
/* NOTE: On Windows we ensure to get a string in UTF-8 encoding. The problem
* is the highlighter that don't use a consistent codification causing a
* problem on Windows platform. Anyway, the plugin for Gemas editor works
* better and don't show this problem.
*/
if ([text isKindOfClass:[NSString class]])
{
NSString * string = text;
if ([text characterAtIndex:0] == 27)
{
NSLog(@"ESC key pressed. Ignoring it");
return;
}
if ([string isEqualToString:@"\n"])
{
/* if ([[NSUserDefaults standardUserDefaults]
boolForKey:@"ReturnDoesAutoindent"])
{*/
int location = [self selectedRange].location;
int offset = ComputeIndentingOffset([self string], location);
char *buf;
buf = (char *) malloc((offset + 2) * sizeof(unichar));
buf[0] = '\n';
memset(&buf[1], ' ', offset);
buf[offset+1] = '\0';
#ifdef WIN32
[super insertText:[NSString stringWithCString: buf
encoding: NSUTF8StringEncoding]];
#else
[super insertText:[NSString stringWithCString:buf]];
#endif
free(buf);
/* }
else
{
[super insertText:text];
}*/
}
else if ([string isEqualToString:@"\t"])
{
[self performIndentation];
/* switch ([[NSUserDefaults standardUserDefaults]
integerForKey:@"TabConversion"])
{
case 0: // no conversion
[super insertText:text];
break;
case 1: // 2 spaces
[super insertText:@" "];
break;
case 2: // 4 spaces
[super insertText:@" "];
break;
case 3: // 8 spaces
[super insertText:@" "];
break;
case 4: // aligned to tab boundaries of 2 spaces long tabs
[self insertSpaceFillAlignedAtTabsOfSize:2];
break;
case 5: // aligned to tab boundaries of 4 spaces long tabs
[self insertSpaceFillAlignedAtTabsOfSize:4];
break;
case 6: // aligned to tab boundaries of 8 spaces long tabs
[self insertSpaceFillAlignedAtTabsOfSize:8];
break;
}*/
}
else
{
#ifdef WIN32
[super insertText: [NSString stringWithCString: [text UTF8String]]];
#else
[super insertText: text];
#endif
}
}
else
{
#ifdef WIN32
[super insertText: [NSString stringWithCString: [text UTF8String]]];
#else
[super insertText: text];
#endif
}
}
/* This extra change tracking is required in order to inform the document
* that the text is changing _before_ it actually changes. This is required
* so that the document can un-highlight any highlit characters before the
* change occurs and after the change recompute any new highlighting.
*/
- (void)keyDown:(NSEvent *)ev
{
[editor editorTextViewWillPressKey:self];
[super keyDown:ev];
[editor editorTextViewDidPressKey:self];
}
- (void)paste:sender
{
[editor editorTextViewWillPressKey:self];
[super paste:sender];
[editor editorTextViewDidPressKey:self];
}
- (void)mouseDown:(NSEvent *)ev
{
[editor editorTextViewWillPressKey:self];
[super mouseDown:ev];
[editor editorTextViewDidPressKey:self];
}
- (NSRect)selectionRect
{
return _insertionPointRect;
}
- (BOOL)usesFindPanel
{
return YES;
}
- (void)performGoToLinePanelAction:(id)sender
{
LineJumper *lj;
lj = [LineJumper sharedInstance];
[lj orderFrontLinePanel:self];
}
- (void)goToLineNumber:(NSUInteger)lineNumber
{
NSUInteger offset;
NSUInteger i;
NSString *line;
NSEnumerator *e;
NSArray *lines;
NSRange range;
lines = [[self string] componentsSeparatedByString: @"\n"];
e = [lines objectEnumerator];
for (offset = 0, i = 1;
(line = [e nextObject]) != nil && i < lineNumber;
i++, offset += [line length] + 1);
if (line != nil)
{
range = NSMakeRange(offset, [line length]);
}
else
{
range = NSMakeRange([[self string] length], 0);
}
[self setSelectedRange:range];
[self scrollRangeToVisible:range];
}
@end