diff --git a/README.md b/README.md index 15e9872..9813538 100644 --- a/README.md +++ b/README.md @@ -6,69 +6,74 @@ For a tutorial and usage overview, take a look at the [![Build Status](https://dev.azure.com/puremouron/Vimspector/_apis/build/status/puremourning.vimspector?branchName=master)](https://dev.azure.com/puremouron/Vimspector/_build/latest?definitionId=1&branchName=master) [![Gitter](https://badges.gitter.im/vimspector/Lobby.svg)](https://gitter.im/vimspector/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - * [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) - * [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) - * [The gadget directory](#the-gadget-directory) - * [Trying it out](#trying-it-out) - * [About](#about) - * [Background](#background) - * [Status](#status) - * [Experimental](#experimental) - * [Mappings](#mappings) - * [Visual Studio / VSCode](#visual-studio--vscode) - * [Human Mode](#human-mode) - * [Usage](#usage) - * [Launch and attach by PID:](#launch-and-attach-by-pid) - * [Launch with options](#launch-with-options) - * [Breakpoints](#breakpoints) - * [Exception breakpoints](#exception-breakpoints) - * [Clear breakpoints](#clear-breakpoints) - * [Stepping](#stepping) - * [Variables and scopes](#variables-and-scopes) - * [Watches](#watches) - * [Stack Traces](#stack-traces) - * [Program Output](#program-output) - * [Console](#console) - * [Closing debugger](#closing-debugger) - * [Debug adapter configuration](#debug-adapter-configuration) - * [C, C , Rust, etc.](#c-c-rust-etc) - * [Remote debugging](#remote-debugging) - * [Remote launch and attach](#remote-launch-and-attach) - * [Python](#python) - * [Remote Debugging](#remote-debugging-1) - * [Remote launch and attach](#remote-launch-and-attach-1) - * [Legacy: vscode-python](#legacy-vscode-python) - * [TCL](#tcl) - * [C♯](#c) - * [Go](#go) - * [PHP](#php) - * [Debug web application](#debug-web-application) - * [Debug cli application](#debug-cli-application) - * [JavaScript, TypeScript, etc.](#javascript-typescript-etc) - * [Java](#java) - * [Usage with YouCompleteMe](#usage-with-youcompleteme) - * [Other LSP clients](#other-lsp-clients) - * [Rust](#rust) - * [Other servers](#other-servers) - * [Customisation](#customisation) - * [Changing the default signs](#changing-the-default-signs) - * [FAQ](#faq) - * [Motivation](#motivation) - * [License](#license) + * [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) + * [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) + * [The gadget directory](#the-gadget-directory) + * [Trying it out](#trying-it-out) + * [About](#about) + * [Background](#background) + * [Status](#status) + * [Experimental](#experimental) + * [Mappings](#mappings) + * [Visual Studio / VSCode](#visual-studio--vscode) + * [Human Mode](#human-mode) + * [Usage](#usage) + * [Launch and attach by PID:](#launch-and-attach-by-pid) + * [Launch with options](#launch-with-options) + * [Debug configuration selection](#debug-configuration-selection) + * [Breakpoints](#breakpoints) + * [Exception breakpoints](#exception-breakpoints) + * [Clear breakpoints](#clear-breakpoints) + * [Stepping](#stepping) + * [Variables and scopes](#variables-and-scopes) + * [Watches](#watches) + * [Stack Traces](#stack-traces) + * [Program Output](#program-output) + * [Console](#console) + * [Closing debugger](#closing-debugger) + * [Debug adapter configuration](#debug-adapter-configuration) + * [C, C , Rust, etc.](#c-c-rust-etc) + * [Remote debugging](#remote-debugging) + * [Remote launch and attach](#remote-launch-and-attach) + * [Python](#python) + * [Remote Debugging](#remote-debugging-1) + * [Remote launch and attach](#remote-launch-and-attach-1) + * [Legacy: vscode-python](#legacy-vscode-python) + * [TCL](#tcl) + * [C♯](#c) + * [Go](#go) + * [PHP](#php) + * [Debug web application](#debug-web-application) + * [Debug cli application](#debug-cli-application) + * [JavaScript, TypeScript, etc.](#javascript-typescript-etc) + * [Java](#java) + * [Usage with YouCompleteMe](#usage-with-youcompleteme) + * [Other LSP clients](#other-lsp-clients) + * [Rust](#rust) + * [Other servers](#other-servers) + * [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) + * [Advanced UI customisation](#advanced-ui-customisation) + * [Example](#example) + * [FAQ](#faq) + * [Motivation](#motivation) + * [License](#license) - + @@ -601,6 +606,19 @@ See [our YouCompleteMe integration guide](#usage-with-youcompleteme) for another example where it can be used to specify the port to connect the [java debugger](#java---partially-supported) +### Debug configuration selection + +Vimspector uses the following logic to choose a configuration to launch: + +1. If a configuration was specified in the launch options (as above), use that. +2. Otherwise if there's only one configuration and it doesn't have `autoselect` + set to `false`, use that. +3. Otherwise if there's exactly one configuration with `default` set to `true` + and without `autoselect` set to `false`, use that. +4. Otherwise, prompt the user to select a configuration. + +See [the reference guide][vimspector-ref-config-selection] for details. + ## Breakpoints * Use `vimspector#ToggleBreakpoint([ { 'condition': '' } ])` @@ -659,6 +677,8 @@ You can configure your choices in the `.vimspector.json`. See ![locals window](https://puremourning.github.io/vimspector-web/img/vimspector-locals-window.png) +Scopes and variables are represented by the buffer `vimspector.Variables`. + ## Watches The watches window is a prompt buffer, where that's available. Enter insert mode @@ -673,6 +693,8 @@ to add a new watch expression. ![watch window](https://puremourning.github.io/vimspector-web/img/vimspector-watch-window.png) +The watches are represented by the buffer `vimspector.StackTrace`. + ## Stack Traces * In the threads window, use `` to expand/collapse. @@ -680,6 +702,8 @@ to add a new watch expression. ![stack trace](https://puremourning.github.io/vimspector-web/img/vimspector-callstack-window.png) +The stack trace is represented by the buffer `vimspector.StackTrace`. + ## Program Output * In the outputs window use the WinBar to select the output channel. @@ -690,6 +714,10 @@ to add a new watch expression. ![output window](https://puremourning.github.io/vimspector-web/img/vimspector-output-window.png) +If the output window is closed, a new one can be opened with +`:VimspectorShowOutput ` (use tab-completion - `wildmenu` to see the +options). + ### Console The console window is a prompt buffer, where that's available, and can be used @@ -704,6 +732,9 @@ adapters. NOTE: See also [Watches](#watches) above. +If the output window is closed, a new one can be opened with +`:VimspectorShowOutput Console`. + ## Closing debugger To close the debugger, use: @@ -1284,6 +1315,145 @@ sign define vimspectorBPDisabled text=🔵 texthl=Normal sign define vimspectorPC text=🔶 texthl=SpellBad ``` +## Changing the default window sizes + +> ***Please Note***: This cusomiation API is ***unstable***, meaning that it may +change at any time. I will endeavour to reduce the impact of this and annouce +changes in Gitter. + +The following options control the default sizes of the UI windows (all of them +are numbers) + +- `g:vimspector_sidebar_width` (default: 50 columns): + The width in columns of the left utility windows (variables, watches, stack + trace) +- `g:vimspector_bottombar_height` (default 10 lines): + The height in rows of the output window below the code window. + +Example: + +```viml +let g:vimspector_sidebar_width = 75 +let g:vimspector_bottombar_height = 15 +``` + +## Changing the terminal size + +The terminal is typically created as a vertical split to the righ of the code +window, and that window is re-used for subsequent terminal buffers. +The following control the sizing of the terminal window used +for debuggee input/output when using Vim's built-in terminal. + +- `g:vimspector_code_minwidth` (default: 82 columns): + Minimum number of columns to try and maintain for the code window when + splitting to create the terminal window. +- `g:vimspector_terminal_maxwidth` (default: 80 columns): + Maximum number of columns to use for the terminal. +- `g:vimspector_terminal_minwidth` (default: 10 columns): + Minimum number of columns to use when it is not possible to fit + `g:vimspector_terminal_maxwidth` columns for the terminal. + +That's a lot of options, but essentially we try to make sure that there are at +least `g:vimspector_code_minwidth` columns for the main code window and that the +terminal is no wider than `g:vimspector_terminal_maxwidth` columns. +`g:vimspector_terminal_minwidth` is there to ensure that there's a reasonable +number of columns for the terminal even when there isn't enough horizontal space +to satisfy the other contraints. + +Example: + +```viml +let g:vimspector_code_minwidth = 90 +let g:vimspector_terminal_maxwidth = 75 +let g:vimspector_terminal_minwidth = 20 +``` + +## Advanced UI customisation + +> ***Please Note***: This cusomiation API is ***unstable***, meaning that it may +change at any time. I will endeavour to reduce the impact of this and annouce +changes in Gitter. + +The above customisation of window sizes is limited intentionally to keep things +simple. Vimspector also provides a way for you to customise the UI without +restrictions, by running a `User` autocommand just after creating the UI or +opening the terminal. This requires you to write some vimscript, but allows you +to do things like: + +* Hide a particular window or windows +* Move a particular window or windows +* Resize windows +* Have multiple windows for a particular buffer (say, you want 2 watch windows) +* etc. + +You can essentially do anything you could do manually by writing a little +vimscript code. + +The `User` autocommand is raised with `pattern` set with the following values: + +* `VimspectorUICreated`: Just after setting up the UI for a debug session +* `VimspectorTerminalOpened`: Just after opening the terminal window for program + input/output. + +The following global variable is set up for you to get access to the UI +elements: `g:vimspector_session_windows`. This is a `dict` with the following +keys: + +* `g:vimspector_session_windows.tagpage`: The tab page for the session +* `g:vimspector_session_windows.variables`: Window ID of the variables window, + containing the `vimspector.Variables` buffer. +* `g:vimspector_session_windows.watches`: Window ID of the watches window, + containng the `vimspector.Watches` buffer. +* `g:vimspector_session_windows.stack_trace`: Window ID of the stack trade + window containing the `vimspector.StackTrace` buffer. +* `g:vimspector_session_windows.code`: Window ID of the code window. +* `g:vimspector_session_windows.output`: Window ID of the output window. + +In addition, the following key is added when triggering the +`VimspectorTerminalOpened` event: + +* `g:vimspector_session_windows.terminal`: Window ID of the terminal window + +## Example + +There is some example code in `support/custom_ui_vimrc` showing how you can use +the window IDs to modify various aspects of the UI using some basic vim +commands, primarily `win_gotoid` funciton and the `wincmd` ex command. + +To try this out `vim -Nu support/custom_ui_vimrc `. + +Here's a rather smaller example. A simple way to use this is to drop it into a +file named `my_vimspector_ui.vim` in `~/.vim/plugin` (or paste into your +`vimrc`): + +```viml +" Set the basic sizes +let g:vimspector_sidebar_width = 80 +let g:vimspector_code_minwidth = 85 +let g:vimspector_terminal_minwidth = 75 + +function! s:CustomiseUI() + " Customise the basic UI... + + " Close the output window + call win_gotoid( g:vimspector_session_windows.output ) + q +endfunction + +function s:SetUpTerminal() + " Customise the terminal window size/position + " For some reasons terminal buffers in Neovim have line numbers + call win_gotoid( g:vimspector_session_windows.terminal ) + set norelativenumber nonumber +endfunction + +augroup MyVimspectorUICustomistaion + autocmd! + autocmd User VimspectorUICreated call s:CustomiseUI() + autocmd User VimspectorTerminalOpened call s:SetUpTerminal() +augroup END +``` + # FAQ 1. Q: Does it work? A: Yeah. It's a bit unpolished. @@ -1366,6 +1536,7 @@ Copyright © 2018 Ben Jackson [vimspector-ref]: https://puremourning.github.io/vimspector/configuration.html [vimspector-ref-var]: https://puremourning.github.io/vimspector/configuration.html#replacements-and-variables [vimspector-ref-exception]: https://puremourning.github.io/vimspector/configuration.html#exception-breakpoints +[vimspector-ref-config-selection]: https://puremourning.github.io/vimspector/configuration.html#configuration-selection [debugpy]: https://github.com/microsoft/debugpy [YouCompleteMe]: https://github.com/ycm-core/YouCompleteMe#java-semantic-completion [remote-debugging]: https://puremourning.github.io/vimspector/configuration.html#remote-debugging-support diff --git a/autoload/vimspector.vim b/autoload/vimspector.vim index f37f27e..a7c6e4b 100644 --- a/autoload/vimspector.vim +++ b/autoload/vimspector.vim @@ -137,14 +137,16 @@ function! vimspector#ListBreakpoints() abort endfunction function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort - let buffers = py3eval( '_vimspector_session.GetOutputBuffers()' ) + let buffers = py3eval( '_vimspector_session.GetOutputBuffers() ' + \ . ' if _vimspector_session else []' ) return join( buffers, "\n" ) endfunction function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort return join( py3eval( '_vimspector_session.GetCompletionsSync( ' \.' vim.eval( "a:CmdLine" ),' - \.' int( vim.eval( "a:CursorPos" ) ) )' ), + \.' int( vim.eval( "a:CursorPos" ) ) )' + \. ' if _vimspector_session else []' ), \ "\n" ) endfunction diff --git a/autoload/vimspector/internal/neoterm.vim b/autoload/vimspector/internal/neoterm.vim index c6e798e..3f5bf66 100644 --- a/autoload/vimspector/internal/neoterm.vim +++ b/autoload/vimspector/internal/neoterm.vim @@ -48,7 +48,7 @@ endfunction function! vimspector#internal#neoterm#Start( cmd, opts ) abort " Prepare current buffer to be turned into a term if curwin is not set if ! get( a:opts, 'curwin', 0 ) - let mods = '' + let mods = 'rightbelow ' if get( a:opts, 'vertical', 0 ) let mods .= 'vertical ' let mods .= get( a:opts, 'term_cols', '' ) diff --git a/autoload/vimspector/internal/term.vim b/autoload/vimspector/internal/term.vim index 5a570fc..aa394ba 100644 --- a/autoload/vimspector/internal/term.vim +++ b/autoload/vimspector/internal/term.vim @@ -20,7 +20,7 @@ set cpoptions&vim " }}} function! vimspector#internal#term#Start( cmd, opts ) abort - return term_start( a:cmd, a:opts ) + rightbelow return term_start( a:cmd, a:opts ) endfunction function! vimspector#internal#term#IsFinished( bufno ) abort diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5dc944a..5fb2b08 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,11 +58,10 @@ stages: - bash: | eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv) export GOPATH=$HOME/go - ./run_tests + ./run_tests --report messages --quiet displayName: 'Run the tests' env: VIMSPECTOR_MIMODE: gdb - VIMSPECTOR_TEST_STDOUT: true - bash: ./make_package linux $(Build.SourceVersion) displayName: 'Package' @@ -103,11 +102,10 @@ stages: - bash: vim --version displayName: 'Print vim version information' - - bash: ./run_tests + - bash: ./run_tests --report messages --quiet displayName: 'Run the tests' env: VIMSPECTOR_MIMODE: lldb - VIMSPECTOR_TEST_STDOUT: true - bash: ./make_package macos $(Build.SourceVersion) displayName: 'Package' diff --git a/compiler/vimspector_test.vim b/compiler/vimspector_test.vim index 8ccd734..5264b4b 100644 --- a/compiler/vimspector_test.vim +++ b/compiler/vimspector_test.vim @@ -92,7 +92,7 @@ function! s:RunTestUnderCursor() let l:cwd = getcwd() execute 'lcd ' . s:root_dir try - execute s:make_cmd . ' ' + execute s:make_cmd . ' --report messages ' \ . get( g:, 'vimspector_test_args', '' ) . ' ' \ . l:test_arg finally @@ -105,7 +105,7 @@ function! s:RunTest() let l:cwd = getcwd() execute 'lcd ' . s:root_dir try - execute s:make_cmd . ' ' + execute s:make_cmd . ' --report messages ' \ . get( g:, 'vimspector_test_args', '' ) \ . ' %:p:t' finally @@ -118,7 +118,7 @@ function! s:RunAllTests() let l:cwd = getcwd() execute 'lcd ' . s:root_dir try - execute s:make_cmd . ' ' + execute s:make_cmd . ' --report messages ' \ . get( g:, 'vimspector_test_args', '' ) finally execute 'lcd ' . l:cwd diff --git a/dev_requirements.txt b/dev_requirements.txt index e65675c..c7a9ec1 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,4 +1,6 @@ flake8==3.8.3 -vim-vint==0.3.21 flake8-comprehensions==3.2.3 flake8-ycm>= 0.1.0 + +# Use fork of vint which is up to date +git+https://github.com/puremourning/vint diff --git a/docs/configuration.md b/docs/configuration.md index 14f5047..134b049 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,6 +16,9 @@ for Vimspector. * [Files and locations](#files-and-locations) * [Adapter configurations](#adapter-configurations) * [Debug configurations](#debug-configurations) + * [Configuration selection](#configuration-selection) + * [Specifying a default configuration](#specifying-a-default-configuration) + * [Preventing automatic selection](#preventing-automatic-selection) * [Exception breakpionts](#exception-breakpionts) * [Predefined Variables](#predefined-variables) * [Remote Debugging Support](#remote-debugging-support) @@ -25,7 +28,7 @@ for Vimspector. * [Appendix: Configuration file format](#appendix-configuration-file-format) * [Appendix: Editor configuration](#appendix-editor-configuration) - + @@ -321,6 +324,62 @@ typical example looks like this: } ``` +### Configuration selection + +When starting debugging, you can specify which debug configuration to launch +with `call vimspector#LaunchWithSettings( #{ configuration: 'name here' } )`. + +Otherwise, if there's only one configuration found, Vimspector will use that +configuration, unless it contains a key `"autoselect": false`. + +If multiple debug configurations are found, and no explicit configuration was +selected on Launch, the user is prompted to select a configuration, unless a +single debug configuration is found with a key `"default": true`. + +#### Specifying a default configuration + +As noted, you can specify a default configuration with `"default": true`: + +```json +{ + "configurations": { + "use this one": { + "default": true, + "adapter": " ... ", + "configuation": { + // ... + } + }, + "don't use this one": { + // ... + } + } +} +``` + +If multiple conifigurations are found with `default` set to `true`, then the +user is prompted anyway. + +#### Preventing automatic selection + +If you don't want a configuration to be selected automatically, then set +`"autoselect": false`. This particularly useful for configurations in the +central (as opposed to project-local) directory. For example: + +```json + "configurations": { + "Don't use this by default!": { + "autoselect": false, + "adapter": " ... ", + "configuation": { + // ... + } + } + } +``` + +Setting `autoselect` to `false` overrides setting `default` to `true`. + ### Exception breakpionts Debug adapters have arbitrary configuration for exception breakpoints. Normally @@ -426,12 +485,12 @@ where the development is done on one host and the runtime is some other host, account, container, etc. In order for it to work, you have to set up passwordless SSH between the local -and remote machines/accounts. Then just tell Vimsector how to remotely launch +and remote machines/accounts. Then just tell Vimspector how to remotely launch and/or attach to the app. This is presented as examples with commentary, as it's a fairly advanced/niche case. If you're not already familiar with remote debugging tools (such as -gdbserver) or not familar with ssh or such, you might need to independently +gdbserver) or not familiar with ssh or such, you might need to independently research that. Vimspector's tools are intended to automate your existing process for setting diff --git a/plugin/vimspector.vim b/plugin/vimspector.vim index cb921eb..fa8312c 100644 --- a/plugin/vimspector.vim +++ b/plugin/vimspector.vim @@ -97,6 +97,13 @@ command! -bar \ VimspectorReset \ call vimspector#Reset() +" Dummy autocommands so that we can call this whenever +augroup VimspectorUserAutoCmds + au! + au User VimspectorUICreated silent + au User VimspectorTerminalOpened silent +augroup END + " boilerplate {{{ call s:restore_cpo() " }}} diff --git a/python3/vimspector/breakpoints.py b/python3/vimspector/breakpoints.py index b6a975d..10b89e8 100644 --- a/python3/vimspector/breakpoints.py +++ b/python3/vimspector/breakpoints.py @@ -342,11 +342,14 @@ class ProjectBreakpoints( object ): f"Invalid value for exception breakpoint filter '{f}': " f"'{result}'. Must be boolean, 'Y', 'N' or '' (default)" ) else: - result = utils.AskForInput( - "{}: Break on {} (Y/N/default: {})? ".format( f[ 'filter' ], - f[ 'label' ], - default_value ), - default_value ) + try: + result = utils.AskForInput( + "{}: Break on {} (Y/N/default: {})? ".format( f[ 'filter' ], + f[ 'label' ], + default_value ), + default_value ) + except KeyboardInterrupt: + result = '' if result == 'Y': exception_filters.append( f[ 'filter' ] ) diff --git a/python3/vimspector/code.py b/python3/vimspector/code.py index b826d21..a4f2514 100644 --- a/python3/vimspector/code.py +++ b/python3/vimspector/code.py @@ -18,7 +18,7 @@ import logging import json from collections import defaultdict -from vimspector import utils +from vimspector import utils, settings class CodeView( object ): @@ -218,7 +218,7 @@ class CodeView( object ): args = params[ 'args' ] env = params.get( 'env', {} ) - options = { + term_options = { 'vertical': 1, 'norestore': 1, 'cwd': cwd, @@ -228,35 +228,54 @@ class CodeView( object ): if self._window.valid: window_for_start = self._window else: - # TOOD: Where? + # TOOD: Where? Maybe we should just use botright vertical ... window_for_start = vim.current.window if self._terminal_window is not None and self._terminal_window.valid: assert self._terminal_buffer_number + window_for_start = self._terminal_window if ( self._terminal_window.buffer.number == self._terminal_buffer_number and int( utils.Call( 'vimspector#internal#{}term#IsFinished'.format( self._api_prefix ), self._terminal_buffer_number ) ) ): - window_for_start = self._terminal_window - options[ 'curwin' ] = 1 + term_options[ 'curwin' ] = 1 + else: + term_options[ 'vertical' ] = 0 buffer_number = None terminal_window = None - with utils.TemporaryVimOptions( { 'splitright': True, - 'equalalways': False } ): - with utils.LetCurrentWindow( window_for_start ): - buffer_number = int( - utils.Call( - 'vimspector#internal#{}term#Start'.format( self._api_prefix ), - args, - options ) ) - terminal_window = vim.current.window + with utils.LetCurrentWindow( window_for_start ): + # If we're making a vertical split from the code window, make it no more + # than 80 columns and no fewer than 10. Also try and keep the code window + # at least 82 columns + if term_options[ 'vertical' ] and not term_options.get( 'curwin', 0 ): + term_options[ 'term_cols' ] = max( + min ( int( vim.eval( 'winwidth( 0 )' ) ) + - settings.Int( 'code_minwidth', 82 ), + settings.Int( 'terminal_maxwidth', 80 ) ), + settings.Int( 'terminal_minwidth' , 10 ) + ) + + buffer_number = int( + utils.Call( + 'vimspector#internal#{}term#Start'.format( self._api_prefix ), + args, + term_options ) ) + terminal_window = vim.current.window if buffer_number is None or buffer_number <= 0: # TODO: Do something better like reject the request? raise ValueError( "Unable to start terminal" ) - else: - self._terminal_window = terminal_window - self._terminal_buffer_number = buffer_number + + self._terminal_window = terminal_window + self._terminal_buffer_number = buffer_number + + vim.vars[ 'vimspector_session_windows' ][ 'terminal' ] = utils.WindowID( + self._terminal_window, + vim.current.tabpage ) + with utils.RestoreCursorPosition(): + with utils.RestoreCurrentWindow(): + with utils.RestoreCurrentBuffer( vim.current.window ): + vim.command( 'doautocmd User VimspectorTerminalOpened' ) return buffer_number diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 1302ab6..9ff680a 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -29,7 +29,8 @@ from vimspector import ( breakpoints, output, stack_trace, utils, - variables ) + variables, + settings ) from vimspector.vendor.json_minify import minify # We cache this once, and don't allow it to change (FIXME?) @@ -125,9 +126,17 @@ class DebugSession( object ): next( iter( configurations.values() ) ).get( "autoselect", True ) ): configuration_name = next( iter( configurations.keys() ) ) else: - configuration_name = utils.SelectFromList( - 'Which launch configuration?', - sorted( configurations.keys() ) ) + # Find a single configuration with 'default' True and autoselect not False + defaults = { n: c for n, c in configurations.items() + if c.get( 'default', False ) is True + and c.get( 'autoselect', True ) is not False } + + if len( defaults ) == 1: + configuration_name = next( iter( defaults.keys() ) ) + else: + configuration_name = utils.SelectFromList( + 'Which launch configuration?', + sorted( configurations.keys() ) ) if not configuration_name or configuration_name not in configurations: return @@ -206,26 +215,30 @@ class DebugSession( object ): USER_CHOICES.update( launch_variables ) variables.update( launch_variables ) - variables.update( - utils.ParseVariables( adapter.get( 'variables', {} ), - variables, - calculus, - USER_CHOICES ) ) - variables.update( - utils.ParseVariables( configuration.get( 'variables', {} ), - variables, - calculus, - USER_CHOICES ) ) + try: + variables.update( + utils.ParseVariables( adapter.get( 'variables', {} ), + variables, + calculus, + USER_CHOICES ) ) + variables.update( + utils.ParseVariables( configuration.get( 'variables', {} ), + variables, + calculus, + USER_CHOICES ) ) - utils.ExpandReferencesInDict( configuration, - variables, - calculus, - USER_CHOICES ) - utils.ExpandReferencesInDict( adapter, - variables, - calculus, - USER_CHOICES ) + utils.ExpandReferencesInDict( configuration, + variables, + calculus, + USER_CHOICES ) + utils.ExpandReferencesInDict( adapter, + variables, + calculus, + USER_CHOICES ) + except KeyboardInterrupt: + self._Reset() + return if not adapter: utils.UserMessage( 'No adapter configured for {}'.format( @@ -285,18 +298,35 @@ class DebugSession( object ): self._StartWithConfiguration( self._configuration, self._adapter ) - def IfConnected( fct ): + def IfConnected( otherwise=None ): + def decorator( fct ): + """Decorator, call fct if self._connected else echo warning""" + @functools.wraps( fct ) + def wrapper( self, *args, **kwargs ): + if not self._connection: + utils.UserMessage( + 'Vimspector not connected, start a debug session first', + persist=False, + error=True ) + return otherwise + return fct( self, *args, **kwargs ) + return wrapper + return decorator + + def RequiresUI( otherwise=None ): """Decorator, call fct if self._connected else echo warning""" - @functools.wraps( fct ) - def wrapper( self, *args, **kwargs ): - if not self._connection: - utils.UserMessage( - 'Vimspector not connected, start a debug session first', - persist=True, - error=True ) - return - return fct( self, *args, **kwargs ) - return wrapper + def decorator( fct ): + @functools.wraps( fct ) + def wrapper( self, *args, **kwargs ): + if not self._uiTab or not self._uiTab.valid: + utils.UserMessage( + 'Vimspector is not active', + persist=False, + error=True ) + return otherwise + return fct( self, *args, **kwargs ) + return wrapper + return decorator def OnChannelData( self, data ): if self._connection is None: @@ -319,7 +349,7 @@ class DebugSession( object ): # TODO: Not calld self._connection = None - @IfConnected + @IfConnected() def Stop( self ): self._logger.debug( "Stop debug adapter with no callback" ) self._StopDebugAdapter() @@ -335,6 +365,8 @@ class DebugSession( object ): self._logger.info( "Debugging complete." ) if self._uiTab: self._logger.debug( "Clearing down UI" ) + + del vim.vars[ 'vimspector_session_windows' ] vim.current.tabpage = self._uiTab self._splash_screen = utils.HideSplash( self._api_prefix, @@ -354,7 +386,7 @@ class DebugSession( object ): # make sure that we're displaying signs in any still-open buffers self._breakpoints.UpdateUI() - @IfConnected + @IfConnected() def StepOver( self ): if self._stackTraceView.GetCurrentThreadId() is None: return @@ -366,7 +398,7 @@ class DebugSession( object ): }, } ) - @IfConnected + @IfConnected() def StepInto( self ): if self._stackTraceView.GetCurrentThreadId() is None: return @@ -378,7 +410,7 @@ class DebugSession( object ): }, } ) - @IfConnected + @IfConnected() def StepOut( self ): if self._stackTraceView.GetCurrentThreadId() is None: return @@ -396,29 +428,29 @@ class DebugSession( object ): else: self.Start() - @IfConnected + @IfConnected() def Pause( self ): self._stackTraceView.Pause() - @IfConnected + @IfConnected() def ExpandVariable( self ): self._variablesView.ExpandVariable() - @IfConnected + @IfConnected() def AddWatch( self, expression ): self._variablesView.AddWatch( self._stackTraceView.GetCurrentFrame(), expression ) - @IfConnected + @IfConnected() def EvaluateConsole( self, expression ): self._outputView.Evaluate( self._stackTraceView.GetCurrentFrame(), expression ) - @IfConnected + @IfConnected() def DeleteWatch( self ): self._variablesView.DeleteWatch() - @IfConnected + @IfConnected() def ShowBalloon( self, winnr, expression ): """Proxy: ballonexpr -> variables.ShowBallon""" frame = self._stackTraceView.GetCurrentFrame() @@ -437,17 +469,32 @@ class DebugSession( object ): # Return variable aware function return self._variablesView.ShowBalloon( frame, expression ) - @IfConnected + @IfConnected() def ExpandFrameOrThread( self ): self._stackTraceView.ExpandFrameOrThread() + @RequiresUI() def ShowOutput( self, category ): + if not self._outputView.WindowIsValid(): + # TODO: The UI code is too scattered. Re-organise into a UI class that + # just deals with these thigns like window layout and custmisattion. + # currently, this class and the CodeView share some responsiblity for this + # and poking into each View class to check its window is valid also feels + # wrong. + with utils.LetCurrentTabpage( self._uiTab ): + vim.command( f'botright { settings.Int( "bottombar_height", 10 ) }new' ) + self._outputView.UseWindow( vim.current.window ) + vim.vars[ 'vimspector_session_windows' ][ 'output' ] = utils.WindowID( + vim.current.window, + self._uiTab ) + self._outputView.ShowOutput( category ) + @RequiresUI( otherwise=[] ) def GetOutputBuffers( self ): return self._outputView.GetCategories() - @IfConnected + @IfConnected( otherwise=[] ) def GetCompletionsSync( self, text_line, column_in_bytes ): if not self._server_capabilities.get( 'supportsCompletionsRequest' ): return [] @@ -473,49 +520,66 @@ class DebugSession( object ): self._uiTab = vim.current.tabpage # Code window - self._codeView = code.CodeView( vim.current.window, - self._api_prefix ) + code_window = vim.current.window + self._codeView = code.CodeView( code_window, self._api_prefix ) # Call stack - with utils.TemporaryVimOptions( { 'splitright': False, - 'equalalways': False, } ): - vim.command( 'topleft 50vspl' ) - vim.command( 'enew' ) - self._stackTraceView = stack_trace.StackTraceView( self, - self._connection, - vim.current.buffer ) + vim.command( + f'topleft vertical { settings.Int( "sidebar_width", 50 ) }new' ) + 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 ) - with utils.TemporaryVimOptions( { 'splitbelow': False, - 'eadirection': 'ver', - 'equalalways': True } ): - # Watches - vim.command( 'spl' ) - vim.command( 'enew' ) - watch_win = vim.current.window + # Watches + vim.command( 'leftabove new' ) + watch_window = vim.current.window - # Variables - vim.command( 'spl' ) - vim.command( 'enew' ) - vars_win = vim.current.window + # Variables + vim.command( 'leftabove new' ) + vars_window = vim.current.window - self._variablesView = variables.VariablesView( self._connection, - vars_win, - watch_win ) + with utils.LetCurrentWindow( vars_window ): + vim.command( f'{ one_third }wincmd _' ) + with utils.LetCurrentWindow( watch_window ): + vim.command( f'{ one_third }wincmd _' ) + with utils.LetCurrentWindow( stack_trace_window ): + vim.command( f'{ one_third }wincmd _' ) + + self._variablesView = variables.VariablesView( self._connection, + 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 ) + + # TODO: If/when we support multiple sessions, we'll need some way to + # indicate which tab was created and store all the tabs + vim.vars[ 'vimspector_session_windows' ] = { + 'tabpage': self._uiTab.number, + 'code': utils.WindowID( code_window, self._uiTab ), + 'stack_trace': utils.WindowID( stack_trace_window, self._uiTab ), + 'variables': utils.WindowID( vars_window, self._uiTab ), + 'watches': utils.WindowID( watch_window, self._uiTab ), + 'output': utils.WindowID( output_window, self._uiTab ), + } + with utils.RestoreCursorPosition(): + with utils.RestoreCurrentWindow(): + with utils.RestoreCurrentBuffer( vim.current.window ): + vim.command( 'doautocmd User VimspectorUICreated' ) - with utils.TemporaryVimOption( 'splitbelow', True ): - vim.current.window = self._codeView._window - - # Output/logging - vim.command( '10spl' ) - vim.command( 'enew' ) - self._outputView = output.OutputView( self._connection, - vim.current.window, - self._api_prefix ) - + @RequiresUI() def ClearCurrentFrame( self ): self.SetCurrentFrame( None ) + @RequiresUI() def SetCurrentFrame( self, frame ): if not frame: self._stackTraceView.Clear() @@ -557,6 +621,9 @@ class DebugSession( object ): if self._adapter[ 'port' ] == 'ask': port = utils.AskForInput( 'Enter port to connect to: ' ) + if port is None: + self._Reset() + return self._adapter[ 'port' ] = port self._connection_type = self._api_prefix + self._connection_type @@ -666,6 +733,8 @@ class DebugSession( object ): prop = atttach_config[ 'pidProperty' ] if prop not in launch_config: pid = utils.AskForInput( 'Enter PID to attach to: ' ) + if pid is None: + return launch_config[ prop ] = pid return elif atttach_config[ 'pidSelect' ] == 'none': diff --git a/python3/vimspector/output.py b/python3/vimspector/output.py index 4599e04..c21f8f1 100644 --- a/python3/vimspector/output.py +++ b/python3/vimspector/output.py @@ -102,6 +102,17 @@ class OutputView( object ): # FIXME: nunmenu the WinBar ? self._buffers = {} + def WindowIsValid( self ): + return self._window.valid + + def UseWindow( self, win ): + assert not self._window.valid + self._window = win + # TODO: Sorting of the WinBar ? + for category, _ in self._buffers.items(): + self._RenderWinBar( category ) + + def _ShowOutput( self, category ): if not self._window.valid: return @@ -157,11 +168,13 @@ class OutputView( object ): def _CreateBuffer( self, category, file_name = None, cmd = None ): - if not self._window.valid: - return + win = self._window + if not win.valid: + # We need to borrow the current window + win = vim.current.window - with utils.LetCurrentWindow( self._window ): - with utils.RestoreCurrentBuffer( self._window ): + with utils.LetCurrentWindow( win ): + with utils.RestoreCurrentBuffer( win ): if file_name is not None: assert cmd is None diff --git a/python3/vimspector/settings.py b/python3/vimspector/settings.py new file mode 100644 index 0000000..c365499 --- /dev/null +++ b/python3/vimspector/settings.py @@ -0,0 +1,26 @@ +# 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. + + +import vim +import builtins + + +def Get( option: str, default=None, cls=str ): + return cls( vim.vars.get( f'vimspector_{ option }', default ) ) + + +def Int( option: str, default=0 ): + return Get( option, default=default, cls=builtins.int ) diff --git a/python3/vimspector/stack_trace.py b/python3/vimspector/stack_trace.py index 0afeb81..d5ea0b5 100644 --- a/python3/vimspector/stack_trace.py +++ b/python3/vimspector/stack_trace.py @@ -21,11 +21,11 @@ from vimspector import utils class StackTraceView( object ): - def __init__( self, session, connection, buf ): + def __init__( self, session, connection, win ): self._logger = logging.getLogger( __name__ ) utils.SetUpLogging( self._logger ) - self._buf = buf + self._buf = win.buffer self._session = session self._connection = connection @@ -38,9 +38,7 @@ class StackTraceView( object ): self._scratch_buffers = [] utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' ) - - vim.current.buffer = self._buf - utils.SetUpUIWindow( vim.current.window ) + utils.SetUpUIWindow( win ) vim.command( 'nnoremap :call vimspector#GoToFrame()' ) diff --git a/python3/vimspector/utils.py b/python3/vimspector/utils.py index 48991f7..bfdddcc 100644 --- a/python3/vimspector/utils.py +++ b/python3/vimspector/utils.py @@ -184,8 +184,10 @@ def RestoreCurrentWindow(): try: yield finally: - vim.current.tabpage = old_tabpage - vim.current.window = old_window + if old_tabpage.valid: + vim.current.tabpage = old_tabpage + if old_window.valid: + vim.current.window = old_window @contextlib.contextmanager @@ -194,9 +196,10 @@ def RestoreCurrentBuffer( window ): try: yield finally: - with RestoreCurrentWindow(): - vim.current.window = window - vim.current.buffer = old_buffer + if window.valid: + with RestoreCurrentWindow(): + vim.current.window = window + vim.current.buffer = old_buffer @contextlib.contextmanager @@ -211,6 +214,13 @@ def AnyWindowForBuffer( buf ): yield +@contextlib.contextmanager +def LetCurrentTabpage( tabpage ): + with RestoreCurrentWindow(): + vim.current.tabpage = tabpage + yield + + @contextlib.contextmanager def LetCurrentWindow( window ): with RestoreCurrentWindow(): @@ -329,7 +339,7 @@ def AskForInput( prompt, default_value = None ): return vim.eval( "input( '{}' {} )".format( Escape( prompt ), default_option ) ) except KeyboardInterrupt: - return '' + return None def AppendToBuffer( buf, line_or_lines, modified=False ): @@ -437,6 +447,10 @@ def ExpandReferencesInString( orig_s, default_value = user_choices.get( key, None ) mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ), default_value ) + + if mapping[ key ] is None: + raise KeyboardInterrupt + user_choices[ key ] = mapping[ key ] _logger.debug( "Value for %s not set in %s (from %s): set to %s", key, @@ -639,3 +653,7 @@ def GetUnusedLocalPort(): port = sock.getsockname()[ 1 ] sock.close() return port + + +def WindowID( window, tab ): + return int( Call( 'win_getid', window.number, tab.number ) ) diff --git a/run_tests b/run_tests index 7ff2ebd..c03c7b9 100755 --- a/run_tests +++ b/run_tests @@ -1,51 +1,76 @@ #!/usr/bin/env bash -if [ "$1" == "--help" ]; then - echo "$(basename $0) [--basedir ] [--install] " - echo "" - echo " --basedir path to runtime directory like the optino to install_gadget.py" - echo " --install run install_gadget.py, useful with --basedir" - echo "e.g.: " - echo " - run all tests: $0" - echo " - run specific tests script: $0 signature_help.test.vim" - echo " - run specific tests fun: $0 signature_help.test.vim:Test_signatures_TopLine\(\)" - echo " - run all tests in a clean env: $0 --basedir \$(mktemp -d) --install" - exit 0 -fi - - BASEDIR=$(dirname $0) INSTALL=0 RUN_VIM="vim -N --clean --not-a-term" RUN_TEST="${RUN_VIM} -S lib/run_test.vim" BASEDIR_CMD='py3 pass' +# 1 is stdout +out_fd=1 + while [ -n "$1" ]; do case "$1" in "--basedir") - BASEDIR=$2 + shift + BASEDIR=$1 + shift if [[ ! $BASEDIR = /* ]]; then # Relative BASEDIR=$(pwd)/${BASEDIR} fi BASEDIR_CMD="let g:vimspector_base_dir='${BASEDIR}'" - shift - shift ;; "--install") INSTALL=1 shift ;; + "--report") + shift + VIMSPECTOR_TEST_STDOUT=$1 + shift + ;; + "--quiet") + shift + # send the output to /dev/null + # https://stackoverflow.com/a/47553900 + # Note we can't use {out_fd} here because the bash version in CI is too + # old on macOS + out_fd=3 + exec 3>/dev/null + ;; "--") shift break ;; + "--help") + shift + echo "$(basename $0) [--basedir ] [--report output] [--quiet] [--install] " + echo "" + echo " --basedir path to runtime directory like the optino to install_gadget.py" + echo " --install run install_gadget.py, useful with --basedir" + echo " --report which logs to dump to stdout after a test" + echo " --quiet suppress vim's stdout" + echo "e.g.: " + echo " - run all tests: $0" + echo " - run specific tests script: $0 signature_help.test.vim" + echo " - run specific tests fun: $0 signature_help.test.vim:Test_signatures_TopLine\(\)" + echo " - run all tests in a clean env: $0 --basedir \$(mktemp -d) --install" + exit 0 + ;; *) break ;; esac done +# We use fd 3 for vim's output. Send it to stdout if not already redirected +# Note: can't use ${out_fd} in a redirect because redirects happen before +# variable substitution +if [ "${out_fd}" = "1" ]; then + exec 3>&1 +fi + if [ $INSTALL = 1 ]; then python3 $(dirname $0)/install_gadget.py --basedir ${BASEDIR} --all fi @@ -71,7 +96,7 @@ echo " * BASEDIR_CMD=$BASEDIR_CMD" echo "%SETUP - Building test programs..." set -e pushd tests/testdata/cpp/simple - make clean simple + make clean all popd set +e echo "%DONE - built test programs" @@ -99,6 +124,7 @@ for t in ${TESTS}; do if ${RUN_TEST} --cmd "${BASEDIR_CMD}" \ --cmd 'au SwapExists * let v:swapchoice = "e"' $t $T \ + >&3\ && [ -f $t.res ]; then echo "%PASS: $t PASSED" else @@ -109,10 +135,22 @@ for t in ${TESTS}; do rm -rf $TESTLOGDIR mkdir -p $TESTLOGDIR ${RUN_VIM} --version > ${TESTLOGDIR}/vimversion + if [ "$VIMSPECTOR_TEST_STDOUT" = "messages" ]; then + if [ -f messages ]; then + echo "%MESSAGES" + cat messages + echo "%END" + else + echo "%MESSAGES" + echo "No messages found" + echo "%END" + fi + fi + for l in messages debuglog test.log *.testlog; do # In CI we can't view the output files, so we just have to cat them if [ -f $l ]; then - if [ "$VIMSPECTOR_TEST_STDOUT" ]; then + if [ "$VIMSPECTOR_TEST_STDOUT" = "all" ]; then echo "" echo "" echo "*** START: $l ***" @@ -126,11 +164,14 @@ for t in ${TESTS}; do rm -f $t.res done +# close out_fd if it's not stdout/stderr/ +(( out_fd > 2 )) && exec 3>&- + + echo "Done running tests" popd > /dev/null echo "" -echo "All done." - +echo "All done. Exit with ${RESULT}" exit $RESULT diff --git a/support/custom_ui_vimrc b/support/custom_ui_vimrc new file mode 100644 index 0000000..d319b28 --- /dev/null +++ b/support/custom_ui_vimrc @@ -0,0 +1,53 @@ +execute 'source' expand( ':p:h' ) . '/minimal_vimrc' +set noequalalways + +function! s:CustomiseUI() + let wins = g:vimspector_session_windows + + " Close the Variables window + if has( 'nvim' ) + " No win_execute in neovim + call win_gotoid( wins.variables ) + quit + else + call win_execute( wins.variables, 'q' ) + endif + + " Put the stack trace at the top of the "left bar" (rotate) + call win_gotoid( wins.stack_trace ) + wincmd r + + " Make the left column at least 70 chars + 70wincmd | + + " Make the code window at least 80 chars + call win_gotoid( wins.code ) + 80wincmd | + + " Make the output window 10 lines high and right at the top of the screen + call win_gotoid( wins.output ) + 10wincmd _ + wincmd K +endfunction + +function s:SetUpTerminal() + let terminal_win = g:vimspector_session_windows.terminal + + " Make the terminal window at most 80 columns wide, ensuring there is enough + " sapce for our code window (80 columns) and the left bar (70 columns) + + " Padding is 2 for the 2 vertical split markers and 2 for the sign column in + " the code window. + let left_bar = 70 + let code = 80 + let padding = 4 + let cols = max( [ min( [ &columns - left_bar - code - padding, 80 ] ), 10 ] ) + call win_gotoid( terminal_win ) + execute cols . 'wincmd |' +endfunction + +augroup TestUICustomistaion + autocmd! + autocmd User VimspectorUICreated call s:CustomiseUI() + autocmd User VimspectorTerminalOpened call s:SetUpTerminal() +augroup END diff --git a/support/minimal_vimrc b/support/minimal_vimrc new file mode 100644 index 0000000..3626d39 --- /dev/null +++ b/support/minimal_vimrc @@ -0,0 +1,4 @@ +let s:vimspector_path = expand( ':p:h:h' ) +let g:vimspector_enable_mappings = 'HUMAN' +exe 'source ' . s:vimspector_path . '/tests/vimrc' + diff --git a/tests/lib/autoload/vimspector/test/setup.vim b/tests/lib/autoload/vimspector/test/setup.vim index d69acef..b0296e0 100644 --- a/tests/lib/autoload/vimspector/test/setup.vim +++ b/tests/lib/autoload/vimspector/test/setup.vim @@ -11,6 +11,12 @@ function! vimspector#test#setup#SetUpWithMappings( mappings ) abort " This is a bit of a hack runtime! plugin/**/*.vim + + augroup VimspectorTestSwap + au! + au SwapExists * let v:swapchoice = 'e' + augroup END + endfunction function! vimspector#test#setup#ClearDown() abort diff --git a/tests/lib/autoload/vimspector/test/signs.vim b/tests/lib/autoload/vimspector/test/signs.vim index 1ef7fc9..bd7a463 100644 --- a/tests/lib/autoload/vimspector/test/signs.vim +++ b/tests/lib/autoload/vimspector/test/signs.vim @@ -2,7 +2,9 @@ function! vimspector#test#signs#AssertCursorIsAtLineInBuffer( buffer, \ line, \ column ) abort call WaitForAssert( {-> - \ assert_equal( a:buffer, bufname( '%' ), 'Current buffer' ) + \ assert_equal( fnamemodify( a:buffer, ':p' ), + \ fnamemodify( bufname( '%' ), ':p' ), + \ 'Current buffer' ) \ }, 10000 ) call WaitForAssert( {-> \ assert_equal( a:line, line( '.' ), 'Current line' ) diff --git a/tests/lib/plugin/screendump.vim b/tests/lib/plugin/screendump.vim index 9b823c3..157febd 100644 --- a/tests/lib/plugin/screendump.vim +++ b/tests/lib/plugin/screendump.vim @@ -94,7 +94,7 @@ func StopVimInTerminal(buf) " In Command-line it's inserted, the CTRL-U removes it again. call term_sendkeys(a:buf, "\:\qa!\") - call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))}) + call WaitForAssert({-> assert_equal('finished', term_getstatus(a:buf))}) only! endfunc diff --git a/tests/lib/plugin/shared.vim b/tests/lib/plugin/shared.vim index d682810..2d656d7 100644 --- a/tests/lib/plugin/shared.vim +++ b/tests/lib/plugin/shared.vim @@ -73,7 +73,7 @@ func s:WaitForCommon(expr, assert, timeout) " Remove the errors added by the assert function. let errors_added = len( v:errors ) - errors_before if errors_added > 0 - call remove(v:errors, -1 * errors_added ) + call remove( v:errors, -1 * errors_added, -1 ) endif endif diff --git a/tests/testdata/cpp/simple/.gitignore b/tests/testdata/cpp/simple/.gitignore index ac54bdc..fec4c0b 100644 --- a/tests/testdata/cpp/simple/.gitignore +++ b/tests/testdata/cpp/simple/.gitignore @@ -1,2 +1,3 @@ simple variables +struct diff --git a/tests/testdata/cpp/simple/.vimspector.json b/tests/testdata/cpp/simple/.vimspector.json index 5eb06e1..93a7a60 100644 --- a/tests/testdata/cpp/simple/.vimspector.json +++ b/tests/testdata/cpp/simple/.vimspector.json @@ -1,17 +1,38 @@ { "configurations": { - "cpptools-run": { + "run-to-entry": { "adapter": "vscode-cpptools", + // This makes this configuration the default. Only one default can be set + // (having two is the same as having none) + "default": true, "configuration": { "request": "launch", "program": "${workspaceRoot}/${fileBasenameNoExtension}", "externalConsole": false, "stopAtEntry": true, "stopOnEntry": true, - "MImode": "${VIMSPECTOR_MIMODE}", - "logging": { - "engineLogging": true + "MImode": "${VIMSPECTOR_MIMODE}" + }, + "breakpoints": { + "exception": { + "cpp_catch": "", + "cpp_throw": "", + "objc_catch": "", + "objc_throw": "", + "swift_catch": "", + "swift_throw": "" } + } + }, + "run-to-breakpoint": { + "adapter": "vscode-cpptools", + "configuration": { + "request": "launch", + "program": "${workspaceRoot}/${fileBasenameNoExtension}", + "externalConsole": false, + "stopAtEntry": false, + "stopOnEntry": false, + "MImode": "${VIMSPECTOR_MIMODE}" }, "breakpoints": { "exception": { diff --git a/tests/testdata/cpp/simple/Makefile b/tests/testdata/cpp/simple/Makefile index 8583202..0e8d8c8 100644 --- a/tests/testdata/cpp/simple/Makefile +++ b/tests/testdata/cpp/simple/Makefile @@ -2,7 +2,7 @@ CXXFLAGS=-g -O0 -std=c++17 .PHONY: all -TARGETS=simple variables +TARGETS=simple variables struct all: $(TARGETS) diff --git a/tests/testdata/cpp/simple/struct.cpp b/tests/testdata/cpp/simple/struct.cpp new file mode 100644 index 0000000..ea55efc --- /dev/null +++ b/tests/testdata/cpp/simple/struct.cpp @@ -0,0 +1,33 @@ +struct AnotherTest +{ + char choo; + int ints[5]; +}; + +struct Test +{ + int i; + char c; + float fffff; + + AnotherTest another_test; +}; + +static Test SetUp( Test t ) +{ + t.another_test.choo = 'p'; + t.another_test.ints[ 0 ] = t.i; return t; +} + +int main( int , char ** ) +{ + Test t = {}; + + t.i = 1; + t.c = 'c'; + t.fffff = 3.14; + + t = SetUp( t ); + + return t.another_test.ints[ 0 ]; +} diff --git a/tests/ui.test.vim b/tests/ui.test.vim new file mode 100644 index 0000000..02bde3e --- /dev/null +++ b/tests/ui.test.vim @@ -0,0 +1,380 @@ +let s:fn='../support/test/python/simple_python/main.py' + +function! SetUp() + call vimspector#test#setup#SetUpWithMappings( 'HUMAN' ) +endfunction + +function! ClearDown() + call vimspector#test#setup#ClearDown() +endfunction + +function! s:StartDebugging() + exe 'edit ' . s:fn + call setpos( '.', [ 0, 23, 1 ] ) + call vimspector#ToggleBreakpoint() + call vimspector#LaunchWithSettings( { 'configuration': 'run' } ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 23, 1 ) +endfunction + +function! Test_StandardLayout() + call s:StartDebugging() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'col', [ + \ [ 'row', [ + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ], + \ ] ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_CloseVariables() + call s:StartDebugging() + + call win_execute( g:vimspector_session_windows.variables, 'q' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'col', [ + \ [ 'row', [ + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ], + \ ] ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_CloseWatches() + call s:StartDebugging() + + call win_execute( g:vimspector_session_windows.watches, 'q' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + " Add a wtch + call vimspector#AddWatch( 't' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 26, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'col', [ + \ [ 'row', [ + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ], + \ ] ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + " Replace the variables view with a watches view! + call win_execute( g:vimspector_session_windows.variables, + \ 'bu vimspector.Watches' ) + + " Delete a watch expression + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 3, 1 ] ) + call feedkeys( "\", 'xt' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + + call vimspector#StepInto() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 13, 1 ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 14, 1 ) + call vimspector#AddWatch( 'i' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' *- Result: 0', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#AddWatch( 'i+1' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+1', + \ ' *- Result: 1', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#AddWatch( 'i+2' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+1', + \ ' - Result: 1', + \ 'Expression: i+2', + \ ' *- Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Delete that middle watch + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 4, 1 ] ) + call vimspector#DeleteWatch() + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+2', + \ ' *- Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 15, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+2', + \ ' - Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Delete the top watch + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 3, 1 ] ) + call vimspector#DeleteWatch() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 13, 1 ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 14, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i+2', + \ ' *- Result: 3', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_CloseStackTrace() + call s:StartDebugging() + + call win_execute( g:vimspector_session_windows.stack_trace, 'q' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ ] ], + \ [ 'col', [ + \ [ 'row', [ + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ], + \ ] ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_CloseOutput() + call s:StartDebugging() + + call win_execute( g:vimspector_session_windows.output, 'q' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_CloseOutput_Early() + augroup TestCustomUI + au! + au User VimspectorUICreated + \ call win_execute( g:vimspector_session_windows.output, 'q' ) + augroup END + + call s:StartDebugging() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + " Open it again! + let g:vimspector_bottombar_height = 5 + VimspectorShowOutput Console + call assert_equal( + \ [ 'col', [ + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.watches ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + " The actual height reported is the number of lines visible. The WinBar takes + " 1 screen row, so g:vimspector_bottombar_height -1 + call assert_equal( 4, winheight( g:vimspector_session_windows.output ) ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 26, 1 ) + + au! TestCustomUI + call vimspector#test#setup#Reset() + %bwipe! +endfunction + + +function! Test_CustomUI() + augroup TestCustomUI + au! + au User VimspectorUICreated + \ call win_execute( g:vimspector_session_windows.watches, 'q' ) + augroup END + + call s:StartDebugging() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + " Add a watch + call vimspector#AddWatch( 't' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 26, 1 ) + + call assert_equal( + \ [ 'row', [ + \ [ 'col', [ + \ [ 'leaf', g:vimspector_session_windows.variables ], + \ [ 'leaf', g:vimspector_session_windows.stack_trace ], + \ ] ], + \ [ 'col', [ + \ [ 'row', [ + \ [ 'leaf', g:vimspector_session_windows.code ], + \ [ 'leaf', g:vimspector_session_windows.terminal ], + \ ] ], + \ [ 'leaf', g:vimspector_session_windows.output ], + \ ] ] + \ ] ], + \ winlayout( g:vimspector_session_windows.tabpage ) ) + + au! TestCustomUI + call vimspector#test#setup#Reset() + %bwipe! +endfunction diff --git a/tests/variables.test.vim b/tests/variables.test.vim new file mode 100644 index 0000000..1b10809 --- /dev/null +++ b/tests/variables.test.vim @@ -0,0 +1,481 @@ +let s:fn='../support/test/python/simple_python/main.py' + +function! SetUp() + call vimspector#test#setup#SetUpWithMappings( 'HUMAN' ) +endfunction + +function! ClearDown() + call vimspector#test#setup#ClearDown() +endfunction + +function! s:assert_match_list( expected, actual ) abort + let ret = assert_equal( len( a:expected ), len( a:actual ) ) + let len = min( [ len( a:expected ), len( a:actual ) ] ) + let idx = 0 + while idx < len + let ret += assert_match( a:expected[ idx ], a:actual[ idx ] ) + let idx += 1 + endwhile + return ret +endfunction + +function! s:StartDebugging( ... ) + if a:0 == 0 + let config = #{ + \ fn: s:fn, + \ line: 23, + \ col: 1, + \ launch: #{ configuration: 'run' } + \ } + else + let config = a:1 + endif + + execute 'edit' config.fn + call setpos( '.', [ 0, config.line, config.col ] ) + call vimspector#ToggleBreakpoint() + call vimspector#LaunchWithSettings( config.launch ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( + \ config.fn, + \ config.line, + \ config.col ) +endfunction + +function! Test_SimpleWatches() + call s:StartDebugging() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 25, 1 ) + + " Add a wtch + call vimspector#AddWatch( 't' ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 26, 1 ) + + " Delete a watch expression + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 3, 1 ] ) + call feedkeys( "\", 'xt' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call assert_equal( 'python', + \ getbufvar( + \ winbufnr( g:vimspector_session_windows.watches ), + \ '&syntax' ) ) + + call vimspector#StepInto() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 13, 1 ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 14, 1 ) + call vimspector#AddWatch( 'i' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' *- Result: 0', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#AddWatch( 'i+1' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+1', + \ ' *- Result: 1', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#AddWatch( 'i+2' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+1', + \ ' - Result: 1', + \ 'Expression: i+2', + \ ' *- Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Delete that middle watch + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 4, 1 ] ) + call vimspector#DeleteWatch() + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+2', + \ ' *- Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 15, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i', + \ ' - Result: 0', + \ 'Expression: i+2', + \ ' - Result: 2', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Delete the top watch + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 3, 1 ] ) + call vimspector#DeleteWatch() + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 13, 1 ) + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 14, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: i+2', + \ ' *- Result: 3', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_ExpandVariables() + let fn = 'testdata/cpp/simple/struct.cpp' + call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{ + \ configuration: 'run-to-breakpoint' + \ } } ) + + " Make sure the Test t is initialised + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '- Scope: Locals', + \ ' *+ t (Test): {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + call assert_equal( 'cpp', + \ getbufvar( + \ winbufnr( g:vimspector_session_windows.variables ), + \ '&syntax' ) ) + + " Expand + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 2, 1 ] ) + call feedkeys( "\", 'xt' ) + + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ '- Scope: Locals', + \ ' \*- t (Test): {...}', + \ ' \*- i (int): 0', + \ ' \*- c (char): 0 ''\\0\{1,3}''', + \ ' \*- fffff (float): 0', + \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Step - stays expanded + call vimspector#StepOver() + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ '- Scope: Locals', + \ ' - t (Test): {...}', + \ ' \*- i (int): 1', + \ ' - c (char): 0 ''\\0\{1,3}''', + \ ' - fffff (float): 0', + \ ' + another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Collapse + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 2, 1 ] ) + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '- Scope: Locals', + \ ' + t (Test): {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 28, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '- Scope: Locals', + \ ' + t (Test): {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 2, 1 ] ) + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ '- Scope: Locals', + \ ' - t (Test): {...}', + \ ' \*- i (int): 1', + \ ' \*- c (char): 99 ''c''', + \ ' \*- fffff (float): 0', + \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Collapse the 'inexpensive' scope and see that it stays collapsed + " Exapand - see that the changed value is highlighted + call win_gotoid( g:vimspector_session_windows.variables ) + call setpos( '.', [ 0, 1, 1 ] ) + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '+ Scope: Locals', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Stays collpased through step + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 30, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '+ Scope: Locals', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Cpptools keeps the same "Locals" scope, so it stays collapsed even throught + " step-in + call vimspector#StepInto() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 18, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ '+ Scope: Locals', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.variables ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! Test_ExpandWatch() + let fn = 'testdata/cpp/simple/struct.cpp' + call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{ + \ configuration: 'run-to-breakpoint' + \ } } ) + + " Make sure the Test t is initialised + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 ) + + call win_gotoid( g:vimspector_session_windows.watches ) + call feedkeys( "it\", 'xt' ) + + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' *+ Result: {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + call assert_equal( 'cpp', + \ getbufvar( + \ winbufnr( g:vimspector_session_windows.watches ), + \ '&syntax' ) ) + + " Expand + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 3, 1 ] ) + call feedkeys( "\", 'xt' ) + + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' \*- Result: {...}', + \ ' \*- i (int): 0', + \ ' \*- c (char): 0 ''\\0\{1,3}''', + \ ' \*- fffff (float): 0', + \ ' \*+ another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Step - stays expanded + call vimspector#StepOver() + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' - Result: {...}', + \ ' \*- i (int): 1', + \ ' - c (char): 0 ''\\0\{1,3}''', + \ ' - fffff (float): 0', + \ ' + another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + " Collapse + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 3, 1 ] ) + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' + Result: {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 28, 1 ) + call WaitForAssert( {-> + \ assert_equal( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' + Result: {...}', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call win_gotoid( g:vimspector_session_windows.watches ) + call setpos( '.', [ 0, 3, 1 ] ) + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ s:assert_match_list( + \ [ + \ 'Watches: ----', + \ 'Expression: t', + \ ' - Result: {...}', + \ ' - i (int): 1', + \ ' - c (char): 99 ''c''', + \ ' - fffff (float): 0', + \ ' + another_test (AnotherTest):\( {...}\)\?', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.watches ), + \ 1, + \ '$' ) + \ ) + \ } ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction diff --git a/tests/vimrc b/tests/vimrc index e845f78..e362824 100644 --- a/tests/vimrc +++ b/tests/vimrc @@ -1,5 +1,6 @@ let g:vimspector_test_plugin_path = expand( ':p:h:h' ) set mouse=a +set noequalalways let &rtp = &rtp . ',' . g:vimspector_test_plugin_path