diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 35ecd03..f19bd78 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -296,7 +296,7 @@ function! vimspector#GetConfigurations() abort return endif let configurations = py3eval( - \ 'list( _vimspector_session.GetConfigurations( {} )[ 1 ].keys() )' + \ 'list( _vimspector_session.GetConfigurations().keys() )' \ . ' if _vimspector_session else []' ) return configurations endfunction diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index ed304e5..e664121 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -76,10 +76,10 @@ class DebugSession( object ): self._server_capabilities = {} self.ClearTemporaryBreakpoints() - def GetConfigurations( self, adapters ): + def GetConfigurations( self ): current_file = utils.GetBufferFilepath( vim.current.buffer ) filetypes = utils.GetBufferFiletypes( vim.current.buffer ) - return launch.GetConfigurations( adapters, current_file, filetypes ) + return launch.GetConfigurations( {}, current_file, filetypes )[ 1 ] def Start( self, launch_variables = None ): @@ -96,33 +96,40 @@ class DebugSession( object ): current_file = utils.GetBufferFilepath( vim.current.buffer ) filetypes = utils.GetBufferFiletypes( vim.current.buffer ) - adapters = launch.GetAdapters( current_file, filetypes ) + adapters = launch.GetAdapters( current_file ) launch_config_file, configurations = launch.GetConfigurations( adapters, current_file, filetypes ) - if not configurations: - utils.UserMessage( 'Unable to find any debug configurations. ' - 'You need to tell vimspector how to launch your ' - 'application.' ) - return - if launch_config_file: self._workspace_root = os.path.dirname( launch_config_file ) else: self._workspace_root = os.path.dirname( current_file ) - configuration_name, configuration = launch.SelectConfiguration( - launch_variables, - configurations ) + + if not configurations: + configuration_name, configuration = launch.SuggestConfiguration( + filetypes ) + else: + configuration_name, configuration = launch.SelectConfiguration( + launch_variables, + configurations ) + + if not configuration: + utils.UserMessage( 'Unable to find any debug configurations. ' + 'You need to tell vimspector how to launch your ' + 'application.' ) + return + adapter = launch.SelectAdapter( self._api_prefix, + self, configuration_name, configuration, adapters, - launch_variables, - self ) + launch_variables ) if not adapter: return + try: launch.ResolveConfiguration( adapter, configuration, diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index 0271069..a98fb8b 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -49,6 +49,40 @@ GADGETS = { } }, }, + 'templates': [ + { + 'description': 'gdb/lldb local debugging with vscode-cpptools', + 'filetypes': ( 'c', 'cpp', 'objc', 'rust' ), + 'configurations': [ + { + 'description': 'Launch local process', + 'launch_configuration': { + 'adapter': 'vscode-cpptools', + 'configuration': { + 'request': 'launch', + 'program': '${Binary:${fileBasenameNoExtension\\}}', + 'args': [ '*${CommandLineArguments}' ], + 'cwd': '${WorkingDir:${fileDirname\\}}', + 'externalConsole#json': '${UseExternalConsole:true\nfalse}', + 'stopAtEntry#json': '${StopAtEntry:true\nfalse}', + 'MIMode': '${Debugger:gdb\nlldb}', + } + } + }, + { + 'description': 'Attach to local process by PID', + 'launch_configuration': { + 'adapter': 'vscode-cpptools', + 'configuration': { + 'request': 'attach', + 'program': '${Binary:${fileBasenameNoExtension\\}}', + 'MIMode': '${Debugger:gdb\nlldb}', + } + } + } + ] + } + ] }, 'linux': { 'file_name': 'cpptools-linux.vsix', @@ -452,6 +486,42 @@ GADGETS = { }, 'all': { 'version': 'v1.5.3', + 'templates': [ + { + 'description': 'LLDB local debugging with CodeLLDB', + 'filetypes': ( 'c', 'cpp', 'objc', 'rust' ), + 'configurations': [ + { + 'description': 'Launch local process', + 'launch_configuration': { + 'adapter': 'CodeLLDB', + 'configuration': { + 'request': 'launch', + 'program': '${Binary:${fileBasenameNoExtension\\}}', + 'args': [ '*${CommandLineArguments}' ], + 'cwd': '${WorkingDir:${fileDirname\\}}', + 'terminal': '${Console:none\nintegrated\nexternal}', + 'stopOnEntry#json': '${StopOnEntry:true\nfalse}', + 'expressions': '${ExpressionType:native\nsimple\npython}' + } + } + }, + { + 'description': 'Attach to local process by PID', + 'launch_configuration': { + 'adapter': 'CodeLLDB', + 'configuration': { + 'request': 'attach', + 'program': '${Binary:${fileBasenameNoExtension\\}}', + 'pid#json': "${pid}", + 'stopOnEntry#json': '${StopOnEntry:true\nfalse}', + 'expressions': '${ExpressionType:native\nsimple\npython}' + } + } + } + ] + } + ] }, 'macos': { 'file_name': 'codelldb-x86_64-darwin.vsix', @@ -486,6 +556,7 @@ GADGETS = { 'name': 'CodeLLDB', 'type': 'CodeLLDB', "command": [ + # FIXME: This probably doesn't work on windows. "${gadgetDir}/CodeLLDB/adapter/codelldb", "--port", "${unusedLocalPort}" ], diff --git a/python3/vimspector/launch.py b/python3/vimspector/launch.py index c5d0616..2903a7f 100644 --- a/python3/vimspector/launch.py +++ b/python3/vimspector/launch.py @@ -19,7 +19,7 @@ import json import glob import shlex -from vimspector import install, installer, utils +from vimspector import install, installer, utils, gadgets from vimspector.vendor.json_minify import minify _logger = logging.getLogger( __name__ ) @@ -33,7 +33,7 @@ USER_CHOICES = {} -def GetAdapters( current_file, filetypes ): +def GetAdapters( current_file ): adapters = {} for gadget_config_file in PathsToAllGadgetConfigs( VIMSPECTOR_HOME, current_file ): @@ -120,13 +120,57 @@ def SelectConfiguration( launch_variables, configurations ): return configuration_name, configuration +def SuggestConfiguration( filetypes ): + nothing = None, None + templates = [] + filetypes = set( filetypes ) + + for gadget_name, gadget in gadgets.GADGETS.items(): + spec = {} + spec.update( gadget.get( 'all', {} ) ) + spec.update( gadget.get( install.GetOS(), {} ) ) + + for template in spec.get( 'templates', [] ): + if filetypes.intersection( template.get( 'filetypes', set() ) ): + templates.append( template ) + + if not templates: + return nothing + + template_idx = utils.SelectFromList( + 'No debug configurations were found for this project, ' + 'Would you like to use one of the following templates?', + [ t[ 'description' ] for t in templates ], + ret = 'index' ) + + if template_idx is None: + return nothing + + template = templates[ template_idx ] + + config_index = utils.SelectFromList( + 'Which configuration?', + [ c[ 'description' ] for c in template[ 'configurations' ] ], + ret = 'index' ) + + if config_index is None: + return nothing + + configuration = template[ 'configurations' ][ config_index ] + configuration_name = utils.AskForInput( 'Give the config a name: ', + configuration[ 'description' ] ) + + + return configuration_name, configuration[ 'launch_configuration' ] + + def SelectAdapter( api_prefix, + debug_session, configuration_name, configuration, adapters, - launch_variables, - debug_session ): - adapter = configuration.get( 'adapter' ) + launch_variables ): + adapter = configuration.get( 'adapter' ) if isinstance( adapter, str ): adapter_dict = adapters.get( adapter ) @@ -136,7 +180,7 @@ def SelectAdapter( api_prefix, if suggested_gadgets: response = utils.AskForInput( f"The specified adapter '{adapter}' is not " - "installed. Would you like to install the following gadgets? ", + "installed. Would you like to install the following gadgets? ", ' '.join( suggested_gadgets ) ) if response: new_launch_variables = dict( launch_variables ) @@ -147,7 +191,7 @@ def SelectAdapter( api_prefix, False, # Don't leave open *shlex.split( response ), then = debug_session.Start( new_launch_variables ) ) - return + return None elif response is None: return None diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 5ab9872..581411a 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -333,25 +333,41 @@ def UserMessage( msg, persist=False, error=False ): @contextlib.contextmanager -def InputSave(): +def AskingForUserInput(): vim.eval( 'inputsave()' ) try: yield finally: vim.eval( 'inputrestore()' ) + # Clear the command line so that subsequent + vim.command( 'redraw' ) -def SelectFromList( prompt, options ): - with InputSave(): +def SelectFromList( prompt, + options, + ret = 'text', + default_index = None ): + with AskingForUserInput(): display_options = [ prompt ] - display_options.extend( [ '{0}: {1}'.format( i + 1, v ) - for i, v in enumerate( options ) ] ) + display_options.extend( [ + '{0}{1}: {2}'.format( '*' if i == default_index else ' ', + 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 ] + if default_index is not None: + selection = default_index + else: + return None + + if ret == 'text': + return options[ selection ] + else: + return selection except ( KeyboardInterrupt, vim.error ): return None @@ -362,7 +378,7 @@ def AskForInput( prompt, default_value = None ): else: default_option = ", '{}'".format( Escape( default_value ) ) - with InputSave(): + with AskingForUserInput(): try: return vim.eval( "input( '{}' {} )".format( Escape( prompt ), default_option ) ) @@ -531,11 +547,25 @@ def ExpandReferencesInString( orig_s, if default_value is None and e.default_value is not None: try: default_value = _Substitute( e.default_value, mapping ) - except MissingSubstitution: - default_value = e.default_value + except MissingSubstitution as f: + if f.name in calculus: + default_value = calculus[ f.name ]() + else: + default_value = e.default_value - mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ), - default_value ) + value_list = None + if default_value: + default_value_list = default_value.split( '\n' ) + if len( default_value_list ) > 1: + value_list = default_value_list + + if value_list: + mapping[ key ] = SelectFromList( f'Select value for { key }: ', + default_value_list, + default_index = 0 ) + else: + mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ), + default_value ) if mapping[ key ] is None: raise KeyboardInterrupt