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 }