Factored out the stack reloading code
[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 * Resets all the displays to be empty
112 */
113 - (void)resetDisplays
114 {
115 [variablesTreeController setContent:nil];
116 [stackController.stack removeAllObjects];
117 [stackArrayController rearrangeObjects];
118 [[sourceViewer textView] setString:@""];
119 sourceViewer.file = nil;
120 }
121
122 /**
123 * Sets the status to be "Error" and then displays the error message
124 */
125 - (void)setError:(NSString *)anError
126 {
127 [errormsg setStringValue:anError];
128 [errormsg setHidden:NO];
129 }
130
131 /**
132 * Handles a GDBpConnection error
133 */
134 - (void)handleConnectionError:(NSNotification *)notif
135 {
136 [self setError:[[notif userInfo] valueForKey:@"NSString"]];
137 }
138
139 /**
140 * Called once the socket accepts and MacGDBp is connected to the debugger
141 */
142 - (void)startDebugger
143 {
144 [self stepIn:self];
145 }
146
147 /**
148 * Forwards the message to run script execution to the connection
149 */
150 - (IBAction)run:(id)sender
151 {
152 [connection run];
153 if ([connection isConnected])
154 [self reloadStack];
155 }
156
157 /**
158 * Tells the connection to ask the server to reconnect
159 */
160 - (IBAction)reconnect:(id)sender
161 {
162 [connection reconnect];
163 [self resetDisplays];
164 }
165
166 /**
167 * Forwards the message to "step in" to the connection
168 */
169 - (IBAction)stepIn:(id)sender
170 {
171 if ([[variablesTreeController selectedObjects] count] > 0)
172 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
173
174 StackFrame *frame = [connection stepIn];
175 if ([frame isShiftedFrame:[stackController peek]])
176 [stackController pop];
177 [stackController push:frame];
178 [self updateStackViewer];
179 }
180
181 /**
182 * Forwards the message to "step out" to the connection
183 */
184 - (IBAction)stepOut:(id)sender
185 {
186 if ([[variablesTreeController selectedObjects] count] > 0)
187 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
188
189 StackFrame *frame = [connection stepOut];
190 [stackController pop]; // frame we were out of
191 [stackController pop]; // frame we are returning to
192 [stackController push:frame];
193 [self updateStackViewer];
194 }
195
196 /**
197 * Forwards the message to "step over" to the connection
198 */
199 - (IBAction)stepOver:(id)sender
200 {
201 if ([[variablesTreeController selectedObjects] count] > 0)
202 selectedVariable = [[variablesTreeController selectedObjects] objectAtIndex:0];
203
204 StackFrame *frame = [connection stepOver];
205 [stackController pop];
206 [stackController push:frame];
207 [self updateStackViewer];
208 }
209
210 /**
211 * NSTableView delegate method that informs the controller that the stack selection did change and that
212 * we should update the source viewer
213 */
214 - (void)tableViewSelectionDidChange:(NSNotification *)notif
215 {
216 [self updateSourceViewer];
217 [self expandVariables];
218 }
219
220 /**
221 * Called whenver an item is expanded. This allows us to determine if we need to fetch deeper
222 */
223 - (void)outlineViewItemDidExpand:(NSNotification *)notif
224 {
225 NSTreeNode *node = [[notif userInfo] objectForKey:@"NSObject"];
226 [expandedVariables addObject:[[node representedObject] fullname]];
227 }
228
229 /**
230 * Called when an item was collapsed. This allows us to remove it from the list of expanded items
231 */
232 - (void)outlineViewItemDidCollapse:(NSNotification *)notif
233 {
234 [expandedVariables removeObject:[[[[notif userInfo] objectForKey:@"NSObject"] representedObject] fullname]];
235 }
236
237 #pragma mark Private
238
239 /**
240 * Does the actual updating of the source viewer by reading in the file
241 */
242 - (void)updateSourceViewer
243 {
244 id selection = [stackArrayController selection];
245 if ([selection valueForKey:@"filename"] == NSNoSelectionMarker)
246 return;
247
248 // get the filename
249 NSString *filename = [selection valueForKey:@"filename"];
250 filename = [[NSURL URLWithString:filename] path];
251 if ([filename isEqualToString:@""])
252 return;
253
254 // replace the source if necessary
255 if (![sourceViewer.file isEqualToString:filename])
256 {
257 NSString *source = [selection valueForKey:@"source"];
258 [sourceViewer setString:source asFile:filename];
259
260 NSSet *breakpoints = [NSSet setWithArray:[[BreakpointManager sharedManager] breakpointsForFile:filename]];
261 [[sourceViewer numberView] setMarkers:breakpoints];
262 }
263
264 int line = [[selection valueForKey:@"lineNumber"] intValue];
265 [sourceViewer setMarkedLine:line];
266 [sourceViewer scrollToLine:line];
267
268 [[sourceViewer textView] display];
269 }
270
271 /**
272 * Does some house keeping to the stack viewer
273 */
274 - (void)updateStackViewer
275 {
276 [stackArrayController rearrangeObjects];
277 [stackArrayController setSelectionIndex:0];
278 [self expandVariables];
279 }
280
281 /**
282 * Expands the variables based on the stored set
283 */
284 - (void)expandVariables
285 {
286 NSString *selection = [selectedVariable fullname];
287
288 for (int i = 0; i < [variablesOutlineView numberOfRows]; i++)
289 {
290 NSTreeNode *node = [variablesOutlineView itemAtRow:i];
291 NSString *fullname = [[node representedObject] fullname];
292
293 // see if it needs expanding
294 if ([expandedVariables containsObject:fullname])
295 [variablesOutlineView expandItem:node];
296
297 // select it if we had it selected before
298 if ([fullname isEqualToString:selection])
299 [variablesTreeController setSelectionIndexPath:[node indexPath]];
300 }
301 }
302
303 /**
304 * This updates the entire stack. Xdebug is queried to get the stack, non-shifted
305 * frames are reused and new ones are fetched.
306 */
307 - (void)reloadStack
308 {
309 NSArray* stack = [connection getCurrentStack];
310 if (stack == nil)
311 return;
312
313 [stackController.stack removeAllObjects];
314 [stackController.stack addObjectsFromArray:stack];
315 [self updateStackViewer];
316 [self updateSourceViewer];
317 }
318
319 #pragma mark BSSourceView Delegate
320
321 /**
322 * The gutter was clicked, which indicates that a breakpoint needs to be changed
323 */
324 - (void)gutterClickedAtLine:(int)line forFile:(NSString *)file
325 {
326 BreakpointManager *mngr = [BreakpointManager sharedManager];
327
328 if ([mngr hasBreakpointAt:line inFile:file])
329 {
330 [mngr removeBreakpointAt:line inFile:file];
331 }
332 else
333 {
334 Breakpoint *bp = [[Breakpoint alloc] initWithLine:line inFile:file];
335 [mngr addBreakpoint:bp];
336 [bp release];
337 }
338
339 [[sourceViewer numberView] setMarkers:[NSSet setWithArray:[mngr breakpointsForFile:file]]];
340 [[sourceViewer numberView] setNeedsDisplay:YES];
341 }
342
343 @end