diff --git a/package.json b/package.json index 8b32ef2..332aa61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "probe-rs-debugger", "displayName": "Debugger for probe-rs", - "version": "0.3.3", + "version": "0.3.4", "publisher": "probe-rs", "description": "probe-rs Debug Adapter for VS Code.", "author": { @@ -13,7 +13,7 @@ "probe-rs embedded debug" ], "engines": { - "vscode": "^1.62.2" + "vscode": "^1.64.0" }, "icon": "images/probe-rs-debugger.png", "categories": [ @@ -43,7 +43,7 @@ "watch-web": "webpack --watch --devtool nosources-source-map --config ./build/web-extension.webpack.config.js", "package-web": "webpack --mode production --config ./build/web-extension.webpack.config.js" }, - "enableApiProposal": false, + "enabledApiProposals": [], "dependencies": { "@vscode/debugadapter": "^1.51.0", "await-notify": "1.0.1", @@ -53,10 +53,10 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.1.0", "@types/node": "^17.0.13", - "@types/vscode": "^1.61.0", - "@typescript-eslint/eslint-plugin": "^5.10.1", + "@types/vscode": "^1.64.0", + "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.1", - "@vscode/debugadapter-testsupport": "^1.51.0", + "@vscode/debugprotocol": "^1.53.0", "eslint": "^8.8.0", "glob": "^7.2.0", "mocha": "^9.2.0", diff --git a/src/debugProtocol.json b/src/debugProtocol.json index d2ea49f..90a36a0 100644 --- a/src/debugProtocol.json +++ b/src/debugProtocol.json @@ -348,8 +348,15 @@ "properties": { "category": { "type": "string", - "description": "The output category. If not specified, 'console' is assumed.", - "_enum": [ "console", "stdout", "stderr", "telemetry" ] + "description": "The output category. If not specified or if the category is not understand by the client, 'console' is assumed.", + "_enum": [ "console", "important", "stdout", "stderr", "telemetry" ], + "enumDescriptions": [ + "Show the output in the client's default message UI, e.g. a 'debug console'. This category should only be used for informational output from the debugger (as opposed to the debuggee).", + "A hint for the client to show the ouput in the client's UI for important and highly visible information, e.g. as a popup notification. This category should only be used for important messages from the debugger (as opposed to the debuggee). Since this category value is a hint, clients might ignore the hint and assume the 'console' category.", + "Show the output as normal program output from the debuggee.", + "Show the output as error program output from the debuggee.", + "Send the output to telemetry instead of showing it to the user." + ] }, "output": { "type": "string", @@ -1378,7 +1385,7 @@ }, "name": { "type": "string", - "description": "The name of the Variable's child to obtain data breakpoint information for.\nIf variablesReference isn’t provided, this can be an expression." + "description": "The name of the Variable's child to obtain data breakpoint information for.\nIf variablesReference isn't provided, this can be an expression." } }, "required": [ "name" ] @@ -1531,7 +1538,7 @@ "ContinueRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to run again.", + "description": "The request resumes execution of all threads. If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true resumes only the specified thread. If not all threads were resumed, the 'allThreadsContinued' attribute of the response must be set to false.", "properties": { "command": { "type": "string", @@ -1550,7 +1557,11 @@ "properties": { "threadId": { "type": "integer", - "description": "Continue execution for the specified thread (if possible).\nIf the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true." + "description": "Specifies the active thread. If the debug adapter supports single thread execution (see 'supportsSingleThreadExecutionRequests') and the optional argument 'singleThread' is true, only the thread with this ID is resumed." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, execution is resumed only for the thread with given 'threadId'." } }, "required": [ "threadId" ] @@ -1565,7 +1576,7 @@ "properties": { "allThreadsContinued": { "type": "boolean", - "description": "If true, the 'continue' request has ignored the specified thread and continued all threads instead.\nIf this attribute is missing a value of 'true' is assumed for backward compatibility." + "description": "The value true (or a missing property) signals to the client that all threads have been resumed. The value false must be returned if not all threads were resumed." } } } @@ -1577,7 +1588,7 @@ "NextRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to run again for one step.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", + "description": "The request executes one step (in the given granularity) for the specified thread and allows all other threads to run freely by resuming them.\nIf the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true prevents other suspended threads from resuming.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", "properties": { "command": { "type": "string", @@ -1596,7 +1607,11 @@ "properties": { "threadId": { "type": "integer", - "description": "Execute 'next' for this thread." + "description": "Specifies the thread for which to resume execution for one step (of the given granularity)." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, all other suspended threads are not resumed." }, "granularity": { "$ref": "#/definitions/SteppingGranularity", @@ -1615,7 +1630,7 @@ "StepInRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to step into a function/method if possible.\nIf it cannot step into a target, 'stepIn' behaves like 'next'.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.\nIf there are multiple function/method calls (or other targets) on the source line,\nthe optional argument 'targetId' can be used to control into which target the 'stepIn' should occur.\nThe list of possible targets for a given source line can be retrieved via the 'stepInTargets' request.", + "description": "The request resumes the given thread to step into a function/method and allows all other threads to run freely by resuming them.\nIf the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true prevents other suspended threads from resuming.\nIf the request cannot step into a target, 'stepIn' behaves like the 'next' request.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.\nIf there are multiple function/method calls (or other targets) on the source line,\nthe optional argument 'targetId' can be used to control into which target the 'stepIn' should occur.\nThe list of possible targets for a given source line can be retrieved via the 'stepInTargets' request.", "properties": { "command": { "type": "string", @@ -1634,7 +1649,11 @@ "properties": { "threadId": { "type": "integer", - "description": "Execute 'stepIn' for this thread." + "description": "Specifies the thread for which to resume execution for one step-into (of the given granularity)." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, all other suspended threads are not resumed." }, "targetId": { "type": "integer", @@ -1657,7 +1676,7 @@ "StepOutRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to run again for one step.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", + "description": "The request resumes the given thread to step out (return) from a function/method and allows all other threads to run freely by resuming them.\nIf the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true prevents other suspended threads from resuming.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", "properties": { "command": { "type": "string", @@ -1676,7 +1695,11 @@ "properties": { "threadId": { "type": "integer", - "description": "Execute 'stepOut' for this thread." + "description": "Specifies the thread for which to resume execution for one step-out (of the given granularity)." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, all other suspended threads are not resumed." }, "granularity": { "$ref": "#/definitions/SteppingGranularity", @@ -1695,7 +1718,7 @@ "StepBackRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to run one step backwards.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.\nClients should only call this request if the capability 'supportsStepBack' is true.", + "description": "The request executes one backward step (in the given granularity) for the specified thread and allows all other threads to run backward freely by resuming them.\nIf the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true prevents other suspended threads from resuming.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.\nClients should only call this request if the capability 'supportsStepBack' is true.", "properties": { "command": { "type": "string", @@ -1714,7 +1737,11 @@ "properties": { "threadId": { "type": "integer", - "description": "Execute 'stepBack' for this thread." + "description": "Specifies the thread for which to resume execution for one step backwards (of the given granularity)." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, all other suspended threads are not resumed." }, "granularity": { "$ref": "#/definitions/SteppingGranularity", @@ -1733,7 +1760,7 @@ "ReverseContinueRequest": { "allOf": [ { "$ref": "#/definitions/Request" }, { "type": "object", - "description": "The request starts the debuggee to run backward.\nClients should only call this request if the capability 'supportsStepBack' is true.", + "description": "The request resumes backward execution of all threads. If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests') setting the 'singleThread' argument to true resumes only the specified thread. If not all threads were resumed, the 'allThreadsContinued' attribute of the response must be set to false.\nClients should only call this request if the capability 'supportsStepBack' is true.", "properties": { "command": { "type": "string", @@ -1752,8 +1779,13 @@ "properties": { "threadId": { "type": "integer", - "description": "Execute 'reverseContinue' for this thread." + "description": "Specifies the active thread. If the debug adapter supports single thread execution (see 'supportsSingleThreadExecutionRequests') and the optional argument 'singleThread' is true, only the thread with this ID is resumed." + }, + "singleThread": { + "type": "boolean", + "description": "If this optional flag is true, backward execution is resumed only for the thread with given 'threadId'." } + }, "required": [ "threadId" ] }, @@ -3109,6 +3141,10 @@ "supportsExceptionFilterOptions": { "type": "boolean", "description": "The debug adapter supports 'filterOptions' as an argument on the 'setExceptionBreakpoints' request." + }, + "supportsSingleThreadExecutionRequests": { + "type": "boolean", + "description": "The debug adapter supports the 'singleThread' property on the execution requests ('continue', 'next', 'stepIn', 'stepOut', 'reverseContinue', 'stepBack')." } } }, @@ -3459,7 +3495,7 @@ }, "value": { "type": "string", - "description": "The variable's value. This can be a multi-line text, e.g. for a function the body of a function." + "description": "The variable's value.\nThis can be a multi-line text, e.g. for a function the body of a function.\nFor structured variables (which do not have a simple value), it is recommended to provide a one line representation of the structured object. This helps to identify the structured object in the collapsed state when its children are not yet visible.\nAn empty string can be used if no value should be shown in the UI." }, "type": { "type": "string", @@ -3794,6 +3830,10 @@ "type": "string", "description": "A string that should be used when comparing this item with other items. When `falsy` the label is used." }, + "detail": { + "type": "string", + "description": "A human-readable string with additional information about this item, like type or symbol information." + }, "type": { "$ref": "#/definitions/CompletionItemType", "description": "The item's type. Typically the client uses this information to render the item in the UI with an icon." diff --git a/src/extension.ts b/src/extension.ts index 795cf12..488c800 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,7 +10,7 @@ import * as vscode from 'vscode'; import { DebugAdapterTracker, DebugAdapterTrackerFactory, } from 'vscode'; // This is just the default. It will be updated after the configuration has been resolved. -var probeRsLogLevel = 'Info'; +var probeRsLogLevel = 'Error'; export async function activate(context: vscode.ExtensionContext) { @@ -47,10 +47,22 @@ function handleExit(code: number | null, signal: string | null) { } } -// Messages to be sent to the debug session's console. Anything sent before or after an active debug session is silently ignored by VSCode. Ditto for any messages that doesn't start with 'ERROR', or 'INFO' , or 'WARN', ... unless the log level is DEBUG. Then everything is logged. -function logToConsole(consoleMesssage: string) { +// Messages to be sent to the debug session's console. +// Anything sent before or after an active debug session is silently ignored by VSCode. +// Any local (generated directly by this extension) messages MUST start with 'ERROR', or 'INFO' , 'WARN', `DEBUG`, or `TRACE` to match the RUST log behaviour. +// Any local messages that start with `CONSOLE` will ALWAYS be logged. +// Any messages that come from the `probe-rs-debugger` STDERR will always be logged, and will already conform with the RUST LOG setting. +function logToConsole(consoleMesssage: string, fromDebugger: boolean = false) { console.log(consoleMesssage); // During VSCode extension development, this will also log to the local debug console - if (consoleMesssage.includes('CONSOLE')) { + if (fromDebugger) { + // Any messages that come directly from the debugger, are assumed to be RUST_LOG messages and should be logged to the console. + vscode.debug.activeDebugConsole.appendLine(consoleMesssage); + // The one exception is RUST_LOG messages of the `error` variant. These deserve to be shown as an error message in the UI also. + // This filter might capture more than expected, but since RUST_LOG messages can take many formats, it seems that this is the safest/most inclusive. + if (consoleMesssage.includes("ERROR")) { + vscode.window.showErrorMessage("`probe-rs-debugger`: " + consoleMesssage); + } + } else if (consoleMesssage.includes('CONSOLE')) { vscode.debug.activeDebugConsole.appendLine(consoleMesssage); } else { switch (probeRsLogLevel) { @@ -178,7 +190,10 @@ class ProbeRSDebugAdapterServerDescriptorFactory implements vscode.DebugAdapterD // - The decision was made during investigation of an [issue](https://github.com/probe-rs/probe-rs/issues/703) ... basically, after the probe-rs API was fixed, the code would work well for TCP connections (`DebugAdapterServer`), but would not work for STDIO connections (`DebugAdapterServer`). After some searches I found other extension developers that also found the TCP based connections to be more stable. // - Since then, we have taken advantage of the access to stderr that `DebugAdapterServer` offers to route `RUST_LOG` output from the debugger to the user's VSCode Debug Console. This is a very useful capability, and cannot easily be implemented in `DebugAdapterExecutable`, because it does not allow access to `stderr` [See ongoing issue in VScode repo](https://github.com/microsoft/vscode/issues/108145). async createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): Promise { - probeRsLogLevel = session.configuration.consoleLogLevel; + if (session.configuration.hasOwnProperty('consoleLogLevel')) { + probeRsLogLevel = session.configuration.consoleLogLevel.toLowerCase(); + }; + // Initiate either the 'attach' or 'launch' request. logToConsole("INFO: Session: " + JSON.stringify(session, null, 2)); @@ -219,15 +234,10 @@ class ProbeRSDebugAdapterServerDescriptorFactory implements vscode.DebugAdapterD args.push("--port"); args.push(debugServer[1]); - var logEnv = 'error'; //This is the default - if (session.configuration.hasOwnProperty('consoleLogLevel')) { - logEnv = session.configuration.consoleLogLevel.toLowerCase(); - }; - var options = { cwd: session.configuration.cwd, // eslint-disable-next-line @typescript-eslint/naming-convention - env: { ...process.env, 'RUST_LOG': logEnv, }, + env: { ...process.env, 'RUST_LOG': probeRsLogLevel, 'DEFMT_LOG': probeRsLogLevel }, windowsHide: true, }; @@ -267,9 +277,9 @@ class ProbeRSDebugAdapterServerDescriptorFactory implements vscode.DebugAdapterD }); launchedDebugAdapter.stderr?.on('data', (data: string) => { if (debuggerStatus === (DebuggerStatus.running as DebuggerStatus)) { - logToConsole("ERROR: " + data); + logToConsole(data, true); } else { - vscode.window.showErrorMessage("`probe-rs-debugger` error: " + data); + vscode.window.showErrorMessage("`probe-rs-debugger`: " + data); } }); launchedDebugAdapter.on('close', (code: number | null, signal: string | null) => {