Refactor syntax highlighting in BSSourceView.
[macgdbp.git] / Source / BSSourceView.mm
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2011, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "BSSourceView.h"
18
19 #import "BSLineNumberRulerView.h"
20 #import "BSSourceViewTextView.h"
21
22 @interface BSSourceView (Private)
23 - (void)setupViews;
24 - (void)errorHighlightingFile:(NSNotification*)notif;
25 - (void)setPlainTextStringFromFile:(NSString*)filePath;
26 @end
27
28 @implementation BSSourceView
29
30 @synthesize textView = textView_;
31 @synthesize scrollView = scrollView_;
32 @synthesize markers = markers_;
33 @synthesize markedLine = markedLine_;
34 @synthesize delegate = delegate_;
35 @synthesize file = file_;
36
37 /**
38 * Initializes the source view with the path of a file
39 */
40 - (id)initWithFrame:(NSRect)frame
41 {
42 if (self = [super initWithFrame:frame]) {
43 [self setupViews];
44 [[NSNotificationCenter defaultCenter]
45 addObserver:self
46 selector:@selector(errorHighlightingFile:)
47 name:NSFileHandleReadToEndOfFileCompletionNotification
48 object:nil
49 ];
50 }
51 return self;
52 }
53
54 /**
55 * Dealloc
56 */
57 - (void)dealloc
58 {
59 [file_ release];
60 [markers_ release];
61
62 [scrollView_ removeFromSuperview];
63 [textView_ removeFromSuperview];
64
65 [super dealloc];
66 }
67
68 - (void)setMarkers:(NSSet*)markers {
69 [markers_ release];
70 markers_ = [markers copy];
71
72 [ruler_ setNeedsDisplay:YES];
73 }
74
75 - (void)setMarkedLine:(NSUInteger)markedLine {
76 markedLine_ = markedLine;
77 [ruler_ setNeedsDisplay:YES];
78 }
79
80 /**
81 * Reads the contents of file at |f| and sets the source viewer and filename
82 * as such.
83 */
84 - (void)setFile:(NSString*)f
85 {
86 if (file_ != f) {
87 [file_ release];
88 file_ = [f retain];
89 }
90
91 if (![[NSFileManager defaultManager] fileExistsAtPath:f]) {
92 [textView_ setString:@""];
93 return;
94 }
95
96 [self setSource:f completionHandler:nil];
97 }
98
99 /**
100 * Sets the contents of the SourceView to |source| representing the file at |path|.
101 */
102 - (void)setString:(NSString*)source asFile:(NSString*)path
103 {
104 if (path != file_) {
105 [file_ release];
106 file_ = [path copy];
107 }
108
109 // Write the source out as a temporary file so it can be highlighted.
110 NSError* error = nil;
111 NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"MacGDBpHighlighter"];
112 [source writeToFile:tmpPath atomically:NO encoding:NSUTF8StringEncoding error:&error];
113 if (error) {
114 [textView_ setString:source];
115 return;
116 }
117
118 [self setSource:tmpPath completionHandler:^{
119 [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:NULL];
120 }];
121 }
122
123 /**
124 * Flip the coordinates
125 */
126 - (BOOL)isFlipped
127 {
128 return YES;
129 }
130
131 /**
132 * Tells the text view to scroll to a certain line
133 */
134 - (void)scrollToLine:(NSUInteger)line
135 {
136 if ([[textView_ textStorage] length] == 0)
137 return;
138
139 // go through the document until we find the NSRange for the line we want
140 NSUInteger rangeIndex = 0;
141 for (NSUInteger i = 0; i < line; i++) {
142 rangeIndex = NSMaxRange([[textView_ string] lineRangeForRange:NSMakeRange(rangeIndex, 0)]);
143 }
144
145 // now get the true start/end markers for it
146 NSUInteger lineStart, lineEnd;
147 [[textView_ string] getLineStart:&lineStart
148 end:NULL
149 contentsEnd:&lineEnd
150 forRange:NSMakeRange(rangeIndex - 1, 0)];
151 [textView_ scrollRangeToVisible:[[textView_ string]
152 lineRangeForRange:NSMakeRange(lineStart, lineEnd - lineStart)]];
153 [scrollView_ setNeedsDisplay:YES];
154 }
155
156 /**
157 * Setup all the subviews for the source metaview
158 */
159 - (void)setupViews
160 {
161 // Create the scroll view.
162 scrollView_ = [[[NSScrollView alloc] initWithFrame:[self bounds]] autorelease];
163 [scrollView_ setHasHorizontalScroller:YES];
164 [scrollView_ setHasVerticalScroller:YES];
165 [scrollView_ setAutohidesScrollers:YES];
166 [scrollView_ setBorderType:NSBezelBorder];
167 [scrollView_ setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
168 [[scrollView_ contentView] setAutoresizesSubviews:YES];
169 [self addSubview:scrollView_];
170
171 // add the text view to the scroll view
172 NSRect textFrame;
173 textFrame.origin = NSMakePoint(0.0, 0.0);
174 textFrame.size = [scrollView_ contentSize];
175 textView_ = [[[BSSourceViewTextView alloc] initWithFrame:textFrame] autorelease];
176 [textView_ setSourceView:self];
177 [textView_ setEditable:NO];
178 [textView_ setFont:[NSFont fontWithName:@"Monaco" size:10.0]];
179 [textView_ setHorizontallyResizable:YES];
180 [textView_ setVerticallyResizable:YES];
181 [textView_ setMinSize:textFrame.size];
182 [textView_ setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
183 [[textView_ textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
184 [[textView_ textContainer] setWidthTracksTextView:NO];
185 [[textView_ textContainer] setHeightTracksTextView:NO];
186 [textView_ setAutoresizingMask:NSViewNotSizable];
187 [scrollView_ setDocumentView:textView_];
188
189 // Set up the ruler.
190 ruler_ = [[[BSLineNumberRulerView alloc] initWithSourceView:self] autorelease];
191 [scrollView_ setVerticalRulerView:ruler_];
192 [scrollView_ setHasHorizontalRuler:NO];
193 [scrollView_ setHasVerticalRuler:YES];
194 [scrollView_ setRulersVisible:YES];
195
196 NSArray* types = [NSArray arrayWithObject:NSFilenamesPboardType];
197 [self registerForDraggedTypes:types];
198 }
199
200 /**
201 * Reads the contents of |filePath| and sets it as the displayed text, after
202 * attempting to highlight it using the PHP binary.
203 */
204 - (void)setSource:(NSString*)filePath completionHandler:(void(^)(void))handler
205 {
206 @try {
207 // Attempt to use the PHP CLI to highlight the source file as HTML
208 NSPipe* outPipe = [NSPipe pipe];
209 NSPipe* errPipe = [NSPipe pipe];
210 NSTask* task = [[NSTask alloc] init];
211
212 [task setLaunchPath:@"/usr/bin/php"]; // This is the path to the default Leopard PHP executable
213 [task setArguments:@[ @"-s", filePath ]];
214 [task setStandardOutput:outPipe];
215 [task setStandardError:errPipe];
216 [task setTerminationHandler:^(NSTask*) {
217 dispatch_async(dispatch_get_main_queue(), ^{
218 if (task.terminationStatus == 0) {
219 NSData* data = [[outPipe fileHandleForReading] readDataToEndOfFile];
220 NSMutableAttributedString* source =
221 [[NSMutableAttributedString alloc] initWithHTML:data
222 options:@{ NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding) }
223 documentAttributes:nil];
224 NSMutableString* stringData = [source mutableString];
225 // PHP uses &nbsp; in the highlighted output, which should be converted
226 // back to normal spaces.
227 [stringData replaceOccurrencesOfString:@"\u00A0" withString:@" " options:0 range:NSMakeRange(0, stringData.length)];
228 [[textView_ textStorage] setAttributedString:source];
229 [source release];
230 } else {
231 NSLog(@"Failed to highlight PHP file %@: %@", filePath, [[errPipe fileHandleForReading] readDataToEndOfFile]);
232 [self setPlainTextStringFromFile:filePath];
233 }
234
235 [ruler_ performLayout];
236
237 [task release];
238
239 if (handler)
240 handler();
241 });
242 }];
243 [task launch];
244 } @catch (NSException* exception) {
245 // If the PHP executable is not available then the NSTask will throw an exception
246 [self setPlainTextStringFromFile:filePath];
247 }
248 }
249
250 /**
251 * Gets the plain-text representation of the file at |filePath| and sets the
252 * contents in the source view.
253 */
254 - (void)setPlainTextStringFromFile:(NSString*)filePath
255 {
256 NSError* error = nil;
257 NSString* contents = [NSString stringWithContentsOfFile:filePath
258 encoding:NSUTF8StringEncoding
259 error:&error];
260 if (error) {
261 NSLog(@"Error reading file at %@: %@", filePath, error);
262 return;
263 }
264 [textView_ setString:contents];
265 }
266
267 // Drag Handlers ///////////////////////////////////////////////////////////////
268
269 /**
270 * Validates an initiated drag operation.
271 */
272 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
273 {
274 return NSDragOperationCopy;
275 }
276
277 /**
278 * Performs a dragging operation of files to set the contents of the file.
279 */
280 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
281 {
282 NSPasteboard* pboard = [sender draggingPasteboard];
283 if ([[pboard types] containsObject:NSFilenamesPboardType]) {
284 NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
285 if ([files count]) {
286 NSString* filename = [files objectAtIndex:0];
287 [self setFile:filename];
288 return YES;
289 }
290 }
291 return NO;
292 }
293
294 @end