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