Merge pull request #297 from puremourning/threads
[WIP] Improve Threads Handling
This commit is contained in:
commit
23130d74ad
18 changed files with 991 additions and 194 deletions
145
README.md
145
README.md
|
|
@ -11,16 +11,17 @@ For a tutorial and usage overview, take a look at the
|
|||
* [Supported languages](#supported-languages)
|
||||
* [Other languages](#other-languages)
|
||||
* [Installation](#installation)
|
||||
* [Quick Start](#quick-start)
|
||||
* [Dependencies](#dependencies)
|
||||
* [Neovim differences](#neovim-differences)
|
||||
* [Windows differences](#windows-differences)
|
||||
* [Clone the plugin](#clone-the-plugin)
|
||||
* [Trying it out](#trying-it-out)
|
||||
* [Cloning the plugin](#cloning-the-plugin)
|
||||
* [Install some gadgets](#install-some-gadgets)
|
||||
* [VimspectorInstall and VimspectorUpdate commands](#vimspectorinstall-and-vimspectorupdate-commands)
|
||||
* [install_gadget.py](#install_gadgetpy)
|
||||
* [Manual gadget installation](#manual-gadget-installation)
|
||||
* [The gadget directory](#the-gadget-directory)
|
||||
* [Trying it out](#trying-it-out)
|
||||
* [Upgrade](#upgrade)
|
||||
* [About](#about)
|
||||
* [Background](#background)
|
||||
|
|
@ -33,9 +34,11 @@ For a tutorial and usage overview, take a look at the
|
|||
* [Launch and attach by PID:](#launch-and-attach-by-pid)
|
||||
* [Launch with options](#launch-with-options)
|
||||
* [Debug configuration selection](#debug-configuration-selection)
|
||||
* [Get configurations](#get-configurations)
|
||||
* [Breakpoints](#breakpoints)
|
||||
* [Exception breakpoints](#exception-breakpoints)
|
||||
* [Clear breakpoints](#clear-breakpoints)
|
||||
* [Run to Cursor](#run-to-cursor)
|
||||
* [Stepping](#stepping)
|
||||
* [Variables and scopes](#variables-and-scopes)
|
||||
* [Watches](#watches)
|
||||
|
|
@ -48,6 +51,7 @@ For a tutorial and usage overview, take a look at the
|
|||
* [Closing debugger](#closing-debugger)
|
||||
* [Debug adapter configuration](#debug-adapter-configuration)
|
||||
* [C, C , Rust, etc.](#c-c-rust-etc)
|
||||
* [Rust](#rust)
|
||||
* [Remote debugging](#remote-debugging)
|
||||
* [Remote launch and attach](#remote-launch-and-attach)
|
||||
* [Python](#python)
|
||||
|
|
@ -65,7 +69,6 @@ For a tutorial and usage overview, take a look at the
|
|||
* [Usage with YouCompleteMe](#usage-with-youcompleteme)
|
||||
* [Other LSP clients](#other-lsp-clients)
|
||||
* [Lua](#lua)
|
||||
* [Rust](#rust)
|
||||
* [Other servers](#other-servers)
|
||||
* [Customisation](#customisation)
|
||||
* [Changing the default signs](#changing-the-default-signs)
|
||||
|
|
@ -80,7 +83,7 @@ For a tutorial and usage overview, take a look at the
|
|||
* [License](#license)
|
||||
* [Sponsorship](#sponsorship)
|
||||
|
||||
<!-- Added by: ben, at: Fri 4 Sep 2020 00:48:17 BST -->
|
||||
<!-- Added by: ben, at: Sun 22 Nov 2020 14:35:00 GMT -->
|
||||
|
||||
<!--te-->
|
||||
|
||||
|
|
@ -121,8 +124,6 @@ And a couple of brief demos:
|
|||
- logging/stdout display
|
||||
- simple stable API for custom tooling (e.g. integrate with language server)
|
||||
|
||||
For other languages, you'll need some other way to install the gadget.
|
||||
|
||||
## Supported languages
|
||||
|
||||
The following table lists the languages that are "built-in" (along with their
|
||||
|
|
@ -158,10 +159,12 @@ To use Vimspector with a language that's not "built-in", see this
|
|||
|
||||
# Installation
|
||||
|
||||
## Quick Start
|
||||
|
||||
There are 2 installation methods:
|
||||
|
||||
* Using a release tarball, or
|
||||
* Manually
|
||||
* Using a release tarball and vim packages
|
||||
* Using a clone of the repo (e.g. package manager)
|
||||
|
||||
Release tarballs come with debug adapters for the default languages
|
||||
pre-packaged. To use a release tarball:
|
||||
|
|
@ -174,15 +177,27 @@ $ mkdir -p $HOME/.vim/pack
|
|||
$ curl -L <url> | tar -C $HOME/.vim/pack zxvf -
|
||||
```
|
||||
|
||||
3. Add `packadd! vimspector` to you `.vimrc`
|
||||
|
||||
3. Configure your project's debug profiles (create `.vimspector.json`)
|
||||
|
||||
Alternatively, you can clone the repo and select which gadgets are installed:
|
||||
|
||||
1. Check the dependencies
|
||||
1. Install the plugin as a Vim package. See `:help packages`.
|
||||
2. Install some 'gadgets' (debug adapters)
|
||||
2. Add `packadd! vimspector` to you `.vimrc`
|
||||
2. Install some 'gadgets' (debug adapters) - see `:VimspectorInstall ...`
|
||||
3. Configure your project's debug profiles (create `.vimspector.json`)
|
||||
|
||||
If you prefer to use a plugin manager, see the plugin manager's docs. For
|
||||
Vundle, use:
|
||||
|
||||
```vim
|
||||
Plugin 'puremourning/vimspector'
|
||||
```
|
||||
|
||||
The following sections expand on the above brief overview.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Vimspector requires:
|
||||
|
|
@ -239,20 +254,54 @@ The following features are not implemented for Windows:
|
|||
|
||||
* Tailing the vimspector log in the Output Window.
|
||||
|
||||
## Clone the plugin
|
||||
## Trying it out
|
||||
|
||||
If you just want to try out vimspector without changing your vim config, there
|
||||
are example projects for a number of languages in `support/test`, including:
|
||||
|
||||
* Python (`support/test/python/simple_python`)
|
||||
* Go (`support/test/go/hello_world`)
|
||||
* Nodejs (`support/test/node/simple`)
|
||||
* Chrome (`support/test/chrome/`)
|
||||
* etc.
|
||||
|
||||
To test one of these out, cd to the directory and run:
|
||||
|
||||
```
|
||||
vim -Nu /path/to/vimspector/tests/vimrc --cmd "let g:vimspector_enable_mappings='HUMAN'"
|
||||
```
|
||||
|
||||
Then press `<F5>`.
|
||||
|
||||
There's also a C++ project in `tests/testdata/cpp/simple/` with a `Makefile`
|
||||
which can be used to check everything is working. This is used by the regression
|
||||
tests in CI so should always work, and is a good way to check if the problem is
|
||||
your configuration rather than a bug.
|
||||
|
||||
## Cloning the plugin
|
||||
|
||||
If you're not using a release tarball, you'll need to clone this repo to the
|
||||
appropriate place.
|
||||
|
||||
1. Clone the plugin
|
||||
|
||||
There are many Vim plugin managers, and I'm not going to state a particular
|
||||
preference, so if you choose to use one, you're on your own with installation
|
||||
issues.
|
||||
preference, so if you choose to use one, follow the plugin manager's
|
||||
documentation. For example, for Vundle, use:
|
||||
|
||||
Install vimspector as a Vim package, either by cloning this repository into your
|
||||
package path, like this:
|
||||
```viml
|
||||
Plugin 'puremourning/vimspector'
|
||||
```
|
||||
|
||||
If you don't use a plugin manager already, install vimspector as a Vim package
|
||||
by cloning this repository into your package path, like this:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/puremourning/vimspector ~/.vim/pack/vimspector/opt/vimspector
|
||||
```
|
||||
|
||||
2. Configure vimspector in your `.vimrc`:
|
||||
2. Configure vimspector in your `.vimrc`, for example to enable the standard
|
||||
mapings:
|
||||
|
||||
```viml
|
||||
let g:vimspector_enable_mappings = 'HUMAN'
|
||||
|
|
@ -265,7 +314,7 @@ let g:vimspector_enable_mappings = 'HUMAN'
|
|||
packadd! vimspector
|
||||
```
|
||||
|
||||
See support/doc/example_vimrc.vim.
|
||||
See support/doc/example_vimrc.vim for a minimal example.
|
||||
|
||||
## Install some gadgets
|
||||
|
||||
|
|
@ -281,11 +330,10 @@ There are a few ways to do this:
|
|||
installed for you.
|
||||
* Using `:VimspectorInstall <adapter> <args...>` (use TAB `wildmenu` to see the
|
||||
options, also accepts any `install_gadget.py` option)
|
||||
* Alternatively, using `python3 install_gadget.py <args>` (use `--help` to see
|
||||
all options)
|
||||
* When attempting to launch a debug configuration, if the configured adapter
|
||||
can't be found, vimspector might suggest installing one.
|
||||
* Use `:VimspectorUpdate` to install the latest supported versions of the
|
||||
* Using `python3 install_gadget.py <args>` (use `--help` to see all options)
|
||||
* Attempting to launch a debug configuration; if the configured adapter
|
||||
can't be found, vimspector will suggest installing one.
|
||||
* Using `:VimspectorUpdate` to install the latest supported versions of the
|
||||
gadgets.
|
||||
|
||||
Here's a demo of doing somee installs and an upgrade:
|
||||
|
|
@ -293,7 +341,7 @@ Here's a demo of doing somee installs and an upgrade:
|
|||
[](https://asciinema.org/a/Hfu4ZvuyTZun8THNen9FQbTay)
|
||||
|
||||
Both `install_gadget.py` and `:VimspectorInstall` do the same set of things,
|
||||
though the default behaviours are slightly different. For supported languages,
|
||||
though the default behaviours are slightly different. For supported languages,
|
||||
they will:
|
||||
|
||||
* Download the relevant debug adapter at a version that's been tested from the
|
||||
|
|
@ -306,7 +354,7 @@ they will:
|
|||
broken in this regard.
|
||||
* Set up the `gadgetDir` symlinks for the platform.
|
||||
|
||||
To install the tested debug adapter for a language, run:
|
||||
For example, to install the tested debug adapter for a language, run:
|
||||
|
||||
| To install | Script | Command |
|
||||
| --- | --- | --- |
|
||||
|
|
@ -468,30 +516,6 @@ Vimspector will also load any fies matching:
|
|||
format as `.gadgets.json` but are not overwritten when running
|
||||
`install_gadget.py`.
|
||||
|
||||
## Trying it out
|
||||
|
||||
If you just want to try out vimspector without changing your vim config, there
|
||||
are example projects for a number of languages in `support/test`, including:
|
||||
|
||||
* Python (`support/test/python/simple_python`)
|
||||
* Go (`support/test/go/hello_world`)
|
||||
* Nodejs (`support/test/node/simple`)
|
||||
* Chrome (`support/test/chrome/`)
|
||||
* etc.
|
||||
|
||||
To test one of these out, cd to the directory and run:
|
||||
|
||||
```
|
||||
vim -Nu /path/to/vimspector/tests/vimrc --cmd "let g:vimspector_enable_mappings='HUMAN'"
|
||||
```
|
||||
|
||||
Then press `<F5>`.
|
||||
|
||||
There's also a C++ project in `tests/testdata/cpp/simple/` with a `Makefile`
|
||||
which can be used to check everything is working. This is used by the regression
|
||||
tests in CI so should always work, and is a good way to check if the problem is
|
||||
your configuration rather than a bug.
|
||||
|
||||
## Upgrade
|
||||
|
||||
After updating the Vimspector code (either via `git pull` or whatever package
|
||||
|
|
@ -751,6 +775,9 @@ Scopes and variables are represented by the buffer `vimspector.Variables`.
|
|||
|
||||
## Watches
|
||||
|
||||
The watch window is used to inspect variables and expressions. Expressions are
|
||||
evaluated in the selected stack frame which is "focussed"
|
||||
|
||||
The watches window is a prompt buffer, where that's available. Enter insert mode
|
||||
to add a new watch expression.
|
||||
|
||||
|
|
@ -767,7 +794,7 @@ The watches are represented by the buffer `vimspector.StackTrace`.
|
|||
|
||||
### Watch autocompletion
|
||||
|
||||
The watch prompt buffer has its `omnifunc` set to a function that will
|
||||
The watch prompt buffer has its `omnifunc` set to a function that will
|
||||
calcualte completion for the current expression. This is trivailly used with
|
||||
`<Ctrl-x><Ctrl-o>` (see `:help ins-completion`), or integrated with your
|
||||
favourite completion system. The filetype in the buffer is set to
|
||||
|
|
@ -783,8 +810,28 @@ let g:ycm_semantic_triggers = {
|
|||
|
||||
## Stack Traces
|
||||
|
||||
* In the threads window, use `<CR>`, or double-click with left mouse to expand/collapse.
|
||||
The stack trace window shows the state of each progream thread. Threads which
|
||||
are stopped can be expanded to show the strack trace of that thread.
|
||||
|
||||
Often, but not always, all threads are stopped when a breakpoint is hit. The
|
||||
status of a thread is show in parentheses after the thread's name. Where
|
||||
supported by the underlying debugger, threads can be paused and continued
|
||||
individually from within the Stack Trace window.
|
||||
|
||||
A particular thread, highlighted with the `CursorLine` highlight group is the
|
||||
"focussed" thread. This is the thread that receives commands like "Stop In",
|
||||
"Stop Out", "Continue" and "Pause" in the code window. The focussed thread can
|
||||
be changed manually to "switch to" that thread.
|
||||
|
||||
* Use `<CR>`, or double-click with left mouse to expand/collapse a thread stack
|
||||
trace, or use the WinBar button.
|
||||
* Use `<CR>`, or double-click with left mouse on a stack frame to jump to it.
|
||||
* Use the WinBar or `vimspector#PauseContinueThread()` to individually pause or
|
||||
continue the selected thread.
|
||||
* Use the "Focus" WinBar button, `<leader><CR>` or `vimspector#SetCurrentThread()`
|
||||
to set the "focussed" thread to the currently selected one. If the selected
|
||||
line is a stack frame, set the focussed thread to the thread of that frame and
|
||||
jump to that frame in the code window.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,20 @@ function! vimspector#Pause() abort
|
|||
py3 _vimspector_session.Pause()
|
||||
endfunction
|
||||
|
||||
function! vimspector#PauseContinueThread() abort
|
||||
if !s:Enabled()
|
||||
return
|
||||
endif
|
||||
py3 _vimspector_session.PauseContinueThread()
|
||||
endfunction
|
||||
|
||||
function! vimspector#SetCurrentThread() abort
|
||||
if !s:Enabled()
|
||||
return
|
||||
endif
|
||||
py3 _vimspector_session.SetCurrentThread()
|
||||
endfunction
|
||||
|
||||
function! vimspector#Stop() abort
|
||||
if !s:Enabled()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ function! vimspector#internal#state#Reset() abort
|
|||
catch /.*/
|
||||
echohl WarningMsg
|
||||
echom 'Exception while loading vimspector:' v:exception
|
||||
echom 'From:' v:throwpoint
|
||||
echom 'Vimspector unavailable: Requires Vim compiled with Python 3.6'
|
||||
echohl None
|
||||
return v:false
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class CodeView( object ):
|
|||
self._logger = logging.getLogger( __name__ )
|
||||
utils.SetUpLogging( self._logger )
|
||||
|
||||
# FIXME: This ID is by group, so should be module scope
|
||||
self._next_sign_id = 1
|
||||
self._breakpoints = defaultdict( list )
|
||||
self._signs = {
|
||||
|
|
|
|||
|
|
@ -429,39 +429,90 @@ class DebugSession( object ):
|
|||
},
|
||||
} )
|
||||
|
||||
self._stackTraceView.OnContinued()
|
||||
self._codeView.SetCurrentFrame( None )
|
||||
|
||||
@IfConnected()
|
||||
def StepInto( self ):
|
||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||
threadId = self._stackTraceView.GetCurrentThreadId()
|
||||
if threadId is None:
|
||||
return
|
||||
|
||||
self._connection.DoRequest( None, {
|
||||
def handler( *_ ):
|
||||
self._stackTraceView.OnContinued( { 'threadId': threadId } )
|
||||
self._codeView.SetCurrentFrame( None )
|
||||
|
||||
self._connection.DoRequest( handler, {
|
||||
'command': 'stepIn',
|
||||
'arguments': {
|
||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
||||
'threadId': threadId
|
||||
},
|
||||
} )
|
||||
|
||||
@IfConnected()
|
||||
def StepOut( self ):
|
||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||
threadId = self._stackTraceView.GetCurrentThreadId()
|
||||
if threadId is None:
|
||||
return
|
||||
|
||||
self._connection.DoRequest( None, {
|
||||
def handler( *_ ):
|
||||
self._stackTraceView.OnContinued( { 'threadId': threadId } )
|
||||
self._codeView.SetCurrentFrame( None )
|
||||
|
||||
self._connection.DoRequest( handler, {
|
||||
'command': 'stepOut',
|
||||
'arguments': {
|
||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
||||
'threadId': threadId
|
||||
},
|
||||
} )
|
||||
|
||||
|
||||
def Continue( self ):
|
||||
if self._connection:
|
||||
self._stackTraceView.Continue()
|
||||
else:
|
||||
if not self._connection:
|
||||
self.Start()
|
||||
return
|
||||
|
||||
threadId = self._stackTraceView.GetCurrentThreadId()
|
||||
if threadId is None:
|
||||
utils.UserMessage( 'No current thread', persist = True )
|
||||
return
|
||||
|
||||
def handler( msg ):
|
||||
self._stackTraceView.OnContinued( {
|
||||
'threadId': threadId,
|
||||
'allThreadsContinued': ( msg.get( 'body' ) or {} ).get(
|
||||
'allThreadsContinued',
|
||||
True )
|
||||
} )
|
||||
self._codeView.SetCurrentFrame( None )
|
||||
|
||||
self._connection.DoRequest( handler, {
|
||||
'command': 'continue',
|
||||
'arguments': {
|
||||
'threadId': threadId,
|
||||
},
|
||||
} )
|
||||
|
||||
@IfConnected()
|
||||
def Pause( self ):
|
||||
self._stackTraceView.Pause()
|
||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
||||
utils.UserMessage( 'No current thread', persist = True )
|
||||
return
|
||||
|
||||
self._connection.DoRequest( None, {
|
||||
'command': 'pause',
|
||||
'arguments': {
|
||||
'threadId': self._stackTraceView.GetCurrentThreadId(),
|
||||
},
|
||||
} )
|
||||
|
||||
@IfConnected()
|
||||
def PauseContinueThread( self ):
|
||||
self._stackTraceView.PauseContinueThread()
|
||||
|
||||
@IfConnected()
|
||||
def SetCurrentThread( self ):
|
||||
self._stackTraceView.SetCurrentThread()
|
||||
|
||||
@IfConnected()
|
||||
def ExpandVariable( self ):
|
||||
|
|
@ -1098,6 +1149,7 @@ class DebugSession( object ):
|
|||
def OnEvent_exited( self, message ):
|
||||
utils.UserMessage( 'The debugee exited with status code: {}'.format(
|
||||
message[ 'body' ][ 'exitCode' ] ) )
|
||||
self.SetCurrentFrame( None )
|
||||
|
||||
def OnEvent_process( self, message ):
|
||||
utils.UserMessage( 'The debugee was started: {}'.format(
|
||||
|
|
@ -1107,7 +1159,8 @@ class DebugSession( object ):
|
|||
pass
|
||||
|
||||
def OnEvent_continued( self, message ):
|
||||
pass
|
||||
self._stackTraceView.OnContinued( message[ 'body' ] )
|
||||
self._codeView.SetCurrentFrame( None )
|
||||
|
||||
def Clear( self ):
|
||||
self._codeView.Clear()
|
||||
|
|
@ -1142,6 +1195,7 @@ class DebugSession( object ):
|
|||
def OnEvent_terminated( self, message ):
|
||||
# We will handle this when the server actually exists
|
||||
utils.UserMessage( "Debugging was terminated by the server." )
|
||||
self.SetCurrentFrame( None )
|
||||
|
||||
def OnEvent_output( self, message ):
|
||||
if self._outputView:
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ DEFAULTS = {
|
|||
|
||||
# Signs
|
||||
'sign_priority': {
|
||||
'vimspectorPC': 200,
|
||||
'vimspectorPCBP': 200,
|
||||
'vimspectorBP': 9,
|
||||
'vimspectorBPCond': 9,
|
||||
'vimspectorBPDisabled': 9,
|
||||
'vimspectorPC': 200,
|
||||
'vimspectorPCBP': 200,
|
||||
'vimspectorBP': 9,
|
||||
'vimspectorBPCond': 9,
|
||||
'vimspectorBPDisabled': 9,
|
||||
'vimspectorCurrentThread': 200
|
||||
},
|
||||
|
||||
# Installer
|
||||
|
|
|
|||
|
|
@ -16,11 +16,75 @@
|
|||
import vim
|
||||
import os
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from vimspector import utils
|
||||
from vimspector import utils, signs
|
||||
|
||||
|
||||
class Thread:
|
||||
"""The state of a single thread."""
|
||||
PAUSED = 0
|
||||
RUNNING = 1
|
||||
TERMINATED = 3
|
||||
state = RUNNING
|
||||
|
||||
stopped_event: typing.Dict
|
||||
thread: typing.Dict
|
||||
stacktrace: typing.List[ typing.Dict ]
|
||||
id: str
|
||||
|
||||
def __init__( self, thread ):
|
||||
self.id = thread[ 'id' ]
|
||||
self.stopped_event = None
|
||||
self.Update( thread )
|
||||
|
||||
def Update( self, thread ):
|
||||
self.thread = thread
|
||||
self.stacktrace = None
|
||||
|
||||
def Paused( self, event ):
|
||||
self.state = Thread.PAUSED
|
||||
self.stopped_event = event
|
||||
|
||||
def Continued( self ):
|
||||
self.state = Thread.RUNNING
|
||||
self.stopped_event = None
|
||||
self.Collapse()
|
||||
|
||||
def Exited( self ):
|
||||
self.state = Thread.TERMINATED
|
||||
self.stopped_event = None
|
||||
|
||||
def State( self ):
|
||||
if self.state == Thread.PAUSED:
|
||||
return self.stopped_event.get( 'description' ) or 'paused'
|
||||
elif self.state == Thread.RUNNING:
|
||||
return 'running'
|
||||
return 'terminated'
|
||||
|
||||
def Expand( self, stack_trace ):
|
||||
self.stacktrace = stack_trace
|
||||
|
||||
def Collapse( self ):
|
||||
self.stacktrace = None
|
||||
|
||||
def IsExpanded( self ):
|
||||
return self.stacktrace is not None
|
||||
|
||||
def CanExpand( self ):
|
||||
return self.state == Thread.PAUSED
|
||||
|
||||
|
||||
class StackTraceView( object ):
|
||||
class ThreadRequestState:
|
||||
NO = 0
|
||||
REQUESTING = 1
|
||||
PENDING = 2
|
||||
|
||||
# FIXME: Make into a dict by id ?
|
||||
_threads: typing.List[ Thread ]
|
||||
_line_to_thread = typing.Dict[ int, Thread ]
|
||||
|
||||
def __init__( self, session, win ):
|
||||
self._logger = logging.getLogger( __name__ )
|
||||
utils.SetUpLogging( self._logger )
|
||||
|
|
@ -37,25 +101,43 @@ class StackTraceView( object ):
|
|||
self._sources = {}
|
||||
self._scratch_buffers = []
|
||||
|
||||
# FIXME: This ID is by group, so should be module scope
|
||||
self._next_sign_id = 1
|
||||
|
||||
utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
|
||||
utils.SetUpUIWindow( win )
|
||||
|
||||
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
||||
':<C-U>call vimspector#GoToFrame()<CR>' )
|
||||
vim.command( 'nnoremap <silent> <buffer> <2-LeftMouse> '
|
||||
':<C-U>call vimspector#GoToFrame()<CR>' )
|
||||
with utils.LetCurrentWindow( win ):
|
||||
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
||||
':<C-U>call vimspector#GoToFrame()<CR>' )
|
||||
vim.command( 'nnoremap <silent> <buffer> <leader><CR> '
|
||||
':<C-U>call vimspector#SetCurrentThread()<CR>' )
|
||||
vim.command( 'nnoremap <silent> <buffer> <2-LeftMouse> '
|
||||
':<C-U>call vimspector#GoToFrame()<CR>' )
|
||||
|
||||
if utils.UseWinBar():
|
||||
vim.command( 'nnoremenu 1.1 WinBar.Pause/Continue '
|
||||
':call vimspector#PauseContinueThread()<CR>' )
|
||||
vim.command( 'nnoremenu 1.2 WinBar.Expand/Collapse '
|
||||
':call vimspector#GoToFrame()<CR>' )
|
||||
vim.command( 'nnoremenu 1.3 WinBar.Focus '
|
||||
':call vimspector#SetCurrentThread()<CR>' )
|
||||
|
||||
win.options[ 'cursorline' ] = False
|
||||
|
||||
|
||||
if not signs.SignDefined( 'vimspectorCurrentThread' ):
|
||||
signs.DefineSign( 'vimspectorCurrentThread',
|
||||
text = '▶ ',
|
||||
double_text = '▶',
|
||||
texthl = 'MatchParen',
|
||||
linehl = 'CursorLine' )
|
||||
|
||||
self._line_to_frame = {}
|
||||
self._line_to_thread = {}
|
||||
|
||||
# TODO: We really need a proper state model
|
||||
#
|
||||
# AWAIT_CONNECTION -- OnServerReady / RequestThreads --> REQUESTING_THREADS
|
||||
# REQUESTING -- OnGotThreads / RequestScopes --> REQUESTING_SCOPES
|
||||
#
|
||||
# When we attach using gdbserver, this whole thing breaks because we request
|
||||
# the threads over and over and get duff data back on later threads.
|
||||
self._requesting_threads = False
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||
self._pending_thread_request = None
|
||||
|
||||
|
||||
def GetCurrentThreadId( self ):
|
||||
|
|
@ -68,14 +150,19 @@ class StackTraceView( object ):
|
|||
self._current_frame = None
|
||||
self._current_thread = None
|
||||
self._current_syntax = ""
|
||||
self._threads = []
|
||||
self._threads.clear()
|
||||
self._sources = {}
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||
self._pending_thread_request = None
|
||||
if self._next_sign_id:
|
||||
signs.UnplaceSign( self._next_sign_id, 'VimspectorStackTrace' )
|
||||
self._next_sign_id = 0
|
||||
|
||||
with utils.ModifiableScratchBuffer( self._buf ):
|
||||
utils.ClearBuffer( self._buf )
|
||||
|
||||
def ConnectionUp( self, connection ):
|
||||
self._connection = connection
|
||||
self._requesting_threads = False
|
||||
|
||||
def ConnectionClosed( self ):
|
||||
self.Clear()
|
||||
|
|
@ -86,47 +173,88 @@ class StackTraceView( object ):
|
|||
utils.CleanUpHiddenBuffer( self._buf )
|
||||
for b in self._scratch_buffers:
|
||||
utils.CleanUpHiddenBuffer( b )
|
||||
|
||||
self._scratch_buffers = []
|
||||
self._buf = None
|
||||
|
||||
def LoadThreads( self, infer_current_frame ):
|
||||
pending_request = False
|
||||
if self._requesting_threads:
|
||||
pending_request = True
|
||||
def LoadThreads( self,
|
||||
infer_current_frame,
|
||||
reason = '',
|
||||
stopEvent = None ):
|
||||
if self._requesting_threads != StackTraceView.ThreadRequestState.NO:
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.PENDING
|
||||
self._pending_thread_request = ( infer_current_frame,
|
||||
reason,
|
||||
stopEvent )
|
||||
return
|
||||
|
||||
def consume_threads( message ):
|
||||
self._requesting_threads = False
|
||||
if self._requesting_threads == StackTraceView.ThreadRequestState.PENDING:
|
||||
# We may have hit a thread event, so try again.
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||
self.LoadThreads( *self._pending_thread_request )
|
||||
return
|
||||
|
||||
if not message[ 'body' ][ 'threads' ]:
|
||||
if pending_request:
|
||||
# We may have hit a thread event, so try again.
|
||||
self.LoadThreads( infer_current_frame )
|
||||
return
|
||||
else:
|
||||
# This is a protocol error. It is required to return at least one!
|
||||
utils.UserMessage( 'Server returned no threads. Is it running?',
|
||||
persist = True )
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||
self._pending_thread_request = None
|
||||
|
||||
if not ( message.get( 'body' ) or {} ).get( 'threads' ):
|
||||
# This is a protocol error. It is required to return at least one!
|
||||
utils.UserMessage( 'Protocol error: Server returned no threads',
|
||||
persist = False,
|
||||
error = True )
|
||||
return
|
||||
|
||||
existing_threads = self._threads[ : ]
|
||||
self._threads.clear()
|
||||
|
||||
for thread in message[ 'body' ][ 'threads' ]:
|
||||
if stopEvent is not None:
|
||||
stoppedThreadId = stopEvent.get( 'threadId' )
|
||||
allThreadsStopped = stopEvent.get( 'allThreadsStopped', False )
|
||||
|
||||
requesting = False
|
||||
|
||||
# FIXME: This is horribly inefficient
|
||||
for t in message[ 'body' ][ 'threads' ]:
|
||||
thread = None
|
||||
for existing_thread in existing_threads:
|
||||
if existing_thread.id == t[ 'id' ]:
|
||||
thread = existing_thread
|
||||
thread.Update( t )
|
||||
break
|
||||
|
||||
if not thread:
|
||||
thread = Thread( t )
|
||||
|
||||
self._threads.append( thread )
|
||||
|
||||
if infer_current_frame and thread[ 'id' ] == self._current_thread:
|
||||
self._LoadStackTrace( thread, True )
|
||||
elif infer_current_frame and self._current_thread is None:
|
||||
self._current_thread = thread[ 'id' ]
|
||||
self._LoadStackTrace( thread, True )
|
||||
# If the threads were requested due to a stopped event, update any
|
||||
# stopped thread state. Note we have to do this here (rather than in the
|
||||
# stopped event handler) because we must apply this event to any new
|
||||
# threads that are received here.
|
||||
if stopEvent:
|
||||
if allThreadsStopped:
|
||||
thread.Paused( stopEvent )
|
||||
elif stoppedThreadId is not None and thread.id == stoppedThreadId:
|
||||
thread.Paused( stopEvent )
|
||||
|
||||
self._DrawThreads()
|
||||
if infer_current_frame:
|
||||
if thread.id == self._current_thread:
|
||||
self._LoadStackTrace( thread, True, reason )
|
||||
requesting = True
|
||||
elif self._current_thread is None:
|
||||
self._current_thread = thread.id
|
||||
self._LoadStackTrace( thread, True, reason )
|
||||
requesting = True
|
||||
|
||||
if not requesting:
|
||||
self._DrawThreads()
|
||||
|
||||
def failure_handler( reason, msg ):
|
||||
# Make sure we request them again if the request fails
|
||||
self._requesting_threads = False
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||
self._pending_thread_request = None
|
||||
|
||||
self._requesting_threads = True
|
||||
self._requesting_threads = StackTraceView.ThreadRequestState.REQUESTING
|
||||
self._connection.DoRequest( consume_threads, {
|
||||
'command': 'threads',
|
||||
}, failure_handler )
|
||||
|
|
@ -135,28 +263,41 @@ class StackTraceView( object ):
|
|||
self._line_to_frame.clear()
|
||||
self._line_to_thread.clear()
|
||||
|
||||
if self._next_sign_id:
|
||||
signs.UnplaceSign( self._next_sign_id, 'VimspectorStackTrace' )
|
||||
else:
|
||||
self._next_sign_id = 1
|
||||
|
||||
with utils.ModifiableScratchBuffer( self._buf ):
|
||||
utils.ClearBuffer( self._buf )
|
||||
with utils.RestoreCursorPosition():
|
||||
utils.ClearBuffer( self._buf )
|
||||
|
||||
for thread in self._threads:
|
||||
icon = '+' if '_frames' not in thread else '-'
|
||||
for thread in self._threads:
|
||||
icon = '+' if not thread.IsExpanded() else '-'
|
||||
line = utils.AppendToBuffer(
|
||||
self._buf,
|
||||
f'{icon} Thread {thread.id}: {thread.thread["name"]} '
|
||||
f'({thread.State()})' )
|
||||
|
||||
line = utils.AppendToBuffer(
|
||||
self._buf,
|
||||
'{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
|
||||
if self._current_thread == thread.id:
|
||||
signs.PlaceSign( self._next_sign_id,
|
||||
'VimspectorStackTrace',
|
||||
'vimspectorCurrentThread',
|
||||
self._buf.name,
|
||||
line )
|
||||
|
||||
self._line_to_thread[ line ] = thread
|
||||
|
||||
self._DrawStackTrace( thread )
|
||||
self._line_to_thread[ line ] = thread
|
||||
self._DrawStackTrace( thread )
|
||||
|
||||
def _LoadStackTrace( self,
|
||||
thread,
|
||||
thread: Thread,
|
||||
infer_current_frame,
|
||||
reason = '' ):
|
||||
|
||||
def consume_stacktrace( message ):
|
||||
thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ]
|
||||
thread.Expand( message[ 'body' ][ 'stackFrames' ] )
|
||||
if infer_current_frame:
|
||||
for frame in thread[ '_frames' ]:
|
||||
for frame in thread.stacktrace:
|
||||
if self._JumpToFrame( frame, reason ):
|
||||
break
|
||||
|
||||
|
|
@ -165,30 +306,62 @@ class StackTraceView( object ):
|
|||
self._connection.DoRequest( consume_stacktrace, {
|
||||
'command': 'stackTrace',
|
||||
'arguments': {
|
||||
'threadId': thread[ 'id' ],
|
||||
'threadId': thread.id,
|
||||
}
|
||||
} )
|
||||
|
||||
def ExpandFrameOrThread( self ):
|
||||
|
||||
def _GetSelectedThread( self ) -> Thread:
|
||||
if vim.current.buffer != self._buf:
|
||||
return None
|
||||
|
||||
return self._line_to_thread.get( vim.current.window.cursor[ 0 ] )
|
||||
|
||||
|
||||
def GetSelectedThreadId( self ):
|
||||
thread = self._GetSelectedThread()
|
||||
return thread.id if thread else thread
|
||||
|
||||
def _SetCurrentThread( self, thread: Thread ):
|
||||
self._current_thread = thread.id
|
||||
self._DrawThreads()
|
||||
|
||||
def SetCurrentThread( self ):
|
||||
thread = self._GetSelectedThread()
|
||||
if thread:
|
||||
self._SetCurrentThread( thread )
|
||||
elif vim.current.buffer != self._buf:
|
||||
return
|
||||
elif vim.current.window.cursor[ 0 ] in self._line_to_frame:
|
||||
thread, frame = self._line_to_frame[ vim.current.window.cursor[ 0 ] ]
|
||||
self._SetCurrentThread( thread )
|
||||
self._JumpToFrame( frame )
|
||||
else:
|
||||
utils.UserMessage( "No thread selected" )
|
||||
|
||||
current_line = vim.current.window.cursor[ 0 ]
|
||||
def ExpandFrameOrThread( self ):
|
||||
thread = self._GetSelectedThread()
|
||||
|
||||
if current_line in self._line_to_frame:
|
||||
self._JumpToFrame( self._line_to_frame[ current_line ] )
|
||||
elif current_line in self._line_to_thread:
|
||||
thread = self._line_to_thread[ current_line ]
|
||||
if '_frames' in thread:
|
||||
del thread[ '_frames' ]
|
||||
with utils.RestoreCursorPosition():
|
||||
self._DrawThreads()
|
||||
else:
|
||||
if thread:
|
||||
if thread.IsExpanded():
|
||||
thread.Collapse()
|
||||
self._DrawThreads()
|
||||
elif thread.CanExpand():
|
||||
self._LoadStackTrace( thread, False )
|
||||
else:
|
||||
utils.UserMessage( "Thread is not stopped" )
|
||||
elif vim.current.buffer != self._buf:
|
||||
return
|
||||
elif vim.current.window.cursor[ 0 ] in self._line_to_frame:
|
||||
thread, frame = self._line_to_frame[ vim.current.window.cursor[ 0 ] ]
|
||||
self._JumpToFrame( frame )
|
||||
|
||||
|
||||
def _JumpToFrame( self, frame, reason = '' ):
|
||||
def do_jump():
|
||||
if 'line' in frame and frame[ 'line' ] > 0:
|
||||
# Should this set the current _Thread_ too ? If i jump to a frame in
|
||||
# Thread 2, should that become the focussed thread ?
|
||||
self._current_frame = frame
|
||||
return self._session.SetCurrentFrame( self._current_frame, reason )
|
||||
return False
|
||||
|
|
@ -205,59 +378,85 @@ class StackTraceView( object ):
|
|||
else:
|
||||
return do_jump()
|
||||
|
||||
|
||||
def PauseContinueThread( self ):
|
||||
thread = self._GetSelectedThread()
|
||||
if thread is None:
|
||||
utils.UserMessage( 'No thread selected' )
|
||||
elif thread.state == Thread.PAUSED:
|
||||
self._session._connection.DoRequest(
|
||||
lambda msg: self.OnContinued( {
|
||||
'threadId': thread.id,
|
||||
'allThreadsContinued': ( msg.get( 'body' ) or {} ).get(
|
||||
'allThreadsContinued',
|
||||
True )
|
||||
} ),
|
||||
{
|
||||
'command': 'continue',
|
||||
'arguments': {
|
||||
'threadId': thread.id,
|
||||
},
|
||||
} )
|
||||
elif thread.state == Thread.RUNNING:
|
||||
self._session._connection.DoRequest( None, {
|
||||
'command': 'pause',
|
||||
'arguments': {
|
||||
'threadId': thread.id,
|
||||
},
|
||||
} )
|
||||
else:
|
||||
utils.UserMessage(
|
||||
f'Thread cannot be modified in state {thread.State()}' )
|
||||
|
||||
|
||||
def OnContinued( self, event = None ):
|
||||
threadId = None
|
||||
allThreadsContinued = True
|
||||
|
||||
if event is not None:
|
||||
threadId = event[ 'threadId' ]
|
||||
allThreadsContinued = event.get( 'allThreadsContinued', False )
|
||||
|
||||
for thread in self._threads:
|
||||
if allThreadsContinued:
|
||||
thread.Continued()
|
||||
elif thread.id == threadId:
|
||||
thread.Continued()
|
||||
break
|
||||
|
||||
self._DrawThreads()
|
||||
|
||||
def OnStopped( self, event ):
|
||||
if 'threadId' in event:
|
||||
self._current_thread = event[ 'threadId' ]
|
||||
elif event.get( 'allThreadsStopped', False ) and self._threads:
|
||||
self._current_thread = self._threads[ 0 ][ 'id' ]
|
||||
threadId = event.get( 'threadId' )
|
||||
allThreadsStopped = event.get( 'allThreadsStopped', False )
|
||||
|
||||
if self._current_thread is not None:
|
||||
for thread in self._threads:
|
||||
if thread[ 'id' ] == self._current_thread:
|
||||
self._LoadStackTrace( thread, True, 'stopped' )
|
||||
return
|
||||
# Work out if we should change the current thread
|
||||
if threadId is not None:
|
||||
self._current_thread = threadId
|
||||
elif self._current_thread is None and allThreadsStopped and self._threads:
|
||||
self._current_thread = self._threads[ 0 ].id
|
||||
|
||||
self.LoadThreads( True )
|
||||
self.LoadThreads( True, 'stopped', event )
|
||||
|
||||
def OnThreadEvent( self, event ):
|
||||
infer_current_frame = False
|
||||
if event[ 'reason' ] == 'started' and self._current_thread is None:
|
||||
self._current_thread = event[ 'threadId' ]
|
||||
self.LoadThreads( True )
|
||||
infer_current_frame = True
|
||||
|
||||
def Continue( self ):
|
||||
if self._current_thread is None:
|
||||
utils.UserMessage( 'No current thread', persist = True )
|
||||
if event[ 'reason' ] == 'exited':
|
||||
for thread in self._threads:
|
||||
if thread.id == event[ 'threadId' ]:
|
||||
thread.Exited()
|
||||
break
|
||||
|
||||
self.LoadThreads( infer_current_frame )
|
||||
|
||||
def _DrawStackTrace( self, thread: Thread ):
|
||||
if not thread.IsExpanded():
|
||||
return
|
||||
|
||||
self._session._connection.DoRequest( None, {
|
||||
'command': 'continue',
|
||||
'arguments': {
|
||||
'threadId': self._current_thread,
|
||||
},
|
||||
} )
|
||||
|
||||
self._session.ClearCurrentFrame()
|
||||
self.LoadThreads( True )
|
||||
|
||||
def Pause( self ):
|
||||
if self._current_thread is None:
|
||||
utils.UserMessage( 'No current thread', persist = True )
|
||||
return
|
||||
|
||||
self._session._connection.DoRequest( None, {
|
||||
'command': 'pause',
|
||||
'arguments': {
|
||||
'threadId': self._current_thread,
|
||||
},
|
||||
} )
|
||||
|
||||
def _DrawStackTrace( self, thread ):
|
||||
if '_frames' not in thread:
|
||||
return
|
||||
|
||||
stackFrames = thread[ '_frames' ]
|
||||
|
||||
for frame in stackFrames:
|
||||
for frame in thread.stacktrace:
|
||||
if frame.get( 'source' ):
|
||||
source = frame[ 'source' ]
|
||||
else:
|
||||
|
|
@ -281,7 +480,7 @@ class StackTraceView( object ):
|
|||
source[ 'name' ],
|
||||
frame[ 'line' ] ) )
|
||||
|
||||
self._line_to_frame[ line ] = frame
|
||||
self._line_to_frame[ line ] = ( thread, frame )
|
||||
|
||||
def _ResolveSource( self, source, and_then ):
|
||||
source_reference = int( source[ 'sourceReference' ] )
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ var msg = 'Hello, world!'
|
|||
var obj = {
|
||||
test: 'testing',
|
||||
toast: function() {
|
||||
return 'toasty' . this.test;
|
||||
return 'toasty' + this.test;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
function! SetUp()
|
||||
call vimspector#test#setup#SetUpWithMappings( v:none )
|
||||
call ThisTestIsFlaky()
|
||||
endfunction
|
||||
|
||||
function! ClearDown()
|
||||
|
|
@ -684,6 +685,7 @@ function! Test_Custom_Breakpoint_Priority_Partial()
|
|||
endfunction
|
||||
|
||||
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
||||
call ThisTestIsFlaky()
|
||||
let moo = 'moo.py'
|
||||
let cow = 'cow.py'
|
||||
lcd ../support/test/python/multiple_files
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
function! SetUp()
|
||||
set ambiwidth=double
|
||||
call vimspector#test#setup#SetUpWithMappings( v:none )
|
||||
call ThisTestIsFlaky()
|
||||
endfunction
|
||||
|
||||
function! ClearDown()
|
||||
|
|
@ -695,6 +696,7 @@ endfunction
|
|||
|
||||
|
||||
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
||||
call ThisTestIsFlaky()
|
||||
let moo = 'moo.py'
|
||||
let cow = 'cow.py'
|
||||
lcd ../support/test/python/multiple_files
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|||
ENV LC_ALL C.UTF-8
|
||||
|
||||
RUN apt-get update && \
|
||||
apt install -y curl dirmngr apt-transport-https lsb-release ca-certificates \
|
||||
software-properties-common && \
|
||||
apt-get install -y curl \
|
||||
dirmngr \
|
||||
apt-transport-https \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
software-properties-common && \
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
|
||||
add-apt-repository ppa:bartbes/love-stable -y && \
|
||||
apt-get update && \
|
||||
apt-get -y dist-upgrade && \
|
||||
apt-get -y install python3-dev \
|
||||
apt-get -y install gcc-8 \
|
||||
g++-8 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
ca-cacert \
|
||||
|
|
@ -31,6 +37,9 @@ RUN apt-get update && \
|
|||
RUN ln -fs /usr/share/zoneinfo/Europe/London /etc/localtime && \
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
|
||||
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 1 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-8
|
||||
|
||||
## cleanup of files from setup
|
||||
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ func s:WaitForCommon(expr, assert, timeout)
|
|||
let start = reltime()
|
||||
endif
|
||||
|
||||
let iters = 0
|
||||
|
||||
while 1
|
||||
let iters += 1
|
||||
let errors_before = len( v:errors )
|
||||
if type(a:expr) == v:t_func
|
||||
let success = a:expr()
|
||||
|
|
@ -65,6 +68,10 @@ func s:WaitForCommon(expr, assert, timeout)
|
|||
return slept
|
||||
endif
|
||||
|
||||
if iters % 20 == 0
|
||||
redraw!
|
||||
endif
|
||||
|
||||
if slept >= a:timeout
|
||||
break
|
||||
endif
|
||||
|
|
@ -91,3 +98,31 @@ endfunc
|
|||
function! ThisTestIsFlaky()
|
||||
let g:test_is_flaky = v:true
|
||||
endfunction
|
||||
|
||||
function! AssertMatchist( 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! GetBufLine( buf, start, end = '$' )
|
||||
if type( a:start ) != v:t_string && a:start < 0
|
||||
let start = getbufinfo( a:buf )[ 0 ].linecount + a:start
|
||||
else
|
||||
let start = a:start
|
||||
endif
|
||||
|
||||
if type( a:end ) != v:t_string && a:end < 0
|
||||
let end = getbufinfo( a:buf )[ 0 ].linecount + a:end
|
||||
else
|
||||
let end = a:end
|
||||
endif
|
||||
|
||||
return getbufline( a:buf, start, end )
|
||||
endfunction
|
||||
|
|
|
|||
358
tests/stack_trace.test.vim
Normal file
358
tests/stack_trace.test.vim
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
let s:fn='testdata/cpp/simple/threads.cpp'
|
||||
|
||||
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 vimspector#SetLineBreakpoint( s:fn, 15 )
|
||||
call vimspector#LaunchWithSettings( #{ configuration: 'run-to-breakpoint' } )
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, 15, 1 )
|
||||
endfunction
|
||||
|
||||
function! Test_Multiple_Threads_Continue()
|
||||
let thread_l = 67
|
||||
let notify_l = 74
|
||||
|
||||
call vimspector#SetLineBreakpoint( s:fn, thread_l )
|
||||
call vimspector#SetLineBreakpoint( s:fn, notify_l )
|
||||
call s:StartDebugging()
|
||||
|
||||
call vimspector#Continue()
|
||||
|
||||
" As we step through the thread creation we should get Thread events
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call cursor( 1, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call cursor( 1, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call cursor( 1, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call cursor( 1, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
" This is the last one
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call cursor( 1, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
" So we break out of the loop
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, notify_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( notify_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
|
||||
call vimspector#ClearBreakpoints()
|
||||
call vimspector#test#setup#Reset()
|
||||
%bwipe!
|
||||
endfunction
|
||||
|
||||
function! Test_Multiple_Threads_Step()
|
||||
let thread_l = 67
|
||||
if $VIMSPECTOR_MIMODE ==# 'lldb'
|
||||
" }
|
||||
let thread_n = thread_l + 1
|
||||
else
|
||||
" for ....
|
||||
let thread_n = 49
|
||||
endif
|
||||
let notify_l = 74
|
||||
|
||||
call vimspector#SetLineBreakpoint( s:fn, thread_l )
|
||||
call vimspector#SetLineBreakpoint( s:fn, notify_l )
|
||||
call s:StartDebugging()
|
||||
call vimspector#Continue()
|
||||
|
||||
" As we step through the thread creation we should get Thread events
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Thread [0-9]\+: .* (paused)',
|
||||
\ ' .*: .*@threads.cpp:' . string( thread_l )
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ 1,
|
||||
\ 2 )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#StepOver()
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ '$',
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#StepOver()
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -1,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -1,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#StepOver()
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -2,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -2,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#StepOver()
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -3,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -3,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#StepOver()
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, thread_n, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -4,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
call vimspector#Continue()
|
||||
|
||||
|
||||
" So we break out of the loop
|
||||
call vimspector#test#signs#AssertCursorIsAtLineInBuffer( s:fn, notify_l, 1 )
|
||||
call WaitForAssert( {->
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ '+ Thread [0-9]\+: .* (paused)',
|
||||
\ ],
|
||||
\ GetBufLine( winbufnr( g:vimspector_session_windows.stack_trace ),
|
||||
\ -4,
|
||||
\ '$' )
|
||||
\ )
|
||||
\ } )
|
||||
|
||||
call vimspector#ClearBreakpoints()
|
||||
call vimspector#test#setup#Reset()
|
||||
%bwipe!
|
||||
endfunction
|
||||
1
tests/testdata/cpp/simple/.gitignore
vendored
1
tests/testdata/cpp/simple/.gitignore
vendored
|
|
@ -2,3 +2,4 @@ simple
|
|||
variables
|
||||
struct
|
||||
printer
|
||||
threads
|
||||
|
|
|
|||
1
tests/testdata/cpp/simple/.ycm_extra_conf.py
vendored
1
tests/testdata/cpp/simple/.ycm_extra_conf.py
vendored
|
|
@ -2,6 +2,7 @@ def Settings( **kwargs ):
|
|||
return {
|
||||
'flags': [
|
||||
'-x', 'c++',
|
||||
'-std=c++17',
|
||||
'-Wextra', '-Werror', '-Wall'
|
||||
]
|
||||
}
|
||||
|
|
|
|||
3
tests/testdata/cpp/simple/Makefile
vendored
3
tests/testdata/cpp/simple/Makefile
vendored
|
|
@ -2,7 +2,8 @@ CXXFLAGS=-g -O0 -std=c++17
|
|||
|
||||
.PHONY: all
|
||||
|
||||
TARGETS=simple variables struct printer
|
||||
TARGETS=simple variables struct printer threads
|
||||
LDLIBS=-lpthread
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
|
|
|
|||
82
tests/testdata/cpp/simple/threads.cpp
vendored
Normal file
82
tests/testdata/cpp/simple/threads.cpp
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
#include <random>
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
int numThreads = {};
|
||||
if ( argc < 2 )
|
||||
{
|
||||
numThreads = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string_view numThreadArg( argv[ 1 ] );
|
||||
if ( auto [ p, ec ] = std::from_chars( numThreadArg.begin(),
|
||||
numThreadArg.end(),
|
||||
numThreads );
|
||||
ec != std::errc() )
|
||||
{
|
||||
std::cerr << "Usage " << argv[ 0 ] << " <number of threads>\n";
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Creating " << numThreads << " threads" << '\n';
|
||||
|
||||
std::vector<std::thread> threads{};
|
||||
threads.reserve( numThreads );
|
||||
|
||||
auto eng = std::default_random_engine() ;
|
||||
auto dist = std::uniform_int_distribution<int>( 250, 1000 );
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable v;
|
||||
bool ready = false;
|
||||
{
|
||||
std::lock_guard l(m);
|
||||
|
||||
std::cout << "Preparing..." << '\n';
|
||||
|
||||
for ( int i = 0; i < numThreads; ++i )
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto tp = [&,tnum=i]() {
|
||||
// Wait for the go-ahead
|
||||
{
|
||||
std::unique_lock l(m);
|
||||
while (!ready) {
|
||||
v.wait(l);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Started thread " << tnum << '\n';
|
||||
std::this_thread::sleep_for(
|
||||
5s + std::chrono::milliseconds( dist( eng ) ) );
|
||||
std::cout << "Completed thread " << tnum << '\n';
|
||||
};
|
||||
|
||||
threads.emplace_back( tp );
|
||||
}
|
||||
|
||||
std::cout << "Ready to go!" << '\n';
|
||||
ready = true;
|
||||
}
|
||||
|
||||
v.notify_all();
|
||||
|
||||
for ( int i = 0; i < numThreads; ++i )
|
||||
{
|
||||
threads[ i ].join();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -8,17 +8,6 @@ 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 = #{
|
||||
|
|
@ -222,7 +211,7 @@ function! Test_ExpandVariables()
|
|||
call feedkeys( "\<CR>", 'xt' )
|
||||
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Scope: Locals',
|
||||
\ ' \*- t (Test): {...}',
|
||||
|
|
@ -240,7 +229,7 @@ function! Test_ExpandVariables()
|
|||
" Step - stays expanded
|
||||
call vimspector#StepOver()
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Scope: Locals',
|
||||
\ ' - t (Test): {...}',
|
||||
|
|
@ -289,7 +278,7 @@ function! Test_ExpandVariables()
|
|||
call setpos( '.', [ 0, 2, 1 ] )
|
||||
call feedkeys( "\<CR>", 'xt' )
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ '- Scope: Locals',
|
||||
\ ' - t (Test): {...}',
|
||||
|
|
@ -389,7 +378,7 @@ function! Test_ExpandWatch()
|
|||
call feedkeys( "\<CR>", 'xt' )
|
||||
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ 'Watches: ----',
|
||||
\ 'Expression: t',
|
||||
|
|
@ -408,7 +397,7 @@ function! Test_ExpandWatch()
|
|||
" Step - stays expanded
|
||||
call vimspector#StepOver()
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ 'Watches: ----',
|
||||
\ 'Expression: t',
|
||||
|
|
@ -460,7 +449,7 @@ function! Test_ExpandWatch()
|
|||
call setpos( '.', [ 0, 3, 1 ] )
|
||||
call feedkeys( "\<CR>", 'xt' )
|
||||
call WaitForAssert( {->
|
||||
\ s:assert_match_list(
|
||||
\ AssertMatchist(
|
||||
\ [
|
||||
\ 'Watches: ----',
|
||||
\ 'Expression: t',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue