Compare commits
6 commits
master
...
setup-guid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e55563c8f | ||
|
|
2fa6142c8f | ||
|
|
6126ac1724 | ||
|
|
7a02f6139f | ||
|
|
acd0f31573 | ||
|
|
b6048fc5c6 |
5 changed files with 496 additions and 210 deletions
|
|
@ -296,7 +296,7 @@ function! vimspector#GetConfigurations() abort
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
let configurations = py3eval(
|
let configurations = py3eval(
|
||||||
\ 'list( _vimspector_session.GetConfigurations( {} )[ 1 ].keys() )'
|
\ 'list( _vimspector_session.GetConfigurations().keys() )'
|
||||||
\ . ' if _vimspector_session else []' )
|
\ . ' if _vimspector_session else []' )
|
||||||
return configurations
|
return configurations
|
||||||
endfunction
|
endfunction
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import glob
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
@ -32,15 +31,11 @@ from vimspector import ( breakpoints,
|
||||||
variables,
|
variables,
|
||||||
settings,
|
settings,
|
||||||
terminal,
|
terminal,
|
||||||
installer )
|
launch )
|
||||||
from vimspector.vendor.json_minify import minify
|
|
||||||
|
|
||||||
# We cache this once, and don't allow it to change (FIXME?)
|
# We cache this once, and don't allow it to change (FIXME?)
|
||||||
VIMSPECTOR_HOME = utils.GetVimspectorBase()
|
VIMSPECTOR_HOME = utils.GetVimspectorBase()
|
||||||
|
|
||||||
# cache of what the user entered for any option we ask them
|
|
||||||
USER_CHOICES = {}
|
|
||||||
|
|
||||||
|
|
||||||
class DebugSession( object ):
|
class DebugSession( object ):
|
||||||
def __init__( self, api_prefix ):
|
def __init__( self, api_prefix ):
|
||||||
|
|
@ -80,24 +75,11 @@ class DebugSession( object ):
|
||||||
self._server_capabilities = {}
|
self._server_capabilities = {}
|
||||||
self.ClearTemporaryBreakpoints()
|
self.ClearTemporaryBreakpoints()
|
||||||
|
|
||||||
def GetConfigurations( self, adapters ):
|
def GetConfigurations( self ):
|
||||||
current_file = utils.GetBufferFilepath( vim.current.buffer )
|
current_file = utils.GetBufferFilepath( vim.current.buffer )
|
||||||
filetypes = utils.GetBufferFiletypes( vim.current.buffer )
|
filetypes = utils.GetBufferFiletypes( vim.current.buffer )
|
||||||
configurations = {}
|
return launch.GetConfigurations( {}, current_file, filetypes )[ 1 ]
|
||||||
|
|
||||||
for launch_config_file in PathsToAllConfigFiles( VIMSPECTOR_HOME,
|
|
||||||
current_file,
|
|
||||||
filetypes ):
|
|
||||||
self._logger.debug( f'Reading configurations from: {launch_config_file}' )
|
|
||||||
if not launch_config_file or not os.path.exists( launch_config_file ):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open( launch_config_file, 'r' ) as f:
|
|
||||||
database = json.loads( minify( f.read() ) )
|
|
||||||
configurations.update( database.get( 'configurations' ) or {} )
|
|
||||||
adapters.update( database.get( 'adapters' ) or {} )
|
|
||||||
|
|
||||||
return launch_config_file, configurations
|
|
||||||
|
|
||||||
def Start( self, launch_variables = None ):
|
def Start( self, launch_variables = None ):
|
||||||
# We mutate launch_variables, so don't mutate the default argument.
|
# We mutate launch_variables, so don't mutate the default argument.
|
||||||
|
|
@ -112,161 +94,48 @@ class DebugSession( object ):
|
||||||
self._launch_config = None
|
self._launch_config = None
|
||||||
|
|
||||||
current_file = utils.GetBufferFilepath( vim.current.buffer )
|
current_file = utils.GetBufferFilepath( vim.current.buffer )
|
||||||
adapters = {}
|
filetypes = utils.GetBufferFiletypes( vim.current.buffer )
|
||||||
launch_config_file, configurations = self.GetConfigurations( adapters )
|
adapters = launch.GetAdapters( current_file )
|
||||||
|
launch_config_file, configurations = launch.GetConfigurations( adapters,
|
||||||
if not configurations:
|
current_file,
|
||||||
utils.UserMessage( 'Unable to find any debug configurations. '
|
filetypes )
|
||||||
'You need to tell vimspector how to launch your '
|
|
||||||
'application.' )
|
|
||||||
return
|
|
||||||
|
|
||||||
glob.glob( install.GetGadgetDir( VIMSPECTOR_HOME ) )
|
|
||||||
for gadget_config_file in PathsToAllGadgetConfigs( VIMSPECTOR_HOME,
|
|
||||||
current_file ):
|
|
||||||
self._logger.debug( f'Reading gadget config: {gadget_config_file}' )
|
|
||||||
if not gadget_config_file or not os.path.exists( gadget_config_file ):
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open( gadget_config_file, 'r' ) as f:
|
|
||||||
a = json.loads( minify( f.read() ) ).get( 'adapters' ) or {}
|
|
||||||
adapters.update( a )
|
|
||||||
|
|
||||||
if 'configuration' in launch_variables:
|
|
||||||
configuration_name = launch_variables.pop( 'configuration' )
|
|
||||||
elif ( len( configurations ) == 1 and
|
|
||||||
next( iter( configurations.values() ) ).get( "autoselect", True ) ):
|
|
||||||
configuration_name = next( iter( configurations.keys() ) )
|
|
||||||
else:
|
|
||||||
# Find a single configuration with 'default' True and autoselect not False
|
|
||||||
defaults = { n: c for n, c in configurations.items()
|
|
||||||
if c.get( 'default', False ) is True
|
|
||||||
and c.get( 'autoselect', True ) is not False }
|
|
||||||
|
|
||||||
if len( defaults ) == 1:
|
|
||||||
configuration_name = next( iter( defaults.keys() ) )
|
|
||||||
else:
|
|
||||||
configuration_name = utils.SelectFromList(
|
|
||||||
'Which launch configuration?',
|
|
||||||
sorted( configurations.keys() ) )
|
|
||||||
|
|
||||||
if not configuration_name or configuration_name not in configurations:
|
|
||||||
return
|
|
||||||
|
|
||||||
if launch_config_file:
|
if launch_config_file:
|
||||||
self._workspace_root = os.path.dirname( launch_config_file )
|
self._workspace_root = os.path.dirname( launch_config_file )
|
||||||
else:
|
else:
|
||||||
self._workspace_root = os.path.dirname( current_file )
|
self._workspace_root = os.path.dirname( current_file )
|
||||||
|
|
||||||
configuration = configurations[ configuration_name ]
|
|
||||||
adapter = configuration.get( 'adapter' )
|
|
||||||
if isinstance( adapter, str ):
|
|
||||||
adapter_dict = adapters.get( adapter )
|
|
||||||
|
|
||||||
if adapter_dict is None:
|
if not configurations:
|
||||||
suggested_gadgets = installer.FindGadgetForAdapter( adapter )
|
configuration_name, configuration = launch.SuggestConfiguration(
|
||||||
if suggested_gadgets:
|
current_file,
|
||||||
response = utils.AskForInput(
|
filetypes )
|
||||||
f"The specified adapter '{adapter}' is not "
|
else:
|
||||||
"installed. Would you like to install the following gadgets? ",
|
configuration_name, configuration = launch.SelectConfiguration(
|
||||||
' '.join( suggested_gadgets ) )
|
launch_variables,
|
||||||
if response:
|
configurations )
|
||||||
new_launch_variables = dict( launch_variables )
|
|
||||||
new_launch_variables[ 'configuration' ] = configuration_name
|
|
||||||
|
|
||||||
installer.RunInstaller(
|
if not configuration:
|
||||||
self._api_prefix,
|
utils.UserMessage( 'Unable to find any debug configurations. '
|
||||||
False, # Don't leave open
|
'You need to tell vimspector how to launch your '
|
||||||
*shlex.split( response ),
|
'application.' )
|
||||||
then = lambda: self.Start( new_launch_variables ) )
|
return
|
||||||
return
|
|
||||||
elif response is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
utils.UserMessage( f"The specified adapter '{adapter}' is not "
|
adapter = launch.SelectAdapter( self._api_prefix,
|
||||||
"available. Did you forget to run "
|
self,
|
||||||
"'install_gadget.py'?",
|
configuration_name,
|
||||||
persist = True,
|
configuration,
|
||||||
error = True )
|
adapters,
|
||||||
return
|
launch_variables )
|
||||||
|
if not adapter:
|
||||||
adapter = adapter_dict
|
return
|
||||||
|
|
||||||
# Additional vars as defined by VSCode:
|
|
||||||
#
|
|
||||||
# ${workspaceFolder} - the path of the folder opened in VS Code
|
|
||||||
# ${workspaceFolderBasename} - the name of the folder opened in VS Code
|
|
||||||
# without any slashes (/)
|
|
||||||
# ${file} - the current opened file
|
|
||||||
# ${relativeFile} - the current opened file relative to workspaceFolder
|
|
||||||
# ${fileBasename} - the current opened file's basename
|
|
||||||
# ${fileBasenameNoExtension} - the current opened file's basename with no
|
|
||||||
# file extension
|
|
||||||
# ${fileDirname} - the current opened file's dirname
|
|
||||||
# ${fileExtname} - the current opened file's extension
|
|
||||||
# ${cwd} - the task runner's current working directory on startup
|
|
||||||
# ${lineNumber} - the current selected line number in the active file
|
|
||||||
# ${selectedText} - the current selected text in the active file
|
|
||||||
# ${execPath} - the path to the running VS Code executable
|
|
||||||
|
|
||||||
def relpath( p, relative_to ):
|
|
||||||
if not p:
|
|
||||||
return ''
|
|
||||||
return os.path.relpath( p, relative_to )
|
|
||||||
|
|
||||||
def splitext( p ):
|
|
||||||
if not p:
|
|
||||||
return [ '', '' ]
|
|
||||||
return os.path.splitext( p )
|
|
||||||
|
|
||||||
variables = {
|
|
||||||
'dollar': '$', # HACK. Hote '$$' also works.
|
|
||||||
'workspaceRoot': self._workspace_root,
|
|
||||||
'workspaceFolder': self._workspace_root,
|
|
||||||
'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME ),
|
|
||||||
'file': current_file,
|
|
||||||
}
|
|
||||||
|
|
||||||
calculus = {
|
|
||||||
'relativeFile': lambda: relpath( current_file,
|
|
||||||
self._workspace_root ),
|
|
||||||
'fileBasename': lambda: os.path.basename( current_file ),
|
|
||||||
'fileBasenameNoExtension':
|
|
||||||
lambda: splitext( os.path.basename( current_file ) )[ 0 ],
|
|
||||||
'fileDirname': lambda: os.path.dirname( current_file ),
|
|
||||||
'fileExtname': lambda: splitext( os.path.basename( current_file ) )[ 1 ],
|
|
||||||
# NOTE: this is the window-local cwd for the current window, *not* Vim's
|
|
||||||
# working directory.
|
|
||||||
'cwd': os.getcwd,
|
|
||||||
'unusedLocalPort': utils.GetUnusedLocalPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Pretend that vars passed to the launch command were typed in by the user
|
|
||||||
# (they may have been in theory)
|
|
||||||
USER_CHOICES.update( launch_variables )
|
|
||||||
variables.update( launch_variables )
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
variables.update(
|
launch.ResolveConfiguration( adapter,
|
||||||
utils.ParseVariables( adapter.get( 'variables', {} ),
|
configuration,
|
||||||
variables,
|
launch_variables,
|
||||||
calculus,
|
self._workspace_root,
|
||||||
USER_CHOICES ) )
|
current_file )
|
||||||
variables.update(
|
|
||||||
utils.ParseVariables( configuration.get( 'variables', {} ),
|
|
||||||
variables,
|
|
||||||
calculus,
|
|
||||||
USER_CHOICES ) )
|
|
||||||
|
|
||||||
|
|
||||||
utils.ExpandReferencesInDict( configuration,
|
|
||||||
variables,
|
|
||||||
calculus,
|
|
||||||
USER_CHOICES )
|
|
||||||
utils.ExpandReferencesInDict( adapter,
|
|
||||||
variables,
|
|
||||||
calculus,
|
|
||||||
USER_CHOICES )
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self._Reset()
|
self._Reset()
|
||||||
return
|
return
|
||||||
|
|
@ -398,6 +267,9 @@ class DebugSession( object ):
|
||||||
|
|
||||||
def _Reset( self ):
|
def _Reset( self ):
|
||||||
self._logger.info( "Debugging complete." )
|
self._logger.info( "Debugging complete." )
|
||||||
|
|
||||||
|
launch.SaveConfiguration( self._configuration )
|
||||||
|
|
||||||
if self._uiTab:
|
if self._uiTab:
|
||||||
self._logger.debug( "Clearing down UI" )
|
self._logger.debug( "Clearing down UI" )
|
||||||
|
|
||||||
|
|
@ -1277,29 +1149,3 @@ class DebugSession( object ):
|
||||||
|
|
||||||
def AddFunctionBreakpoint( self, function, options ):
|
def AddFunctionBreakpoint( self, function, options ):
|
||||||
return self._breakpoints.AddFunctionBreakpoint( function, options )
|
return self._breakpoints.AddFunctionBreakpoint( function, options )
|
||||||
|
|
||||||
|
|
||||||
def PathsToAllGadgetConfigs( vimspector_base, current_file ):
|
|
||||||
yield install.GetGadgetConfigFile( vimspector_base )
|
|
||||||
for p in sorted( glob.glob(
|
|
||||||
os.path.join( install.GetGadgetConfigDir( vimspector_base ),
|
|
||||||
'*.json' ) ) ):
|
|
||||||
yield p
|
|
||||||
|
|
||||||
yield utils.PathToConfigFile( '.gadgets.json',
|
|
||||||
os.path.dirname( current_file ) )
|
|
||||||
|
|
||||||
|
|
||||||
def PathsToAllConfigFiles( vimspector_base, current_file, filetypes ):
|
|
||||||
for ft in filetypes + [ '_all' ]:
|
|
||||||
for p in sorted( glob.glob(
|
|
||||||
os.path.join( install.GetConfigDirForFiletype( vimspector_base, ft ),
|
|
||||||
'*.json' ) ) ):
|
|
||||||
yield p
|
|
||||||
|
|
||||||
for ft in filetypes:
|
|
||||||
yield utils.PathToConfigFile( f'.vimspector.{ft}.json',
|
|
||||||
os.path.dirname( current_file ) )
|
|
||||||
|
|
||||||
yield utils.PathToConfigFile( '.vimspector.json',
|
|
||||||
os.path.dirname( current_file ) )
|
|
||||||
|
|
|
||||||
|
|
@ -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': {
|
'linux': {
|
||||||
'file_name': 'cpptools-linux.vsix',
|
'file_name': 'cpptools-linux.vsix',
|
||||||
|
|
@ -452,6 +486,42 @@ GADGETS = {
|
||||||
},
|
},
|
||||||
'all': {
|
'all': {
|
||||||
'version': 'v1.5.3',
|
'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:integrated\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': {
|
'macos': {
|
||||||
'file_name': 'codelldb-x86_64-darwin.vsix',
|
'file_name': 'codelldb-x86_64-darwin.vsix',
|
||||||
|
|
@ -486,6 +556,7 @@ GADGETS = {
|
||||||
'name': 'CodeLLDB',
|
'name': 'CodeLLDB',
|
||||||
'type': 'CodeLLDB',
|
'type': 'CodeLLDB',
|
||||||
"command": [
|
"command": [
|
||||||
|
# FIXME: This probably doesn't work on windows.
|
||||||
"${gadgetDir}/CodeLLDB/adapter/codelldb",
|
"${gadgetDir}/CodeLLDB/adapter/codelldb",
|
||||||
"--port", "${unusedLocalPort}"
|
"--port", "${unusedLocalPort}"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
335
python3/vimspector/launch.py
Normal file
335
python3/vimspector/launch.py
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
# vimspector - A multi-language debugging system for Vim
|
||||||
|
# Copyright 2020 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 os
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import glob
|
||||||
|
import shlex
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from vimspector import install, installer, utils, gadgets
|
||||||
|
from vimspector.vendor.json_minify import minify
|
||||||
|
|
||||||
|
_logger = logging.getLogger( __name__ )
|
||||||
|
utils.SetUpLogging( _logger )
|
||||||
|
|
||||||
|
# We cache this once, and don't allow it to change (FIXME?)
|
||||||
|
VIMSPECTOR_HOME = utils.GetVimspectorBase()
|
||||||
|
|
||||||
|
# cache of what the user entered for any option we ask them
|
||||||
|
USER_CHOICES = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def GetAdapters( current_file ):
|
||||||
|
adapters = {}
|
||||||
|
for gadget_config_file in PathsToAllGadgetConfigs( VIMSPECTOR_HOME,
|
||||||
|
current_file ):
|
||||||
|
_logger.debug( f'Reading gadget config: {gadget_config_file}' )
|
||||||
|
if not gadget_config_file or not os.path.exists( gadget_config_file ):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open( gadget_config_file, 'r' ) as f:
|
||||||
|
a = json.loads( minify( f.read() ) ).get( 'adapters' ) or {}
|
||||||
|
adapters.update( a )
|
||||||
|
|
||||||
|
return adapters
|
||||||
|
|
||||||
|
|
||||||
|
def PathsToAllGadgetConfigs( vimspector_base, current_file ):
|
||||||
|
yield install.GetGadgetConfigFile( vimspector_base )
|
||||||
|
for p in sorted( glob.glob(
|
||||||
|
os.path.join( install.GetGadgetConfigDir( vimspector_base ),
|
||||||
|
'*.json' ) ) ):
|
||||||
|
yield p
|
||||||
|
|
||||||
|
yield utils.PathToConfigFile( '.gadgets.json',
|
||||||
|
os.path.dirname( current_file ) )
|
||||||
|
|
||||||
|
|
||||||
|
def GetConfigurations( adapters, current_file, filetypes ):
|
||||||
|
configurations = {}
|
||||||
|
for launch_config_file in PathsToAllConfigFiles( VIMSPECTOR_HOME,
|
||||||
|
current_file,
|
||||||
|
filetypes ):
|
||||||
|
_logger.debug( f'Reading configurations from: {launch_config_file}' )
|
||||||
|
if not launch_config_file or not os.path.exists( launch_config_file ):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open( launch_config_file, 'r' ) as f:
|
||||||
|
database = json.loads( minify( f.read() ) )
|
||||||
|
configurations.update( database.get( 'configurations' ) or {} )
|
||||||
|
adapters.update( database.get( 'adapters' ) or {} )
|
||||||
|
|
||||||
|
# We return the last config file inspected which is the most specific one
|
||||||
|
# (i.e. the project-local one)
|
||||||
|
return launch_config_file, configurations
|
||||||
|
|
||||||
|
|
||||||
|
def PathsToAllConfigFiles( vimspector_base, current_file, filetypes ):
|
||||||
|
for ft in filetypes + [ '_all' ]:
|
||||||
|
for p in sorted( glob.glob(
|
||||||
|
os.path.join( install.GetConfigDirForFiletype( vimspector_base, ft ),
|
||||||
|
'*.json' ) ) ):
|
||||||
|
yield p
|
||||||
|
|
||||||
|
for ft in filetypes:
|
||||||
|
yield utils.PathToConfigFile( f'.vimspector.{ft}.json',
|
||||||
|
os.path.dirname( current_file ) )
|
||||||
|
|
||||||
|
yield utils.PathToConfigFile( '.vimspector.json',
|
||||||
|
os.path.dirname( current_file ) )
|
||||||
|
|
||||||
|
|
||||||
|
def SelectConfiguration( launch_variables, configurations ):
|
||||||
|
if 'configuration' in launch_variables:
|
||||||
|
configuration_name = launch_variables.pop( 'configuration' )
|
||||||
|
elif ( len( configurations ) == 1 and
|
||||||
|
next( iter( configurations.values() ) ).get( "autoselect", True ) ):
|
||||||
|
configuration_name = next( iter( configurations.keys() ) )
|
||||||
|
else:
|
||||||
|
# Find a single configuration with 'default' True and autoselect not False
|
||||||
|
defaults = { n: c for n, c in configurations.items()
|
||||||
|
if c.get( 'default', False ) is True
|
||||||
|
and c.get( 'autoselect', True ) is not False }
|
||||||
|
|
||||||
|
if len( defaults ) == 1:
|
||||||
|
configuration_name = next( iter( defaults.keys() ) )
|
||||||
|
else:
|
||||||
|
configuration_name = utils.SelectFromList(
|
||||||
|
'Which launch configuration?',
|
||||||
|
sorted( configurations.keys() ) )
|
||||||
|
|
||||||
|
if not configuration_name or configuration_name not in configurations:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
configuration = configurations[ configuration_name ]
|
||||||
|
|
||||||
|
return configuration_name, configuration
|
||||||
|
|
||||||
|
|
||||||
|
def SuggestConfiguration( current_file, 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() ) ):
|
||||||
|
# We _must_ copy the template as we end up taking bits out of it to
|
||||||
|
# assign to the resulting config. Python... references... mutabiliy..
|
||||||
|
# fun.
|
||||||
|
templates.append( copy.deepcopy( 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' ] )
|
||||||
|
|
||||||
|
configuration[ 'launch_configuration' ][ 'generated' ] = {
|
||||||
|
'name': configuration_name,
|
||||||
|
'path': os.path.join( os.path.dirname( current_file ), '.vimspector.json' ),
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration_name, configuration[ 'launch_configuration' ]
|
||||||
|
|
||||||
|
|
||||||
|
def SaveConfiguration( configuration ):
|
||||||
|
gen = configuration.pop( 'generated', None )
|
||||||
|
if not gen:
|
||||||
|
return
|
||||||
|
|
||||||
|
if utils.Confirm(
|
||||||
|
f'Would you like to save the configuration named "{ gen[ "name" ] }"',
|
||||||
|
'&Yes\n&No' ) != 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
config_path = utils.AskForInput( 'Enter the path to save to: ',
|
||||||
|
gen[ 'path' ] )
|
||||||
|
|
||||||
|
if not config_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
os.makedirs( os.path.dirname( config_path ), exist_ok = True )
|
||||||
|
current_contents = {}
|
||||||
|
|
||||||
|
if os.path.exists( config_path ):
|
||||||
|
if utils.Confirm( 'File exists, merge with this configuration?\n'
|
||||||
|
'(NOTE: comments and formatting in the existing file '
|
||||||
|
'will be LOST!!)',
|
||||||
|
'&Yes\n&No' ) != 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
with open( config_path, 'r' ) as f:
|
||||||
|
current_contents = json.loads( minify( f.read() ) )
|
||||||
|
|
||||||
|
# TODO: how much of configuration is mangled at this point ?
|
||||||
|
# TODO: how about the defaulted arguments? All the refs are replaced at this
|
||||||
|
# point?
|
||||||
|
current_contents.setdefault( 'configurations', {} )
|
||||||
|
current_contents[ 'configurations' ][ gen[ 'name' ] ] = configuration
|
||||||
|
|
||||||
|
with open( config_path, 'w' ) as f:
|
||||||
|
json.dump( current_contents, f, indent=2 )
|
||||||
|
|
||||||
|
utils.UserMessage( f'Wrote { config_path }', persist = True )
|
||||||
|
|
||||||
|
|
||||||
|
def SelectAdapter( api_prefix,
|
||||||
|
debug_session,
|
||||||
|
configuration_name,
|
||||||
|
configuration,
|
||||||
|
adapters,
|
||||||
|
launch_variables ):
|
||||||
|
adapter = configuration.get( 'adapter' )
|
||||||
|
|
||||||
|
if isinstance( adapter, str ):
|
||||||
|
adapter_dict = adapters.get( adapter )
|
||||||
|
|
||||||
|
if adapter_dict is None:
|
||||||
|
suggested_gadgets = installer.FindGadgetForAdapter( adapter )
|
||||||
|
if suggested_gadgets:
|
||||||
|
response = utils.AskForInput(
|
||||||
|
f"The specified adapter '{adapter}' is not "
|
||||||
|
"installed. Would you like to install the following gadgets? ",
|
||||||
|
' '.join( suggested_gadgets ) )
|
||||||
|
if response:
|
||||||
|
new_launch_variables = dict( launch_variables )
|
||||||
|
new_launch_variables[ 'configuration' ] = configuration_name
|
||||||
|
|
||||||
|
installer.RunInstaller(
|
||||||
|
api_prefix,
|
||||||
|
False, # Don't leave open
|
||||||
|
*shlex.split( response ),
|
||||||
|
then = debug_session.Start( new_launch_variables ) )
|
||||||
|
return None
|
||||||
|
elif response is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
utils.UserMessage( f"The specified adapter '{adapter}' is not "
|
||||||
|
"available. Did you forget to run "
|
||||||
|
"'install_gadget.py'?",
|
||||||
|
persist = True,
|
||||||
|
error = True )
|
||||||
|
return None
|
||||||
|
|
||||||
|
adapter = adapter_dict
|
||||||
|
|
||||||
|
return adapter
|
||||||
|
|
||||||
|
|
||||||
|
def ResolveConfiguration( adapter,
|
||||||
|
configuration,
|
||||||
|
launch_variables,
|
||||||
|
workspace_root,
|
||||||
|
current_file ):
|
||||||
|
# Additional vars as defined by VSCode:
|
||||||
|
#
|
||||||
|
# ${workspaceFolder} - the path of the folder opened in VS Code
|
||||||
|
# ${workspaceFolderBasename} - the name of the folder opened in VS Code
|
||||||
|
# without any slashes (/)
|
||||||
|
# ${file} - the current opened file
|
||||||
|
# ${relativeFile} - the current opened file relative to workspaceFolder
|
||||||
|
# ${fileBasename} - the current opened file's basename
|
||||||
|
# ${fileBasenameNoExtension} - the current opened file's basename with no
|
||||||
|
# file extension
|
||||||
|
# ${fileDirname} - the current opened file's dirname
|
||||||
|
# ${fileExtname} - the current opened file's extension
|
||||||
|
# ${cwd} - the task runner's current working directory on startup
|
||||||
|
# ${lineNumber} - the current selected line number in the active file
|
||||||
|
# ${selectedText} - the current selected text in the active file
|
||||||
|
# ${execPath} - the path to the running VS Code executable
|
||||||
|
def relpath( p, relative_to ):
|
||||||
|
if not p:
|
||||||
|
return ''
|
||||||
|
return os.path.relpath( p, relative_to )
|
||||||
|
|
||||||
|
def splitext( p ):
|
||||||
|
if not p:
|
||||||
|
return [ '', '' ]
|
||||||
|
return os.path.splitext( p )
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
'dollar': '$', # HACK. Hote '$$' also works.
|
||||||
|
'workspaceRoot': workspace_root,
|
||||||
|
'workspaceFolder': workspace_root,
|
||||||
|
'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME ),
|
||||||
|
'file': current_file,
|
||||||
|
}
|
||||||
|
|
||||||
|
calculus = {
|
||||||
|
'relativeFile': lambda: relpath( current_file, workspace_root ),
|
||||||
|
'fileBasename': lambda: os.path.basename( current_file ),
|
||||||
|
'fileBasenameNoExtension':
|
||||||
|
lambda: splitext( os.path.basename( current_file ) )[ 0 ],
|
||||||
|
'fileDirname': lambda: os.path.dirname( current_file ),
|
||||||
|
'fileExtname': lambda: splitext( os.path.basename( current_file ) )[ 1 ],
|
||||||
|
# NOTE: this is the window-local cwd for the current window, *not* Vim's
|
||||||
|
# working directory.
|
||||||
|
'cwd': os.getcwd,
|
||||||
|
'unusedLocalPort': utils.GetUnusedLocalPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pretend that vars passed to the launch command were typed in by the user
|
||||||
|
# (they may have been in theory)
|
||||||
|
USER_CHOICES.update( launch_variables )
|
||||||
|
variables.update( launch_variables )
|
||||||
|
|
||||||
|
variables.update(
|
||||||
|
utils.ParseVariables( adapter.get( 'variables', {} ),
|
||||||
|
variables,
|
||||||
|
calculus,
|
||||||
|
USER_CHOICES ) )
|
||||||
|
variables.update(
|
||||||
|
utils.ParseVariables( configuration.get( 'variables', {} ),
|
||||||
|
variables,
|
||||||
|
calculus,
|
||||||
|
USER_CHOICES ) )
|
||||||
|
|
||||||
|
|
||||||
|
utils.ExpandReferencesInDict( configuration,
|
||||||
|
variables,
|
||||||
|
calculus,
|
||||||
|
USER_CHOICES )
|
||||||
|
utils.ExpandReferencesInDict( adapter,
|
||||||
|
variables,
|
||||||
|
calculus,
|
||||||
|
USER_CHOICES )
|
||||||
|
|
@ -333,25 +333,41 @@ def UserMessage( msg, persist=False, error=False ):
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def InputSave():
|
def AskingForUserInput():
|
||||||
vim.eval( 'inputsave()' )
|
vim.eval( 'inputsave()' )
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
vim.eval( 'inputrestore()' )
|
vim.eval( 'inputrestore()' )
|
||||||
|
# Clear the command line so that subsequent
|
||||||
|
vim.command( 'redraw' )
|
||||||
|
|
||||||
|
|
||||||
def SelectFromList( prompt, options ):
|
def SelectFromList( prompt,
|
||||||
with InputSave():
|
options,
|
||||||
|
ret = 'text',
|
||||||
|
default_index = None ):
|
||||||
|
with AskingForUserInput():
|
||||||
display_options = [ prompt ]
|
display_options = [ prompt ]
|
||||||
display_options.extend( [ '{0}: {1}'.format( i + 1, v )
|
display_options.extend( [
|
||||||
for i, v in enumerate( options ) ] )
|
'{0}{1}: {2}'.format( '*' if i == default_index else ' ',
|
||||||
|
i + 1,
|
||||||
|
v )
|
||||||
|
for i, v in enumerate( options )
|
||||||
|
] )
|
||||||
try:
|
try:
|
||||||
selection = int( vim.eval(
|
selection = int( vim.eval(
|
||||||
'inputlist( ' + json.dumps( display_options ) + ' )' ) ) - 1
|
'inputlist( ' + json.dumps( display_options ) + ' )' ) ) - 1
|
||||||
if selection < 0 or selection >= len( options ):
|
if selection < 0 or selection >= len( options ):
|
||||||
return None
|
if default_index is not None:
|
||||||
return options[ selection ]
|
selection = default_index
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if ret == 'text':
|
||||||
|
return options[ selection ]
|
||||||
|
else:
|
||||||
|
return selection
|
||||||
except ( KeyboardInterrupt, vim.error ):
|
except ( KeyboardInterrupt, vim.error ):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -362,7 +378,7 @@ def AskForInput( prompt, default_value = None ):
|
||||||
else:
|
else:
|
||||||
default_option = ", '{}'".format( Escape( default_value ) )
|
default_option = ", '{}'".format( Escape( default_value ) )
|
||||||
|
|
||||||
with InputSave():
|
with AskingForUserInput():
|
||||||
try:
|
try:
|
||||||
return vim.eval( "input( '{}' {} )".format( Escape( prompt ),
|
return vim.eval( "input( '{}' {} )".format( Escape( prompt ),
|
||||||
default_option ) )
|
default_option ) )
|
||||||
|
|
@ -370,6 +386,14 @@ def AskForInput( prompt, default_value = None ):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def Confirm( msg, *args ):
|
||||||
|
with AskingForUserInput():
|
||||||
|
try:
|
||||||
|
return int( Call( 'confirm', msg, *args ) )
|
||||||
|
except ( KeyboardInterrupt, vim.error ):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def AppendToBuffer( buf, line_or_lines, modified=False ):
|
def AppendToBuffer( buf, line_or_lines, modified=False ):
|
||||||
line = 1
|
line = 1
|
||||||
try:
|
try:
|
||||||
|
|
@ -512,10 +536,7 @@ def ExpandReferencesInString( orig_s,
|
||||||
|
|
||||||
# Parse any variables passed in in mapping, and ask for any that weren't,
|
# Parse any variables passed in in mapping, and ask for any that weren't,
|
||||||
# storing the result in mapping
|
# storing the result in mapping
|
||||||
bug_catcher = 0
|
while True:
|
||||||
while bug_catcher < 100:
|
|
||||||
++bug_catcher
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s = _Substitute( s, mapping )
|
s = _Substitute( s, mapping )
|
||||||
break
|
break
|
||||||
|
|
@ -531,11 +552,25 @@ def ExpandReferencesInString( orig_s,
|
||||||
if default_value is None and e.default_value is not None:
|
if default_value is None and e.default_value is not None:
|
||||||
try:
|
try:
|
||||||
default_value = _Substitute( e.default_value, mapping )
|
default_value = _Substitute( e.default_value, mapping )
|
||||||
except MissingSubstitution:
|
except MissingSubstitution as f:
|
||||||
default_value = e.default_value
|
if f.name in calculus:
|
||||||
|
default_value = calculus[ f.name ]()
|
||||||
|
else:
|
||||||
|
default_value = e.default_value
|
||||||
|
|
||||||
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
|
value_list = None
|
||||||
default_value )
|
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:
|
if mapping[ key ] is None:
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
@ -570,8 +605,6 @@ def CoerceType( mapping: typing.Dict[ str, typing.Any ], key: str ):
|
||||||
mapping[ key ] = DICT_TYPES[ new_type ]( value )
|
mapping[ key ] = DICT_TYPES[ new_type ]( value )
|
||||||
|
|
||||||
|
|
||||||
# TODO: Should we just run the substitution on the whole JSON string instead?
|
|
||||||
# That woul dallow expansion in bool and number values, such as ports etc. ?
|
|
||||||
def ExpandReferencesInDict( obj, mapping, calculus, user_choices ):
|
def ExpandReferencesInDict( obj, mapping, calculus, user_choices ):
|
||||||
for k in list( obj.keys() ):
|
for k in list( obj.keys() ):
|
||||||
obj[ k ] = ExpandReferencesInObject( obj[ k ],
|
obj[ k ] = ExpandReferencesInObject( obj[ k ],
|
||||||
|
|
@ -787,6 +820,7 @@ def WindowID( window, tab=None ):
|
||||||
return int( Call( 'win_getid', window.number, tab.number ) )
|
return int( Call( 'win_getid', window.number, tab.number ) )
|
||||||
|
|
||||||
|
|
||||||
|
@memoize
|
||||||
def UseWinBar():
|
def UseWinBar():
|
||||||
# Buggy neovim doesn't render correctly when the WinBar is defined:
|
# Buggy neovim doesn't render correctly when the WinBar is defined:
|
||||||
# https://github.com/neovim/neovim/issues/12689
|
# https://github.com/neovim/neovim/issues/12689
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue