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