Merge pull request #153 from puremourning/conditional-breakpoints

Add basic support for conditional breakpoints
This commit is contained in:
mergify[bot] 2020-04-26 06:44:57 +00:00 • committed by GitHub
commit 1b71d84f58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 49 deletions

View file

@ -74,17 +74,20 @@ But for now, here's a (rather old) screenshot of Vimsepctor debugging Vim:
## Supported debugging features ## Supported debugging features
- flexible configuration syntax that can be checked in to source control
- breakpoints (function, line and exception breakpoints) - breakpoints (function, line and exception breakpoints)
- conditional breakpoints (function, line)
- step in/out/over/up, stop, restart - step in/out/over/up, stop, restart
- launch and attach - launch and attach
- remote launch, remote attach - remote launch, remote attach
- locals and globals display - locals and globals display
- watch expressions - watch expressions
- call stack and navigation - call stack display and navigation
- variable value display hover - variable value display hover
- interactive debug console - interactive debug console
- launch debugee within Vim's embedded terminal - launch debugee within Vim's embedded terminal
- logging/stdout display - logging/stdout display
- simple stable API for custom tooling (e.g. integrate with language server)
## Supported languages: ## Supported languages:
@ -427,14 +430,6 @@ than welcome.
The backlog can be [viewed on Trello](https://trello.com/b/yvAKK0rD/vimspector). The backlog can be [viewed on Trello](https://trello.com/b/yvAKK0rD/vimspector).
In order to use it you have to currently:
- Write a mostly undocumented configuration file that contains essentially
undocumented parameters.
- Accept that it isn't complete yet
- Work around some frustrating bugs in Vim
- Ignore probably many bugs in vimspector!
### Experimental ### Experimental
The plugin is currently _experimental_. That means that any part of it The plugin is currently _experimental_. That means that any part of it
@ -443,8 +438,9 @@ can (and probably will) change, including things like:
- breaking changes to the configuration - breaking changes to the configuration
- keys, layout, functionatlity of the UI - keys, layout, functionatlity of the UI
If a large number of people start using it then I will do my best to However, I commit to only doing this in the most extreme cases and to annouce
minimise this, or at least announce on Gitter. such changes on Gitter well in advance. There's nothing more annoying than stuff
just breaking on you. I get that.
# Mappings # Mappings
@ -458,12 +454,13 @@ features to set your own mappings. To that end, Vimspector defines the following
* `<Plug>VimspectorRestart` * `<Plug>VimspectorRestart`
* `<Plug>VimspectorPause` * `<Plug>VimspectorPause`
* `<Plug>VimspectorToggleBreakpoint` * `<Plug>VimspectorToggleBreakpoint`
* `<Plug>VimspectorToggleConditionalBreakpoint`
* `<Plug>VimspectorAddFunctionBreakpoint` * `<Plug>VimspectorAddFunctionBreakpoint`
* `<Plug>VimspectorStepOver` * `<Plug>VimspectorStepOver`
* `<Plug>VimspectorStepInto` * `<Plug>VimspectorStepInto`
* `<Plug>VimspectorStepOut` * `<Plug>VimspectorStepOut`
These map 1-1 with the API functions below. These map roughly 1-1 with the API functions below.
For example, if you want `<F5>` to start/continue debugging, add this to some For example, if you want `<F5>` to start/continue debugging, add this to some
appropriate place, such as your `vimrc` (hint: run `:e $MYVIMRC`). appropriate place, such as your `vimrc` (hint: run `:e $MYVIMRC`).
@ -513,17 +510,18 @@ loading vimspector**:
let g:vimspector_enable_mappings = 'HUMAN' let g:vimspector_enable_mappings = 'HUMAN'
``` ```
| Key | Function | API | | Key | Function | API |
| --- | --- | --- | | --- | --- | --- |
| `F5` | When debugging, continue. Otherwise start debugging. | `vimspector#Continue()` | | `F5` | When debugging, continue. Otherwise start debugging. | `vimspector#Continue()` |
| `F3` | Stop debugging. | `vimspector#Stop()` | | `F3` | Stop debugging. | `vimspector#Stop()` |
| `F4` | Restart debugging with the same configuration. | `vimspector#Restart()` | | `F4` | Restart debugging with the same configuration. | `vimspector#Restart()` |
| `F6` | Pause debugee. | `vimspector#Pause()` | | `F6` | Pause debugee. | `vimspector#Pause()` |
| `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` | | `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` |
| `F8` | Add a function breakpoint for the expression under cursor | `vimspector#AddFunctionBreakpoint( '<cexpr>' )` | | `<leader>F9` | Toggle conditional line breakpoint on the current line. | `vimspector#ToggleBreakpoint( {condition, hit condition } )` |
| `F10` | Step Over | `vimspector#StepOver()` | | `F8` | Add a function breakpoint for the expression under cursor | `vimspector#AddFunctionBreakpoint( '<cexpr>' )` |
| `F11` | Step Into | `vimspector#StepInto()` | | `F10` | Step Over | `vimspector#StepOver()` |
| `F12` | Step out of current function scope | `vimspector#StepOut()` | | `F11` | Step Into | `vimspector#StepInto()` |
| `F12` | Step out of current function scope | `vimspector#StepOut()` |
# Usage # Usage
@ -564,9 +562,10 @@ debugger](#java---partially-supported)
## Breakpoints ## Breakpoints
* Use `vimspector#ToggleBreakpoint()` to set/disable/delete a line breakpoint. * Use `vimspector#ToggleBreakpoint([ { 'condition': '<condition>' } ])`
* Use `vimspector#AddFunctionBreakpoint( '<name>' )` to add a function to set/disable/delete a line breakpoint, with optional condition.
breakpoint. * Use `vimspector#AddFunctionBreakpoint( '<name>' [, { 'condition': '<condition>' } ] )`
to add a function breakpoint with optional condition.
## Stepping ## Stepping

View file

@ -42,12 +42,23 @@ function! vimspector#ClearBreakpoints() abort
py3 _vimspector_session.ClearBreakpoints() py3 _vimspector_session.ClearBreakpoints()
endfunction endfunction
function! vimspector#ToggleBreakpoint() abort function! vimspector#ToggleBreakpoint( ... ) abort
py3 _vimspector_session.ToggleBreakpoint() if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.ToggleBreakpoint( vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#AddFunctionBreakpoint( function ) abort function! vimspector#AddFunctionBreakpoint( function, ... ) abort
py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ) ) if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ),
\ vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#StepOver() abort 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>VimspectorPause :<c-u>call vimspector#Pause()<CR>
nnoremap <Plug>VimspectorToggleBreakpoint nnoremap <Plug>VimspectorToggleBreakpoint
\ :<c-u>call vimspector#ToggleBreakpoint()<CR> \ :<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 nnoremap <Plug>VimspectorAddFunctionBreakpoint
\ :<c-u>call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR> \ :<c-u>call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR>
nnoremap <Plug>VimspectorStepOver :<c-u>call vimspector#StepOver()<CR> nnoremap <Plug>VimspectorStepOver :<c-u>call vimspector#StepOver()<CR>
@ -65,6 +70,7 @@ elseif s:mappings ==# 'HUMAN'
nmap <F4> <Plug>VimspectorRestart nmap <F4> <Plug>VimspectorRestart
nmap <F6> <Plug>VimspectorPause nmap <F6> <Plug>VimspectorPause
nmap <F9> <Plug>VimspectorToggleBreakpoint nmap <F9> <Plug>VimspectorToggleBreakpoint
nmap <leader><F9> <Plug>VimspectorToggleConditionalBreakpoint
nmap <F8> <Plug>VimspectorAddFunctionBreakpoint nmap <F8> <Plug>VimspectorAddFunctionBreakpoint
nmap <F10> <Plug>VimspectorStepOver nmap <F10> <Plug>VimspectorStepOver
nmap <F11> <Plug>VimspectorStepInto nmap <F11> <Plug>VimspectorStepInto

View file

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

View file

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