vimspector/python3/vimspector/stack_trace.py
Ben Jackson 23e5f6bbf4 Switch to running the actual install_gadget.py
This re-uses the OutputView code to run the installer script. Refactor
to remove connection from the base OutputView (and other places, it
wasn't used - only used after ConnectionUp).

This also consolidates the stdout and stderr buffers for running jobs.
The distinction was always arbitrary and probably an error, based on the
fact that they were separate in the APIs not based on usability.
2020-07-22 10:52:48 +01:00

316 lines
9.6 KiB
Python

# vimspector - A multi-language debugging system for Vim
# Copyright 2018 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.
import vim
import os
import logging
from vimspector import utils
class StackTraceView( object ):
def __init__( self, session, win ):
self._logger = logging.getLogger( __name__ )
utils.SetUpLogging( self._logger )
self._buf = win.buffer
self._session = session
self._connection = None
self._current_thread = None
self._current_frame = None
self._current_syntax = ""
self._threads = []
self._sources = {}
self._scratch_buffers = []
utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
utils.SetUpUIWindow( win )
vim.command( 'nnoremap <buffer> <CR> :call vimspector#GoToFrame()<CR>' )
self._line_to_frame = {}
self._line_to_thread = {}
# TODO: We really need a proper state model
#
# AWAIT_CONNECTION -- OnServerReady / RequestThreads --> REQUESTING_THREADS
# REQUESTING -- OnGotThreads / RequestScopes --> REQUESTING_SCOPES
#
# When we attach using gdbserver, this whole thing breaks because we request
# the threads over and over and get duff data back on later threads.
self._requesting_threads = False
def GetCurrentThreadId( self ):
return self._current_thread
def GetCurrentFrame( self ):
return self._current_frame
def Clear( self ):
self._current_frame = None
self._current_thread = None
self._current_syntax = ""
self._threads = []
self._sources = {}
with utils.ModifiableScratchBuffer( self._buf ):
utils.ClearBuffer( self._buf )
def ConnectionUp( self, connection ):
self._connection = connection
self._requesting_threads = False
def ConnectionClosed( self ):
self.Clear()
self._connection = None
def Reset( self ):
self.Clear()
utils.CleanUpHiddenBuffer( self._buf )
for b in self._scratch_buffers:
utils.CleanUpHiddenBuffer( b )
self._scratch_buffers = []
self._buf = None
def LoadThreads( self, infer_current_frame ):
pending_request = False
if self._requesting_threads:
pending_request = True
return
def consume_threads( message ):
self._requesting_threads = False
if not message[ 'body' ][ 'threads' ]:
if pending_request:
# We may have hit a thread event, so try again.
self.LoadThreads( infer_current_frame )
return
else:
# This is a protocol error. It is required to return at least one!
utils.UserMessage( 'Server returned no threads. Is it running?',
persist = True )
self._threads.clear()
for thread in message[ 'body' ][ 'threads' ]:
self._threads.append( thread )
if infer_current_frame and thread[ 'id' ] == self._current_thread:
self._LoadStackTrace( thread, True )
elif infer_current_frame and self._current_thread is None:
self._current_thread = thread[ 'id' ]
self._LoadStackTrace( thread, True )
self._DrawThreads()
def failure_handler( reason, msg ):
# Make sure we request them again if the request fails
self._requesting_threads = False
self._requesting_threads = True
self._connection.DoRequest( consume_threads, {
'command': 'threads',
}, failure_handler )
def _DrawThreads( self ):
self._line_to_frame.clear()
self._line_to_thread.clear()
with utils.ModifiableScratchBuffer( self._buf ):
utils.ClearBuffer( self._buf )
for thread in self._threads:
icon = '+' if '_frames' not in thread else '-'
line = utils.AppendToBuffer(
self._buf,
'{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
self._line_to_thread[ line ] = 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 self._JumpToFrame( frame ):
break
self._DrawThreads()
self._connection.DoRequest( consume_stacktrace, {
'command': 'stackTrace',
'arguments': {
'threadId': thread[ 'id' ],
}
} )
def ExpandFrameOrThread( self ):
if vim.current.buffer != self._buf:
return
current_line = vim.current.window.cursor[ 0 ]
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' ]
with utils.RestoreCursorPosition():
self._DrawThreads()
else:
self._LoadStackTrace( thread, False )
def _JumpToFrame( self, frame ):
def do_jump():
if 'line' in frame and frame[ 'line' ] > 0:
self._current_frame = frame
return self._session.SetCurrentFrame( self._current_frame )
return False
source = frame.get( 'source' ) or {}
if source.get( 'sourceReference', 0 ) > 0:
def handle_resolved_source( resolved_source ):
frame[ 'source' ] = resolved_source
do_jump()
self._ResolveSource( source, handle_resolved_source )
# The assumption here is that we _will_ eventually find something to jump
# to
return True
else:
return do_jump()
def OnStopped( self, event ):
if 'threadId' in event:
self._current_thread = event[ 'threadId' ]
elif event.get( 'allThreadsStopped', False ) and self._threads:
self._current_thread = self._threads[ 0 ][ 'id' ]
if self._current_thread is not None:
for thread in self._threads:
if thread[ 'id' ] == self._current_thread:
self._LoadStackTrace( thread, True )
return
self.LoadThreads( True )
def OnThreadEvent( self, event ):
if event[ 'reason' ] == 'started' and self._current_thread is None:
self._current_thread = event[ 'threadId' ]
self.LoadThreads( True )
def Continue( self ):
if self._current_thread is None:
utils.UserMessage( 'No current thread', persist = True )
return
self._session._connection.DoRequest( None, {
'command': 'continue',
'arguments': {
'threadId': self._current_thread,
},
} )
self._session.ClearCurrentFrame()
self.LoadThreads( True )
def Pause( self ):
if self._current_thread is None:
utils.UserMessage( 'No current thread', persist = True )
return
self._session._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': self._current_thread,
},
} )
def _DrawStackTrace( self, thread ):
if '_frames' not in thread:
return
stackFrames = thread[ '_frames' ]
for frame in stackFrames:
if frame.get( 'source' ):
source = frame[ 'source' ]
else:
source = { 'name': '<unknown>' }
if 'name' not in source:
source[ 'name' ] = os.path.basename( source.get( 'path', 'unknwon' ) )
if frame.get( 'presentationHint' ) == 'label':
# Sigh. FOr some reason, it's OK for debug adapters to completely ignore
# the protocol; it seems that the chrome adapter sets 'label' and
# doesn't set 'line'
line = utils.AppendToBuffer(
self._buf,
' {0}: {1}'.format( frame[ 'id' ], frame[ 'name' ] ) )
else:
line = utils.AppendToBuffer(
self._buf,
' {0}: {1}@{2}:{3}'.format( frame[ 'id' ],
frame[ 'name' ],
source[ 'name' ],
frame[ 'line' ] ) )
self._line_to_frame[ line ] = frame
def _ResolveSource( self, source, and_then ):
source_reference = int( source[ 'sourceReference' ] )
try:
and_then( self._sources[ source_reference ] )
except KeyError:
# We must retrieve the source contents from the server
self._logger.debug( "Requesting source: %s", source )
def consume_source( msg ):
self._sources[ source_reference ] = source
buf_name = os.path.join( '_vimspector_tmp',
source.get( 'path', source[ 'name' ] ) )
self._logger.debug( "Received source %s: %s", buf_name, msg )
buf = utils.BufferForFile( buf_name )
self._scratch_buffers.append( buf )
utils.SetUpHiddenBuffer( buf, buf_name )
source[ 'path' ] = buf_name
with utils.ModifiableScratchBuffer( buf ):
utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] )
and_then( self._sources[ source_reference ] )
self._session._connection.DoRequest( consume_source, {
'command': 'source',
'arguments': {
'sourceReference': source[ 'sourceReference' ],
'source': source
}
} )
def SetSyntax( self, syntax ):
self._current_syntax = utils.SetSyntax( self._current_syntax,
syntax,
self._buf )