Add a toolbar icon for the HUD window, which required making DebuggerController the...
[macgdbp.git] / Source / DebuggerController.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2009, 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 "DebuggerController.h"
18 #import "GDBpConnection.h"
19 #import "NSXMLElementAdditions.h"
20 #import "AppDelegate.h"
21 #import "BreakpointManager.h"
22
23 @interface DebuggerController (Private)
24 - (void)updateSourceViewer;
25 - (void)updateStackViewer;
26 - (void)expandVariables;
27 - (void)reloadStack;
28 @end
29
30 @implementation DebuggerController
31
32 @synthesize connection, sourceViewer, inspector;
33
34 /**
35 * Initializes the window controller and sets the connection using preference
36 * values
37 */
38 - (id)init
39 {
40 if (self = [super initWithWindowNibName:@"Debugger"])
41 {
42 stackController = [[StackController alloc] init];
43
44 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
45 connection = [[GDBpConnection alloc] initWithPort:[defaults integerForKey:@"Port"] session:[defaults stringForKey:@"IDEKey"]];
46 expandedVariables = [[NSMutableSet alloc] init];
47 [[self window] makeKeyAndOrderFront:nil];
48 [[self window] setDelegate:self];
49
50 [[NSNotificationCenter defaultCenter]
51 addObserver:self
52 selector:@selector(handleConnectionError:)
53 name:kErrorOccurredNotif
54 object:connection
55 ];
56
57 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"InspectorWindowVisible"])
58 [inspector orderFront:self];
59 }
60 return self;
61 }
62
63 /**
64 * Dealloc
65 */
66 - (void)dealloc
67 {
68 [connection release];
69 [expandedVariables release];
70 [stackController release];
71 [super dealloc];
72 }
73
74 /**
75 * Before the display get's comfortable, set up the NSTextView to scroll horizontally
76 */
77 - (void)awakeFromNib
78 {
79 [[self window] setExcludedFromWindowsMenu:YES];
80 [[self window] setTitle:[NSString stringWithFormat:@"GDBp @ %@:%d/%@", [connection remoteHost], [connection port], [connection session]]];
81 [sourceViewer setDelegate:self];
82 [stackArrayController setSortDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES] autorelease]]];
83 }
84
85 /**
86 * Called right before the window closes so that we can tell the socket to close down
87 */
88 - (void)windowWillClose:(NSNotification*)notif
89 {
90 [[connection socket] close];
91 }
92
93 /**
94 * Validates the menu items for the "Debugger" menu
95 */
96 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
97 {
98 SEL action = [anItem action];
99
100 if (action == @selector(stepOut:))
101 return ([connection isConnected] && [stackController.stack count] > 1);
102 else if (action == @selector(stepIn:) || action == @selector(stepOver:) || action == @selector(run:))
103 return [connection isConnected];
104 else if (action == @selector(reconnect:))
105 return ![connection isConnected];
106
107 return [[self window] validateUserInterfaceItem:anItem];
108 }
109
110 /**
111 * Shows the inspector window
112 */
113 - (IBAction)showInspectorWindow:(id)sender
114 {
115 if (![inspector isVisible])
116 [inspector makeKeyAndOrderFront:sender];
117 else
118 [inspector orderOut:sender];
119 }
120
121 /**
122 * Resets all the displays to be empty
123 */
124 - (void)resetDisplays
125 {
126 [variablesTreeController setContent:nil];
127 [stackController.stack removeAllObjects];
128 [stackArrayController rearrangeObjects];
129 [[sourceViewer textView] setString:@""];
130 sourceViewer.file = nil;
131 }
132
133 /**
134 * Sets the status to be "Error" and then displays the error message
135 */
136 - (void)setError:(NSString*)anError
137 {
138 [errormsg setStringValue:anError];
139 [errormsg setHidden:NO];
140 }
141
142 /**
143 * Handles a GDBpConnection error
144 */
145 - (void)handleConnectionError:(NSNotification*)notif
146 {
147 [self setError:[[notif userInfo] valueForKey:@"NSString"]];
148 }
149
150 /**
151 * Called once the socket accepts and MacGDBp is connected to the debugger
152 */
153 - (void)startDebugger
154 {
155 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"BreakOnFirstLine"])
156 [self stepIn:self];
157 }
158
159 /**
160 * Forwards the message to run script execution to the connection
161 */
162 - (IBAction)run:(id)sender
163 {
164 [connection run];
165 if ([connection isConnected])
166 [self reloadStack];
167 }
168
169 /**
170 * Tells the connection to ask the server to reconnect
171 */
172 - (IBAction)reconnect:(id)sender
173 {
174 [connection reconnect];
175 [self resetDisplays];
176 }
177
178 /**
179 * Forwards the message to "step in" to the connection
180 */
181 - (IBAction)stepIn:(id)sender
182 {
183 if ([[variablesTreeController selectedObjects] count] > 0)
184 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
185
186 [connection stepIn];
187 if ([connection isConnected])
188 [self reloadStack];
189 }
190
191 /**
192 * Forwards the message to "step out" to the connection
193 */
194 - (IBAction)stepOut:(id)sender
195 {
196 if ([[variablesTreeController selectedObjects] count] > 0)
197 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
198
199 [connection stepOut];
200 if ([connection isConnected])
201 [self reloadStack];
202 }
203
204 /**
205 * Forwards the message to "step over" to the connection
206 */
207 - (IBAction)stepOver:(id)sender
208 {
209 if ([[variablesTreeController selectedObjects] count] > 0)
210 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
211
212 [connection stepOver];
213 if ([connection isConnected])
214 [self reloadStack];
215 }
216
217 /**
218 * NSTableView delegate method that informs the controller that the stack selection did change and that
219 * we should update the source viewer
220 */
221 - (void)tableViewSelectionDidChange:(NSNotification*)notif
222 {
223 [self updateSourceViewer];
224 [self expandVariables];
225 }
226
227 /**
228 * Called whenver an item is expanded. This allows us to determine if we need to fetch deeper
229 */
230 - (void)outlineViewItemDidExpand:(NSNotification*)notif
231 {
232 NSTreeNode* node = [[notif userInfo] objectForKey:@"NSObject"];
233 [expandedVariables addObject:[[node representedObject] fullname]];
234 }
235
236 /**
237 * Called when an item was collapsed. This allows us to remove it from the list of expanded items
238 */
239 - (void)outlineViewItemDidCollapse:(NSNotification*)notif
240 {
241 [expandedVariables removeObject:[[[[notif userInfo] objectForKey:@"NSObject"] representedObject] fullname]];
242 }
243
244 #pragma mark Private
245
246 /**
247 * Does the actual updating of the source viewer by reading in the file
248 */
249 - (void)updateSourceViewer
250 {
251 id selection = [stackArrayController selection];
252 if ([selection valueForKey:@"filename"] == NSNoSelectionMarker)
253 return;
254
255 // get the filename
256 NSString* filename = [selection valueForKey:@"filename"];
257 filename = [[NSURL URLWithString:filename] path];
258 if ([filename isEqualToString:@""])
259 return;
260
261 // replace the source if necessary
262 if (![sourceViewer.file isEqualToString:filename])
263 {
264 NSString* source = [selection valueForKey:@"source"];
265 [sourceViewer setString:source asFile:filename];
266
267 NSSet* breakpoints = [NSSet setWithArray:[[BreakpointManager sharedManager] breakpointsForFile:filename]];
268 [[sourceViewer numberView] setMarkers:breakpoints];
269 }
270
271 int line = [[selection valueForKey:@"lineNumber"] intValue];
272 [sourceViewer setMarkedLine:line];
273 [sourceViewer scrollToLine:line];
274
275 [[sourceViewer textView] display];
276 }
277
278 /**
279 * Does some house keeping to the stack viewer
280 */
281 - (void)updateStackViewer
282 {
283 [stackArrayController rearrangeObjects];
284 [stackArrayController setSelectionIndex:0];
285 [self expandVariables];
286 }
287
288 /**
289 * Expands the variables based on the stored set
290 */
291 - (void)expandVariables
292 {
293 NSString* selection = [selectedVariable fullname];
294
295 for (int i = 0; i < [variablesOutlineView numberOfRows]; i++)
296 {
297 NSTreeNode* node = [variablesOutlineView itemAtRow:i];
298 NSString* fullname = [[node representedObject] fullname];
299
300 // see if it needs expanding
301 if ([expandedVariables containsObject:fullname])
302 [variablesOutlineView expandItem:node];
303
304 // select it if we had it selected before
305 if ([fullname isEqualToString:selection])
306 [variablesTreeController setSelectionIndexPath:[node indexPath]];
307 }
308 }
309
310 /**
311 * This updates the entire stack. Xdebug is queried to get the stack, non-shifted
312 * frames are reused and new ones are fetched.
313 */
314 - (void)reloadStack
315 {
316 NSArray* stack = [connection getCurrentStack];
317 if (stack == nil)
318 return;
319
320 [stackController.stack removeAllObjects];
321 [stackController.stack addObjectsFromArray:stack];
322 [self updateStackViewer];
323 [self updateSourceViewer];
324 }
325
326 #pragma mark BSSourceView Delegate
327
328 /**
329 * The gutter was clicked, which indicates that a breakpoint needs to be changed
330 */
331 - (void)gutterClickedAtLine:(int)line forFile:(NSString*)file
332 {
333 BreakpointManager* mngr = [BreakpointManager sharedManager];
334
335 if ([mngr hasBreakpointAt:line inFile:file])
336 {
337 [mngr removeBreakpointAt:line inFile:file];
338 }
339 else
340 {
341 Breakpoint* bp = [[Breakpoint alloc] initWithLine:line inFile:file];
342 [mngr addBreakpoint:bp];
343 [bp release];
344 }
345
346 [[sourceViewer numberView] setMarkers:[NSSet setWithArray:[mngr breakpointsForFile:file]]];
347 [[sourceViewer numberView] setNeedsDisplay:YES];
348 }
349
350 @end