Call |-setNeedsDisplay:| in the right places so that the line numbers paint properly
[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 #include "BSLineNumberRulerView.h"
20
21 @interface BSSourceView (Private)
22 - (void)setupViews;
23 - (void)errorHighlightingFile:(NSNotification*)notif;
24 - (void)setPlainTextStringFromFile:(NSString*)filePath;
25 @end
26
27 @implementation BSSourceView
28
29 @synthesize textView = textView_;
30 @synthesize scrollView = scrollView_;
31 @synthesize markers = markers_;
32 @synthesize markedLine;
33 @synthesize delegate;
34 @synthesize file;
35
36 /**
37 * Initializes the source view with the path of a file
38 */
39 - (id)initWithFrame:(NSRect)frame
40 {
41 if (self = [super initWithFrame:frame])
42 {
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
61 [scrollView_ removeFromSuperview];
62 [textView_ removeFromSuperview];
63
64 [super dealloc];
65 }
66
67 /**
68 * Sets the file name as well as the text of the source view
69 */
70 - (void)setFile:(NSString*)f
71 {
72 if (file != f)
73 {
74 [file release];
75 file = [f retain];
76 }
77
78 if (![[NSFileManager defaultManager] fileExistsAtPath:f])
79 {
80 [textView_ setString:@""];
81 return;
82 }
83
84 @try
85 {
86 // Attempt to use the PHP CLI to highlight the source file as HTML
87 NSPipe* outPipe = [NSPipe pipe];
88 NSPipe* errPipe = [NSPipe pipe];
89 NSTask* task = [[NSTask new] autorelease];
90
91 [task setLaunchPath:@"/usr/bin/php"]; // This is the path to the default Leopard PHP executable
92 [task setArguments:[NSArray arrayWithObjects:@"-s", f, nil]];
93 [task setStandardOutput:outPipe];
94 [task setStandardError:errPipe];
95 [task launch];
96
97 [[errPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
98
99 NSData* data = [[outPipe fileHandleForReading] readDataToEndOfFile];
100 NSAttributedString* source = [[NSAttributedString alloc] initWithHTML:data documentAttributes:NULL];
101 [[textView_ textStorage] setAttributedString:source];
102 [source release];
103 }
104 @catch (NSException* exception)
105 {
106 // If the PHP executable is not available then the NSTask will throw an exception
107 [self setPlainTextStringFromFile:f];
108 }
109
110 [ruler_ performLayout];
111 }
112
113 /**
114 * Sets the contents of the SourceView via a string rather than loading from a path
115 */
116 - (void)setString:(NSString*)source asFile:(NSString*)path
117 {
118 // create the temp file
119 NSError* error = nil;
120 NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"MacGDBpHighlighter"];
121 [source writeToFile:tmpPath atomically:NO encoding:NSUTF8StringEncoding error:&error];
122 if (error)
123 {
124 [textView_ setString:source];
125 return;
126 }
127
128 // highlight the temporary file
129 [self setFile:tmpPath];
130
131 // delete the temp file
132 [[NSFileManager defaultManager] removeItemAtPath:tmpPath error:NULL];
133
134 // plop in our fake path so nobody knows the difference
135 if (path != file)
136 {
137 [file release];
138 file = [path copy];
139 }
140
141 [ruler_ performLayout];
142 }
143
144 /**
145 * If an error occurs in reading the highlighted PHP source, this will merely set the string
146 */
147 - (void)errorHighlightingFile:(NSNotification*)notif
148 {
149 NSData* data = [[notif userInfo] objectForKey:NSFileHandleNotificationDataItem];
150 if ([data length] > 0) // there's something on stderr, so the PHP CLI failed
151 [self setPlainTextStringFromFile:file];
152 }
153
154 /**
155 * Flip the coordinates
156 */
157 - (BOOL)isFlipped
158 {
159 return YES;
160 }
161
162 /**
163 * Tells the text view to scroll to a certain line
164 */
165 - (void)scrollToLine:(int)line
166 {
167 if ([[textView_ textStorage] length] == 0)
168 return;
169
170 // go through the document until we find the NSRange for the line we want
171 int rangeIndex = 0;
172 for (int i = 0; i < line; i++)
173 {
174 rangeIndex = NSMaxRange([[textView_ string] lineRangeForRange:NSMakeRange(rangeIndex, 0)]);
175 }
176
177 // now get the true start/end markers for it
178 unsigned lineStart, lineEnd;
179 [[textView_ string] getLineStart:&lineStart end:NULL contentsEnd:&lineEnd forRange:NSMakeRange(rangeIndex - 1, 0)];
180 [textView_ scrollRangeToVisible:[[textView_ string] lineRangeForRange:NSMakeRange(lineStart, lineEnd - lineStart)]];
181
182 [scrollView_ setNeedsDisplay:YES];
183 }
184
185 /**
186 * Setup all the subviews for the source metaview
187 */
188 - (void)setupViews
189 {
190 // Create the scroll view.
191 scrollView_ = [[[NSScrollView alloc] initWithFrame:[self bounds]] autorelease];
192 [scrollView_ setHasHorizontalScroller:YES];
193 [scrollView_ setHasVerticalScroller:YES];
194 [scrollView_ setAutohidesScrollers:YES];
195 [scrollView_ setBorderType:NSBezelBorder];
196 [scrollView_ setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
197 [[scrollView_ contentView] setAutoresizesSubviews:YES];
198 [self addSubview:scrollView_];
199
200 // add the text view to the scroll view
201 NSRect textFrame;
202 textFrame.origin = NSMakePoint(0.0, 0.0);
203 textFrame.size = [scrollView_ contentSize];
204 textView_ = [[[NSTextView alloc] initWithFrame:textFrame] autorelease];
205 [textView_ setEditable:NO];
206 [textView_ setFont:[NSFont fontWithName:@"Monaco" size:10.0]];
207 [textView_ setHorizontallyResizable:YES];
208 [textView_ setVerticallyResizable:YES];
209 [textView_ setMinSize:textFrame.size];
210 [textView_ setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
211 [[textView_ textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
212 [[textView_ textContainer] setWidthTracksTextView:NO];
213 [[textView_ textContainer] setHeightTracksTextView:NO];
214 [textView_ setAutoresizingMask:NSViewNotSizable];
215 [scrollView_ setDocumentView:textView_];
216
217 // Set up the ruler.
218 ruler_ = [[[BSLineNumberRulerView alloc] initWithSourceView:self] autorelease];
219 [scrollView_ setVerticalRulerView:ruler_];
220 [scrollView_ setHasHorizontalRuler:NO];
221 [scrollView_ setHasVerticalRuler:YES];
222 [scrollView_ setRulersVisible:YES];
223
224 NSArray* types = [NSArray arrayWithObject:NSFilenamesPboardType];
225 [self registerForDraggedTypes:types];
226 }
227
228 /**
229 * Gets the plain-text representation of the file at |filePath| and sets the
230 * contents in the source view.
231 */
232 - (void)setPlainTextStringFromFile:(NSString*)filePath
233 {
234 NSError* error;
235 NSString* contents = [NSString stringWithContentsOfFile:filePath
236 encoding:NSUTF8StringEncoding
237 error:&error];
238 if (error) {
239 NSLog(@"Error reading file at %@: %@", filePath, error);
240 return;
241 }
242 [textView_ setString:contents];
243 }
244
245 // Drag Handlers ///////////////////////////////////////////////////////////////
246
247 /**
248 * Validates an initiated drag operation.
249 */
250 - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
251 {
252 if ([delegate respondsToSelector:@selector(sourceView:acceptsDropOfFile:)])
253 return NSDragOperationCopy;
254 return NSDragOperationNone;
255 }
256
257 /**
258 * Performs a dragging operation of files to set the contents of the file.
259 */
260 - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
261 {
262 NSPasteboard* pboard = [sender draggingPasteboard];
263 if ([[pboard types] containsObject:NSFilenamesPboardType]) {
264 NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
265 if ([files count]) {
266 NSString* filename = [files objectAtIndex:0];
267 if ([delegate respondsToSelector:@selector(sourceView:acceptsDropOfFile:)] &&
268 [delegate sourceView:self acceptsDropOfFile:filename]) {
269 [self setFile:filename];
270 return YES;
271 }
272 }
273 }
274 return NO;
275 }
276
277 @end