Merge pull request #350 from puremourning/nvim-float-window
Replace vim balloons with popups
This commit is contained in:
commit
e70b8f37a3
12 changed files with 784 additions and 113 deletions
54
README.md
54
README.md
|
|
@ -50,6 +50,7 @@ For detailed explanatin of the `.vimspector.json` format, see the
|
||||||
* [Run to Cursor](#run-to-cursor)
|
* [Run to Cursor](#run-to-cursor)
|
||||||
* [Stepping](#stepping)
|
* [Stepping](#stepping)
|
||||||
* [Variables and scopes](#variables-and-scopes)
|
* [Variables and scopes](#variables-and-scopes)
|
||||||
|
* [Variable or selection hover evaluation](#variable-or-selection-hover-evaluation)
|
||||||
* [Watches](#watches)
|
* [Watches](#watches)
|
||||||
* [Watch autocompletion](#watch-autocompletion)
|
* [Watch autocompletion](#watch-autocompletion)
|
||||||
* [Stack Traces](#stack-traces)
|
* [Stack Traces](#stack-traces)
|
||||||
|
|
@ -89,7 +90,7 @@ For detailed explanatin of the `.vimspector.json` format, see the
|
||||||
* [Example](#example)
|
* [Example](#example)
|
||||||
* [FAQ](#faq)
|
* [FAQ](#faq)
|
||||||
|
|
||||||
<!-- Added by: ben, at: Sat 9 Jan 2021 13:13:28 GMT -->
|
<!-- Added by: ben, at: Sun 21 Feb 2021 16:59:12 GMT -->
|
||||||
|
|
||||||
<!--te-->
|
<!--te-->
|
||||||
|
|
||||||
|
|
@ -102,7 +103,7 @@ language that Visual Studio Code supports (but see caveats).
|
||||||
The [Vimspector website][website] has an overview of the UI, along with basic
|
The [Vimspector website][website] has an overview of the UI, along with basic
|
||||||
instructions for configuration and setup.
|
instructions for configuration and setup.
|
||||||
|
|
||||||
But for now, here's a (rather old) screenshot of Vimsepctor debugging Vim:
|
But for now, here's a (rather old) screenshot of Vimspector debugging Vim:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -124,7 +125,7 @@ And a couple of brief demos:
|
||||||
- locals and globals display
|
- locals and globals display
|
||||||
- watch expressions with autocompletion
|
- watch expressions with autocompletion
|
||||||
- call stack display and navigation
|
- call stack display and navigation
|
||||||
- variable value display hover
|
- hierarchical variable value display popup (see `<Plug>VimspectorBalloonEval`)
|
||||||
- interactive debug console with autocompletion
|
- interactive debug console with autocompletion
|
||||||
- launch debugee within Vim's embedded terminal
|
- launch debugee within Vim's embedded terminal
|
||||||
- logging/stdout display
|
- logging/stdout display
|
||||||
|
|
@ -230,8 +231,8 @@ Why such a new vim ? Well 2 reasons:
|
||||||
if you hit them.
|
if you hit them.
|
||||||
|
|
||||||
Why is neovim experimental? Because the author doesn't use neovim regularly, and
|
Why is neovim experimental? Because the author doesn't use neovim regularly, and
|
||||||
there are no regression tests for vimspector in neovim, so it's likely to break
|
there are no regression tests for vimspector in neovim, so it may break
|
||||||
frequently. Issue reports are handled on best-efforts basis, and PRs are
|
occasionally. Issue reports are handled on best-efforts basis, and PRs are
|
||||||
welcome to fix bugs. See also the next section descibing differences for neovim
|
welcome to fix bugs. See also the next section descibing differences for neovim
|
||||||
vs vim.
|
vs vim.
|
||||||
|
|
||||||
|
|
@ -249,7 +250,8 @@ neovim doesn't implement some features Vimspector relies on:
|
||||||
the output window's current output.
|
the output window's current output.
|
||||||
* Prompt Buffers - used to send commands in the Console and add Watches.
|
* Prompt Buffers - used to send commands in the Console and add Watches.
|
||||||
(*Note*: prompt buffers are available in neovim nightly)
|
(*Note*: prompt buffers are available in neovim nightly)
|
||||||
* Balloons - used to display the values of variables when debugging.
|
* Balloons - this allows for the variable evaluation popup to be displayed when
|
||||||
|
hovering the mouse. See below for how to create a keyboard mapping instead.
|
||||||
|
|
||||||
Workarounds are in place as follows:
|
Workarounds are in place as follows:
|
||||||
|
|
||||||
|
|
@ -258,9 +260,18 @@ Workarounds are in place as follows:
|
||||||
[`:VimspectorReset`](#closing-debugger)
|
[`:VimspectorReset`](#closing-debugger)
|
||||||
* Prompt Buffers - There are [`:VimspectorEval`](#console)
|
* Prompt Buffers - There are [`:VimspectorEval`](#console)
|
||||||
and [`:VimspectorWatch`](#watches)
|
and [`:VimspectorWatch`](#watches)
|
||||||
|
* Balloons - There is the `<Plug>VimspectorBalloonEval` mapping. There is no
|
||||||
|
default mapping for this, so I recommend something like this to get variable
|
||||||
|
display in a popup:
|
||||||
|
|
||||||
There is no workaroud for the lack of balloons; you'll just have to use
|
```viml
|
||||||
`:VimspectorEval` or `:VimspectorWatch`, or switch to Vim.
|
" mnemonic 'di' = 'debug inspect' (pick your own, if you prefer!)
|
||||||
|
|
||||||
|
" for normal mode - the word under the cursor
|
||||||
|
nmap <Leader>di <Plug>VimspectorBalloonEval
|
||||||
|
" for visual mode, the visually selected text
|
||||||
|
xmap <Leader>di <Plug>VimspectorBalloonEval
|
||||||
|
```
|
||||||
|
|
||||||
## Windows differences
|
## Windows differences
|
||||||
|
|
||||||
|
|
@ -653,6 +664,7 @@ features to set your own mappings. To that end, Vimspector defines the following
|
||||||
* `<Plug>VimspectorStepInto`
|
* `<Plug>VimspectorStepInto`
|
||||||
* `<Plug>VimspectorStepOut`
|
* `<Plug>VimspectorStepOut`
|
||||||
* `<Plug>VimspectorRunToCursor`
|
* `<Plug>VimspectorRunToCursor`
|
||||||
|
* `<Plug>VimspectorBalloonEval`
|
||||||
|
|
||||||
These map roughly 1-1 with the API functions below.
|
These map roughly 1-1 with the API functions below.
|
||||||
|
|
||||||
|
|
@ -715,6 +727,18 @@ let g:vimspector_enable_mappings = 'HUMAN'
|
||||||
| `F11` | Step Into | `vimspector#StepInto()` |
|
| `F11` | Step Into | `vimspector#StepInto()` |
|
||||||
| `F12` | Step out of current function scope | `vimspector#StepOut()` |
|
| `F12` | Step out of current function scope | `vimspector#StepOut()` |
|
||||||
|
|
||||||
|
In addition, I recommend adding a mapping to `<Plug>VimspectorBalloonEval`, in
|
||||||
|
normal and visual modes, for example:
|
||||||
|
|
||||||
|
```viml
|
||||||
|
" mnemonic 'di' = 'debug inspect' (pick your own, if you prefer!)
|
||||||
|
|
||||||
|
" for normal mode - the word under the cursor
|
||||||
|
nmap <Leader>di <Plug>VimspectorBalloonEval
|
||||||
|
" for visual mode, the visually selected text
|
||||||
|
xmap <Leader>di <Plug>VimspectorBalloonEval
|
||||||
|
```
|
||||||
|
|
||||||
# Usage and API
|
# Usage and API
|
||||||
|
|
||||||
This section defines detailed usage instructions, organised by feature. For most
|
This section defines detailed usage instructions, organised by feature. For most
|
||||||
|
|
@ -890,6 +914,20 @@ breakpoint when it is hit.
|
||||||
|
|
||||||
Scopes and variables are represented by the buffer `vimspector.Variables`.
|
Scopes and variables are represented by the buffer `vimspector.Variables`.
|
||||||
|
|
||||||
|
## Variable or selection hover evaluation
|
||||||
|
|
||||||
|
All rules for `Variables and scopes` apply plus the following:
|
||||||
|
|
||||||
|
* With mouse enabled, hover over a variable and get the value it evaluates to.
|
||||||
|
* Use your mouse to perform a visual selection of an expression (e.g. `a + b`)
|
||||||
|
and get its result.
|
||||||
|
* Make a normal mode (`nmap`) and visual mode (`xmap`) mapping to
|
||||||
|
`<Plug>VimspectorBalloonEval` to manually trigger the popup.
|
||||||
|
* Use regular nagivation keys (`j`, `k`) to chose the current selection; `<Esc>`
|
||||||
|
(or leave the tooltip window) to close the tooltip.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Watches
|
## Watches
|
||||||
|
|
||||||
The watch window is used to inspect variables and expressions. Expressions are
|
The watch window is used to inspect variables and expressions. Expressions are
|
||||||
|
|
|
||||||
|
|
@ -523,6 +523,22 @@ function! vimspector#OnBufferCreated( file_name ) abort
|
||||||
py3 _vimspector_session.RefreshSigns( vim.eval( 'a:file_name' ) )
|
py3 _vimspector_session.RefreshSigns( vim.eval( 'a:file_name' ) )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! vimspector#ShowEvalBalloon( is_visual ) abort
|
||||||
|
if a:is_visual
|
||||||
|
let expr = py3eval( '__import__( "vimspector", fromlist = [ "utils" ] )'
|
||||||
|
\ . '.utils.GetVisualSelection('
|
||||||
|
\ . ' int( vim.eval( "winbufnr( winnr() )" ) ) )' )
|
||||||
|
let expr = join( expr, '\n' )
|
||||||
|
else
|
||||||
|
let expr = expand( '<cexpr>' )
|
||||||
|
endif
|
||||||
|
|
||||||
|
return py3eval( '_vimspector_session.ShowEvalBalloon('
|
||||||
|
\ . ' int( vim.eval( "winnr()" ) ), "'
|
||||||
|
\ . expr
|
||||||
|
\ . '", 0 )' )
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
" Boilerplate {{{
|
" Boilerplate {{{
|
||||||
let &cpoptions=s:save_cpo
|
let &cpoptions=s:save_cpo
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,293 @@ let s:save_cpo = &cpoptions
|
||||||
set cpoptions&vim
|
set cpoptions&vim
|
||||||
" }}}
|
" }}}
|
||||||
|
|
||||||
" Returns: py.ShowBalloon( winnr, expresssion )
|
scriptencoding utf-8
|
||||||
function! vimspector#internal#balloon#BalloonExpr() abort
|
|
||||||
" winnr + 1 because for *no good reason* winnr is 0 based here unlike
|
let s:popup_win_id = 0
|
||||||
" everywhere else
|
let s:nvim_border_win_id = 0
|
||||||
" int() because for *no good reason* winnr is a string.
|
"
|
||||||
return py3eval('_vimspector_session.ShowBalloon('
|
" tooltip dimensions
|
||||||
\ . 'int( vim.eval( "v:beval_winnr" ) ) + 1,'
|
let s:min_width = 1
|
||||||
\ . 'vim.eval( "v:beval_text" ) )' )
|
let s:min_height = 1
|
||||||
|
let s:max_width = 80
|
||||||
|
let s:max_height = 20
|
||||||
|
|
||||||
|
let s:is_neovim = has( 'nvim' )
|
||||||
|
|
||||||
|
|
||||||
|
" This is used as the balloonexpr in vim to show the Tooltip at the hover
|
||||||
|
" position
|
||||||
|
function! vimspector#internal#balloon#HoverTooltip() abort
|
||||||
|
return py3eval( '_vimspector_session.ShowEvalBalloon('
|
||||||
|
\ . ' int( vim.eval( "v:beval_winnr" ) ) + 1,'
|
||||||
|
\ . ' vim.eval( "v:beval_text"),'
|
||||||
|
\ . ' 1 )' )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! vimspector#internal#balloon#CreateTooltip( is_hover, ... ) abort
|
||||||
|
let body = []
|
||||||
|
if a:0 > 0
|
||||||
|
let body = a:1
|
||||||
|
endif
|
||||||
|
|
||||||
|
if s:popup_win_id != 0
|
||||||
|
call vimspector#internal#balloon#Close()
|
||||||
|
endif
|
||||||
|
|
||||||
|
if s:is_neovim
|
||||||
|
call s:CreateNeovimTooltip( body )
|
||||||
|
else
|
||||||
|
let config = {
|
||||||
|
\ 'wrap': 0,
|
||||||
|
\ 'filtermode': 'n',
|
||||||
|
\ 'maxwidth': s:max_width,
|
||||||
|
\ 'maxheight': s:max_height,
|
||||||
|
\ 'minwidth': s:min_width,
|
||||||
|
\ 'minheight': s:min_height,
|
||||||
|
\ 'scrollbar': 1,
|
||||||
|
\ 'border': [],
|
||||||
|
\ 'padding': [ 0, 1, 0, 1],
|
||||||
|
\ 'drag': 1,
|
||||||
|
\ 'resize': 1,
|
||||||
|
\ 'close': 'button',
|
||||||
|
\ 'callback': 'vimspector#internal#balloon#CloseCallback'
|
||||||
|
\ }
|
||||||
|
|
||||||
|
" When ambiwidth is single, use prettier characters for the border. This
|
||||||
|
" would look silly when ambiwidth is double.
|
||||||
|
if &ambiwidth ==# 'single' && &encoding ==? 'utf-8'
|
||||||
|
let config[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '┛', '╰' ]
|
||||||
|
endif
|
||||||
|
|
||||||
|
if a:is_hover
|
||||||
|
let config[ 'filter' ] = 'vimspector#internal#balloon#MouseFilter'
|
||||||
|
let config[ 'mousemoved' ] = [ 0, 0, 0 ]
|
||||||
|
let s:popup_win_id = popup_beval( body, config )
|
||||||
|
else
|
||||||
|
let config[ 'filter' ] = 'vimspector#internal#balloon#CursorFilter'
|
||||||
|
let config[ 'moved' ] = 'any'
|
||||||
|
let config[ 'cursorline' ] = 1
|
||||||
|
let s:popup_win_id = popup_atcursor( body, config )
|
||||||
|
endif
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
return s:popup_win_id
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Filters for vim {{{
|
||||||
|
function! vimspector#internal#balloon#MouseFilter( winid, key ) abort
|
||||||
|
if a:key ==# "\<Esc>"
|
||||||
|
call vimspector#internal#balloon#Close()
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
|
||||||
|
if index( [ "\<leftmouse>", "\<2-leftmouse>" ], a:key ) < 0
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
|
||||||
|
let handled = 0
|
||||||
|
let mouse_coords = getmousepos()
|
||||||
|
|
||||||
|
" close the popup if mouse is clicked outside the window
|
||||||
|
if mouse_coords[ 'winid' ] != a:winid
|
||||||
|
call vimspector#internal#balloon#Close()
|
||||||
|
return 0
|
||||||
|
endif
|
||||||
|
|
||||||
|
" place the cursor according to the click
|
||||||
|
call win_execute( a:winid,
|
||||||
|
\ ':call cursor( '
|
||||||
|
\ . mouse_coords[ 'line' ]
|
||||||
|
\ . ', '
|
||||||
|
\ . mouse_coords[ 'column' ]
|
||||||
|
\ . ' )' )
|
||||||
|
|
||||||
|
" expand the variable if we got double click
|
||||||
|
if a:key ==? "\<2-leftmouse>"
|
||||||
|
" forward line number to python, since vim does not allow us to focus
|
||||||
|
" the correct window
|
||||||
|
call py3eval( '_vimspector_session.ExpandVariable('
|
||||||
|
\ . 'buf = vim.buffers[ ' . winbufnr( a:winid ) . ' ],'
|
||||||
|
\ . 'line_num = ' . line( '.', a:winid )
|
||||||
|
\ . ')' )
|
||||||
|
let handled = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
return handled
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! vimspector#internal#balloon#CursorFilter( winid, key ) abort
|
||||||
|
if a:key ==? "\<CR>"
|
||||||
|
" forward line number to python, since vim does not allow us to focus
|
||||||
|
" the correct window
|
||||||
|
call py3eval( '_vimspector_session.ExpandVariable('
|
||||||
|
\ . 'buf = vim.buffers[ ' . winbufnr( a:winid ) . ' ],'
|
||||||
|
\ . 'line_num = ' . line( '.', a:winid )
|
||||||
|
\ . ')' )
|
||||||
|
return 1
|
||||||
|
elseif index( [ "\<LeftMouse>", "\<2-LeftMouse>" ], a:key ) >= 0
|
||||||
|
return vimspector#internal#balloon#MouseFilter( a:winid, a:key )
|
||||||
|
endif
|
||||||
|
|
||||||
|
return popup_filter_menu( a:winid, a:key )
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Closing {{{
|
||||||
|
|
||||||
|
function! vimspector#internal#balloon#CloseCallback( ... ) abort
|
||||||
|
let s:popup_win_id = 0
|
||||||
|
let s:nvim_border_win_id = 0
|
||||||
|
return py3eval( '_vimspector_session.CleanUpTooltip()' )
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! vimspector#internal#balloon#Close() abort
|
||||||
|
if s:is_neovim
|
||||||
|
call nvim_win_close( s:popup_win_id, v:true )
|
||||||
|
call nvim_win_close( s:nvim_border_win_id, v:true )
|
||||||
|
|
||||||
|
call vimspector#internal#balloon#CloseCallback()
|
||||||
|
else
|
||||||
|
call popup_close(s:popup_win_id)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
" Neovim pollyfill {{{
|
||||||
|
|
||||||
|
function! vimspector#internal#balloon#ResizeTooltip() abort
|
||||||
|
if !s:is_neovim
|
||||||
|
" Vim does this for us
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
if s:popup_win_id <= 0 || s:nvim_border_win_id <= 0
|
||||||
|
" nothing to resize
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
noautocmd call win_gotoid( s:popup_win_id )
|
||||||
|
let buf_lines = getline( 1, '$' )
|
||||||
|
|
||||||
|
let width = s:min_width
|
||||||
|
let height = min( [ max( [ s:min_height, len( buf_lines ) ] ),
|
||||||
|
\ s:max_height ] )
|
||||||
|
|
||||||
|
" calculate the longest line
|
||||||
|
for l in buf_lines
|
||||||
|
let width = max( [ width, len( l ) ] )
|
||||||
|
endfor
|
||||||
|
|
||||||
|
let width = min( [ width, s:max_width ] )
|
||||||
|
|
||||||
|
let opts = {
|
||||||
|
\ 'width': width,
|
||||||
|
\ 'height': height,
|
||||||
|
\ }
|
||||||
|
|
||||||
|
" resize the content window
|
||||||
|
call nvim_win_set_config( s:popup_win_id, opts )
|
||||||
|
|
||||||
|
" resize the border window
|
||||||
|
let opts[ 'width' ] = width + 4
|
||||||
|
let opts[ 'height' ] = height + 2
|
||||||
|
|
||||||
|
call nvim_win_set_config( s:nvim_border_win_id, opts )
|
||||||
|
call nvim_buf_set_lines( nvim_win_get_buf( s:nvim_border_win_id ),
|
||||||
|
\ 0,
|
||||||
|
\ -1,
|
||||||
|
\ v:true,
|
||||||
|
\ s:GenerateBorder( width, height ) )
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" neovim doesn't have the border support, so we have to make our own.
|
||||||
|
" FIXME: This will likely break if the user has `ambiwidth=2`
|
||||||
|
function! s:GenerateBorder( width, height ) abort
|
||||||
|
|
||||||
|
let top = '╭' . repeat('─',a:width + 2) . '╮'
|
||||||
|
let mid = '│' . repeat(' ',a:width + 2) . '│'
|
||||||
|
let bot = '╰' . repeat('─',a:width + 2) . '╯'
|
||||||
|
let lines = [ top ] + repeat( [ mid ], a:height ) + [ bot ]
|
||||||
|
|
||||||
|
return lines
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:CreateNeovimTooltip( body ) abort
|
||||||
|
" generate border for the float window by creating a background buffer and
|
||||||
|
" overlaying the content buffer
|
||||||
|
" see https://github.com/neovim/neovim/issues/9718#issuecomment-546603628
|
||||||
|
let buf_id = nvim_create_buf( v:false, v:true )
|
||||||
|
call nvim_buf_set_lines( buf_id,
|
||||||
|
\ 0,
|
||||||
|
\ -1,
|
||||||
|
\ v:true,
|
||||||
|
\ s:GenerateBorder( s:max_width, s:max_height ) )
|
||||||
|
|
||||||
|
" default the dimensions initially, then we'll calculate the real size and
|
||||||
|
" resize it.
|
||||||
|
let opts = {
|
||||||
|
\ 'relative': 'cursor',
|
||||||
|
\ 'width': s:max_width + 2,
|
||||||
|
\ 'height': s:max_height + 2,
|
||||||
|
\ 'col': 0,
|
||||||
|
\ 'row': 1,
|
||||||
|
\ 'anchor': 'NW',
|
||||||
|
\ 'style': 'minimal'
|
||||||
|
\ }
|
||||||
|
|
||||||
|
" this is the border window
|
||||||
|
let s:nvim_border_win_id = nvim_open_win( buf_id, 0, opts )
|
||||||
|
call nvim_win_set_option( s:nvim_border_win_id, 'signcolumn', 'no' )
|
||||||
|
call nvim_win_set_option( s:nvim_border_win_id, 'relativenumber', v:false )
|
||||||
|
call nvim_win_set_option( s:nvim_border_win_id, 'number', v:false )
|
||||||
|
|
||||||
|
" when calculating where to display the content window, we need to account
|
||||||
|
" for the border
|
||||||
|
let opts.row += 1
|
||||||
|
let opts.height -= 2
|
||||||
|
let opts.col += 2
|
||||||
|
let opts.width -= 4
|
||||||
|
|
||||||
|
" create the content window
|
||||||
|
let buf_id = nvim_create_buf( v:false, v:true )
|
||||||
|
call nvim_buf_set_lines( buf_id, 0, -1, v:true, a:body )
|
||||||
|
call nvim_buf_set_option( buf_id, 'modifiable', v:false )
|
||||||
|
let s:popup_win_id = nvim_open_win( buf_id, v:false, opts )
|
||||||
|
|
||||||
|
call nvim_win_set_option( s:popup_win_id, 'wrap', v:false )
|
||||||
|
call nvim_win_set_option( s:popup_win_id, 'cursorline', v:true )
|
||||||
|
call nvim_win_set_option( s:popup_win_id, 'signcolumn', 'no' )
|
||||||
|
call nvim_win_set_option( s:popup_win_id, 'relativenumber', v:false )
|
||||||
|
call nvim_win_set_option( s:popup_win_id, 'number', v:false )
|
||||||
|
|
||||||
|
" Move the cursor into the popup window, as this is the only way we can
|
||||||
|
" interract with the popup in neovim
|
||||||
|
noautocmd call win_gotoid( s:popup_win_id )
|
||||||
|
|
||||||
|
nnoremap <silent> <buffer> <CR>
|
||||||
|
\ <cmd>call vimspector#ExpandVariable()<CR>
|
||||||
|
nnoremap <silent> <buffer> <Esc>
|
||||||
|
\ <cmd>quit<CR>
|
||||||
|
nnoremap <silent> <buffer> <2-LeftMouse>
|
||||||
|
\ <cmd>call vimspector#ExpandVariable()<CR>
|
||||||
|
|
||||||
|
" Close the popup whenever we leave this window
|
||||||
|
augroup vimspector#internal#balloon#nvim_float
|
||||||
|
autocmd!
|
||||||
|
autocmd WinLeave <buffer>
|
||||||
|
\ :call vimspector#internal#balloon#Close()
|
||||||
|
\ | autocmd! vimspector#internal#balloon#nvim_float
|
||||||
|
augroup END
|
||||||
|
|
||||||
|
call vimspector#internal#balloon#ResizeTooltip()
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" }}}
|
||||||
|
|
||||||
|
|
||||||
" Boilerplate {{{
|
" Boilerplate {{{
|
||||||
let &cpoptions=s:save_cpo
|
let &cpoptions=s:save_cpo
|
||||||
unlet s:save_cpo
|
unlet s:save_cpo
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,13 @@ nnoremap <silent> <Plug>VimspectorStepOut
|
||||||
nnoremap <silent> <Plug>VimspectorRunToCursor
|
nnoremap <silent> <Plug>VimspectorRunToCursor
|
||||||
\ :<c-u>call vimspector#RunToCursor()<CR>
|
\ :<c-u>call vimspector#RunToCursor()<CR>
|
||||||
|
|
||||||
|
" Eval for normal mode
|
||||||
|
nnoremap <silent> <Plug>VimspectorBalloonEval
|
||||||
|
\ :<c-u>call vimspector#ShowEvalBalloon( 0 )<CR>
|
||||||
|
" And for visual modes
|
||||||
|
xnoremap <silent> <Plug>VimspectorBalloonEval
|
||||||
|
\ :<c-u>call vimspector#ShowEvalBalloon( 1 )<CR>
|
||||||
|
|
||||||
if s:mappings ==# 'VISUAL_STUDIO'
|
if s:mappings ==# 'VISUAL_STUDIO'
|
||||||
nmap <F5> <Plug>VimspectorContinue
|
nmap <F5> <Plug>VimspectorContinue
|
||||||
nmap <S-F5> <Plug>VimspectorStop
|
nmap <S-F5> <Plug>VimspectorStop
|
||||||
|
|
|
||||||
|
|
@ -520,8 +520,8 @@ class DebugSession( object ):
|
||||||
self._stackTraceView.SetCurrentThread()
|
self._stackTraceView.SetCurrentThread()
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def ExpandVariable( self ):
|
def ExpandVariable( self, buf = None, line_num = None ):
|
||||||
self._variablesView.ExpandVariable()
|
self._variablesView.ExpandVariable( buf, line_num )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def AddWatch( self, expression ):
|
def AddWatch( self, expression ):
|
||||||
|
|
@ -538,13 +538,13 @@ class DebugSession( object ):
|
||||||
def DeleteWatch( self ):
|
def DeleteWatch( self ):
|
||||||
self._variablesView.DeleteWatch()
|
self._variablesView.DeleteWatch()
|
||||||
|
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def ShowBalloon( self, winnr, expression ):
|
def ShowEvalBalloon( self, winnr, expression, is_hover ):
|
||||||
"""Proxy: ballonexpr -> variables.ShowBallon"""
|
|
||||||
frame = self._stackTraceView.GetCurrentFrame()
|
frame = self._stackTraceView.GetCurrentFrame()
|
||||||
# Check if RIP is in a frame
|
# Check if RIP is in a frame
|
||||||
if frame is None:
|
if frame is None:
|
||||||
self._logger.debug( 'Balloon: Not in a stack frame' )
|
self._logger.debug( 'Tooltip: Not in a stack frame' )
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# Check if cursor in code window
|
# Check if cursor in code window
|
||||||
|
|
@ -555,7 +555,11 @@ class DebugSession( object ):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# Return variable aware function
|
# Return variable aware function
|
||||||
return self._variablesView.ShowBalloon( frame, expression )
|
return self._variablesView.VariableEval( frame, expression, is_hover )
|
||||||
|
|
||||||
|
|
||||||
|
def CleanUpTooltip( self ):
|
||||||
|
return self._variablesView.CleanUpTooltip()
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def ExpandFrameOrThread( self ):
|
def ExpandFrameOrThread( self ):
|
||||||
|
|
@ -679,6 +683,7 @@ class DebugSession( object ):
|
||||||
'variables': utils.WindowID( vars_window, self._uiTab ),
|
'variables': utils.WindowID( vars_window, self._uiTab ),
|
||||||
'watches': utils.WindowID( watch_window, self._uiTab ),
|
'watches': utils.WindowID( watch_window, self._uiTab ),
|
||||||
'output': utils.WindowID( output_window, self._uiTab ),
|
'output': utils.WindowID( output_window, self._uiTab ),
|
||||||
|
'eval': None # this is going to be updated every time eval popup is opened
|
||||||
}
|
}
|
||||||
with utils.RestoreCursorPosition():
|
with utils.RestoreCursorPosition():
|
||||||
with utils.RestoreCurrentWindow():
|
with utils.RestoreCurrentWindow():
|
||||||
|
|
|
||||||
|
|
@ -634,15 +634,19 @@ def ParseVariables( variables_list,
|
||||||
return new_variables
|
return new_variables
|
||||||
|
|
||||||
|
|
||||||
def DisplayBaloon( is_term, display ):
|
def DisplayBalloon( is_term, display, is_hover = False ):
|
||||||
if not is_term:
|
if not is_term:
|
||||||
display = '\n'.join( display )
|
|
||||||
# To enable the Windows GUI to display the balloon correctly
|
# To enable the Windows GUI to display the balloon correctly
|
||||||
# Refer https://github.com/vim/vim/issues/1512#issuecomment-492070685
|
# Refer https://github.com/vim/vim/issues/1512#issuecomment-492070685
|
||||||
vim.eval( "balloon_show( '' )" )
|
display = '\n'.join( display )
|
||||||
|
|
||||||
vim.eval( "balloon_show( {0} )".format(
|
created_win_id = int( vim.eval(
|
||||||
json.dumps( display ) ) )
|
"vimspector#internal#balloon#CreateTooltip({}, {})".format(
|
||||||
|
int( is_hover ), json.dumps( display )
|
||||||
|
)
|
||||||
|
) )
|
||||||
|
|
||||||
|
return created_win_id
|
||||||
|
|
||||||
|
|
||||||
def GetBufferFilepath( buf ):
|
def GetBufferFilepath( buf ):
|
||||||
|
|
@ -719,6 +723,28 @@ def GetBufferFiletypes( buf ):
|
||||||
return ft.split( '.' )
|
return ft.split( '.' )
|
||||||
|
|
||||||
|
|
||||||
|
def GetVisualSelection( bufnr ):
|
||||||
|
start_line, start_col = vim.current.buffer.mark( "<" )
|
||||||
|
end_line, end_col = vim.current.buffer.mark( ">" )
|
||||||
|
|
||||||
|
|
||||||
|
# lines are 1 based, but columns are 0 based
|
||||||
|
# don't ask me why...
|
||||||
|
start_line -= 1
|
||||||
|
end_line -= 1
|
||||||
|
|
||||||
|
lines = vim.buffers[ bufnr ][ start_line : end_line + 1 ]
|
||||||
|
# Do end first, in case it's on the same line as start (as doing start first
|
||||||
|
# would change the offset)
|
||||||
|
lines[ -1 ] = lines[ -1 ][ : end_col + 1 ]
|
||||||
|
lines[ 0 ] = lines[ 0 ][ start_col : ]
|
||||||
|
|
||||||
|
_logger.debug( f'Visual selection: { lines } from '
|
||||||
|
f'{ start_line }/{ start_col } -> { end_line }/{ end_col }' )
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def DisplaySplash( api_prefix, splash, text ):
|
def DisplaySplash( api_prefix, splash, text ):
|
||||||
if splash:
|
if splash:
|
||||||
return Call( f'vimspector#internal#{api_prefix}popup#UpdateSplash',
|
return Call( f'vimspector#internal#{api_prefix}popup#UpdateSplash',
|
||||||
|
|
|
||||||
|
|
@ -117,17 +117,36 @@ class Watch:
|
||||||
self.expression = expression
|
self.expression = expression
|
||||||
self.result = None
|
self.result = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def New( frame, expression, context ):
|
||||||
|
watch = {
|
||||||
|
'expression': expression,
|
||||||
|
'context': context,
|
||||||
|
}
|
||||||
|
if frame:
|
||||||
|
watch[ 'frameId' ] = frame[ 'id' ]
|
||||||
|
|
||||||
|
return Watch( watch )
|
||||||
|
|
||||||
|
|
||||||
class View:
|
class View:
|
||||||
lines: typing.Dict[ int, Expandable ]
|
lines: typing.Dict[ int, Expandable ]
|
||||||
draw: typing.Callable
|
draw: typing.Callable
|
||||||
|
syntax: str
|
||||||
|
|
||||||
def __init__( self, win, lines, draw ):
|
def __init__( self, win, lines, draw ):
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
self.draw = draw
|
self.draw = draw
|
||||||
self.buf = win.buffer
|
self.syntax = None
|
||||||
|
if win is not None:
|
||||||
|
self.buf = win.buffer
|
||||||
|
utils.SetUpUIWindow( win )
|
||||||
|
|
||||||
utils.SetUpUIWindow( win )
|
|
||||||
|
class BufView( View ):
|
||||||
|
def __init__( self, buf, lines, draw ):
|
||||||
|
super().__init__( None, lines, draw )
|
||||||
|
self.buf = buf
|
||||||
|
|
||||||
|
|
||||||
class VariablesView( object ):
|
class VariablesView( object ):
|
||||||
|
|
@ -138,6 +157,9 @@ class VariablesView( object ):
|
||||||
self._connection = None
|
self._connection = None
|
||||||
self._current_syntax = ''
|
self._current_syntax = ''
|
||||||
|
|
||||||
|
self._variable_eval: Scope = None
|
||||||
|
self._variable_eval_view: View = None
|
||||||
|
|
||||||
def AddExpandMappings():
|
def AddExpandMappings():
|
||||||
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
||||||
':<C-u>call vimspector#ExpandVariable()<CR>' )
|
':<C-u>call vimspector#ExpandVariable()<CR>' )
|
||||||
|
|
@ -183,7 +205,9 @@ class VariablesView( object ):
|
||||||
'balloonexpr': vim.options[ 'balloonexpr' ],
|
'balloonexpr': vim.options[ 'balloonexpr' ],
|
||||||
'balloondelay': vim.options[ 'balloondelay' ],
|
'balloondelay': vim.options[ 'balloondelay' ],
|
||||||
}
|
}
|
||||||
vim.options[ 'balloonexpr' ] = 'vimspector#internal#balloon#BalloonExpr()'
|
vim.options[ 'balloonexpr' ] = ( "vimspector#internal#"
|
||||||
|
"balloon#HoverTooltip()" )
|
||||||
|
|
||||||
vim.options[ 'balloondelay' ] = 250
|
vim.options[ 'balloondelay' ] = 250
|
||||||
|
|
||||||
if has_balloon:
|
if has_balloon:
|
||||||
|
|
@ -201,6 +225,7 @@ class VariablesView( object ):
|
||||||
utils.ClearBuffer( self._vars.buf )
|
utils.ClearBuffer( self._vars.buf )
|
||||||
with utils.ModifiableScratchBuffer( self._watch.buf ):
|
with utils.ModifiableScratchBuffer( self._watch.buf ):
|
||||||
utils.ClearBuffer( self._watch.buf )
|
utils.ClearBuffer( self._watch.buf )
|
||||||
|
self.ClearTooltip()
|
||||||
self._current_syntax = ''
|
self._current_syntax = ''
|
||||||
|
|
||||||
def ConnectionUp( self, connection ):
|
def ConnectionUp( self, connection ):
|
||||||
|
|
@ -216,6 +241,8 @@ class VariablesView( object ):
|
||||||
|
|
||||||
utils.CleanUpHiddenBuffer( self._vars.buf )
|
utils.CleanUpHiddenBuffer( self._vars.buf )
|
||||||
utils.CleanUpHiddenBuffer( self._watch.buf )
|
utils.CleanUpHiddenBuffer( self._watch.buf )
|
||||||
|
self.ClearTooltip()
|
||||||
|
|
||||||
|
|
||||||
def LoadScopes( self, frame ):
|
def LoadScopes( self, frame ):
|
||||||
def scopes_consumer( message ):
|
def scopes_consumer( message ):
|
||||||
|
|
@ -267,15 +294,98 @@ class VariablesView( object ):
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
def AddWatch( self, frame, expression ):
|
def _DrawBalloonEval( self ):
|
||||||
watch = {
|
watch = self._variable_eval
|
||||||
'expression': expression,
|
view = self._variable_eval_view
|
||||||
'context': 'watch',
|
|
||||||
}
|
|
||||||
if frame:
|
|
||||||
watch[ 'frameId' ] = frame[ 'id' ]
|
|
||||||
|
|
||||||
self._watches.append( Watch( watch ) )
|
with utils.RestoreCursorPosition():
|
||||||
|
with utils.ModifiableScratchBuffer( view.buf ):
|
||||||
|
utils.ClearBuffer( view.buf )
|
||||||
|
# FIXME: This probably doesn't work reliably
|
||||||
|
view.syntax = utils.SetSyntax( None,
|
||||||
|
self._current_syntax,
|
||||||
|
view.buf )
|
||||||
|
|
||||||
|
self._DrawWatchResult( view,
|
||||||
|
0,
|
||||||
|
watch,
|
||||||
|
is_short = True )
|
||||||
|
|
||||||
|
vim.eval( "vimspector#internal#balloon#ResizeTooltip()" )
|
||||||
|
|
||||||
|
def ClearTooltip( self ):
|
||||||
|
# This will actually end up calling CleanUpTooltip via the popup close
|
||||||
|
# callback
|
||||||
|
vim.eval( 'vimspector#internal#balloon#Close()' )
|
||||||
|
|
||||||
|
def CleanUpTooltip( self ) :
|
||||||
|
# remove reference to old tooltip window
|
||||||
|
self._variable_eval_view = None
|
||||||
|
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = None
|
||||||
|
|
||||||
|
def VariableEval( self, frame, expression, is_hover ):
|
||||||
|
"""Callback to display variable under cursor `:h ballonexpr`"""
|
||||||
|
if not self._connection:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def handler( message ):
|
||||||
|
|
||||||
|
watch = self._variable_eval
|
||||||
|
if watch.result is None:
|
||||||
|
watch.result = WatchResult( message[ 'body' ] )
|
||||||
|
else:
|
||||||
|
watch.result.Update( message[ 'body' ] )
|
||||||
|
|
||||||
|
popup_win_id = utils.DisplayBalloon( self._is_term, [], is_hover )
|
||||||
|
# record the global eval window id
|
||||||
|
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = int( popup_win_id )
|
||||||
|
popup_bufnr = int( vim.eval( "winbufnr({})".format( popup_win_id ) ) )
|
||||||
|
|
||||||
|
# We don't need to do any UI window setup here, as it's already done as
|
||||||
|
# part of the popup creation, so just pass the buffer to the View instance
|
||||||
|
self._variable_eval_view = BufView(
|
||||||
|
vim.buffers[ popup_bufnr ],
|
||||||
|
{},
|
||||||
|
self._DrawBalloonEval
|
||||||
|
)
|
||||||
|
|
||||||
|
if watch.result.IsExpandable():
|
||||||
|
# Always expand the first level
|
||||||
|
watch.result.expanded = Expandable.EXPANDED_BY_US
|
||||||
|
|
||||||
|
if watch.result.IsExpanded():
|
||||||
|
self._connection.DoRequest( partial( self._ConsumeVariables,
|
||||||
|
self._variable_eval_view.draw,
|
||||||
|
watch.result ), {
|
||||||
|
'command': 'variables',
|
||||||
|
'arguments': {
|
||||||
|
'variablesReference': watch.result.VariablesReference(),
|
||||||
|
},
|
||||||
|
} )
|
||||||
|
|
||||||
|
self._DrawBalloonEval()
|
||||||
|
|
||||||
|
def failure_handler( reason, message ):
|
||||||
|
display = [ reason ]
|
||||||
|
float_win_id = utils.DisplayBalloon( self._is_term, display, is_hover )
|
||||||
|
# record the global eval window id
|
||||||
|
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = int( float_win_id )
|
||||||
|
|
||||||
|
self._variable_eval = Watch.New( frame,
|
||||||
|
expression,
|
||||||
|
'hover' )
|
||||||
|
|
||||||
|
# Send async request
|
||||||
|
self._connection.DoRequest( handler, {
|
||||||
|
'command': 'evaluate',
|
||||||
|
'arguments': self._variable_eval.expression,
|
||||||
|
}, failure_handler )
|
||||||
|
|
||||||
|
# Return working (meanwhile)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def AddWatch( self, frame, expression ):
|
||||||
|
self._watches.append( Watch.New( frame, expression, 'watch' ) )
|
||||||
self.EvaluateWatches()
|
self.EvaluateWatches()
|
||||||
|
|
||||||
def DeleteWatch( self ):
|
def DeleteWatch( self ):
|
||||||
|
|
@ -338,19 +448,27 @@ class VariablesView( object ):
|
||||||
watch.result = WatchFailure( reason )
|
watch.result = WatchFailure( reason )
|
||||||
self._DrawWatches()
|
self._DrawWatches()
|
||||||
|
|
||||||
def ExpandVariable( self ):
|
def ExpandVariable( self, buf = None, line_num = None ):
|
||||||
if vim.current.buffer == self._vars.buf:
|
if buf is None:
|
||||||
|
buf = vim.current.buffer
|
||||||
|
|
||||||
|
if line_num is None:
|
||||||
|
line_num = vim.current.window.cursor[ 0 ]
|
||||||
|
|
||||||
|
if buf == self._vars.buf:
|
||||||
view = self._vars
|
view = self._vars
|
||||||
elif vim.current.buffer == self._watch.buf:
|
elif buf == self._watch.buf:
|
||||||
view = self._watch
|
view = self._watch
|
||||||
|
elif ( self._variable_eval_view is not None
|
||||||
|
and buf == self._variable_eval_view.buf ):
|
||||||
|
view = self._variable_eval_view
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
current_line = vim.current.window.cursor[ 0 ]
|
if line_num not in view.lines:
|
||||||
if current_line not in view.lines:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
variable = view.lines[ current_line ]
|
variable = view.lines[ line_num ]
|
||||||
|
|
||||||
if variable.IsExpanded():
|
if variable.IsExpanded():
|
||||||
# Collapse
|
# Collapse
|
||||||
|
|
@ -371,25 +489,40 @@ class VariablesView( object ):
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
def _DrawVariables( self, view, variables, indent ):
|
def _DrawVariables( self, view, variables, indent, is_short = False ):
|
||||||
assert indent > 0
|
assert indent > 0
|
||||||
for variable in variables:
|
for variable in variables:
|
||||||
line = utils.AppendToBuffer(
|
text = ''
|
||||||
view.buf,
|
if is_short:
|
||||||
'{indent}{marker}{icon} {name} ({type_}): {value}'.format(
|
text = '{indent}{icon} {name}: {value}'.format(
|
||||||
|
# We borrow 1 space of indent to draw the change marker
|
||||||
|
indent = ' ' * ( indent - 1 ),
|
||||||
|
icon = '+' if ( variable.IsExpandable()
|
||||||
|
and not variable.IsExpanded() ) else '-',
|
||||||
|
name = variable.variable.get( 'name', '' ),
|
||||||
|
value = variable.variable.get( 'value', '<unknown>' )
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text = '{indent}{marker}{icon} {name} ({type_}): {value}'.format(
|
||||||
# We borrow 1 space of indent to draw the change marker
|
# We borrow 1 space of indent to draw the change marker
|
||||||
indent = ' ' * ( indent - 1 ),
|
indent = ' ' * ( indent - 1 ),
|
||||||
marker = '*' if variable.changed else ' ',
|
marker = '*' if variable.changed else ' ',
|
||||||
icon = '+' if ( variable.IsExpandable()
|
icon = '+' if ( variable.IsExpandable()
|
||||||
and not variable.IsExpanded() ) else '-',
|
and not variable.IsExpanded() ) else '-',
|
||||||
name = variable.variable[ 'name' ],
|
name = variable.variable.get( 'name', '' ),
|
||||||
type_ = variable.variable.get( 'type', '' ),
|
type_ = variable.variable.get( 'type', '' ),
|
||||||
value = variable.variable.get( 'value',
|
value = variable.variable.get( 'value', '<unknown>' )
|
||||||
'<unknown>' ) ).split( '\n' ) )
|
)
|
||||||
|
|
||||||
|
line = utils.AppendToBuffer(
|
||||||
|
view.buf,
|
||||||
|
text.split( '\n' )
|
||||||
|
)
|
||||||
|
|
||||||
view.lines[ line ] = variable
|
view.lines[ line ] = variable
|
||||||
|
|
||||||
if variable.ShouldDrawDrillDown():
|
if variable.ShouldDrawDrillDown():
|
||||||
self._DrawVariables( view, variable.variables, indent + 2 )
|
self._DrawVariables( view, variable.variables, indent + 2, is_short )
|
||||||
|
|
||||||
def _DrawScopes( self ):
|
def _DrawScopes( self ):
|
||||||
# FIXME: The drawing is dumb and draws from scratch every time. This is
|
# FIXME: The drawing is dumb and draws from scratch every time. This is
|
||||||
|
|
@ -416,7 +549,7 @@ class VariablesView( object ):
|
||||||
'Expression: '
|
'Expression: '
|
||||||
+ watch.expression[ 'expression' ] )
|
+ watch.expression[ 'expression' ] )
|
||||||
watch.line = line
|
watch.line = line
|
||||||
self._DrawWatchResult( 2, watch )
|
self._DrawWatchResult( self._watch, 2, watch )
|
||||||
|
|
||||||
def _DrawScope( self, indent, scope ):
|
def _DrawScope( self, indent, scope ):
|
||||||
icon = '+' if scope.IsExpandable() and not scope.IsExpanded() else '-'
|
icon = '+' if scope.IsExpandable() and not scope.IsExpanded() else '-'
|
||||||
|
|
@ -432,27 +565,36 @@ class VariablesView( object ):
|
||||||
indent += 2
|
indent += 2
|
||||||
self._DrawVariables( self._vars, scope.variables, indent )
|
self._DrawVariables( self._vars, scope.variables, indent )
|
||||||
|
|
||||||
def _DrawWatchResult( self, indent, watch ):
|
def _DrawWatchResult( self, view, indent, watch, is_short = False ):
|
||||||
if not watch.result:
|
if not watch.result:
|
||||||
return
|
return
|
||||||
|
|
||||||
assert indent > 0
|
assert is_short or indent > 0
|
||||||
icon = '+' if ( watch.result.IsExpandable() and
|
|
||||||
not watch.result.IsExpanded() ) else '-'
|
|
||||||
|
|
||||||
line = '{indent}{marker}{icon} Result: {result}'.format(
|
if is_short:
|
||||||
|
# The first result is always expanded in a hover (short format)
|
||||||
|
icon = ''
|
||||||
|
marker = ''
|
||||||
|
leader = ''
|
||||||
|
else:
|
||||||
|
icon = '+' if ( watch.result.IsExpandable() and
|
||||||
|
not watch.result.IsExpanded() ) else '-'
|
||||||
|
marker = '*' if watch.result.changed else ' '
|
||||||
|
leader = ' Result: '
|
||||||
|
|
||||||
|
line = '{indent}{marker}{icon}{leader}{result}'.format(
|
||||||
# We borrow 1 space of indent to draw the change marker
|
# We borrow 1 space of indent to draw the change marker
|
||||||
indent = ' ' * ( indent - 1 ),
|
indent = ' ' * ( indent - 1 ),
|
||||||
marker = '*' if watch.result.changed else ' ',
|
marker = marker,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
|
leader = leader,
|
||||||
result = watch.result.result.get( 'result', '<unknown>' ) )
|
result = watch.result.result.get( 'result', '<unknown>' ) )
|
||||||
|
|
||||||
line = utils.AppendToBuffer( self._watch.buf, line.split( '\n' ) )
|
line = utils.AppendToBuffer( view.buf, line.split( '\n' ) )
|
||||||
self._watch.lines[ line ] = watch.result
|
view.lines[ line ] = watch.result
|
||||||
|
|
||||||
if watch.result.ShouldDrawDrillDown():
|
if watch.result.ShouldDrawDrillDown():
|
||||||
indent = 4
|
self._DrawVariables( view, watch.result.variables, indent + 2, is_short )
|
||||||
self._DrawVariables( self._watch, watch.result.variables, indent )
|
|
||||||
|
|
||||||
def _ConsumeVariables( self, draw, parent, message ):
|
def _ConsumeVariables( self, draw, parent, message ):
|
||||||
new_variables = []
|
new_variables = []
|
||||||
|
|
@ -467,7 +609,6 @@ class VariablesView( object ):
|
||||||
variable = v
|
variable = v
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
variable = Variable( variable_body )
|
variable = Variable( variable_body )
|
||||||
else:
|
else:
|
||||||
|
|
@ -489,47 +630,10 @@ class VariablesView( object ):
|
||||||
|
|
||||||
draw()
|
draw()
|
||||||
|
|
||||||
def ShowBalloon( self, frame, expression ):
|
|
||||||
"""Callback to display variable under cursor `:h ballonexpr`"""
|
|
||||||
if not self._connection:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def handler( message ):
|
|
||||||
# TODO: this result count be expandable, but we have no way to allow the
|
|
||||||
# user to interact with the balloon to expand it, unless we use a popup
|
|
||||||
# instead, but even then we don't really want to trap the cursor.
|
|
||||||
body = message[ 'body' ]
|
|
||||||
result = body[ 'result' ]
|
|
||||||
if result is None:
|
|
||||||
result = 'null'
|
|
||||||
display = [
|
|
||||||
'Type: ' + body.get( 'type', '<unknown>' ),
|
|
||||||
'Value: ' + result
|
|
||||||
]
|
|
||||||
utils.DisplayBaloon( self._is_term, display )
|
|
||||||
|
|
||||||
def failure_handler( reason, message ):
|
|
||||||
display = [ reason ]
|
|
||||||
utils.DisplayBaloon( self._is_term, display )
|
|
||||||
|
|
||||||
# Send async request
|
|
||||||
self._connection.DoRequest( handler, {
|
|
||||||
'command': 'evaluate',
|
|
||||||
'arguments': {
|
|
||||||
'expression': expression,
|
|
||||||
'frameId': frame[ 'id' ],
|
|
||||||
'context': 'hover',
|
|
||||||
}
|
|
||||||
}, failure_handler )
|
|
||||||
|
|
||||||
# Return working (meanwhile)
|
|
||||||
return '...'
|
|
||||||
|
|
||||||
|
|
||||||
def SetSyntax( self, syntax ):
|
def SetSyntax( self, syntax ):
|
||||||
|
# TODO: Switch to View.syntax
|
||||||
self._current_syntax = utils.SetSyntax( self._current_syntax,
|
self._current_syntax = utils.SetSyntax( self._current_syntax,
|
||||||
syntax,
|
syntax,
|
||||||
self._vars.buf,
|
self._vars.buf,
|
||||||
self._watch.buf )
|
self._watch.buf )
|
||||||
|
|
||||||
# vim: sw=2
|
# vim: sw=2
|
||||||
|
|
|
||||||
|
|
@ -280,8 +280,8 @@ function! Test_Conditional_Line_Breakpoint()
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 )
|
||||||
call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 )
|
call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 )
|
||||||
|
|
||||||
" Add the conditional breakpoint
|
" Add the conditional breakpoint (note , is the mapleader)
|
||||||
call feedkeys( "\\\<F9>argc==0\<CR>\<CR>", 'xt' )
|
call feedkeys( ",\<F9>argc==0\<CR>\<CR>", 'xt' )
|
||||||
call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP',
|
call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP',
|
||||||
\ 16,
|
\ 16,
|
||||||
\ 'vimspectorBPCond',
|
\ 'vimspectorBPCond',
|
||||||
|
|
@ -360,8 +360,8 @@ function! Test_Conditional_Line_Breakpoint_Hit()
|
||||||
exe 'edit' fn
|
exe 'edit' fn
|
||||||
call setpos( '.', [ 0, 14, 1 ] )
|
call setpos( '.', [ 0, 14, 1 ] )
|
||||||
|
|
||||||
" Add the conditional breakpoint (3 times)
|
" Add the conditional breakpoint (3 times) (note , is the mapleader)
|
||||||
call feedkeys( "\\\<F9>\<CR>3\<CR>", 'xt' )
|
call feedkeys( ",\<F9>\<CR>3\<CR>", 'xt' )
|
||||||
call vimspector#test#signs#AssertSignGroupSingletonAtLine(
|
call vimspector#test#signs#AssertSignGroupSingletonAtLine(
|
||||||
\ 'VimspectorBP',
|
\ 'VimspectorBP',
|
||||||
\ 14,
|
\ 14,
|
||||||
|
|
|
||||||
|
|
@ -293,8 +293,8 @@ function! Test_Conditional_Line_Breakpoint()
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 16, 1 )
|
||||||
call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 )
|
call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 16 )
|
||||||
|
|
||||||
" Add the conditional breakpoint
|
" Add the conditional breakpoint (, is mapleader)
|
||||||
call feedkeys( "\\\<F9>argc==0\<CR>\<CR>", 'xt' )
|
call feedkeys( ",\<F9>argc==0\<CR>\<CR>", 'xt' )
|
||||||
call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP',
|
call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP',
|
||||||
\ 16,
|
\ 16,
|
||||||
\ 'vimspectorBPCond',
|
\ 'vimspectorBPCond',
|
||||||
|
|
@ -370,8 +370,8 @@ function! Test_Conditional_Line_Breakpoint_Hit()
|
||||||
exe 'edit' fn
|
exe 'edit' fn
|
||||||
call setpos( '.', [ 0, 14, 1 ] )
|
call setpos( '.', [ 0, 14, 1 ] )
|
||||||
|
|
||||||
" Add the conditional breakpoint (3 times)
|
" Add the conditional breakpoint (3 times) (, is mapleader)
|
||||||
call feedkeys( "\\\<F9>\<CR>3\<CR>", 'xt' )
|
call feedkeys( ",\<F9>\<CR>3\<CR>", 'xt' )
|
||||||
call vimspector#test#signs#AssertSignGroupSingletonAtLine(
|
call vimspector#test#signs#AssertSignGroupSingletonAtLine(
|
||||||
\ 'VimspectorBP',
|
\ 'VimspectorBP',
|
||||||
\ 14,
|
\ 14,
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,9 @@ function! Test_Use_Mappings_HUMAN()
|
||||||
\ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 16 )
|
\ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 16 )
|
||||||
\ } )
|
\ } )
|
||||||
|
|
||||||
" Run to cursor
|
" Run to cursor (note , is the mapleader)
|
||||||
call cursor( 9, 1 )
|
call cursor( 9, 1 )
|
||||||
call feedkeys( "\\\<F8>", 'xt' )
|
call feedkeys( ",\<F8>", 'xt' )
|
||||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 )
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( 'simple.cpp', 9, 1 )
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 9 )
|
\ vimspector#test#signs#AssertPCIsAtLineInBuffer( 'simple.cpp', 9 )
|
||||||
|
|
|
||||||
|
|
@ -591,3 +591,199 @@ function! Test_EvaluateFailure()
|
||||||
call vimspector#test#setup#Reset()
|
call vimspector#test#setup#Reset()
|
||||||
%bwipe!
|
%bwipe!
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! Test_VariableEval()
|
||||||
|
let fn = 'testdata/cpp/simple/struct.cpp'
|
||||||
|
call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{
|
||||||
|
\ configuration: 'run-to-breakpoint'
|
||||||
|
\ } } )
|
||||||
|
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 )
|
||||||
|
|
||||||
|
" leader is ,
|
||||||
|
xmap <buffer> <Leader>d <Plug>VimspectorBalloonEval
|
||||||
|
nmap <buffer> <Leader>d <Plug>VimspectorBalloonEval
|
||||||
|
|
||||||
|
"evaluate the prev line
|
||||||
|
call setpos( '.', [ 0, 24, 8 ] )
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 24, 8 )
|
||||||
|
call feedkeys( ',d', 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_notequal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '{...}',
|
||||||
|
\ ' - i: 0',
|
||||||
|
\ ' - c: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' - fffff: 0',
|
||||||
|
\ ' + another_test: ',
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
"Close
|
||||||
|
call feedkeys( "\<Esc>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_equal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
" test selection
|
||||||
|
call setpos( '.', [ 0, 24, 8 ] )
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 24, 8 )
|
||||||
|
|
||||||
|
call feedkeys( 'viw,d', 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_notequal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '{...}',
|
||||||
|
\ ' - i: 0',
|
||||||
|
\ ' - c: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' - fffff: 0',
|
||||||
|
\ ' + another_test: ',
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
"Close
|
||||||
|
call feedkeys( "\<Esc>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_equal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
" Get back to normal mode
|
||||||
|
call feedkeys( "\<Esc>", 'xt' )
|
||||||
|
|
||||||
|
" Evaluation error
|
||||||
|
call setpos( '.', [ 0, 25, 1 ] )
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 25, 1 )
|
||||||
|
call feedkeys( ',d', 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_notequal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ 'Evaluation error',
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
"Close
|
||||||
|
call feedkeys( "\<Esc>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_equal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call vimspector#test#setup#Reset()
|
||||||
|
%bwipe!
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! Test_VariableEvalExpand()
|
||||||
|
let fn = 'testdata/cpp/simple/struct.cpp'
|
||||||
|
call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{
|
||||||
|
\ configuration: 'run-to-breakpoint'
|
||||||
|
\ } } )
|
||||||
|
|
||||||
|
call vimspector#StepOver()
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 )
|
||||||
|
|
||||||
|
" leader is ,
|
||||||
|
xmap <buffer> <Leader>d <Plug>VimspectorBalloonEval
|
||||||
|
nmap <buffer> <Leader>d <Plug>VimspectorBalloonEval
|
||||||
|
|
||||||
|
"evaluate the prev line
|
||||||
|
call setpos( '.', [ 0, 24, 8 ] )
|
||||||
|
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 24, 8 )
|
||||||
|
call feedkeys( ',d', 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_notequal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '{...}',
|
||||||
|
\ ' - i: 0',
|
||||||
|
\ ' - c: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' - fffff: 0',
|
||||||
|
\ ' + another_test: ',
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
" Expand
|
||||||
|
call feedkeys( "jjjj\<CR>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '{...}',
|
||||||
|
\ ' - i: 0',
|
||||||
|
\ ' - c: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' - fffff: 0',
|
||||||
|
\ ' - another_test: ',
|
||||||
|
\ ' - choo: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' + ints: '
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
"Collapse
|
||||||
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ AssertMatchist(
|
||||||
|
\ [
|
||||||
|
\ '{...}',
|
||||||
|
\ ' - i: 0',
|
||||||
|
\ ' - c: 0 ''\\0\{1,3}''',
|
||||||
|
\ ' - fffff: 0',
|
||||||
|
\ ' + another_test: ',
|
||||||
|
\ ],
|
||||||
|
\ getbufline( winbufnr( g:vimspector_session_windows.eval ),
|
||||||
|
\ 1,
|
||||||
|
\ '$' )
|
||||||
|
\ )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
"Close
|
||||||
|
call feedkeys( "\<Esc>", 'xt' )
|
||||||
|
|
||||||
|
call WaitForAssert( {->
|
||||||
|
\ assert_equal( v:none, g:vimspector_session_windows.eval )
|
||||||
|
\ } )
|
||||||
|
|
||||||
|
call vimspector#test#setup#Reset()
|
||||||
|
%bwipe!
|
||||||
|
endfunction
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
let g:vimspector_test_plugin_path = expand( '<sfile>:p:h:h' )
|
let g:vimspector_test_plugin_path = expand( '<sfile>:p:h:h' )
|
||||||
set mouse=a
|
set mouse=a
|
||||||
set noequalalways
|
set noequalalways
|
||||||
|
let mapleader = ','
|
||||||
|
let maplocalleader = "\<Space>"
|
||||||
|
|
||||||
let &rtp = &rtp . ',' . g:vimspector_test_plugin_path
|
let &runtimepath = &runtimepath . ',' . g:vimspector_test_plugin_path
|
||||||
|
|
||||||
filetype plugin indent on
|
filetype plugin indent on
|
||||||
syntax enable
|
syntax enable
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue