1 module dlangide.ui.debuggerui;
2 
3 import dlangui.core.logger;
4 import dlangui.core.events;
5 import dlangui.widgets.docks;
6 import dlangide.workspace.project;
7 import dlangide.workspace.workspace;
8 import dlangide.ui.frame;
9 import dlangide.ui.commands;
10 import dlangide.ui.dsourceedit;
11 import dlangide.ui.stackpanel;
12 import dlangide.ui.watchpanel;
13 import ddebug.common.execution;
14 import ddebug.common.debugger;
15 
16 class DebuggerUIHandler : DebuggerCallback, StackFrameSelectedHandler {
17 
18     private IDEFrame _ide;
19     private Debugger _debugger;
20     private DebuggingState _state = DebuggingState.loaded;
21     private DebugFrame _location;
22     private WatchPanel _watchPanel;
23     private StackPanel _stackPanel;
24     private DebugThreadList _debugInfo;
25     private ulong _currentThreadId;
26 
27     this(IDEFrame ide, Debugger debugger) {
28         _ide = ide;
29         _debugger = debugger;
30         _debugger.setDebuggerCallback(this);
31     }
32 
33     /// called when program execution is stopped
34     void onProgramExecutionStatus(ProgramExecution process, ExecutionStatus status, int exitCode) {
35         Log.d("Debugger exit status: ", status, " ", exitCode);
36         updateLocation(null);
37         switchToDevelopPerspective();
38         _ide.debugFinished(process, status, exitCode);
39         //_callbackDelegate( delegate() { _callback.onProgramExecutionStatus(this, status, exitCode); } );
40     }
41 
42     /// send debug context (threads, stack frames, local vars...)
43     void onDebugContextInfo(DebugThreadList info, ulong threadId, int frameId) {
44         Log.d("Debugger context received threadId=", threadId, " frameId=", frameId);
45         _debugInfo = info;
46         _currentThreadId = threadId;
47         _stackPanel.updateDebugInfo(info, threadId, frameId);
48         _watchPanel.updateDebugInfo(info, threadId, frameId);
49     }
50 
51     void onStackFrameSelected(ulong threadId, int frame) {
52         Log.d("Stack frame selected threadId=", threadId, " frameId=", frame);
53         if (_debugInfo) {
54             if (DebugThread t = _debugInfo.findThread(threadId)) {
55                 DebugFrame f = (frame < t.length) ? t[frame] : t.frame;
56                 updateLocation(f);
57                 _debugger.requestDebugContextInfo(threadId, frame);
58             }
59         }
60     }
61 
62     void onResponse(ResponseCode code, string msg) {
63         Log.d("Debugger response: ", code, " ", msg);
64         //_callbackDelegate( delegate() { _callback.onResponse(code, msg); } );
65     }
66 
67     void onDebuggerMessage(string msg) {
68         _ide.logPanel.logLine("DBG: " ~ msg);
69     }
70 
71     /// debugger is started and loaded program, you can set breakpoints at this time
72     void onProgramLoaded(bool successful, bool debugInfoLoaded) {
73         _ide.logPanel.logLine("Program is loaded");
74         _ide.statusLine.setStatusText("Loaded"d);
75         switchToDebugPerspective();
76         // TODO: check succes status and debug info
77         if (_breakpoints.length) {
78             Log.v("Setting breakpoints");
79             _debugger.setBreakpoints(_breakpoints);
80         }
81         Log.v("Starting execution");
82         _debugger.execStart();
83     }
84 
85     void updateLocation(DebugFrame location) {
86         _location = location;
87         ProjectSourceFile sourceFile = location ? currentWorkspace.findSourceFile(location.projectFilePath, location.fullFilePath) : null;
88         if (location) {
89             if (sourceFile) {
90                 _ide.openSourceFile(sourceFile.filename, sourceFile, true);
91             } else {
92 	        import std.file;
93 		if (exists(location.fullFilePath)) {
94 	             _ide.openSourceFile(location.fullFilePath, null, true);
95 		} else {
96 		     Log.d("can not update location sourcefile does not exists:" ~ location.fullFilePath);
97 		}
98             }
99         }
100         DSourceEdit[] editors = _ide.allOpenedEditors;
101         foreach(ed; editors) {
102             if (location && ed.projectSourceFile is sourceFile)
103                 ed.executionLine = location.line - 1;
104             else
105                 ed.executionLine = -1;
106         }
107     }
108 
109     /// state changed: running / paused / stopped
110     void onDebugState(DebuggingState state, StateChangeReason reason, DebugFrame location, Breakpoint bp) {
111         Log.d("onDebugState: ", state, " reason=", reason);
112         _state = state;
113         if (state == DebuggingState.stopped) {
114             _ide.logPanel.logLine("Program is stopped");
115             _ide.statusLine.setStatusText("Stopped"d);
116             _debugger.stop();
117         } else if (state == DebuggingState.running) {
118             //_ide.logPanel.logLine("Program is started");
119             _ide.statusLine.setStatusText("Running"d);
120             _ide.window.update();
121         } else if (state == DebuggingState.paused) {
122             updateLocation(location);
123             //_ide.logPanel.logLine("Program is paused.");
124             if (reason == StateChangeReason.exception)
125                 _ide.statusLine.setStatusText("Signal received"d);
126             else
127                 _ide.statusLine.setStatusText("Paused"d);
128             _ide.window.update();
129         }
130     }
131 
132     private Breakpoint[] _breakpoints;
133     void onBreakpointListUpdated(Breakpoint[] breakpoints) {
134         _breakpoints = breakpoints;
135         if (_state == DebuggingState.running || _state == DebuggingState.paused) {
136             _debugger.setBreakpoints(_breakpoints);
137         }
138     }
139 
140     void run() {
141         _debugger.run();
142     }
143 
144     @property ulong currentThreadId() {
145         if (_currentThreadId)
146             return _currentThreadId;
147         return _debugInfo ? _debugInfo.currentThreadId : 0;
148     }
149     bool handleAction(const Action a) {
150         switch(a.id) {
151             case IDEActions.DebugPause:
152                 if (_state == DebuggingState.running) {
153                     _currentThreadId = 0;
154                     _debugger.execPause();
155                 }
156                 return true;
157             case IDEActions.DebugContinue:
158                 if (_state == DebuggingState.paused) {
159                     _currentThreadId = 0;
160                     _debugger.execContinue();
161                 }
162                 return true;
163             case IDEActions.DebugStop:
164                 //_debugger.execStop();
165                 Log.d("Trying to stop debugger");
166                 _currentThreadId = 0;
167                 _debugger.stop();
168                 return true;
169             case IDEActions.DebugStepInto:
170                 if (_state == DebuggingState.paused) {
171                     Log.d("DebugStepInto threadId=", currentThreadId);
172                     _debugger.execStepIn(currentThreadId);
173                 }
174                 return true;
175             case IDEActions.DebugStepOver:
176                 if (_state == DebuggingState.paused) {
177                     Log.d("DebugStepOver threadId=", currentThreadId);
178                     _debugger.execStepOver(currentThreadId);
179                 }
180                 return true;
181             case IDEActions.DebugStepOut:
182                 if (_state == DebuggingState.paused) {
183                     Log.d("DebugStepOut threadId=", currentThreadId);
184                     _debugger.execStepOut(currentThreadId);
185                 }
186                 return true;
187             case IDEActions.DebugRestart:
188                 _currentThreadId = 0;
189                 _debugger.execRestart();
190                 return true;
191             default:
192                 return false;
193         }
194     }
195 
196     /// override to handle specific actions state (e.g. change enabled state for supported actions)
197     bool handleActionStateRequest(const Action a) {
198         switch (a.id) {
199             case IDEActions.DebugStop:
200             case IDEActions.DebugPause:
201                 if (_state == DebuggingState.running)
202                     a.state = ACTION_STATE_ENABLED;
203                 else
204                     a.state = ACTION_STATE_DISABLE;
205                 return true;
206             case IDEActions.DebugContinue:
207             case IDEActions.DebugStepInto:
208             case IDEActions.DebugStepOver:
209             case IDEActions.DebugStepOut:
210                 if (_state == DebuggingState.paused)
211                     a.state = ACTION_STATE_ENABLED;
212                 else
213                     a.state = ACTION_STATE_DISABLE;
214                 return true;
215             case IDEActions.DebugRestart:
216                 a.state = ACTION_STATE_ENABLED;
217                 return true;
218             default:
219                 return true;
220         }
221     }
222 
223 
224 
225     void switchToDebugPerspective() {
226         _ide.dockHost.layoutPriority = [DockAlignment.Bottom, DockAlignment.Top, DockAlignment.Left, DockAlignment.Right];
227         _watchPanel = new WatchPanel("watch");
228         _watchPanel.dockAlignment = DockAlignment.Bottom;
229         _ide.dockHost.addDockedWindow(_watchPanel);
230         _stackPanel = new StackPanel("stack");
231         _stackPanel.dockAlignment = DockAlignment.Right;
232         _ide.dockHost.addDockedWindow(_stackPanel);
233         _stackPanel.stackFrameSelected = this;
234     }
235 
236     void switchToDevelopPerspective() {
237         _ide.dockHost.layoutPriority = [DockAlignment.Top, DockAlignment.Left, DockAlignment.Right, DockAlignment.Bottom];
238         _watchPanel = null;
239         auto w = _ide.dockHost.removeDockedWindow("watch");
240         if (w)
241             destroy(w);
242         _stackPanel = null;
243         w = _ide.dockHost.removeDockedWindow("stack");
244         if (w)
245             destroy(w);
246     }
247 }