As I continued to look for new issues in an effort to contribute to Visual Studio Code, it was back to the 4000+ issue list for my next bug.
In no time I came across a recently filed “feature request”. #46509 – Disable breakpoint command.
If you’re not a regular user of Visual Studio Code, it’s important to point out that it comes bundled with great debugging support. VS Code’s built-in debugger helps accelerate your edit, compile and debug loop.
Along with the debugger tools, Visual Studio Code also provides a rich and easy keybinding editing experience. It lists all available commands with and without key bindings. Allowing you to easily change, remove and reset their key bindings using the available actions.
I believe it’s important for a developer to master their IDE environment and using key bindings is a great way to improve your productivity.
Issue#46509 ties both of these features together in order to deliver a more efficient experience
Since I’ve never worked with vs code’s key binding and debugging features before, some research was required.
VScode maintainer isidor, had suggested that this new command would live in /src/vs/workbench/parts/debug/browser/debugcommands.ts. This is where all the debugger commands for key bindings and workbench tools can be found.
I figured a great place to start would by analyzing how a similar feature has been implemented. The Debug
already has the two functions which Enable all breakpoints
and Disable all breakpoints
.
I made a conscious effort to analyze and understand each line of the debug.toggleBreakpoint
function to get a better idea of how to add the new command.
Analysis
Lets break down the debug.toggleBreakpoint
command code block from /src/vs/workbench/parts/debug/browser/debugCommands.ts#L176-L203
Immediately we see that were defining an registerCommandAndKeybindingRule
object defined by an IKeybindingsRegistry
interface.
In order to define a key binding command we need to provide the following parameters:
- id: how the command is identified in the key bindings pages
- weight: will this command be apart of vs code’s code, workbench, or a builtin/external extension
- when: ensures that the command can only be executed during the defined state
- primary: the default key bindings
KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'debug.toggleBreakpoint', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5), when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, InputFocusedContext.toNegated()), primary: KeyCode.Space, ... });
Next, we have the handler, which like the name suggests, handles the command functionality.
Before executing a command we need to get the current state of the editor’s environment. Essentially we’re checking to see if the user is currently focused in the editor window.
If we are able to get control of the editor, we can access the objects within it. In this case getFocusedElements()
function will return a list of all breakpoints within the current window.
handler: (accessor) => { const listService = accessor.get(IListService); const debugService = accessor.get(IDebugService); const list = listService.lastFocusedList; if (list instanceof List) { const focused = <IEnablement[]>list.getFocusedElements(); if (focused && focused.length) { debugService.enableOrDisableBreakpoints(!focused[0].enabled, focused[0]).done(null, errors.onUnexpectedError); } } }
If the window is focused, enableOrDisableBreakpoints
function will enable or disable ALL breakpoints.
Key word being all.
I’ve found that breaking down pieces of code line by line has helped big time in reading new code.
Implement
Following our previous analysis, I began to plugin the KeyBindingRegistry
required fields. (id, weight, primary, when).
Similar to the debug.toggleBreakpoint
function we need to get the current state of the editor. Along with creating a debugService
handler.
The handler from the analysis was looping through a List
of breakpoints. Since we want to toggle a single breakpoint, we will not be requiring this service. Instead, we need a to retrieve the underlying control of the editor by creating a variable containing an ICodeEditor
object.
const control = <ICodeEditor>editor.getControl();
Once we verify that our control
variable is true, we begin to retrieve the status of our debug services.
const position = control.getPosition(); // retrieves the active cursor position const modelUri = control.getModel().uri; // a uri model
Since we only need the status of an individual breakpoint we need to filter the debugService
by column and line number. At the end, we call the pop()
function to return the last element of the array and store it a const bp
variable.
const bp = debugService.getModel().getBreakpoints() .filter(bp => bp.lineNumber === position.lineNumber, bp => bp.column === position.column && bp.uri.toString() === modelUri.toString()).pop();
Once we verify that bp
is a valid IBreakpoint
object, we can begin to toggle it’s status.
The following function checks if the breakpoint is enabled or disabled and returns the opposite value.
debugService.enableOrDisableBreakpoints(!bp.enabled, bp).done(null, errors.onUnexpectedError);
Once we build and run our code, we see that our newly added command has been added to the key binding list where we can assign a shortcut.