diff --git a/.vimspector.json b/.vimspector.json index 278ea9f..ef8a3e0 100644 --- a/.vimspector.json +++ b/.vimspector.json @@ -52,5 +52,22 @@ "debugOptions": [], "program": "/Users/ben/.vim/bundle/vimspector/support/test/python/simple_python/main.py" } + }, + "simple_c_program - MS Attach": { + "adapter": { + "name": "cppdbg", + "command": [ "/Users/ben/.vscode/extensions/ms-vscode.cpptools-0.17.3/debugAdapters/OpenDebugAD7" ], + "attach": { + "pidProperty": "processId", + "pidSelect": "ask" + } + }, + "configuration": { + "name": "(lldb) Attach", + "type": "cppdbg", + "request": "attach", + "program": "/Users/ben/.vim/bundle/vimspector/support/test/cpp/simple_c_program/test", + "MIMode": "lldb" + } } } diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index 03f5420..123d092 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -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' ] ) ) diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index 75793d2..e6615d1 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -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' ] ) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 39edeb4..b78b785 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -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', '' ), + 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 ) diff --git a/python3/vimspector/stack_trace.py b/python3/vimspector/stack_trace.py index f67b929..c3abbfc 100644 --- a/python3/vimspector/stack_trace.py +++ b/python3/vimspector/stack_trace.py @@ -67,8 +67,14 @@ class StackTraceView( object ): stackFrames = message[ 'body' ][ 'stackFrames' ] + current_frame = None for frame in stackFrames: - source = frame[ 'source' ] or { 'name': '' } + if frame[ 'source' ]: + current_frame = current_frame or frame + source = frame[ 'source' ] + else: + source = { 'name': '' } + 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 ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index b409d80..88ba9f5 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -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 ) ) )