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