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