From 733843a6d47ef6f17166d59e6f50d3abc66f9a9b Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Sep 2020 17:46:06 +0100 Subject: [PATCH 01/11] Support completion for console and watches. Add omnifunc for prompt buffers This synchronous completion can be used with any completion system including built-in CTRL-X CTRL-O. The filetype of the prompt buffers is set to VimspectorPrompt so that it can be identified by completion systems. For example, this works well with YCM: let g:ycm_semantic_triggers = { \ 'VimspectorPrompt': [ '.', '->', ':', '<' ] \ } --- autoload/vimspector.vim | 103 +++++++++++++++++++++++++++- python3/vimspector/debug_session.py | 3 +- python3/vimspector/output.py | 3 +- python3/vimspector/utils.py | 9 ++- python3/vimspector/variables.py | 3 +- 5 files changed, 113 insertions(+), 8 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 302b34d..df0b6a9 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -232,17 +232,114 @@ function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort return join( buffers, "\n" ) endfunction +py3 < s:latest_completion_request.start_pos + " fix up the text (insert anything that is already present in the line + " that would be erased by the fixed-up earlier start position) + " + " both start_pos and item.start are 1-based + let item.text = s:latest_completion_request.text[ + \ s:latest_completion_request.start_pos + pfxlen - 1 : + \ item.start + pfxlen - 1 ] . item.text + endif + + call add( items, { 'word': item.text, + \ 'abbr': item.label, + \ 'menu': get( item, 'type', '' ), + \ 'icase': 1, + \ } ) + endfor + let s:latest_completion_request = {} + return { 'words': items, 'refresh': 'always' } + endif +endfunction + +function! vimspector#OmniFuncWatch( find_start, query ) abort + return vimspector#CompleteFuncSync( 'Expression: ', a:find_start, a:query ) +endfunction + +function! vimspector#OmniFuncConsole( find_start, query ) abort + return vimspector#CompleteFuncSync( '> ', a:find_start, a:query ) +endfunction + function! vimspector#Install( bang, ... ) abort if !s:enabled return diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 5a542c6..6a40294 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -554,8 +554,7 @@ class DebugSession( object ): # TODO: # - start / length # - sortText - return [ i.get( 'text' ) or i[ 'label' ] - for i in response[ 'body' ][ 'targets' ] ] + return response[ 'body' ][ 'targets' ] def _SetUpUI( self ): diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index 90df09d..71c81e9 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -192,7 +192,8 @@ class OutputView( object ): utils.SetUpPromptBuffer( tab_buffer.buf, name, '> ', - 'vimspector#EvaluateConsole' ) + 'vimspector#EvaluateConsole', + 'vimspector#OmniFuncConsole' ) else: utils.SetUpHiddenBuffer( tab_buffer.buf, name ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 17c8166..2874635 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -135,7 +135,7 @@ def SetUpHiddenBuffer( buf, name ): buf.name = name -def SetUpPromptBuffer( buf, name, prompt, callback ): +def SetUpPromptBuffer( buf, name, prompt, callback, omnifunc ): # This feature is _super_ new, so only enable when available if not Exists( '*prompt_setprompt' ): return SetUpHiddenBuffer( buf, name ) @@ -148,6 +148,7 @@ def SetUpPromptBuffer( buf, name, prompt, callback ): buf.options[ 'buflisted' ] = False buf.options[ 'bufhidden' ] = 'hide' buf.options[ 'textwidth' ] = 0 + buf.options[ 'omnifunc' ] = omnifunc buf.name = name vim.eval( "prompt_setprompt( {0}, '{1}' )".format( buf.number, @@ -156,6 +157,12 @@ def SetUpPromptBuffer( buf, name, prompt, callback ): buf.number, Escape( callback ) ) ) + # This serves a few purposes, mainly to ensure that completion systems have + # something to work with. In particular it makes YCM use its identifier engine + # and you can config ycm to trigger semantic (annoyingly, synchronously) using + # some let g:ycm_auto_trggier + Call( 'setbufvar', buf.number, '&filetype', 'VimspectorPrompt' ) + def SetUpUIWindow( win ): win.options[ 'wrap' ] = False diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index d82090a..5694e46 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -147,7 +147,8 @@ class VariablesView( object ): utils.SetUpPromptBuffer( self._watch.buf, 'vimspector.Watches', 'Expression: ', - 'vimspector#AddWatchPrompt' ) + 'vimspector#AddWatchPrompt', + 'vimspector#OmniFuncWatch' ) with utils.LetCurrentWindow( watches_win ): vim.command( 'nnoremap :call vimspector#ExpandVariable()' ) From 97bdb0d0cc35f7508fb3ce07a88f302b813a11e1 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Sep 2020 22:09:22 +0100 Subject: [PATCH 02/11] Show launch failure reason in the splash --- autoload/vimspector/internal/neopopup.vim | 47 +++++++++++++++-------- python3/vimspector/debug_session.py | 17 +++++++- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/autoload/vimspector/internal/neopopup.vim b/autoload/vimspector/internal/neopopup.vim index 148107c..fe5fe05 100644 --- a/autoload/vimspector/internal/neopopup.vim +++ b/autoload/vimspector/internal/neopopup.vim @@ -26,24 +26,39 @@ set cpoptions&vim let s:db = {} let s:next_id = 0 + +function! s:MessageToList( message ) abort + if type( a:message ) == type( [] ) + let message = a:message + else + let message = [ a:message ] + endif + return message +endfunction + +function! s:GetSplashConfig( message ) abort + let l = max( map( a:message, 'len( v:val )' ) ) + let h = len( a:message ) + + return { 'relative': 'editor', + \ 'width': l, + \ 'height': h, + \ 'col': ( &columns / 2 ) - ( l / 2 ), + \ 'row': ( &lines / 2 ) - h / 2, + \ 'anchor': 'NW', + \ 'style': 'minimal', + \ 'focusable': v:false, + \ } +endfunction + function! vimspector#internal#neopopup#DisplaySplash( message ) abort + let message = s:MessageToList( a:message ) let buf = nvim_create_buf(v:false, v:true) - call nvim_buf_set_lines(buf, 0, -1, v:true, [ a:message ] ) + call nvim_buf_set_lines(buf, 0, -1, v:true, message ) - let l = len( a:message ) - - let opts = { - \ 'relative': 'editor', - \ 'width': l, - \ 'height': 1, - \ 'col': ( &columns / 2 ) - ( l / 2 ), - \ 'row': &lines / 2, - \ 'anchor': 'NW', - \ 'style': 'minimal', - \ 'focusable': v:false, - \ } - let win = nvim_open_win(buf, 0, opts) + let win = nvim_open_win(buf, 0, s:GetSplashConfig( message ) ) call nvim_win_set_option(win, 'wrap', v:false) + call nvim_win_set_option(win, 'colorcolumn', '') let id = s:next_id let s:next_id += 1 @@ -53,7 +68,9 @@ endfunction function! vimspector#internal#neopopup#UpdateSplash( id, message ) abort let splash = s:db[ a:id ] - call nvim_buf_set_lines(splash.buf, 0, -1, v:true, [ a:message ] ) + let message = s:MessageToList( a:message ) + call nvim_buf_set_lines( splash.buf, 0, -1, v:true, message ) + call nvim_win_set_config( splash.win, s:GetSplashConfig( message ) ) return a:id endfunction diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 6a40294..3956d7f 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -940,13 +940,26 @@ class DebugSession( object ): if 'name' not in launch_config: launch_config[ 'name' ] = 'test' + def failure_handler( reason, msg ): + text = [ + 'Launch Failed', + '', + reason, + '', + 'Use :VimspectorReset to close' + ] + self._splash_screen = utils.DisplaySplash( self._api_prefix, + self._splash_screen, + text ) + + self._connection.DoRequest( lambda msg: self._OnLaunchComplete(), { 'command': launch_config[ 'request' ], 'arguments': launch_config - } - ) + }, + failure_handler ) def _OnLaunchComplete( self ): From 3aa949431e6e114e40d93f52db401cd0017e88fc Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Sep 2020 22:09:59 +0100 Subject: [PATCH 03/11] Upgrade chrome debugger to 4.12.10 --- python3/vimspector/gadgets.py | 8 ++++---- support/test/chrome/run_server | 3 +++ support/test/chrome/www/js/test.js | 9 ++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100755 support/test/chrome/run_server diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index 7e1d619..21073f1 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -407,14 +407,14 @@ GADGETS = { 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' 'publishers/msjsdiag/vsextensions/' 'debugger-for-chrome/${version}/vspackage', - 'target': 'msjsdiag.debugger-for-chrome-4.12.0.vsix.gz', + 'target': 'msjsdiag.debugger-for-chrome-4.12.10.vsix.gz', 'format': 'zip.gz', }, 'all': { - 'version': '4.12.0', - 'file_name': 'msjsdiag.debugger-for-chrome-4.12.0.vsix', + 'version': '4.12.10', + 'file_name': 'msjsdiag.debugger-for-chrome-4.12.10.vsix', 'checksum': - '0df2fe96d059a002ebb0936b0003e6569e5a5c35260dc3791e1657d27d82ccf5' + '' }, 'adapters': { 'chrome': { diff --git a/support/test/chrome/run_server b/support/test/chrome/run_server new file mode 100755 index 0000000..9e5b569 --- /dev/null +++ b/support/test/chrome/run_server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +php -S localhost:1234 -t www diff --git a/support/test/chrome/www/js/test.js b/support/test/chrome/www/js/test.js index 273804e..9c1c4a1 100644 --- a/support/test/chrome/www/js/test.js +++ b/support/test/chrome/www/js/test.js @@ -6,5 +6,12 @@ $( document ).ready( function() { return msg; }; - alert( 'test: ' + getMessage() ); + var obj = { + test: getMessage(), + toast: function() { return 'egg'; }, + spam: 'ham' + }; + + alert( 'test: ' + obj.test ); + alert( 'toast: ' + obj.toast() ); } ); From 2710ee2bfa2387c7f62f1fc8847a486827b07db8 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Sep 2020 23:44:18 +0100 Subject: [PATCH 04/11] When the start parameter is missing, the behabiour is arbitary It seems that the behaviour of the start parameter being missing is server (or perhaps a specific client) dependent. The specification clearely says that it should be inserted at the column of the original request, but the servers clearly expect either for that column to be the beginning of an identifier or for the client to ignore the spec and request from that position anyway. Reading the VSCode code, we see that the 'word' before the cursor is guessed, and if only if BOTH 'start' AND 'length' are supplied, then they are used to determine where insertion starts, otherwise the current 'word' is used. Unclear what 'word' means in the specific contexts, but we're relying on iskeyword. --- autoload/vimspector.vim | 51 ++++++++++++++++++++++++------ support/test/node/simple/simple.js | 9 +++++- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index df0b6a9..f7486f6 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -19,6 +19,13 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} +function! s:Debug( ... ) + py3 < len( a:query ) + call s:Debug( 'Rejecting %s, length is greater than %s', + \ item, + \ len( a:query ) ) + continue + endif + call add( items, { 'word': item.text, \ 'abbr': item.label, \ 'menu': get( item, 'type', '' ), @@ -328,6 +359,8 @@ function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort \ } ) endfor let s:latest_completion_request = {} + + call s:Debug( 'Items: %s', items ) return { 'words': items, 'refresh': 'always' } endif endfunction diff --git a/support/test/node/simple/simple.js b/support/test/node/simple/simple.js index fc6e558..77b8e32 100644 --- a/support/test/node/simple/simple.js +++ b/support/test/node/simple/simple.js @@ -1,3 +1,10 @@ var msg = 'Hello, world!' -console.log( "OK stuff happened" ) +var obj = { + test: 'testing', + toast: function() { + return 'toasty' . this.test; + } +} + +console.log( "OK stuff happened " + obj.toast() ) From 3a79ce9ab7acb4fc37ff7ffa8b4bd3bf9e24771d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Sep 2020 23:51:44 +0100 Subject: [PATCH 05/11] Add a bash test script --- support/test/bash/test_script | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 support/test/bash/test_script diff --git a/support/test/bash/test_script b/support/test/bash/test_script new file mode 100644 index 0000000..4e27bd9 --- /dev/null +++ b/support/test/bash/test_script @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +function Test() { + echo $1 +} + +for i in "$@"; do + Test $i +done From 0867edd81c8ad95f64ca8c1f2427217f1a4e3db2 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 4 Sep 2020 00:37:39 +0100 Subject: [PATCH 06/11] FixUp: Correct return values from omnifunc --- autoload/vimspector.vim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index f7486f6..a25e13f 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -266,14 +266,17 @@ let s:latest_completion_request = {} function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort if py3eval( 'not _vimspector_session' ) - return [] + if a:find_start + return -3 + endif + return v:none endif if a:find_start " We're busy if !empty( s:latest_completion_request ) - return -1 + return -3 endif let line = getline( line( '.' ) )[ len( a:prompt ) : ] From 710cffe2da0baac57c97fff727c2e0c21c5ffba5 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 4 Sep 2020 00:48:40 +0100 Subject: [PATCH 07/11] update docs --- README.md | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 674bb0b..61422aa 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,11 @@ For a tutorial and usage overview, take a look at the * [Stepping](#stepping) * [Variables and scopes](#variables-and-scopes) * [Watches](#watches) + * [Watch autocompletion](#watch-autocompletion) * [Stack Traces](#stack-traces) * [Program Output](#program-output) * [Console](#console) + * [Console autocompletion](#console-autocompletion) * [Log View](#log-view) * [Closing debugger](#closing-debugger) * [Debug adapter configuration](#debug-adapter-configuration) @@ -77,7 +79,7 @@ For a tutorial and usage overview, take a look at the * [License](#license) * [Sponsorship](#sponsorship) - + @@ -109,10 +111,10 @@ And a couple of brief demos: - launch and attach - remote launch, remote attach - locals and globals display -- watch expressions +- watch expressions with autocompletion - call stack display and navigation - variable value display hover -- interactive debug console +- interactive debug console with autocompletion - launch debugee within Vim's embedded terminal - logging/stdout display - simple stable API for custom tooling (e.g. integrate with language server) @@ -742,6 +744,22 @@ to add a new watch expression. The watches are represented by the buffer `vimspector.StackTrace`. +### Watch autocompletion + +The watch prompt buffer has its `omnifunc` set to a function that will +calcualte completion for the current expression. This is trivailly used with +`` (see `:help ins-completion`), or integrated with your +favourite completion system. The filetype in the buffer is set to +`VimspectorPrompt`. + +For YouCompleteMe, the following config works well: + +```viml +let g:ycm_semantic_triggers = { + \ 'VimspectorPrompt': [ '.', '->', ':', '<' ] +} +``` + ## Stack Traces * In the threads window, use `` to expand/collapse. @@ -782,6 +800,22 @@ NOTE: See also [Watches](#watches) above. If the output window is closed, a new one can be opened with `:VimspectorShowOutput Console`. +### Console autocompletion + +The console prompt buffer has its `omnifunc` set to a function that will +calcualte completion for the current command/expression. This is trivailly used +with `` (see `:help ins-completion`), or integrated with your +favourite completion system. The filetype in the buffer is set to +`VimspectorPrompt`. + +For YouCompleteMe, the following config works well: + +```viml +let g:ycm_semantic_triggers = { + \ 'VimspectorPrompt': [ '.', '->', ':', '<' ] +} +``` + ### Log View The Vimspector log file contains a full trace of the communication between From 1ace7b648ead7d7890e5c0794c813bfd34ea08db Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 4 Sep 2020 00:49:37 +0100 Subject: [PATCH 08/11] FixUp: vint --- autoload/vimspector.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index a25e13f..b853681 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -19,7 +19,7 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} -function! s:Debug( ... ) +function! s:Debug( ... ) abort py3 < Date: Fri, 4 Sep 2020 00:52:36 +0100 Subject: [PATCH 09/11] Remove slow debugging code --- autoload/vimspector.vim | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index b853681..1a141d6 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -324,13 +324,13 @@ function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort let s:latest_completion_request.start_pos = start_pos let s:latest_completion_request.prompt = a:prompt - call s:Debug( 'FindStart: %s', { - \ 'line': line, - \ 'col': col, - \ 'prompt': len( a:prompt ), - \ 'start_pos': start_pos, - \ 'returning': ( start_pos + len( a:prompt ) ) - 1, - \ } ) + " call s:Debug( 'FindStart: %s', { + " \ 'line': line, + " \ 'col': col, + " \ 'prompt': len( a:prompt ), + " \ 'start_pos': start_pos, + " \ 'returning': ( start_pos + len( a:prompt ) ) - 1, + " \ } ) " start_pos is 1-based and the return of findstart is 0-based return ( start_pos + len( a:prompt ) ) - 1 @@ -349,9 +349,9 @@ function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort endif if item.length > len( a:query ) - call s:Debug( 'Rejecting %s, length is greater than %s', - \ item, - \ len( a:query ) ) + " call s:Debug( 'Rejecting %s, length is greater than %s', + " \ item, + " \ len( a:query ) ) continue endif @@ -363,7 +363,7 @@ function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort endfor let s:latest_completion_request = {} - call s:Debug( 'Items: %s', items ) + " call s:Debug( 'Items: %s', items ) return { 'words': items, 'refresh': 'always' } endif endfunction From 4e5011fe1b0a998e201ed4ea6e4d24572506020a Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 4 Sep 2020 01:18:59 +0100 Subject: [PATCH 10/11] Make command line completion work too --- autoload/vimspector.vim | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 1a141d6..c1f3640 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -240,25 +240,38 @@ function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort endfunction py3 < Date: Fri, 4 Sep 2020 01:29:08 +0100 Subject: [PATCH 11/11] Fix flake when terminal takes a while to close --- tests/tabpage.test.vim | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/tabpage.test.vim b/tests/tabpage.test.vim index 6772c66..1a5b1bc 100644 --- a/tests/tabpage.test.vim +++ b/tests/tabpage.test.vim @@ -71,12 +71,8 @@ function! Test_All_Buffers_Deleted_NoHidden() let buffers_after = getbufinfo( opts ) - if assert_equal( len( buffers_before ), len( buffers_after ) ) - call assert_report( 'Expected ' - \ . string( buffers_before ) - \ . ' but found ' - \ . string( buffers_after ) ) - endif + call WaitForAssert( {-> + \ assert_equal( len( buffers_before ), len( buffers_after ) ) } ) set hidden& lcd - @@ -106,12 +102,8 @@ function! Test_All_Buffers_Deleted_Hidden() let buffers_after = getbufinfo( opts ) - if assert_equal( len( buffers_before ), len( buffers_after ) ) - call assert_report( 'Expected ' - \ . string( buffers_before ) - \ . ' but found ' - \ . string( buffers_after ) ) - endif + call WaitForAssert( {-> + \ assert_equal( len( buffers_before ), len( buffers_after ) ) } ) set hidden& lcd - @@ -126,12 +118,8 @@ function! Test_All_Buffers_Deleted_ToggleLog() VimspectorToggleLog let buffers_after = getbufinfo( #{ buflisted: 1 } ) - if assert_equal( len( buffers_before ), len( buffers_after ) ) - call assert_report( 'Expected ' - \ . string( buffers_before ) - \ . ' but found ' - \ . string( buffers_after ) ) - endif + call WaitForAssert( {-> + \ assert_equal( len( buffers_before ), len( buffers_after ) ) } ) call vimspector#test#setup#Reset() set hidden& @@ -158,12 +146,8 @@ function! Test_All_Buffers_Deleted_Installer() \ 120000 ) let buffers_after = getbufinfo( #{ buflisted: 1 } ) - if assert_equal( len( buffers_before ), len( buffers_after ) ) - call assert_report( 'Expected ' - \ . string( buffers_before ) - \ . ' but found ' - \ . string( buffers_after ) ) - endif + call WaitForAssert( {-> + \ assert_equal( len( buffers_before ), len( buffers_after ) ) } ) call vimspector#test#setup#Reset() set hidden&