WIP: Make multi-session debugging sort of work
This passes the session id around everywhere and ensures that things like buffer names are all unique. We now have the _vimspector_session update to point to the active (or last accessed tab). This feels fairly natural and mostly seems to work. NOTE: in vscode there is no "multiple tabs" - they actually add the subprocesses to the stack trace somehow and when you click on a frame in that it switches to the session for that process. Each process PC is visible in the editor. It's kind of confusing. Things still broken: - vimspector_session_windows - breakpoints need to be project-wide - PC display (how to show "all" PCs, or just show the current one for the current tab ?) - it would be nice for the tterminal buffers to be visible on all tabs. not sure how to do that.
This commit is contained in:
parent
0cdab6be4e
commit
2f05a7f66a
16 changed files with 261 additions and 99 deletions
|
|
@ -28,9 +28,8 @@ function! s:_OnServerData( session_id, channel, data ) abort
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
py3 << EOF
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnChannelData(
|
||||||
_vimspector_session.OnChannelData( vim.eval( 'a:data' ) )
|
\ vim.eval( 'a:data' ) )
|
||||||
EOF
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:_OnClose( session_id, channel ) abort
|
function! s:_OnClose( session_id, channel ) abort
|
||||||
|
|
@ -42,7 +41,7 @@ function! s:_OnClose( session_id, channel ) abort
|
||||||
echom 'Channel closed'
|
echom 'Channel closed'
|
||||||
redraw
|
redraw
|
||||||
unlet s:channels[ a:session_id ]
|
unlet s:channels[ a:session_id ]
|
||||||
py3 _vimspector_session.OnServerExit( 0 )
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerExit( 0 )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! vimspector#internal#channel#StartDebugSession(
|
function! vimspector#internal#channel#StartDebugSession(
|
||||||
|
|
@ -99,9 +98,8 @@ function! vimspector#internal#channel#Send( session_id, msg ) abort
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! vimspector#internal#channel#Timeout( session_id, id ) abort
|
function! vimspector#internal#channel#Timeout( session_id, id ) abort
|
||||||
py3 << EOF
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnRequestTimeout(
|
||||||
_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) )
|
\ vim.eval( 'a:id' ) )
|
||||||
EOF
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:_ChannelExists( session_id ) abort
|
function! s:_ChannelExists( session_id ) abort
|
||||||
|
|
@ -149,7 +147,7 @@ function! vimspector#internal#channel#StopDebugSession( session_id ) abort
|
||||||
call s:_OnServerData( s:channels[ a:session_id ], data )
|
call s:_OnServerData( s:channels[ a:session_id ], data )
|
||||||
endwhile
|
endwhile
|
||||||
if has_key( s:channels, a:session_id )
|
if has_key( s:channels, a:session_id )
|
||||||
call s:_OnClose( s:channels[ a:session_id ] )
|
call s:_OnClose( a:session_id, s:channels[ a:session_id ] )
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ function! s:_OnServerData( session_id, channel, data ) abort
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
py3 _vimspector_session.OnChannelData( vim.eval( 'a:data' ) )
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnChannelData(
|
||||||
|
\ vim.eval( 'a:data' ) )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:_OnServerError( session_id, channel, data ) abort
|
function! s:_OnServerError( session_id, channel, data ) abort
|
||||||
|
|
@ -39,7 +40,8 @@ function! s:_OnServerError( session_id, channel, data ) abort
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
py3 _vimspector_session.OnServerStderr( vim.eval( 'a:data' ) )
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerStderr(
|
||||||
|
\ vim.eval( 'a:data' ) )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,7 +60,8 @@ function! s:_OnExit( session_id, channel, status ) abort
|
||||||
if has_key( s:jobs, a:session_id )
|
if has_key( s:jobs, a:session_id )
|
||||||
unlet s:jobs[ a:session_id ]
|
unlet s:jobs[ a:session_id ]
|
||||||
endif
|
endif
|
||||||
py3 _vimspector_session.OnServerExit( vim.eval( 'a:status' ) )
|
py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerExit(
|
||||||
|
\ vim.eval( 'a:status' ) )
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:_OnClose( session_id, channel ) abort
|
function! s:_OnClose( session_id, channel ) abort
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,22 @@ endif
|
||||||
|
|
||||||
function! vimspector#internal#state#Reset() abort
|
function! vimspector#internal#state#Reset() abort
|
||||||
try
|
try
|
||||||
py3 import vim
|
py3 <<EOF
|
||||||
py3 _vimspector_session = __import__(
|
|
||||||
\ "vimspector",
|
import vim
|
||||||
\ fromlist=[ "session_manager" ] ).session_manager.Get().NewSession(
|
|
||||||
\ vim.eval( 's:prefix' ) )
|
_vimspector_session_man = __import__(
|
||||||
|
"vimspector",
|
||||||
|
fromlist=[ "session_manager" ] ).session_manager.Get()
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
_vimspector_session = _vimspector_session_man.NewSession(
|
||||||
|
vim.eval( 's:prefix' ) )
|
||||||
|
|
||||||
|
def _VimspectorSession( session_id ):
|
||||||
|
return _vimspector_session_man.GetSession( int( session_id ) )
|
||||||
|
|
||||||
|
EOF
|
||||||
catch /.*/
|
catch /.*/
|
||||||
echohl WarningMsg
|
echohl WarningMsg
|
||||||
echom 'Exception while loading vimspector:' v:exception
|
echom 'Exception while loading vimspector:' v:exception
|
||||||
|
|
@ -47,6 +58,22 @@ function! vimspector#internal#state#GetAPIPrefix() abort
|
||||||
return s:prefix
|
return s:prefix
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! vimspector#internal#state#SwitchToSession( id ) abort
|
||||||
|
py3 _vimspector_session = _VimspectorSession( vim.eval( 'a:id' ) )
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
|
function! vimspector#internal#state#OnTabEnter() abort
|
||||||
|
py3 <<EOF
|
||||||
|
session = _vimspector_session_man.SessionForTab(
|
||||||
|
int( vim.eval( 'tabpagenr()' ) ) )
|
||||||
|
|
||||||
|
if session is not None:
|
||||||
|
_vimspector_session = session
|
||||||
|
EOF
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
|
||||||
" Boilerplate {{{
|
" Boilerplate {{{
|
||||||
let &cpoptions=s:save_cpo
|
let &cpoptions=s:save_cpo
|
||||||
unlet s:save_cpo
|
unlet s:save_cpo
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,7 @@ augroup END
|
||||||
augroup Vimspector
|
augroup Vimspector
|
||||||
autocmd!
|
autocmd!
|
||||||
autocmd BufNew * call vimspector#OnBufferCreated( expand( '<afile>' ) )
|
autocmd BufNew * call vimspector#OnBufferCreated( expand( '<afile>' ) )
|
||||||
|
autocmd TabEnter * call vimspector#internal#state#OnTabEnter()
|
||||||
augroup END
|
augroup END
|
||||||
|
|
||||||
" boilerplate {{{
|
" boilerplate {{{
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,24 @@ class ServerBreakpointHandler( object ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: THis really should be project scope and not associated with a debug
|
||||||
|
# session. Breakpoints set by the user should be independent and breakpoints for
|
||||||
|
# the current active session should be associated with the session when they are
|
||||||
|
# in use.
|
||||||
|
#
|
||||||
|
# Questions include:
|
||||||
|
# 1. what happens if we set/chnage a breakpiont in session #2 while session #1
|
||||||
|
# is active ? Maybe we re-send the breakpoints to _all_ active sessions?
|
||||||
|
#
|
||||||
|
# More...
|
||||||
class ProjectBreakpoints( object ):
|
class ProjectBreakpoints( object ):
|
||||||
def __init__( self ):
|
def __init__( self, session_id ):
|
||||||
self._connection = None
|
self._connection = None
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger, session_id )
|
||||||
|
|
||||||
# These are the user-entered breakpoints.
|
# These are the user-entered breakpoints. NOTE: if updating this, also
|
||||||
|
# update Copy()
|
||||||
self._line_breakpoints = defaultdict( list )
|
self._line_breakpoints = defaultdict( list )
|
||||||
self._func_breakpoints = []
|
self._func_breakpoints = []
|
||||||
self._exception_breakpoints = None
|
self._exception_breakpoints = None
|
||||||
|
|
@ -91,6 +102,12 @@ class ProjectBreakpoints( object ):
|
||||||
# FIXME: If the adapter type changes, we should probably forget this ?
|
# FIXME: If the adapter type changes, we should probably forget this ?
|
||||||
|
|
||||||
|
|
||||||
|
def Copy( self, other: 'ProjectBreakpoints' ):
|
||||||
|
self._line_breakpoints = dict( other._line_breakpoints )
|
||||||
|
self._func_breakpoints = list( other._func_breakpoints )
|
||||||
|
if other._exception_breakpoints is not None:
|
||||||
|
self._exception_breakpoints = dict( other._exception_breakpoints )
|
||||||
|
|
||||||
def BreakpointsAsQuickFix( self ):
|
def BreakpointsAsQuickFix( self ):
|
||||||
# FIXME: Handling of breakpoints is a mess, split between _codeView and this
|
# FIXME: Handling of breakpoints is a mess, split between _codeView and this
|
||||||
# object. This makes no sense and should be centralised so that we don't
|
# object. This makes no sense and should be centralised so that we don't
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,20 @@ from collections import defaultdict
|
||||||
|
|
||||||
from vimspector import utils, terminal, signs
|
from vimspector import utils, terminal, signs
|
||||||
|
|
||||||
|
NEXT_SIGN_ID = 1
|
||||||
|
|
||||||
|
|
||||||
class CodeView( object ):
|
class CodeView( object ):
|
||||||
def __init__( self, window, api_prefix ):
|
def __init__( self, session_id, window, api_prefix ):
|
||||||
self._window = window
|
self._window = window
|
||||||
self._api_prefix = api_prefix
|
self._api_prefix = api_prefix
|
||||||
|
|
||||||
self._terminal = None
|
self._terminal = None
|
||||||
self.current_syntax = None
|
self.current_syntax = None
|
||||||
|
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger )
|
||||||
|
|
||||||
# FIXME: This ID is by group, so should be module scope
|
|
||||||
self._next_sign_id = 1
|
|
||||||
self._breakpoints = defaultdict( list )
|
self._breakpoints = defaultdict( list )
|
||||||
self._signs = {
|
self._signs = {
|
||||||
'vimspectorPC': None,
|
'vimspectorPC': None,
|
||||||
|
|
@ -92,8 +92,9 @@ class CodeView( object ):
|
||||||
self._UndisplayPC( clear_pc = False )
|
self._UndisplayPC( clear_pc = False )
|
||||||
|
|
||||||
# FIXME: Do we relly need to keep using up IDs ?
|
# FIXME: Do we relly need to keep using up IDs ?
|
||||||
self._signs[ 'vimspectorPC' ] = self._next_sign_id
|
global NEXT_SIGN_ID
|
||||||
self._next_sign_id += 1
|
self._signs[ 'vimspectorPC' ] = NEXT_SIGN_ID
|
||||||
|
NEXT_SIGN_ID += 1
|
||||||
|
|
||||||
sign = 'vimspectorPC'
|
sign = 'vimspectorPC'
|
||||||
# If there's also a breakpoint on this line, use vimspectorPCBP
|
# If there's also a breakpoint on this line, use vimspectorPCBP
|
||||||
|
|
@ -247,8 +248,9 @@ class CodeView( object ):
|
||||||
if 'line' not in breakpoint:
|
if 'line' not in breakpoint:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sign_id = self._next_sign_id
|
global NEXT_SIGN_ID
|
||||||
self._next_sign_id += 1
|
sign_id = NEXT_SIGN_ID
|
||||||
|
NEXT_SIGN_ID += 1
|
||||||
self._signs[ 'breakpoints' ].append( sign_id )
|
self._signs[ 'breakpoints' ].append( sign_id )
|
||||||
if utils.BufferExists( file_name ):
|
if utils.BufferExists( file_name ):
|
||||||
signs.PlaceSign( sign_id,
|
signs.PlaceSign( sign_id,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from vimspector.debug_session import DebugSession
|
from vimspector.debug_session import DebugSession
|
||||||
from vimspector import session_manager
|
from vimspector import session_manager, gadgets, utils
|
||||||
|
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
|
|
@ -25,22 +25,53 @@ class Debugpy( object ):
|
||||||
|
|
||||||
def __init__( self, debug_session: DebugSession ):
|
def __init__( self, debug_session: DebugSession ):
|
||||||
self.parent = debug_session
|
self.parent = debug_session
|
||||||
|
self.queue = []
|
||||||
|
|
||||||
|
def LaunchSubprocessDebugSession( self, result ):
|
||||||
|
launch_arguments = self.queue.pop( 0 )
|
||||||
|
|
||||||
|
if result == 1:
|
||||||
|
session = session_manager.Get().NewSession( self.parent._api_prefix )
|
||||||
|
|
||||||
|
# Inject the launch config (HACK!). This will actually mean that the
|
||||||
|
# configuration passed below is ignored.
|
||||||
|
session._launch_config = launch_arguments
|
||||||
|
|
||||||
|
# FIXME: We probably do need to add a StartWithLauncArguments and somehow
|
||||||
|
# tell the new session that it shoud not support "Restart" requests ?
|
||||||
|
#
|
||||||
|
# In fact, what even would Reset do... ?
|
||||||
|
session._breakpoints.Copy( self.parent._breakpoints )
|
||||||
|
session._StartWithConfiguration( { 'configuration': launch_arguments },
|
||||||
|
launch_arguments[ 'connect' ] )
|
||||||
|
|
||||||
|
self.HandleNext()
|
||||||
|
|
||||||
|
|
||||||
def OnEvent_debugpyAttach( self, message ):
|
def OnEvent_debugpyAttach( self, message ):
|
||||||
# Debugpy sends us the contents of a launch request that we should use. We
|
# Debugpy sends us the contents of a launch request that we should use. We
|
||||||
# probaly just jave to guess the rest
|
# probaly just jave to guess the rest
|
||||||
launch_argyments = message[ 'body' ]
|
launch_arguments = message[ 'body' ]
|
||||||
session = session_manager.Get().NewSession( self.parent._api_prefix )
|
self.queue.append( launch_arguments )
|
||||||
|
|
||||||
# Inject the launch config (HACK!). This will actually mean that the
|
# We use a queue because the confirm mechanism is quasi-modal and we can't
|
||||||
# configuration passed below is ignored.
|
# do multiple 'confirm' dialogs at once. It's not uncommon for
|
||||||
session._launch_config = launch_argyments
|
# multiprocessing to create multiple subprocesses all at the same time.
|
||||||
|
if len( self.queue ) == 1:
|
||||||
|
self.HandleNext()
|
||||||
|
|
||||||
# FIXME: We probably do need to add a StartWithLauncArguments and somehow
|
def HandleNext( self ):
|
||||||
# tell the new session that it shoud not support "Restart" requests ?
|
if not self.queue:
|
||||||
#
|
return
|
||||||
# In fact, what even would Reset do... ?
|
|
||||||
session._StartWithConfiguration( self.parent._configuration,
|
launch_argyments = self.queue[ 0 ]
|
||||||
self.parent._adapter )
|
pid = launch_argyments[ 'subProcessId' ]
|
||||||
|
|
||||||
|
utils.Confirm(
|
||||||
|
self.parent._api_prefix,
|
||||||
|
f"Subprocess {pid} was launched.\nAttach to it in a new tab?",
|
||||||
|
self.LaunchSubprocessDebugSession,
|
||||||
|
default_value = 1,
|
||||||
|
options = [ 'Yes', 'No' ],
|
||||||
|
keys = [ 'y', 'n' ] )
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ class PendingRequest( object ):
|
||||||
|
|
||||||
class DebugAdapterConnection( object ):
|
class DebugAdapterConnection( object ):
|
||||||
def __init__( self, handlers, session_id, send_func ):
|
def __init__( self, handlers, session_id, send_func ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger, session_id )
|
||||||
|
|
||||||
self._Write = send_func
|
self._Write = send_func
|
||||||
self._SetState( 'READ_HEADER' )
|
self._SetState( 'READ_HEADER' )
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,13 @@ class DebugSession( object ):
|
||||||
def __init__( self, session_id, session_manager, api_prefix ):
|
def __init__( self, session_id, session_manager, api_prefix ):
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.manager = session_manager
|
self.manager = session_manager
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger, session_id )
|
||||||
|
|
||||||
self._api_prefix = api_prefix
|
self._api_prefix = api_prefix
|
||||||
|
|
||||||
self._logger.info( "**** INITIALISING NEW VIMSPECTOR SESSION ****" )
|
self._logger.info( "**** INITIALISING NEW VIMSPECTOR SESSION FOR ID "
|
||||||
|
f"{session_id } ****" )
|
||||||
self._logger.info( "API is: {}".format( api_prefix ) )
|
self._logger.info( "API is: {}".format( api_prefix ) )
|
||||||
self._logger.info( 'VIMSPECTOR_HOME = %s', VIMSPECTOR_HOME )
|
self._logger.info( 'VIMSPECTOR_HOME = %s', VIMSPECTOR_HOME )
|
||||||
self._logger.info( 'gadgetDir = %s',
|
self._logger.info( 'gadgetDir = %s',
|
||||||
|
|
@ -62,7 +63,7 @@ class DebugSession( object ):
|
||||||
self._stackTraceView = None
|
self._stackTraceView = None
|
||||||
self._variablesView = None
|
self._variablesView = None
|
||||||
self._outputView = None
|
self._outputView = None
|
||||||
self._breakpoints = breakpoints.ProjectBreakpoints()
|
self._breakpoints = breakpoints.ProjectBreakpoints( session_id )
|
||||||
self._splash_screen = None
|
self._splash_screen = None
|
||||||
self._remote_term = None
|
self._remote_term = None
|
||||||
|
|
||||||
|
|
@ -414,7 +415,13 @@ class DebugSession( object ):
|
||||||
if self._uiTab:
|
if self._uiTab:
|
||||||
self._logger.debug( "Clearing down UI" )
|
self._logger.debug( "Clearing down UI" )
|
||||||
|
|
||||||
del vim.vars[ 'vimspector_session_windows' ]
|
try:
|
||||||
|
# FIXME: vimspector_session_windows is totally buseted with multiple
|
||||||
|
# sessions
|
||||||
|
del vim.vars[ 'vimspector_session_windows' ]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
vim.current.tabpage = self._uiTab
|
vim.current.tabpage = self._uiTab
|
||||||
|
|
||||||
self._splash_screen = utils.HideSplash( self._api_prefix,
|
self._splash_screen = utils.HideSplash( self._api_prefix,
|
||||||
|
|
@ -673,6 +680,15 @@ class DebugSession( object ):
|
||||||
|
|
||||||
def _SetUpUI( self ):
|
def _SetUpUI( self ):
|
||||||
vim.command( 'tab split' )
|
vim.command( 'tab split' )
|
||||||
|
|
||||||
|
# Switch to this session now that we've made it visible. Note that the
|
||||||
|
# TabEnter autocmd does trigger when the above is run, but that's before the
|
||||||
|
# following line assigns the tab to this session, so when we try to find
|
||||||
|
# this session by tab number, it's not found. So we have to manually switch
|
||||||
|
# to it when creating a new tab.
|
||||||
|
utils.Call( 'vimspector#internal#state#SwitchToSession',
|
||||||
|
self.session_id )
|
||||||
|
|
||||||
self._uiTab = vim.current.tabpage
|
self._uiTab = vim.current.tabpage
|
||||||
|
|
||||||
mode = settings.Get( 'ui_mode' )
|
mode = settings.Get( 'ui_mode' )
|
||||||
|
|
@ -716,7 +732,9 @@ class DebugSession( object ):
|
||||||
def _SetUpUIHorizontal( self ):
|
def _SetUpUIHorizontal( self ):
|
||||||
# Code window
|
# Code window
|
||||||
code_window = vim.current.window
|
code_window = vim.current.window
|
||||||
self._codeView = code.CodeView( code_window, self._api_prefix )
|
self._codeView = code.CodeView( self.session_id,
|
||||||
|
code_window,
|
||||||
|
self._api_prefix )
|
||||||
|
|
||||||
# Call stack
|
# Call stack
|
||||||
vim.command(
|
vim.command(
|
||||||
|
|
@ -741,7 +759,8 @@ class DebugSession( object ):
|
||||||
with utils.LetCurrentWindow( stack_trace_window ):
|
with utils.LetCurrentWindow( stack_trace_window ):
|
||||||
vim.command( f'{ one_third }wincmd _' )
|
vim.command( f'{ one_third }wincmd _' )
|
||||||
|
|
||||||
self._variablesView = variables.VariablesView( vars_window,
|
self._variablesView = variables.VariablesView( self,
|
||||||
|
vars_window,
|
||||||
watch_window )
|
watch_window )
|
||||||
|
|
||||||
# Output/logging
|
# Output/logging
|
||||||
|
|
@ -749,7 +768,8 @@ class DebugSession( object ):
|
||||||
vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' )
|
vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' )
|
||||||
output_window = vim.current.window
|
output_window = vim.current.window
|
||||||
self._outputView = output.DAPOutputView( output_window,
|
self._outputView = output.DAPOutputView( output_window,
|
||||||
self._api_prefix )
|
self._api_prefix,
|
||||||
|
session_id = self.session_id )
|
||||||
|
|
||||||
# TODO: If/when we support multiple sessions, we'll need some way to
|
# TODO: If/when we support multiple sessions, we'll need some way to
|
||||||
# indicate which tab was created and store all the tabs
|
# indicate which tab was created and store all the tabs
|
||||||
|
|
@ -772,7 +792,9 @@ class DebugSession( object ):
|
||||||
def _SetUpUIVertical( self ):
|
def _SetUpUIVertical( self ):
|
||||||
# Code window
|
# Code window
|
||||||
code_window = vim.current.window
|
code_window = vim.current.window
|
||||||
self._codeView = code.CodeView( code_window, self._api_prefix )
|
self._codeView = code.CodeView( self.session_id,
|
||||||
|
code_window,
|
||||||
|
self._api_prefix )
|
||||||
|
|
||||||
# Call stack
|
# Call stack
|
||||||
vim.command(
|
vim.command(
|
||||||
|
|
@ -799,7 +821,8 @@ class DebugSession( object ):
|
||||||
with utils.LetCurrentWindow( stack_trace_window ):
|
with utils.LetCurrentWindow( stack_trace_window ):
|
||||||
vim.command( f'{ one_third }wincmd |' )
|
vim.command( f'{ one_third }wincmd |' )
|
||||||
|
|
||||||
self._variablesView = variables.VariablesView( vars_window,
|
self._variablesView = variables.VariablesView( self,
|
||||||
|
vars_window,
|
||||||
watch_window )
|
watch_window )
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -808,7 +831,8 @@ class DebugSession( object ):
|
||||||
vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' )
|
vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' )
|
||||||
output_window = vim.current.window
|
output_window = vim.current.window
|
||||||
self._outputView = output.DAPOutputView( output_window,
|
self._outputView = output.DAPOutputView( output_window,
|
||||||
self._api_prefix )
|
self._api_prefix,
|
||||||
|
session_id = self.session_id )
|
||||||
|
|
||||||
# TODO: If/when we support multiple sessions, we'll need some way to
|
# TODO: If/when we support multiple sessions, we'll need some way to
|
||||||
# indicate which tab was created and store all the tabs
|
# indicate which tab was created and store all the tabs
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,17 @@ class OutputView( object ):
|
||||||
files or the output of commands."""
|
files or the output of commands."""
|
||||||
_buffers: typing.Dict[ str, TabBuffer ]
|
_buffers: typing.Dict[ str, TabBuffer ]
|
||||||
|
|
||||||
def __init__( self, window, api_prefix ):
|
def __init__( self, window, api_prefix, session_id = None ):
|
||||||
self._window = window
|
self._window = window
|
||||||
self._buffers = {}
|
self._buffers = {}
|
||||||
self._api_prefix = api_prefix
|
self._api_prefix = api_prefix
|
||||||
VIEWS.add( self )
|
VIEWS.add( self )
|
||||||
# FIXME: hack?
|
|
||||||
self._session_id = hash( self )
|
if session_id is None:
|
||||||
|
# FIXME: hack?
|
||||||
|
self._session_id = hash( self )
|
||||||
|
else:
|
||||||
|
self._session_id = session_id
|
||||||
|
|
||||||
def Print( self, categroy, text ):
|
def Print( self, categroy, text ):
|
||||||
self._Print( 'server', text.splitlines() )
|
self._Print( 'server', text.splitlines() )
|
||||||
|
|
@ -176,9 +180,9 @@ class OutputView( object ):
|
||||||
|
|
||||||
if cmd is not None:
|
if cmd is not None:
|
||||||
out = utils.SetUpCommandBuffer(
|
out = utils.SetUpCommandBuffer(
|
||||||
self._session_id, # TODO: not really a session id
|
self._session_id,
|
||||||
cmd,
|
cmd,
|
||||||
category,
|
utils.BufferNameForSession( category, self._session_id ),
|
||||||
self._api_prefix,
|
self._api_prefix,
|
||||||
completion_handler = completion_handler )
|
completion_handler = completion_handler )
|
||||||
|
|
||||||
|
|
@ -191,6 +195,8 @@ class OutputView( object ):
|
||||||
else:
|
else:
|
||||||
name = 'vimspector.Output:{0}'.format( category )
|
name = 'vimspector.Output:{0}'.format( category )
|
||||||
|
|
||||||
|
name = utils.BufferNameForSession( name, self._session_id )
|
||||||
|
|
||||||
tab_buffer = TabBuffer( utils.NewEmptyBuffer(), len( self._buffers ) )
|
tab_buffer = TabBuffer( utils.NewEmptyBuffer(), len( self._buffers ) )
|
||||||
|
|
||||||
self._buffers[ category ] = tab_buffer
|
self._buffers[ category ] = tab_buffer
|
||||||
|
|
@ -253,8 +259,8 @@ class OutputView( object ):
|
||||||
|
|
||||||
class DAPOutputView( OutputView ):
|
class DAPOutputView( OutputView ):
|
||||||
"""Specialised OutputView which adds the DAP Console (REPL)"""
|
"""Specialised OutputView which adds the DAP Console (REPL)"""
|
||||||
def __init__( self, *args ):
|
def __init__( self, *args, **kwargs ):
|
||||||
super().__init__( *args )
|
super().__init__( *args, **kwargs )
|
||||||
|
|
||||||
self._connection = None
|
self._connection = None
|
||||||
for b in set( BUFFER_MAP.values() ):
|
for b in set( BUFFER_MAP.values() ):
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ _session_manager = None
|
||||||
class SessionManager:
|
class SessionManager:
|
||||||
next_session_id = 0
|
next_session_id = 0
|
||||||
sessions = {}
|
sessions = {}
|
||||||
current_session = None
|
|
||||||
|
|
||||||
|
|
||||||
def NewSession( self, *args, **kwargs ):
|
def NewSession( self, *args, **kwargs ):
|
||||||
|
|
@ -31,21 +30,24 @@ class SessionManager:
|
||||||
session = DebugSession( session_id, self, *args, **kwargs )
|
session = DebugSession( session_id, self, *args, **kwargs )
|
||||||
self.sessions[ session_id ] = session
|
self.sessions[ session_id ] = session
|
||||||
|
|
||||||
if self.current_session is None:
|
|
||||||
self.current_session = session.session_id
|
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
|
||||||
def DestroySession( self, session: DebugSession ):
|
def DestroySession( self, session: DebugSession ):
|
||||||
|
# TODO: Call this!
|
||||||
del self.sessions[ session.session_id ]
|
del self.sessions[ session.session_id ]
|
||||||
|
|
||||||
|
|
||||||
def GetSession( self, session_id ):
|
def GetSession( self, session_id ):
|
||||||
return self.sessions.get( session_id )
|
return self.sessions.get( session_id )
|
||||||
|
|
||||||
def CurrentSession( self ):
|
|
||||||
return self.GetSession( self.current_session )
|
def SessionForTab( self, tabnr ):
|
||||||
|
for _, session in self.sessions.items():
|
||||||
|
if session._HasUI() and session._uiTab.number == int( tabnr ):
|
||||||
|
return session
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def Get():
|
def Get():
|
||||||
|
|
|
||||||
|
|
@ -86,8 +86,8 @@ class StackTraceView( object ):
|
||||||
_line_to_thread = typing.Dict[ int, Thread ]
|
_line_to_thread = typing.Dict[ int, Thread ]
|
||||||
|
|
||||||
def __init__( self, session, win ):
|
def __init__( self, session, win ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session.session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger, session.session_id )
|
||||||
|
|
||||||
self._buf = win.buffer
|
self._buf = win.buffer
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
@ -104,7 +104,10 @@ class StackTraceView( object ):
|
||||||
# FIXME: This ID is by group, so should be module scope
|
# FIXME: This ID is by group, so should be module scope
|
||||||
self._next_sign_id = 1
|
self._next_sign_id = 1
|
||||||
|
|
||||||
utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
|
utils.SetUpHiddenBuffer(
|
||||||
|
self._buf,
|
||||||
|
utils.BufferNameForSession( 'vimspector.StackTrace',
|
||||||
|
self._session.session_id ) )
|
||||||
utils.SetUpUIWindow( win )
|
utils.SetUpUIWindow( win )
|
||||||
|
|
||||||
mappings = settings.Dict( 'mappings' )[ 'stack_trace' ]
|
mappings = settings.Dict( 'mappings' )[ 'stack_trace' ]
|
||||||
|
|
@ -562,7 +565,10 @@ class StackTraceView( object ):
|
||||||
|
|
||||||
buf = utils.BufferForFile( buf_name )
|
buf = utils.BufferForFile( buf_name )
|
||||||
self._scratch_buffers.append( buf )
|
self._scratch_buffers.append( buf )
|
||||||
utils.SetUpHiddenBuffer( buf, buf_name )
|
utils.SetUpHiddenBuffer( buf,
|
||||||
|
utils.BufferNameForSession(
|
||||||
|
buf_name,
|
||||||
|
self._session.session_id ) )
|
||||||
source[ 'path' ] = buf_name
|
source[ 'path' ] = buf_name
|
||||||
with utils.ModifiableScratchBuffer( buf ):
|
with utils.ModifiableScratchBuffer( buf ):
|
||||||
utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] )
|
utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] )
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,30 @@ LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) )
|
||||||
_log_handler = logging.FileHandler( LOG_FILE, mode = 'w' )
|
_log_handler = logging.FileHandler( LOG_FILE, mode = 'w' )
|
||||||
|
|
||||||
_log_handler.setFormatter(
|
_log_handler.setFormatter(
|
||||||
logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) )
|
logging.Formatter( '%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - '
|
||||||
|
'%(context)s - %(message)s' ) )
|
||||||
|
|
||||||
|
|
||||||
def SetUpLogging( logger ):
|
class ContextLogFilter( logging.Filter ):
|
||||||
|
context: str
|
||||||
|
|
||||||
|
def __init__( self, context ):
|
||||||
|
self.context = str( context )
|
||||||
|
|
||||||
|
def filter( self, record: logging.LogRecord ):
|
||||||
|
if self.context is None:
|
||||||
|
record.context = 'UNKNOWN'
|
||||||
|
else:
|
||||||
|
record.context = self.context
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def SetUpLogging( logger, context = None ):
|
||||||
logger.setLevel( logging.DEBUG )
|
logger.setLevel( logging.DEBUG )
|
||||||
if _log_handler not in logger.handlers:
|
if _log_handler not in logger.handlers:
|
||||||
logger.addHandler( _log_handler )
|
logger.addHandler( _log_handler )
|
||||||
|
logger.addFilter( ContextLogFilter( context ) )
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger( __name__ )
|
_logger = logging.getLogger( __name__ )
|
||||||
|
|
@ -404,6 +421,8 @@ def Confirm( api_prefix,
|
||||||
default_value = 2,
|
default_value = 2,
|
||||||
options: list = None,
|
options: list = None,
|
||||||
keys: list = None ):
|
keys: list = None ):
|
||||||
|
# TODO: Implement a queue here? If calling code calls Confirm (async) multiple
|
||||||
|
# times, we... well what happens?!
|
||||||
if not options:
|
if not options:
|
||||||
options = [ '(Y)es', '(N)o' ]
|
options = [ '(Y)es', '(N)o' ]
|
||||||
if not keys:
|
if not keys:
|
||||||
|
|
@ -871,3 +890,12 @@ def UseWinBar():
|
||||||
# Buggy neovim doesn't render correctly when the WinBar is defined:
|
# Buggy neovim doesn't render correctly when the WinBar is defined:
|
||||||
# https://github.com/neovim/neovim/issues/12689
|
# https://github.com/neovim/neovim/issues/12689
|
||||||
return not int( Call( 'has', 'nvim' ) )
|
return not int( Call( 'has', 'nvim' ) )
|
||||||
|
|
||||||
|
|
||||||
|
def BufferNameForSession( name, session_id ):
|
||||||
|
if session_id == 0:
|
||||||
|
# Hack for backward compat - don't suffix with the ID for the "first"
|
||||||
|
# session
|
||||||
|
return name
|
||||||
|
|
||||||
|
return f'{name}[{session_id}]'
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,11 @@ def AddExpandMappings( mappings = None ):
|
||||||
|
|
||||||
|
|
||||||
class VariablesView( object ):
|
class VariablesView( object ):
|
||||||
def __init__( self, variables_win, watches_win ):
|
def __init__( self, session, variables_win, watches_win ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ + '.' + str( session.session_id ) )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger, session.session_id )
|
||||||
|
|
||||||
|
self._session = session
|
||||||
self._connection = None
|
self._connection = None
|
||||||
self._current_syntax = ''
|
self._current_syntax = ''
|
||||||
self._server_capabilities = None
|
self._server_capabilities = None
|
||||||
|
|
@ -182,7 +183,10 @@ class VariablesView( object ):
|
||||||
# Set up the "Variables" buffer in the variables_win
|
# Set up the "Variables" buffer in the variables_win
|
||||||
self._scopes: typing.List[ Scope ] = []
|
self._scopes: typing.List[ Scope ] = []
|
||||||
self._vars = View( variables_win, {}, self._DrawScopes )
|
self._vars = View( variables_win, {}, self._DrawScopes )
|
||||||
utils.SetUpHiddenBuffer( self._vars.buf, 'vimspector.Variables' )
|
utils.SetUpHiddenBuffer(
|
||||||
|
self._vars.buf,
|
||||||
|
utils.BufferNameForSession( 'vimspector.Variables',
|
||||||
|
self._session.session_id ) )
|
||||||
with utils.LetCurrentWindow( variables_win ):
|
with utils.LetCurrentWindow( variables_win ):
|
||||||
if utils.UseWinBar():
|
if utils.UseWinBar():
|
||||||
vim.command( 'nnoremenu <silent> 1.1 WinBar.Set '
|
vim.command( 'nnoremenu <silent> 1.1 WinBar.Set '
|
||||||
|
|
@ -193,11 +197,14 @@ class VariablesView( object ):
|
||||||
# there)
|
# there)
|
||||||
self._watches: typing.List[ Watch ] = []
|
self._watches: typing.List[ Watch ] = []
|
||||||
self._watch = View( watches_win, {}, self._DrawWatches )
|
self._watch = View( watches_win, {}, self._DrawWatches )
|
||||||
utils.SetUpPromptBuffer( self._watch.buf,
|
utils.SetUpPromptBuffer(
|
||||||
'vimspector.Watches',
|
self._watch.buf,
|
||||||
'Expression: ',
|
utils.BufferNameForSession( 'vimspector.Watches',
|
||||||
'vimspector#AddWatchPrompt',
|
self._session.session_id ),
|
||||||
'vimspector#OmniFuncWatch' )
|
'Expression: ',
|
||||||
|
'vimspector#AddWatchPrompt',
|
||||||
|
'vimspector#OmniFuncWatch' )
|
||||||
|
|
||||||
with utils.LetCurrentWindow( watches_win ):
|
with utils.LetCurrentWindow( watches_win ):
|
||||||
AddExpandMappings( mappings )
|
AddExpandMappings( mappings )
|
||||||
for mapping in utils.GetVimList( mappings, 'delete' ):
|
for mapping in utils.GetVimList( mappings, 'delete' ):
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,26 @@
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"program": "${file}",
|
"program": "${file}",
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": true,
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"subProcess": true
|
||||||
|
},
|
||||||
|
"breakpoints": {
|
||||||
|
"exception": {
|
||||||
|
"raised": "N",
|
||||||
|
"uncaught": "Y",
|
||||||
|
"userUnhandled": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attach": {
|
||||||
|
"adapter": "multi-session",
|
||||||
|
"configuration": {
|
||||||
|
"request": "attach",
|
||||||
|
"type": "python",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"program": "${file}",
|
||||||
|
"stopOnEntry": true,
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"subProcess": true
|
"subProcess": true
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,16 @@ import multiprocessing as mp
|
||||||
|
|
||||||
|
|
||||||
def First():
|
def First():
|
||||||
for _ in range( 100 ):
|
for i in range( 10 ):
|
||||||
print( "in first" )
|
print( f"in first x {i}" )
|
||||||
time.sleep( 0.1 )
|
time.sleep( 0.1 )
|
||||||
|
|
||||||
|
|
||||||
def Second():
|
if __name__ == '__main__':
|
||||||
for _ in range( 100 ):
|
print( "main" )
|
||||||
print( "in second" )
|
p1 = mp.Process( target=First )
|
||||||
time.sleep( 0.1 )
|
|
||||||
|
|
||||||
|
p1.start()
|
||||||
|
p1.join()
|
||||||
|
|
||||||
print( "main" )
|
print( "Done" )
|
||||||
p1 = mp.Process( target=First )
|
|
||||||
p2 = mp.Process( target=Second )
|
|
||||||
|
|
||||||
p1.start()
|
|
||||||
p2.start()
|
|
||||||
|
|
||||||
p1.join()
|
|
||||||
p2.join()
|
|
||||||
|
|
||||||
print( "Done" )
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue