diff --git a/README.md b/README.md index e6b63e4..4f29955 100644 --- a/README.md +++ b/README.md @@ -517,7 +517,7 @@ let g:vimspector_enable_mappings = 'HUMAN' | `F4` | Restart debugging with the same configuration. | `vimspector#Restart()` | | `F6` | Pause debugee. | `vimspector#Pause()` | | `F9` | Toggle line breakpoint on the current line. | `vimspector#ToggleBreakpoint()` | -| `F9` | Toggle conditional line breakpoint on the current line. | `vimspector#ToggleBreakpoint( {condition, hit condition } )` | +| `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( '' )` | | `F10` | Step Over | `vimspector#StepOver()` | | `F11` | Step Into | `vimspector#StepInto()` | @@ -530,6 +530,8 @@ let g:vimspector_enable_mappings = 'HUMAN' * Create `vimspector.json`. See [below](#supported-languages). * `:call vimspector#Launch()` and select a configuration. +![debug session](https://puremourning.github.io/vimspector-web/img/vimspector-overview.png) + ### Launch with options To launch a specific debug configuration, or specify [replacement @@ -562,16 +564,35 @@ debugger](#java---partially-supported) ## Breakpoints -* Use `vimspector#ToggleBreakpoint([ { 'condition': '' } ])` +* Use `vimspector#ToggleBreakpoint([ { 'condition': '' } ])` to set/disable/delete a line breakpoint, with optional condition. -* Use `vimspector#AddFunctionBreakpoint( '' [, { 'condition': '' } ] )` +* Use `vimspector#AddFunctionBreakpoint( '' [, { 'condition': '' } ] )` to add a function breakpoint with optional condition. +Both of these functions take a single optional argument which is a dictionary of +options. The dictionary can have the following keys: + +* `condition`: An optional expression evaluated to deterimie if the breakpoint + should fire. Not supported by all debug adapters. For example, to break when + `abc` is `10`, enter something like `abc == 10`, depending on the language. +* `hitCondition`: An optional expression evaluated to determine a number of + times the breakpoint should be ignored. Should (probablty?) not be used in + combination with `condition`. Not supported by all debug adapters. For + example, to break on the 3rd time hitting this line, enter `3`. + +In both cases, the expression is evaluated by the debugger, so should be in +whatever dialect the debugger understands when evaluating expressions. + +When using the `` mapping, the user is prompted to enter these +expressions in a command line (with history). + ## Stepping -* Step in/out, finish, continue, pause etc. using the WinBar. +* Step in/out, finish, continue, pause etc. using the WinBar, or mappings. * If you really want to, the API is `vimspector#StepInto()` etc. +![code window](https://puremourning.github.io/vimspector-web/img/vimspector-code-window.png) + ## Variables and scopes * Current scope shows values of locals. @@ -579,6 +600,8 @@ debugger](#java---partially-supported) * When changing the stack frame the locals window updates. * While paused, hover to see values +![locals window](https://puremourning.github.io/vimspector-web/img/vimspector-locals-window.png) + ## Watches The watches window is a prompt buffer, where that's available. Enter insert mode @@ -591,11 +614,15 @@ to add a new watch expression. * Expand result with ``. * Delete with ``. +![watch window](https://puremourning.github.io/vimspector-web/img/vimspector-watch-window.png) + ## Stack Traces * In the threads window, use `` to expand/collapse. * Use `` on a stack frame to jump to it. +![stack trace](https://puremourning.github.io/vimspector-web/img/vimspector-callstack-window.png) + ## Program Output * In the outputs window use the WinBar to select the output channel. @@ -604,6 +631,8 @@ to add a new watch expression. * The debugee prints to the stdout channel. * Other channels may be useful for debugging. +![output window](https://puremourning.github.io/vimspector-web/img/vimspector-output-window.png) + ### Console The console window is a prompt buffer, where that's available, and can be used diff --git a/plugin/vimspector.vim b/plugin/vimspector.vim index 6909dab..ed61609 100644 --- a/plugin/vimspector.vim +++ b/plugin/vimspector.vim @@ -45,8 +45,8 @@ nnoremap VimspectorToggleBreakpoint \ :call vimspector#ToggleBreakpoint() nnoremap VimspectorToggleConditionalBreakpoint \ :call vimspector#ToggleBreakpoint( - \ { 'condition': input( 'Enter condition: ' ), - \ 'hitCondition': input( 'Enter hit condition: ' ) } + \ { 'condition': input( 'Enter condition expression: ' ), + \ 'hitCondition': input( 'Enter hit count expression: ' ) } \ ) nnoremap VimspectorAddFunctionBreakpoint \ :call vimspector#AddFunctionBreakpoint( expand( '' ) ) diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index e86b23f..e1a21ce 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -313,7 +313,7 @@ class VariablesView( object ): if result_str is None: result_str = 'null' - line = '{0}{1} Result: {2} '.format( ' ' * indent, icon, result_str ) + line = '{0}{1} Result: {2}'.format( ' ' * indent, icon, result_str ) line = utils.AppendToBuffer( self._watch.win.buffer, line.split( '\n' ) ) self._watch.lines[ line ] = result diff --git a/tests/breakpoints.test.vim b/tests/breakpoints.test.vim index dd00840..07c008e 100644 --- a/tests/breakpoints.test.vim +++ b/tests/breakpoints.test.vim @@ -295,3 +295,158 @@ function! Test_Insert_Code_Above_Breakpoint() call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 26 ) endfunction + +function! SetUp_Test_Conditional_Line_Breakpoint() + let g:vimspector_enable_mappings = 'HUMAN' +endfunction + +function! Test_Conditional_Line_Breakpoint() + lcd testdata/cpp/simple + edit simple.cpp + call setpos( '.', [ 0, 16, 1 ] ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 ) + + " Add the conditional breakpoint + call feedkeys( "\\\argc==0\\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', + \ 16, + \ 'vimspectorBPCond' ) + + " Disable the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 16, + \ 'vimspectorBPDisabled' ) + + " Delete the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 ) + + " Add breakpoint using API: + " - on line 16 condition which doesn't match + " - then an unconditional one on line 9, unconditional + " - then on line 17, condition which matches + call vimspector#ToggleBreakpoint( { 'condition': 'argc == 0' } ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 16, + \ 'vimspectorBPCond' ) + call setpos( '.', [ 0, 9, 1 ] ) + call vimspector#ToggleBreakpoint() + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 9, + \ 'vimspectorBP' ) + + call setpos( '.', [ 0, 17, 1 ] ) + call vimspector#ToggleBreakpoint( { 'condition': 'argc == 1' } ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 17, + \ 'vimspectorBPCond' ) + + call setpos( '.', [ 0, 1, 1 ] ) + + " Start debugging + call vimspector#Continue() + " break on main + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) + + " Ignore non-matching on line 16, break on line 9 + call vimspector#Continue() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 ) + + " Condition matches on line 17 + call vimspector#Continue() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 17, 1 ) + + call vimspector#test#setup#Reset() + + lcd - + %bwipeout! +endfunction + +function! SetUp_Test_Conditional_Line_Breakpoint_Hit() + let g:vimspector_enable_mappings = 'HUMAN' +endfunction + +function! Test_Conditional_Line_Breakpoint_Hit() + let fn = '../support/test/python/simple_python/main.py' + exe 'edit' fn + call setpos( '.', [ 0, 14, 1 ] ) + + " Add the conditional breakpoint (3 times) + call feedkeys( "\\\\3\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( + \ 'VimspectorBP', + \ 14, + \ 'vimspectorBPCond' ) + + call vimspector#LaunchWithSettings( { 'configuration': 'run' } ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 14, 1 ) + + " difficult to check if we really did run 3 times, so just use the watch + " window (also, tests the watch window!) + call vimspector#AddWatch( 'i' ) + call WaitForAssert( {-> + \ assert_equal( [ ' - Result: 2' ], + \ getbufline( 'vimspector.Watches', '$' ) ) + \ } ) + + + call vimspector#test#setup#Reset() + %bwipeout! +endfunction + +function! Test_Function_Breakpoint() + lcd testdata/cpp/simple + edit simple.cpp + call vimspector#AddFunctionBreakpoint( 'foo' ) + call vimspector#Launch() + " break on main + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) + call vimspector#Continue() + " break on func + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 ) + call vimspector#test#setup#Reset() + %bwipeout! +endfunction + +function! Test_Function_Breakpoint_Condition() + lcd testdata/cpp/simple + edit simple.cpp + call vimspector#AddFunctionBreakpoint( 'foo', { 'condition': '1' } ) + call vimspector#Launch() + " break on main + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) + call vimspector#Continue() + " break on func + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 ) + call vimspector#test#setup#Reset() + %bwipeout! +endfunction + +" Can't find an adapter that supports conditional function breakpoints which are +" probably pretty niche anyway +" +" function! Test_Function_Breakpoint_Condition_False() +" lcd testdata/cpp/simple +" edit simple.cpp +" +" call vimspector#AddFunctionBreakpoint( 'foo', { 'condition': '0' } ) +" call setpos( '.', [ 0, 17, 1 ] ) +" call vimspector#ToggleBreakpoint() +" call vimspector#Launch() +" " break on main +" call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 15, 1 ) +" call vimspector#Continue() +" +" " doesn't break in func, break on line 17 +" call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 17, 1 ) +" call vimspector#test#setup#Reset() +" %bwipeout! +" throw "xfail cpptools doesn't seem to honour conditions on function bps" +" endfunction diff --git a/tests/lib/run_test.vim b/tests/lib/run_test.vim index a5c2f39..75d9a91 100644 --- a/tests/lib/run_test.vim +++ b/tests/lib/run_test.vim @@ -32,11 +32,12 @@ " call ch_log( ",,,message..." ) " Then view it in 'debuglog' -" Let a test take up to 1 minute +" Let a test take up to 1 minute, unless debugging let s:single_test_timeout = 60000 " Restrict the runtimepath to the exact minimum needed for testing -set runtimepath=$PWD/lib,$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after +let &rtp = getcwd() . '/lib' +set runtimepath+=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after if has('packages') let &packpath = &runtimepath endif @@ -70,6 +71,10 @@ func s:TestFailed() endfunc func! Abort( timer_id ) + if exists( '&debugfunc' ) && &debugfunc != '' + return + endif + call assert_report( 'Test timed out!!!' ) qa! endfunc @@ -152,6 +157,26 @@ func RunTheTest(test) \ 'SKIPPED ' . a:test \ . ': ' \ . substitute(v:exception, '^\S*\s\+', '', '')) + catch /^\cxfail/ + if len( v:errors ) == 0 + call add(v:errors, + \ 'Expected failure but no error in ' . a:test + \ . ': ' + \ . v:exception + \ . ' @ ' + \ . g:testpath + \ . ':' + \ . v:throwpoint) + + call s:TestFailed() + else + let v:errors = [] + call add(s:messages, ' XFAIL' ) + call add(s:skipped, + \ 'XFAIL ' . a:test + \ . ': ' + \ . substitute(v:exception, '^\S*\s\+', '', '')) + endif catch call add(v:errors, \ 'Caught exception in ' . a:test