From fb2bad216f40bc72eff9310df4243e583f4eb184 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 3 Dec 2020 20:42:32 +0000 Subject: [PATCH 1/4] Add a session ID to debug_session - vim only for now --- autoload/vimspector/internal/channel.vim | 90 ++++++++------ autoload/vimspector/internal/job.vim | 116 +++++++++++------- autoload/vimspector/internal/neochannel.vim | 57 +++++---- autoload/vimspector/internal/neojob.vim | 96 +++++++++------ .../vimspector/debug_adapter_connection.py | 7 +- python3/vimspector/debug_session.py | 31 ++++- python3/vimspector/output.py | 5 +- python3/vimspector/utils.py | 23 ++-- python3/vimspector/variables.py | 5 +- .../test/python/simple_python/print_env.py | 3 +- tests/language_python.test.vim | 43 +++++++ 11 files changed, 320 insertions(+), 156 deletions(-) diff --git a/autoload/vimspector/internal/channel.vim b/autoload/vimspector/internal/channel.vim index e033cff..fb97b19 100644 --- a/autoload/vimspector/internal/channel.vim +++ b/autoload/vimspector/internal/channel.vim @@ -19,8 +19,12 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} -function! s:_OnServerData( channel, data ) abort - if !exists( 's:ch' ) || s:ch isnot a:channel +let s:channels = {} +let s:jobs = {} + +function! s:_OnServerData( session_id, channel, data ) abort + if !has_key( s:channels, a:session_id ) || + \ s:channels[ a:session_id ] isnot a:channel return endif @@ -29,20 +33,23 @@ _vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) EOF endfunction -function! s:_OnClose( channel ) abort - if !exists( 's:ch' ) || s:ch isnot a:channel +function! s:_OnClose( session_id, channel ) abort + if !has_key( s:channels, a:session_id ) || + \ s:channels[ a:session_id ] isnot a:channel return endif echom 'Channel closed' redraw - unlet s:ch + unlet s:channels[ a:session_id ] py3 _vimspector_session.OnServerExit( 0 ) endfunction -function! vimspector#internal#channel#StartDebugSession( config ) abort +function! vimspector#internal#channel#StartDebugSession( + \ session_id, + \ config ) abort - if exists( 's:ch' ) + if has_key( s:channels, a:session_id ) echo 'Channel is already running' return v:false endif @@ -50,7 +57,7 @@ function! vimspector#internal#channel#StartDebugSession( config ) abort " If we _also_ have a command line, then start the actual job. This allows for " servers which start up and listen on some port if has_key( a:config, 'command' ) - let s:job = job_start( a:config[ 'command' ], + let s:jobs[ a:session_id ] = job_start( a:config[ 'command' ], \ { \ 'in_mode': 'raw', \ 'out_mode': 'raw', @@ -65,16 +72,19 @@ function! vimspector#internal#channel#StartDebugSession( config ) abort let l:addr = get( a:config, 'host', '127.0.0.1' ) . ':' . a:config[ 'port' ] echo 'Connecting to ' . l:addr . '... (waiting fo up to 10 seconds)' - let s:ch = ch_open( l:addr, + " FIXME: This _always_ waits 10s; the neochannel version is quicker + let s:channels[ a:session_id ] = ch_open( l:addr, \ { \ 'mode': 'raw', - \ 'callback': funcref( 's:_OnServerData' ), - \ 'close_cb': funcref( 's:_OnClose' ), + \ 'callback': funcref( 's:_OnServerData', + \ [ a:session_id ] ), + \ 'close_cb': funcref( 's:_OnClose', + \ [ a:session_id ] ), \ 'waittime': 10000, \ } \ ) - if ch_status( s:ch ) !=# 'open' + if ch_status( s:channels[ a:session_id ] ) !=# 'open' echom 'Unable to connect to' l:addr redraw return v:false @@ -83,62 +93,68 @@ function! vimspector#internal#channel#StartDebugSession( config ) abort return v:true endfunction -function! vimspector#internal#channel#Send( msg ) abort - call ch_sendraw( s:ch, a:msg ) +function! vimspector#internal#channel#Send( session_id, msg ) abort + call ch_sendraw( s:channels[ a:session_id ], a:msg ) return 1 endfunction -function! vimspector#internal#channel#Timeout( id ) abort +function! vimspector#internal#channel#Timeout( session_id, id ) abort py3 << EOF _vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) ) EOF endfunction -function! vimspector#internal#channel#StopDebugSession() abort +function! s:_ChannelExists( session_id ) abort + return has_key( s:channels, a:session_id ) && + \ count( [ 'closed', 'fail' ], + \ ch_status( s:channels[ a:session_id ] ) ) == 0 +endfunction - if exists( 's:job' ) +function! vimspector#internal#channel#StopDebugSession( session_id ) abort + + if has_key( s:jobs, a:session_id ) " We started the job, so we need to kill it and wait to read all the data " from the socket - if job_status( s:job ) ==# 'run' - call job_stop( s:job, 'term' ) + let job = s:jobs[ a:session_id ] + if job_status( job ) ==# 'run' + call job_stop( job, 'term' ) endif - while job_status( s:job ) ==# 'run' - call job_stop( s:job, 'kill' ) + while job_status( job ) ==# 'run' + call job_stop( job, 'kill' ) endwhile - unlet s:job + call remove( s:jobs, a:session_id ) - if exists( 's:ch' ) && count( [ 'closed', 'fail' ], ch_status( s:ch ) ) == 0 + if s:_ChannelExists( a:session_id ) " We're going to block on this channel reading, then manually call the " close callback, so remove the automatic close callback to avoid tricky " re-entrancy - call ch_setoptions( s:ch, { 'close_cb': '' } ) + call ch_setoptions( s:channels[ a:session_id ], { 'close_cb': '' } ) endif - - elseif exists( 's:ch' ) && - \ count( [ 'closed', 'fail' ], ch_status( s:ch ) ) == 0 - + elseif s:_ChannelExists( a:session_id ) " channel is open, close it and trigger the callback. The callback is _not_ " triggered when manually calling ch_close. if we get here and the channel " is not open, then we there is a _OnClose callback waiting for us, so do " nothing. - call ch_close( s:ch ) + call ch_close( s:channels[ a:session_id ] ) endif " block until we've read all data from the socket and handled it. - while count( [ 'open', 'buffered' ], ch_status( s:ch ) ) == 1 - let data = ch_read( s:ch, { 'timeout': 10 } ) - call s:_OnServerData( s:ch, data ) + while has_key( s:channels, a:session_id ) && + \ count( [ 'open', 'buffered' ], + \ ch_status( s:channels[ a:session_id ] ) ) == 1 + let data = ch_read( s:channels[ a:session_id ], { 'timeout': 10 } ) + call s:_OnServerData( s:channels[ a:session_id ], data ) endwhile - call s:_OnClose( s:ch ) + if has_key( s:channels, a:session_id ) + call s:_OnClose( s:channels[ a:session_id ] ) + endif endfunction -function! vimspector#internal#channel#Reset() abort - if exists( 's:ch' ) || exists( 's:job' ) - call vimspector#internal#channel#StopDebugSession() - endif +function! vimspector#internal#channel#Reset( session_id ) abort + call vimspector#internal#channel#StopDebugSession( a:session_id ) endfunction " Boilerplate {{{ diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index 206baf0..39dca29 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -19,8 +19,12 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} -function! s:_OnServerData( channel, data ) abort - if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job +let s:jobs = {} +let s:commands = {} + +function! s:_OnServerData( session_id, channel, data ) abort + if !has_key( s:jobs, a:session_id ) || + \ ch_getjob( a:channel ) isnot s:jobs[ a:session_id ] call ch_log( 'Get data after process exit' ) return endif @@ -28,8 +32,9 @@ function! s:_OnServerData( channel, data ) abort py3 _vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) endfunction -function! s:_OnServerError( channel, data ) abort - if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job +function! s:_OnServerError( session_id, channel, data ) abort + if !has_key( s:jobs, a:session_id ) || + \ ch_getjob( a:channel ) isnot s:jobs[ a:session_id ] call ch_log( 'Get data after process exit' ) return endif @@ -41,22 +46,24 @@ 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 +function! s:_OnExit( session_id, channel, status ) abort + if !has_key( s:jobs, a:session_id ) || + \ ch_getjob( a:channel ) isnot s:jobs[ a:session_id ] call ch_log( 'Unexpected exit callback' ) return endif echom 'Channel exit with status ' . a:status redraw - if exists( 's:job' ) - unlet s:job + if has_key( s:jobs, a:session_id ) + unlet s:jobs[ a:session_id ] endif py3 _vimspector_session.OnServerExit( vim.eval( 'a:status' ) ) endfunction -function! s:_OnClose( channel ) abort - if !exists( 's:job' ) || job_getchannel( s:job ) != a:channel +function! s:_OnClose( session_id, channel ) abort + if !has_key( s:jobs, a:session_id ) || + \ ch_getjob( a:channel ) isnot s:jobs[ a:session_id ] call ch_log( 'Channel closed after exit' ) return endif @@ -65,34 +72,38 @@ function! s:_OnClose( channel ) abort redraw endfunction -function! vimspector#internal#job#StartDebugSession( config ) abort - if exists( 's:job' ) +function! vimspector#internal#job#StartDebugSession( session_id, config ) abort + if has_key( s:jobs, a:session_id ) echom 'Not starting: Job is already running' redraw return v:false endif - let s:job = job_start( a:config[ 'command' ], + let s:jobs[ a:session_id ] = job_start( a:config[ 'command' ], \ { \ 'in_mode': 'raw', \ 'out_mode': 'raw', \ 'err_mode': 'raw', - \ 'exit_cb': funcref( 's:_OnExit' ), - \ 'close_cb': funcref( 's:_OnClose' ), - \ 'out_cb': funcref( 's:_OnServerData' ), - \ 'err_cb': funcref( 's:_OnServerError' ), + \ 'exit_cb': funcref( 's:_OnExit', + \ [ a:session_id ] ), + \ 'close_cb': funcref( 's:_OnClose', + \ [ a:session_id ] ), + \ 'out_cb': funcref( 's:_OnServerData', + \ [ a:session_id ] ), + \ 'err_cb': funcref( 's:_OnServerError', + \ [ a:session_id ] ), \ 'stoponexit': 'term', \ 'env': a:config[ 'env' ], \ 'cwd': a:config[ 'cwd' ], \ } \ ) - if !exists( 's:job' ) + if !has_key( s:jobs, a:session_id ) " The job died immediately after starting and we cleaned up return v:false endif - let status = job_status( s:job ) + let status = job_status( s:jobs[ a:session_id ] ) echom 'Started job, status is: ' . status redraw @@ -104,20 +115,22 @@ function! vimspector#internal#job#StartDebugSession( config ) abort return v:true endfunction -function! vimspector#internal#job#Send( msg ) abort - if ! exists( 's:job' ) +function! vimspector#internal#job#Send( session_id, msg ) abort + if ! has_key( s:jobs, a:session_id ) echom "Can't send message: Job was not initialised correctly" redraw return 0 endif - if job_status( s:job ) !=# 'run' + let job = s:jobs[ a:session_id ] + + if job_status( job ) !=# 'run' echom "Can't send message: Job is not running" redraw return 0 endif - let ch = job_getchannel( s:job ) + let ch = job_getchannel( job ) if ch ==# 'channel fail' echom 'Channel was closed unexpectedly!' redraw @@ -128,45 +141,55 @@ function! vimspector#internal#job#Send( msg ) abort return 1 endfunction -function! vimspector#internal#job#StopDebugSession() abort - if !exists( 's:job' ) +function! vimspector#internal#job#StopDebugSession( session_id ) abort + if ! has_key( s:jobs, a:session_id ) echom "Not stopping session: Job doesn't exist" redraw return endif - if job_status( s:job ) ==# 'run' + let job = s:jobs[ a:session_id ] + + if job_status( job ) ==# 'run' echom 'Terminating job' redraw - call job_stop( s:job, 'kill' ) + call job_stop( job, 'kill' ) endif endfunction -function! vimspector#internal#job#Reset() abort - call vimspector#internal#job#StopDebugSession() +function! vimspector#internal#job#Reset( session_id ) abort + call vimspector#internal#job#StopDebugSession( a:session_id ) endfunction -function! s:_OnCommandExit( category, ch, code ) abort +function! s:_OnCommandExit( session_id, category, ch, code ) abort py3 __import__( "vimspector", \ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete( + \ vim.eval( 'a:session_id' ), \ vim.eval( 'a:category' ), \ int( vim.eval( 'a:code' ) ) ) endfunction -function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort +function! vimspector#internal#job#StartCommandWithLog( + \ session_id, + \ cmd, + \ category ) abort if ! exists( 's:commands' ) let s:commands = {} endif - if ! has_key( s:commands, a:category ) - let s:commands[ a:category ] = [] + if ! has_key( s:commands, a:session_id ) + let s:commands[ a:session_id ] = {} endif - let l:index = len( s:commands[ a:category ] ) + if ! has_key( s:commands[ a:session_id ], a:category ) + let s:commands[ a:session_id ][ a:category ] = [] + endif - let buf = '_vimspector_log_' . a:category + let l:index = len( s:commands[ a:session_id ][ a:category ] ) - call add( s:commands[ a:category ], job_start( + let buf = '_vimspector_log_' . a:session_id . '_' . a:category + + call add( s:commands[ a:session_id ][ a:category ], job_start( \ a:cmd, \ { \ 'out_io': 'buffer', @@ -175,13 +198,14 @@ function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort \ 'err_msg': 0, \ 'out_name': buf, \ 'err_name': buf, - \ 'exit_cb': funcref( 's:_OnCommandExit', [ a:category ] ), + \ 'exit_cb': funcref( 's:_OnCommandExit', + \ [ a:session_id, a:category ] ), \ 'out_modifiable': 0, \ 'err_modifiable': 0, \ 'stoponexit': 'kill' \ } ) ) - if job_status( s:commands[ a:category ][ index ] ) !=# 'run' + if job_status( s:commands[ a:session_id ][ a:category ][ index ] ) !=# 'run' echom 'Unable to start job for ' . string( a:cmd ) redraw return v:none @@ -191,19 +215,27 @@ function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort endfunction -function! vimspector#internal#job#CleanUpCommand( category ) abort +function! vimspector#internal#job#CleanUpCommand( session_id, category ) abort if ! exists( 's:commands' ) let s:commands = {} endif - if ! has_key( s:commands, a:category ) + if ! has_key( s:commands, a:session_id ) + let s:commands[ a:session_id ] = {} + endif + + if ! has_key( s:commands[ a:session_id ], a:category ) return endif - for j in s:commands[ a:category ] + for j in s:commands[ a:session_id ][ a:category ] call job_stop( j, 'kill' ) endfor - unlet s:commands[ a:category ] + unlet s:commands[ a:session_id ][ a:category ] + + if len( s:commands[ a:session_id ] ) == 0 + unlet s:commands[ a:session_id ] + endif endfunction " Boilerplate {{{ diff --git a/autoload/vimspector/internal/neochannel.vim b/autoload/vimspector/internal/neochannel.vim index f20684d..3a6fe0e 100644 --- a/autoload/vimspector/internal/neochannel.vim +++ b/autoload/vimspector/internal/neochannel.vim @@ -20,28 +20,34 @@ set cpoptions&vim " }}} +let s:channels = {} +let s:jobs = {} -function! s:_OnEvent( chan_id, data, event ) abort + +function! s:_OnEvent( session_id, chan_id, data, event ) abort if v:exiting isnot# v:null return endif - if !exists( 's:ch' ) || a:chan_id != s:ch + if !has_key( s:channels, a:session_id ) || + \ a:chan_id != s:channels[ a:session_id ] return endif if a:data == [''] echom 'Channel closed' redraw - unlet s:ch + unlet s:channels[ a:session_id ] py3 _vimspector_session.OnServerExit( 0 ) else py3 _vimspector_session.OnChannelData( '\n'.join( vim.eval( 'a:data' ) ) ) endif endfunction -function! vimspector#internal#neochannel#StartDebugSession( config ) abort - if exists( 's:ch' ) +function! vimspector#internal#neochannel#StartDebugSession( + \ session_id, + \ config ) abort + if has_key( s:channels, a:session_id ) echom 'Not starting: Channel is already running' redraw return v:false @@ -54,12 +60,12 @@ function! vimspector#internal#neochannel#StartDebugSession( config ) abort try let old_env = vimspector#internal#neoterm#PrepareEnvironment( \ a:config[ 'env' ] ) - let s:job = jobstart( a:config[ 'command' ], - \ { - \ 'cwd': a:config[ 'cwd' ], - \ 'env': a:config[ 'env' ], - \ } - \ ) + let s:jobs[ a:session_id ] = jobstart( a:config[ 'command' ], + \ { + \ 'cwd': a:config[ 'cwd' ], + \ 'env': a:config[ 'env' ], + \ } + \ ) finally call vimspector#internal#neoterm#ResetEnvironment( a:config[ 'env' ], \ old_env ) @@ -72,9 +78,10 @@ function! vimspector#internal#neochannel#StartDebugSession( config ) abort while attempt <= 10 echo 'Connecting to ' . l:addr . '... (attempt' attempt 'of 10)' try - let s:ch = sockconnect( 'tcp', - \ addr, - \ { 'on_data': funcref( 's:_OnEvent' ) } ) + let s:channels[ a:session_id ] = sockconnect( + \ 'tcp', + \ addr, + \ { 'on_data': funcref( 's:_OnEvent', [ a:session_id ] ) } ) redraw return v:true catch /connection refused/ @@ -88,30 +95,30 @@ function! vimspector#internal#neochannel#StartDebugSession( config ) abort return v:false endfunction -function! vimspector#internal#neochannel#Send( msg ) abort - if ! exists( 's:ch' ) +function! vimspector#internal#neochannel#Send( session_id, msg ) abort + if ! has_key( s:channels, a:session_id ) echom "Can't send message: Channel was not initialised correctly" redraw return 0 endif - call chansend( s:ch, a:msg ) + call chansend( s:channels[ a:session_id ], a:msg ) return 1 endfunction -function! vimspector#internal#neochannel#StopDebugSession() abort - if exists( 's:ch' ) - call chanclose( s:ch ) +function! vimspector#internal#neochannel#StopDebugSession( session_id ) abort + if has_key( s:channels, a:session_id ) + call chanclose( s:channels[ a:session_id ] ) " It doesn't look like we get a callback after chanclos. Who knows if we " will subsequently receive data callbacks. - call s:_OnEvent( s:ch, [ '' ], 'data' ) + call s:_OnEvent( a:session_id, s:channels[ a:session_id ], [ '' ], 'data' ) endif - if exists( 's:job' ) - if vimspector#internal#neojob#JobIsRunning( s:job ) - call jobstop( s:job ) + if has_key( s:jobs, a:session_id ) + if vimspector#internal#neojob#JobIsRunning( s:jobs[ a:session_id ] ) + call jobstop( s:jobs[ a:session_id ] ) endif - unlet s:job + unlet s:jobs[ a:session_id ] endif endfunction diff --git a/autoload/vimspector/internal/neojob.vim b/autoload/vimspector/internal/neojob.vim index 0cefc63..73a7984 100644 --- a/autoload/vimspector/internal/neojob.vim +++ b/autoload/vimspector/internal/neojob.vim @@ -19,14 +19,17 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} +let s:jobs = {} +let s:commands = {} -function! s:_OnEvent( chan_id, data, event ) abort + +function! s:_OnEvent( session_id, chan_id, data, event ) abort if v:exiting isnot# v:null return endif - if !exists( 's:job' ) || a:chan_id != s:job + if !has_key( s:jobs, a:session_id ) || a:chan_id != s:jobs[ a:session_id ] return endif @@ -38,13 +41,15 @@ function! s:_OnEvent( chan_id, data, event ) abort elseif a:event ==# 'exit' echom 'Channel exit with status ' . a:data redraw - unlet s:job + unlet s:jobs[ a:session_id ] py3 _vimspector_session.OnServerExit( vim.eval( 'a:data' ) ) endif endfunction -function! vimspector#internal#neojob#StartDebugSession( config ) abort - if exists( 's:job' ) +function! vimspector#internal#neojob#StartDebugSession( + \ session_id, + \ config ) abort + if has_key( s:jobs, a:session_id ) echom 'Not starging: Job is already running' redraw return v:false @@ -57,11 +62,14 @@ function! vimspector#internal#neojob#StartDebugSession( config ) abort try let old_env = vimspector#internal#neoterm#PrepareEnvironment( \ a:config[ 'env' ] ) - let s:job = jobstart( a:config[ 'command' ], + let s:jobs[ a:session_id ] = jobstart( a:config[ 'command' ], \ { - \ 'on_stdout': funcref( 's:_OnEvent' ), - \ 'on_stderr': funcref( 's:_OnEvent' ), - \ 'on_exit': funcref( 's:_OnEvent' ), + \ 'on_stdout': funcref( 's:_OnEvent', + \ [ a:session_id ] ), + \ 'on_stderr': funcref( 's:_OnEvent', + \ [ a:session_id ] ), + \ 'on_exit': funcref( 's:_OnEvent', + \ [ a:session_id ] ), \ 'cwd': a:config[ 'cwd' ], \ 'env': a:config[ 'env' ], \ } @@ -78,40 +86,40 @@ function! vimspector#internal#neojob#JobIsRunning( job ) abort return jobwait( [ a:job ], 0 )[ 0 ] == -1 endfunction -function! vimspector#internal#neojob#Send( msg ) abort - if ! exists( 's:job' ) +function! vimspector#internal#neojob#Send( session_id, msg ) abort + if ! has_key( s:jobs, a:session_id ) echom "Can't send message: Job was not initialised correctly" redraw return 0 endif - if !vimspector#internal#neojob#JobIsRunning( s:job ) + if !vimspector#internal#neojob#JobIsRunning( s:jobs[ a:session_id ] ) echom "Can't send message: Job is not running" redraw return 0 endif - call chansend( s:job, a:msg ) + call chansend( s:jobs[ a:session_id ], a:msg ) return 1 endfunction -function! vimspector#internal#neojob#StopDebugSession() abort - if !exists( 's:job' ) +function! vimspector#internal#neojob#StopDebugSession( session_id ) abort + if !has_key( s:jobs, a:session_id ) return endif - if vimspector#internal#neojob#JobIsRunning( s:job ) + if vimspector#internal#neojob#JobIsRunning( s:jobs[ a:session_id ] ) echom 'Terminating job' redraw - call jobstop( s:job ) + call jobstop( s:jobs[ a:session_id ] ) endif endfunction -function! vimspector#internal#neojob#Reset() abort - call vimspector#internal#neojob#StopDebugSession() +function! vimspector#internal#neojob#Reset( session_id ) abort + call vimspector#internal#neojob#StopDebugSession( a:session_id ) endfunction -function! s:_OnCommandEvent( category, id, data, event ) abort +function! s:_OnCommandEvent( session_id, category, id, data, event ) abort if v:exiting isnot# v:null return endif @@ -121,18 +129,22 @@ function! s:_OnCommandEvent( category, id, data, event ) abort return endif - if !has_key( s:commands, a:category ) + if ! has_key( s:commands, a:session_id ) return endif - if !has_key( s:commands[ a:category ], a:id ) + if !has_key( s:commands[ a:session_id ], a:category ) + return + endif + + if !has_key( s:commands[ a:session_id ][ a:category ], a:id ) return endif if a:event ==# 'stdout' - let buffer = s:commands[ a:category ][ a:id ].stdout + let buffer = s:commands[ a:session_id ][ a:category ][ a:id ].stdout elseif a:event ==# 'stderr' - let buffer = s:commands[ a:category ][ a:id ].stderr + let buffer = s:commands[ a:session_id ][ a:category ][ a:id ].stderr endif try @@ -173,6 +185,7 @@ function! s:_OnCommandEvent( category, id, data, event ) abort elseif a:event ==# 'exit' py3 __import__( "vimspector", \ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete( + \ vim.eval( 'a:session_id' ), \ vim.eval( 'a:category' ), \ int( vim.eval( 'a:data' ) ) ) endif @@ -198,11 +211,16 @@ function! s:MakeBufferWritable( buffer ) abort endfunction -let s:commands = {} +function! vimspector#internal#neojob#StartCommandWithLog( + \ session_id, + \ cmd, + \ category ) abort + if ! has_key( s:commands, a:session_id ) + let s:commands[ a:session_id ] = {} + endif -function! vimspector#internal#neojob#StartCommandWithLog( cmd, category ) abort - if ! has_key( s:commands, a:category ) - let s:commands[ a:category ] = {} + if ! has_key( s:commands[ a:session_id ], a:category ) + let s:commands[ a:session_id ][ a:category ] = {} endif let buf = bufnr( '_vimspector_log_' . a:category, v:true ) @@ -215,14 +233,14 @@ function! vimspector#internal#neojob#StartCommandWithLog( cmd, category ) abort let id = jobstart(a:cmd, \ { \ 'on_stdout': funcref( 's:_OnCommandEvent', - \ [ a:category ] ), + \ [ a:session_id, a:category ] ), \ 'on_stderr': funcref( 's:_OnCommandEvent', - \ [ a:category ] ), + \ [ a:session_id, a:category ] ), \ 'on_exit': funcref( 's:_OnCommandEvent', - \ [ a:category ] ), + \ [ a:session_id, a:category ] ), \ } ) - let s:commands[ a:category ][ id ] = { + let s:commands[ a:session_id ][ a:category ][ id ] = { \ 'stdout': buf, \ 'stderr': buf \ } @@ -230,19 +248,25 @@ function! vimspector#internal#neojob#StartCommandWithLog( cmd, category ) abort return buf endfunction -function! vimspector#internal#neojob#CleanUpCommand( category ) abort - if ! has_key( s:commands, a:category ) +function! vimspector#internal#neojob#CleanUpCommand( + \ session_id, + \ category ) abort + if ! has_key( s:commands, a:session_id ) return endif - for id in keys( s:commands[ a:category ] ) + if ! has_key( s:commands[ a:session_id ], a:category ) + return + endif + + for id in keys( s:commands[ a:session_id ][ a:category ] ) let id = str2nr( id ) if jobwait( [ id ], 0 )[ 0 ] == -1 call jobstop( id ) endif call jobwait( [ id ], -1 ) endfor - unlet! s:commands[ a:category ] + unlet! s:commands[ a:session_id ][ a:category ] endfunction " Boilerplate {{{ diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index df2ef13..4b7016b 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -53,8 +53,11 @@ class DebugAdapterConnection( object ): # TODO/FIXME: This is so messy expiry_id = vim.eval( - 'timer_start( {}, "vimspector#internal#channel#Timeout" )'.format( - timeout ) ) + 'timer_start( {}, ' + ' function( "vimspector#internal#channel#Timeout", ' + ' [ {} ] ) )'.format( + timeout, + self._handler.session_id ) ) request = PendingRequest( msg, handler, diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 2f132f9..e1af7a7 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -42,9 +42,25 @@ VIMSPECTOR_HOME = utils.GetVimspectorBase() # cache of what the user entered for any option we ask them USER_CHOICES = {} +NEXT_SESSION_ID = 0 +SESSIONS = {} + + +def PushSession( session ): + global NEXT_SESSION_ID + this_id = NEXT_SESSION_ID + NEXT_SESSION_ID = NEXT_SESSION_ID + 1 + SESSIONS[ this_id ] = session + return this_id + + +def PopSession( session ): + SESSIONS.pop( session.session_id, None ) + class DebugSession( object ): def __init__( self, api_prefix ): + self.session_id = PushSession( self ) self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) @@ -73,6 +89,11 @@ class DebugSession( object ): self._ResetServerState() + + def __del__( self ): + PopSession( self ) + + def _ResetServerState( self ): self._connection = None self._init_complete = False @@ -887,8 +908,10 @@ class DebugSession( object ): vim.vars[ '_vimspector_adapter_spec' ] = self._adapter if not vim.eval( "vimspector#internal#{}#StartDebugSession( " + " {}," " g:_vimspector_adapter_spec " - ")".format( self._connection_type ) ): + ")".format( self._connection_type, + self.session_id ) ): self._logger.error( "Unable to start debug server" ) self._splash_screen = utils.DisplaySplash( self._api_prefix, self._splash_screen, @@ -911,6 +934,7 @@ class DebugSession( object ): handlers, lambda msg: utils.Call( "vimspector#internal#{}#Send".format( self._connection_type ), + self.session_id, msg ) ) self._logger.info( 'Debug Adapter Started' ) @@ -933,8 +957,9 @@ class DebugSession( object ): assert not self._run_on_server_exit self._run_on_server_exit = callback - vim.eval( 'vimspector#internal#{}#StopDebugSession()'.format( - self._connection_type ) ) + vim.eval( 'vimspector#internal#{}#StopDebugSession( {} )'.format( + self._connection_type, + self.session_id ) ) self._connection.DoRequest( handler, { 'command': 'disconnect', diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index c453417..3322b0d 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -63,6 +63,8 @@ class OutputView( object ): self._buffers = {} self._api_prefix = api_prefix VIEWS.add( self ) + # FIXME: hack? + self._session_id = hash( self ) def Print( self, categroy, text ): self._Print( 'server', text.splitlines() ) @@ -105,7 +107,7 @@ class OutputView( object ): def Clear( self ): for category, tab_buffer in self._buffers.items(): if tab_buffer.is_job: - utils.CleanUpCommand( category, self._api_prefix ) + utils.CleanUpCommand( self._session_id, category, self._api_prefix ) utils.CleanUpHiddenBuffer( tab_buffer.buf ) # FIXME: nunmenu the WinBar ? @@ -174,6 +176,7 @@ class OutputView( object ): if cmd is not None: out = utils.SetUpCommandBuffer( + self._session_id, # TODO: not really a session id cmd, category, self._api_prefix, diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 5f836fc..ef0a077 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -87,16 +87,21 @@ def OpenFileInCurrentWindow( file_name ): COMMAND_HANDLERS = {} -def OnCommandWithLogComplete( name, exit_code ): - cb = COMMAND_HANDLERS.get( name ) +def OnCommandWithLogComplete( session_id, name, exit_code ): + cb = COMMAND_HANDLERS.get( str( session_id ) + '.' + name ) if cb: cb( exit_code ) -def SetUpCommandBuffer( cmd, name, api_prefix, completion_handler = None ): - COMMAND_HANDLERS[ name ] = completion_handler +def SetUpCommandBuffer( session_id, + cmd, + name, + api_prefix, + completion_handler = None ): + COMMAND_HANDLERS[ str( session_id ) + '.' + name ] = completion_handler buf = Call( f'vimspector#internal#{api_prefix}job#StartCommandWithLog', + session_id, cmd, name ) @@ -110,10 +115,12 @@ def SetUpCommandBuffer( cmd, name, api_prefix, completion_handler = None ): return vim.buffers[ int( buf ) ] -def CleanUpCommand( name, api_prefix ): - return vim.eval( 'vimspector#internal#{}job#CleanUpCommand( "{}" )'.format( - api_prefix, - name ) ) +def CleanUpCommand( session_id, name, api_prefix ): + return vim.eval( + 'vimspector#internal#{}job#CleanUpCommand( {}, "{}" )'.format( + api_prefix, + session_id, + name ) ) def CleanUpHiddenBuffer( buf ): diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index 8dcb493..5cdd712 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -224,9 +224,12 @@ class VariablesView( object ): 'balloonexpr': vim.options[ 'balloonexpr' ], 'balloondelay': vim.options[ 'balloondelay' ], } + # TODO: How can we make this work. I think we can set ballooneval as a + # buffer-local or maybe window-local variable ? We could pass session_id + # to the expression here, but still how would it work with 2 concurrent + # sessions? vim.options[ 'balloonexpr' ] = ( "vimspector#internal#" "balloon#HoverTooltip()" ) - vim.options[ 'balloondelay' ] = 250 if has_balloon: diff --git a/support/test/python/simple_python/print_env.py b/support/test/python/simple_python/print_env.py index 4b88f2d..17b6861 100644 --- a/support/test/python/simple_python/print_env.py +++ b/support/test/python/simple_python/print_env.py @@ -6,8 +6,9 @@ import os def Main(): print( os.environ.get( 'Something', 'ERROR' ) ) print( os.environ.get( 'SomethingElse', 'ERROR' ) ) + print( os.environ.get( 'PATH', 'ERROR' ) ) - for k, v in os.environ: + for k, v in os.environ.items(): print( f'{ k } = "{ v }"' ) diff --git a/tests/language_python.test.vim b/tests/language_python.test.vim index cc49adb..37dcc16 100644 --- a/tests/language_python.test.vim +++ b/tests/language_python.test.vim @@ -107,3 +107,46 @@ function! Test_Python_Remote_Attach() lcd - %bwipeout! endfunction + +function! SetUp_Test_Python_Remote_Attach_With_Run() + let g:vimspector_enable_mappings = 'HUMAN' +endfunction + +function! Test_Python_Remote_Attach_With_Run() + lcd ../support/test/python/simple_python + let fn='main.py' + exe 'edit ' . fn + + call setpos( '.', [ 0, 6, 1 ] ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 6, 1 ) + call vimspector#test#signs#AssertSignGroupEmptyAtLine( 'VimspectorBP', 6 ) + + " Add the breakpoint + call feedkeys( "\", 'xt' ) + call vimspector#test#signs#AssertSignGroupSingletonAtLine( 'VimspectorBP', + \ 6, + \ 'vimspectorBP', + \ 9 ) + + call setpos( '.', [ 0, 1, 1 ] ) + + " Here we go. Start Debugging (note will wait up to 10s for the script to do + " its virtualenv thing) + call vimspector#LaunchWithSettings( { + \ 'configuration': 'attach-run', + \ } ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 6, 1 ) + + " Step + call feedkeys( "\", 'xt' ) + + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 7, 1 ) + call WaitForAssert( {-> + \ vimspector#test#signs#AssertPCIsAtLineInBuffer( fn, 7 ) + \ } ) + + call vimspector#test#setup#Reset() + lcd - + %bwipeout! +endfunction From 099431ba556e7bf73e97886a2b9a14a52c663a96 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 5 Dec 2020 22:24:54 +0000 Subject: [PATCH 2/4] Start to create session manager Kind of works, but there's still a single global _vimspector_session --- autoload/vimspector/internal/state.vim | 8 ++-- .../vimspector/debug_adapter_connection.py | 6 +-- python3/vimspector/debug_session.py | 28 +++-------- python3/vimspector/session_manager.py | 46 +++++++++++++++++++ 4 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 python3/vimspector/session_manager.py diff --git a/autoload/vimspector/internal/state.vim b/autoload/vimspector/internal/state.vim index f1e690a..e628eaa 100644 --- a/autoload/vimspector/internal/state.vim +++ b/autoload/vimspector/internal/state.vim @@ -27,10 +27,12 @@ endif function! vimspector#internal#state#Reset() abort try py3 import vim - py3 _vimspector_session = __import__( + py3 _vimspector_session_manager = __import__( \ "vimspector", - \ fromlist=[ "debug_session" ] ).debug_session.DebugSession( - \ vim.eval( 's:prefix' ) ) + \ fromlist=[ "session_manager" ] ).session_manager.SessionManager() + + py3 _vimspector_session = _vimspector_session_manager = + \ _vimspector_session_manager.NewSession( vim.eval( 's:prefix' ) ) catch /.*/ echohl WarningMsg echom 'Exception while loading vimspector:' v:exception diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index 4b7016b..d4cc239 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -29,7 +29,7 @@ class PendingRequest( object ): class DebugAdapterConnection( object ): - def __init__( self, handlers, send_func ): + def __init__( self, handlers, session_id, send_func ): self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) @@ -37,6 +37,7 @@ class DebugAdapterConnection( object ): self._SetState( 'READ_HEADER' ) self._buffer = bytes() self._handlers = handlers + self._session_id = session_id self._next_message_id = 0 self._outstanding_requests = {} @@ -51,13 +52,12 @@ class DebugAdapterConnection( object ): msg[ 'seq' ] = this_id msg[ 'type' ] = 'request' - # TODO/FIXME: This is so messy expiry_id = vim.eval( 'timer_start( {}, ' ' function( "vimspector#internal#channel#Timeout", ' ' [ {} ] ) )'.format( timeout, - self._handler.session_id ) ) + self._session_id ) ) request = PendingRequest( msg, handler, diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index e1af7a7..7b1916d 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -42,25 +42,10 @@ VIMSPECTOR_HOME = utils.GetVimspectorBase() # cache of what the user entered for any option we ask them USER_CHOICES = {} -NEXT_SESSION_ID = 0 -SESSIONS = {} - - -def PushSession( session ): - global NEXT_SESSION_ID - this_id = NEXT_SESSION_ID - NEXT_SESSION_ID = NEXT_SESSION_ID + 1 - SESSIONS[ this_id ] = session - return this_id - - -def PopSession( session ): - SESSIONS.pop( session.session_id, None ) - - class DebugSession( object ): - def __init__( self, api_prefix ): - self.session_id = PushSession( self ) + def __init__( self, session_id, session_manager, api_prefix ): + self.session_id = session_id + self.manager = session_manager self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) @@ -91,7 +76,7 @@ class DebugSession( object ): def __del__( self ): - PopSession( self ) + self.manager.DestroySession( self ) def _ResetServerState( self ): @@ -931,8 +916,9 @@ class DebugSession( object ): handlers = [ self ] self._connection = debug_adapter_connection.DebugAdapterConnection( - handlers, - lambda msg: utils.Call( + handlers = handlers, + session_id = self.session_id, + send_func = lambda msg: utils.Call( "vimspector#internal#{}#Send".format( self._connection_type ), self.session_id, msg ) ) diff --git a/python3/vimspector/session_manager.py b/python3/vimspector/session_manager.py new file mode 100644 index 0000000..25a6f85 --- /dev/null +++ b/python3/vimspector/session_manager.py @@ -0,0 +1,46 @@ +# vimspector - A multi-language debugging system for Vim +# Copyright 2020 Ben Jackson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from vimspector.debug_session import DebugSession + + +class SessionManager: + next_session_id = 0 + sessions = {} + current_session = None + + + def NewSession( self, *args, **kwargs ): + session_id = self.next_session_id + self.next_session_id += 1 + session = DebugSession( session_id, self, *args, **kwargs ) + self.sessions[ session_id ] = session + + if self.current_session is None: + self.current_session = session.session_id + + return session + + + def DestroySession( self, session: DebugSession ): + del self.sessions[ session.session_id ] + + + def GetSession( self, session_id ): + return self.sessions.get( session_id ) + + + def CurrentSession( self ): + return self.GetSession( self.current_session ) From 0cdab6be4ef4fd6c2c54db2d80be77d549c3b8f5 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 25 Mar 2021 22:08:31 +0000 Subject: [PATCH 3/4] React to the debugpyAttach event and try and start a new session; this gets tripped up by the _vimspector.Variables (etc) buffers already existing. Probably need to add the sesssion ID to the buffer name --- autoload/vimspector/internal/state.vim | 8 ++-- python3/vimspector/custom/python.py | 46 +++++++++++++++++++ python3/vimspector/debug_session.py | 7 +-- python3/vimspector/gadgets.py | 3 +- python3/vimspector/session_manager.py | 12 ++++- .../python/multiprocessing/.vimspector.json | 24 ++++++++++ .../multiprocessing/multiprocessing_test.py | 27 +++++++++++ 7 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 python3/vimspector/custom/python.py create mode 100644 support/test/python/multiprocessing/.vimspector.json create mode 100644 support/test/python/multiprocessing/multiprocessing_test.py diff --git a/autoload/vimspector/internal/state.vim b/autoload/vimspector/internal/state.vim index e628eaa..1c0458f 100644 --- a/autoload/vimspector/internal/state.vim +++ b/autoload/vimspector/internal/state.vim @@ -27,12 +27,10 @@ endif function! vimspector#internal#state#Reset() abort try py3 import vim - py3 _vimspector_session_manager = __import__( + py3 _vimspector_session = __import__( \ "vimspector", - \ fromlist=[ "session_manager" ] ).session_manager.SessionManager() - - py3 _vimspector_session = _vimspector_session_manager = - \ _vimspector_session_manager.NewSession( vim.eval( 's:prefix' ) ) + \ fromlist=[ "session_manager" ] ).session_manager.Get().NewSession( + \ vim.eval( 's:prefix' ) ) catch /.*/ echohl WarningMsg echom 'Exception while loading vimspector:' v:exception diff --git a/python3/vimspector/custom/python.py b/python3/vimspector/custom/python.py new file mode 100644 index 0000000..8f6f578 --- /dev/null +++ b/python3/vimspector/custom/python.py @@ -0,0 +1,46 @@ +# vimspector - A multi-language debugging system for Vim +# Copyright 2021 Ben Jackson +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from vimspector.debug_session import DebugSession +from vimspector import session_manager + +from typing import Sequence + + +class Debugpy( object ): + parent: DebugSession + sessions: Sequence[ DebugSession ] + + def __init__( self, debug_session: DebugSession ): + self.parent = debug_session + + + def OnEvent_debugpyAttach( self, message ): + # Debugpy sends us the contents of a launch request that we should use. We + # probaly just jave to guess the rest + launch_argyments = message[ 'body' ] + 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_argyments + + # 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._StartWithConfiguration( self.parent._configuration, + self.parent._adapter ) + diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 7b1916d..f80c217 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -1190,9 +1190,10 @@ class DebugSession( object ): self._on_init_complete_handlers = [] self._logger.debug( "LAUNCH!" ) - self._launch_config = {} - self._launch_config.update( self._adapter.get( 'configuration', {} ) ) - self._launch_config.update( self._configuration[ 'configuration' ] ) + if self._launch_config is None: + self._launch_config = {} + self._launch_config.update( self._adapter.get( 'configuration', {} ) ) + self._launch_config.update( self._configuration[ 'configuration' ] ) request = self._configuration.get( 'remote-request', diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index 528f60c..5524899 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -136,7 +136,8 @@ GADGETS = { # doesn't support the custom messages) # https://github.com/puremourning/vimspector/issues/141 "subProcess": False, - } + }, + 'custom_handler': 'vimspector.custom.python.Debugpy' } }, }, diff --git a/python3/vimspector/session_manager.py b/python3/vimspector/session_manager.py index 25a6f85..e48f03d 100644 --- a/python3/vimspector/session_manager.py +++ b/python3/vimspector/session_manager.py @@ -15,6 +15,9 @@ from vimspector.debug_session import DebugSession +# Singleton +_session_manager = None + class SessionManager: next_session_id = 0 @@ -41,6 +44,13 @@ class SessionManager: def GetSession( self, session_id ): return self.sessions.get( session_id ) - def CurrentSession( self ): return self.GetSession( self.current_session ) + + +def Get(): + global _session_manager + if _session_manager is None: + _session_manager = SessionManager() + + return _session_manager diff --git a/support/test/python/multiprocessing/.vimspector.json b/support/test/python/multiprocessing/.vimspector.json new file mode 100644 index 0000000..66272e3 --- /dev/null +++ b/support/test/python/multiprocessing/.vimspector.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json", + "configurations": { + "run": { + "adapter": "debugpy", + "configuration": { + "request": "launch", + "type": "python", + "cwd": "${workspaceRoot}", + "program": "${file}", + "stopOnEntry": false, + "console": "integratedTerminal", + "subProcess": true + }, + "breakpoints": { + "exception": { + "raised": "N", + "uncaught": "Y", + "userUnhandled": "" + } + } + } + } +} diff --git a/support/test/python/multiprocessing/multiprocessing_test.py b/support/test/python/multiprocessing/multiprocessing_test.py new file mode 100644 index 0000000..1a4d0a7 --- /dev/null +++ b/support/test/python/multiprocessing/multiprocessing_test.py @@ -0,0 +1,27 @@ +import time +import multiprocessing as mp + + +def First(): + for _ in range( 100 ): + print( "in first" ) + time.sleep( 0.1 ) + + +def Second(): + for _ in range( 100 ): + print( "in second" ) + time.sleep( 0.1 ) + + +print( "main" ) +p1 = mp.Process( target=First ) +p2 = mp.Process( target=Second ) + +p1.start() +p2.start() + +p1.join() +p2.join() + +print( "Done" ) From 2f05a7f66abf9a836287f0edc09262700137c47b Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 31 Mar 2021 21:55:33 +0100 Subject: [PATCH 4/4] 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. --- autoload/vimspector/internal/channel.vim | 14 ++--- autoload/vimspector/internal/job.vim | 9 ++- autoload/vimspector/internal/state.vim | 37 +++++++++++-- plugin/vimspector.vim | 1 + python3/vimspector/breakpoints.py | 25 +++++++-- python3/vimspector/code.py | 18 +++--- python3/vimspector/custom/python.py | 55 +++++++++++++++---- .../vimspector/debug_adapter_connection.py | 4 +- python3/vimspector/debug_session.py | 46 ++++++++++++---- python3/vimspector/output.py | 20 ++++--- python3/vimspector/session_manager.py | 14 +++-- python3/vimspector/stack_trace.py | 14 +++-- python3/vimspector/utils.py | 32 ++++++++++- python3/vimspector/variables.py | 25 ++++++--- .../python/multiprocessing/.vimspector.json | 21 ++++++- .../multiprocessing/multiprocessing_test.py | 25 +++------ 16 files changed, 261 insertions(+), 99 deletions(-) diff --git a/autoload/vimspector/internal/channel.vim b/autoload/vimspector/internal/channel.vim index fb97b19..60eddf4 100644 --- a/autoload/vimspector/internal/channel.vim +++ b/autoload/vimspector/internal/channel.vim @@ -28,9 +28,8 @@ function! s:_OnServerData( session_id, channel, data ) abort return endif - py3 << EOF -_vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) -EOF + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnChannelData( + \ vim.eval( 'a:data' ) ) endfunction function! s:_OnClose( session_id, channel ) abort @@ -42,7 +41,7 @@ function! s:_OnClose( session_id, channel ) abort echom 'Channel closed' redraw unlet s:channels[ a:session_id ] - py3 _vimspector_session.OnServerExit( 0 ) + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerExit( 0 ) endfunction function! vimspector#internal#channel#StartDebugSession( @@ -99,9 +98,8 @@ function! vimspector#internal#channel#Send( session_id, msg ) abort endfunction function! vimspector#internal#channel#Timeout( session_id, id ) abort - py3 << EOF -_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) ) -EOF + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnRequestTimeout( + \ vim.eval( 'a:id' ) ) endfunction 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 ) endwhile 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 endfunction diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index 39dca29..413abb8 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -29,7 +29,8 @@ function! s:_OnServerData( session_id, channel, data ) abort return endif - py3 _vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnChannelData( + \ vim.eval( 'a:data' ) ) endfunction function! s:_OnServerError( session_id, channel, data ) abort @@ -39,7 +40,8 @@ function! s:_OnServerError( session_id, channel, data ) abort return endif - py3 _vimspector_session.OnServerStderr( vim.eval( 'a:data' ) ) + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerStderr( + \ vim.eval( 'a:data' ) ) endfunction @@ -58,7 +60,8 @@ function! s:_OnExit( session_id, channel, status ) abort if has_key( s:jobs, a:session_id ) unlet s:jobs[ a:session_id ] endif - py3 _vimspector_session.OnServerExit( vim.eval( 'a:status' ) ) + py3 _VimspectorSession( vim.eval( 'a:session_id' ) ).OnServerExit( + \ vim.eval( 'a:status' ) ) endfunction function! s:_OnClose( session_id, channel ) abort diff --git a/autoload/vimspector/internal/state.vim b/autoload/vimspector/internal/state.vim index 1c0458f..59966dd 100644 --- a/autoload/vimspector/internal/state.vim +++ b/autoload/vimspector/internal/state.vim @@ -26,11 +26,22 @@ endif function! vimspector#internal#state#Reset() abort try - py3 import vim - py3 _vimspector_session = __import__( - \ "vimspector", - \ fromlist=[ "session_manager" ] ).session_manager.Get().NewSession( - \ vim.eval( 's:prefix' ) ) + py3 <' ) ) + autocmd TabEnter * call vimspector#internal#state#OnTabEnter() augroup END " boilerplate {{{ diff --git a/python3/vimspector/breakpoints.py b/python3/vimspector/breakpoints.py index 7aa50ce..a33c468 100644 --- a/python3/vimspector/breakpoints.py +++ b/python3/vimspector/breakpoints.py @@ -34,13 +34,24 @@ class ServerBreakpointHandler( object ): 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 ): - def __init__( self ): + def __init__( self, session_id ): self._connection = None - self._logger = logging.getLogger( __name__ ) - utils.SetUpLogging( self._logger ) + self._logger = logging.getLogger( __name__ + '.' + str( session_id ) ) + 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._func_breakpoints = [] self._exception_breakpoints = None @@ -91,6 +102,12 @@ class ProjectBreakpoints( object ): # 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 ): # 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 diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index 98aeca5..ffcf912 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -20,20 +20,20 @@ from collections import defaultdict from vimspector import utils, terminal, signs +NEXT_SIGN_ID = 1 + class CodeView( object ): - def __init__( self, window, api_prefix ): + def __init__( self, session_id, window, api_prefix ): self._window = window self._api_prefix = api_prefix self._terminal = None self.current_syntax = None - self._logger = logging.getLogger( __name__ ) + self._logger = logging.getLogger( __name__ + '.' + str( session_id ) ) 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._signs = { 'vimspectorPC': None, @@ -92,8 +92,9 @@ class CodeView( object ): self._UndisplayPC( clear_pc = False ) # FIXME: Do we relly need to keep using up IDs ? - self._signs[ 'vimspectorPC' ] = self._next_sign_id - self._next_sign_id += 1 + global NEXT_SIGN_ID + self._signs[ 'vimspectorPC' ] = NEXT_SIGN_ID + NEXT_SIGN_ID += 1 sign = 'vimspectorPC' # If there's also a breakpoint on this line, use vimspectorPCBP @@ -247,8 +248,9 @@ class CodeView( object ): if 'line' not in breakpoint: continue - sign_id = self._next_sign_id - self._next_sign_id += 1 + global NEXT_SIGN_ID + sign_id = NEXT_SIGN_ID + NEXT_SIGN_ID += 1 self._signs[ 'breakpoints' ].append( sign_id ) if utils.BufferExists( file_name ): signs.PlaceSign( sign_id, diff --git a/python3/vimspector/custom/python.py b/python3/vimspector/custom/python.py index 8f6f578..2283ee1 100644 --- a/python3/vimspector/custom/python.py +++ b/python3/vimspector/custom/python.py @@ -14,7 +14,7 @@ # limitations under the License. from vimspector.debug_session import DebugSession -from vimspector import session_manager +from vimspector import session_manager, gadgets, utils from typing import Sequence @@ -25,22 +25,53 @@ class Debugpy( object ): def __init__( self, debug_session: DebugSession ): 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 ): # Debugpy sends us the contents of a launch request that we should use. We # probaly just jave to guess the rest - launch_argyments = message[ 'body' ] - session = session_manager.Get().NewSession( self.parent._api_prefix ) + launch_arguments = message[ 'body' ] + self.queue.append( launch_arguments ) - # Inject the launch config (HACK!). This will actually mean that the - # configuration passed below is ignored. - session._launch_config = launch_argyments + # We use a queue because the confirm mechanism is quasi-modal and we can't + # do multiple 'confirm' dialogs at once. It's not uncommon for + # 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 - # tell the new session that it shoud not support "Restart" requests ? - # - # In fact, what even would Reset do... ? - session._StartWithConfiguration( self.parent._configuration, - self.parent._adapter ) + def HandleNext( self ): + if not self.queue: + return + + launch_argyments = self.queue[ 0 ] + 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' ] ) diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index d4cc239..edebf68 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -30,8 +30,8 @@ class PendingRequest( object ): class DebugAdapterConnection( object ): def __init__( self, handlers, session_id, send_func ): - self._logger = logging.getLogger( __name__ ) - utils.SetUpLogging( self._logger ) + self._logger = logging.getLogger( __name__ + '.' + str( session_id ) ) + utils.SetUpLogging( self._logger, session_id ) self._Write = send_func self._SetState( 'READ_HEADER' ) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index f80c217..82eb70e 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -46,12 +46,13 @@ class DebugSession( object ): def __init__( self, session_id, session_manager, api_prefix ): self.session_id = session_id self.manager = session_manager - self._logger = logging.getLogger( __name__ ) - utils.SetUpLogging( self._logger ) + self._logger = logging.getLogger( __name__ + '.' + str( session_id ) ) + utils.SetUpLogging( self._logger, session_id ) 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( 'VIMSPECTOR_HOME = %s', VIMSPECTOR_HOME ) self._logger.info( 'gadgetDir = %s', @@ -62,7 +63,7 @@ class DebugSession( object ): self._stackTraceView = None self._variablesView = None self._outputView = None - self._breakpoints = breakpoints.ProjectBreakpoints() + self._breakpoints = breakpoints.ProjectBreakpoints( session_id ) self._splash_screen = None self._remote_term = None @@ -414,7 +415,13 @@ class DebugSession( object ): if self._uiTab: 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 self._splash_screen = utils.HideSplash( self._api_prefix, @@ -673,6 +680,15 @@ class DebugSession( object ): def _SetUpUI( self ): 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 mode = settings.Get( 'ui_mode' ) @@ -716,7 +732,9 @@ class DebugSession( object ): def _SetUpUIHorizontal( self ): # Code 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 vim.command( @@ -741,7 +759,8 @@ class DebugSession( object ): with utils.LetCurrentWindow( stack_trace_window ): vim.command( f'{ one_third }wincmd _' ) - self._variablesView = variables.VariablesView( vars_window, + self._variablesView = variables.VariablesView( self, + vars_window, watch_window ) # Output/logging @@ -749,7 +768,8 @@ class DebugSession( object ): vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' ) output_window = vim.current.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 # indicate which tab was created and store all the tabs @@ -772,7 +792,9 @@ class DebugSession( object ): def _SetUpUIVertical( self ): # Code 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 vim.command( @@ -799,7 +821,8 @@ class DebugSession( object ): with utils.LetCurrentWindow( stack_trace_window ): vim.command( f'{ one_third }wincmd |' ) - self._variablesView = variables.VariablesView( vars_window, + self._variablesView = variables.VariablesView( self, + vars_window, watch_window ) @@ -808,7 +831,8 @@ class DebugSession( object ): vim.command( f'rightbelow { settings.Int( "bottombar_height" ) }new' ) output_window = vim.current.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 # indicate which tab was created and store all the tabs diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index 3322b0d..223eb96 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -58,13 +58,17 @@ class OutputView( object ): files or the output of commands.""" _buffers: typing.Dict[ str, TabBuffer ] - def __init__( self, window, api_prefix ): + def __init__( self, window, api_prefix, session_id = None ): self._window = window self._buffers = {} self._api_prefix = api_prefix 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 ): self._Print( 'server', text.splitlines() ) @@ -176,9 +180,9 @@ class OutputView( object ): if cmd is not None: out = utils.SetUpCommandBuffer( - self._session_id, # TODO: not really a session id + self._session_id, cmd, - category, + utils.BufferNameForSession( category, self._session_id ), self._api_prefix, completion_handler = completion_handler ) @@ -191,6 +195,8 @@ class OutputView( object ): else: name = 'vimspector.Output:{0}'.format( category ) + name = utils.BufferNameForSession( name, self._session_id ) + tab_buffer = TabBuffer( utils.NewEmptyBuffer(), len( self._buffers ) ) self._buffers[ category ] = tab_buffer @@ -253,8 +259,8 @@ class OutputView( object ): class DAPOutputView( OutputView ): """Specialised OutputView which adds the DAP Console (REPL)""" - def __init__( self, *args ): - super().__init__( *args ) + def __init__( self, *args, **kwargs ): + super().__init__( *args, **kwargs ) self._connection = None for b in set( BUFFER_MAP.values() ): diff --git a/python3/vimspector/session_manager.py b/python3/vimspector/session_manager.py index e48f03d..1564388 100644 --- a/python3/vimspector/session_manager.py +++ b/python3/vimspector/session_manager.py @@ -22,7 +22,6 @@ _session_manager = None class SessionManager: next_session_id = 0 sessions = {} - current_session = None def NewSession( self, *args, **kwargs ): @@ -31,21 +30,24 @@ class SessionManager: session = DebugSession( session_id, self, *args, **kwargs ) self.sessions[ session_id ] = session - if self.current_session is None: - self.current_session = session.session_id - return session def DestroySession( self, session: DebugSession ): + # TODO: Call this! del self.sessions[ session.session_id ] def GetSession( self, 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(): diff --git a/python3/vimspector/stack_trace.py b/python3/vimspector/stack_trace.py index ae14e68..f55224a 100644 --- a/python3/vimspector/stack_trace.py +++ b/python3/vimspector/stack_trace.py @@ -86,8 +86,8 @@ class StackTraceView( object ): _line_to_thread = typing.Dict[ int, Thread ] def __init__( self, session, win ): - self._logger = logging.getLogger( __name__ ) - utils.SetUpLogging( self._logger ) + self._logger = logging.getLogger( __name__ + '.' + str( session.session_id ) ) + utils.SetUpLogging( self._logger, session.session_id ) self._buf = win.buffer self._session = session @@ -104,7 +104,10 @@ class StackTraceView( object ): # FIXME: This ID is by group, so should be module scope 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 ) mappings = settings.Dict( 'mappings' )[ 'stack_trace' ] @@ -562,7 +565,10 @@ class StackTraceView( object ): buf = utils.BufferForFile( buf_name ) 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 with utils.ModifiableScratchBuffer( buf ): utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index ef0a077..242d070 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -32,13 +32,30 @@ LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) ) _log_handler = logging.FileHandler( LOG_FILE, mode = 'w' ) _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 ) if _log_handler not in logger.handlers: logger.addHandler( _log_handler ) + logger.addFilter( ContextLogFilter( context ) ) _logger = logging.getLogger( __name__ ) @@ -404,6 +421,8 @@ def Confirm( api_prefix, default_value = 2, options: 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: options = [ '(Y)es', '(N)o' ] if not keys: @@ -871,3 +890,12 @@ def UseWinBar(): # Buggy neovim doesn't render correctly when the WinBar is defined: # https://github.com/neovim/neovim/issues/12689 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}]' diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index 5cdd712..5f64ba6 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -166,10 +166,11 @@ def AddExpandMappings( mappings = None ): class VariablesView( object ): - def __init__( self, variables_win, watches_win ): - self._logger = logging.getLogger( __name__ ) - utils.SetUpLogging( self._logger ) + def __init__( self, session, variables_win, watches_win ): + self._logger = logging.getLogger( __name__ + '.' + str( session.session_id ) ) + utils.SetUpLogging( self._logger, session.session_id ) + self._session = session self._connection = None self._current_syntax = '' self._server_capabilities = None @@ -182,7 +183,10 @@ class VariablesView( object ): # 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' ) + utils.SetUpHiddenBuffer( + self._vars.buf, + utils.BufferNameForSession( 'vimspector.Variables', + self._session.session_id ) ) with utils.LetCurrentWindow( variables_win ): if utils.UseWinBar(): vim.command( 'nnoremenu 1.1 WinBar.Set ' @@ -193,11 +197,14 @@ class VariablesView( object ): # there) self._watches: typing.List[ Watch ] = [] self._watch = View( watches_win, {}, self._DrawWatches ) - utils.SetUpPromptBuffer( self._watch.buf, - 'vimspector.Watches', - 'Expression: ', - 'vimspector#AddWatchPrompt', - 'vimspector#OmniFuncWatch' ) + utils.SetUpPromptBuffer( + self._watch.buf, + utils.BufferNameForSession( 'vimspector.Watches', + self._session.session_id ), + 'Expression: ', + 'vimspector#AddWatchPrompt', + 'vimspector#OmniFuncWatch' ) + with utils.LetCurrentWindow( watches_win ): AddExpandMappings( mappings ) for mapping in utils.GetVimList( mappings, 'delete' ): diff --git a/support/test/python/multiprocessing/.vimspector.json b/support/test/python/multiprocessing/.vimspector.json index 66272e3..6eee4c7 100644 --- a/support/test/python/multiprocessing/.vimspector.json +++ b/support/test/python/multiprocessing/.vimspector.json @@ -8,7 +8,26 @@ "type": "python", "cwd": "${workspaceRoot}", "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", "subProcess": true }, diff --git a/support/test/python/multiprocessing/multiprocessing_test.py b/support/test/python/multiprocessing/multiprocessing_test.py index 1a4d0a7..c00d86d 100644 --- a/support/test/python/multiprocessing/multiprocessing_test.py +++ b/support/test/python/multiprocessing/multiprocessing_test.py @@ -3,25 +3,16 @@ import multiprocessing as mp def First(): - for _ in range( 100 ): - print( "in first" ) + for i in range( 10 ): + print( f"in first x {i}" ) time.sleep( 0.1 ) -def Second(): - for _ in range( 100 ): - print( "in second" ) - time.sleep( 0.1 ) +if __name__ == '__main__': + print( "main" ) + p1 = mp.Process( target=First ) + p1.start() + p1.join() -print( "main" ) -p1 = mp.Process( target=First ) -p2 = mp.Process( target=Second ) - -p1.start() -p2.start() - -p1.join() -p2.join() - -print( "Done" ) + print( "Done" )