diff --git a/README.md b/README.md index f70f537..37b05c5 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,12 @@ For a tutorial and usage overview, take a look at the * [Features and Usage](#features-and-usage) * [Supported debugging features](#supported-debugging-features) - * [Supported languages:](#supported-languages) - * [Languages known to work](#languages-known-to-work) - * [Languages known not to work](#languages-known-not-to-work) + * [Supported languages](#supported-languages) * [Other languages](#other-languages) * [Installation](#installation) * [Dependencies](#dependencies) * [Neovim differences](#neovim-differences) * [Windows differences](#windows-differences) - * [Language dependencies](#language-dependencies) * [Clone the plugin](#clone-the-plugin) * [Install some gadgets](#install-some-gadgets) * [Manual gadget installation](#manual-gadget-installation) @@ -66,7 +63,7 @@ For a tutorial and usage overview, take a look at the * [Customisation](#customisation) * [Changing the default signs](#changing-the-default-signs) * [Changing the default window sizes](#changing-the-default-window-sizes) - * [Changing the terminal size](#changing-the-terminal-size) + * [Changing the terminal size](#changing-the-terminal-size) * [Advanced UI customisation](#advanced-ui-customisation) * [Example](#example) * [FAQ](#faq) @@ -74,7 +71,7 @@ For a tutorial and usage overview, take a look at the * [License](#license) * [Sponsorship](#sponsorship) - + @@ -91,6 +88,12 @@ But for now, here's a (rather old) screenshot of Vimsepctor debugging Vim: ![vimspector-vim-screenshot](https://puremourning.github.io/vimspector-web/img/vimspector-overview.png) +And a couple of brief demos: + +[![asciicast](https://asciinema.org/a/VmptWmFHTNLPfK3DVsrR2bv8S.svg)](https://asciinema.org/a/VmptWmFHTNLPfK3DVsrR2bv8S) + +[![asciicast](https://asciinema.org/a/1wZJSoCgs3AvjkhKwetJOJhDh.svg)](https://asciinema.org/a/1wZJSoCgs3AvjkhKwetJOJhDh) + ## Supported debugging features - flexible configuration syntax that can be checked in to source control @@ -108,31 +111,32 @@ But for now, here's a (rather old) screenshot of Vimsepctor debugging Vim: - logging/stdout display - simple stable API for custom tooling (e.g. integrate with language server) -## Supported languages: +For other languages, you'll need some other way to install the gadget. -The following languages are used frequently by the author and are known to work -with little effort, and are supported as first-class languages. +## Supported languages -- C, C++, etc. (languages supported by gdb or lldb) -- Python 2 and Python 3 -- TCL -- Bash scripts -- Java +The following table lists the languages that are "built-in" (along with their +runtime dependencies). They are categorised by their level of support: -## Languages known to work +* `Tested` : Fully supported, Vimspector regression tests cover them +* `Supported` : Fully supported, frequently used and manually tested +* `Experimental`: Working, but not frequently used and rarely tested +* `Legacy`: No longer supported, please migrate your config -The following languages are used frequently by the author, but require some sort -of hackery that makes it challenging to support generally. These languages are -on a best-efforts basis: - -- C# (c-sharp) using dotnet core -- Go (requires separate installation of [Delve][]) -- Node.js (requires node <12 for installation) -- Anything running in chrome (i.e. javascript). - -## Languages known not to work - -- C# (c-sharp) using mono debug adapter (vimspector unable to set breakpoints) +| Language | Status | Switch (for `install_gadget.py`) | Adapter (for `:VimspectorInstall`) | Dependencies | +|------------------|--------------|----------------------------------|------------------------------------|--------------------------------------------| +| C, C++, etc. | Tested | `--all` or `--enable-c` | vscode-cpptools | mono-core | +| Python | Tested | `--all` or `--enable-python` | debugpy | Python 2.7 or Python 3 | +| Go | Tested | `--enable-go` | vscode-go | Go, [Delve][] | +| TCL | Supported | `--all` or `--enable-tcl` | tclpro | TCL 8.5 | +| Bourne Shell | Supported | `--all` or `--enable-bash` | vscode-bash-debug | Bash v?? | +| Node.js | Supported | `--force-enable-node` | vscode-node-debug2 | 6 < Node < 12, Npm | +| Javascript | Supported | `--force-enable-chrome` | debugger-for-chrome | Chrome | +| Java | Supported | `--force-enable-java ` | vscode-java-debug | Compatible LSP plugin (see [later](#java)) | +| C# (dotnet core) | Experimental | `--force-enable-csharp` | netcoredbg | DotNet core | +| C# (mono) | Experimental | `--force-enable-csharp` | vscode-mono-debug | Mono | +| Rust (CodeLLDB) | Experimental | `--force-enable-rust` | CodeLLDB | Python 3 | +| Python.legacy | Legacy | `--force-enable-python.legacy` | vscode-python | Node 10, Python 2.7 or Python 3 | ## Other languages @@ -224,33 +228,6 @@ The following features are not implemented for Windows: * Tailing the vimspector log in the Output Window. -## Language dependencies - -The debug adapters themselves have certain runtime dependencies. They are -categorised as follows: - -* `Tested` : Fully supported, Vimspector regression tests cover them -* `Supported` : Fully supported, frequently used and manually tested -* `Experimental`: Working, but not frequently used and rarely tested -* `Legacy`: No longer supported, please migrate your config - -| Language | Status | Switch | Adapter | Dependencies | -|------------------|--------------|--------------------------------|---------------------|--------------------------------------------| -| C, C++, etc. | Tested | `--all` or `--enable-c` | vscode-cpptools | mono-core | -| Python | Tested | `--all` or `--enable-python` | debugpy | Python 2.7 or Python 3 | -| Go | Tested | `--enable-go` | vscode-go | Go, [Delve][] | -| TCL | Supported | `--all` or `--enable-tcl` | tclpro | TCL 8.5 | -| Bourne Shell | Supported | `--all` or `--enable-bash` | vscode-bash-debug | Bash v?? | -| Node.js | Supported | `--force-enable-node` | vscode-node-debug2 | 6 < Node < 12, Npm | -| Javascript | Supported | `--force-enable-chrome` | debugger-for-chrome | Chrome | -| Java | Supported | `--force-enable-java ` | vscode-java-debug | Compatible LSP plugin (see [later](#java)) | -| C# (dotnet core) | Experimental | `--force-enable-csharp` | netcoredbg | DotNet core | -| C# (mono) | Experimental | `--force-enable-csharp` | vscode-mono-debug | Mono | -| Rust (CodeLLDB) | Experimental | `--force-enable-rust` | CodeLLDB | Python 3 | -| Python.legacy | Legacy | `--force-enable-python.legacy` | vscode-python | Node 10, Python 2.7 or Python 3 | - -For other languages, you'll need some other way to install the gadget. - ## Clone the plugin There are many Vim plugin managers, and I'm not going to state a particular @@ -281,10 +258,32 @@ See support/doc/example_vimrc.vim. ## Install some gadgets -There are a couple of ways of doing this, but ***using `install_gadget.py` is -highly recommended*** where that's an option. +Vimspector is a generic client for Debug Adapters. Debug Adapters (referred to +as 'gadgets' or 'adapters') are what actually do the work of talking to the real +debugers. -For supported languages, `install_gadget.py` will: +In order for Vimspector to be useful, you need to have some adapters installed. + +There are a few ways to do this: + +* If you downloaded a tarball, gadgets for main supported langauges are already + installed for you. +* Using `:VimspectorInstall ` (use TAB `wildmenu` to see the + options, also accepts any `install_gadget.py` option) +* Alternatively, using `python3 install_gadget.py ` (use `--help` to see + all options) +* When attempting to launch a debug configuration, if the configured adapter + can't be found, vimspector might suggest installing one. +* Use `:VimspectorUpdate` to install the latest supported versions of the + gadgets. + +Here's a demo of doing somee installs and an upgrade: + +[![asciicast](https://asciinema.org/a/Hfu4ZvuyTZun8THNen9FQbTay.svg)](https://asciinema.org/a/Hfu4ZvuyTZun8THNen9FQbTay) + +Both `install_gadget.py` and `:VimspectorInstall` do the same set of things, +though the default behaviours are slightly different. For supported languages, +they will: * Download the relevant debug adapter at a version that's been tested from the internet, either as a 'vsix' (Visusal Studio plugin), or clone from GitHub. If @@ -298,25 +297,25 @@ For supported languages, `install_gadget.py` will: To install the tested debug adapter for a language, run: -``` -./install_gadget.py --enable- -``` +| To install | Script | Command | +| --- | --- | --- | +| `` | | `:VimspectorInstall ` | +| ``, ``, ... | | `:VimspectorInstall ...` | +| `` | `./install_gadget.py --enable- ...` | `:VimspectorInstall --enable- ...` | +| Supported adapters | `./install_gadget.py --all` | `:VimspectorInstall --all` | +| Supported adapters, but not TCL | `./install_gadget.py --all --disable-tcl` | `:VimspectorInstall --all --disable-tcl` | +| Supported and experimental adapters | `./install_gadget.py --all --force-all` | `:VimspectorInstall --all` | +| Adapter for specific debug config | | Suggested by Vimspector when starting debugging | -Or to install all supported gagtets: +`"VimspectorInstall` runs `install_gadget.py` in the background with some of +the options defaulted. -``` -./install_gadget.py --all -``` +By default `install_gadget.py` will overwrite your `.gadgets.json` with the set +of adapters just installed, whereas `:VimspectorInstall` will _update_ it, +overwriting only newly changed or installed adapters. -To install everything other than TCL (because TCL is sadly not as popular as it -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: +If you want to just add a new adapter using the script without destroying the +exisitng ones, add `--update-gadget-config`, as in: ```bash $ ./install_gadget.py --enable-tcl @@ -339,10 +338,16 @@ Then add this to your `.vimrc`: let g:vimspector_base_dir=expand( '$HOME/.vim/vimspector-config' ) ``` -See `--help` for more info. +When usnig `:VimspectorInstall`, the `g:vimspector_base_dir` setting is +respected unless `--basedir` is manually added (not recommended). + +See `--help` for more info on the various options. ## Manual gadget installation +If the language you want to debug is not in the supported list above, you can +probably still make it work, but it's more effort. + You essentially need to get a working installation of the debug adapter, find out how to start it, and configure that in an `adapters` entry in either your `.vimspector.json` or in `.gadgets.json`. @@ -352,7 +357,9 @@ its extension manager to install the relevant extension. You can then configure the adapter manually in the `adapters` section of your `.vimspector.json` or in a `gadgets.json`. -PRs are always welcome to add configuration to do this to `install_gadget.py`. +PRs are always welcome to add supported languages (which roughly translates to +updating `python/vimspector/gadgets.py` and testing it). + ### The gadget directory @@ -415,7 +422,8 @@ Example: } ``` -The gadget file is automatically written by `install_gadget.py`. +The gadget file is automatically written by `install_gadget.py` (or +`:VimspectorInstall`). Vimspector will also load any fies matching: `/gadgets//.gadgets.d/*.json`. These have the same @@ -446,6 +454,11 @@ which can be used to check everything is working. This is used by the regression tests in CI so should always work, and is a good way to check if the problem is your configuration rather than a bug. +## Upgrade + +After updating the Vimspector code (either via `git pull` or whatever pacakge +manager), run `:VimspectorUpdate` to update any already-installed gadets. + # About ## Background @@ -865,9 +878,9 @@ an example of getting Vimspector to remotely launch and attach. ## Python * Python: [debugpy][] -* Requires `install_gadget.py --enable-python`, ideally requires a working - compiler and the python development headers/libs to build a C python extension - for performance. +* Install with `install_gadget.py --enable-python` or `:VimspectorInstall + debugpy`, ideally requires a working compiler and the python development + headers/libs to build a C python extension for performance. * Full options: https://github.com/microsoft/debugpy/wiki/Debug-configuration-settings @@ -978,7 +991,8 @@ See [my fork of TclProDebug](https://github.com/puremourning/TclProDebug) for in * C# - dotnet core -Requires `install_gadget.py --force-enable-csharp` +Install with `install_gadget.py --force-enable-csharp` or `:VimspectorInstall +netcoredbg` ```json { @@ -998,7 +1012,8 @@ Requires `install_gadget.py --force-enable-csharp` * C# - mono -Requires `install_gadget.py --force-enable-csharp`. +Install with `install_gadget.py --force-enable-csharp` or `:VimspectorInstall +vscode-mono-debug`. ***Known not to work.*** @@ -1029,7 +1044,7 @@ Requires `install_gadget.py --force-enable-csharp`. Requires: -* `install_gadget.py --enable-go` +* `install_gadget.py --enable-go` or `:VimspectorInstall vscode-go` * [Delve][delve-install] installed, e.g. `go get -u github.com/go-delve/delve/cmd/dlv` * Delve to be in your PATH, or specify the `dlvToolPath` launch option @@ -1057,7 +1072,8 @@ https://marketplace.visualstudio.com/items?itemName=felixfbecker.php-debug Requires: * (optional) Xdebug helper for chrome https://chrome.google.com/webstore/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc -* `install_gadget.py --force-enable-php` +* `install_gadget.py --force-enable-php` or `:VimspectorInstall + vscode-php-debug` * configured php xdebug extension ```ini zend_extension=xdebug.so @@ -1161,7 +1177,8 @@ https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome It allows you to debug scripts running inside chrome from within Vim. -* `./install_gadget.py --force-enable-chrome` +* `./install_gadget.py --force-enable-chrome` or `:VimspectorInstall + debugger-for-chrome` * Example: `support/test/chrome` ```json @@ -1195,7 +1212,8 @@ use it with Vimspector. * Set up [YCM for java][YcmJava]. * Get Vimspector to download the java debug plugin: - `install_gadget.py --force-enable-java ` + `install_gadget.py --force-enable-java ` or + `:VimspectorInstall java-debug-adapter` * Configure Vimspector for your project using the `vscode-java` adapter, e.g.: ```json @@ -1266,7 +1284,7 @@ Rust is supported with any gdb/lldb-based debugger. So it works fine with `vscode-cpptools` and `lldb-vscode` above. However, support for rust is best in [`CodeLLDB`](https://github.com/vadimcn/vscode-lldb#features). -* `./install_gadget.py --force-enable-rust` +* `./install_gadget.py --force-enable-rust` or `:VimspectorInstall CodeLLDB` * Example: `support/test/rust/vimspector_test` ```json @@ -1478,6 +1496,14 @@ hi link jsonCommentError Comment hi link jsonComment Comment ``` +7. What is the difference between a `gadget` and an `adapter`? A gadget is + somethin you install with `:VimspectorInstall` or `install_gadget.py`, an + `adapter` is something that Vimspector talks to (actually it's the Vimsepctor + config describing that thing). These are _usually_ one-to-one, + but in theory a single gadget can supply multiple `adapter` configs. + Typically this happens when a `gadget` supplies different `adapter` config + for, say remote debugging, or debugging in a container, etc. + # Motivation A message from the author about the motivation for this plugin: diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index a7c6e4b..1ada223 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -20,29 +20,47 @@ set cpoptions&vim " }}} -call vimspector#internal#state#Reset() +let s:enabled = vimspector#internal#state#Reset() function! vimspector#Launch() abort + if !s:enabled + return + endif py3 _vimspector_session.Start() endfunction function! vimspector#LaunchWithSettings( settings ) abort + if !s:enabled + return + endif py3 _vimspector_session.Start( launch_variables = vim.eval( 'a:settings' ) ) endfunction function! vimspector#Reset() abort + if !s:enabled + return + endif py3 _vimspector_session.Reset() endfunction function! vimspector#Restart() abort + if !s:enabled + return + endif py3 _vimspector_session.Restart() endfunction function! vimspector#ClearBreakpoints() abort + if !s:enabled + return + endif py3 _vimspector_session.ClearBreakpoints() endfunction function! vimspector#ToggleBreakpoint( ... ) abort + if !s:enabled + return + endif if a:0 == 0 let options = {} else @@ -52,6 +70,9 @@ function! vimspector#ToggleBreakpoint( ... ) abort endfunction function! vimspector#AddFunctionBreakpoint( function, ... ) abort + if !s:enabled + return + endif if a:0 == 0 let options = {} else @@ -62,42 +83,72 @@ function! vimspector#AddFunctionBreakpoint( function, ... ) abort endfunction function! vimspector#StepOver() abort + if !s:enabled + return + endif py3 _vimspector_session.StepOver() endfunction function! vimspector#StepInto() abort + if !s:enabled + return + endif py3 _vimspector_session.StepInto() endfunction function! vimspector#StepOut() abort + if !s:enabled + return + endif py3 _vimspector_session.StepOut() endfunction function! vimspector#Continue() abort + if !s:enabled + return + endif py3 _vimspector_session.Continue() endfunction function! vimspector#Pause() abort + if !s:enabled + return + endif py3 _vimspector_session.Pause() endfunction function! vimspector#Stop() abort + if !s:enabled + return + endif py3 _vimspector_session.Stop() endfunction function! vimspector#ExpandVariable() abort + if !s:enabled + return + endif py3 _vimspector_session.ExpandVariable() endfunction function! vimspector#DeleteWatch() abort + if !s:enabled + return + endif py3 _vimspector_session.DeleteWatch() endfunction function! vimspector#GoToFrame() abort + if !s:enabled + return + endif py3 _vimspector_session.ExpandFrameOrThread() endfunction function! vimspector#AddWatch( ... ) abort + if !s:enabled + return + endif if a:0 == 0 let expr = input( 'Enter watch expression: ' ) else @@ -112,37 +163,68 @@ function! vimspector#AddWatch( ... ) abort endfunction function! vimspector#AddWatchPrompt( expr ) abort + if !s:enabled + return + endif stopinsert setlocal nomodified call vimspector#AddWatch( a:expr ) endfunction function! vimspector#Evaluate( expr ) abort + if !s:enabled + return + endif py3 _vimspector_session.ShowOutput( 'Console' ) py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ) ) endfunction function! vimspector#EvaluateConsole( expr ) abort + if !s:enabled + return + endif stopinsert setlocal nomodified py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ) ) endfunction function! vimspector#ShowOutput( category ) abort + if !s:enabled + return + endif py3 _vimspector_session.ShowOutput( vim.eval( 'a:category' ) ) endfunction +function! vimspector#ShowOutputInWindow( win_id, category ) abort + if !s:enabled + return + endif + py3 __import__( 'vimspector', + \ fromlist = [ 'output' ] ).output.ShowOutputInWindow( + \ int( vim.eval( 'a:win_id' ) ), + \ vim.eval( 'a:category' ) ) +endfunction + function! vimspector#ListBreakpoints() abort + if !s:enabled + return + endif py3 _vimspector_session.ListBreakpoints() endfunction function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort + if !s:enabled + return + endif let buffers = py3eval( '_vimspector_session.GetOutputBuffers() ' \ . ' if _vimspector_session else []' ) return join( buffers, "\n" ) endfunction function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort + if !s:enabled + return + endif return join( py3eval( '_vimspector_session.GetCompletionsSync( ' \.' vim.eval( "a:CmdLine" ),' \.' int( vim.eval( "a:CursorPos" ) ) )' @@ -150,6 +232,52 @@ function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort \ "\n" ) endfunction +function! vimspector#Install( ... ) abort + if !s:enabled + return + endif + if a:0 < 1 + return + endif + let prefix = vimspector#internal#state#GetAPIPrefix() + py3 __import__( 'vimspector', + \ fromlist = [ 'installer' ] ).installer.RunInstaller( + \ vim.eval( 'prefix' ), + \ *vim.eval( 'a:000' ) ) +endfunction + +function! vimspector#CompleteInstall( ArgLead, CmdLine, CursorPos ) abort + if !s:enabled + return + endif + return py3eval( '"\n".join(' + \ . '__import__( "vimspector", fromlist = [ "gadgets" ] )' + \ . '.gadgets.GADGETS.keys() ' + \ . ')' ) +endfunction + +function! vimspector#Update( ... ) abort + if !s:enabled + return + endif + + let prefix = vimspector#internal#state#GetAPIPrefix() + py3 __import__( 'vimspector', + \ fromlist = [ 'installer' ] ).installer.RunUpdate( + \ vim.eval( 'prefix' ), + \ *vim.eval( 'a:000' ) ) +endfunction + +function! vimspector#AbortInstall() abort + if !s:enabled + return + endif + + let prefix = vimspector#internal#state#GetAPIPrefix() + py3 __import__( 'vimspector', fromlist = [ 'installer' ] ).installer.Abort() +endfunction + + " Boilerplate {{{ let &cpoptions=s:save_cpo unlet s:save_cpo diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index 20a0560..474eb7c 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -146,6 +146,13 @@ function! vimspector#internal#job#Reset() abort call vimspector#internal#job#StopDebugSession() endfunction +function! s:_OnCommandExit( category, ch, code ) abort + py3 __import__( "vimspector", + \ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete( + \ vim.eval( 'a:category' ), + \ int( vim.eval( 'a:code' ) ) ) +endfunction + function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort if ! exists( 's:commands' ) let s:commands = {} @@ -157,14 +164,19 @@ function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort let l:index = len( s:commands[ a:category ] ) + let buf = '_vimspector_log_' . a:category + call add( s:commands[ a:category ], job_start( \ a:cmd, \ { \ 'out_io': 'buffer', \ 'in_io': 'null', \ 'err_io': 'buffer', - \ 'out_name': '_vimspector_log_' . a:category . '_out', - \ 'err_name': '_vimspector_log_' . a:category . '_err', + \ 'out_msg': 0, + \ 'err_msg': 0, + \ 'out_name': buf, + \ 'err_name': buf, + \ 'exit_cb': funcref( 's:_OnCommandExit', [ a:category ] ), \ 'out_modifiable': 0, \ 'err_modifiable': 0, \ 'stoponexit': 'kill' @@ -176,12 +188,7 @@ function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort return v:none endif - let l:stdout = ch_getbufnr( - \ job_getchannel( s:commands[ a:category ][ index ] ), 'out' ) - let l:stderr = ch_getbufnr( - \ job_getchannel( s:commands[ a:category ][ index ] ), 'err' ) - - return [ l:stdout, l:stderr ] + return bufnr( buf ) endfunction diff --git a/autoload/vimspector/internal/neojob.vim b/autoload/vimspector/internal/neojob.vim index 8bcae90..0cefc63 100644 --- a/autoload/vimspector/internal/neojob.vim +++ b/autoload/vimspector/internal/neojob.vim @@ -116,58 +116,65 @@ function! s:_OnCommandEvent( category, id, data, event ) abort return endif - if a:data == [''] - return - endif - - if !has_key( s:commands, a:category ) - return - endif - - if !has_key( s:commands[ a:category ], a:id ) - return - endif - - if a:event ==# 'stdout' - let buffer = s:commands[ a:category ][ a:id ].stdout - elseif a:event ==# 'stderr' - let buffer = s:commands[ a:category ][ a:id ].stderr - endif - - try - call bufload( buffer ) - catch /E325/ - " Ignore E325/ATTENTION - endtry - - - let numlines = py3eval( "len( vim.buffers[ int( vim.eval( 'buffer' ) ) ] )" ) - let last_line = getbufline( buffer, '$' )[ 0 ] - - call s:MakeBufferWritable( buffer ) - try - if numlines == 1 && last_line ==# '' - call setbufline( buffer, 1, a:data[ 0 ] ) - else - call setbufline( buffer, '$', last_line . a:data[ 0 ] ) + if a:event ==# 'stdout' || a:event ==# 'stderr' + if a:data == [''] + return endif - call appendbufline( buffer, '$', a:data[ 1: ] ) - finally - call s:MakeBufferReadOnly( buffer ) - call setbufvar( buffer, '&modified', 0 ) - endtry + if !has_key( s:commands, a:category ) + return + endif + + if !has_key( s:commands[ a:category ], a:id ) + return + endif + + if a:event ==# 'stdout' + let buffer = s:commands[ a:category ][ a:id ].stdout + elseif a:event ==# 'stderr' + let buffer = s:commands[ a:category ][ a:id ].stderr + endif - " if the buffer is visible, scroll it - let w = bufwinnr( buffer ) - if w > 0 - let cw = winnr() try - execute w . 'wincmd w' - normal! Gz. - finally - execute cw . 'wincmd w' + call bufload( buffer ) + catch /E325/ + " Ignore E325/ATTENTION endtry + + + let numlines = py3eval( "len( vim.buffers[ int( vim.eval( 'buffer' ) ) ] )" ) + let last_line = getbufline( buffer, '$' )[ 0 ] + + call s:MakeBufferWritable( buffer ) + try + if numlines == 1 && last_line ==# '' + call setbufline( buffer, 1, a:data[ 0 ] ) + else + call setbufline( buffer, '$', last_line . a:data[ 0 ] ) + endif + + call appendbufline( buffer, '$', a:data[ 1: ] ) + finally + call s:MakeBufferReadOnly( buffer ) + call setbufvar( buffer, '&modified', 0 ) + endtry + + " if the buffer is visible, scroll it + let w = bufwinnr( buffer ) + if w > 0 + let cw = winnr() + try + execute w . 'wincmd w' + normal! Gz- + finally + execute cw . 'wincmd w' + endtry + endif + elseif a:event ==# 'exit' + py3 __import__( "vimspector", + \ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete( + \ vim.eval( 'a:category' ), + \ int( vim.eval( 'a:data' ) ) ) endif endfunction @@ -198,29 +205,29 @@ function! vimspector#internal#neojob#StartCommandWithLog( cmd, category ) abort let s:commands[ a:category ] = {} endif - let stdout_buf = bufnr( '_vimspector_log_' . a:category . '_out', v:true ) - let stderr_buf = bufnr( '_vimspector_log_' . a:category . '_err', v:true ) + let buf = bufnr( '_vimspector_log_' . a:category, v:true ) " FIXME: This largely duplicates the same stuff in the python layer, but we " don't want to potentially mess up Vim behaviour where the job output is " attached to a buffer set up by Vim. So we sort o mimic that here. - call s:SetUpHiddenBuffer( stdout_buf ) - call s:SetUpHiddenBuffer( stderr_buf ) + call s:SetUpHiddenBuffer( buf ) let id = jobstart(a:cmd, \ { \ 'on_stdout': funcref( 's:_OnCommandEvent', \ [ a:category ] ), \ 'on_stderr': funcref( 's:_OnCommandEvent', - \ [ a:category ] ) + \ [ a:category ] ), + \ 'on_exit': funcref( 's:_OnCommandEvent', + \ [ a:category ] ), \ } ) let s:commands[ a:category ][ id ] = { - \ 'stdout': stdout_buf, - \ 'stderr': stderr_buf + \ 'stdout': buf, + \ 'stderr': buf \ } - return [ stdout_buf, stderr_buf ] + return buf endfunction function! vimspector#internal#neojob#CleanUpCommand( category ) abort @@ -229,8 +236,11 @@ function! vimspector#internal#neojob#CleanUpCommand( category ) abort endif for id in keys( s:commands[ a:category ] ) - call jobstop( str2nr( id ) ) - call jobwait( [ str2nr( id ) ] ) + let id = str2nr( id ) + if jobwait( [ id ], 0 )[ 0 ] == -1 + call jobstop( id ) + endif + call jobwait( [ id ], -1 ) endfor unlet! s:commands[ a:category ] endfunction diff --git a/autoload/vimspector/internal/state.vim b/autoload/vimspector/internal/state.vim index 4e048c4..b4f18aa 100644 --- a/autoload/vimspector/internal/state.vim +++ b/autoload/vimspector/internal/state.vim @@ -19,18 +19,31 @@ let s:save_cpo = &cpoptions set cpoptions&vim " }}} -let s:is_neovim = has( 'nvim' ) +let s:prefix = '' +if has( 'nvim' ) + let s:prefix='neo' +endif function! vimspector#internal#state#Reset() abort - let prefix = '' - if s:is_neovim - let prefix='neo' - endif - py3 << EOF -import vim -from vimspector import debug_session -_vimspector_session = debug_session.DebugSession( vim.eval( 'prefix' ) ) -EOF + try + py3 import vim + py3 _vimspector_session = __import__( + \ "vimspector", + \ fromlist=[ "debug_session" ] ).debug_session.DebugSession( + \ vim.eval( 's:prefix' ) ) + catch /.*/ + echohl WarningMsg + echom 'Exception while loading vimspector:' v:exception + echom 'Vimspector unavailable: Requires Vim compiled with Python 3.6' + echohl None + return v:false + endtry + + return v:true +endfunction + +function! vimspector#internal#state#GetAPIPrefix() abort + return s:prefix endfunction " Boilerplate {{{ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5fb2b08..ade040c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,7 +28,7 @@ stages: - bash: pip3 install -r dev_requirements.txt displayName: "Install requirements" - - bash: $HOME/.local/bin/vint autoload/ compiler/ plugin/ tests/ + - bash: $HOME/.local/bin/vint autoload/ compiler/ plugin/ tests/ syntax/ displayName: "Run vint" - job: 'linux' @@ -45,20 +45,17 @@ stages: - task: CacheBeta@0 inputs: - key: v1 | gadgets | $(Agent.OS) | install_gadget.py + key: v2 | gadgets | $(Agent.OS) | python3/vimspector/gadgets.py path: gadgets/linux/download displayName: Cache gadgets - - bash: python3 install_gadget.py --all - displayName: 'Install gadgets - python3' - - bash: vim --version displayName: 'Print vim version information' - bash: | eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv) export GOPATH=$HOME/go - ./run_tests --report messages --quiet + ./run_tests --install --update --report messages --quiet displayName: 'Run the tests' env: VIMSPECTOR_MIMODE: gdb @@ -92,17 +89,14 @@ stages: - task: CacheBeta@0 inputs: - key: v1 | gadgets | $(Agent.OS) | install_gadget.py + key: v2 | gadgets | $(Agent.OS) | python3/vimspector/gadgets.py path: gadgets/macos/download displayName: Cache gadgets - - bash: python3 install_gadget.py --all - displayName: 'Install gadgets - python3' - - bash: vim --version displayName: 'Print vim version information' - - bash: ./run_tests --report messages --quiet + - bash: ./run_tests --install --update --report messages --quiet displayName: 'Run the tests' env: VIMSPECTOR_MIMODE: lldb diff --git a/install_gadget.py b/install_gadget.py index f805e80..4b42984 100755 --- a/install_gadget.py +++ b/install_gadget.py @@ -23,9 +23,6 @@ if sys.version_info.major < 3: import argparse import os -import string -import subprocess -import traceback import json import functools import operator @@ -35,628 +32,9 @@ import glob sys.path.insert( 1, os.path.join( os.path.dirname( __file__ ), 'python3' ) ) -from vimspector import install, installer +from vimspector import install, installer, gadgets from vimspector.vendor.json_minify import minify -GADGETS = { - 'vscode-cpptools': { - 'language': 'c', - 'download': { - 'url': 'https://github.com/Microsoft/vscode-cpptools/releases/download/' - '${version}/${file_name}', - }, - 'do': lambda name, root, gadget: InstallCppTools( name, root, gadget ), - 'all': { - 'version': '0.27.0', - "adapters": { - "vscode-cpptools": { - "name": "cppdbg", - "command": [ - "${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7" - ], - "attach": { - "pidProperty": "processId", - "pidSelect": "ask" - }, - "configuration": { - "type": "cppdbg", - "args": [], - "cwd": "${workspaceRoot}", - "environment": [], - } - }, - }, - }, - 'linux': { - 'file_name': 'cpptools-linux.vsix', - 'checksum': - '3695202e1e75a03de18049323b66d868165123f26151f8c974a480eaf0205435', - }, - 'macos': { - 'file_name': 'cpptools-osx.vsix', - 'checksum': - 'cb061e3acd7559a539e5586f8d3f535101c4ec4e8a48195856d1d39380b5cf3c', - }, - 'windows': { - 'file_name': 'cpptools-win32.vsix', - 'checksum': - 'aa294368ed16d48c59e49c8000e146eae5a19ad07b654efed5db8ec93b24229e', - "adapters": { - "vscode-cpptools": { - "name": "cppdbg", - "command": [ - "${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7.exe" - ], - "attach": { - "pidProperty": "processId", - "pidSelect": "ask" - }, - "configuration": { - "type": "cppdbg", - "args": [], - "cwd": "${workspaceRoot}", - "environment": [], - "MIMode": "gdb", - "MIDebuggerPath": "gdb.exe" - } - }, - }, - }, - }, - 'vscode-python': { - 'language': 'python.legacy', - 'enabled': False, - 'download': { - 'url': 'https://github.com/Microsoft/vscode-python/releases/download/' - '${version}/${file_name}', - }, - 'all': { - 'version': '2019.11.50794', - 'file_name': 'ms-python-release.vsix', - 'checksum': - '6a9edf9ecabed14aac424e6007858068204a3638bf3bb4f235bd6035d823acc6', - }, - 'adapters': { - "vscode-python": { - "name": "vscode-python", - "command": [ - "node", - "${gadgetDir}/vscode-python/out/client/debugger/debugAdapter/main.js", - ], - } - }, - }, - 'debugpy': { - 'language': 'python', - 'download': { - 'url': 'https://github.com/microsoft/debugpy/archive/${file_name}' - }, - 'all': { - 'version': '1.0.0b12', - 'file_name': 'v1.0.0b12.zip', - 'checksum': - '210632bba2221fbb841c9785a615258819ceec401d1abdbeb5f2326f12cc72a1' - }, - 'do': lambda name, root, gadget: InstallDebugpy( name, root, gadget ), - 'adapters': { - 'debugpy': { - "command": [ - sys.executable, - "${gadgetDir}/debugpy/build/lib/debugpy/adapter" - ], - "name": "debugpy", - "configuration": { - "python": sys.executable, - # Don't debug into subprocesses, as this leads to problems (vimspector - # doesn't support the custom messages) - # https://github.com/puremourning/vimspector/issues/141 - "subProcess": False, - } - } - }, - }, - 'vscode-java-debug': { - 'language': 'java', - 'enabled': False, - 'download': { - 'url': 'https://github.com/microsoft/vscode-java-debug/releases/download/' - '${version}/${file_name}', - }, - 'all': { - 'version': '0.26.0', - 'file_name': 'vscjava.vscode-java-debug-0.26.0.vsix', - 'checksum': - 'de49116ff3a3c941dad0c36d9af59baa62cd931e808a2ab392056cbb235ad5ef', - }, - 'adapters': { - "vscode-java": { - "name": "vscode-java", - "port": "${DAPPort}", - } - }, - }, - 'java-language-server': { - 'language': 'javac', - 'enabled': False, - 'download': { - 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' - 'publishers/georgewfraser/vsextensions/vscode-javac/${version}/' - 'vspackage', - 'target': 'georgewfraser.vscode-javac-0.2.31.vsix.gz', - 'format': 'zip.gz', - }, - 'all': { - 'version': '0.2.31', - 'file_name': 'georgewfraser.vscode-javac-0.2.31.vsix.gz', - 'checksum': - '5b0248ec1198d3ece9a9c6b9433b30c22e308f0ae6e4c7bd09cd943c454e3e1d', - }, - 'adapters': { - "vscode-javac": { - "name": "vscode-javac", - "type": "vscode-javac", - "command": [ - "${gadgetDir}/java-language-server/dist/debug_adapter_mac.sh" - ], - "attach": { - "pidSelect": "none" - } - } - }, - }, - 'tclpro': { - 'language': 'tcl', - 'repo': { - 'url': 'https://github.com/puremourning/TclProDebug', - 'ref': 'master' - }, - 'do': lambda name, root, gadget: InstallTclProDebug( name, root, gadget ), - 'adapters': { - "tclpro": { - "name": "tclpro", - "type": "tclpro", - "command": [ - "${gadgetDir}/tclpro/bin/debugadapter" - ], - "attach": { - "pidSelect": "none" - }, - "configuration": { - "target": "${file}", - "args": [ "*${args}" ], - "tclsh": "tclsh", - "cwd": "${workspaceRoot}", - "extensionDirs": [ - "${workspaceRoot}/.tclpro/extensions", - "${HOME}/.tclpro/extensions", - ] - } - } - }, - }, - 'netcoredbg': { - 'language': 'csharp', - 'enabled': False, - 'download': { - 'url': 'https://github.com/Samsung/netcoredbg/releases/download/latest/' - '${file_name}', - 'format': 'tar', - }, - 'all': { - 'version': 'master' - }, - 'macos': { - 'file_name': 'netcoredbg-osx-master.tar.gz', - 'checksum': - 'c1dc6ed58c3f5b0473cfb4985a96552999360ceb9795e42d9c9be64af054f821', - }, - 'linux': { - 'file_name': 'netcoredbg-linux-master.tar.gz', - 'checksum': '', - }, - 'windows': { - 'file_name': 'netcoredbg-win64-master.zip', - 'checksum': '', - }, - 'do': lambda name, root, gadget: installer.MakeSymlink( - gadget_dir, - name, - os.path.join( root, 'netcoredbg' ) ), - 'adapters': { - 'netcoredbg': { - "name": "netcoredbg", - "command": [ - "${gadgetDir}/netcoredbg/netcoredbg", - "--interpreter=vscode" - ], - "attach": { - "pidProperty": "processId", - "pidSelect": "ask" - }, - }, - } - }, - 'vscode-mono-debug': { - 'language': 'csharp', - 'enabled': False, - 'download': { - 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' - 'publishers/ms-vscode/vsextensions/mono-debug/${version}/' - 'vspackage', - 'target': 'vscode-mono-debug.vsix.gz', - 'format': 'zip.gz', - }, - 'all': { - 'file_name': 'vscode-mono-debug.vsix', - 'version': '0.15.8', - 'checksum': - '723eb2b621b99d65a24f215cb64b45f5fe694105613a900a03c859a62a810470', - }, - 'adapters': { - 'vscode-mono-debug': { - "name": "mono-debug", - "command": [ - "mono", - "${gadgetDir}/vscode-mono-debug/bin/Release/mono-debug.exe" - ], - "attach": { - "pidSelect": "none" - }, - }, - } - }, - 'vscode-bash-debug': { - 'language': 'bash', - 'download': { - 'url': 'https://github.com/rogalmic/vscode-bash-debug/releases/' - 'download/${version}/${file_name}', - }, - 'all': { - 'file_name': 'bash-debug-0.3.7.vsix', - 'version': 'v0.3.7', - 'checksum': - '7b73e5b4604375df8658fb5a72c645c355785a289aa785a986e508342c014bb4', - }, - 'do': lambda name, root, gadget: InstallBashDebug( name, root, gadget ), - 'adapters': { - "vscode-bash": { - "name": "bashdb", - "command": [ - "node", - "${gadgetDir}/vscode-bash-debug/out/bashDebug.js" - ], - "variables": { - "BASHDB_HOME": "${gadgetDir}/vscode-bash-debug/bashdb_dir" - }, - "configuration": { - "request": "launch", - "type": "bashdb", - "program": "${file}", - "args": [], - "env": {}, - "pathBash": "bash", - "pathBashdb": "${BASHDB_HOME}/bashdb", - "pathBashdbLib": "${BASHDB_HOME}", - "pathCat": "cat", - "pathMkfifo": "mkfifo", - "pathPkill": "pkill", - "cwd": "${workspaceRoot}", - "terminalKind": "integrated", - } - } - } - }, - 'vscode-go': { - 'language': 'go', - 'download': { - 'url': 'https://github.com/microsoft/vscode-go/releases/download/' - '${version}/${file_name}' - }, - 'all': { - 'version': '0.11.4', - 'file_name': 'Go-0.11.4.vsix', - 'checksum': - 'ff7d7b944da5448974cb3a0086f4a2fd48e2086742d9c013d6964283d416027e' - }, - 'adapters': { - 'vscode-go': { - 'name': 'delve', - 'command': [ - 'node', - '${gadgetDir}/vscode-go/out/src/debugAdapter/goDebug.js' - ], - }, - }, - }, - 'vscode-php-debug': { - 'language': 'php', - 'enabled': False, - 'download': { - 'url': - 'https://github.com/felixfbecker/vscode-php-debug/releases/download/' - '${version}/${file_name}', - }, - 'all': { - 'version': 'v1.13.0', - 'file_name': 'php-debug.vsix', - 'checksum': - '8a51e593458fd14623c1c89ebab87347b087d67087717f18bcf77bb788052718', - }, - 'adapters': { - 'vscode-php-debug': { - 'name': "php-debug", - 'command': [ - 'node', - "${gadgetDir}/vscode-php-debug/out/phpDebug.js", - ] - } - } - }, - 'vscode-node-debug2': { - 'language': 'node', - 'enabled': False, - 'repo': { - 'url': 'https://github.com/microsoft/vscode-node-debug2', - 'ref': 'v1.42.0', - }, - 'do': lambda name, root, gadget: InstallNodeDebug( name, root, gadget ), - 'adapters': { - 'vscode-node': { - 'name': 'node2', - 'type': 'node2', - 'command': [ - 'node', - '${gadgetDir}/vscode-node-debug2/out/src/nodeDebug.js' - ] - }, - }, - }, - 'debugger-for-chrome': { - 'language': 'chrome', - 'enabled': False, - 'download': { - 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' - 'publishers/msjsdiag/vsextensions/' - 'debugger-for-chrome/${version}/vspackage', - 'target': 'msjsdiag.debugger-for-chrome-4.12.0.vsix.gz', - 'format': 'zip.gz', - }, - 'all': { - 'version': '4.12.0', - 'file_name': 'msjsdiag.debugger-for-chrome-4.12.0.vsix', - 'checksum': - '0df2fe96d059a002ebb0936b0003e6569e5a5c35260dc3791e1657d27d82ccf5' - }, - 'adapters': { - 'chrome': { - 'name': 'debugger-for-chrome', - 'type': 'chrome', - 'command': [ - 'node', - '${gadgetDir}/debugger-for-chrome/out/src/chromeDebug.js' - ], - }, - }, - }, - 'CodeLLDB': { - 'language': 'rust', - 'enabled': False, - 'download': { - 'url': 'https://github.com/vadimcn/vscode-lldb/releases/download/' - '${version}/${file_name}', - }, - 'all': { - 'version': 'v1.5.3', - }, - 'macos': { - 'file_name': 'codelldb-x86_64-darwin.vsix', - 'checksum': - '7505bc1cdfcfd1cb981e2996aec62d63577440709bac31dcadb41a3b4b44631a', - 'make_executable': [ - 'adapter/codelldb', - 'lldb/bin/debugserver', - 'lldb/bin/lldb', - 'lldb/bin/lldb-argdumper', - ], - }, - 'linux': { - 'file_name': 'codelldb-x86_64-linux.vsix', - 'checksum': - 'ce7efc3e94d775368e5942a02bf5c326b6809a0b4c389f79ffa6a8f6f6b72139', - 'make_executable': [ - 'adapter/codelldb', - 'lldb/bin/lldb', - 'lldb/bin/lldb-server', - 'lldb/bin/lldb-argdumper', - ], - }, - 'windows': { - 'file_name': 'codelldb-x86_64-windows.vsix', - 'checksum': - '', - 'make_executable': [] - }, - 'adapters': { - 'CodeLLDB': { - 'name': 'CodeLLDB', - 'type': 'CodeLLDB', - "command": [ - "${gadgetDir}/CodeLLDB/adapter/codelldb", - "--port", "${unusedLocalPort}" - ], - "port": "${unusedLocalPort}", - "configuration": { - "type": "lldb", - "name": "lldb", - "cargo": {}, - "args": [], - "cwd": "${workspaceRoot}", - "env": {}, - "terminal": "integrated", - } - }, - }, - }, -} - - -def InstallGeneric( name, root, gadget ): - extension = os.path.join( root, 'extension' ) - for f in gadget.get( 'make_executable', [] ): - installer.MakeExecutable( os.path.join( extension, f ) ) - - installer.MakeExtensionSymlink( vimspector_base, name, root ) - - -def InstallCppTools( name, root, gadget ): - extension = os.path.join( root, 'extension' ) - - # It's hilarious, but the execute bits aren't set in the vsix. So they - # actually have javascript code which does this. It's just a horrible horrible - # hack that really is not funny. - installer.MakeExecutable( os.path.join( extension, - 'debugAdapters', - 'OpenDebugAD7' ) ) - with open( os.path.join( extension, 'package.json' ) ) as f: - package = json.load( f ) - runtime_dependencies = package[ 'runtimeDependencies' ] - for dependency in runtime_dependencies: - for binary in dependency.get( 'binaries' ): - file_path = os.path.abspath( os.path.join( extension, binary ) ) - if os.path.exists( file_path ): - installer.MakeExecutable( os.path.join( extension, binary ) ) - - installer.MakeExtensionSymlink( vimspector_base, name, root ) - - -def InstallBashDebug( name, root, gadget ): - installer.MakeExecutable( os.path.join( root, - 'extension', - 'bashdb_dir', - 'bashdb' ) ) - installer.MakeExtensionSymlink( vimspector_base, name, root ) - - -def InstallDebugpy( name, root, gadget ): - wd = os.getcwd() - root = os.path.join( root, 'debugpy-{}'.format( gadget[ 'version' ] ) ) - os.chdir( root ) - try: - subprocess.check_call( [ sys.executable, 'setup.py', 'build' ] ) - finally: - os.chdir( wd ) - - installer.MakeSymlink( gadget_dir, name, root ) - - -def InstallTclProDebug( name, root, gadget ): - configure = [ './configure' ] - - if OS == 'macos': - # Apple removed the headers from system frameworks because they are - # determined to make life difficult. And the TCL configure scripts are super - # old so don't know about this. So we do their job for them and try and find - # a tclConfig.sh. - # - # NOTE however that in Apple's infinite wisdom, installing the "headers" in - # the other location is actually broken because the paths in the - # tclConfig.sh are pointing at the _old_ location. You actually do have to - # run the package installation which puts the headers back in order to work. - # This is why the below list is does not contain stuff from - # /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform - # '/Applications/Xcode.app/Contents/Developer/Platforms' - # '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System' - # '/Library/Frameworks/Tcl.framework', - # '/Applications/Xcode.app/Contents/Developer/Platforms' - # '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System' - # '/Library/Frameworks/Tcl.framework/Versions' - # '/Current', - for p in [ '/usr/local/opt/tcl-tk/lib' ]: - if os.path.exists( os.path.join( p, 'tclConfig.sh' ) ): - configure.append( '--with-tcl=' + p ) - break - - - with installer.CurrentWorkingDir( os.path.join( root, 'lib', 'tclparser' ) ): - subprocess.check_call( configure ) - subprocess.check_call( [ 'make' ] ) - - installer.MakeSymlink( gadget_dir, name, root ) - - -def InstallNodeDebug( name, root, gadget ): - node_version = subprocess.check_output( [ 'node', '--version' ], - universal_newlines=True ).strip() - print( "Node.js version: {}".format( node_version ) ) - if list( map( int, node_version[ 1: ].split( '.' ) ) ) >= [ 12, 0, 0 ]: - print( "Can't install vscode-debug-node2:" ) - print( "Sorry, you appear to be running node 12 or later. That's not " - "compatible with the build system for this extension, and as far as " - "we know, there isn't a pre-built independent package." ) - print( "My advice is to install nvm, then do:" ) - print( " $ nvm install --lts 10" ) - print( " $ nvm use --lts 10" ) - print( " $ ./install_gadget.py --enable-node ..." ) - raise RuntimeError( 'Invalid node environent for node debugger' ) - - with installer.CurrentWorkingDir( root ): - subprocess.check_call( [ 'npm', 'install' ] ) - subprocess.check_call( [ 'npm', 'run', 'build' ] ) - installer.MakeSymlink( gadget_dir, name, root ) - - -def InstallGagdet( name, gadget, failed, all_adapters ): - try: - v = {} - v.update( gadget.get( 'all', {} ) ) - v.update( gadget.get( OS, {} ) ) - - if 'download' in gadget: - if 'file_name' not in v: - raise RuntimeError( "Unsupported OS {} for gadget {}".format( OS, - name ) ) - - destination = os.path.join( gadget_dir, 'download', name, v[ 'version' ] ) - - url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( v ) - - file_path = installer.DownloadFileTo( - url, - destination, - file_name = gadget[ 'download' ].get( 'target' ), - checksum = v.get( 'checksum' ), - check_certificate = not args.no_check_certificate ) - - root = os.path.join( destination, 'root' ) - installer.ExtractZipTo( - file_path, - root, - format = gadget[ 'download' ].get( 'format', 'zip' ) ) - elif 'repo' in gadget: - url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( v ) - ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( v ) - - destination = os.path.join( gadget_dir, 'download', name ) - installer.CloneRepoTo( url, ref, destination ) - root = destination - - if 'do' in gadget: - gadget[ 'do' ]( name, root, v ) - else: - InstallGeneric( name, root, v ) - - # Allow per-OS adapter overrides. v already did that for us... - all_adapters.update( v.get( 'adapters', {} ) ) - # Add any other "all" adapters - all_adapters.update( gadget.get( 'adapters', {} ) ) - - print( "Done installing {}".format( name ) ) - except Exception as e: - traceback.print_exc() - failed.append( name ) - print( "FAILED installing {}: {}".format( name, e ) ) - - # ------------------------------------------------------------------------------ # Entry point # ------------------------------------------------------------------------------ @@ -691,6 +69,14 @@ parser.add_argument( '--force-all', action = 'store_true', help = 'Enable all unsupported completers' ) +parser.add_argument( '--quiet', + action = 'store_true', + help = 'Suppress installation output' ) + +parser.add_argument( '--verbose', + action = 'store_true', + help = 'Force installation output' ) + parser.add_argument( '--basedir', action = 'store', help = 'Advanced option. ' @@ -723,7 +109,7 @@ parser.add_argument( '--sudo', "run this as root via sudo, pass this flag." ) done_languages = set() -for name, gadget in GADGETS.items(): +for name, gadget in gadgets.GADGETS.items(): lang = gadget[ 'language' ] if lang in done_languages: continue @@ -766,12 +152,9 @@ if args.basedir: vimspector_base = os.path.abspath( args.basedir ) install.MakeInstallDirs( vimspector_base ) - -OS = install.GetOS() -gadget_dir = install.GetGadgetDir( vimspector_base, OS ) - -print( 'OS = ' + OS ) -print( 'gadget_dir = ' + gadget_dir ) +installer.Configure( vimspector_base = vimspector_base, + quiet = args.quiet and not args.verbose, + no_check_certificate = args.no_check_certificate ) if args.force_all and not args.all: args.all = True @@ -789,21 +172,11 @@ for custom_file_name in functools.reduce( operator.add, failed = [] -if args.update_gadget_config: - with open( install.GetGadgetConfigFile( vimspector_base ), 'r' ) as f: - all_adapters = json.load( f ).get( 'adapters', {} ) -else: - all_adapters = {} +succeeded = [] +all_adapters = installer.ReadAdapters( + read_existing = args.update_gadget_config ) -# Include "built-in" adapter for multi-session mode -all_adapters.update( { - 'multi-session': { - 'port': '${port}', - 'host': '${host}' - }, -} ) - -for name, gadget in GADGETS.items(): +for name, gadget in gadgets.GADGETS.items(): if not gadget.get( 'enabled', True ): if ( not args.force_all and not getattr( args, 'force_enable_' + gadget[ 'language' ] ) ): @@ -814,33 +187,36 @@ for name, gadget in GADGETS.items(): if getattr( args, 'disable_' + gadget[ 'language' ] ): continue - InstallGagdet( name, - gadget, - failed, - all_adapters ) + installer.InstallGagdet( name, + gadget, + succeeded, + failed, + all_adapters ) for name, gadget in CUSTOM_GADGETS.items(): - InstallGagdet( name, gadget, failed, all_adapters ) - -adapter_config = json.dumps ( { 'adapters': all_adapters }, - indent=2, - sort_keys=True ) + installer.InstallGagdet( name, gadget, succeeded, failed, all_adapters ) if args.no_gadget_config: print( "" ) print( "Would write the following gadgets: " ) - print( adapter_config ) + installer.WriteAdapters( all_adapters, to_file = sys.stdout ) else: - with open( install.GetGadgetConfigFile( vimspector_base ), 'w' ) as f: - f.write( adapter_config ) - -if failed: - raise RuntimeError( 'Failed to install gadgets: {}'.format( - ','.join( failed ) ) ) + installer.WriteAdapters( all_adapters ) if args.basedir: print( "" ) print( "***NOTE***: You set --basedir to " + args.basedir + ". Therefore you _must_ ensure this is in your vimrc:\n" "let g:vimspector_base_dir='" + vimspector_base + "'" ) + +if succeeded: + print( "Done" ) + print( "The following adapters were installed successfully:\n - {}".format( + '\n - '.join( succeeded ) ) ) + +if failed: + sys.exit( 'Failed to install adapters:\n * {}{}'.format( + '\n * '.join( failed ), + "\nRe-run with --verbose for more info on failures" + if args.quiet and not args.verbose else '' ) ) diff --git a/plugin/vimspector.vim b/plugin/vimspector.vim index fa8312c..3e937da 100644 --- a/plugin/vimspector.vim +++ b/plugin/vimspector.vim @@ -34,6 +34,7 @@ endif " - Add commands/mappings/menus? let g:loaded_vimpector = 1 +let g:vimspector_home = expand( ':p:h:h' ) let s:mappings = get( g:, 'vimspector_enable_mappings', '' ) @@ -97,6 +98,19 @@ command! -bar \ VimspectorReset \ call vimspector#Reset() +" Installer commands +command! -bar -nargs=* -complete=custom,vimspector#CompleteInstall + \ VimspectorInstall + \ call vimspector#Install( ) +command! -bar -nargs=* + \ VimspectorUpdate + \ call vimspector#Update( ) +command! -bar -nargs=0 + \ VimspectorAbortInstall + \ call vimspector#AbortInstall() + + + " Dummy autocommands so that we can call this whenever augroup VimspectorUserAutoCmds au! diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 9ff680a..cc23a47 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -30,7 +30,8 @@ from vimspector import ( breakpoints, stack_trace, utils, variables, - settings ) + settings, + installer ) from vimspector.vendor.json_minify import minify # We cache this once, and don't allow it to change (FIXME?) @@ -51,8 +52,7 @@ class DebugSession( object ): self._logger.info( "API is: {}".format( api_prefix ) ) self._logger.info( 'VIMSPECTOR_HOME = %s', VIMSPECTOR_HOME ) self._logger.info( 'gadgetDir = %s', - install.GetGadgetDir( VIMSPECTOR_HOME, - install.GetOS() ) ) + install.GetGadgetDir( VIMSPECTOR_HOME ) ) self._uiTab = None self._stackTraceView = None @@ -91,7 +91,7 @@ class DebugSession( object ): configurations = {} adapters = {} - glob.glob( install.GetGadgetDir( VIMSPECTOR_HOME, install.GetOS() ) ) + 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}' ) @@ -152,6 +152,24 @@ class DebugSession( object ): 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( + self._api_prefix, + *shlex.split( response ), + then = lambda: self.Start( new_launch_variables ) ) + return + elif response is None: + return + utils.UserMessage( f"The specified adapter '{adapter}' is not " "available. Did you forget to run " "'install_gadget.py'?", @@ -192,7 +210,7 @@ class DebugSession( object ): 'dollar': '$', # HACK. Hote '$$' also works. 'workspaceRoot': self._workspace_root, 'workspaceFolder': self._workspace_root, - 'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME, install.GetOS() ), + 'gadgetDir': install.GetGadgetDir( VIMSPECTOR_HOME ), 'file': current_file, } @@ -529,7 +547,6 @@ class DebugSession( object ): stack_trace_window = vim.current.window one_third = int( vim.eval( 'winheight( 0 )' ) ) / 3 self._stackTraceView = stack_trace.StackTraceView( self, - self._connection, stack_trace_window ) # Watches @@ -547,17 +564,15 @@ class DebugSession( object ): with utils.LetCurrentWindow( stack_trace_window ): vim.command( f'{ one_third }wincmd _' ) - self._variablesView = variables.VariablesView( self._connection, - vars_window, + self._variablesView = variables.VariablesView( vars_window, watch_window ) # Output/logging vim.current.window = code_window vim.command( f'rightbelow { settings.Int( "bottombar_height", 10 ) }new' ) output_window = vim.current.window - self._outputView = output.OutputView( self._connection, - output_window, - self._api_prefix ) + self._outputView = output.DAPOutputView( output_window, + self._api_prefix ) # TODO: If/when we support multiple sessions, we'll need some way to # indicate which tab was created and store all the tabs diff --git a/python3/vimspector/developer.py b/python3/vimspector/developer.py index 4945e6a..49e96c3 100644 --- a/python3/vimspector/developer.py +++ b/python3/vimspector/developer.py @@ -23,8 +23,7 @@ from vimspector import install, utils def SetUpDebugpy( wait=False, port=5678 ): sys.path.insert( 1, - os.path.join( install.GetGadgetDir( utils.GetVimspectorBase(), - install.GetOS() ), + os.path.join( install.GetGadgetDir( utils.GetVimspectorBase() ), 'debugpy', 'build', 'lib' ) ) diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py new file mode 100644 index 0000000..a96c9f5 --- /dev/null +++ b/python3/vimspector/gadgets.py @@ -0,0 +1,490 @@ +# 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. + + +from vimspector import installer +import sys +import os + + +GADGETS = { + 'vscode-cpptools': { + 'language': 'c', + 'download': { + 'url': 'https://github.com/Microsoft/vscode-cpptools/releases/download/' + '${version}/${file_name}', + }, + 'do': lambda name, root, gadget: installer.InstallCppTools( name, + root, + gadget ), + 'all': { + 'version': '0.27.0', + "adapters": { + "vscode-cpptools": { + "name": "cppdbg", + "command": [ + "${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7" + ], + "attach": { + "pidProperty": "processId", + "pidSelect": "ask" + }, + "configuration": { + "type": "cppdbg", + "args": [], + "cwd": "${workspaceRoot}", + "environment": [], + } + }, + }, + }, + 'linux': { + 'file_name': 'cpptools-linux.vsix', + 'checksum': + '3695202e1e75a03de18049323b66d868165123f26151f8c974a480eaf0205435', + }, + 'macos': { + 'file_name': 'cpptools-osx.vsix', + 'checksum': + 'cb061e3acd7559a539e5586f8d3f535101c4ec4e8a48195856d1d39380b5cf3c', + }, + 'windows': { + 'file_name': 'cpptools-win32.vsix', + 'checksum': + 'aa294368ed16d48c59e49c8000e146eae5a19ad07b654efed5db8ec93b24229e', + "adapters": { + "vscode-cpptools": { + "name": "cppdbg", + "command": [ + "${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7.exe" + ], + "attach": { + "pidProperty": "processId", + "pidSelect": "ask" + }, + "configuration": { + "type": "cppdbg", + "args": [], + "cwd": "${workspaceRoot}", + "environment": [], + "MIMode": "gdb", + "MIDebuggerPath": "gdb.exe" + } + }, + }, + }, + }, + 'vscode-python': { + 'language': 'python.legacy', + 'enabled': False, + 'download': { + 'url': 'https://github.com/Microsoft/vscode-python/releases/download/' + '${version}/${file_name}', + }, + 'all': { + 'version': '2019.11.50794', + 'file_name': 'ms-python-release.vsix', + 'checksum': + '6a9edf9ecabed14aac424e6007858068204a3638bf3bb4f235bd6035d823acc6', + }, + 'adapters': { + "vscode-python": { + "name": "vscode-python", + "command": [ + "node", + "${gadgetDir}/vscode-python/out/client/debugger/debugAdapter/main.js", + ], + } + }, + }, + 'debugpy': { + 'language': 'python', + 'download': { + 'url': 'https://github.com/microsoft/debugpy/archive/${file_name}' + }, + 'all': { + 'version': '1.0.0b12', + 'file_name': 'v1.0.0b12.zip', + 'checksum': + '210632bba2221fbb841c9785a615258819ceec401d1abdbeb5f2326f12cc72a1' + }, + 'do': lambda name, root, gadget: installer.InstallDebugpy( name, + root, + gadget ), + 'adapters': { + 'debugpy': { + "command": [ + sys.executable, # TODO: Will this work from within Vim ? + "${gadgetDir}/debugpy/build/lib/debugpy/adapter" + ], + "name": "debugpy", + "configuration": { + "python": sys.executable, # TODO: Will this work from within Vim ? + # Don't debug into subprocesses, as this leads to problems (vimspector + # doesn't support the custom messages) + # https://github.com/puremourning/vimspector/issues/141 + "subProcess": False, + } + } + }, + }, + 'vscode-java-debug': { + 'language': 'java', + 'enabled': False, + 'download': { + 'url': 'https://github.com/microsoft/vscode-java-debug/releases/download/' + '${version}/${file_name}', + }, + 'all': { + 'version': '0.26.0', + 'file_name': 'vscjava.vscode-java-debug-0.26.0.vsix', + 'checksum': + 'de49116ff3a3c941dad0c36d9af59baa62cd931e808a2ab392056cbb235ad5ef', + }, + 'adapters': { + "vscode-java": { + "name": "vscode-java", + "port": "${DAPPort}", + } + }, + }, + 'java-language-server': { + 'language': 'javac', + 'enabled': False, + 'download': { + 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' + 'publishers/georgewfraser/vsextensions/vscode-javac/${version}/' + 'vspackage', + 'target': 'georgewfraser.vscode-javac-0.2.31.vsix.gz', + 'format': 'zip.gz', + }, + 'all': { + 'version': '0.2.31', + 'file_name': 'georgewfraser.vscode-javac-0.2.31.vsix.gz', + 'checksum': + '5b0248ec1198d3ece9a9c6b9433b30c22e308f0ae6e4c7bd09cd943c454e3e1d', + }, + 'adapters': { + "vscode-javac": { + "name": "vscode-javac", + "type": "vscode-javac", + "command": [ + "${gadgetDir}/java-language-server/dist/debug_adapter_mac.sh" + ], + "attach": { + "pidSelect": "none" + } + } + }, + }, + 'tclpro': { + 'language': 'tcl', + 'repo': { + 'url': 'https://github.com/puremourning/TclProDebug', + 'ref': 'master' + }, + 'do': lambda name, root, gadget: installer.InstallTclProDebug( name, + root, + gadget ), + 'adapters': { + "tclpro": { + "name": "tclpro", + "type": "tclpro", + "command": [ + "${gadgetDir}/tclpro/bin/debugadapter" + ], + "attach": { + "pidSelect": "none" + }, + "configuration": { + "target": "${file}", + "args": [ "*${args}" ], + "tclsh": "tclsh", + "cwd": "${workspaceRoot}", + "extensionDirs": [ + "${workspaceRoot}/.tclpro/extensions", + "${HOME}/.tclpro/extensions", + ] + } + } + }, + }, + 'netcoredbg': { + 'language': 'csharp', + 'enabled': False, + 'download': { + 'url': 'https://github.com/Samsung/netcoredbg/releases/download/latest/' + '${file_name}', + 'format': 'tar', + }, + 'all': { + 'version': 'master' + }, + 'macos': { + 'file_name': 'netcoredbg-osx-master.tar.gz', + 'checksum': + 'c1dc6ed58c3f5b0473cfb4985a96552999360ceb9795e42d9c9be64af054f821', + }, + 'linux': { + 'file_name': 'netcoredbg-linux-master.tar.gz', + 'checksum': '', + }, + 'windows': { + 'file_name': 'netcoredbg-win64-master.zip', + 'checksum': '', + }, + 'do': lambda name, root, gadget: installer.MakeSymlink( + name, + os.path.join( root, 'netcoredbg' ) ), + 'adapters': { + 'netcoredbg': { + "name": "netcoredbg", + "command": [ + "${gadgetDir}/netcoredbg/netcoredbg", + "--interpreter=vscode" + ], + "attach": { + "pidProperty": "processId", + "pidSelect": "ask" + }, + }, + } + }, + 'vscode-mono-debug': { + 'language': 'csharp', + 'enabled': False, + 'download': { + 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' + 'publishers/ms-vscode/vsextensions/mono-debug/${version}/' + 'vspackage', + 'target': 'vscode-mono-debug.vsix.gz', + 'format': 'zip.gz', + }, + 'all': { + 'file_name': 'vscode-mono-debug.vsix', + 'version': '0.15.8', + 'checksum': + '723eb2b621b99d65a24f215cb64b45f5fe694105613a900a03c859a62a810470', + }, + 'adapters': { + 'vscode-mono-debug': { + "name": "mono-debug", + "command": [ + "mono", + "${gadgetDir}/vscode-mono-debug/bin/Release/mono-debug.exe" + ], + "attach": { + "pidSelect": "none" + }, + }, + } + }, + 'vscode-bash-debug': { + 'language': 'bash', + 'download': { + 'url': 'https://github.com/rogalmic/vscode-bash-debug/releases/' + 'download/${version}/${file_name}', + }, + 'all': { + 'file_name': 'bash-debug-0.3.7.vsix', + 'version': 'v0.3.7', + 'checksum': + '7b73e5b4604375df8658fb5a72c645c355785a289aa785a986e508342c014bb4', + }, + 'do': lambda name, root, gadget: installer.InstallBashDebug( name, + root, + gadget ), + 'adapters': { + "vscode-bash": { + "name": "bashdb", + "command": [ + "node", + "${gadgetDir}/vscode-bash-debug/out/bashDebug.js" + ], + "variables": { + "BASHDB_HOME": "${gadgetDir}/vscode-bash-debug/bashdb_dir" + }, + "configuration": { + "request": "launch", + "type": "bashdb", + "program": "${file}", + "args": [], + "env": {}, + "pathBash": "bash", + "pathBashdb": "${BASHDB_HOME}/bashdb", + "pathBashdbLib": "${BASHDB_HOME}", + "pathCat": "cat", + "pathMkfifo": "mkfifo", + "pathPkill": "pkill", + "cwd": "${workspaceRoot}", + "terminalKind": "integrated", + } + } + } + }, + 'vscode-go': { + 'language': 'go', + 'download': { + 'url': 'https://github.com/microsoft/vscode-go/releases/download/' + '${version}/${file_name}' + }, + 'all': { + 'version': '0.11.4', + 'file_name': 'Go-0.11.4.vsix', + 'checksum': + 'ff7d7b944da5448974cb3a0086f4a2fd48e2086742d9c013d6964283d416027e' + }, + 'adapters': { + 'vscode-go': { + 'name': 'delve', + 'command': [ + 'node', + '${gadgetDir}/vscode-go/out/src/debugAdapter/goDebug.js' + ], + }, + }, + }, + 'vscode-php-debug': { + 'language': 'php', + 'enabled': False, + 'download': { + 'url': + 'https://github.com/felixfbecker/vscode-php-debug/releases/download/' + '${version}/${file_name}', + }, + 'all': { + 'version': 'v1.13.0', + 'file_name': 'php-debug.vsix', + 'checksum': + '8a51e593458fd14623c1c89ebab87347b087d67087717f18bcf77bb788052718', + }, + 'adapters': { + 'vscode-php-debug': { + 'name': "php-debug", + 'command': [ + 'node', + "${gadgetDir}/vscode-php-debug/out/phpDebug.js", + ] + } + } + }, + 'vscode-node-debug2': { + 'language': 'node', + 'enabled': False, + 'repo': { + 'url': 'https://github.com/microsoft/vscode-node-debug2', + 'ref': 'v1.42.0', + }, + 'do': lambda name, root, gadget: installer.InstallNodeDebug( name, + root, + gadget ), + 'adapters': { + 'vscode-node': { + 'name': 'node2', + 'type': 'node2', + 'command': [ + 'node', + '${gadgetDir}/vscode-node-debug2/out/src/nodeDebug.js' + ] + }, + }, + }, + 'debugger-for-chrome': { + 'language': 'chrome', + 'enabled': False, + 'download': { + 'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/' + 'publishers/msjsdiag/vsextensions/' + 'debugger-for-chrome/${version}/vspackage', + 'target': 'msjsdiag.debugger-for-chrome-4.12.0.vsix.gz', + 'format': 'zip.gz', + }, + 'all': { + 'version': '4.12.0', + 'file_name': 'msjsdiag.debugger-for-chrome-4.12.0.vsix', + 'checksum': + '0df2fe96d059a002ebb0936b0003e6569e5a5c35260dc3791e1657d27d82ccf5' + }, + 'adapters': { + 'chrome': { + 'name': 'debugger-for-chrome', + 'type': 'chrome', + 'command': [ + 'node', + '${gadgetDir}/debugger-for-chrome/out/src/chromeDebug.js' + ], + }, + }, + }, + 'CodeLLDB': { + 'language': 'rust', + 'enabled': False, + 'download': { + 'url': 'https://github.com/vadimcn/vscode-lldb/releases/download/' + '${version}/${file_name}', + }, + 'all': { + 'version': 'v1.5.3', + }, + 'macos': { + 'file_name': 'codelldb-x86_64-darwin.vsix', + 'checksum': + '7505bc1cdfcfd1cb981e2996aec62d63577440709bac31dcadb41a3b4b44631a', + 'make_executable': [ + 'adapter/codelldb', + 'lldb/bin/debugserver', + 'lldb/bin/lldb', + 'lldb/bin/lldb-argdumper', + ], + }, + 'linux': { + 'file_name': 'codelldb-x86_64-linux.vsix', + 'checksum': + 'ce7efc3e94d775368e5942a02bf5c326b6809a0b4c389f79ffa6a8f6f6b72139', + 'make_executable': [ + 'adapter/codelldb', + 'lldb/bin/lldb', + 'lldb/bin/lldb-server', + 'lldb/bin/lldb-argdumper', + ], + }, + 'windows': { + 'file_name': 'codelldb-x86_64-windows.vsix', + 'checksum': + '', + 'make_executable': [] + }, + 'adapters': { + 'CodeLLDB': { + 'name': 'CodeLLDB', + 'type': 'CodeLLDB', + "command": [ + "${gadgetDir}/CodeLLDB/adapter/codelldb", + "--port", "${unusedLocalPort}" + ], + "port": "${unusedLocalPort}", + "configuration": { + "type": "lldb", + "name": "lldb", + "cargo": {}, + "args": [], + "cwd": "${workspaceRoot}", + "env": {}, + "terminal": "integrated", + } + }, + }, + }, +} diff --git a/python3/vimspector/install.py b/python3/vimspector/install.py index 4726a5b..7dc0897 100644 --- a/python3/vimspector/install.py +++ b/python3/vimspector/install.py @@ -38,18 +38,16 @@ def MakeInstallDirs( vimspector_base ): mkdirs( GetConfigDirForFiletype( vimspector_base, '_all' ) ) -def GetGadgetDir( vimspector_base, OS ): - return os.path.join( os.path.abspath( vimspector_base ), 'gadgets', OS ) +def GetGadgetDir( vimspector_base ): + return os.path.join( os.path.abspath( vimspector_base ), 'gadgets', GetOS() ) def GetGadgetConfigFile( vimspector_base ): - return os.path.join( GetGadgetDir( vimspector_base, GetOS() ), - '.gadgets.json' ) + return os.path.join( GetGadgetDir( vimspector_base ), '.gadgets.json' ) def GetGadgetConfigDir( vimspector_base ): - return os.path.join( GetGadgetDir( vimspector_base, GetOS() ), - '.gadgets.d' ) + return os.path.join( GetGadgetDir( vimspector_base ), '.gadgets.d' ) def GetConfigDirForFiletype( vimspector_base, filetype ): diff --git a/python3/vimspector/installer.py b/python3/vimspector/installer.py index 1afea27..e610fe4 100644 --- a/python3/vimspector/installer.py +++ b/python3/vimspector/installer.py @@ -16,21 +16,397 @@ # limitations under the License. from urllib import request -import io import contextlib -import zipfile -import gzip -import shutil -import tarfile -import hashlib -import time -import ssl -import subprocess import functools +import gzip +import hashlib +import io import os +import shutil +import ssl +import string +import subprocess import sys +import tarfile +import time +import traceback +import zipfile +import json -from vimspector import install +from vimspector import install, gadgets + +OUTPUT_VIEW = None + + +class Options: + vimspector_base = None + no_check_certificate = False + quiet = False + + +options = Options() + + +def Configure( **kwargs ): + for k, v in kwargs.items(): + setattr( options, k, v ) + + +def Print( *args, **kwargs ): + if not options.quiet: + print( *args, **kwargs ) + + +def CheckCall( *args, **kwargs ): + if options.quiet: + out = subprocess.PIPE + else: + out = sys.stdout + + kwargs[ 'stdout' ] = out + kwargs[ 'stderr' ] = subprocess.STDOUT + + subprocess.check_call( *args, **kwargs ) + + +def PathToAnyWorkingPython3(): + # We can't rely on sys.executable because it's usually 'vim' (fixme, not with + # neovim?) + paths = os.environ[ 'PATH' ].split( os.pathsep ) + + if install.GetOS() == 'windows': + paths.insert( 0, os.getcwd() ) + candidates = [ os.path.join( sys.exec_prefix, 'python.exe' ), + 'python.exe' ] + else: + candidates = [ os.path.join( sys.exec_prefix, 'bin', 'python3' ), + 'python3', + 'python' ] + + for candidate in candidates: + for path in paths: + filename = os.path.abspath( os.path.join( path, candidate ) ) + if not os.path.isfile( filename ): + continue + if not os.access( filename, os.F_OK | os.X_OK ): + continue + + return filename + + raise RuntimeError( "Unable to find a working python3" ) + + +def RunInstaller( api_prefix, *args, **kwargs ): + from vimspector import utils, output, settings + import vim + + args = GadgetListToInstallerArgs( *args ) + + vimspector_home = utils.GetVimString( vim.vars, 'vimspector_home' ) + vimspector_base_dir = utils.GetVimspectorBase() + + global OUTPUT_VIEW + _ResetInstaller() + + with utils.RestoreCurrentWindow(): + vim.command( f'botright { settings.Int( "bottombar_height", 10 ) }new' ) + win = vim.current.window + OUTPUT_VIEW = output.OutputView( win, api_prefix ) + + cmd = [ + PathToAnyWorkingPython3(), + '-u', + os.path.join( vimspector_home, 'install_gadget.py' ), + '--quiet', + '--update-gadget-config', + ] + if not vimspector_base_dir == vimspector_home: + cmd.extend( [ '--basedir', vimspector_base_dir ] ) + cmd.extend( args ) + + def handler( exit_code ): + if exit_code == 0: + _ResetInstaller() + utils.UserMessage( "Vimspector gadget installation complete!" ) + vim.command( 'silent doautocmd User VimspectorInstallSuccess' ) + if 'then' in kwargs: + kwargs[ 'then' ]() + else: + utils.UserMessage( 'Vimspector gadget installation reported errors', + error = True ) + vim.command( 'silent doautocmd User VimspectorInstallFailed' ) + + + OUTPUT_VIEW.RunJobWithOutput( 'Installer', + cmd, + completion_handler = handler, + syntax = 'vimspector-installer' ) + OUTPUT_VIEW.ShowOutput( 'Installer' ) + + +def RunUpdate( api_prefix, *args ): + from vimspector import utils + Configure( vimspector_base = utils.GetVimspectorBase() ) + + args = list( args ) + current_adapters = ReadAdapters( read_existing = True ) + for adapter_name in current_adapters.keys(): + args.extend( FindGadgetForAdapter( adapter_name ) ) + + if args: + RunInstaller( api_prefix, *args ) + + +def _ResetInstaller(): + global OUTPUT_VIEW + if OUTPUT_VIEW: + OUTPUT_VIEW.Reset() + OUTPUT_VIEW = None + + +def Abort(): + _ResetInstaller() + from vimspector import utils + utils.UserMessage( 'Vimspector installation aborted', + persist = True, + error = True ) + + +def GadgetListToInstallerArgs( *gadget_list ): + installer_args = [] + for name in gadget_list: + if name.startswith( '-' ): + installer_args.append( name ) + continue + + try: + gadget = gadgets.GADGETS[ name ] + except KeyError: + continue + + if not gadget.get( 'enabled', True ): + installer_args.append( f'--force-enable-{ gadget[ "language" ] }' ) + else: + installer_args.append( f'--enable-{ gadget[ "language" ] }' ) + + return installer_args + + +def FindGadgetForAdapter( adapter_name ): + candidates = [] + for name, gadget in gadgets.GADGETS.items(): + v = {} + v.update( gadget.get( 'all', {} ) ) + v.update( gadget.get( install.GetOS(), {} ) ) + + adapters = {} + adapters.update( v.get( 'adapters', {} ) ) + adapters.update( gadget.get( 'adapters', {} ) ) + + if adapter_name in adapters: + candidates.append( name ) + + return candidates + + +def InstallGeneric( name, root, gadget ): + extension = os.path.join( root, 'extension' ) + for f in gadget.get( 'make_executable', [] ): + MakeExecutable( os.path.join( extension, f ) ) + + MakeExtensionSymlink( name, root ) + + +def InstallCppTools( name, root, gadget ): + extension = os.path.join( root, 'extension' ) + + # It's hilarious, but the execute bits aren't set in the vsix. So they + # actually have javascript code which does this. It's just a horrible horrible + # hack that really is not funny. + MakeExecutable( os.path.join( extension, 'debugAdapters', 'OpenDebugAD7' ) ) + with open( os.path.join( extension, 'package.json' ) ) as f: + package = json.load( f ) + runtime_dependencies = package[ 'runtimeDependencies' ] + for dependency in runtime_dependencies: + for binary in dependency.get( 'binaries' ): + file_path = os.path.abspath( os.path.join( extension, binary ) ) + if os.path.exists( file_path ): + MakeExecutable( os.path.join( extension, binary ) ) + + MakeExtensionSymlink( name, root ) + + +def InstallBashDebug( name, root, gadget ): + MakeExecutable( os.path.join( root, + 'extension', + 'bashdb_dir', + 'bashdb' ) ) + MakeExtensionSymlink( name, root ) + + +def InstallDebugpy( name, root, gadget ): + wd = os.getcwd() + root = os.path.join( root, 'debugpy-{}'.format( gadget[ 'version' ] ) ) + os.chdir( root ) + try: + CheckCall( [ sys.executable, 'setup.py', 'build' ] ) + finally: + os.chdir( wd ) + + MakeSymlink( name, root ) + + +def InstallTclProDebug( name, root, gadget ): + configure = [ './configure' ] + + if install.GetOS() == 'macos': + # Apple removed the headers from system frameworks because they are + # determined to make life difficult. And the TCL configure scripts are super + # old so don't know about this. So we do their job for them and try and find + # a tclConfig.sh. + # + # NOTE however that in Apple's infinite wisdom, installing the "headers" in + # the other location is actually broken because the paths in the + # tclConfig.sh are pointing at the _old_ location. You actually do have to + # run the package installation which puts the headers back in order to work. + # This is why the below list is does not contain stuff from + # /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform + # '/Applications/Xcode.app/Contents/Developer/Platforms' + # '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System' + # '/Library/Frameworks/Tcl.framework', + # '/Applications/Xcode.app/Contents/Developer/Platforms' + # '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System' + # '/Library/Frameworks/Tcl.framework/Versions' + # '/Current', + for p in [ '/usr/local/opt/tcl-tk/lib' ]: + if os.path.exists( os.path.join( p, 'tclConfig.sh' ) ): + configure.append( '--with-tcl=' + p ) + break + + + with CurrentWorkingDir( os.path.join( root, 'lib', 'tclparser' ) ): + CheckCall( configure ) + CheckCall( [ 'make' ] ) + + MakeSymlink( name, root ) + + +def InstallNodeDebug( name, root, gadget ): + node_version = subprocess.check_output( [ 'node', '--version' ], + universal_newlines=True ).strip() + Print( "Node.js version: {}".format( node_version ) ) + if list( map( int, node_version[ 1: ].split( '.' ) ) ) >= [ 12, 0, 0 ]: + Print( "Can't install vscode-debug-node2:" ) + Print( "Sorry, you appear to be running node 12 or later. That's not " + "compatible with the build system for this extension, and as far as " + "we know, there isn't a pre-built independent package." ) + Print( "My advice is to install nvm, then do:" ) + Print( " $ nvm install --lts 10" ) + Print( " $ nvm use --lts 10" ) + Print( " $ ./install_gadget.py --enable-node ..." ) + raise RuntimeError( 'Node 10 is required to install node debugger (sadly)' ) + + with CurrentWorkingDir( root ): + CheckCall( [ 'npm', 'install' ] ) + CheckCall( [ 'npm', 'run', 'build' ] ) + MakeSymlink( name, root ) + + +def InstallGagdet( name, gadget, succeeded, failed, all_adapters ): + try: + print( f"Installing {name}..." ) + v = {} + v.update( gadget.get( 'all', {} ) ) + v.update( gadget.get( install.GetOS(), {} ) ) + + if 'download' in gadget: + if 'file_name' not in v: + raise RuntimeError( "Unsupported OS {} for gadget {}".format( + install.GetOS(), + name ) ) + + destination = os.path.join( + install.GetGadgetDir( options.vimspector_base ), + 'download', + name, + v[ 'version' ] ) + + url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( v ) + + file_path = DownloadFileTo( + url, + destination, + file_name = gadget[ 'download' ].get( 'target' ), + checksum = v.get( 'checksum' ), + check_certificate = not options.no_check_certificate ) + + root = os.path.join( destination, 'root' ) + ExtractZipTo( + file_path, + root, + format = gadget[ 'download' ].get( 'format', 'zip' ) ) + elif 'repo' in gadget: + url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( v ) + ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( v ) + + destination = os.path.join( + install.GetGadgetDir( options.vimspector_base ), + 'download', + name ) + CloneRepoTo( url, ref, destination ) + root = destination + + if 'do' in gadget: + gadget[ 'do' ]( name, root, v ) + else: + InstallGeneric( name, root, v ) + + # Allow per-OS adapter overrides. v already did that for us... + all_adapters.update( v.get( 'adapters', {} ) ) + # Add any other "all" adapters + all_adapters.update( gadget.get( 'adapters', {} ) ) + + succeeded.append( name ) + print( f" - Done installing {name}" ) + except Exception as e: + if not options.quiet: + traceback.print_exc() + failed.append( name ) + print( f" - FAILED installing {name}: {e}".format( name, e ) ) + + +def ReadAdapters( read_existing = True ): + all_adapters = {} + if read_existing: + try: + with open( install.GetGadgetConfigFile( options.vimspector_base ), + 'r' ) as f: + all_adapters = json.load( f ).get( 'adapters', {} ) + except OSError: + pass + + # Include "built-in" adapter for multi-session mode + all_adapters.update( { + 'multi-session': { + 'port': '${port}', + 'host': '${host}' + }, + } ) + + return all_adapters + + +def WriteAdapters( all_adapters, to_file=None ): + adapter_config = json.dumps ( { 'adapters': all_adapters }, + indent=2, + sort_keys=True ) + + if to_file: + to_file.write( adapter_config ) + else: + with open( install.GetGadgetConfigFile( options.vimspector_base ), + 'w' ) as f: + f.write( adapter_config ) @contextlib.contextmanager @@ -45,7 +421,7 @@ def CurrentWorkingDir( d ): def MakeExecutable( file_path ): # TODO: import stat and use them by _just_ adding the X bit. - print( 'Making executable: {}'.format( file_path ) ) + Print( 'Making executable: {}'.format( file_path ) ) os.chmod( file_path, 0o755 ) @@ -62,7 +438,7 @@ def WithRetry( f ): return f( *args, **kwargs ) except Exception as e: thrown = e - print( "Failed - {}, will retry in {} seconds".format( e, timeout ) ) + Print( "Failed - {}, will retry in {} seconds".format( e, timeout ) ) time.sleep( timeout ) raise thrown @@ -90,13 +466,13 @@ def DownloadFileTo( url, if os.path.exists( file_path ): if checksum: if ValidateCheckSumSHA256( file_path, checksum ): - print( "Checksum matches for {}, using it".format( file_path ) ) + Print( "Checksum matches for {}, using it".format( file_path ) ) return file_path else: - print( "Checksum doesn't match for {}, removing it".format( + Print( "Checksum doesn't match for {}, removing it".format( file_path ) ) - print( "Removing existing {}".format( file_path ) ) + Print( "Removing existing {}".format( file_path ) ) os.remove( file_path ) r = request.Request( url, headers = { 'User-Agent': 'Vimspector' } ) @@ -123,7 +499,7 @@ def DownloadFileTo( url, GetChecksumSHA254( file_path ), checksum ) ) else: - print( "Checksum for {}: {}".format( file_path, + Print( "Checksum for {}: {}".format( file_path, GetChecksumSHA254( file_path ) ) ) return file_path @@ -141,7 +517,7 @@ def ValidateCheckSumSHA256( file_path, checksum ): def RemoveIfExists( destination ): if os.path.islink( destination ): - print( "Removing file {}".format( destination ) ) + Print( "Removing file {}".format( destination ) ) os.remove( destination ) return @@ -152,21 +528,21 @@ def RemoveIfExists( destination ): return "{}.{}".format( destination, N ) while os.path.isdir( BackupDir() ): - print( "Removing old dir {}".format( BackupDir() ) ) + Print( "Removing old dir {}".format( BackupDir() ) ) try: shutil.rmtree( BackupDir() ) - print ( "OK, removed it" ) + Print ( "OK, removed it" ) break except OSError: - print ( "FAILED" ) + Print ( "FAILED" ) N = N + 1 if os.path.exists( destination ): - print( "Removing dir {}".format( destination ) ) + Print( "Removing dir {}".format( destination ) ) try: shutil.rmtree( destination ) except OSError: - print( "FAILED, moving {} to dir {}".format( destination, BackupDir() ) ) + Print( "FAILED, moving {} to dir {}".format( destination, BackupDir() ) ) os.rename( destination, BackupDir() ) @@ -187,7 +563,7 @@ class ModePreservingZipFile( zipfile.ZipFile ): def ExtractZipTo( file_path, destination, format ): - print( "Extracting {} to {}".format( file_path, destination ) ) + Print( "Extracting {} to {}".format( file_path, destination ) ) RemoveIfExists( destination ) if format == 'zip': @@ -209,17 +585,17 @@ def ExtractZipTo( file_path, destination, format ): # windows-generated tar files os.makedirs( destination ) with CurrentWorkingDir( destination ): - subprocess.check_call( [ 'tar', 'zxvf', file_path ] ) + CheckCall( [ 'tar', 'zxvf', file_path ] ) -def MakeExtensionSymlink( vimspector_base, name, root ): - MakeSymlink( install.GetGadgetDir( vimspector_base, - install.GetOS() ), - name, - os.path.join( root, 'extension' ) ), +def MakeExtensionSymlink( name, root ): + MakeSymlink( name, os.path.join( root, 'extension' ) ), -def MakeSymlink( in_folder, link, pointing_to ): +def MakeSymlink( link, pointing_to, in_folder = None ): + if not in_folder: + in_folder = install.GetGadgetDir( options.vimspector_base ) + RemoveIfExists( os.path.join( in_folder, link ) ) in_folder = os.path.abspath( in_folder ) @@ -233,12 +609,7 @@ def MakeSymlink( in_folder, link, pointing_to ): link_path = os.path.abspath( link_path ) if os.path.isdir( link_path ): os.rmdir( link_path ) - subprocess.check_call( [ 'cmd.exe', - '/c', - 'mklink', - '/J', - link_path, - pointing_to ] ) + CheckCall( [ 'cmd.exe', '/c', 'mklink', '/J', link_path, pointing_to ] ) else: os.symlink( pointing_to_relative, link_path ) @@ -246,13 +617,10 @@ def MakeSymlink( in_folder, link, pointing_to ): def CloneRepoTo( url, ref, destination ): RemoveIfExists( destination ) git_in_repo = [ 'git', '-C', destination ] - subprocess.check_call( [ 'git', 'clone', url, destination ] ) - subprocess.check_call( git_in_repo + [ 'checkout', ref ] ) - subprocess.check_call( git_in_repo + [ 'submodule', 'sync', '--recursive' ] ) - subprocess.check_call( git_in_repo + [ 'submodule', - 'update', - '--init', - '--recursive' ] ) + CheckCall( [ 'git', 'clone', url, destination ] ) + CheckCall( git_in_repo + [ 'checkout', ref ] ) + CheckCall( git_in_repo + [ 'submodule', 'sync', '--recursive' ] ) + CheckCall( git_in_repo + [ 'submodule', 'update', '--init', '--recursive' ] ) def AbortIfSUperUser( force_sudo ): diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index a359448..1d349aa 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -25,7 +25,7 @@ class TabBuffer( object ): self.index = index self.flag = False self.is_job = False - self.job_category = None + self.syntax = None BUFFER_MAP = { @@ -40,18 +40,26 @@ def CategoryToBuffer( category ): return BUFFER_MAP.get( category, category ) +VIEWS = set() + + +def ShowOutputInWindow( win_id, category ): + for view in VIEWS: + if view._window.valid and utils.WindowID( view._window ) == win_id: + view.ShowOutput( category ) + return + + raise ValueError( f'Unable to find output object for win id {win_id}!' ) + + class OutputView( object ): - def __init__( self, connection, window, api_prefix ): + """Container for a 'tabbed' window of buffers that can be used to display + files or the output of commands.""" + def __init__( self, window, api_prefix ): self._window = window - self._connection = connection self._buffers = {} self._api_prefix = api_prefix - - for b in set( BUFFER_MAP.values() ): - self._CreateBuffer( b ) - - self._CreateBuffer( 'Vimspector', file_name = utils.LOG_FILE ) - self._ShowOutput( 'Console' ) + VIEWS.add( self ) def Print( self, categroy, text ): self._Print( 'server', text.splitlines() ) @@ -82,21 +90,15 @@ class OutputView( object ): with utils.RestoreCurrentBuffer( self._window ): self._ShowOutput( category ) - def ConnectionUp( self, connection ): - self._connection = connection - - def ConnectionClosed( self ): - # Don't clear because output is probably still useful - self._connection = None - def Reset( self ): self.Clear() + VIEWS.remove( self ) + def Clear( self ): for category, tab_buffer in self._buffers.items(): if tab_buffer.is_job: - utils.CleanUpCommand( tab_buffer.job_category or category, - self._api_prefix ) + utils.CleanUpCommand( category, self._api_prefix ) utils.CleanUpHiddenBuffer( tab_buffer.buf ) # FIXME: nunmenu the WinBar ? @@ -125,6 +127,115 @@ class OutputView( object ): self._ToggleFlag( category, False ) self._ShowOutput( category ) + def _ToggleFlag( self, category, flag ): + if self._buffers[ category ].flag != flag: + self._buffers[ category ].flag = flag + + if self._window.valid: + with utils.LetCurrentWindow( self._window ): + self._RenderWinBar( category ) + + + def RunJobWithOutput( self, category, cmd, **kwargs ): + self._CreateBuffer( category, cmd = cmd, **kwargs ) + + + def _CreateBuffer( self, + category, + file_name = None, + cmd = None, + completion_handler = None, + syntax = None ): + if file_name is not None: + assert cmd is None + if install.GetOS() == "windows": + # FIXME: Can't display fiels in windows (yet?) + return + + cmd = [ 'tail', '-F', '-n', '+1', '--', file_name ] + + if cmd is not None: + out = utils.SetUpCommandBuffer( + cmd, + category, + self._api_prefix, + completion_handler = completion_handler ) + self._buffers[ category ] = TabBuffer( out, len( self._buffers ) ) + self._buffers[ category ].is_job = True + self._RenderWinBar( category ) + else: + if category == 'Console': + name = 'vimspector.Console' + else: + name = 'vimspector.Output:{0}'.format( category ) + + tab_buffer = TabBuffer( utils.NewEmptyBuffer(), len( self._buffers ) ) + self._buffers[ category ] = tab_buffer + + if category == 'Console': + utils.SetUpPromptBuffer( tab_buffer.buf, + name, + '> ', + 'vimspector#EvaluateConsole' ) + else: + utils.SetUpHiddenBuffer( tab_buffer.buf, name ) + + self._RenderWinBar( category ) + + self._buffers[ category ].syntax = utils.SetSyntax( + self._buffers[ category ].syntax, + syntax, + self._buffers[ category ].buf ) + + + def _RenderWinBar( self, category ): + if not self._window.valid: + return + + with utils.LetCurrentWindow( self._window ): + tab_buffer = self._buffers[ category ] + + try: + if tab_buffer.flag: + vim.command( 'nunmenu WinBar.{}'.format( utils.Escape( category ) ) ) + else: + vim.command( 'nunmenu WinBar.{}*'.format( utils.Escape( category ) ) ) + except vim.error as e: + # E329 means the menu doesn't exist; ignore that. + if 'E329' not in str( e ): + raise + + vim.command( + "nnoremenu 1.{0} WinBar.{1}{2} " + ":call vimspector#ShowOutputInWindow( {3}, '{1}' )".format( + tab_buffer.index, + utils.Escape( category ), + '*' if tab_buffer.flag else '', + utils.WindowID( self._window ) ) ) + + def GetCategories( self ): + return list( self._buffers.keys() ) + + +class DAPOutputView( OutputView ): + """Specialised OutputView which adds the DAP Console (REPL)""" + def __init__( self, *args ): + super().__init__( *args ) + + self._connection = None + for b in set( BUFFER_MAP.values() ): + self._CreateBuffer( b ) + + self._CreateBuffer( 'Vimspector', file_name = utils.LOG_FILE ) + self._ShowOutput( 'Console' ) + + def ConnectionUp( self, connection ): + self._connection = connection + + def ConnectionClosed( self ): + # Don't clear because output is probably still useful + self._connection = None + def Evaluate( self, frame, expression ): self._Print( 'Console', [ 'Evaluating: ' + expression ] ) @@ -146,82 +257,3 @@ class OutputView( object ): request[ 'arguments' ][ 'frameId' ] = frame[ 'id' ] self._connection.DoRequest( print_result, request ) - - def _ToggleFlag( self, category, flag ): - if self._buffers[ category ].flag != flag: - self._buffers[ category ].flag = flag - - if self._window.valid: - with utils.LetCurrentWindow( self._window ): - self._RenderWinBar( category ) - - - def RunJobWithOutput( self, category, cmd ): - self._CreateBuffer( category, cmd = cmd ) - - - def _CreateBuffer( self, category, file_name = None, cmd = None ): - win = self._window - if not win.valid: - # We need to borrow the current window - win = vim.current.window - - with utils.LetCurrentWindow( win ): - with utils.RestoreCurrentBuffer( win ): - - if file_name is not None: - assert cmd is None - if install.GetOS() == "windows": - # FIXME: Can't display fiels in windows (yet?) - return - - cmd = [ 'tail', '-F', '-n', '+1', '--', file_name ] - - if cmd is not None: - out, err = utils.SetUpCommandBuffer( cmd, category, self._api_prefix ) - self._buffers[ category + '-out' ] = TabBuffer( out, - len( self._buffers ) ) - self._buffers[ category + '-out' ].is_job = True - self._buffers[ category + '-out' ].job_category = category - self._buffers[ category + '-err' ] = TabBuffer( err, - len( self._buffers ) ) - self._buffers[ category + '-err' ].is_job = False - self._RenderWinBar( category + '-out' ) - self._RenderWinBar( category + '-err' ) - else: - vim.command( 'enew' ) - tab_buffer = TabBuffer( vim.current.buffer, len( self._buffers ) ) - self._buffers[ category ] = tab_buffer - if category == 'Console': - utils.SetUpPromptBuffer( tab_buffer.buf, - 'vimspector.Console', - '> ', - 'vimspector#EvaluateConsole' ) - else: - utils.SetUpHiddenBuffer( - tab_buffer.buf, - 'vimspector.Output:{0}'.format( category ) ) - - self._RenderWinBar( category ) - - def _RenderWinBar( self, category ): - tab_buffer = self._buffers[ category ] - - try: - if tab_buffer.flag: - vim.command( 'nunmenu WinBar.{}'.format( utils.Escape( category ) ) ) - else: - vim.command( 'nunmenu WinBar.{}*'.format( utils.Escape( category ) ) ) - except vim.error as e: - # E329 means the menu doesn't exist; ignore that. - if 'E329' not in str( e ): - raise - - vim.command( "nnoremenu 1.{0} WinBar.{1}{2} " - ":call vimspector#ShowOutput( '{1}' )".format( - tab_buffer.index, - utils.Escape( category ), - '*' if tab_buffer.flag else '' ) ) - - def GetCategories( self ): - return list( self._buffers.keys() ) diff --git a/python3/vimspector/stack_trace.py b/python3/vimspector/stack_trace.py index d5ea0b5..6caccc8 100644 --- a/python3/vimspector/stack_trace.py +++ b/python3/vimspector/stack_trace.py @@ -21,13 +21,13 @@ from vimspector import utils class StackTraceView( object ): - def __init__( self, session, connection, win ): + def __init__( self, session, win ): self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) self._buf = win.buffer self._session = session - self._connection = connection + self._connection = None self._current_thread = None self._current_frame = None diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index bfdddcc..ad5bc9a 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -52,6 +52,12 @@ def BufferForFile( file_name ): return vim.buffers[ BufferNumberForFile( file_name ) ] +def NewEmptyBuffer(): + bufnr = int( vim.eval( 'bufadd("")' ) ) + Call( 'bufload', bufnr ) + return vim.buffers[ bufnr ] + + def WindowForBuffer( buf ): for w in vim.current.tabpage.windows: if w.buffer == buf: @@ -71,21 +77,30 @@ def OpenFileInCurrentWindow( file_name ): return vim.buffers[ buffer_number ] -def SetUpCommandBuffer( cmd, name, api_prefix ): - bufs = vim.eval( - 'vimspector#internal#{}job#StartCommandWithLog( {}, "{}" )'.format( - api_prefix, - json.dumps( cmd ), - name ) ) +COMMAND_HANDLERS = {} - if bufs is None: + +def OnCommandWithLogComplete( name, exit_code ): + cb = COMMAND_HANDLERS.get( name ) + if cb: + cb( exit_code ) + + +def SetUpCommandBuffer( cmd, name, api_prefix, completion_handler = None ): + COMMAND_HANDLERS[ name ] = completion_handler + + buf = Call( f'vimspector#internal#{api_prefix}job#StartCommandWithLog', + cmd, + name ) + + if buf is None: raise RuntimeError( "Unable to start job {}: {}".format( cmd, name ) ) - elif not all( int( b ) > 0 for b in bufs ): + elif int( buf ) <= 0: raise RuntimeError( "Unable to get all streams for job {}: {}".format( name, cmd ) ) - return [ vim.buffers[ int( b ) ] for b in bufs ] + return vim.buffers[ int( buf ) ] def CleanUpCommand( name, api_prefix ): @@ -184,9 +199,8 @@ def RestoreCurrentWindow(): try: yield finally: - if old_tabpage.valid: + if old_tabpage.valid and old_window.valid: vim.current.tabpage = old_tabpage - if old_window.valid: vim.current.window = old_window @@ -343,6 +357,7 @@ def AskForInput( prompt, default_value = None ): def AppendToBuffer( buf, line_or_lines, modified=False ): + line = 1 try: # After clearing the buffer (using buf[:] = None) there is always a single # empty line in the buffer object and no "is empty" method. @@ -558,7 +573,6 @@ def Call( vimscript_function, *args ): call += 'g:' + arg_name call += ')' - _logger.debug( 'Calling: {}'.format( call ) ) return vim.eval( call ) @@ -605,8 +619,7 @@ def SetSyntax( current_syntax, syntax, *args ): # doesn't actually trigger the Syntax autocommand, and i'm not sure that # 'doautocmd Syntax' is the right solution or not for buf in args: - with AnyWindowForBuffer( buf ): - vim.command( 'set syntax={}'.format( Escape( syntax ) ) ) + Call( 'setbufvar', buf.number, '&syntax', syntax ) return syntax @@ -633,16 +646,26 @@ def HideSplash( api_prefix, splash ): return None +def GetVimString( vim_dict, name, default=None ): + + # FIXME: use 'encoding' ? + try: + value = vim_dict[ name ] + except KeyError: + return default + + if isinstance( value, bytes ): + return value.decode( 'utf-8' ) + return value + + def GetVimspectorBase(): - base = vim.vars.get( 'vimspector_base_dir' ) - if base is None: - return os.path.abspath( os.path.join( os.path.dirname( __file__ ), - '..', - '..' ) ) - elif isinstance( base, bytes ): - return base.decode( 'utf-8' ) - else: - return base + return GetVimString( vim.vars, + 'vimspector_base_dir', + os.path.abspath( + os.path.join( os.path.dirname( __file__ ), + '..', + '..' ) ) ) def GetUnusedLocalPort(): @@ -655,5 +678,7 @@ def GetUnusedLocalPort(): return port -def WindowID( window, tab ): +def WindowID( window, tab=None ): + if tab is None: + tab = window.tabpage return int( Call( 'win_getid', window.number, tab.number ) ) diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index f6df06c..ffd641d 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -124,11 +124,11 @@ class View: class VariablesView( object ): - def __init__( self, connection, variables_win, watches_win ): + def __init__( self, variables_win, watches_win ): self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) - self._connection = connection + self._connection = None self._current_syntax = '' # Set up the "Variables" buffer in the variables_win diff --git a/run_tests b/run_tests index c03c7b9..44e09f3 100755 --- a/run_tests +++ b/run_tests @@ -2,6 +2,7 @@ BASEDIR=$(dirname $0) INSTALL=0 +UPDATE=0 RUN_VIM="vim -N --clean --not-a-term" RUN_TEST="${RUN_VIM} -S lib/run_test.vim" BASEDIR_CMD='py3 pass' @@ -25,6 +26,15 @@ while [ -n "$1" ]; do INSTALL=1 shift ;; + "--install-method") + shift + INSTALL=$1 + shift + ;; + "--update") + UPDATE=1 + shift + ;; "--report") shift VIMSPECTOR_TEST_STDOUT=$1 @@ -71,10 +81,36 @@ if [ "${out_fd}" = "1" ]; then exec 3>&1 fi -if [ $INSTALL = 1 ]; then - python3 $(dirname $0)/install_gadget.py --basedir ${BASEDIR} --all +if [ "$INSTALL" = "1" ] || [ "$INSTALL" = "script" ]; then + if ! python3 $(dirname $0)/install_gadget.py --basedir ${BASEDIR} --all; then + echo "Script installation reported errors" >&2 + exit 1 + fi fi +if [ "$INSTALL" = "1" ] || [ "$INSTALL" = "vim" ]; then + if ! $RUN_VIM -u $(dirname $0)/tests/vimrc \ + --cmd "${BASEDIR_CMD}" \ + -c 'autocmd User VimspectorInstallSuccess qa!' \ + -c 'autocmd User VimspectorInstallFailed cquit!' \ + -c "VimspectorInstall --all"; then + echo "Vim installation reported errors" >&2 + exit 1 + fi +fi + +if [ "$UPDATE" = "1" ]; then + if ! $RUN_VIM -u $(dirname $0)/tests/vimrc \ + --cmd "${BASEDIR_CMD}" \ + -c 'autocmd User VimspectorInstallSuccess qa!' \ + -c 'autocmd User VimspectorInstallFailed cquit!' \ + -c "VimspectorUpdate"; then + echo "Vim update reported errors" >&2 + exit 1 + fi +fi + + if [ -z "$VIMSPECTOR_MIMODE" ]; then if which lldb >/dev/null 2>&1; then export VIMSPECTOR_MIMODE=lldb diff --git a/syntax/vimspector-installer.vim b/syntax/vimspector-installer.vim new file mode 100644 index 0000000..98ea48b --- /dev/null +++ b/syntax/vimspector-installer.vim @@ -0,0 +1,13 @@ +if exists( 'b:current_syntax' ) + finish +endif + +let b:current_syntax = 'vimspector-installer' + +syn keyword VimspectorInstalling Installing +syn keyword VimspectorDone Done +syn keyword VimspectorError Failed FAILED + +hi default link VimspectorInstalling Constant +hi default link VimspectorDone DiffAdd +hi default link VimspectorError WarningMsg