From 28517b70ee832b53052e16331b5623841c0e3012 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 1 Oct 2023 20:49:43 +0100 Subject: [PATCH 01/10] Add feature option to disable auto hovering 3 people have now asked for this, and I can sort of see why you might want to do it. So you can now disable the mouse balloon feature with g:vimspector_enable_auto_hover=0. Default remains on, of course. Closes #496 --- README.md | 12 +- python3/vimspector/settings.py | 3 +- python3/vimspector/variables.py | 37 +++--- tests/ci/image/Dockerfile | 4 +- tests/lib/autoload/vimspector/test/setup.vim | 4 + tests/lib/plugin/shared.vim | 14 ++- tests/variables.test.vim | 126 +++++++++++++++++++ 7 files changed, 175 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 42f36bb1f..d43430ac5 100644 --- a/README.md +++ b/README.md @@ -261,8 +261,8 @@ The following sections expand on the above brief overview. Vimspector requires: * One of: - * Vim 8.2 Huge build compiled with Python 3.10 or later - * Neovim 0.8 with Python 3.10 or later (experimental) + * Vim 8.2.4797 or later "huge" build compiled with Python 3.10 or later + * Neovim 0.8 with Python 3.10 or later * One of the following operating systems: * Linux * macOS Mojave or later @@ -1266,6 +1266,10 @@ All rules for `Variables and scopes` apply plus the following: ![variable eval hover](https://puremourning.github.io/vimspector-web/img/vimspector-variable-eval-hover.png) +You can disable automatic hovering popup by settings +`g:vimspector_enable_auto_hover=0` before starting the debug session. You can +then map something to `VimspectorBalloonEval` and trigger it manually. + ## Watches The watch window is used to inspect variables and expressions. Expressions are @@ -1294,6 +1298,10 @@ value are displayed, with other data available from hovering the mouse or triggering `VimspectorBalloonEval` on the line containing the value in the variables (or watches) window. +You can disable automatic hovering popup by settings +`g:vimspector_enable_auto_hover=0` before starting the debug session. You can +then map something to `VimspectorBalloonEval` and trigger it manually. + ### Watch autocompletion The watch prompt buffer has its `omnifunc` set to a function that will diff --git a/python3/vimspector/settings.py b/python3/vimspector/settings.py index 73580ea71..26d6258b3 100644 --- a/python3/vimspector/settings.py +++ b/python3/vimspector/settings.py @@ -37,8 +37,9 @@ 'terminal_maxheight': 15, 'terminal_minheight': 5, - # WinBar + # Features 'enable_winbar': True, + 'enable_auto_hover': True, # Session files 'session_file_name': '.vimspector.session', diff --git a/python3/vimspector/variables.py b/python3/vimspector/variables.py index 31c4d22b4..97e004f4e 100644 --- a/python3/vimspector/variables.py +++ b/python3/vimspector/variables.py @@ -282,28 +282,29 @@ def __init__( self, session_id, variables_win, watches_win ): ) # Set the (global!) balloon expr if supported - has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) ) - has_balloon_term = int( vim.eval( "has( 'balloon_eval_term' )" ) ) - self._oldoptions = {} - if has_balloon or has_balloon_term: - self._oldoptions = { - 'balloonexpr': vim.options[ 'balloonexpr' ], - 'balloondelay': vim.options[ 'balloondelay' ], - } - vim.options[ 'balloonexpr' ] = ( - "vimspector#internal#balloon#HoverEvalTooltip()" - ) + if settings.Bool( 'enable_auto_hover' ): + has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) ) + has_balloon_term = int( vim.eval( "has( 'balloon_eval_term' )" ) ) + + if has_balloon or has_balloon_term: + self._oldoptions = { + 'balloonexpr': vim.options[ 'balloonexpr' ], + 'balloondelay': vim.options[ 'balloondelay' ], + } + vim.options[ 'balloonexpr' ] = ( + "vimspector#internal#balloon#HoverEvalTooltip()" + ) - vim.options[ 'balloondelay' ] = 250 + vim.options[ 'balloondelay' ] = 250 - if has_balloon: - self._oldoptions[ 'ballooneval' ] = vim.options[ 'ballooneval' ] - vim.options[ 'ballooneval' ] = True + if has_balloon: + self._oldoptions[ 'ballooneval' ] = vim.options[ 'ballooneval' ] + vim.options[ 'ballooneval' ] = True - if has_balloon_term: - self._oldoptions[ 'balloonevalterm' ] = vim.options[ 'balloonevalterm' ] - vim.options[ 'balloonevalterm' ] = True + if has_balloon_term: + self._oldoptions[ 'balloonevalterm' ] = vim.options[ 'balloonevalterm' ] + vim.options[ 'balloonevalterm' ] = True def Clear( self ): diff --git a/tests/ci/image/Dockerfile b/tests/ci/image/Dockerfile index 171298e99..7caf00623 100644 --- a/tests/ci/image/Dockerfile +++ b/tests/ci/image/Dockerfile @@ -5,6 +5,8 @@ ENV LC_ALL C.UTF-8 ARG GOARCH=amd64 ARG GOVERSION=1.20.1 ARG NODE_MAJOR=18 +ARG VIM_VERSION=v8.2.4797 + RUN apt-get update && \ apt-get install -y curl \ @@ -81,8 +83,6 @@ RUN apt-get -y autoremove ## cleanup of files from setup RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -ARG VIM_VERSION=v8.2.4797 - ENV CONF_ARGS "--with-features=huge \ --enable-python3interp \ --enable-terminal \ diff --git a/tests/lib/autoload/vimspector/test/setup.vim b/tests/lib/autoload/vimspector/test/setup.vim index 5ea0e3e2e..7ab0101e4 100644 --- a/tests/lib/autoload/vimspector/test/setup.vim +++ b/tests/lib/autoload/vimspector/test/setup.vim @@ -107,6 +107,7 @@ function! vimspector#test#setup#PushGlobal( name, value ) abort endif let old_value = get( g:, a:name, v:null ) + call TestLog( 'Overriding ' . a:name . ' to ' . string( a:value ) ) call add( s:g_stack[ a:name ], old_value ) let g:[ a:name ] = a:value @@ -119,6 +120,7 @@ function! vimspector#test#setup#PopGlobal( name ) abort endif let old_value = s:g_stack[ a:name ][ -1 ] + call TestLog( 'Restoring ' . a:name . ' to ' . string( old_value ) ) call remove( s:g_stack[ a:name ], -1 ) if old_value is v:null @@ -139,6 +141,7 @@ function! vimspector#test#setup#PushOption( name, value ) abort let old_value = v:null execute 'let old_value = &' . a:name + call TestLog( 'Overriding &' . a:name . ' to ' . string( a:value ) ) call add( s:o_stack[ a:name ], old_value ) execute 'set ' . a:name . '=' . a:value return old_value @@ -150,6 +153,7 @@ function! vimspector#test#setup#PopOption( name ) abort endif let old_value = s:o_stack[ a:name ][ -1 ] + call TestLog( 'Restoring &' . a:name . ' to ' . string( old_value ) ) call remove( s:o_stack[ a:name ], -1 ) execute 'set ' . a:name . '=' . old_value diff --git a/tests/lib/plugin/shared.vim b/tests/lib/plugin/shared.vim index 8cb998763..589b4b2e4 100644 --- a/tests/lib/plugin/shared.vim +++ b/tests/lib/plugin/shared.vim @@ -112,12 +112,12 @@ endfunction " In neovim, py3eval( 'None' ) returns v:null, and v:none does not exist function! AssertNull( actual ) abort return assert_equal( type( v:null ), type( a:actual ), - \ 'actual: ' .. a:actual ) + \ 'Expected null, but actually: ' .. a:actual ) endfunction function! AssertNotNull( actual ) abort return assert_notequal( type( v:null ), type( a:actual ), - \ 'actual: ' .. a:actual ) + \ 'Expected not null, but actually: ' .. a:actual ) endfunction function! AssertMatchList( expected, actual ) abort @@ -175,3 +175,13 @@ function! FunctionBreakOnBrace() abort return trim( system( 'uname -m' ) ) ==# 'x86_64' \ && trim( system( 'uname -s' ) ) ==# 'Linux' endfunction + +function MoveMouseToPositionInWindow( win_id, line, colum ) abort + let pos = screenpos( a:win_id, a:line, a:colum ) + return MoveMouseTo( pos.row, pos.col ) +endfunction + +function! MoveMouseTo( screen_line, screen_column ) abort + call test_setmouse( a:screen_line, a:screen_column ) + call feedkeys("\", 'xt') +endfunction diff --git a/tests/variables.test.vim b/tests/variables.test.vim index 31c60f81c..5e5c911c6 100644 --- a/tests/variables.test.vim +++ b/tests/variables.test.vim @@ -759,6 +759,132 @@ function! Test_VariableEval() %bwipe! endfunction +function! Test_VariableEval_Hover() + call SkipNeovim() + let fn = 'testdata/cpp/simple/struct.cpp' + call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{ + \ configuration: 'run-to-breakpoint' + \ } } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 ) + + "evaluate the prev line + call MoveMouseToPositionInWindow( g:vimspector_session_windows.code, 24, 8 ) + + call WaitForAssert( {-> + \ AssertNotNull( g:vimspector_session_windows.eval ) + \ } ) + + call WaitForAssert( {-> + \ AssertMatchList( + \ [ + \ '{...}', + \ ' - i: 0', + \ ' - c: 0 ''\\0\{1,3}''', + \ ' - fffff: 0', + \ ' + another_test: ', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.eval ), + \ 1, + \ '$' ) + \ ) + \ } ) + + "Close + call MoveMouseToPositionInWindow( g:vimspector_session_windows.code, 1, 1 ) + call WaitForAssert( {-> + \ AssertNull( g:vimspector_session_windows.eval ) + \ } ) + call WaitForAssert( {-> + \ assert_equal( len( popup_list() ), 0 ) } ) + + call vimspector#test#setup#Reset() + %bwipe! +endfunction + +function! SetUp_Test_VariableEval_HoverDisabled() + call vimspector#test#setup#PushOption( 'balloondelay', 10 ) + call vimspector#test#setup#PushGlobal( 'vimspector_enable_auto_hover', 0 ) +endfunction + +function! TearDown_Test_VariableEval_HoverDisabled() + call vimspector#test#setup#PopOption( 'balloondelay' ) + call vimspector#test#setup#PopGlobal( 'vimspector_enable_auto_hover' ) +endfunction + +function! Test_VariableEval_HoverDisabled() + call SkipNeovim() + + let fn = 'testdata/cpp/simple/struct.cpp' + call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{ + \ configuration: 'run-to-breakpoint' + \ } } ) + + call vimspector#StepOver() + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 26, 1 ) + + "evaluate the prev line + call MoveMouseToPositionInWindow( g:vimspector_session_windows.code, 24, 8 ) + + " balloondelay is 10ms + need some time for the popup to appear. And CI can be + " very slow, so let's be super conservative and wait a whole 5s to be super + " sure that we actually don't show a popup. + sleep 5000m + + call WaitForAssert( {-> + \ AssertNull( g:vimspector_session_windows.eval ) + \ } ) + call WaitForAssert( {-> + \ assert_equal( len( popup_list() ), 0 ) } ) + + " leader is , + xmap d VimspectorBalloonEval + nmap d VimspectorBalloonEval + + "evaluate the prev line + call setpos( '.', [ 0, 24, 8 ] ) + call vimspector#test#signs#AssertCursorIsAtLineInBuffer( fn, 24, 8 ) + call feedkeys( ',d', 'xt' ) + + call WaitForAssert( {-> + \ AssertNotNull( g:vimspector_session_windows.eval ) + \ } ) + call WaitForAssert( {-> + \ assert_equal( len( popup_list() ), 1 ) } ) + + call WaitForAssert( {-> + \ AssertMatchList( + \ [ + \ '{...}', + \ ' - i: 0', + \ ' - c: 0 ''\\0\{1,3}''', + \ ' - fffff: 0', + \ ' + another_test: ', + \ ], + \ getbufline( winbufnr( g:vimspector_session_windows.eval ), + \ 1, + \ '$' ) + \ ) + \ } ) + + "Close + call MoveMouseToPositionInWindow( g:vimspector_session_windows.code, 1, 1 ) + sleep 20m + call WaitForAssert( {-> + \ AssertNotNull( g:vimspector_session_windows.eval ) + \ } ) + + call feedkeys( "\", 'xt' ) + call WaitForAssert( {-> + \ AssertNull( g:vimspector_session_windows.eval ) + \ } ) + call WaitForAssert( {-> + \ assert_equal( len( popup_list() ), 0 ) } ) + call vimspector#test#setup#Reset() + %bwipe! +endfunction + function! Test_VariableEvalExpand() call SkipNeovim() let fn = 'testdata/cpp/simple/struct.cpp' From 473f3d5d6457e8e059af2265004706279b200bca Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 1 Oct 2023 22:10:14 +0100 Subject: [PATCH 02/10] Allow skipping tests from SetUp. This works around options that break neovim --- tests/lib/run_test.vim | 49 +++++++++++++++++++++++++++++----------- tests/variables.test.vim | 3 +-- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/tests/lib/run_test.vim b/tests/lib/run_test.vim index e7f8b800c..d2d8bccb8 100644 --- a/tests/lib/run_test.vim +++ b/tests/lib/run_test.vim @@ -121,9 +121,21 @@ func RunTheTest(test) " directory after executing the test. let s:save_cwd = getcwd() + " Used to record when a test is skipped by throwing within the SetUp handlers. + " This mostly allows us to skip running the test itself (at all) and to skip + " any teardown function too. + let skip_this_test=0 + if exists('*SetUp_' . a:test) try exe 'call SetUp_' . a:test + catch /^\cskipped/ + call add(s:messages, ' Skipped') + call add(s:skipped, + \ 'SKIPPED ' . a:test + \ . ': ' + \ . substitute(v:exception, '^\S*\s\+', '', '')) + let skip_this_test=1 catch call add(v:errors, \ 'Caught exception in SetUp_' . a:test . ' before ' @@ -140,6 +152,13 @@ func RunTheTest(test) if exists('*SetUp') try call SetUp() + catch /^\cskipped/ + call add(s:messages, ' Skipped') + call add(s:skipped, + \ 'SKIPPED ' . a:test + \ . ': ' + \ . substitute(v:exception, '^\S*\s\+', '', '')) + let skip_this_test=1 catch call add(v:errors, \ 'Caught exception in SetUp() before ' @@ -153,23 +172,26 @@ func RunTheTest(test) endtry endif - call add(s:messages, 'Executing ' . a:test) - let s:done += 1 let timer = timer_start( s:single_test_timeout, funcref( 'Abort' ) ) try - let s:test = a:test - let s:testid = g:testpath . ':' . a:test + if !skip_this_test + call add(s:messages, 'Executing ' . a:test) + let s:done += 1 - let test_filesafe = substitute( a:test, '[)(,:]', '_', 'g' ) - let s:testid_filesafe = g:testpath . '_' . test_filesafe + let s:test = a:test + let s:testid = g:testpath . ':' . a:test - augroup EarlyExit - au! - au VimLeavePre * call EarlyExit(s:test) - augroup END + let test_filesafe = substitute( a:test, '[)(,:]', '_', 'g' ) + let s:testid_filesafe = g:testpath . '_' . test_filesafe - exe 'call ' . a:test + augroup EarlyExit + au! + au VimLeavePre * call EarlyExit(s:test) + augroup END + + exe 'call ' . a:test + endif catch /^\cskipped/ call add(s:messages, ' Skipped') call add(s:skipped, @@ -217,7 +239,8 @@ func RunTheTest(test) " reset to avoid trouble with anything else. set noinsertmode - if exists('*TearDown') + " only perform teardown if the test was not skipped + if !skip_this_test && exists('*TearDown') try call TearDown() catch @@ -232,7 +255,7 @@ func RunTheTest(test) endtry endif - if exists('*TearDown_' . a:test) + if !skip_this_test && exists('*TearDown_' . a:test) try exe 'call TearDown_' . a:test catch diff --git a/tests/variables.test.vim b/tests/variables.test.vim index 5e5c911c6..4eb6a6aac 100644 --- a/tests/variables.test.vim +++ b/tests/variables.test.vim @@ -804,6 +804,7 @@ function! Test_VariableEval_Hover() endfunction function! SetUp_Test_VariableEval_HoverDisabled() + call SkipNeovim() call vimspector#test#setup#PushOption( 'balloondelay', 10 ) call vimspector#test#setup#PushGlobal( 'vimspector_enable_auto_hover', 0 ) endfunction @@ -814,8 +815,6 @@ function! TearDown_Test_VariableEval_HoverDisabled() endfunction function! Test_VariableEval_HoverDisabled() - call SkipNeovim() - let fn = 'testdata/cpp/simple/struct.cpp' call s:StartDebugging( #{ fn: fn, line: 24, col: 1, launch: #{ \ configuration: 'run-to-breakpoint' From 6ca681d3e233859a7d98583f28a09c008930e134 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 1 Oct 2023 22:47:37 +0100 Subject: [PATCH 03/10] Tidy/speed up container build by consolidating some layers --- tests/ci/image/Dockerfile | 50 +++++++++++++++-------------------- tests/manual/image/Dockerfile | 7 ++--- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/tests/ci/image/Dockerfile b/tests/ci/image/Dockerfile index 7caf00623..3b86ef06b 100644 --- a/tests/ci/image/Dockerfile +++ b/tests/ci/image/Dockerfile @@ -6,6 +6,7 @@ ARG GOARCH=amd64 ARG GOVERSION=1.20.1 ARG NODE_MAJOR=18 ARG VIM_VERSION=v8.2.4797 +ARG NVIM_VERSION=v0.8.3 RUN apt-get update && \ @@ -40,9 +41,25 @@ RUN apt-get update && \ nodejs \ pkg-config \ lua5.1 \ - luajit && \ - pip3 install neovim - + luajit \ + lua5.1-dev \ + luajit-5.1-dev \ + libsdl2-dev \ + libopenal-dev \ + libfreetype6-dev \ + libmodplug-dev \ + libvorbis-dev \ + libtheora-dev \ + libmpg123-dev \ + ninja-build \ + gettext \ + cmake \ + unzip \ + curl && \ + apt-get -y autoremove && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + pip3 install neovim && \ + pip install "git+https://github.com/puremourning/asciinema@exit_code#egg=asciinema" RUN ln -fs /usr/share/zoneinfo/Europe/London /etc/localtime && \ dpkg-reconfigure --frontend noninteractive tzdata @@ -51,21 +68,10 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 1 \ --slave /usr/bin/g++ g++ /usr/bin/g++-8 RUN curl -LO https://golang.org/dl/go${GOVERSION}.linux-${GOARCH}.tar.gz && \ - tar -C /usr/local -xzvf go${GOVERSION}.linux-${GOARCH}.tar.gz - -RUN update-alternatives --install /usr/local/bin/go go /usr/local/go/bin/go 1 + tar -C /usr/local -xzvf go${GOVERSION}.linux-${GOARCH}.tar.gz && \ + update-alternatives --install /usr/local/bin/go go /usr/local/go/bin/go 1 # In order for love to work on arm64, we have to build it ourselves -RUN apt-get -y install lua5.1-dev \ - luajit-5.1-dev \ - libsdl2-dev \ - libopenal-dev \ - libfreetype6-dev \ - libmodplug-dev \ - libvorbis-dev \ - libtheora-dev \ - libmpg123-dev - RUN curl -LO https://github.com/love2d/love/releases/download/11.3/love-11.3-linux-src.tar.gz && \ tar zxvf love-11.3-linux-src.tar.gz && \ cd love-11.3 && \ @@ -76,13 +82,6 @@ RUN curl -LO https://github.com/love2d/love/releases/download/11.3/love-11.3-lin rm -rf love-11.3 && \ rm -f love-11.3-linux-src.tar.gz -RUN apt-get install -y ninja-build gettext cmake unzip curl - -RUN apt-get -y autoremove - -## cleanup of files from setup -RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - ENV CONF_ARGS "--with-features=huge \ --enable-python3interp \ --enable-terminal \ @@ -98,8 +97,6 @@ RUN mkdir -p $HOME/vim && \ cd && \ rm -rf $HOME/vim -ARG NVIM_VERSION=v0.8.3 - RUN mkdir -p $HOME/nvim && \ cd $HOME/nvim && \ git clone --depth 1 --branch ${NVIM_VERSION} https://github.com/neovim/neovim && \ @@ -115,8 +112,5 @@ RUN curl -sSL https://dot.net/v1/dotnet-install.sh \ update-alternatives --install /usr/bin/dotnet dotnet \ /usr/share/dotnet/dotnet 1 -# test requirements -RUN pip install "git+https://github.com/puremourning/asciinema@exit_code#egg=asciinema" - # clean up RUN rm -rf ~/.cache diff --git a/tests/manual/image/Dockerfile b/tests/manual/image/Dockerfile index ae661f273..2b604ef63 100644 --- a/tests/manual/image/Dockerfile +++ b/tests/manual/image/Dockerfile @@ -3,11 +3,8 @@ ARG ARCH=x86_64 FROM puremourning/vimspector:test-${ARCH} RUN apt-get update && \ - apt-get -y dist-upgrade && \ - apt-get -y install sudo - -## cleanup of files from setup -RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + apt-get -y install sudo && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN useradd -ms /bin/bash -d /home/dev -G sudo dev && \ echo "dev:dev" | chpasswd && \ From 6404c23004cde5014daa356dac2911e5df66fbb6 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 2 Oct 2023 21:00:45 +0100 Subject: [PATCH 04/10] Catch writing to destroyed channel too --- autoload/vimspector/internal/channel.vim | 3 +++ autoload/vimspector/internal/job.vim | 2 ++ 2 files changed, 5 insertions(+) diff --git a/autoload/vimspector/internal/channel.vim b/autoload/vimspector/internal/channel.vim index 166fdbdaf..9f39be063 100644 --- a/autoload/vimspector/internal/channel.vim +++ b/autoload/vimspector/internal/channel.vim @@ -121,6 +121,9 @@ function! vimspector#internal#channel#Send( session_id, msg ) abort catch /E631/ " Channel is closed return 0 + catch /E906/ + " Channel is closed + return 0 endtry endfunction diff --git a/autoload/vimspector/internal/job.vim b/autoload/vimspector/internal/job.vim index da483c64b..3954f3d8b 100644 --- a/autoload/vimspector/internal/job.vim +++ b/autoload/vimspector/internal/job.vim @@ -145,6 +145,8 @@ function! vimspector#internal#job#Send( session_id, msg ) abort return 1 catch /E631/ return 0 + catch /E906/ + return 0 endtry endfunction From 68552418170675001db2c56dc4a0ccbf67288a36 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 2 Oct 2023 21:01:10 +0100 Subject: [PATCH 05/10] Only ask about terminateDebugee once rather than for every session in the tree --- python3/vimspector/debug_session.py | 138 +++++++++++++++------------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 415e8d2bb..0ede8984a 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -569,16 +569,29 @@ def OnChannelClosed( self ): self._connection = None + def _StopNextSession( self, terminateDebuggee, then ): + if self.child_sessions: + c = self.child_sessions.pop() + c._StopNextSession( terminateDebuggee, + then = lambda: self._StopNextSession( + terminateDebuggee, + then ) ) + elif self._connection: + self._StopDebugAdapter( terminateDebuggee, callback = then ) + elif then: + then() + def StopAllSessions( self, interactive = False, then = None ): - def Next(): - if self.child_sessions: - c = self.child_sessions.pop() - c.StopAllSessions( interactive = interactive, then = Next ) - elif self._connection: - self._StopDebugAdapter( interactive = interactive, callback = then ) - else: - then() - Next() + if not interactive: + self._StopNextSession( None, then ) + elif not self._server_capabilities.get( 'supportTerminateDebuggee' ): + self._StopNextSession( None, then ) + elif not self._stackTraceView.AnyThreadsRunning(): + self._StopNextSession( None, then ) + else: + self._ConfirmTerminateDebugee( + lambda terminateDebuggee: self._StopNextSession( terminateDebuggee, + then ) ) @ParentOnly() @IfConnected() @@ -1490,65 +1503,62 @@ def _StartDebugAdapter( self ): self._logger.info( 'Debug Adapter Started' ) return True - def _StopDebugAdapter( self, interactive = False, callback = None ): + def _StopDebugAdapter( self, terminateDebuggee, callback ): arguments = {} - def disconnect(): - self._splash_screen = utils.DisplaySplash( - self._api_prefix, - self._splash_screen, - f"Shutting down debug adapter for session {self.DisplayName()}..." ) - - def handler( *args ): - self._splash_screen = utils.HideSplash( self._api_prefix, - self._splash_screen ) - - if callback: - self._logger.debug( "Setting server exit handler before disconnect" ) - assert not self._run_on_server_exit - self._run_on_server_exit = callback - - vim.eval( 'vimspector#internal#{}#StopDebugSession( {} )'.format( - self._connection_type, - self.session_id ) ) - - self._connection.DoRequest( - handler, - { - 'command': 'disconnect', - 'arguments': arguments, - }, - failure_handler = handler, - timeout = self._connection.sync_timeout ) + if terminateDebuggee is not None: + arguments[ 'terminateDebuggee' ] = terminateDebuggee - if not interactive: - disconnect() - elif not self._server_capabilities.get( 'supportTerminateDebuggee' ): - disconnect() - elif not self._stackTraceView.AnyThreadsRunning(): - disconnect() - else: - def handle_choice( choice ): - if choice == 1: - # yes - arguments[ 'terminateDebuggee' ] = True - elif choice == 2: - # no - arguments[ 'terminateDebuggee' ] = False - elif choice <= 0: - # Abort - return - # Else, use server default - - disconnect() - - utils.Confirm( self._api_prefix, - "Terminate debuggee?", - handle_choice, - default_value = 3, - options = [ '(Y)es', '(N)o', '(D)efault' ], - keys = [ 'y', 'n', 'd' ] ) + self._splash_screen = utils.DisplaySplash( + self._api_prefix, + self._splash_screen, + f"Shutting down debug adapter for session {self.DisplayName()}..." ) + + def handler( *args ): + self._splash_screen = utils.HideSplash( self._api_prefix, + self._splash_screen ) + + if callback: + self._logger.debug( "Setting server exit handler before disconnect" ) + assert not self._run_on_server_exit + self._run_on_server_exit = callback + + vim.eval( 'vimspector#internal#{}#StopDebugSession( {} )'.format( + self._connection_type, + self.session_id ) ) + + self._connection.DoRequest( + handler, + { + 'command': 'disconnect', + 'arguments': arguments, + }, + failure_handler = handler, + timeout = self._connection.sync_timeout ) + + + def _ConfirmTerminateDebugee( self, then ): + def handle_choice( choice ): + terminateDebuggee = None + if choice == 1: + # yes + terminateDebuggee = True + elif choice == 2: + # no + terminateDebuggee = False + elif choice <= 0: + # Abort + return + # Else, use server default + + then( terminateDebuggee ) + utils.Confirm( self._api_prefix, + "Terminate debuggee?", + handle_choice, + default_value = 3, + options = [ '(Y)es', '(N)o', '(D)efault' ], + keys = [ 'y', 'n', 'd' ] ) def _PrepareAttach( self, adapter_config, launch_config ): attach_config = adapter_config.get( 'attach' ) From d0c23329e02d503e815096f2c8ae6bab18c56d23 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 25 Nov 2023 16:54:55 +0000 Subject: [PATCH 06/10] Add timeout to individual scripts from test runner, as sometimes vim just gets stuck Centralise where timeouts are configured in tests --- .github/workflows/build.yaml | 2 +- CONTRIBUTING.md | 13 +++++++++++++ run_tests | 11 +++++++++-- tests/lib/autoload/vimspector/test/setup.vim | 4 ++-- tests/lib/autoload/vimspector/test/signs.vim | 4 ++-- tests/lib/plugin/shared.vim | 4 ++-- tests/lib/run_test.vim | 5 +++++ tests/tabpage.test.vim | 2 +- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 021ee8409..8851df2c5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -122,7 +122,7 @@ jobs: brew update-reset brew doctor || true brew cleanup || true - for p in vim go tcl-tk llvm lua luajit love neovim; do + for p in vim go tcl-tk llvm lua luajit love neovim coreutils; do brew install $p || brew outdated $p || brew upgrade $p done brew reinstall icu4c diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f0876b29..3c0d363ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -173,6 +173,19 @@ When contributing pull requests, I ask that: ### Running the tests locally +Requirements for running the tests: + +* Linux or macOS +* Supported Vim or Neovim version (ideally both) +* acsiinema instaled (`pip3 install --user asciinema`) +* `timeout` installed (on macOS: `brew install coreutils`) +* various other dependencies for the individual debuggers. + +The simplest way to run the tests is using the container image, as all +dependencies are there for you. If you decide not to, then the best way to work +out what's required is to look at either the `Dockerfile` (for Linux) or the +`.github/workflows/build.yaml` (for macOS). + There are 2 ways: 1. In the docker container. The CI tests for linux run in a container, so as to diff --git a/run_tests b/run_tests index 31f270e29..49c1cdc6c 100755 --- a/run_tests +++ b/run_tests @@ -129,10 +129,17 @@ VERSION_OPTIONS=$([ "$IS_NVIM" -ne 0 ] && echo '--headless' || echo '--not-a-ter RUN_VIM="${VIM_EXE} -N --clean ${VERSION_OPTIONS}" +if which timeout >/dev/null 2>&1; then + TIMEOUT_SCRIPT="timeout --verbose --foreground --kill-after 7m 5m" +else + TIMEOUT_SCRIPT="" + echo "WARNING: No timeout found, not using timeout" +fi + # There seems to be a race condition with the way asciinema sets the pty size. # It does fork() then if is_child: execvpe(...) else: set_pty_size(). This means # the child might read the pty size before it's set. So we use a 1s sleepy. -RUN_TEST="sleep 1 && ${RUN_VIM} -S lib/run_test.vim" +RUN_TEST="sleep 1 && ${TIMEOUT_SCRIPT} ${RUN_VIM} -S lib/run_test.vim" # 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 @@ -143,7 +150,7 @@ fi # Basic pre-flight check that vim and python work # ffs neovim https://github.com/neovim/neovim/issues/14438 -if ${RUN_VIM} -u support/test_python3.vim .fail; then +if ${TIMEOUT_SCRIPT} ${RUN_VIM} -u support/test_python3.vim .fail; then rm -f .fail echo "$VIM_EXE appears to have working python3 support. Let's go..." else diff --git a/tests/lib/autoload/vimspector/test/setup.vim b/tests/lib/autoload/vimspector/test/setup.vim index 7ab0101e4..c1bedd1c7 100644 --- a/tests/lib/autoload/vimspector/test/setup.vim +++ b/tests/lib/autoload/vimspector/test/setup.vim @@ -66,7 +66,7 @@ function! vimspector#test#setup#WaitForReset() abort call WaitForAssert( {-> \ assert_true( pyxeval( '_vimspector_session is None or ' . \ '_vimspector_session._uiTab is None' ) ) - \ }, 10000 ) + \ }, g:test_long_timeout ) call vimspector#test#signs#AssertSignGroupEmpty( 'VimspectorCode' ) endfunction @@ -80,7 +80,7 @@ function! vimspector#test#setup#WaitForSessionReset( session_id ) abort call WaitForAssert( {-> \ assert_true( pyxeval( s ..' is None or ' . \ s .. '._uiTab is None' ) ) - \ }, 10000 ) + \ }, g:test_long_timeout ) endfunction function! vimspector#test#setup#Reset() abort diff --git a/tests/lib/autoload/vimspector/test/signs.vim b/tests/lib/autoload/vimspector/test/signs.vim index 3e78bf719..39d1cdaef 100644 --- a/tests/lib/autoload/vimspector/test/signs.vim +++ b/tests/lib/autoload/vimspector/test/signs.vim @@ -10,10 +10,10 @@ function! vimspector#test#signs#AssertCursorIsAtLineInBuffer( buffer, \ assert_equal( fnamemodify( a:buffer, ':p' ), \ fnamemodify( bufname( '%' ), ':p' ), \ 'Current buffer' ) - \ }, 15000 ) + \ }, g:test_long_timeout ) call WaitForAssert( {-> \ assert_equal( a:line, line( '.' ), 'Current line' ) - \ }, 15000 ) + \ }, g:test_long_timeout ) if a:column isnot v:null call assert_equal( a:column, col( '.' ), 'Current column' ) endif diff --git a/tests/lib/plugin/shared.vim b/tests/lib/plugin/shared.vim index 589b4b2e4..60a61c28b 100644 --- a/tests/lib/plugin/shared.vim +++ b/tests/lib/plugin/shared.vim @@ -17,7 +17,7 @@ endif " When running into the timeout an exception is thrown, thus the function does " not return. func WaitFor(expr, ...) - let timeout = get(a:000, 0, 10000) + let timeout = get(a:000, 0, g:test_long_timeout) let slept = s:WaitForCommon(a:expr, v:null, timeout) if slept < 0 throw 'WaitFor() timed out after ' . timeout . ' msec' @@ -34,7 +34,7 @@ endfunc " " Return zero for success, one for failure (like the assert function). func WaitForAssert(assert, ...) - let timeout = get(a:000, 0, 5000) + let timeout = get(a:000, 0, g:test_timeout) if s:WaitForCommon(v:null, a:assert, timeout) < 0 return 1 endif diff --git a/tests/lib/run_test.vim b/tests/lib/run_test.vim index d2d8bccb8..a12cad1f4 100644 --- a/tests/lib/run_test.vim +++ b/tests/lib/run_test.vim @@ -39,6 +39,11 @@ let g:vimspector_batch_mode = 1 " Let a test take up to 1 minute, unless debugging let s:single_test_timeout = 60000 +" Timeouts in milliseconds +let g:test_long_timeout = 15000 +let g:test_timeout = 5000 +let g:test_short_timeout = 1000 + " Restrict the runtimepath to the exact minimum needed for testing let &runtimepath = getcwd() . '/lib' set runtimepath+=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after diff --git a/tests/tabpage.test.vim b/tests/tabpage.test.vim index 6b40845da..05a21c2b0 100644 --- a/tests/tabpage.test.vim +++ b/tests/tabpage.test.vim @@ -33,7 +33,7 @@ function! Test_Step_With_Different_Tabpage() let vimspector_tabnr = tabpagenr() call WaitForAssert( {-> \ assert_equal( 'simple.cpp', bufname( '%' ), 'Current buffer' ) - \ }, 10000 ) + \ }, g:test_long_timeout ) call assert_equal( 15, line( '.' ), 'Current line' ) call assert_equal( 1, col( '.' ), 'Current column' ) From 8a4dc1ec1b816595f267a910978ed2a437bbdec8 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 25 Nov 2023 18:10:59 +0000 Subject: [PATCH 07/10] Fix invalid escape exception with python 3.12 --- python3/vimspector/vendor/json_minify.py | 2 +- tests/mappings.test.vim | 2 +- tests/memory.test.vim | 4 ++-- tests/variables.test.vim | 6 +++--- tests/variables_compact.test.vim | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/python3/vimspector/vendor/json_minify.py b/python3/vimspector/vendor/json_minify.py index 3d818238e..7261bb644 100644 --- a/python3/vimspector/vendor/json_minify.py +++ b/python3/vimspector/vendor/json_minify.py @@ -40,7 +40,7 @@ # Vimspector modification: strip_space defaults to False; we don't actually want # to minify - we just want to strip comments. def minify(string, strip_space=False): - tokenizer = re.compile('"|(/\*)|(\*/)|(//)|\n|\r') + tokenizer = re.compile(r'"|(/\*)|(\*/)|(//)|\n|\r') end_slashes_re = re.compile(r'(\\)*$') in_string = False diff --git a/tests/mappings.test.vim b/tests/mappings.test.vim index fef754287..fc1102c2f 100644 --- a/tests/mappings.test.vim +++ b/tests/mappings.test.vim @@ -354,7 +354,7 @@ function! Test_Partial_Mappings_Dict_Override() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( "\\100\", "xt" )' ) EOF call WaitForAssert( {-> diff --git a/tests/memory.test.vim b/tests/memory.test.vim index f69ae6759..6ae6650e2 100644 --- a/tests/memory.test.vim +++ b/tests/memory.test.vim @@ -111,7 +111,7 @@ function! Test_DumpMemory_VariableWindow() py3 <5\\", "xt" )' ) + vim.eval( r'feedkeys( ",m\5\\", "xt" )' ) EOF call WaitForAssert( {-> \ AssertMatchList( @@ -238,7 +238,7 @@ function! Test_DumpMemory_WatchWindow() py3 <1\\", "xt" )' ) + vim.eval( r'feedkeys( ",m\1\\", "xt" )' ) EOF call WaitForAssert( {-> \ AssertMatchList( diff --git a/tests/variables.test.vim b/tests/variables.test.vim index 4eb6a6aac..1b1e17175 100644 --- a/tests/variables.test.vim +++ b/tests/variables.test.vim @@ -1027,7 +1027,7 @@ function! Test_SetVariableValue_Local() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( "\\100\", "xt" )' ) EOF call WaitForAssert( {-> @@ -1152,7 +1152,7 @@ function! Test_SetVariableValue_Watch() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( ",\\100\", "xt" )' ) EOF @@ -1244,7 +1244,7 @@ function! Test_SetVariableValue_Balloon() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( "\\100\", "xt" )' ) EOF call WaitForAssert( {-> diff --git a/tests/variables_compact.test.vim b/tests/variables_compact.test.vim index 525c6c4bf..c5a30935b 100644 --- a/tests/variables_compact.test.vim +++ b/tests/variables_compact.test.vim @@ -899,7 +899,7 @@ function! Test_SetVariableValue_Local() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( "\\100\", "xt" )' ) EOF call WaitForAssert( {-> @@ -1024,7 +1024,7 @@ function! Test_SetVariableValue_Watch() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( ",\\100\", "xt" )' ) EOF @@ -1116,7 +1116,7 @@ function! Test_SetVariableValue_Balloon() py3 <\100\", "xt" )' ) + vim.eval( r'feedkeys( "\\100\", "xt" )' ) EOF call WaitForAssert( {-> From 02e904641636d7e55d1734da4f2a7cdf4feef44c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 26 Nov 2023 19:12:03 +0000 Subject: [PATCH 08/10] Validate working vim python in test driver --- run_tests | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/run_tests b/run_tests index 49c1cdc6c..8768fb184 100755 --- a/run_tests +++ b/run_tests @@ -136,6 +136,14 @@ else echo "WARNING: No timeout found, not using timeout" fi +# Check that vim's python support actual works, else we can't run tests +if ! ${TIMEOUT_SCRIPT} ${RUN_VIM} --cmd "py3 import vim; vim.command( 'qa!' )" --cmd "cquit!"; then + echo "Vim's python support doesn't work. Cannot run tests" >&2 + exit 1 +else + echo "Vim's python support works. Let's go..." +fi + # There seems to be a race condition with the way asciinema sets the pty size. # It does fork() then if is_child: execvpe(...) else: set_pty_size(). This means # the child might read the pty size before it's set. So we use a 1s sleepy. From 01bf39486ef3dac3186ed83782913ebd7064a71d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 26 Nov 2023 19:39:36 +0000 Subject: [PATCH 09/10] Neovim doesn't work with Python 3.12 Use pynvim master (for now?) --- .github/workflows/build.yaml | 6 ++++-- tests/vimrc | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8851df2c5..9b50e2384 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -126,10 +126,12 @@ jobs: brew install $p || brew outdated $p || brew upgrade $p done brew reinstall icu4c - brew link --overwrite python + # neovim doesn't work on python 3.12 + brew link --overwrite python@3.11 brew link --overwrite vim brew link --overwrite go - pip3 install --user neovim + # https://github.com/neovim/pynvim/issues/538 + pip install --user 'pynvim @ git+https://github.com/neovim/pynvim' name: 'Install vim and deps' - name: 'Install requirements' diff --git a/tests/vimrc b/tests/vimrc index ad2208896..9b84e8c4d 100644 --- a/tests/vimrc +++ b/tests/vimrc @@ -9,6 +9,11 @@ if exists( '$VIMSPECTOR_TEST_BASE' ) let g:vimspector_base_dir = g:vimspector_test_plugin_path .. '/' .. $VIMSPECTOR_TEST_BASE endif +if has('nvim' ) && exists( 'g:vimspector_base_dir' ) + \ && isdirectory( g:vimspector_base_dir . '/nvim_env' ) + let g:python3_host_prog = g:vimspector_base_dir . '/nvim_env/bin/python' +endif + let &runtimepath = &runtimepath . ',' . g:vimspector_test_plugin_path filetype plugin indent on From 0f93928ad7607c3a750547f084b1dd75246d1943 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sun, 26 Nov 2023 20:17:00 +0000 Subject: [PATCH 10/10] Actually use python 3.12 for neovim --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9b50e2384..90c66d4e2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -126,12 +126,12 @@ jobs: brew install $p || brew outdated $p || brew upgrade $p done brew reinstall icu4c - # neovim doesn't work on python 3.12 - brew link --overwrite python@3.11 + brew link --overwrite python brew link --overwrite vim brew link --overwrite go + # latest neovim doesn't work on python 3.12 # https://github.com/neovim/pynvim/issues/538 - pip install --user 'pynvim @ git+https://github.com/neovim/pynvim' + pip3 install --user 'pynvim @ git+https://github.com/neovim/pynvim' name: 'Install vim and deps' - name: 'Install requirements'