diff --git a/package.json b/package.json index 12ee93b7..6c58d5e4 100644 --- a/package.json +++ b/package.json @@ -1604,6 +1604,19 @@ ], "default": "//", "scope": "machine" + }, + "objectscript.debug.stepGranularity": { + "markdownDescription": "Controls the granularity of the debugger's [step action buttons](https://code.visualstudio.com/docs/editor/debugging#_debug-actions). Changing this setting while a debugging session is active will not change the behavior of the active session. **NOTE:** Only supported on IRIS 2023.1.5, 2024.1.1+, 2024.2 and subsequent versions! On all other versions, line stepping will be used.", + "type": "string", + "enum": [ + "command", + "line" + ], + "enumDescriptions": [ + "The step buttons execute a single command.", + "The step buttons execute an entire line." + ], + "default": "command" } } }, diff --git a/src/debug/debugSession.ts b/src/debug/debugSession.ts index fc5d5f1f..73aeb03e 100644 --- a/src/debug/debugSession.ts +++ b/src/debug/debugSession.ts @@ -205,6 +205,10 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { await this._connection.sendFeatureSetCommand("max_children", 32); await this._connection.sendFeatureSetCommand("max_depth", 2); await this._connection.sendFeatureSetCommand("notify_ok", 1); + await this._connection.sendFeatureSetCommand( + "step_granularity", + vscode.workspace.getConfiguration("objectscript.debug").get("stepGranularity") + ); this.sendResponse(response); @@ -599,6 +603,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { const place = `${stackFrame.method}+${stackFrame.methodOffset}`; const stackFrameId = this._stackFrameIdCounter++; const fileText: string | undefined = await getFileText(fileUri).catch(() => undefined); + const hasCmdLoc = typeof stackFrame.cmdBeginLine == "number"; if (fileText == undefined) { // Can't get the source for the document this._stackFrames.set(stackFrameId, stackFrame); @@ -611,7 +616,7 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { presentationHint: "deemphasize", }, line, - column: 1, + column: 0, }; } let noSource = false; @@ -656,12 +661,20 @@ export class ObjectScriptDebugSession extends LoggingDebugSession { } catch (ex) { noSource = true; } + const lineDiff = line - stackFrame.line; return { id: stackFrameId, name: place, source: noSource ? null : source, line, - column: 1, + column: hasCmdLoc ? stackFrame.cmdBeginPos + 1 : 0, + endLine: hasCmdLoc ? stackFrame.cmdEndLine + lineDiff : undefined, + endColumn: hasCmdLoc + ? (stackFrame.cmdEndPos == 0 + ? // A command that ends at position zero means "rest of this line" + fileText.split(/\r?\n/)[stackFrame.cmdEndLine + lineDiff - 1].length + : stackFrame.cmdEndPos) + 1 + : undefined, }; }) ); diff --git a/src/debug/xdebugConnection.ts b/src/debug/xdebugConnection.ts index f2438ea8..5f986afa 100644 --- a/src/debug/xdebugConnection.ts +++ b/src/debug/xdebugConnection.ts @@ -391,6 +391,14 @@ export class StackFrame { public line: number; /** The line number inside of method of class */ public methodOffset: number; + /** The start line number of the current command */ + public cmdBeginLine?: number; + /** The start position of the current command within the line */ + public cmdBeginPos?: number; + /** The end line number of the current command */ + public cmdEndLine?: number; + /** The end position of the current command within the line */ + public cmdEndPos?: number; /** The level (index) inside the stack trace at which the stack frame receides */ public level: number; /** The connection this stackframe belongs to */ @@ -406,6 +414,16 @@ export class StackFrame { this.line = parseInt(stackFrameNode.getAttribute("lineno"), 10); this.methodOffset = parseInt(stackFrameNode.getAttribute("methodoffset"), 10); this.level = parseInt(stackFrameNode.getAttribute("level"), 10); + const cmdBegin = stackFrameNode.getAttribute("cmdbegin"); + const cmdEnd = stackFrameNode.getAttribute("cmdend"); + if (cmdBegin && cmdEnd) { + const [cmdBeginLine, cmdBeginPos] = cmdBegin.split(":"); + const [cmdEndLine, cmdEndPos] = cmdEnd.split(":"); + this.cmdBeginLine = parseInt(cmdBeginLine, 10); + this.cmdBeginPos = parseInt(cmdBeginPos, 10); + this.cmdEndLine = parseInt(cmdEndLine, 10); + this.cmdEndPos = parseInt(cmdEndPos, 10); + } this.connection = connection; } /** Returns the available contexts (scopes, such as "Local" and "Superglobals") by doing a context_names command */