Merge pull request #248 from puremourning/completion
Support completion for console and watches.
This commit is contained in:
commit
2c401a859c
13 changed files with 287 additions and 59 deletions
40
README.md
40
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)
|
||||
|
||||
<!-- 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ):
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>' )
|
||||
|
|
|
|||
9
support/test/bash/test_script
Normal file
9
support/test/bash/test_script
Normal 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
3
support/test/chrome/run_server
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
php -S localhost:1234 -t www
|
||||
|
|
@ -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() );
|
||||
} );
|
||||
|
|
|
|||
|
|
@ -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() )
|
||||
|
|
|
|||
|
|
@ -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&
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue