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