Merge pull request #196 from puremourning/auto-port

Automatically pick a local port for CodeLLDB
This commit is contained in:
mergify[bot] 2020-07-09 18:51:53 +00:00 • committed by GitHub
commit 915fb29a9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 40 deletions

View file

@ -33,6 +33,8 @@ For a tutorial and usage overview, take a look at the
* [Launch and attach by PID:](#launch-and-attach-by-pid) * [Launch and attach by PID:](#launch-and-attach-by-pid)
* [Launch with options](#launch-with-options) * [Launch with options](#launch-with-options)
* [Breakpoints](#breakpoints) * [Breakpoints](#breakpoints)
* [Exception breakpoints](#exception-breakpoints)
* [Clear breakpoints](#clear-breakpoints)
* [Stepping](#stepping) * [Stepping](#stepping)
* [Variables and scopes](#variables-and-scopes) * [Variables and scopes](#variables-and-scopes)
* [Watches](#watches) * [Watches](#watches)
@ -66,7 +68,7 @@ For a tutorial and usage overview, take a look at the
* [Motivation](#motivation) * [Motivation](#motivation)
* [License](#license) * [License](#license)
<!-- Added by: ben, at: Mon 6 Jul 2020 11:30:01 BST --> <!-- Added by: ben, at: Thu 9 Jul 2020 18:19:20 BST -->
<!--te--> <!--te-->
@ -307,6 +309,30 @@ should be):
./install_gadget.py --all --disable-tcl ./install_gadget.py --all --disable-tcl
``` ```
If you want to just add a new adapter without destroying the exisitng ones, add
`--update-gadget-config`, as in:
```bash
$ ./install_gadget.py --enable-tcl
$ ./install_gadget.py --enable-rust --update-gadget-config
$ ./install_gadget.py --enable-java --update-gadget-config
```
If you want to maintain `configurations` outside of the vimspector repository
(this can be useful if you have custom gadgets or global configurations),
you can tell the installer to use a different basedir, then set
`g:vimspector_base_dir` to point to that directory, for example:
```bash
$ ./install_gadget.py --basedir $HOME/.vim/vimspector-config --all --force-all
```
Then add this to your `.vimrc`:
```viml
let g:vimspector_base_dir=expand( '$HOME/.vim/vimspector-config' )
```
See `--help` for more info. See `--help` for more info.
## Manual gadget installation ## Manual gadget installation
@ -571,7 +597,7 @@ This would start the `Run Test` configuration with `${Test}` set to `'Name of
the test'` and Vimspector would _not_ prompt the user to enter or confirm these the test'` and Vimspector would _not_ prompt the user to enter or confirm these
things. things.
See [this issue](https://github.com/puremourning/vimspector/issues/97) for See [our YouCompleteMe integration guide](#usage-with-youcompleteme) for
another example where it can be used to specify the port to connect the [java another example where it can be used to specify the port to connect the [java
debugger](#java---partially-supported) debugger](#java---partially-supported)
@ -599,6 +625,24 @@ whatever dialect the debugger understands when evaluating expressions.
When using the `<leader><F9>` mapping, the user is prompted to enter these When using the `<leader><F9>` mapping, the user is prompted to enter these
expressions in a command line (with history). expressions in a command line (with history).
### Exception breakpoints
When starting debugging, you may be asekd a few questions about how to handle
exceptoins. These are "exception breakpoints" and vimspector remembers your
choices while Vim is still running.
Typically you can accept the defaults (just keep pressing `<CR>`!) as most debug
adapter defaults are sane, but if you want to break on, say `uncaught exception`
then answer `Y` to that (for example).
You can configure your choices in the `.vimspector.json`. See
[the configuration guide][vimspector-ref-exception] for details on that.
### Clear breakpoints
* Use `vimspector#ClearBreakpoints()`
to clear all breakpoints including the memory of exception breakpoint choices.
## Stepping ## Stepping
* Step in/out, finish, continue, pause etc. using the WinBar, or mappings. * Step in/out, finish, continue, pause etc. using the WinBar, or mappings.

View file

@ -11,15 +11,21 @@ for Vimspector.
* [Debug adapter configuration](#debug-adapter-configuration) * [Debug adapter configuration](#debug-adapter-configuration)
* [Debug profile configuration](#debug-profile-configuration) * [Debug profile configuration](#debug-profile-configuration)
* [Replacements and variables](#replacements-and-variables) * [Replacements and variables](#replacements-and-variables)
* [The splat operator](#the-splat-operator)
* [Configuration Format](#configuration-format) * [Configuration Format](#configuration-format)
* [Files and locations](#files-and-locations) * [Files and locations](#files-and-locations)
* [Adapter configurations](#adapter-configurations) * [Adapter configurations](#adapter-configurations)
* [Debug configurations](#debug-configurations) * [Debug configurations](#debug-configurations)
* [Exception breakpionts](#exception-breakpionts) * [Exception breakpionts](#exception-breakpionts)
* [Predefined Variables](#predefined-variables) * [Predefined Variables](#predefined-variables)
* [Remote Debugging Support](#remote-debugging-support)
* [Python (debugpy) Example](#python-debugpy-example)
* [C-family (gdbserver) Example](#c-family-gdbserver-example)
* [Docker Example](#docker-example)
* [Appendix: Configuration file format](#appendix-configuration-file-format)
* [Appendix: Editor configuration](#appendix-editor-configuration) * [Appendix: Editor configuration](#appendix-editor-configuration)
<!-- Added by: ben, at: Tue 28 Jan 2020 08:47:40 GMT --> <!-- Added by: ben, at: Thu 9 Jul 2020 18:19:39 BST -->
<!--te--> <!--te-->
@ -330,7 +336,7 @@ the configured response is empty string, the debug adapter default will be used.
Referring to the above example, the following tells the debug adapter to use the Referring to the above example, the following tells the debug adapter to use the
default value for `caught` exceptoins and to break on `uncaught` exception: default value for `caught` exceptoins and to break on `uncaught` exception:
``` ```json
{ {
"configurations": { "configurations": {
"example-debug-configuration": { "example-debug-configuration": {
@ -344,6 +350,52 @@ default value for `caught` exceptoins and to break on `uncaught` exception:
... ...
``` ```
The keys in the `exception` mapping are what Vimspector includes in the prompt.
For example, when prompted with the following:
```
cpp_throw: Break on C++: on throw (Y/N/default: Y)?
```
The exception breakpoint "type" is `cpp_throw` and the default is `Y`.
Similarly:
```
cpp_catch: Break on C++: on catch (Y/N/default: N)?
```
The exception breakpoint "type" is `cpp_catch` and the default is `N`.
Use the following to set the values in config and not get asked:
```json
"configurations": {
"example-debug-configuration": {
"adapter": "example-adapter-name",
"breakpoints": {
"exception": {
"cpp_throw": "Y",
"cpp_catch": "Y"
}
},
```
To just accept the defaults for these exception breakpoint types, don't specify
a value, as in :
```json
"configurations": {
"example-debug-configuration": {
"adapter": "example-adapter-name",
"breakpoints": {
"exception": {
"cpp_throw": "",
"cpp_catch": ""
}
},
```
## Predefined Variables ## Predefined Variables
The following variables are provided: The following variables are provided:
@ -364,6 +416,7 @@ The following variables are provided:
* `${fileDirname}` - the current opened file's dirname * `${fileDirname}` - the current opened file's dirname
* `${fileExtname}` - the current opened file's extension * `${fileExtname}` - the current opened file's extension
* `${cwd}` - the current working directory of the active window on launch * `${cwd}` - the current working directory of the active window on launch
* `${unusedLocalPort}` - an unused local TCP port
## Remote Debugging Support ## Remote Debugging Support

View file

@ -478,9 +478,9 @@ GADGETS = {
'type': 'CodeLLDB', 'type': 'CodeLLDB',
"command": [ "command": [
"${gadgetDir}/CodeLLDB/adapter/codelldb", "${gadgetDir}/CodeLLDB/adapter/codelldb",
"--port", "${port}" "--port", "${unusedLocalPort}"
], ],
"port": "${port}", "port": "${unusedLocalPort}",
"configuration": { "configuration": {
"type": "lldb", "type": "lldb",
"name": "lldb", "name": "lldb",

View file

@ -177,43 +177,52 @@ class DebugSession( object ):
return [ '', '' ] return [ '', '' ]
return os.path.splitext( p ) return os.path.splitext( p )
self._variables = { variables = {
'dollar': '$', # HACK. Hote '$$' also works. 'dollar': '$', # HACK. Hote '$$' also works.
'workspaceRoot': self._workspace_root, 'workspaceRoot': self._workspace_root,
'workspaceFolder': self._workspace_root, 'workspaceFolder': self._workspace_root,
'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME, install.GetOS() ), 'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME, install.GetOS() ),
'file': current_file, 'file': current_file,
'relativeFile': relpath( current_file, self._workspace_root ), }
'fileBasename': os.path.basename( current_file ),
calculus = {
'relativeFile': lambda: relpath( current_file,
self._workspace_root ),
'fileBasename': lambda: os.path.basename( current_file ),
'fileBasenameNoExtension': 'fileBasenameNoExtension':
splitext( os.path.basename( current_file ) )[ 0 ], lambda: splitext( os.path.basename( current_file ) )[ 0 ],
'fileDirname': os.path.dirname( current_file ), 'fileDirname': lambda: os.path.dirname( current_file ),
'fileExtname': splitext( os.path.basename( current_file ) )[ 1 ], 'fileExtname': lambda: splitext( os.path.basename( current_file ) )[ 1 ],
# NOTE: this is the window-local cwd for the current window, *not* Vim's # NOTE: this is the window-local cwd for the current window, *not* Vim's
# working directory. # working directory.
'cwd': os.getcwd(), 'cwd': os.getcwd,
'unusedLocalPort': utils.GetUnusedLocalPort,
} }
# Pretend that vars passed to the launch command were typed in by the user # Pretend that vars passed to the launch command were typed in by the user
# (they may have been in theory) # (they may have been in theory)
USER_CHOICES.update( launch_variables ) USER_CHOICES.update( launch_variables )
self._variables.update( launch_variables ) variables.update( launch_variables )
self._variables.update( variables.update(
utils.ParseVariables( adapter.get( 'variables', {} ), utils.ParseVariables( adapter.get( 'variables', {} ),
self._variables, variables,
calculus,
USER_CHOICES ) ) USER_CHOICES ) )
self._variables.update( variables.update(
utils.ParseVariables( configuration.get( 'variables', {} ), utils.ParseVariables( configuration.get( 'variables', {} ),
self._variables, variables,
calculus,
USER_CHOICES ) ) USER_CHOICES ) )
utils.ExpandReferencesInDict( configuration, utils.ExpandReferencesInDict( configuration,
self._variables, variables,
calculus,
USER_CHOICES ) USER_CHOICES )
utils.ExpandReferencesInDict( adapter, utils.ExpandReferencesInDict( adapter,
self._variables, variables,
calculus,
USER_CHOICES ) USER_CHOICES )
if not adapter: if not adapter:

View file

@ -345,9 +345,9 @@ def IsCurrent( window, buf ):
return vim.current.window == window and vim.current.window.buffer == buf return vim.current.window == window and vim.current.window.buffer == buf
def ExpandReferencesInObject( obj, mapping, user_choices ): def ExpandReferencesInObject( obj, mapping, calculus, user_choices ):
if isinstance( obj, dict ): if isinstance( obj, dict ):
ExpandReferencesInDict( obj, mapping, user_choices ) ExpandReferencesInDict( obj, mapping, calculus, user_choices )
elif isinstance( obj, list ): elif isinstance( obj, list ):
j_offset = 0 j_offset = 0
obj_copy = list( obj ) obj_copy = list( obj )
@ -360,6 +360,7 @@ def ExpandReferencesInObject( obj, mapping, user_choices ):
# *${something} - expand list in place # *${something} - expand list in place
value = ExpandReferencesInString( obj_copy[ i ][ 1: ], value = ExpandReferencesInString( obj_copy[ i ][ 1: ],
mapping, mapping,
calculus,
user_choices ) user_choices )
obj.pop( j ) obj.pop( j )
j_offset -= 1 j_offset -= 1
@ -369,14 +370,18 @@ def ExpandReferencesInObject( obj, mapping, user_choices ):
else: else:
obj[ j ] = ExpandReferencesInObject( obj_copy[ i ], obj[ j ] = ExpandReferencesInObject( obj_copy[ i ],
mapping, mapping,
calculus,
user_choices ) user_choices )
elif isinstance( obj, str ): elif isinstance( obj, str ):
obj = ExpandReferencesInString( obj, mapping, user_choices ) obj = ExpandReferencesInString( obj, mapping, calculus, user_choices )
return obj return obj
def ExpandReferencesInString( orig_s, mapping, user_choices ): def ExpandReferencesInString( orig_s,
mapping,
calculus,
user_choices ):
s = os.path.expanduser( orig_s ) s = os.path.expanduser( orig_s )
s = os.path.expandvars( s ) s = os.path.expandvars( s )
@ -393,15 +398,19 @@ def ExpandReferencesInString( orig_s, mapping, user_choices ):
# HACK: This is seemingly the only way to get the key. str( e ) returns # HACK: This is seemingly the only way to get the key. str( e ) returns
# the key surrounded by '' for unknowable reasons. # the key surrounded by '' for unknowable reasons.
key = e.args[ 0 ] key = e.args[ 0 ]
default_value = user_choices.get( key, None )
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ), if key in calculus:
default_value ) mapping[ key ] = calculus[ key ]()
user_choices[ key ] = mapping[ key ] else:
_logger.debug( "Value for %s not set in %s (from %s): set to %s", default_value = user_choices.get( key, None )
key, mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
s, default_value )
orig_s, user_choices[ key ] = mapping[ key ]
mapping[ key ] ) _logger.debug( "Value for %s not set in %s (from %s): set to %s",
key,
s,
orig_s,
mapping[ key ] )
except ValueError as e: except ValueError as e:
UserMessage( 'Invalid $ in string {}: {}'.format( s, e ), UserMessage( 'Invalid $ in string {}: {}'.format( s, e ),
persist = True ) persist = True )
@ -412,12 +421,18 @@ def ExpandReferencesInString( orig_s, mapping, user_choices ):
# TODO: Should we just run the substitution on the whole JSON string instead? # 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. ? # That woul dallow expansion in bool and number values, such as ports etc. ?
def ExpandReferencesInDict( obj, mapping, user_choices ): def ExpandReferencesInDict( obj, mapping, calculus, user_choices ):
for k in obj.keys(): for k in obj.keys():
obj[ k ] = ExpandReferencesInObject( obj[ k ], mapping, user_choices ) obj[ k ] = ExpandReferencesInObject( obj[ k ],
mapping,
calculus,
user_choices )
def ParseVariables( variables_list, mapping, user_choices ): def ParseVariables( variables_list,
mapping,
calculus,
user_choices ):
new_variables = {} new_variables = {}
new_mapping = mapping.copy() new_mapping = mapping.copy()
@ -431,7 +446,10 @@ def ParseVariables( variables_list, mapping, user_choices ):
if 'shell' in v: if 'shell' in v:
new_v = v.copy() new_v = v.copy()
# Bit of a hack. Allows environment variables to be used. # Bit of a hack. Allows environment variables to be used.
ExpandReferencesInDict( new_v, new_mapping, user_choices ) ExpandReferencesInDict( new_v,
new_mapping,
calculus,
user_choices )
env = os.environ.copy() env = os.environ.copy()
env.update( new_v.get( 'env' ) or {} ) env.update( new_v.get( 'env' ) or {} )
@ -455,6 +473,7 @@ def ParseVariables( variables_list, mapping, user_choices ):
else: else:
new_variables[ n ] = ExpandReferencesInObject( v, new_variables[ n ] = ExpandReferencesInObject( v,
mapping, mapping,
calculus,
user_choices ) user_choices )
return new_variables return new_variables
@ -575,3 +594,13 @@ def GetVimspectorBase():
return base.decode( 'utf-8' ) return base.decode( 'utf-8' )
else: else:
return base return base
def GetUnusedLocalPort():
import socket
sock = socket.socket()
# This tells the OS to give us any free port in the range [1024 - 65535]
sock.bind( ( '', 0 ) )
port = sock.getsockname()[ 1 ]
sock.close()
return port

View file

@ -230,7 +230,7 @@ class VariablesView( object ):
scope ), { scope ), {
'command': 'variables', 'command': 'variables',
'arguments': { 'arguments': {
'variablesReference': scope.scope[ 'variablesReference' ] 'variablesReference': scope.VariablesReference(),
}, },
} ) } )
@ -295,10 +295,10 @@ class VariablesView( object ):
watch.result.IsExpandedByUser() ): watch.result.IsExpandedByUser() ):
self._connection.DoRequest( partial( self._ConsumeVariables, self._connection.DoRequest( partial( self._ConsumeVariables,
self._watch.draw, self._watch.draw,
watch.result.result ), { watch.result ), {
'command': 'variables', 'command': 'variables',
'arguments': { 'arguments': {
'variablesReference': watch.result.result[ 'variablesReference' ] 'variablesReference': watch.result.VariablesReference(),
}, },
} ) } )