Merge pull request #197 from puremourning/use-buffers-not-windows

Allow users to close UI windows
This commit is contained in:
mergify[bot] 2020-07-11 13:50:34 +00:00 committed by GitHub
commit fe58b94bb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 244 additions and 112 deletions

View file

@ -20,12 +20,20 @@ set cpoptions&vim
" }}}
function! s:_OnServerData( channel, data ) abort
if !exists( 's:ch' ) || s:ch isnot a:channel
return
endif
py3 << EOF
_vimspector_session.OnChannelData( vim.eval( 'a:data' ) )
EOF
endfunction
function! s:_OnClose( channel ) abort
if !exists( 's:ch' ) || s:ch isnot a:channel
return
endif
echom 'Channel closed'
redraw
unlet s:ch

View file

@ -20,7 +20,7 @@ set cpoptions&vim
" }}}
function! s:_OnServerData( channel, data ) abort
if !exists( 's:job' )
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Get data after process exit' )
return
endif
@ -29,7 +29,7 @@ function! s:_OnServerData( channel, data ) abort
endfunction
function! s:_OnServerError( channel, data ) abort
if !exists( 's:job' )
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Get data after process exit' )
return
endif
@ -37,7 +37,16 @@ function! s:_OnServerError( channel, data ) abort
py3 _vimspector_session.OnServerStderr( vim.eval( 'a:data' ) )
endfunction
" FIXME: We should wait until both the exit_cb _and_ the channel closed callback
" have been received before OnServerExit?
function! s:_OnExit( channel, status ) abort
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Unexpected exit callback' )
return
endif
echom 'Channel exit with status ' . a:status
redraw
if exists( 's:job' )
@ -47,6 +56,11 @@ function! s:_OnExit( channel, status ) abort
endfunction
function! s:_OnClose( channel ) abort
if !exists( 's:job' ) || job_getchannel( s:job ) != a:channel
call ch_log( 'Channel closed after exit' )
return
endif
echom 'Channel closed'
redraw
endfunction

View file

@ -22,6 +22,14 @@ set cpoptions&vim
function! s:_OnEvent( chan_id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if !exists( 's:ch' ) || a:chan_id != s:ch
return
endif
if a:data == ['']
echom 'Channel closed'
redraw

View file

@ -22,6 +22,14 @@ set cpoptions&vim
function! s:_OnEvent( chan_id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if !exists( 's:job' ) || a:chan_id != s:job
return
endif
" In neovim, the data argument is a list.
if a:event ==# 'stdout'
py3 _vimspector_session.OnChannelData( '\n'.join( vim.eval( 'a:data' ) ) )
@ -30,11 +38,7 @@ function! s:_OnEvent( chan_id, data, event ) abort
elseif a:event ==# 'exit'
echom 'Channel exit with status ' . a:data
redraw
if exists( 's:job' )
unlet s:job
endif
" This causes terminal spam in neovim due to
" https://github.com/neovim/neovim/issues/11725
unlet s:job
py3 _vimspector_session.OnServerExit( vim.eval( 'a:data' ) )
endif
endfunction
@ -108,10 +112,22 @@ function! vimspector#internal#neojob#Reset() abort
endfunction
function! s:_OnCommandEvent( category, id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if a:data == ['']
return
endif
if !has_key( s:commands, a:category )
return
endif
if !has_key( s:commands[ a:category ], a:id )
return
endif
if a:event ==# 'stdout'
let buffer = s:commands[ a:category ][ a:id ].stdout
elseif a:event ==# 'stderr'
@ -212,8 +228,8 @@ function! vimspector#internal#neojob#CleanUpCommand( category ) abort
endif
for id in keys( s:commands[ a:category ] )
call jobstop( id )
call jobwait( id )
call jobstop( str2nr( id ) )
call jobwait( [ str2nr( id ) ] )
endfor
unlet! s:commands[ a:category ]
endfunction

View file

@ -46,12 +46,17 @@ function! vimspector#internal#neoterm#ResetEnvironment( env, old_env ) abort
endfunction
function! vimspector#internal#neoterm#Start( cmd, opts ) abort
" Prepare current buffer to be turned into a term if curwin is not set
if ! get( a:opts, 'curwin', 0 )
let mods = ''
if get( a:opts, 'vertical', 0 )
vnew
let mods .= 'vertical '
let mods .= get( a:opts, 'term_cols', '' )
else
new
let mods .= get( a:opts, 'term_rows', '' )
endif
execute mods . 'new'
endif
" HACK: Neovim's termopen doesn't support env

View file

@ -40,7 +40,6 @@ class CodeView( object ):
'breakpoints': []
}
with utils.LetCurrentWindow( self._window ):
vim.command( 'nnoremenu WinBar.Continue :call vimspector#Continue()<CR>' )
vim.command( 'nnoremenu WinBar.Next :call vimspector#StepOver()<CR>' )
@ -56,6 +55,10 @@ class CodeView( object ):
def SetCurrentFrame( self, frame ):
"""Returns True if the code window was updated with the frame, False
otherwise. False means either the frame is junk, we couldn't find the file
(or don't have the data) or the code window no longer exits."""
if self._signs[ 'vimspectorPC' ]:
vim.command( 'sign unplace {} group=VimspectorCode'.format(
self._signs[ 'vimspectorPC' ] ) )
@ -67,8 +70,25 @@ class CodeView( object ):
if 'path' not in frame[ 'source' ]:
return False
utils.JumpToWindow( self._window )
self._signs[ 'vimspectorPC' ] = self._next_sign_id
self._next_sign_id += 1
try:
vim.command( 'sign place {0} group=VimspectorCode priority=20 '
'line={1} name=vimspectorPC '
'file={2}'.format(
self._signs[ 'vimspectorPC' ],
frame[ 'line' ],
frame[ 'source' ][ 'path' ] ) )
except vim.error as e:
# Ignore 'invalid buffer name'
if 'E158' not in str( e ):
raise
if not self._window.valid:
return False
utils.JumpToWindow( self._window )
try:
utils.OpenFileInCurrentWindow( frame[ 'source' ][ 'path' ] )
except vim.error:
@ -89,16 +109,6 @@ class CodeView( object ):
frame[ 'source' ][ 'path' ] )
return False
self._signs[ 'vimspectorPC' ] = self._next_sign_id
self._next_sign_id += 1
vim.command( 'sign place {0} group=VimspectorCode priority=20 '
'line={1} name=vimspectorPC '
'file={2}'.format(
self._signs[ 'vimspectorPC' ],
frame[ 'line' ],
frame[ 'source' ][ 'path' ] ) )
self.current_syntax = utils.ToUnicode(
vim.current.buffer.options[ 'syntax' ] )
@ -210,13 +220,19 @@ class CodeView( object ):
options = {
'vertical': 1,
'term_cols': 80,
'norestore': 1,
'cwd': cwd,
'env': env,
}
window_for_start = self._window
if self._terminal_window is not None:
if self._window.valid:
window_for_start = self._window
else:
# TOOD: Where?
window_for_start = vim.current.window
if self._terminal_window is not None and self._terminal_window.valid:
assert self._terminal_buffer_number
if ( self._terminal_window.buffer.number == self._terminal_buffer_number
and int( utils.Call( 'vimspector#internal#{}term#IsFinished'.format(

View file

@ -518,6 +518,10 @@ class DebugSession( object ):
self.SetCurrentFrame( None )
def SetCurrentFrame( self, frame ):
if not frame:
self._stackTraceView.Clear()
self._variablesView.Clear()
if not self._codeView.SetCurrentFrame( frame ):
return False
@ -526,9 +530,6 @@ class DebugSession( object ):
self._stackTraceView.SetSyntax( self._codeView.current_syntax )
self._variablesView.LoadScopes( frame )
self._variablesView.EvaluateWatches()
else:
self._stackTraceView.Clear()
self._variablesView.Clear()
return True

View file

@ -77,9 +77,10 @@ class OutputView( object ):
self._ToggleFlag( category, True )
# Scroll the buffer
with utils.RestoreCurrentWindow():
with utils.RestoreCurrentBuffer( self._window ):
self._ShowOutput( category )
if self._window.valid:
with utils.RestoreCurrentWindow():
with utils.RestoreCurrentBuffer( self._window ):
self._ShowOutput( category )
def ConnectionUp( self, connection ):
self._connection = connection
@ -96,18 +97,17 @@ class OutputView( object ):
if tab_buffer.is_job:
utils.CleanUpCommand( tab_buffer.job_category or category,
self._api_prefix )
try:
vim.command( 'bdelete! {0}'.format( tab_buffer.buf.number ) )
except vim.error as e:
# FIXME: For now just ignore the "no buffers were deleted" error
if 'E516' not in str( e ):
raise
utils.CleanUpHiddenBuffer( tab_buffer.buf )
# FIXME: nunmenu the WinBar ?
self._buffers = {}
def _ShowOutput( self, category ):
if not self._window.valid:
return
utils.JumpToWindow( self._window )
vim.command( 'bu {0}'.format( self._buffers[ category ].buf.name ) )
vim.current.buffer = self._buffers[ category ].buf
vim.command( 'normal G' )
def ShowOutput( self, category ):
@ -146,8 +146,10 @@ class OutputView( object ):
def _ToggleFlag( self, category, flag ):
if self._buffers[ category ].flag != flag:
self._buffers[ category ].flag = flag
with utils.LetCurrentWindow( self._window ):
self._RenderWinBar( category )
if self._window.valid:
with utils.LetCurrentWindow( self._window ):
self._RenderWinBar( category )
def RunJobWithOutput( self, category, cmd ):
@ -155,6 +157,9 @@ class OutputView( object ):
def _CreateBuffer( self, category, file_name = None, cmd = None ):
if not self._window.valid:
return
with utils.LetCurrentWindow( self._window ):
with utils.RestoreCurrentBuffer( self._window ):
@ -185,8 +190,7 @@ class OutputView( object ):
utils.SetUpPromptBuffer( tab_buffer.buf,
'vimspector.Console',
'> ',
'vimspector#EvaluateConsole',
hidden=True )
'vimspector#EvaluateConsole' )
else:
utils.SetUpHiddenBuffer(
tab_buffer.buf,

View file

@ -36,13 +36,10 @@ class StackTraceView( object ):
self._threads = []
self._sources = {}
utils.SetUpScratchBuffer( self._buf, 'vimspector.StackTrace' )
utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
vim.current.buffer = self._buf
# FIXME: Remove all usage of "Windown" and just use buffers to prevent all
# the bugs around the window being closed.
self._win = vim.current.window
utils.SetUpUIWindow( self._win )
utils.SetUpUIWindow( vim.current.window )
vim.command( 'nnoremap <buffer> <CR> :call vimspector#GoToFrame()<CR>' )
@ -84,7 +81,7 @@ class StackTraceView( object ):
def Reset( self ):
self.Clear()
# TODO: delete the buffer ?
utils.CleanUpHiddenBuffer( self._buf )
def LoadThreads( self, infer_current_frame ):
pending_request = False
@ -311,4 +308,4 @@ class StackTraceView( object ):
def SetSyntax( self, syntax ):
self._current_syntax = utils.SetSyntax( self._current_syntax,
syntax,
self._win )
self._buf )

View file

@ -86,15 +86,18 @@ def CleanUpCommand( name, api_prefix ):
name ) )
def CleanUpHiddenBuffer( buf ):
try:
vim.command( 'bdelete! {}'.format( buf.number ) )
except vim.error as e:
# FIXME: For now just ignore the "no buffers were deleted" error
if 'E516' not in str( e ):
raise
def SetUpScratchBuffer( buf, name ):
buf.options[ 'buftype' ] = 'nofile'
buf.options[ 'swapfile' ] = False
buf.options[ 'modifiable' ] = False
buf.options[ 'modified' ] = False
buf.options[ 'readonly' ] = True
buf.options[ 'buflisted' ] = False
SetUpHiddenBuffer( buf, name )
buf.options[ 'bufhidden' ] = 'wipe'
buf.name = name
def SetUpHiddenBuffer( buf, name ):
@ -108,7 +111,7 @@ def SetUpHiddenBuffer( buf, name ):
buf.name = name
def SetUpPromptBuffer( buf, name, prompt, callback, hidden=False ):
def SetUpPromptBuffer( buf, name, prompt, callback ):
# This feature is _super_ new, so only enable when available
if not Exists( '*prompt_setprompt' ):
return SetUpHiddenBuffer( buf, name )
@ -119,7 +122,8 @@ def SetUpPromptBuffer( buf, name, prompt, callback, hidden=False ):
buf.options[ 'modified' ] = False
buf.options[ 'readonly' ] = False
buf.options[ 'buflisted' ] = False
buf.options[ 'bufhidden' ] = 'wipe' if not hidden else 'hide'
buf.options[ 'bufhidden' ] = 'hide'
buf.options[ 'textwidth' ] = 0
buf.name = name
vim.eval( "prompt_setprompt( {0}, '{1}' )".format( buf.number,
@ -177,7 +181,6 @@ def RestoreCurrentWindow():
@contextlib.contextmanager
def RestoreCurrentBuffer( window ):
# TODO: Don't trigger autoccommands when shifting buffers
old_buffer = window.buffer
try:
yield
@ -194,6 +197,14 @@ def LetCurrentWindow( window ):
yield
@contextlib.contextmanager
def LetCurrentBuffer( buf ):
with RestoreCursorPosition():
with RestoreCurrentBuffer( vim.current.window ):
vim.current.buffer = buf
yield
def JumpToWindow( window ):
vim.current.tabpage = window.tabpage
vim.current.window = window
@ -555,8 +566,11 @@ def SetSyntax( current_syntax, syntax, *args ):
if current_syntax == syntax:
return
for win in args:
with LetCurrentWindow( win ):
for buf in args:
with LetCurrentBuffer( buf ):
# We use set syn= because just setting vim.Buffer.options[ 'syntax' ]
# doesn't actually trigger the Syntax autocommand, and i'm not sure that
# 'doautocmd Syntax' is the right solution or not
vim.command( 'set syntax={}'.format( Escape( syntax ) ) )
return syntax

View file

@ -16,15 +16,11 @@
import abc
import vim
import logging
from collections import namedtuple
from functools import partial
import typing
from vimspector import utils
View = namedtuple( 'View', [ 'win', 'lines', 'draw' ] )
class Expandable:
"""Base for anything which might contain a hierarchy of values represented by
@ -104,7 +100,6 @@ class Variable( Expandable ):
self.variable = variable
class Watch:
"""Holds a user watch expression (DAP request) and the result (WatchResult)"""
def __init__( self, expression: dict ):
@ -114,29 +109,43 @@ class Watch:
self.result = None
class View:
lines: typing.Dict[ int, Expandable ]
draw: typing.Callable
def __init__( self, win, lines, draw ):
self.lines = lines
self.draw = draw
self.buf = win.buffer
utils.SetUpUIWindow( win )
class VariablesView( object ):
def __init__( self, connection, variables_win, watches_win ):
self._logger = logging.getLogger( __name__ )
utils.SetUpLogging( self._logger )
self._vars = View( variables_win, {}, self._DrawScopes )
self._watch = View( watches_win, {}, self._DrawWatches )
self._connection = connection
self._current_syntax = ''
# Allows us to hit <CR> to expand/collapse variables
with utils.LetCurrentWindow( self._vars.win ):
# Set up the "Variables" buffer in the variables_win
self._scopes: typing.List[ Scope ] = []
self._vars = View( variables_win, {}, self._DrawScopes )
utils.SetUpHiddenBuffer( self._vars.buf, 'vimspector.Variables' )
with utils.LetCurrentWindow( variables_win ):
vim.command(
'nnoremap <buffer> <CR> :call vimspector#ExpandVariable()<CR>' )
# List of current scopes of type Scope
self._scopes: typing.List[ 'Scope' ] = []
# List of current Watches of type Watch
self._watches: typing.List[ 'Watch' ] = []
# Allows us to hit <CR> to expand/collapse variables
with utils.LetCurrentWindow( self._watch.win ):
# Set up the "Watches" buffer in the watches_win (and create a WinBar in
# there)
self._watches: typing.List[ Watch ] = []
self._watch = View( watches_win, {}, self._DrawWatches )
utils.SetUpPromptBuffer( self._watch.buf,
'vimspector.Watches',
'Expression: ',
'vimspector#AddWatchPrompt' )
with utils.LetCurrentWindow( watches_win ):
vim.command(
'nnoremap <buffer> <CR> :call vimspector#ExpandVariable()<CR>' )
vim.command(
@ -149,15 +158,7 @@ class VariablesView( object ):
vim.command( 'nnoremenu 1.3 WinBar.Delete '
':call vimspector#DeleteWatch()<CR>' )
utils.SetUpScratchBuffer( self._vars.win.buffer, 'vimspector.Variables' )
utils.SetUpPromptBuffer( self._watch.win.buffer,
'vimspector.Watches',
'Expression: ',
'vimspector#AddWatchPrompt' )
utils.SetUpUIWindow( self._vars.win )
utils.SetUpUIWindow( self._watch.win )
# Set the (global!) balloon expr if supported
has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) )
has_balloon_term = int( vim.eval( "has( 'balloon_eval_term' )" ) )
@ -181,10 +182,10 @@ class VariablesView( object ):
self._is_term = not bool( int( vim.eval( "has( 'gui_running' )" ) ) )
def Clear( self ):
with utils.ModifiableScratchBuffer( self._vars.win.buffer ):
utils.ClearBuffer( self._vars.win.buffer )
with utils.ModifiableScratchBuffer( self._watch.win.buffer ):
utils.ClearBuffer( self._watch.win.buffer )
with utils.ModifiableScratchBuffer( self._vars.buf ):
utils.ClearBuffer( self._vars.buf )
with utils.ModifiableScratchBuffer( self._watch.buf ):
utils.ClearBuffer( self._watch.buf )
self._current_syntax = ''
def ConnectionUp( self, connection ):
@ -198,8 +199,8 @@ class VariablesView( object ):
for k, v in self._oldoptions.items():
vim.options[ k ] = v
vim.command( 'bdelete! ' + str( self._watch.win.buffer.number ) )
vim.command( 'bdelete! ' + str( self._vars.win.buffer.number ) )
utils.CleanUpHiddenBuffer( self._vars.buf )
utils.CleanUpHiddenBuffer( self._watch.buf )
def LoadScopes( self, frame ):
def scopes_consumer( message ):
@ -256,8 +257,8 @@ class VariablesView( object ):
self.EvaluateWatches()
def DeleteWatch( self ):
if vim.current.window != self._watch.win:
utils.UserMessage( 'Not a watch window' )
if vim.current.buffer != self._watch.buf:
utils.UserMessage( 'Not a watch buffer' )
return
current_line = vim.current.window.cursor[ 0 ]
@ -305,9 +306,9 @@ class VariablesView( object ):
self._DrawWatches()
def ExpandVariable( self ):
if vim.current.window == self._vars.win:
if vim.current.buffer == self._vars.buf:
view = self._vars
elif vim.current.window == self._watch.win:
elif vim.current.buffer == self._watch.buf:
view = self._watch
else:
return
@ -341,7 +342,7 @@ class VariablesView( object ):
assert indent > 0
for variable in variables:
line = utils.AppendToBuffer(
view.win.buffer,
view.buf,
'{indent}{marker}{icon} {name} ({type_}): {value}'.format(
# We borrow 1 space of indent to draw the change marker
indent = ' ' * ( indent - 1 ),
@ -363,8 +364,8 @@ class VariablesView( object ):
# However it is pretty inefficient.
self._vars.lines.clear()
with utils.RestoreCursorPosition():
with utils.ModifiableScratchBuffer( self._vars.win.buffer ):
utils.ClearBuffer( self._vars.win.buffer )
with utils.ModifiableScratchBuffer( self._vars.buf ):
utils.ClearBuffer( self._vars.buf )
for scope in self._scopes:
self._DrawScope( 0, scope )
@ -374,11 +375,11 @@ class VariablesView( object ):
# However it is pretty inefficient.
self._watch.lines.clear()
with utils.RestoreCursorPosition():
with utils.ModifiableScratchBuffer( self._watch.win.buffer ):
utils.ClearBuffer( self._watch.win.buffer )
utils.AppendToBuffer( self._watch.win.buffer, 'Watches: ----' )
with utils.ModifiableScratchBuffer( self._watch.buf ):
utils.ClearBuffer( self._watch.buf )
utils.AppendToBuffer( self._watch.buf, 'Watches: ----' )
for watch in self._watches:
line = utils.AppendToBuffer( self._watch.win.buffer,
line = utils.AppendToBuffer( self._watch.buf,
'Expression: '
+ watch.expression[ 'expression' ] )
watch.line = line
@ -387,7 +388,7 @@ class VariablesView( object ):
def _DrawScope( self, indent, scope ):
icon = '+' if scope.IsExpandable() and not scope.IsExpandedByUser() else '-'
line = utils.AppendToBuffer( self._vars.win.buffer,
line = utils.AppendToBuffer( self._vars.buf,
'{0}{1} Scope: {2}'.format(
' ' * indent,
icon,
@ -413,7 +414,7 @@ class VariablesView( object ):
icon = icon,
result = watch.result.result.get( 'result', '<unknown>' ) )
line = utils.AppendToBuffer( self._watch.win.buffer, line.split( '\n' ) )
line = utils.AppendToBuffer( self._watch.buf, line.split( '\n' ) )
self._watch.lines[ line ] = watch.result
if watch.result.ShouldDrawDrillDown():
@ -495,7 +496,7 @@ class VariablesView( object ):
def SetSyntax( self, syntax ):
self._current_syntax = utils.SetSyntax( self._current_syntax,
syntax,
self._vars.win,
self._watch.win )
self._vars.buf,
self._watch.buf )
# vim: sw=2

View file

@ -9,7 +9,10 @@
"classPaths": [ "${workspaceRoot}/target/classes" ],
"args": "hello world!",
"stopOnEntry": true,
"console": "integratedTerminal"
"console": "integratedTerminal",
"stepFilters": {
"skipClasses": [ "$$JDK" ]
}
}
},
"Java Attach": {
@ -19,7 +22,10 @@
"sourcePaths": [ "${workspaceRoot}/src/main/java" ],
"stopOnEntry": true,
"hostName": "localhost",
"port": "${JVMDebugPort}"
"port": "${JVMDebugPort}",
"stepFilters": {
"skipClasses": [ "$$JDK" ]
}
}
},
"Attach with vscode-javac": {

View file

@ -0,0 +1,24 @@
let g:ycm_java_jdtls_extension_path = [
\ expand( '<sfile>:p:h:h:h:h:h' ) . '/gadgets/macos'
\ ]
let s:jdt_ls_debugger_port = 0
function! s:StartDebugging()
if s:jdt_ls_debugger_port <= 0
" Get the DAP port
let s:jdt_ls_debugger_port = youcompleteme#GetCommandResponse(
\ 'ExecuteCommand',
\ 'vscode.java.startDebugSession' )
if s:jdt_ls_debugger_port == ''
echom "Unable to get DAP port - is YCM initialized?"
let s:jdt_ls_debugger_port = 0
return
endif
endif
" Start debugging with the DAP port
call vimspector#LaunchWithSettings( { 'DAPPort': s:jdt_ls_debugger_port } )
endfunction
nnoremap <silent> <buffer> <Leader><F5> :call <SID>StartDebugging()<CR>

View file

@ -4,7 +4,7 @@
<artifactId>TestApplication</artifactId>
<version>1</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

View file

@ -0,0 +1,8 @@
package com.vimspector.test;
class Base {
public String DoSomething()
{
return "";
}
}

View file

@ -24,6 +24,16 @@ public class TestApplication {
}
}
private static class TestGeneric<T extends Base > {
T t;
public TestGeneric( T t ) {
this.t = t;
}
public void DoSomethingUseful() {
System.out.println( t.DoSomething() );
}
}
private static <T extends Base> void DoGeneric( T b ) {
TestGeneric<T> foo = new TestGeneric<>( b );
foo.DoSomethingUseful();