diff --git a/autoload/vimspector/internal/channel.vim b/autoload/vimspector/internal/channel.vim index eb7cc9d..c58ff64 100644 --- a/autoload/vimspector/internal/channel.vim +++ b/autoload/vimspector/internal/channel.vim @@ -42,6 +42,12 @@ function! s:_Send( msg ) abort call ch_sendraw( s:ch, a:msg ) endfunction +function! vimspector#internal#channel#Timeout( id ) abort + py3 << EOF +_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) ) +EOF +endfunction + function! vimspector#internal#channel#StartDebugSession( config ) abort if exists( 's:ch' ) diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index d252629..ef8058d 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -39,8 +39,13 @@ function! s:_OnClose( channel ) abort endfunction function! s:_Send( msg ) abort + if ! exists( 's:job' ) + echom "Can't send message: Job was not initialised correctly" + return + endif + if job_status( s:job ) != 'run' - echom "Server isnt running" + echom "Can't send message: Job is not running" return endif @@ -55,7 +60,7 @@ endfunction function! vimspector#internal#job#StartDebugSession( config ) abort if exists( 's:job' ) - echo "Job is already running" + echom "Not starging: Job is already running" return v:none endif @@ -72,8 +77,10 @@ function! vimspector#internal#job#StartDebugSession( config ) abort \ } \ ) + echom 'Started job, status is: ' . job_status( s:job ) + if job_status( s:job ) != 'run' - echom 'Fail whale. Job is ' . job_status( s:job ) + echom 'Unable to start job, status is: ' . job_status( s:job ) return v:none endif @@ -81,6 +88,11 @@ function! vimspector#internal#job#StartDebugSession( config ) abort endfunction function! vimspector#internal#job#StopDebugSession() abort + if !exists( 's:job' ) + echom "Not stopping session: Job doesn't exist" + return + endif + if job_status( s:job ) == 'run' call job_stop( s:job, 'term' ) endif @@ -89,9 +101,7 @@ function! vimspector#internal#job#StopDebugSession() abort endfunction function! vimspector#internal#job#Reset() abort - if exists( 's:job' ) - call vimspector#internal#job#StopDebugSession() - endif + call vimspector#internal#job#StopDebugSession() endfunction function! vimspector#internal#job#ForceRead() abort diff --git a/python3/vimspector/debug_adapter_connection.py b/python3/vimspector/debug_adapter_connection.py index 1932b5c..8f475b2 100644 --- a/python3/vimspector/debug_adapter_connection.py +++ b/python3/vimspector/debug_adapter_connection.py @@ -15,13 +15,17 @@ import logging import json - -from collections import namedtuple +import vim from vimspector import utils -PendingRequest = namedtuple( 'PendingRequest', - [ 'msg', 'handler', 'failure_handler' ] ) + +class PendingRequest( object ): + def __init__( self, msg, handler, failure_handler, expiry_id ): + self.msg = msg + self.handler = handler + self.failure_handler = failure_handler + self.expiry_id = expiry_id class DebugAdapterConnection( object ): @@ -36,18 +40,40 @@ class DebugAdapterConnection( object ): self._next_message_id = 0 self._outstanding_requests = {} - def DoRequest( self, handler, msg, failure_handler=None ): + def DoRequest( self, + handler, + msg, + failure_handler=None, + timeout = 15000 ): this_id = self._next_message_id self._next_message_id += 1 msg[ 'seq' ] = this_id msg[ 'type' ] = 'request' + # TODO/FIXME: This is so messy + expiry_id = vim.eval( + 'timer_start( {}, "vimspector#internal#channel#Timeout" )'.format( + timeout ) ) + self._outstanding_requests[ this_id ] = PendingRequest( msg, handler, - failure_handler ) + failure_handler, + expiry_id ) self._SendMessage( msg ) + def OnRequestTimeout( self, timer_id ): + request_id = None + for seq, request in self._outstanding_requests.items(): + if request.expiry_id == timer_id: + request_id = seq + break + + # Avoid modifying _outstanding_requests while looping + if request_id is not None: + request = self._outstanding_requests.pop( request_id ) + self._AbortRequest( request, 'Timeout' ) + def DoResponse( self, request, error, response ): this_id = self._next_message_id self._next_message_id += 1 @@ -70,6 +96,22 @@ class DebugAdapterConnection( object ): self._Write = None self._handler = None + while self._outstanding_requests: + _, request = self._outstanding_requests.popitem() + self._AbortRequest( request, 'Closing down' ) + + def _AbortRequest( self, request, reason ): + self._logger.debug( '{}: Aborting request {}'.format( reason, + request.msg ) ) + _KillTimer( request ) + if request.failure_handler: + request.failure_handler( reason, {} ) + else: + utils.UserMessage( 'Request for {} aborted: {}'.format( + request.msg[ 'command' ], + reason ) ) + + def OnData( self, data ): data = bytes( data, 'utf-8' ) # self._logger.debug( 'Received ({0}/{1}): {2},'.format( type( data ), @@ -110,6 +152,11 @@ class DebugAdapterConnection( object ): if len( parts ) > 1: headers = parts[ 0 ] for header_line in headers.split( bytes( '\r\n', 'utf-8' ) ): + if bytes( '\n', 'utf-8' ) in header_line: + # Work around bugs in cppdbg where mono spams nonesense to stdout. + # This is such a dodgyhack, but it fixes the issues. + header_line = header_line.split( bytes( '\n', 'utf-8' ) )[ -1 ] + if header_line.strip(): key, value = str( header_line, 'utf-8' ).split( ':', 1 ) self._headers[ key ] = value @@ -130,6 +177,7 @@ class DebugAdapterConnection( object ): # Skip to reading headers. Because, what else can we do. self._logger.error( 'Missing Content-Length header in: {0}'.format( json.dumps( self._headers ) ) ) + self._buffer = bytes( '', 'utf-8' ) self._SetState( 'READ_HEADER' ) return @@ -170,6 +218,8 @@ class DebugAdapterConnection( object ): self._logger.exception( 'Duplicate response: {}'.format( message ) ) return + _KillTimer( request ) + if message[ 'success' ]: if request.handler: request.handler( message ) @@ -181,7 +231,7 @@ class DebugAdapterConnection( object ): # TODO: Actually make this work reason = fmt else: - message = 'No reason' + reason = 'No reason' self._logger.error( 'Request failed: {0}'.format( reason ) ) if request.failure_handler: @@ -203,3 +253,9 @@ class DebugAdapterConnection( object ): utils.UserMessage( 'Unhandled request: {0}'.format( message[ 'command' ] ), persist = True ) + + +def _KillTimer( request ): + if request.expiry_id is not None: + vim.eval( 'timer_stop( {} )'.format( request.expiry_id ) ) + request.expiry_id = None diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index f3b9cd0..7f11ab6 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -189,6 +189,10 @@ class DebugSession( object ): if self._connection: self._connection.OnData( data ) + def OnRequestTimeout( self, timer_id ): + if self._connection: + self._connection.OnRequestTimeout( timer_id ) + def OnChannelClosed( self ): self._connection = None @@ -384,7 +388,7 @@ class DebugSession( object ): # scope) state = { 'done': False } - def handler( self ): + def handler( *args ): state[ 'done' ] = True self._connection.DoRequest( handler, { @@ -392,11 +396,10 @@ class DebugSession( object ): 'arguments': { 'terminateDebugee': True }, - } ) + }, failure_handler = handler, timeout = 5000 ) - tries = 0 - while not state[ 'done' ] and tries < 10: - tries = tries + 1 + # This request times out after 5 seconds + while not state[ 'done' ]: vim.eval( 'vimspector#internal#{}#ForceRead()'.format( self._connection_type ) ) @@ -404,7 +407,7 @@ class DebugSession( object ): self._connection_type ) ) def _StopDebugAdapter( self, callback = None ): - def handler( message ): + def handler( *args ): vim.eval( 'vimspector#internal#{}#StopDebugSession()'.format( self._connection_type ) ) @@ -423,7 +426,7 @@ class DebugSession( object ): 'arguments': { 'terminateDebugee': True }, - } ) + }, failure_handler = handler, timeout = 5000 ) def _SelectProcess( self, adapter_config, launch_config ): atttach_config = adapter_config[ 'attach' ] diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index d2fb6e9..f57e7fb 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -202,25 +202,35 @@ def AskForInput( prompt ): def AppendToBuffer( buf, line_or_lines, modified=False ): - # After clearing the buffer (using buf[:] = None) there is always a single - # empty line in the buffer object and no "is empty" method. - if len( buf ) > 1 or buf[ 0 ]: - line = len( buf ) + 1 - buf.append( line_or_lines ) - elif isinstance( line_or_lines, str ): - line = 1 - buf[-1] = line_or_lines - else: - line = 1 - buf[:] = line_or_lines - - if not modified: - buf.options[ 'modified' ] = False + try: + # After clearing the buffer (using buf[:] = None) there is always a single + # empty line in the buffer object and no "is empty" method. + if len( buf ) > 1 or buf[ 0 ]: + line = len( buf ) + 1 + buf.append( line_or_lines ) + elif isinstance( line_or_lines, str ): + line = 1 + buf[-1] = line_or_lines + else: + line = 1 + buf[:] = line_or_lines + except vim.error as e: + # There seem to be a lot of Vim bugs that lead to E351, whose help says that + # this is an internal error. Ignore the error, but write a trace to the log. + if 'E315' in str( e ): + logging.getLogger( __name__ ).exception( + 'Internal error while updating buffer' ) + else: + raise e + finally: + if not modified: + buf.options[ 'modified' ] = False # Return the first Vim line number (1-based) that we just set. return line + def ClearBuffer( buf ): buf[:] = None