From aa26d4bc1ae3d58ff750ddffc78281e4c61a8e3b Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 22 Jul 2020 23:58:12 +0100 Subject: [PATCH 1/6] Add ability to specify defaults for variables --- docs/configuration.md | 34 ++++++++- docs/custom_gadget_file.md | 25 +++++++ python3/vimspector/utils.py | 74 +++++++++++++++++-- .../python/simple_python/.vimspector.json | 18 +++++ 4 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 docs/custom_gadget_file.md diff --git a/docs/configuration.md b/docs/configuration.md index 134b049..54a2f54 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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) - + @@ -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 diff --git a/docs/custom_gadget_file.md b/docs/custom_gadget_file.md new file mode 100644 index 0000000..428b7d9 --- /dev/null +++ b/docs/custom_gadget_file.md @@ -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. diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 0aea216..9381407 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -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\$) | # Another doller = escaped + (?P[_a-z][_a-z0-9]*) | # or An identifier - named param + {(?P[_a-z][_a-z0-9]*)} | # or An {identifier} - braced param + {(?P # or An {id:default} - default param, as + (?P[_a-z][a-z0-9]*) # an ID + : # then a colon + (?P(?:[^}]|\})*) # then anything up to }, or a \} + )} | # + (?P) # 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 ) diff --git a/support/test/python/simple_python/.vimspector.json b/support/test/python/simple_python/.vimspector.json index 3d51d2c..6a1b838 100644 --- a/support/test/python/simple_python/.vimspector.json +++ b/support/test/python/simple_python/.vimspector.json @@ -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": { From f6158d0ffb3519554e4c10412e475379d64fc28c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Thu, 23 Jul 2020 15:51:57 +0100 Subject: [PATCH 2/6] Make installer quiet too --- run_tests | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/run_tests b/run_tests index 44e09f3..af0a598 100755 --- a/run_tests +++ b/run_tests @@ -3,6 +3,7 @@ BASEDIR=$(dirname $0) INSTALL=0 UPDATE=0 +INSTALLER_ARGS='' RUN_VIM="vim -N --clean --not-a-term" RUN_TEST="${RUN_VIM} -S lib/run_test.vim" BASEDIR_CMD='py3 pass' @@ -48,6 +49,7 @@ while [ -n "$1" ]; do # old on macOS out_fd=3 exec 3>/dev/null + INSTALLER_ARGS="${INSTALLER_ARGS} --quiet" ;; "--") shift @@ -82,7 +84,10 @@ if [ "${out_fd}" = "1" ]; then fi if [ "$INSTALL" = "1" ] || [ "$INSTALL" = "script" ]; then - if ! python3 $(dirname $0)/install_gadget.py --basedir ${BASEDIR} --all; then + if ! python3 $(dirname $0)/install_gadget.py \ + --basedir ${BASEDIR} \ + ${INSTALLER_ARGS} \ + --all; then echo "Script installation reported errors" >&2 exit 1 fi From 6593f383cff7482a080539dacf598fd462c41feb Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 24 Jul 2020 23:15:39 +0100 Subject: [PATCH 3/6] Use any python for debugpy --- python3/vimspector/developer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python3/vimspector/developer.py b/python3/vimspector/developer.py index 49e96c3..42de1af 100644 --- a/python3/vimspector/developer.py +++ b/python3/vimspector/developer.py @@ -17,7 +17,7 @@ import sys import os -from vimspector import install, utils +from vimspector import install, utils, installer def SetUpDebugpy( wait=False, port=5678 ): @@ -32,7 +32,7 @@ def SetUpDebugpy( wait=False, port=5678 ): exe = sys.executable try: # debugpy uses sys.executable (which is `vim`, so we hack it) - sys.executable = 'python3' + sys.executable = installer.PathToAnyWorkingPython3() debugpy.listen( port ) finally: sys.executable = exe From 10e9a75fc7cf646ef26b5f8de2702e76c4aa5576 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 25 Jul 2020 09:56:23 +0100 Subject: [PATCH 4/6] Tidy UI - use some unicode symbols and organise winbar --- python3/vimspector/breakpoints.py | 6 +++--- python3/vimspector/code.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/python3/vimspector/breakpoints.py b/python3/vimspector/breakpoints.py index 10b89e8..59d1370 100644 --- a/python3/vimspector/breakpoints.py +++ b/python3/vimspector/breakpoints.py @@ -53,13 +53,13 @@ class ProjectBreakpoints( object ): self._next_sign_id = 1 if not utils.SignDefined( 'vimspectorBP' ): - vim.command( 'sign define vimspectorBP text==> texthl=Error' ) + vim.command( 'sign define vimspectorBP text=\\ ● texthl=WarningMsg' ) if not utils.SignDefined( 'vimspectorBPCond' ): - vim.command( 'sign define vimspectorBPCond text=?> texthl=Error' ) + vim.command( 'sign define vimspectorBPCond text=\\ ◆ texthl=WarningMsg' ) if not utils.SignDefined( 'vimspectorBPDisabled' ): - vim.command( 'sign define vimspectorBPDisabled text=!> texthl=Warning' ) + vim.command( 'sign define vimspectorBPDisabled text=\\ ● texthl=LineNr' ) def ConnectionUp( self, connection ): diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index a4f2514..8e33ece 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -41,17 +41,17 @@ class CodeView( object ): } with utils.LetCurrentWindow( self._window ): - vim.command( 'nnoremenu WinBar.Continue :call vimspector#Continue()' ) - vim.command( 'nnoremenu WinBar.Next :call vimspector#StepOver()' ) - vim.command( 'nnoremenu WinBar.Step :call vimspector#StepInto()' ) - vim.command( 'nnoremenu WinBar.Finish :call vimspector#StepOut()' ) - vim.command( 'nnoremenu WinBar.Pause :call vimspector#Pause()' ) - vim.command( 'nnoremenu WinBar.Stop :call vimspector#Stop()' ) - vim.command( 'nnoremenu WinBar.Restart :call vimspector#Restart()' ) - vim.command( 'nnoremenu WinBar.Reset :call vimspector#Reset()' ) + vim.command( 'nnoremenu WinBar.■\\ Stop :call vimspector#Stop()' ) + vim.command( 'nnoremenu WinBar.▶\\ Cont :call vimspector#Continue()' ) + vim.command( 'nnoremenu WinBar.▷\\ Pause :call vimspector#Pause()' ) + vim.command( 'nnoremenu WinBar.↷\\ Next :call vimspector#StepOver()' ) + vim.command( 'nnoremenu WinBar.→\\ Step :call vimspector#StepInto()' ) + vim.command( 'nnoremenu WinBar.←\\ Out :call vimspector#StepOut()' ) + vim.command( 'nnoremenu WinBar.⟲: :call vimspector#Restart()' ) + vim.command( 'nnoremenu WinBar.✕ :call vimspector#Reset()' ) if not utils.SignDefined( 'vimspectorPC' ): - vim.command( 'sign define vimspectorPC text=-> texthl=Search' ) + vim.command( 'sign define vimspectorPC text=\\ ▶ texthl=MatchParen' ) def SetCurrentFrame( self, frame ): From d86b42bf5b06ed09e12c75622d2a13fc75893959 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 27 Jul 2020 22:49:13 +0100 Subject: [PATCH 5/6] Allow VimspectorShowOutput with no argument --- autoload/vimspector.vim | 8 ++++++-- plugin/vimspector.vim | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index 3b5dd75..929a8b3 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -188,11 +188,15 @@ function! vimspector#EvaluateConsole( expr ) abort py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ) ) endfunction -function! vimspector#ShowOutput( category ) abort +function! vimspector#ShowOutput( ... ) abort if !s:enabled return endif - py3 _vimspector_session.ShowOutput( vim.eval( 'a:category' ) ) + if a:0 == 1 + py3 _vimspector_session.ShowOutput( vim.eval( 'a:1' ) ) + else + py3 _vimspector_session.ShowOutput( 'Console' ) + endif endfunction function! vimspector#ShowOutputInWindow( win_id, category ) abort diff --git a/plugin/vimspector.vim b/plugin/vimspector.vim index 416eb04..0aa5f13 100644 --- a/plugin/vimspector.vim +++ b/plugin/vimspector.vim @@ -88,7 +88,7 @@ endif command! -bar -nargs=1 -complete=custom,vimspector#CompleteExpr \ VimspectorWatch \ call vimspector#AddWatch( ) -command! -bar -nargs=1 -complete=custom,vimspector#CompleteOutput +command! -bar -nargs=? -complete=custom,vimspector#CompleteOutput \ VimspectorShowOutput \ call vimspector#ShowOutput( ) command! -bar -nargs=1 -complete=custom,vimspector#CompleteExpr From 3a160aa77a471629bbb69db2da4430bc798be59e Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 27 Jul 2020 22:49:50 +0100 Subject: [PATCH 6/6] Fix: Crash when deleting a watch that never got a result --- python3/vimspector/variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index ffd641d..d82090a 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -106,6 +106,7 @@ class Watch: """Holds a user watch expression (DAP request) and the result (WatchResult)""" def __init__( self, expression: dict ): self.result: WatchResult + self.line = None self.expression = expression self.result = None