Merge pull request #248 from puremourning/completion

Support completion for console and watches.
This commit is contained in:
mergify[bot] 2020-09-04 00:52:40 +00:00 committed by GitHub
commit 2c401a859c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 287 additions and 59 deletions

View file

@ -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)
<!-- Added by: ben, at: Tue 1 Sep 2020 13:42:32 BST -->
<!-- Added by: ben, at: Fri 4 Sep 2020 00:48:17 BST -->
<!--te-->
@ -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
`<Ctrl-x><Ctrl-o>` (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 `<CR>` 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 `<Ctrl-x><Ctrl-o>` (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

View file

@ -19,6 +19,13 @@ let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
function! s:Debug( ... ) abort
py3 <<EOF
if _vimspector_session is not None:
_vimspector_session._logger.debug( *vim.eval( 'a:000' ) )
EOF
endfunction
let s:enabled = vimspector#internal#state#Reset()
@ -232,17 +239,156 @@ function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort
return join( buffers, "\n" )
endfunction
py3 <<EOF
def _vimspector_GetExprCompletions( ArgLead, prev_non_keyword_char ):
if not _vimspector_session:
return []
items = []
for candidate in _vimspector_session.GetCompletionsSync(
ArgLead,
prev_non_keyword_char ):
label = candidate.get( 'text', candidate[ 'label' ] )
start = prev_non_keyword_char - 1
if 'start' in candidate and 'length' in candidate:
start = candidate[ 'start' ]
items.append( ArgLead[ 0 : start ] + label )
return items
EOF
function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort
if !s:enabled
return
endif
return join( py3eval( '_vimspector_session.GetCompletionsSync( '
\.' vim.eval( "a:CmdLine" ),'
\.' int( vim.eval( "a:CursorPos" ) ) )'
\. ' if _vimspector_session else []' ),
let col = len( a:ArgLead )
let prev_non_keyword_char = match( a:ArgLead[ 0 : col - 1 ], '\k*$' ) + 1
return join( py3eval( '_vimspector_GetExprCompletions( '
\ . 'vim.eval( "a:ArgLead" ), '
\ . 'int( vim.eval( "prev_non_keyword_char" ) ) )' ),
\ "\n" )
endfunction
let s:latest_completion_request = {}
function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort
if py3eval( 'not _vimspector_session' )
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 -3
endif
let line = getline( line( '.' ) )[ len( a:prompt ) : ]
let col = col( '.' ) - len( a:prompt )
" It seems that most servers don't implement the 'start' parameter, which is
" clearly necessary, as they all seem to assume a specific behaviour, which
" is undocumented.
let s:latest_completion_request.items =
\ py3eval( '_vimspector_session.GetCompletionsSync( '
\.' vim.eval( "line" ), '
\.' int( vim.eval( "col" ) ) )' )
let s:latest_completion_request.line = line
let s:latest_completion_request.col = col
let prev_non_keyword_char = match( line[ 0 : col - 1 ], '\k*$' ) + 1
let query_len = col - prev_non_keyword_char
let start_pos = col
for item in s:latest_completion_request.items
if !has_key( item, 'start' ) || !has_key( item, 'length' )
" The specification states that if start is not supplied, isertion
" should be at the requested column. But about 0 of the servers actually
" implement that
" (https://github.com/microsoft/debug-adapter-protocol/issues/138)
let item.start = prev_non_keyword_char
let item.length = query_len
else
" For some reason, the returned start value is 0-indexed even though we
" use columnsStartAt1
let item.start += 1
endif
if !has_key( item, 'text' )
let item.text = item.label
endif
if item.start < start_pos
let start_pos = item.start
endif
endfor
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,
" \ } )
" start_pos is 1-based and the return of findstart is 0-based
return ( start_pos + len( a:prompt ) ) - 1
else
let items = []
let pfxlen = len( s:latest_completion_request.prompt )
for item in s:latest_completion_request.items
if item.start > 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.line[
\ s:latest_completion_request.start_pos + pfxlen - 1 :
\ item.start + pfxlen - 1 ] . item.text
endif
if item.length > 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', '' ),
\ 'icase': 1,
\ } )
endfor
let s:latest_completion_request = {}
" call s:Debug( 'Items: %s', items )
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

View file

@ -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

View file

@ -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 ):
@ -941,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 ):

View file

@ -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': {

View file

@ -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 )

View file

@ -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

View file

@ -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 <buffer> <CR> :call vimspector#ExpandVariable()<CR>' )

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
function Test() {
echo $1
}
for i in "$@"; do
Test $i
done

3
support/test/chrome/run_server Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
php -S localhost:1234 -t www

View file

@ -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() );
} );

View file

@ -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() )

View file

@ -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&