Add basic support for conditional breakpoints

This is the minimal required for a user to use conditional breakpoint -
we add an options dict to each breakpoint (line and function) and allow
the condition to be supplied. We add a plug mapping and a default
shortcut (<leader><F9>) to add one where we ask the user to enter the
condition and hit expression. This isn't great but it works.

We don't check the capabilities, so they would just be ignored if used
on a server that doesn't support them. We also ask for a hit expression
which most users won't understand so this isn't ideal either.

No tests yet.
This commit is contained in:
Ben Jackson 2020-04-25 19:30:16 +01:00
commit 7a70519b03
5 changed files with 69 additions and 37 deletions

View file

@ -458,6 +458,7 @@ features to set your own mappings. To that end, Vimspector defines the following
* `<Plug>VimspectorRestart`
* `<Plug>VimspectorPause`
* `<Plug>VimspectorToggleBreakpoint`
* `<Plug>VimspectorToggleConditionalBreakpoint`
* `<Plug>VimspectorAddFunctionBreakpoint`
* `<Plug>VimspectorStepOver`
* `<Plug>VimspectorStepInto`
@ -513,17 +514,18 @@ loading vimspector**:
let g:vimspector_enable_mappings = 'HUMAN'
```
| Key | Function | API |
| --- | --- | --- |
| `F5` | When debugging, continue. Otherwise start debugging. | `vimspector#Continue()` |
| `F3` | Stop debugging. | `vimspector#Stop()` |
| `F4` | Restart debugging with the same configuration. | `vimspector#Restart()` |
| `F6` | Pause debugee. | `vimspector#Pause()` |
| `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` |
| `F8` | Add a function breakpoint for the expression under cursor | `vimspector#AddFunctionBreakpoint( '<cexpr>' )` |
| `F10` | Step Over | `vimspector#StepOver()` |
| `F11` | Step Into | `vimspector#StepInto()` |
| `F12` | Step out of current function scope | `vimspector#StepOut()` |
| Key | Function | API |
| --- | --- | --- |
| `F5` | When debugging, continue. Otherwise start debugging. | `vimspector#Continue()` |
| `F3` | Stop debugging. | `vimspector#Stop()` |
| `F4` | Restart debugging with the same configuration. | `vimspector#Restart()` |
| `F6` | Pause debugee. | `vimspector#Pause()` |
| `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` |
| `<leader>F9` | Toggle conditional line breakpoint on the current line. | `vimspector#ToggleBreakpoint( {condition, hit condition } )` |
| `F8` | Add a function breakpoint for the expression under cursor | `vimspector#AddFunctionBreakpoint( '<cexpr>' )` |
| `F10` | Step Over | `vimspector#StepOver()` |
| `F11` | Step Into | `vimspector#StepInto()` |
| `F12` | Step out of current function scope | `vimspector#StepOut()` |
# Usage
@ -564,9 +566,10 @@ debugger](#java---partially-supported)
## Breakpoints
* Use `vimspector#ToggleBreakpoint()` to set/disable/delete a line breakpoint.
* Use `vimspector#AddFunctionBreakpoint( '<name>' )` to add a function
breakpoint.
* Use `vimspector#ToggleBreakpoint([ { 'condition': '<condition>' } ])`
to set/disable/delete a line breakpoint, with optional condition.
* Use `vimspector#AddFunctionBreakpoint( '<name>' [, { 'condition': '<condition>' } ] )`
to add a function breakpoint with optional condition.
## Stepping

View file

@ -42,12 +42,13 @@ function! vimspector#ClearBreakpoints() abort
py3 _vimspector_session.ClearBreakpoints()
endfunction
function! vimspector#ToggleBreakpoint() abort
py3 _vimspector_session.ToggleBreakpoint()
function! vimspector#ToggleBreakpoint( options = {} ) abort
py3 _vimspector_session.ToggleBreakpoint( vim.eval( 'a:options' ) )
endfunction
function! vimspector#AddFunctionBreakpoint( function ) abort
py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ) )
function! vimspector#AddFunctionBreakpoint( function, options = {} ) abort
py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ),
\ vim.eval( 'a:options' ) )
endfunction
function! vimspector#StepOver() abort

View file

@ -43,6 +43,11 @@ nnoremap <Plug>VimspectorRestart :<c-u>call vimspector#Restart()<CR>
nnoremap <Plug>VimspectorPause :<c-u>call vimspector#Pause()<CR>
nnoremap <Plug>VimspectorToggleBreakpoint
\ :<c-u>call vimspector#ToggleBreakpoint()<CR>
nnoremap <Plug>VimspectorToggleConditionalBreakpoint
\ :<c-u>call vimspector#ToggleBreakpoint(
\ { 'condition': input( 'Enter condition: ' ),
\ 'hitCondition': input( 'Enter hit condition: ' ) }
\ )<CR>
nnoremap <Plug>VimspectorAddFunctionBreakpoint
\ :<c-u>call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR>
nnoremap <Plug>VimspectorStepOver :<c-u>call vimspector#StepOver()<CR>
@ -65,6 +70,7 @@ elseif s:mappings ==# 'HUMAN'
nmap <F4> <Plug>VimspectorRestart
nmap <F6> <Plug>VimspectorPause
nmap <F9> <Plug>VimspectorToggleBreakpoint
nmap <leader><F9> <Plug>VimspectorToggleConditionalBreakpoint
nmap <F8> <Plug>VimspectorAddFunctionBreakpoint
nmap <F10> <Plug>VimspectorStepOver
nmap <F11> <Plug>VimspectorStepInto

View file

@ -55,6 +55,9 @@ class ProjectBreakpoints( object ):
if not utils.SignDefined( 'vimspectorBP' ):
vim.command( 'sign define vimspectorBP text==> texthl=Error' )
if not utils.SignDefined( 'vimspectorBPCond' ):
vim.command( 'sign define vimspectorBPCond text=?> texthl=Error' )
if not utils.SignDefined( 'vimspectorBPDisabled' ):
vim.command( 'sign define vimspectorBPDisabled text=!> texthl=Warning' )
@ -96,8 +99,9 @@ class ProjectBreakpoints( object ):
'col': 1,
'type': 'L',
'valid': 1 if bp[ 'state' ] == 'ENABLED' else 0,
'text': "Line breakpoint - {}".format(
bp[ 'state' ] )
'text': "Line breakpoint - {}: {}".format(
bp[ 'state' ],
json.dumps( bp[ 'options' ] ) )
} )
# I think this shows that the qf list is not right for this.
for bp in self._func_breakpoints:
@ -107,7 +111,8 @@ class ProjectBreakpoints( object ):
'col': 1,
'type': 'F',
'valid': 1,
'text': "Function breakpoint: {}".format( bp[ 'function' ] ),
'text': "Function breakpoint: {}: {}".format( bp[ 'function' ],
bp[ 'options' ] ),
} )
vim.eval( 'setqflist( {} )'.format( json.dumps( qf ) ) )
@ -127,7 +132,7 @@ class ProjectBreakpoints( object ):
self.UpdateUI()
def ToggleBreakpoint( self ):
def ToggleBreakpoint( self, options ):
line, column = vim.current.window.cursor
file_name = vim.current.buffer.name
@ -161,9 +166,10 @@ class ProjectBreakpoints( object ):
self._line_breakpoints[ file_name ].append( {
'state': 'ENABLED',
'line': line,
'options': options,
# 'sign_id': <filled in when placed>,
#
# Used by other breakpoint types:
# Used by other breakpoint types (specified in options):
# 'condition': ...,
# 'hitCondition': ...,
# 'logMessage': ...
@ -171,10 +177,14 @@ class ProjectBreakpoints( object ):
self.UpdateUI()
def AddFunctionBreakpoint( self, function ):
def AddFunctionBreakpoint( self, function, options ):
self._func_breakpoints.append( {
'state': 'ENABLED',
'function': function,
'state': 'ENABLED',
'function': function,
'options': options,
# Specified in options:
# 'condition': ...,
# 'hitCondition': ...,
} )
# TODO: We don't really have aanything to update here, but if we're going to
@ -239,7 +249,10 @@ class ProjectBreakpoints( object ):
if bp[ 'state' ] != 'ENABLED':
continue
breakpoints.append( { 'line': bp[ 'line' ] } )
dap_bp = {}
dap_bp.update( bp[ 'options' ] )
dap_bp.update( { 'line': bp[ 'line' ] } )
breakpoints.append( dap_bp )
source = {
'name': os.path.basename( file_name ),
@ -264,15 +277,21 @@ class ProjectBreakpoints( object ):
if self._server_capabilities.get( 'supportsFunctionBreakpoints' ):
awaiting = awaiting + 1
breakpoints = []
for bp in self._func_breakpoints:
if bp[ 'state' ] != 'ENABLED':
continue
dap_bp = {}
dap_bp.update( bp[ 'options' ] )
dap_bp.update( { 'name': bp[ 'function' ] } )
breakpoints.append( dap_bp )
self._connection.DoRequest(
lambda msg: response_handler( None, msg ),
{
'command': 'setFunctionBreakpoints',
'arguments': {
'breakpoints': [
{ 'name': bp[ 'function' ] }
for bp in self._func_breakpoints if bp[ 'state' ] == 'ENABLED'
],
'breakpoints': breakpoints,
}
},
failure_handler = lambda *_: response_received()
@ -354,12 +373,15 @@ class ProjectBreakpoints( object ):
bp[ 'sign_id' ] = self._next_sign_id
self._next_sign_id += 1
sign = ( 'vimspectorBPDisabled' if bp[ 'state' ] != 'ENABLED'
else 'vimspectorBPCond' if 'condition' in bp[ 'options' ]
else 'vimspectorBP' )
vim.command(
'sign place {0} group=VimspectorBP line={1} name={2} file={3}'.format(
bp[ 'sign_id' ] ,
bp[ 'line' ],
'vimspectorBP' if bp[ 'state' ] == 'ENABLED'
else 'vimspectorBPDisabled',
sign,
file_name ) )

View file

@ -982,8 +982,8 @@ class DebugSession( object ):
def ListBreakpoints( self ):
return self._breakpoints.ListBreakpoints()
def ToggleBreakpoint( self ):
return self._breakpoints.ToggleBreakpoint()
def ToggleBreakpoint( self, options ):
return self._breakpoints.ToggleBreakpoint( options )
def ClearBreakpoints( self ):
if self._connection:
@ -991,8 +991,8 @@ class DebugSession( object ):
return self._breakpoints.ClearBreakpoints()
def AddFunctionBreakpoint( self, function ):
return self._breakpoints.AddFunctionBreakpoint( function )
def AddFunctionBreakpoint( self, function, options ):
return self._breakpoints.AddFunctionBreakpoint( function, options )
def PathsToAllGadgetConfigs( vimspector_base, current_file ):