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 with options](#launch-with-options)
* [Breakpoints](#breakpoints)
* [Exception breakpoints](#exception-breakpoints)
* [Clear breakpoints](#clear-breakpoints)
* [Stepping](#stepping)
* [Variables and scopes](#variables-and-scopes)
* [Watches](#watches)
@ -66,7 +68,7 @@ For a tutorial and usage overview, take a look at the
* [Motivation](#motivation)
* [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-->
@ -307,6 +309,30 @@ should be):
./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.
## 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
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
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
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
* 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 profile configuration](#debug-profile-configuration)
* [Replacements and variables](#replacements-and-variables)
* [The splat operator](#the-splat-operator)
* [Configuration Format](#configuration-format)
* [Files and locations](#files-and-locations)
* [Adapter configurations](#adapter-configurations)
* [Debug configurations](#debug-configurations)
* [Exception breakpionts](#exception-breakpionts)
* [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)
<!-- 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-->
@ -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
default value for `caught` exceptoins and to break on `uncaught` exception:
```
```json
{
"configurations": {
"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
The following variables are provided:
@ -364,6 +416,7 @@ The following variables are provided:
* `${fileDirname}` - the current opened file's dirname
* `${fileExtname}` - the current opened file's extension
* `${cwd}` - the current working directory of the active window on launch
* `${unusedLocalPort}` - an unused local TCP port
## Remote Debugging Support

View file

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

View file

@ -177,43 +177,52 @@ class DebugSession( object ):
return [ '', '' ]
return os.path.splitext( p )
self._variables = {
variables = {
'dollar': '$', # HACK. Hote '$$' also works.
'workspaceRoot': self._workspace_root,
'workspaceFolder': self._workspace_root,
'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME, install.GetOS() ),
'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':
splitext( os.path.basename( current_file ) )[ 0 ],
'fileDirname': os.path.dirname( current_file ),
'fileExtname': splitext( os.path.basename( current_file ) )[ 1 ],
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(),
'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 )
self._variables.update( launch_variables )
variables.update( launch_variables )
self._variables.update(
variables.update(
utils.ParseVariables( adapter.get( 'variables', {} ),
self._variables,
variables,
calculus,
USER_CHOICES ) )
self._variables.update(
variables.update(
utils.ParseVariables( configuration.get( 'variables', {} ),
self._variables,
variables,
calculus,
USER_CHOICES ) )
utils.ExpandReferencesInDict( configuration,
self._variables,
variables,
calculus,
USER_CHOICES )
utils.ExpandReferencesInDict( adapter,
self._variables,
variables,
calculus,
USER_CHOICES )
if not adapter:

View file

@ -345,9 +345,9 @@ def IsCurrent( window, 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 ):
ExpandReferencesInDict( obj, mapping, user_choices )
ExpandReferencesInDict( obj, mapping, calculus, user_choices )
elif isinstance( obj, list ):
j_offset = 0
obj_copy = list( obj )
@ -360,6 +360,7 @@ def ExpandReferencesInObject( obj, mapping, user_choices ):
# *${something} - expand list in place
value = ExpandReferencesInString( obj_copy[ i ][ 1: ],
mapping,
calculus,
user_choices )
obj.pop( j )
j_offset -= 1
@ -369,14 +370,18 @@ def ExpandReferencesInObject( obj, mapping, user_choices ):
else:
obj[ j ] = ExpandReferencesInObject( obj_copy[ i ],
mapping,
calculus,
user_choices )
elif isinstance( obj, str ):
obj = ExpandReferencesInString( obj, mapping, user_choices )
obj = ExpandReferencesInString( obj, mapping, calculus, user_choices )
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.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
# the key surrounded by '' for unknowable reasons.
key = e.args[ 0 ]
default_value = user_choices.get( key, None )
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
default_value )
user_choices[ key ] = mapping[ key ]
_logger.debug( "Value for %s not set in %s (from %s): set to %s",
key,
s,
orig_s,
mapping[ key ] )
if key in calculus:
mapping[ key ] = calculus[ key ]()
else:
default_value = user_choices.get( key, None )
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
default_value )
user_choices[ 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:
UserMessage( 'Invalid $ in string {}: {}'.format( s, e ),
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?
# 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():
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_mapping = mapping.copy()
@ -431,7 +446,10 @@ def ParseVariables( variables_list, mapping, user_choices ):
if 'shell' in v:
new_v = v.copy()
# 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.update( new_v.get( 'env' ) or {} )
@ -455,6 +473,7 @@ def ParseVariables( variables_list, mapping, user_choices ):
else:
new_variables[ n ] = ExpandReferencesInObject( v,
mapping,
calculus,
user_choices )
return new_variables
@ -575,3 +594,13 @@ def GetVimspectorBase():
return base.decode( 'utf-8' )
else:
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 ), {
'command': 'variables',
'arguments': {
'variablesReference': scope.scope[ 'variablesReference' ]
'variablesReference': scope.VariablesReference(),
},
} )
@ -295,10 +295,10 @@ class VariablesView( object ):
watch.result.IsExpandedByUser() ):
self._connection.DoRequest( partial( self._ConsumeVariables,
self._watch.draw,
watch.result.result ), {
watch.result ), {
'command': 'variables',
'arguments': {
'variablesReference': watch.result.result[ 'variablesReference' ]
'variablesReference': watch.result.VariablesReference(),
},
} )