Very hacky: Support for attach

This introduced more hacks than it should. In particular, the sequence
about stack trace requesting became very messy. When we attach, we don't
instantly get a stopped event. This required making the Pause command
actually work (sort of). In this case we often won't have a proper
current thread.

Instead we sort of request all threads whenever we get a thread event,
thought his is horribly hacky and we should really just use the thread
event as-is. We then attempt to pause ALL threads on pause and continue
requests when we don't know the current thread.

Another issue is that when pausing it's likely we don't have the source
location info for the pause location (something like select() or
whatever), so we only set the current frame to the lowest one we have
source for. This sort of roughly matches what you want.

This whole thing makes it clear that threads and stack trace in separate
panes makes no sense. We need to replicate the hierarchy in the
variables view for threads and stack traces.
This commit is contained in:
Ben Jackson 2018-05-27 23:40:37 +01:00
commit b02d35e78d
6 changed files with 151 additions and 31 deletions

View file

@ -52,12 +52,15 @@ class CodeView( object ):
def SetCurrentFrame( self, frame ):
vim.current.window = self._window
if self._signs[ 'vimspectorPC' ]:
vim.command( 'sign unplace {0}'.format( self._signs[ 'vimspectorPC' ] ) )
self._signs[ 'vimspectorPC' ] = None
if not frame or not frame[ 'source' ]:
return
vim.current.window = self._window
buffer_number = vim.eval( 'bufnr( "{0}", 1 )'.format(
frame[ 'source' ][ 'path' ] ) )

View file

@ -41,6 +41,10 @@ class DebugAdapterConnection( object ):
self._outstanding_requests[ this_id ] = handler
self._SendMessage( msg )
def Reset( self ):
self._Write = None
self._handler = None
def OnData( self, data ):
data = bytes( data, 'utf-8' )
self._logger.debug( 'Received ({0}/{1}): {2},'.format( type( data ),
@ -111,6 +115,9 @@ class DebugAdapterConnection( object ):
self._SetState( 'READ_HEADER' )
def _OnMessageReceived( self, message ):
if not self._handler:
return
if message[ 'type' ] == 'response':
handler = self._outstanding_requests.pop( message[ 'request_seq' ] )

View file

@ -38,12 +38,13 @@ class DebugSession( object ):
self._connection = None
self._uiTab = None
self._threadsBuffer = None
self._threadsBuffer = None # TODO: Move to stack trace
self._outputBuffer = None
self._stackTraceView = None
self._variablesView = None
self._currentThread = None
self._threads = []
self._currentFrame = None
self._next_sign_id = SIGN_ID_OFFSET
@ -158,6 +159,9 @@ class DebugSession( object ):
vim.eval( 'vimspector#internal#state#Reset()' )
def StepOver( self ):
if self._currentThread is None:
return
self._connection.DoRequest( None, {
'command': 'next',
'arguments': {
@ -166,6 +170,9 @@ class DebugSession( object ):
} )
def StepInto( self ):
if self._currentThread is None:
return
self._connection.DoRequest( None, {
'command': 'stepIn',
'arguments': {
@ -174,6 +181,9 @@ class DebugSession( object ):
} )
def StepOut( self ):
if self._currentThread is None:
return
self._connection.DoRequest( None, {
'command': 'stepOut',
'arguments': {
@ -182,6 +192,15 @@ class DebugSession( object ):
} )
def Continue( self ):
if self._currentThread is None:
for thread in self._threads:
self._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': thread[ 'id' ]
},
} )
self._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
@ -189,7 +208,18 @@ class DebugSession( object ):
},
} )
self.ClearCurrentFrame()
def Pause( self ):
if self._currentThread is None:
for thread in self._threads:
self._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': thread[ 'id' ],
},
} )
self._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
@ -207,6 +237,9 @@ class DebugSession( object ):
self._variablesView.DeleteWatch()
def ShowBalloon( self, winnr, expression ):
if self._currentFrame is None:
return
if winnr == int( self._codeView._window.number ):
self._variablesView.ShowBalloon( self._currentFrame, expression )
else:
@ -251,11 +284,19 @@ class DebugSession( object ):
self._variablesView = variables.VariablesView( self._connection,
vim.current.buffer )
def ClearCurrentFrame( self ):
self.SetCurrentFrame( None )
def SetCurrentFrame( self, frame ):
self._currentFrame = frame
self._codeView.SetCurrentFrame( frame )
self._variablesView.LoadScopes( frame )
self._variablesView.EvaluateWatches()
if frame:
self._variablesView.LoadScopes( frame )
self._variablesView.EvaluateWatches()
else:
self._stackTraceView.Clear()
self._variablesView.Clear()
def _StartDebugAdapter( self ):
self._logger.info( 'Starting debug adapter with: {0}'.format( json.dumps(
@ -274,6 +315,7 @@ class DebugSession( object ):
def _StopDebugAdapter( self, callback = None ):
def handler( message ):
vim.eval( 'vimspector#internal#job#StopDebugSession()' )
self._connection.Reset()
self._connection = None
self._stackTraceView.ConnectionClosed()
self._variablesView.ConnectionClosed()
@ -288,24 +330,42 @@ class DebugSession( object ):
} )
def _SelectProcess( self, adapter_config, launch_config ):
atttach_config = adapter_config[ 'attach' ]
if atttach_config[ 'pidSelect' ] == 'ask':
pid = utils.AskForInput( 'Enter PID to attach to: ' )
launch_config[ atttach_config[ 'pidProperty' ] ] = pid
return
raise ValueError( 'Unrecognised pidSelect {0}'.format(
atttach_config[ 'pidSelect' ] ) )
def _Initialise( self ):
adapter_config = self._configuration[ 'adapter' ]
launch_config = self._configuration[ 'configuration' ]
if 'attach' in adapter_config:
self._SelectProcess( adapter_config, launch_config )
self._connection.DoRequest( None, {
'command': 'initialize',
'arguments': {
'adapterID': self._configuration[ 'adapter' ].get( 'name', 'adapter' ),
'adapterID': adapter_config.get( 'name', 'adapter' ),
'linesStartAt1': True,
'columnsStartAt1': True,
'pathFormat': 'path',
},
} )
# FIXME: name is mandatory. Forcefully add it (we should really use the
# _actual_ name, but that isn't actually remembered at this point)
if 'name' not in self._configuration[ 'configuration' ]:
self._configuration[ 'configuration' ][ 'name' ] = 'test'
if 'name' not in launch_config:
launch_config[ 'name' ] = 'test'
self._connection.DoRequest( None, {
'command': self._configuration[ 'configuration' ][ 'request' ],
'arguments': self._configuration[ 'configuration' ]
'command': launch_config[ 'request' ],
'arguments': launch_config
} )
def _UpdateBreakpoints( self, source, message ):
@ -314,13 +374,10 @@ class DebugSession( object ):
def OnEvent_initialized( self, message ):
self._codeView.ClearBreakpoints()
self._SendBreakpoints()
def OnEvent_thread( self, message ):
# TODO: set self_currentThread ? Not really that useful I guess as the
# stopped event basically gives us this.
pass
self._GetThreads()
def OnEvent_breakpoint( self, message ):
reason = message[ 'body' ][ 'reason' ]
@ -338,6 +395,7 @@ class DebugSession( object ):
self._codeView.Clear()
self._stackTraceView.Clear()
self._variablesView.Clear()
self._threads.clear()
with utils.ModifiableScratchBuffer( self._threadsBuffer ):
self._threadsBuffer[:] = None
@ -415,19 +473,36 @@ class DebugSession( object ):
self._outputBuffer.append( t, 0 )
def OnEvent_stopped( self, message ):
self._currentThread = message[ 'body' ][ 'threadId' ]
event = message[ 'body' ]
utils.UserMessage( 'Paused in thread {0} due to {1}'.format(
event.get( 'threadId', '<unknown>' ),
event.get( 'description', event[ 'reason' ] ) ) )
if 'threadId' in event:
self._currentThread = event[ 'threadId' ]
elif event.get( 'allThreadsStopped', False ) and self._threads:
self._currentThread = self._threads[ 0 ][ 'id' ]
self._GetThreads()
self._stackTraceView.LoadStackTrace( self._currentThread )
def _GetThreads( self ):
# TODO: We need an expandable thing like variables for threads, and allow
# the user to select a thread
def threads_printer( message ):
self._threads.clear()
with utils.ModifiableScratchBuffer( self._threadsBuffer ):
self._threadsBuffer[:] = None
self._threadsBuffer.append( 'Threads: ' )
for thread in message[ 'body' ][ 'threads' ]:
if self._currentThread is None:
self._currentThread = thread[ 'id' ]
self._threads.append( thread )
self._threadsBuffer.append(
'Thread {0}: {1}'.format( thread[ 'id' ], thread[ 'name' ] ) )
self._connection.DoRequest( threads_printer, {
'command': 'threads',
} )
self._stackTraceView.LoadStackTrace( self._currentThread )

View file

@ -67,8 +67,14 @@ class StackTraceView( object ):
stackFrames = message[ 'body' ][ 'stackFrames' ]
current_frame = None
for frame in stackFrames:
source = frame[ 'source' ] or { 'name': '<unknown>' }
if frame[ 'source' ]:
current_frame = current_frame or frame
source = frame[ 'source' ]
else:
source = { 'name': '<unknown>' }
self._buf.append(
'{0}: {1}@{2}:{3}'.format( frame[ 'id' ],
frame[ 'name' ],
@ -76,4 +82,4 @@ class StackTraceView( object ):
frame[ 'line' ] ) )
self._line_to_frame[ len( self._buf ) ] = frame
self._session.SetCurrentFrame( stackFrames[ 0 ] )
self._session.SetCurrentFrame( current_frame )

View file

@ -96,18 +96,30 @@ def UserMessage( msg, persist=False ):
vim.command( "{0} '{1}'".format( cmd, Escape( line ) ) )
def SelectFromList( prompt, options ):
@contextlib.contextmanager
def InputSave():
vim.eval( 'inputsave()' )
display_options = [ prompt ]
display_options.extend( [ '{0}: {1}'.format( i + 1, v )
for i, v in enumerate( options ) ] )
try:
selection = int( vim.eval(
'inputlist( ' + json.dumps( display_options ) + ' )' ) ) - 1
if selection < 0 or selection >= len( options ):
return None
return options[ selection ]
except KeyboardInterrupt:
return None
finally:
yield
except:
vim.eval( 'inputrestore()' )
def SelectFromList( prompt, options ):
with InputSave():
display_options = [ prompt ]
display_options.extend( [ '{0}: {1}'.format( i + 1, v )
for i, v in enumerate( options ) ] )
try:
selection = int( vim.eval(
'inputlist( ' + json.dumps( display_options ) + ' )' ) ) - 1
if selection < 0 or selection >= len( options ):
return None
return options[ selection ]
except KeyboardInterrupt:
return None
def AskForInput( prompt ):
with InputSave():
return vim.eval( "input( '{0}' )".format( Escape( prompt ) ) )