From 0d112d70a0f6adccbfd9e7e60bb6e327ebab5fbc Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 13 Oct 2020 22:16:11 +0100 Subject: [PATCH 1/2] Add SetLineBreakpoint and ClaerLineBreakpoint APIs These are useful for running tests (i.e. ensure there's a breakpiont at the start of the test) and/or other programmatic usages. They will also be needed for setting temporary breakpionts. --- autoload/vimspector.vim | 25 +++++++ python3/vimspector/breakpoints.py | 108 +++++++++++++++++----------- python3/vimspector/debug_session.py | 6 ++ python3/vimspector/signs.py | 4 +- python3/vimspector/utils.py | 6 +- 5 files changed, 105 insertions(+), 44 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 68c49b4..f4d2f58 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -76,6 +76,31 @@ function! vimspector#ToggleBreakpoint( ... ) abort py3 _vimspector_session.ToggleBreakpoint( vim.eval( 'options' ) ) endfunction +function! vimspector#SetLineBreakpoint( file_name, line_num, ... ) abort + if !s:enabled + return + endif + if a:0 == 0 + let options = {} + else + let options = a:1 + endif + py3 _vimspector_session.SetLineBreakpoint( + \ vim.eval( 'a:file_name' ), + \ int( vim.eval( 'a:line_num' ) ), + \ vim.eval( 'options' ) ) +endfunction + +function! vimspector#ClearLineBreakpoint( file_name, line_num ) abort + if !s:enabled + return + endif + py3 _vimspector_session.ClearLineBreakpoint( + \ vim.eval( 'a:file_name' ), + \ int( vim.eval( 'a:line_num' ) ) ) +endfunction + + function! vimspector#AddFunctionBreakpoint( function, ... ) abort if !s:enabled return diff --git a/python3/vimspector/breakpoints.py b/python3/vimspector/breakpoints.py index 6ab14f5..f5999c3 100644 --- a/python3/vimspector/breakpoints.py +++ b/python3/vimspector/breakpoints.py @@ -141,50 +141,74 @@ class ProjectBreakpoints( object ): self.UpdateUI() + def _FindLineBreakpoint( self, file_name, line ): + file_name = os.path.abspath( file_name ) + for index, bp in enumerate( self._line_breakpoints[ file_name ] ): + self._SignToLine( file_name, bp ) + if bp[ 'line' ] == line: + return bp, index + + return None, None + + + def _PutLineBreakpoint( self, file_name, line, options ): + self._line_breakpoints[ os.path.abspath( file_name ) ].append( { + 'state': 'ENABLED', + 'line': line, + 'options': options, + # 'sign_id': , + # + # Used by other breakpoint types (specified in options): + # 'condition': ..., + # 'hitCondition': ..., + # 'logMessage': ... + } ) + + + def _DeleteLineBreakpoint( self, bp, file_name, index ): + if 'sign_id' in bp: + signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' ) + del self._line_breakpoints[ os.path.abspath( file_name ) ][ index ] + + def ToggleBreakpoint( self, options ): - line, column = vim.current.window.cursor + line, _ = vim.current.window.cursor file_name = vim.current.buffer.name if not file_name: return - found_bp = False - action = 'New' - for index, bp in enumerate( self._line_breakpoints[ file_name ] ): - self._SignToLine( file_name, bp ) - if bp[ 'line' ] == line: - found_bp = True - if bp[ 'state' ] == 'ENABLED' and not self._connection: - bp[ 'state' ] = 'DISABLED' - action = 'Disable' - else: - if 'sign_id' in bp: - signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' ) - del self._line_breakpoints[ file_name ][ index ] - action = 'Delete' - break - - self._logger.debug( "Toggle found bp at {}:{} ? {} ({})".format( - file_name, - line, - found_bp, - action ) ) - - if not found_bp: - self._line_breakpoints[ file_name ].append( { - 'state': 'ENABLED', - 'line': line, - 'options': options, - # 'sign_id': , - # - # Used by other breakpoint types (specified in options): - # 'condition': ..., - # 'hitCondition': ..., - # 'logMessage': ... - } ) + bp, index = self._FindLineBreakpoint( file_name, line ) + if bp is None: + # ADD + self._PutLineBreakpoint( file_name, line, options ) + elif bp[ 'state' ] == 'ENABLED' and not self._connection: + # DISABLE + bp[ 'state' ] = 'DISABLED' + else: + # DELETE + self._DeleteLineBreakpoint( bp, file_name, index ) self.UpdateUI() + + def SetLineBreakpoint( self, file_name, line_num, options ): + bp, _ = self._FindLineBreakpoint( file_name, line_num ) + if bp is not None: + bp[ 'options' ] = options + return + self._PutLineBreakpoint( file_name, line_num, options ) + self.UpdateUI() + + + def ClearLineBreakpoint( self, file_name, line_num ): + bp, index = self._FindLineBreakpoint( file_name, line_num ) + if bp is None: + return + self._DeleteLineBreakpoint( bp, file_name, index ) + self.UpdateUI() + + def AddFunctionBreakpoint( self, function, options ): self._func_breakpoints.append( { 'state': 'ENABLED', @@ -386,17 +410,21 @@ class ProjectBreakpoints( object ): else 'vimspectorBPCond' if 'condition' in bp[ 'options' ] else 'vimspectorBP' ) - signs.PlaceSign( bp[ 'sign_id' ], - 'VimspectorBP', - sign, - file_name, - bp[ 'line' ] ) + if utils.BufferNumberForFile( file_name, False ) > 0: + signs.PlaceSign( bp[ 'sign_id' ], + 'VimspectorBP', + sign, + file_name, + bp[ 'line' ] ) def _SignToLine( self, file_name, bp ): if 'sign_id' not in bp: return bp[ 'line' ] + if utils.BufferNumberForFile( file_name, False ) <= 0: + return bp[ 'line' ] + signs = vim.eval( "sign_getplaced( '{}', {} )".format( utils.Escape( file_name ), json.dumps( { 'id': bp[ 'sign_id' ], 'group': 'VimspectorBP', } ) ) ) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index ccec07b..85f32da 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -1165,6 +1165,12 @@ class DebugSession( object ): def ToggleBreakpoint( self, options ): return self._breakpoints.ToggleBreakpoint( options ) + def SetLineBreakpoint( self, file_name, line_num, options ): + return self._breakpoints.SetLineBreakpoint( file_name, line_num, options ) + + def ClearLineBreakpoint( self, file_name, line_num ): + return self._breakpoints.ClearLineBreakpoint( file_name, line_num ) + def ClearBreakpoints( self ): if self._connection: self._codeView.ClearBreakpoints() diff --git a/python3/vimspector/signs.py b/python3/vimspector/signs.py index c51bd76..39d1f03 100644 --- a/python3/vimspector/signs.py +++ b/python3/vimspector/signs.py @@ -29,7 +29,7 @@ def DefineSign( name, text, double_text, texthl, col = 'right', **kwargs ): vim.command( cmd ) -def PlaceSign( sign_id, group, name, file, line ): +def PlaceSign( sign_id, group, name, file_name, line ): priority = settings.Dict( 'sign_priority' )[ name ] cmd = ( f'sign place { sign_id } ' @@ -37,7 +37,7 @@ def PlaceSign( sign_id, group, name, file, line ): f'name={ name } ' f'priority={ priority } ' f'line={ line } ' - f'file={ file }' ) + f'file={ file_name }' ) vim.command( cmd ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 879e910..4ce387d 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -45,8 +45,10 @@ _logger = logging.getLogger( __name__ ) SetUpLogging( _logger ) -def BufferNumberForFile( file_name ): - return int( vim.eval( "bufnr( '{0}', 1 )".format( Escape( file_name ) ) ) ) +def BufferNumberForFile( file_name, create = True ): + return int( vim.eval( "bufnr( '{0}', {1} )".format( + Escape( file_name ), + int( create ) ) ) ) def BufferForFile( file_name ): From 80985148e72bb1ef6913664f0f8e73946ce49e03 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Tue, 13 Oct 2020 22:43:52 +0100 Subject: [PATCH 2/2] Add "run to cursor" support We add a 'temporary' option to line breakpionts and try and clear any temporary breakpionts on the line we end up stopping on. This might not be art, but _probably_ works in almost all cases that matter. it's a bit hacky the way we have to push the reason around, but we don't know where we stopped until we actually get the stack trace response and SetCurrentFrame Move temporary breakpionts to match server response Also delete any existing ones when adding a new one and add tests for run-to-cursor. Only continue after we successfully set the breakpoints. This makes it work in go --- README.md | 9 + autoload/vimspector.vim | 107 +++++++--- plugin/vimspector.vim | 15 +- python3/vimspector/breakpoints.py | 84 +++++++- python3/vimspector/code.py | 24 ++- python3/vimspector/debug_session.py | 43 +++- python3/vimspector/stack_trace.py | 13 +- python3/vimspector/utils.py | 4 + .../python/simple_python/.vimspector.json | 9 +- support/test/python/simple_python/make_env.sh | 9 + .../test/python/simple_python/print_env.py | 15 ++ tests/breakpoints.test.vim | 160 ++++++-------- tests/breakpoints_doublewidth.test.vim | 34 +-- tests/language_go.test.vim | 25 +++ tests/lib/autoload/vimspector/test/signs.vim | 2 + tests/mappings.test.vim | 123 +++++++++++ tests/temporary_breakpoints.test.vim | 200 ++++++++++++++++++ tests/testdata/cpp/simple/.vimspector.json | 22 ++ 18 files changed, 693 insertions(+), 205 deletions(-) create mode 100755 support/test/python/simple_python/make_env.sh create mode 100644 support/test/python/simple_python/print_env.py create mode 100644 tests/mappings.test.vim create mode 100644 tests/temporary_breakpoints.test.vim diff --git a/README.md b/README.md index ba72b14..38bd320 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ And a couple of brief demos: - breakpoints (function, line and exception breakpoints) - conditional breakpoints (function, line) - step in/out/over/up, stop, restart +- run to cursor - launch and attach - remote launch, remote attach - locals and globals display @@ -552,6 +553,7 @@ features to set your own mappings. To that end, Vimspector defines the following * `VimspectorStepOver` * `VimspectorStepInto` * `VimspectorStepOut` +* `VimspectorRunToCursor` These map roughly 1-1 with the API functions below. @@ -612,6 +614,7 @@ let g:vimspector_enable_mappings = 'HUMAN' | `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` | | `F9` | Toggle conditional line breakpoint on the current line. | `vimspector#ToggleBreakpoint( { trigger expr, hit count expr } )` | | `F8` | Add a function breakpoint for the expression under cursor | `vimspector#AddFunctionBreakpoint( '' )` | +| `F8` | Run to Cursor | `vimspector#RunToCursor()` | | `F10` | Step Over | `vimspector#StepOver()` | | `F11` | Step Into | `vimspector#StepInto()` | | `F12` | Step out of current function scope | `vimspector#StepOut()` | @@ -710,6 +713,12 @@ You can configure your choices in the `.vimspector.json`. See * Use `vimspector#ClearBreakpoints()` to clear all breakpoints including the memory of exception breakpoint choices. +### Run to Cursor + +* Use `vimspector#RunToCursor` or ``: this creates a temporary + breakpoint on the current line, then continues execution, clearing the + breakpiont when it is hit. + ## Stepping * Step in/out, finish, continue, pause etc. using the WinBar, or mappings. diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index f4d2f58..410fd2d 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -27,45 +27,57 @@ EOF endfunction -let s:enabled = vimspector#internal#state#Reset() +let s:enabled = v:null + +function! s:Initialised() abort + return s:enabled != v:null +endfunction + +function! s:Enabled() abort + if !s:Initialised() + let s:enabled = vimspector#internal#state#Reset() + endif + + return s:enabled +endfunction function! vimspector#Launch() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Start() endfunction function! vimspector#LaunchWithSettings( settings ) abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Start( launch_variables = vim.eval( 'a:settings' ) ) endfunction function! vimspector#Reset() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Reset() endfunction function! vimspector#Restart() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Restart() endfunction function! vimspector#ClearBreakpoints() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ClearBreakpoints() endfunction function! vimspector#ToggleBreakpoint( ... ) abort - if !s:enabled + if !s:Enabled() return endif if a:0 == 0 @@ -77,7 +89,7 @@ function! vimspector#ToggleBreakpoint( ... ) abort endfunction function! vimspector#SetLineBreakpoint( file_name, line_num, ... ) abort - if !s:enabled + if !s:Enabled() return endif if a:0 == 0 @@ -92,7 +104,7 @@ function! vimspector#SetLineBreakpoint( file_name, line_num, ... ) abort endfunction function! vimspector#ClearLineBreakpoint( file_name, line_num ) abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ClearLineBreakpoint( @@ -101,8 +113,18 @@ function! vimspector#ClearLineBreakpoint( file_name, line_num ) abort endfunction +function! vimspector#RunToCursor() abort + if !s:Enabled() + return + endif + py3 _vimspector_session.RunTo( + \ vim.eval( "expand( '%' )" ), + \ int( vim.eval( "line( '.' )" ) ) ) +endfunction + + function! vimspector#AddFunctionBreakpoint( function, ... ) abort - if !s:enabled + if !s:Enabled() return endif if a:0 == 0 @@ -115,70 +137,70 @@ function! vimspector#AddFunctionBreakpoint( function, ... ) abort endfunction function! vimspector#StepOver() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.StepOver() endfunction function! vimspector#StepInto() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.StepInto() endfunction function! vimspector#StepOut() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.StepOut() endfunction function! vimspector#Continue() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Continue() endfunction function! vimspector#Pause() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Pause() endfunction function! vimspector#Stop() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.Stop() endfunction function! vimspector#ExpandVariable() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ExpandVariable() endfunction function! vimspector#DeleteWatch() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.DeleteWatch() endfunction function! vimspector#GoToFrame() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ExpandFrameOrThread() endfunction function! vimspector#AddWatch( ... ) abort - if !s:enabled + if !s:Enabled() return endif if a:0 == 0 @@ -195,7 +217,7 @@ function! vimspector#AddWatch( ... ) abort endfunction function! vimspector#AddWatchPrompt( expr ) abort - if !s:enabled + if !s:Enabled() return endif stopinsert @@ -204,7 +226,7 @@ function! vimspector#AddWatchPrompt( expr ) abort endfunction function! vimspector#Evaluate( expr ) abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ShowOutput( 'Console' ) @@ -212,7 +234,7 @@ function! vimspector#Evaluate( expr ) abort endfunction function! vimspector#EvaluateConsole( expr ) abort - if !s:enabled + if !s:Enabled() return endif stopinsert @@ -221,7 +243,7 @@ function! vimspector#EvaluateConsole( expr ) abort endfunction function! vimspector#ShowOutput( ... ) abort - if !s:enabled + if !s:Enabled() return endif if a:0 == 1 @@ -232,7 +254,7 @@ function! vimspector#ShowOutput( ... ) abort endfunction function! vimspector#ShowOutputInWindow( win_id, category ) abort - if !s:enabled + if !s:Enabled() return endif py3 __import__( 'vimspector', @@ -242,21 +264,21 @@ function! vimspector#ShowOutputInWindow( win_id, category ) abort endfunction function! vimspector#ToggleLog() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ToggleLog() endfunction function! vimspector#ListBreakpoints() abort - if !s:enabled + if !s:Enabled() return endif py3 _vimspector_session.ListBreakpoints() endfunction function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort - if !s:enabled + if !s:Enabled() return endif let buffers = py3eval( '_vimspector_session.GetOutputBuffers() ' @@ -287,7 +309,7 @@ def _vimspector_GetExprCompletions( ArgLead, prev_non_keyword_char ): EOF function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort - if !s:enabled + if !s:Enabled() return endif @@ -415,7 +437,7 @@ function! vimspector#OmniFuncConsole( find_start, query ) abort endfunction function! vimspector#Install( bang, ... ) abort - if !s:enabled + if !s:Enabled() return endif let prefix = vimspector#internal#state#GetAPIPrefix() @@ -427,7 +449,7 @@ function! vimspector#Install( bang, ... ) abort endfunction function! vimspector#CompleteInstall( ArgLead, CmdLine, CursorPos ) abort - if !s:enabled + if !s:Enabled() return endif return py3eval( '"\n".join(' @@ -437,7 +459,7 @@ function! vimspector#CompleteInstall( ArgLead, CmdLine, CursorPos ) abort endfunction function! vimspector#Update( bang, ... ) abort - if !s:enabled + if !s:Enabled() return endif @@ -450,7 +472,7 @@ function! vimspector#Update( bang, ... ) abort endfunction function! vimspector#AbortInstall() abort - if !s:enabled + if !s:Enabled() return endif @@ -459,6 +481,25 @@ function! vimspector#AbortInstall() abort endfunction +function! vimspector#OnBufferCreated( file_name ) abort + if len( a:file_name ) == 0 + return + endif + + " Don't actually load up vimsepctor python in autocommands that trigger + " regularly. We'll only create the session obkect in s:Enabled() + if !s:Initialised() + return + endif + + if !s:Enabled() + return + endif + + py3 _vimspector_session.RefreshSigns( vim.eval( 'a:file_name' ) ) +endfunction + + " Boilerplate {{{ let &cpoptions=s:save_cpo unlet s:save_cpo diff --git a/plugin/vimspector.vim b/plugin/vimspector.vim index b1e9481..2dbbd82 100644 --- a/plugin/vimspector.vim +++ b/plugin/vimspector.vim @@ -62,6 +62,9 @@ nnoremap VimspectorStepInto nnoremap VimspectorStepOut \ :call vimspector#StepOut() +nnoremap VimspectorRunToCursor + \ :call vimspector#RunToCursor() + if s:mappings ==# 'VISUAL_STUDIO' nmap VimspectorContinue nmap VimspectorStop @@ -80,6 +83,7 @@ elseif s:mappings ==# 'HUMAN' nmap VimspectorToggleBreakpoint nmap VimspectorToggleConditionalBreakpoint nmap VimspectorAddFunctionBreakpoint + nmap VimspectorRunToCursor nmap VimspectorStepOver nmap VimspectorStepInto nmap VimspectorStepOut @@ -116,9 +120,14 @@ command! -bar -nargs=0 " Dummy autocommands so that we can call this whenever augroup VimspectorUserAutoCmds - au! - au User VimspectorUICreated silent - au User VimspectorTerminalOpened silent + autocmd! + autocmd User VimspectorUICreated silent + autocmd User VimspectorTerminalOpened silent +augroup END + +augroup Vimspector + autocmd! + autocmd BufNew * call vimspector#OnBufferCreated( expand( '' ) ) augroup END " boilerplate {{{ diff --git a/python3/vimspector/breakpoints.py b/python3/vimspector/breakpoints.py index f5999c3..19075ae 100644 --- a/python3/vimspector/breakpoints.py +++ b/python3/vimspector/breakpoints.py @@ -192,13 +192,13 @@ class ProjectBreakpoints( object ): self.UpdateUI() - def SetLineBreakpoint( self, file_name, line_num, options ): + def SetLineBreakpoint( self, file_name, line_num, options, then = None ): bp, _ = self._FindLineBreakpoint( file_name, line_num ) if bp is not None: bp[ 'options' ] = options return self._PutLineBreakpoint( file_name, line_num, options ) - self.UpdateUI() + self.UpdateUI( then ) def ClearLineBreakpoint( self, file_name, line_num ): @@ -209,6 +209,51 @@ class ProjectBreakpoints( object ): self.UpdateUI() + def ClearTemporaryBreakpoint( self, file_name, line_num ): + bp, index = self._FindLineBreakpoint( file_name, line_num ) + if bp is None: + return + if bp[ 'options' ].get( 'temporary' ): + self._DeleteLineBreakpoint( bp, file_name, index ) + self.UpdateUI() + + + def ClearTemporaryBreakpoints( self ): + for file_name, breakpoints in self._line_breakpoints.items(): + self._line_breakpoints[ file_name ] = list( filter( + lambda bp: not bp[ 'options' ].get( 'temporary' ), + breakpoints ) ) + + + def _UpdateTemporaryBreakpoints( self, breakpoints, temp_idxs ): + # adjust any temporary breakpoints to match the server result + # TODO: Maybe now is the time to ditch the split breakpoints nonesense + for temp_idx, user_bp in temp_idxs: + if temp_idx >= len( breakpoints ): + # Just can't trust servers ? + self._logger.debug( "Server Error - invalid breakpoints list did not " + "contain entry for temporary breakpoint at index " + f"{ temp_idx } i.e. { user_bp }" ) + continue + + bp = breakpoints[ temp_idx ] + + if 'line' not in bp or not bp[ 'verified' ]: + utils.UserMessage( + "Unable to set temporary breakpoint at line " + f"{ user_bp[ 'line' ] } execution will continue...", + persist = True, + error = True ) + + self._logger.debug( f"Updating temporary breakpoint { user_bp } line " + f"{ user_bp[ 'line' ] } to { bp[ 'line' ] }" ) + + # if it was moved, update the user-breakpoint so that we unset it + # again properly + user_bp[ 'line' ] = bp[ 'line' ] + + + def AddFunctionBreakpoint( self, function, options ): self._func_breakpoints.append( { 'state': 'ENABLED', @@ -224,11 +269,13 @@ class ProjectBreakpoints( object ): self.UpdateUI() - def UpdateUI( self ): + def UpdateUI( self, then = None ): if self._connection: - self.SendBreakpoints() + self.SendBreakpoints( then ) else: self._ShowBreakpoints() + if then: + then() def SetBreakpointsHandler( self, handler ): @@ -254,9 +301,12 @@ class ProjectBreakpoints( object ): if awaiting == 0 and doneHandler: doneHandler() - def response_handler( source, msg ): + def response_handler( source, msg, temp_idxs = [] ): if msg: self._breakpoints_handler.AddBreakpoints( source, msg ) + + breakpoints = ( msg.get( 'body' ) or {} ).get( 'breakpoints' ) or [] + self._UpdateTemporaryBreakpoints( breakpoints, temp_idxs ) response_received() @@ -267,9 +317,9 @@ class ProjectBreakpoints( object ): # TODO: add the _configured_breakpoints to line_breakpoints - # TODO: the line numbers might have changed since pressing the F9 key! for file_name, line_breakpoints in self._line_breakpoints.items(): + temp_idxs = [] breakpoints = [] for bp in line_breakpoints: self._SignToLine( file_name, bp ) @@ -283,8 +333,15 @@ class ProjectBreakpoints( object ): dap_bp = {} dap_bp.update( bp[ 'options' ] ) dap_bp.update( { 'line': bp[ 'line' ] } ) + + dap_bp.pop( 'temporary', None ) + + if bp[ 'options' ].get( 'temporary' ): + temp_idxs.append( [ len( breakpoints ), bp ] ) + breakpoints.append( dap_bp ) + source = { 'name': os.path.basename( file_name ), 'path': file_name, @@ -295,7 +352,10 @@ class ProjectBreakpoints( object ): # The source=source here is critical to ensure that we capture each # source in the iteration, rather than ending up passing the same source # to each callback. - lambda msg, source=source: response_handler( source, msg ), + lambda msg, source=source, temp_idxs=temp_idxs: response_handler( + source, + msg, + temp_idxs = temp_idxs ), { 'command': 'setBreakpoints', 'arguments': { @@ -396,6 +456,12 @@ class ProjectBreakpoints( object ): # pay any attention to them anyway. self._exception_breakpoints[ 'exceptionOptions' ] = [] + + def Refresh( self, file_name ): + # TODO: Just this file ? + self._ShowBreakpoints() + + def _ShowBreakpoints( self ): for file_name, line_breakpoints in self._line_breakpoints.items(): for bp in line_breakpoints: @@ -410,7 +476,7 @@ class ProjectBreakpoints( object ): else 'vimspectorBPCond' if 'condition' in bp[ 'options' ] else 'vimspectorBP' ) - if utils.BufferNumberForFile( file_name, False ) > 0: + if utils.BufferExists( file_name ): signs.PlaceSign( bp[ 'sign_id' ], 'VimspectorBP', sign, @@ -422,7 +488,7 @@ class ProjectBreakpoints( object ): if 'sign_id' not in bp: return bp[ 'line' ] - if utils.BufferNumberForFile( file_name, False ) <= 0: + if not utils.BufferExists( file_name ): return bp[ 'line' ] signs = vim.eval( "sign_getplaced( '{}', {} )".format( diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index afd8c03..e1b7f59 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -93,16 +93,12 @@ class CodeView( object ): sign = 'vimspectorPCBP' break - try: + if utils.BufferExists( frame[ 'source' ][ 'path' ] ): signs.PlaceSign( self._signs[ 'vimspectorPC' ], 'VimspectorCode', sign, frame[ 'source' ][ 'path' ], frame[ 'line' ] ) - except vim.error as e: - # Ignore 'invalid buffer name' - if 'E158' not in str( e ): - raise def SetCurrentFrame( self, frame ): @@ -215,6 +211,11 @@ class CodeView( object ): return + def Refresh( self, file_name ): + # TODO: jsut the file ? + self.ShowBreakpoints() + + def _UndisplaySigns( self ): for sign_id in self._signs[ 'breakpoints' ]: signs.UnplaceSign( sign_id, 'VimspectorCode' ) @@ -236,12 +237,13 @@ class CodeView( object ): sign_id = self._next_sign_id self._next_sign_id += 1 self._signs[ 'breakpoints' ].append( sign_id ) - signs.PlaceSign( sign_id, - 'VimspectorCode', - 'vimspectorBP' if breakpoint[ 'verified' ] - else 'vimspectorBPDisabled', - file_name, - breakpoint[ 'line' ] ) + if utils.BufferExists( file_name ): + signs.PlaceSign( sign_id, + 'VimspectorCode', + 'vimspectorBP' if breakpoint[ 'verified' ] + else 'vimspectorBPDisabled', + file_name, + breakpoint[ 'line' ] ) # We need to also check if there's a breakpoint on this PC line and chnge # the PC diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 85f32da..42b6340 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -77,6 +77,7 @@ class DebugSession( object ): self._launch_complete = False self._on_init_complete_handlers = [] self._server_capabilities = {} + self.ClearTemporaryBreakpoints() def Start( self, launch_variables = None ): # We mutate launch_variables, so don't mutate the default argument. @@ -560,6 +561,13 @@ class DebugSession( object ): return response[ 'body' ][ 'targets' ] + def RefreshSigns( self, file_name ): + if self._connection: + self._codeView.Refresh( file_name ) + else: + self._breakpoints.Refresh( file_name ) + + def _SetUpUI( self ): vim.command( 'tab split' ) self._uiTab = vim.current.tabpage @@ -622,7 +630,7 @@ class DebugSession( object ): self.SetCurrentFrame( None ) @RequiresUI() - def SetCurrentFrame( self, frame ): + def SetCurrentFrame( self, frame, reason = '' ): if not frame: self._stackTraceView.Clear() self._variablesView.Clear() @@ -630,11 +638,16 @@ class DebugSession( object ): if not self._codeView.SetCurrentFrame( frame ): return False - if frame: - self._variablesView.SetSyntax( self._codeView.current_syntax ) - self._stackTraceView.SetSyntax( self._codeView.current_syntax ) - self._variablesView.LoadScopes( frame ) - self._variablesView.EvaluateWatches() + # the codeView.SetCurrentFrame already checked the frame was valid and + # countained a valid source + self._variablesView.SetSyntax( self._codeView.current_syntax ) + self._stackTraceView.SetSyntax( self._codeView.current_syntax ) + self._variablesView.LoadScopes( frame ) + self._variablesView.EvaluateWatches() + + if reason == 'stopped': + self._breakpoints.ClearTemporaryBreakpoint( frame[ 'source' ][ 'path' ], + frame[ 'line' ] ) return True @@ -1165,8 +1178,22 @@ class DebugSession( object ): def ToggleBreakpoint( self, options ): return self._breakpoints.ToggleBreakpoint( options ) - def SetLineBreakpoint( self, file_name, line_num, options ): - return self._breakpoints.SetLineBreakpoint( file_name, line_num, options ) + def RunTo( self, file_name, line ): + self.ClearTemporaryBreakpoints() + self.SetLineBreakpoint( file_name, + line, + { 'temporary': True }, + lambda: self.Continue() ) + + + def ClearTemporaryBreakpoints( self ): + return self._breakpoints.ClearTemporaryBreakpoints() + + def SetLineBreakpoint( self, file_name, line_num, options, then = None ): + return self._breakpoints.SetLineBreakpoint( file_name, + line_num, + options, + then ) def ClearLineBreakpoint( self, file_name, line_num ): return self._breakpoints.ClearLineBreakpoint( file_name, line_num ) diff --git a/python3/vimspector/stack_trace.py b/python3/vimspector/stack_trace.py index 8af6536..584370f 100644 --- a/python3/vimspector/stack_trace.py +++ b/python3/vimspector/stack_trace.py @@ -149,12 +149,15 @@ class StackTraceView( object ): self._DrawStackTrace( thread ) - def _LoadStackTrace( self, thread, infer_current_frame ): + def _LoadStackTrace( self, + thread, + infer_current_frame, + reason = '' ): def consume_stacktrace( message ): thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ] if infer_current_frame: for frame in thread[ '_frames' ]: - if self._JumpToFrame( frame ): + if self._JumpToFrame( frame, reason ): break self._DrawThreads() @@ -183,11 +186,11 @@ class StackTraceView( object ): else: self._LoadStackTrace( thread, False ) - def _JumpToFrame( self, frame ): + def _JumpToFrame( self, frame, reason = '' ): def do_jump(): if 'line' in frame and frame[ 'line' ] > 0: self._current_frame = frame - return self._session.SetCurrentFrame( self._current_frame ) + return self._session.SetCurrentFrame( self._current_frame, reason ) return False source = frame.get( 'source' ) or {} @@ -211,7 +214,7 @@ class StackTraceView( object ): if self._current_thread is not None: for thread in self._threads: if thread[ 'id' ] == self._current_thread: - self._LoadStackTrace( thread, True ) + self._LoadStackTrace( thread, True, 'stopped' ) return self.LoadThreads( True ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 4ce387d..66f8d40 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -55,6 +55,10 @@ def BufferForFile( file_name ): return vim.buffers[ BufferNumberForFile( file_name ) ] +def BufferExists( file_name ): + return bool( int ( vim.eval( f"bufexists( '{ Escape( file_name ) }' )" ) ) ) + + def NewEmptyBuffer(): bufnr = int( vim.eval( 'bufadd("")' ) ) Call( 'bufload', bufnr ) diff --git a/support/test/python/simple_python/.vimspector.json b/support/test/python/simple_python/.vimspector.json index 044c669..aba0578 100644 --- a/support/test/python/simple_python/.vimspector.json +++ b/support/test/python/simple_python/.vimspector.json @@ -52,6 +52,11 @@ }, "run - default": { "adapter": "debugpy", + "variables": { + "MAKE_ENV_OUTPUT": { + "shell": "${workspaceRoot}/make_env.sh" + } + }, "configuration": { "request": "launch", "type": "python", @@ -60,8 +65,8 @@ "stopOnEntry#json": "${StopOnEntry:true}", "console": "integratedTerminal", "args#json": "${args:[]}", - "env#json": "${env:{\\}}", - "igored#json#s": "string not json" + "igored#json#s": "string not json", + "env#json": "${MAKE_ENV_OUTPUT}" }, "breakpoints": { "exception": { diff --git a/support/test/python/simple_python/make_env.sh b/support/test/python/simple_python/make_env.sh new file mode 100755 index 0000000..a4da8ca --- /dev/null +++ b/support/test/python/simple_python/make_env.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +cat <<"EOF" +{ + "Something": "Value1", + "SomethingElse": "Value2" +} +EOF + diff --git a/support/test/python/simple_python/print_env.py b/support/test/python/simple_python/print_env.py new file mode 100644 index 0000000..4b88f2d --- /dev/null +++ b/support/test/python/simple_python/print_env.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import os + + +def Main(): + print( os.environ.get( 'Something', 'ERROR' ) ) + print( os.environ.get( 'SomethingElse', 'ERROR' ) ) + + for k, v in os.environ: + print( f'{ k } = "{ v }"' ) + + +Main() + diff --git a/tests/breakpoints.test.vim b/tests/breakpoints.test.vim index f6cafa5..e6a1357 100644 --- a/tests/breakpoints.test.vim +++ b/tests/breakpoints.test.vim @@ -6,38 +6,6 @@ function! ClearDown() call vimspector#test#setup#ClearDown() endfunction -function! SetUp_Test_Mappings_Are_Added_HUMAN() - let g:vimspector_enable_mappings = 'HUMAN' -endfunction - -function! Test_Mappings_Are_Added_HUMAN() - call assert_true( hasmapto( 'vimspector#Continue()' ) ) - call assert_false( hasmapto( 'vimspector#Launch()' ) ) - call assert_true( hasmapto( 'vimspector#Stop()' ) ) - call assert_true( hasmapto( 'vimspector#Restart()' ) ) - call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) - call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) - call assert_true( hasmapto( 'vimspector#StepOver()' ) ) - call assert_true( hasmapto( 'vimspector#StepInto()' ) ) - call assert_true( hasmapto( 'vimspector#StepOut()' ) ) -endfunction - -function! SetUp_Test_Mappings_Are_Added_VISUAL_STUDIO() - let g:vimspector_enable_mappings = 'VISUAL_STUDIO' -endfunction - -function! Test_Mappings_Are_Added_VISUAL_STUDIO() - call assert_true( hasmapto( 'vimspector#Continue()' ) ) - call assert_false( hasmapto( 'vimspector#Launch()' ) ) - call assert_true( hasmapto( 'vimspector#Stop()' ) ) - call assert_true( hasmapto( 'vimspector#Restart()' ) ) - call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) - call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) - call assert_true( hasmapto( 'vimspector#StepOver()' ) ) - call assert_true( hasmapto( 'vimspector#StepInto()' ) ) - call assert_true( hasmapto( 'vimspector#StepOut()' ) ) -endfunction - function! SetUp_Test_Signs_Placed_Using_API_Are_Shown() let g:vimspector_enable_mappings = 'VISUAL_STUDIO' endfunction @@ -78,68 +46,6 @@ function! Test_Signs_Placed_Using_API_Are_Shown() %bwipeout! endfunction -function! SetUp_Test_Use_Mappings_HUMAN() - let g:vimspector_enable_mappings = 'HUMAN' -endfunction - -function! Test_Use_Mappings_HUMAN() - lcd testdata/cpp/simple - edit simple.cpp - call setpos( '.', [ 0, 15, 1 ] ) - - call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) - call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 15 ) - - " Add the breakpoint - call feedkeys( "\", 'xt' ) - call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', - \ 15, - \ 'vimspectorBP', - \ 9 ) - - " Disable the breakpoint - call feedkeys( "\", 'xt' ) - call vimspector#test#signs#AssertSignGroupSingletonAtLine( - \ 'VimspectorBP', - \ 15, - \ 'vimspectorBPDisabled', - \ 9 ) - - " Delete the breakpoint - call feedkeys( "\", 'xt' ) - call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 15 ) - - " Add it again - call feedkeys( "\", 'xt' ) - call vimspector#test#signs#AssertSignGroupSingletonAtLine( - \ 'VimspectorBP', - \ 15, - \ 'vimspectorBP', - \ 9 ) - - " Here we go. Start Debugging - call feedkeys( "\", 'xt' ) - - call assert_equal( 2, len( gettabinfo() ) ) - let cur_tabnr = tabpagenr() - call assert_equal( 5, len( gettabinfo( cur_tabnr )[ 0 ].windows ) ) - - call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) - - " Step - call feedkeys( "\", 'xt' ) - - call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) - call WaitForAssert( {-> - \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cp', 16 ) - \ } ) - - call vimspector#test#setup#Reset() - - lcd - - %bwipeout! -endfunction - function! SetUp_Test_StopAtEntry() let g:vimspector_enable_mappings = 'HUMAN' endfunction @@ -200,8 +106,8 @@ function Test_DisableBreakpointWhileDebugging() \ 16 ) \ } ) - " Add the breakpoint - call feedkeys( "\", 'xt' ) + call setpos( '.', [ 0, 1, 1 ] ) + call vimspector#SetLineBreakpoint( 'simple.cpp', 16 ) call WaitForAssert( {-> \ vimspector#test#signs#AssertSignGroupSingletonAtLine( \ 'VimspectorCode', @@ -211,7 +117,6 @@ function Test_DisableBreakpointWhileDebugging() \ } ) " Run to breakpoint - call setpos( '.', [ 0, 15, 1 ] ) call feedkeys( "\", 'xt' ) call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) call WaitForAssert( {-> @@ -258,6 +163,58 @@ function Test_DisableBreakpointWhileDebugging() %bwipeout! endfunction +function! Test_Add_Breakpoints_In_File_Then_Open() + lcd testdata/cpp/simple + + " Set and clear without file open + call vimspector#SetLineBreakpoint( 'simple.cpp', 16 ) + call vimspector#ClearLineBreakpoint( 'simple.cpp', 16 ) + + " Clear non-set breakpoint + call vimspector#ClearLineBreakpoint( 'simple.cpp', 1 ) + + " Re-add + call vimspector#SetLineBreakpoint( 'simple.cpp', 16 ) + + " Open and expect sign to be added + edit simple.cpp + call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', + \ 16, + \ 'vimspectorBP', + \ 9 ) + call vimspector#LaunchWithSettings( { 'configuration': 'run-to-breakpoint' } ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) + + call vimspector#test#setup#Reset() + lcd - + %bwipeout! +endfunction + +function! Test_Add_Breakpoints_In_NonOpenedFile_RunToBreak() + lcd testdata/cpp/simple + + " add + call vimspector#SetLineBreakpoint( 'simple.cpp', 16 ) + + call vimspector#LaunchWithSettings( { + \ 'configuration': 'run-to-breakpoint-specify-file', + \ 'prog': 'simple' + \ } ) + call WaitFor( {-> bufexists( 'simple.cpp' ) } ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 16 ) + + call vimspector#test#signs#AssertSignAtLine( + \ 'VimspectorCode', + \ 16, + \ 'vimspectorPCBP', + \ 200 ) + + call vimspector#test#setup#Reset() + lcd - + %bwipeout! +endfunction + function! SetUp_Test_Insert_Code_Above_Breakpoint() let g:vimspector_enable_mappings = 'HUMAN' endfunction @@ -308,7 +265,6 @@ function! Test_Insert_Code_Above_Breakpoint() " Delete it call feedkeys( "\", 'xt' ) call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 26 ) - endfunction function! SetUp_Test_Conditional_Line_Breakpoint() @@ -360,8 +316,11 @@ function! Test_Conditional_Line_Breakpoint() \ 'vimspectorBP', \ 9 ) - call setpos( '.', [ 0, 17, 1 ] ) - call vimspector#ToggleBreakpoint( { 'condition': 'argc == 1' } ) + call setpos( '.', [ 0, 1, 1 ] ) + call vimspector#SetLineBreakpoint( + \ 'simple.cpp', + \ 17, + \ { 'condition': 'argc == 1' } ) call vimspector#test#signs#AssertSignGroupSingletonAtLine( \ 'VimspectorBP', \ 17, @@ -724,7 +683,6 @@ function! Test_Custom_Breakpoint_Priority_Partial() unlet! g:vimspector_sign_priority endfunction - function! Test_Add_Line_BP_In_Other_File_While_Debugging() let moo = 'moo.py' let cow = 'cow.py' diff --git a/tests/breakpoints_doublewidth.test.vim b/tests/breakpoints_doublewidth.test.vim index 8ab35bb..cfd4e6c 100644 --- a/tests/breakpoints_doublewidth.test.vim +++ b/tests/breakpoints_doublewidth.test.vim @@ -7,38 +7,6 @@ function! ClearDown() call vimspector#test#setup#ClearDown() endfunction -function! SetUp_Test_Mappings_Are_Added_HUMAN() - let g:vimspector_enable_mappings = 'HUMAN' -endfunction - -function! Test_Mappings_Are_Added_HUMAN() - call assert_true( hasmapto( 'vimspector#Continue()' ) ) - call assert_false( hasmapto( 'vimspector#Launch()' ) ) - call assert_true( hasmapto( 'vimspector#Stop()' ) ) - call assert_true( hasmapto( 'vimspector#Restart()' ) ) - call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) - call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) - call assert_true( hasmapto( 'vimspector#StepOver()' ) ) - call assert_true( hasmapto( 'vimspector#StepInto()' ) ) - call assert_true( hasmapto( 'vimspector#StepOut()' ) ) -endfunction - -function! SetUp_Test_Mappings_Are_Added_VISUAL_STUDIO() - let g:vimspector_enable_mappings = 'VISUAL_STUDIO' -endfunction - -function! Test_Mappings_Are_Added_VISUAL_STUDIO() - call assert_true( hasmapto( 'vimspector#Continue()' ) ) - call assert_false( hasmapto( 'vimspector#Launch()' ) ) - call assert_true( hasmapto( 'vimspector#Stop()' ) ) - call assert_true( hasmapto( 'vimspector#Restart()' ) ) - call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) - call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) - call assert_true( hasmapto( 'vimspector#StepOver()' ) ) - call assert_true( hasmapto( 'vimspector#StepInto()' ) ) - call assert_true( hasmapto( 'vimspector#StepOut()' ) ) -endfunction - function! SetUp_Test_Signs_Placed_Using_API_Are_Shown() let g:vimspector_enable_mappings = 'VISUAL_STUDIO' endfunction @@ -132,7 +100,7 @@ function! Test_Use_Mappings_HUMAN() call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) call WaitForAssert( {-> - \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cp', 16 ) + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 16 ) \ } ) call vimspector#test#setup#Reset() diff --git a/tests/language_go.test.vim b/tests/language_go.test.vim index b6a7da4..200d5d6 100644 --- a/tests/language_go.test.vim +++ b/tests/language_go.test.vim @@ -45,3 +45,28 @@ function! Test_Go_Simple() lcd - %bwipeout! endfunction + + +function! Test_Run_To_Cursor() + let fn='hello-world.go' + lcd ../support/test/go/hello_world + exe 'edit ' . fn + + call vimspector#SetLineBreakpoint( fn, 4 ) + call vimspector#Launch() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 4, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( fn, 4 ) + \ } ) + + call cursor( 5, 1 ) + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 5, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( fn, 5 ) + \ } ) + + call vimspector#test#setup#Reset() + lcd - + %bwipeout! +endfunction diff --git a/tests/lib/autoload/vimspector/test/signs.vim b/tests/lib/autoload/vimspector/test/signs.vim index a6bb698..cf64520 100644 --- a/tests/lib/autoload/vimspector/test/signs.vim +++ b/tests/lib/autoload/vimspector/test/signs.vim @@ -1,6 +1,7 @@ function! vimspector#test#signs#AssertCursorIsAtLineInBuffer( buffer, \ line, \ column ) abort + call WaitFor( {-> bufexists( a:buffer ) } ) call WaitForAssert( {-> \ assert_equal( fnamemodify( a:buffer, ':p' ), \ fnamemodify( bufname( '%' ), ':p' ), @@ -15,6 +16,7 @@ function! vimspector#test#signs#AssertCursorIsAtLineInBuffer( buffer, endfunction function! vimspector#test#signs#AssertPCIsAtLineInBuffer( buffer, line ) abort + call WaitFor( {-> bufexists( a:buffer ) } ) let signs = sign_getplaced( a:buffer, { \ 'group': 'VimspectorCode', \ } ) diff --git a/tests/mappings.test.vim b/tests/mappings.test.vim new file mode 100644 index 0000000..fcd19fe --- /dev/null +++ b/tests/mappings.test.vim @@ -0,0 +1,123 @@ +function! SetUp() + call vimspector#test#setup#SetUpWithMappings( v:none ) +endfunction + +function! ClearDown() + call vimspector#test#setup#ClearDown() +endfunction + +function! SetUp_Test_Mappings_Are_Added_HUMAN() + let g:vimspector_enable_mappings = 'HUMAN' +endfunction + +function! Test_Mappings_Are_Added_HUMAN() + call assert_true( hasmapto( 'vimspector#Continue()' ) ) + call assert_false( hasmapto( 'vimspector#Launch()' ) ) + call assert_true( hasmapto( 'vimspector#Stop()' ) ) + call assert_true( hasmapto( 'vimspector#Restart()' ) ) + call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) + call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) + call assert_true( hasmapto( 'vimspector#StepOver()' ) ) + call assert_true( hasmapto( 'vimspector#StepInto()' ) ) + call assert_true( hasmapto( 'vimspector#StepOut()' ) ) + call assert_true( hasmapto( 'vimspector#RunToCursor()' ) ) +endfunction + +function! SetUp_Test_Mappings_Are_Added_VISUAL_STUDIO() + let g:vimspector_enable_mappings = 'VISUAL_STUDIO' +endfunction + +function! Test_Mappings_Are_Added_VISUAL_STUDIO() + call assert_true( hasmapto( 'vimspector#Continue()' ) ) + call assert_false( hasmapto( 'vimspector#Launch()' ) ) + call assert_true( hasmapto( 'vimspector#Stop()' ) ) + call assert_true( hasmapto( 'vimspector#Restart()' ) ) + call assert_true( hasmapto( 'vimspector#ToggleBreakpoint()' ) ) + call assert_true( hasmapto( 'vimspector#AddFunctionBreakpoint' ) ) + call assert_true( hasmapto( 'vimspector#StepOver()' ) ) + call assert_true( hasmapto( 'vimspector#StepInto()' ) ) + call assert_true( hasmapto( 'vimspector#StepOut()' ) ) +endfunction + +function! SetUp_Test_Use_Mappings_HUMAN() + let g:vimspector_enable_mappings = 'HUMAN' +endfunction + +function! Test_Use_Mappings_HUMAN() + call ThisTestIsFlaky() + lcd testdata/cpp/simple + edit simple.cpp + call setpos( '.', [ 0, 15, 1 ] ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 15 ) + + " Add the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', + \ 15, + \ 'vimspectorBP', + \ 9 ) + + " Disable the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 15, + \ 'vimspectorBPDisabled', + \ 9 ) + + " Delete the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 15 ) + + " Add and clear using API + call vimspector#SetLineBreakpoint( 'simple.cpp', 15 ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', + \ 15, + \ 'vimspectorBP', + \ 9 ) + + call vimspector#ClearLineBreakpoint( 'simple.cpp', 15 ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 15 ) + + " Add it again + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 15, + \ 'vimspectorBP', + \ 9 ) + + " Here we go. Start Debugging + call feedkeys( "\", 'xt' ) + + call assert_equal( 2, len( gettabinfo() ) ) + let cur_tabnr = tabpagenr() + call assert_equal( 5, len( gettabinfo( cur_tabnr )[ 0 ].windows ) ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) + + " Step + call feedkeys( "\", 'xt' ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 16 ) + \ } ) + + " Run to cursor + call cursor( 9, 1 ) + call feedkeys( "\\\", 'xt' ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 9 ) + \ } ) + + + call vimspector#test#setup#Reset() + + lcd - + %bwipeout! +endfunction + diff --git a/tests/temporary_breakpoints.test.vim b/tests/temporary_breakpoints.test.vim new file mode 100644 index 0000000..7bcad55 --- /dev/null +++ b/tests/temporary_breakpoints.test.vim @@ -0,0 +1,200 @@ +function! SetUp() + call vimspector#test#setup#SetUpWithMappings( v:none ) +endfunction + +function! ClearDown() + call vimspector#test#setup#ClearDown() +endfunction + +function s:Start() + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call vimspector#Launch() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 1, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 1 ) + call vimspector#Continue() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 13, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 13 ) +endfunction + +function Test_Run_To_Cursor_Simple() + " Run to a position that will certainly be executed + lcd ../support/test/python/multiple_files + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call s:Start() + + call cursor( 8, 27 ) + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 8, 27 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 8 ) + \ } ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 9, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 9 ) + " Check there is no breakpoint set on line 8 + call WaitForAssert( {-> + \ vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorCode', 8 ) + \ } ) + call vimspector#test#setup#Reset() + lcd - + %bwipe! +endfunction + +function Test_Run_To_Cursor_On_NonBreaking_Line() + " Run to a position that will certainly be executed, but is not a real line + lcd ../support/test/python/multiple_files + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call s:Start() + + call cursor( 7, 1 ) + " Interestingly, debugpy moves the breakpoint to the previous line, which is + " kinda annoying + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 6, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 6 ) + \ } ) + call vimspector#StepOver() + " It's a loop, so we go up a line + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 5, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 5 ) + + " Check there is no breakpoint set on lines 7 and 6: + " 7 - where we put the 'temporary' breakpoint + " 6 - where it got placed + " + " FIXME: This is broken, we don't _know_ that the breakpoint that was hit was + " the temporary one, and there's no way to know. + " + " I wonder if the relocated breakpoint can be matched with the _original_ + " breakpoint + call WaitForAssert( {-> + \ vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorCode', 7 ) + \ } ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorCode', 6 ) + \ } ) + call vimspector#test#setup#Reset() + lcd - + %bwipe! +endfunction + +function Test_Run_To_Cursor_Different_File() + " Run into a different file + " Run to a position that will certainly be executed, but is not a real line + lcd ../support/test/python/multiple_files + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call s:Start() + + edit cow.py + call cursor( 2, 1 ) + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'cow.py', 2, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'cow.py', 2 ) + \ } ) + + bu moo.py + call cursor( 9, 12 ) + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 9, 12 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 9 ) + \ } ) + + call vimspector#test#setup#Reset() + lcd - + %bwipe! +endfunction + +function Test_Run_To_Cursor_Hit_Another_Breakpoint() + " Run to cursor, but hit a non-temporary breakpoint + lcd ../support/test/python/multiple_files + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call s:Start() + + call vimspector#SetLineBreakpoint( 'moo.py', 5 ) + call cursor( 6, 1 ) + + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 5, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 5 ) + \ } ) + + " The temporary breakpoint is still there + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorCode', + \ 6, + \ 'vimspectorBP', + \ 9 ) + + call vimspector#ClearLineBreakpoint( 'moo.py', 5 ) + + call cursor( 8, 1 ) + call vimspector#RunToCursor() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 8, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 8 ) + \ } ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorCode', 6 ) + \ } ) + + call vimspector#test#setup#Reset() + lcd - + %bwipe! +endfunction + +function! Test_InvalidBreakpoint() + " Run to cursor, but hit a non-temporary breakpoint + lcd ../support/test/python/multiple_files + call vimspector#SetLineBreakpoint( 'moo.py', 13 ) + call s:Start() + + call vimspector#SetLineBreakpoint( 'moo.py', 9 ) + + edit .vimspector.json + call cursor( 1, 1 ) + call vimspector#RunToCursor() + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 9, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 9 ) + \ } ) + + call vimspector#test#setup#Reset() + lcd - + %bwipe! +endfunction + +function! Test_StartDebuggingWithRunToCursor() + lcd ../support/test/python/multiple_files + edit moo.py + call cursor( 9, 1 ) + call vimspector#RunToCursor() + " Stop on entry is still hit + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 1, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 1 ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorCode', + \ 9, + \ 'vimspectorBP', + \ 9 ) + + call vimspector#Continue() + " Runs to cursor + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 9, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 9 ) + + call vimspector#StepOver() + " And claers the temp breakpoint + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'moo.py', 8, 1 ) + call vimspector#test#signs#AssertPCIsAtLineInBuffer( 'moo.py', 8 ) + + call WaitForAssert( {-> + \ vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorCode', 9 ) + \ } ) + + lcd - +endfunction diff --git a/tests/testdata/cpp/simple/.vimspector.json b/tests/testdata/cpp/simple/.vimspector.json index 176a44d..0dca061 100644 --- a/tests/testdata/cpp/simple/.vimspector.json +++ b/tests/testdata/cpp/simple/.vimspector.json @@ -46,6 +46,28 @@ } } }, + "run-to-breakpoint-specify-file": { + "adapter": "vscode-cpptools", + "configuration": { + "request": "launch", + "program": "${workspaceRoot}/${prog}", + "cwd": "${workspaceRoot}", + "externalConsole": false, + "stopAtEntry": false, + "stopOnEntry": false, + "MImode": "${VIMSPECTOR_MIMODE}" + }, + "breakpoints": { + "exception": { + "cpp_catch": "", + "cpp_throw": "", + "objc_catch": "", + "objc_throw": "", + "swift_catch": "", + "swift_throw": "" + } + } + }, "calculate-some-variable": { "adapter": "vscode-cpptools", "variables": {