Add ability to specify defaults for variables

This commit is contained in:
Ben Jackson 2020-07-22 23:58:12 +01:00
commit aa26d4bc1a
4 changed files with 143 additions and 8 deletions

View file

@ -12,6 +12,7 @@ for Vimspector.
* [Debug profile configuration](#debug-profile-configuration)
* [Replacements and variables](#replacements-and-variables)
* [The splat operator](#the-splat-operator)
* [Default values](#default-values)
* [Configuration Format](#configuration-format)
* [Files and locations](#files-and-locations)
* [Adapter configurations](#adapter-configurations)
@ -28,7 +29,7 @@ for Vimspector.
* [Appendix: Configuration file format](#appendix-configuration-file-format)
* [Appendix: Editor configuration](#appendix-editor-configuration)
<!-- Added by: ben, at: Sun 12 Jul 2020 16:46:18 BST -->
<!-- Added by: ben, at: Fri 31 Jul 2020 22:13:39 BST -->
<!--te-->
@ -204,6 +205,37 @@ You can also combine with static values:
This would yield the intuitive result:
`[ "First", "one", "two three", "four", "Last" ]`
### Default values
You can specify replacesments with default values. In this case if the user has
not specified a value, they are prompted but with the default value
pre-populated, allowing them to just press return to accept the default.
The syntax is `${variableName:default value}`. The default value can contain any
character, but to include a `}` you must escape it with a backslash. To include
a backslash in the JSON you must write `\\`, as in:
```json
{ "key": "${value:default {\\} stuff}" }
```
The default value can also be a replacement variable. However, this _must_ be a
veriable that's already defined, such as one of the [predefined
variables](#predefined-variables), or one speified in a `variables` block. In
order to reference them, you _must_ use `${var}` syntax and you _must_ escape
the closing `}`. For example, the is a common and useful case:
```json
{
"configuration": {
"program": "${script:${file\\}}"
}
}
```
This will prompt the user to specify `script`, but it will default to the path
to the current file.
## Configuration Format
All Vimspector configuration is defined in a JSON object. The complete

View file

@ -0,0 +1,25 @@
---
title: Configuration
---
This document describes how to use vimspector's `install_gadget.py` to install
custom debug adapters. This can be useful as a way to get an adapter working
that isn't officially supported by Vimspector, but otherwise can be made to work
by simply downloading the VScode extension into the gadget directory.
## Usage
```
./install_gadget.py --enable-custom=/path/to/a.json \
--enable-custom=/path/to/b.json`
```
This tells `install_gadget.py` to read `a.json` and `b.json` as _gadget
definitions_ and download/unpack the specified gadgets into the gadget dir, just
like the supported adapters.
## Gadget Definitions
A _gadget definition_ is a file containing a single JSON object definition,
describing the debug adapter and how to download and install it. This mechanism
is crude but can be effective.

View file

@ -19,11 +19,11 @@ import os
import contextlib
import vim
import json
import string
import functools
import subprocess
import shlex
import collections
import re
LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) )
@ -435,6 +435,60 @@ def ExpandReferencesInObject( obj, mapping, calculus, user_choices ):
return obj
# Based on the python standard library string.Template().substitue, enhanced to
# add ${name:default} parsing, and to remove the unnecessary generality.
VAR_MATCH = re.compile(
r"""
\$(?: # A dollar, followed by...
(?P<escaped>\$) | # Another doller = escaped
(?P<named>[_a-z][_a-z0-9]*) | # or An identifier - named param
{(?P<braced>[_a-z][_a-z0-9]*)} | # or An {identifier} - braced param
{(?P<braceddefault> # or An {id:default} - default param, as
(?P<defname>[_a-z][a-z0-9]*) # an ID
: # then a colon
(?P<default>(?:[^}]|\})*) # then anything up to }, or a \}
)} | #
(?P<invalid>) # or Something else - invalid
)
""",
re.IGNORECASE | re.VERBOSE )
class MissingSubstitution( Exception ):
def __init__( self, name, default_value = None ):
self.name = name
self.default_value = default_value
def _Substitute( template, mapping ):
def convert( mo ):
# Check the most common path first.
named = mo.group( 'named' ) or mo.group( 'braced' )
if named is not None:
if named not in mapping:
raise MissingSubstitution( named )
return str( mapping[ named ] )
if mo.group( 'escaped' ) is not None:
return '$'
if mo.group( 'braceddefault' ) is not None:
named = mo.group( 'defname' )
if named not in mapping:
''
raise MissingSubstitution(
named,
mo.group( 'default' ).replace( '\\}', '}' ) )
return str( mapping[ named ] )
if mo.group( 'invalid' ) is not None:
raise ValueError( f"Invalid placeholder in string { template }" )
raise ValueError( 'Unrecognized named group in pattern', VAR_MATCH )
return VAR_MATCH.sub( convert, template )
def ExpandReferencesInString( orig_s,
mapping,
calculus,
@ -449,17 +503,23 @@ def ExpandReferencesInString( orig_s,
++bug_catcher
try:
s = string.Template( s ).substitute( mapping )
s = _Substitute( s, mapping )
break
except KeyError as e:
# 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 ]
except MissingSubstitution as e:
key = e.name
if key in calculus:
mapping[ key ] = calculus[ key ]()
else:
default_value = user_choices.get( key, None )
default_value = user_choices.get( key )
# Allow _one_ level of additional substitution. This allows a very real
# use case of "program": ${prgram:${file\\}}
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
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
default_value )

View file

@ -50,6 +50,24 @@
}
}
},
"run - default": {
"adapter": "debugpy",
"configuration": {
"request": "launch",
"type": "python",
"cwd": "${workspaceRoot}",
"program": "${program:${file\\}}",
"stopOnEntry": false,
"console": "integratedTerminal"
},
"breakpoints": {
"exception": {
"raised": "N",
"uncaught": "",
"userUnhandled": ""
}
}
},
"run - main.py": {
"adapter": "debugpy",
"configuration": {