Move the thread handling into the stack frame handling

This shows stack traces as children of a thread, removing one of the
UI splits. However, we still have the somewhat awkward concepts of
"current" thread and "current" stack frame. These are messy and
incredibly fiddly. And in any case, probably wrong _most_ of the time.
This commit is contained in:
Ben Jackson 2018-05-28 02:34:05 +01:00
commit 5ed9a06ed5
3 changed files with 175 additions and 116 deletions

View file

@ -72,7 +72,7 @@ function! vimspector#DeleteWatch() abort
endfunction endfunction
function! vimspector#GoToFrame() abort function! vimspector#GoToFrame() abort
py3 _vimspector_session.GoToFrame() py3 _vimspector_session.ExpandFrameOrThread()
endfunction endfunction
function! vimspector#AddWatch( expr ) abort function! vimspector#AddWatch( expr ) abort

View file

@ -38,14 +38,10 @@ class DebugSession( object ):
self._connection = None self._connection = None
self._uiTab = None self._uiTab = None
self._threadsBuffer = None # TODO: Move to stack trace
self._outputBuffer = None # TODO: Need something less terrible here self._outputBuffer = None # TODO: Need something less terrible here
self._stackTraceView = None self._stackTraceView = None
self._variablesView = None self._variablesView = None
self._currentThread = None
self._threads = []
self._currentFrame = None
self._next_sign_id = SIGN_ID_OFFSET self._next_sign_id = SIGN_ID_OFFSET
# TODO: Move to code view and consolidate into user-requested-breakpoints, # TODO: Move to code view and consolidate into user-requested-breakpoints,
@ -132,7 +128,8 @@ class DebugSession( object ):
self.Start() self.Start()
def OnChannelData( self, data ): def OnChannelData( self, data ):
self._connection.OnData( data ) if self._connection:
self._connection.OnData( data )
def OnChannelClosed( self ): def OnChannelClosed( self ):
self._connection = None self._connection = None
@ -159,96 +156,68 @@ class DebugSession( object ):
vim.eval( 'vimspector#internal#state#Reset()' ) vim.eval( 'vimspector#internal#state#Reset()' )
def StepOver( self ): def StepOver( self ):
if self._currentThread is None: if self._stackTraceView.GetCurrentThreadId() is None:
return return
self._connection.DoRequest( None, { self._connection.DoRequest( None, {
'command': 'next', 'command': 'next',
'arguments': { 'arguments': {
'threadId': self._currentThread 'threadId': self._stackTraceView.GetCurrentThreadId()
}, },
} ) } )
def StepInto( self ): def StepInto( self ):
if self._currentThread is None: if self._stackTraceView.GetCurrentThreadId() is None:
return return
self._connection.DoRequest( None, { self._connection.DoRequest( None, {
'command': 'stepIn', 'command': 'stepIn',
'arguments': { 'arguments': {
'threadId': self._currentThread 'threadId': self._stackTraceView.GetCurrentThreadId()
}, },
} ) } )
def StepOut( self ): def StepOut( self ):
if self._currentThread is None: if self._stackTraceView.GetCurrentThreadId() is None:
return return
self._connection.DoRequest( None, { self._connection.DoRequest( None, {
'command': 'stepOut', 'command': 'stepOut',
'arguments': { 'arguments': {
'threadId': self._currentThread 'threadId': self._stackTraceView.GetCurrentThreadId()
}, },
} ) } )
def Continue( self ): def Continue( self ):
if self._currentThread is None: self._stackTraceView.Continue()
for thread in self._threads:
self._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': thread[ 'id' ]
},
} )
self._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': self._currentThread
},
} )
self.ClearCurrentFrame()
def Pause( self ): def Pause( self ):
if self._currentThread is None: self._stackTraceView.Pause()
for thread in self._threads:
self._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': thread[ 'id' ],
},
} )
self._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': self._currentThread
},
} )
def ExpandVariable( self ): def ExpandVariable( self ):
self._variablesView.ExpandVariable() self._variablesView.ExpandVariable()
def AddWatch( self, expression ): def AddWatch( self, expression ):
self._variablesView.AddWatch( self._currentFrame, expression ) self._variablesView.AddWatch( self._stackTraceView.GetCurrentFrame(),
expression )
def DeleteWatch( self ): def DeleteWatch( self ):
self._variablesView.DeleteWatch() self._variablesView.DeleteWatch()
def ShowBalloon( self, winnr, expression ): def ShowBalloon( self, winnr, expression ):
if self._currentFrame is None: if self._stackTraceView.GetCurrentFrame() is None:
return return
if winnr == int( self._codeView._window.number ): if winnr == int( self._codeView._window.number ):
self._variablesView.ShowBalloon( self._currentFrame, expression ) self._variablesView.ShowBalloon( self._stackTraceView.GetCurrentFrame(),
expression )
else: else:
self._logger.debug( 'Winnr {0} is not the code window {1}'.format( self._logger.debug( 'Winnr {0} is not the code window {1}'.format(
winnr, winnr,
self._codeView._window.number ) ) self._codeView._window.number ) )
def GoToFrame( self ): def ExpandFrameOrThread( self ):
self._stackTraceView.GoToFrame() self._stackTraceView.ExpandFrameOrThread()
def _SetUpUI( self ): def _SetUpUI( self ):
vim.command( 'tabnew' ) vim.command( 'tabnew' )
@ -257,21 +226,15 @@ class DebugSession( object ):
# Code window # Code window
self._codeView = code.CodeView( vim.current.window ) self._codeView = code.CodeView( vim.current.window )
# Threads # Call stack
vim.command( '50vspl' ) vim.command( 'vspl' )
vim.command( 'enew' ) vim.command( 'enew' )
self._threadsBuffer = vim.current.buffer self._stackTraceView = stack_trace.StackTraceView( self,
utils.SetUpScratchBuffer( self._threadsBuffer, "vimspector.Threads" ) self._connection,
vim.current.buffer )
with utils.TemporaryVimOption( 'eadirection', 'ver' ): with utils.TemporaryVimOption( 'eadirection', 'ver' ):
with utils.TemporaryVimOption( 'equalalways', 1 ): with utils.TemporaryVimOption( 'equalalways', 1 ):
# Call stack
vim.command( 'spl' )
vim.command( 'enew' )
self._stackTraceView = stack_trace.StackTraceView( self,
self._connection,
vim.current.buffer )
# Output/logging # Output/logging
vim.command( 'spl' ) vim.command( 'spl' )
vim.command( 'enew' ) vim.command( 'enew' )
@ -288,7 +251,6 @@ class DebugSession( object ):
self.SetCurrentFrame( None ) self.SetCurrentFrame( None )
def SetCurrentFrame( self, frame ): def SetCurrentFrame( self, frame ):
self._currentFrame = frame
self._codeView.SetCurrentFrame( frame ) self._codeView.SetCurrentFrame( frame )
if frame: if frame:
@ -377,7 +339,12 @@ class DebugSession( object ):
self._SendBreakpoints() self._SendBreakpoints()
def OnEvent_thread( self, message ): def OnEvent_thread( self, message ):
self._GetThreads() if message[ 'body' ][ 'reason' ] == 'started':
pass
elif message[ 'body' ][ 'reason' ] == 'exited':
pass
self._stackTraceView.OnThreadEvent( message[ 'body' ] )
def OnEvent_breakpoint( self, message ): def OnEvent_breakpoint( self, message ):
reason = message[ 'body' ][ 'reason' ] reason = message[ 'body' ][ 'reason' ]
@ -395,9 +362,6 @@ class DebugSession( object ):
self._codeView.Clear() self._codeView.Clear()
self._stackTraceView.Clear() self._stackTraceView.Clear()
self._variablesView.Clear() self._variablesView.Clear()
self._threads.clear()
with utils.ModifiableScratchBuffer( self._threadsBuffer ):
self._threadsBuffer[:] = None
def OnEvent_terminated( self, message ): def OnEvent_terminated( self, message ):
utils.UserMessage( "The program was terminated because: {0}".format( utils.UserMessage( "The program was terminated because: {0}".format(
@ -474,35 +438,9 @@ class DebugSession( object ):
def OnEvent_stopped( self, message ): def OnEvent_stopped( self, message ):
event = message[ 'body' ] event = message[ 'body' ]
utils.UserMessage( 'Paused in thread {0} due to {1}'.format( utils.UserMessage( 'Paused in thread {0} due to {1}'.format(
event.get( 'threadId', '<unknown>' ), event.get( 'threadId', '<unknown>' ),
event.get( 'description', event[ 'reason' ] ) ) ) event.get( 'description', event[ 'reason' ] ) ) )
if 'threadId' in event: self._stackTraceView.OnStopped( 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',
} )

View file

@ -24,13 +24,29 @@ class StackTraceView( object ):
self._session = session self._session = session
self._connection = connection self._connection = connection
self._currentThread = None
self._currentFrame = None
self._threads = []
utils.SetUpScratchBuffer( self._buf, 'vimspector.StackTrace' ) utils.SetUpScratchBuffer( self._buf, 'vimspector.StackTrace' )
vim.current.buffer = self._buf vim.current.buffer = self._buf
vim.command( 'nnoremap <buffer> <CR> :call vimspector#GoToFrame()<CR>' ) vim.command( 'nnoremap <buffer> <CR> :call vimspector#GoToFrame()<CR>' )
self._line_to_frame = {} self._line_to_frame = {}
self._line_to_thread = {}
def GetCurrentThreadId( self ):
return self._currentThread
def GetCurrentFrame( self ):
return self._currentFrame
def Clear( self ): def Clear( self ):
self._currentFrame = None
self._currentThread = None
self._threads = []
with utils.ModifiableScratchBuffer( self._buf ): with utils.ModifiableScratchBuffer( self._buf ):
self._buf[:] = None self._buf[:] = None
@ -42,44 +58,149 @@ class StackTraceView( object ):
self.Clear() self.Clear()
# TODO: delete the buffer ? # TODO: delete the buffer ?
def LoadStackTrace( self, thread_id ): def LoadThreads( self, infer_current_frame ):
self._connection.DoRequest( self._PrintStackTrace, { def consume_threads( message ):
self._threads.clear()
for thread in message[ 'body' ][ 'threads' ]:
self._threads.append( thread )
if infer_current_frame and thread[ 'id' ] == self._currentThread:
self._LoadStackTrace( thread, True )
elif infer_current_frame and not self._currentThread:
self._currentThread = thread[ 'id' ]
self._LoadStackTrace( thread, True )
self._DrawThreads()
self._connection.DoRequest( consume_threads, {
'command': 'threads',
} )
def _DrawThreads( self ):
self._line_to_frame.clear()
self._line_to_thread.clear()
with utils.ModifiableScratchBuffer( self._buf ):
self._buf[:] = None
for thread in self._threads:
icon = '+' if '_frames' not in thread else '-'
self._buf.append( '{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
self._line_to_thread[ len( self._buf ) ] = thread
self._DrawStackTrace( thread )
def _LoadStackTrace( self, thread, infer_current_frame ):
def consume_stacktrace( message ):
thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ]
if infer_current_frame:
for frame in thread[ '_frames' ]:
if frame[ 'source' ]:
self._JumpToFrame( frame )
break
self._DrawThreads()
self._connection.DoRequest( consume_stacktrace, {
'command': 'stackTrace', 'command': 'stackTrace',
'arguments': { 'arguments': {
'threadId': thread_id, 'threadId': thread[ 'id' ],
} }
} ) } )
def GoToFrame( self ): def ExpandFrameOrThread( self ):
if vim.current.buffer != self._buf: if vim.current.buffer != self._buf:
return return
current_line = vim.current.window.cursor[ 0 ] current_line = vim.current.window.cursor[ 0 ]
if current_line not in self._line_to_frame:
if current_line in self._line_to_frame:
self._JumpToFrame( self._line_to_frame[ current_line ] )
elif current_line in self._line_to_thread:
thread = self._line_to_thread[ current_line ]
if '_frames' in thread:
del thread[ '_frames' ]
self._DrawThreads()
else:
self._LoadStackTrace( thread, False )
def _JumpToFrame( self, frame ):
self._currentFrame = frame
self._session.SetCurrentFrame( self._currentFrame )
def OnStopped( self, event ):
if 'threadId' in event:
self._currentThread = event[ 'threadId' ]
elif event.get( 'allThreadsStopped', False ) and self._threads:
self._currentThread = self._threads[ 0 ][ 'id' ]
# if threadId:
# for thread in self._threads:
# if thread[ 'id' ] == self._currentThread:
# self._LoadStackTrace( thread, True )
# return
self.LoadThreads( True )
def OnThreadEvent( self, event ):
if event[ 'reason' ] == 'started' and self._currentThread is None:
self.LoadThreads( True )
def Continue( self ):
if not self._currentThread:
for thread in self._threads:
self._session._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': thread[ 'id' ]
},
} )
return return
self._session.SetCurrentFrame( self._line_to_frame[ current_line ] ) self._session._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': self._currentThread,
},
} )
def _PrintStackTrace( self, message ): self._session.ClearCurrentFrame()
with utils.ModifiableScratchBuffer( self._buf ):
self._buf[:] = None
self._buf.append( 'Stack trace' )
stackFrames = message[ 'body' ][ 'stackFrames' ] def Pause( self ):
if not self._currentThread:
for thread in self._threads:
self._session._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': thread[ 'id' ],
},
} )
return
current_frame = None self._session._connection.DoRequest( None, {
for frame in stackFrames: 'command': 'pause',
if frame[ 'source' ]: 'arguments': {
current_frame = current_frame or frame 'threadId': self._currentThread,
source = frame[ 'source' ] },
else: } )
source = { 'name': '<unknown>' }
self._buf.append( def _DrawStackTrace( self, thread ):
'{0}: {1}@{2}:{3}'.format( frame[ 'id' ], if '_frames' not in thread:
return
stackFrames = thread[ '_frames' ]
for frame in stackFrames:
if frame[ 'source' ]:
source = frame[ 'source' ]
else:
source = { 'name': '<unknown>' }
self._buf.append(
' {0}: {1}@{2}:{3}'.format( frame[ 'id' ],
frame[ 'name' ], frame[ 'name' ],
source[ 'name' ], source[ 'name' ],
frame[ 'line' ] ) ) frame[ 'line' ] ) )
self._line_to_frame[ len( self._buf ) ] = frame self._line_to_frame[ len( self._buf ) ] = frame
self._session.SetCurrentFrame( current_frame )