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)
|
* [Supported languages](#supported-languages)
|
||||||
* [Other languages](#other-languages)
|
* [Other languages](#other-languages)
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
|
* [Quick Start](#quick-start)
|
||||||
* [Dependencies](#dependencies)
|
* [Dependencies](#dependencies)
|
||||||
* [Neovim differences](#neovim-differences)
|
* [Neovim differences](#neovim-differences)
|
||||||
* [Windows differences](#windows-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)
|
* [Install some gadgets](#install-some-gadgets)
|
||||||
* [VimspectorInstall and VimspectorUpdate commands](#vimspectorinstall-and-vimspectorupdate-commands)
|
* [VimspectorInstall and VimspectorUpdate commands](#vimspectorinstall-and-vimspectorupdate-commands)
|
||||||
* [install_gadget.py](#install_gadgetpy)
|
* [install_gadget.py](#install_gadgetpy)
|
||||||
* [Manual gadget installation](#manual-gadget-installation)
|
* [Manual gadget installation](#manual-gadget-installation)
|
||||||
* [The gadget directory](#the-gadget-directory)
|
* [The gadget directory](#the-gadget-directory)
|
||||||
* [Trying it out](#trying-it-out)
|
|
||||||
* [Upgrade](#upgrade)
|
* [Upgrade](#upgrade)
|
||||||
* [About](#about)
|
* [About](#about)
|
||||||
* [Background](#background)
|
* [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 and attach by PID:](#launch-and-attach-by-pid)
|
||||||
* [Launch with options](#launch-with-options)
|
* [Launch with options](#launch-with-options)
|
||||||
* [Debug configuration selection](#debug-configuration-selection)
|
* [Debug configuration selection](#debug-configuration-selection)
|
||||||
|
* [Get configurations](#get-configurations)
|
||||||
* [Breakpoints](#breakpoints)
|
* [Breakpoints](#breakpoints)
|
||||||
* [Exception breakpoints](#exception-breakpoints)
|
* [Exception breakpoints](#exception-breakpoints)
|
||||||
* [Clear breakpoints](#clear-breakpoints)
|
* [Clear breakpoints](#clear-breakpoints)
|
||||||
|
* [Run to Cursor](#run-to-cursor)
|
||||||
* [Stepping](#stepping)
|
* [Stepping](#stepping)
|
||||||
* [Variables and scopes](#variables-and-scopes)
|
* [Variables and scopes](#variables-and-scopes)
|
||||||
* [Watches](#watches)
|
* [Watches](#watches)
|
||||||
|
|
@ -48,6 +51,7 @@ For a tutorial and usage overview, take a look at the
|
||||||
* [Closing debugger](#closing-debugger)
|
* [Closing debugger](#closing-debugger)
|
||||||
* [Debug adapter configuration](#debug-adapter-configuration)
|
* [Debug adapter configuration](#debug-adapter-configuration)
|
||||||
* [C, C , Rust, etc.](#c-c-rust-etc)
|
* [C, C , Rust, etc.](#c-c-rust-etc)
|
||||||
|
* [Rust](#rust)
|
||||||
* [Remote debugging](#remote-debugging)
|
* [Remote debugging](#remote-debugging)
|
||||||
* [Remote launch and attach](#remote-launch-and-attach)
|
* [Remote launch and attach](#remote-launch-and-attach)
|
||||||
* [Python](#python)
|
* [Python](#python)
|
||||||
|
|
@ -65,7 +69,6 @@ For a tutorial and usage overview, take a look at the
|
||||||
* [Usage with YouCompleteMe](#usage-with-youcompleteme)
|
* [Usage with YouCompleteMe](#usage-with-youcompleteme)
|
||||||
* [Other LSP clients](#other-lsp-clients)
|
* [Other LSP clients](#other-lsp-clients)
|
||||||
* [Lua](#lua)
|
* [Lua](#lua)
|
||||||
* [Rust](#rust)
|
|
||||||
* [Other servers](#other-servers)
|
* [Other servers](#other-servers)
|
||||||
* [Customisation](#customisation)
|
* [Customisation](#customisation)
|
||||||
* [Changing the default signs](#changing-the-default-signs)
|
* [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)
|
* [License](#license)
|
||||||
* [Sponsorship](#sponsorship)
|
* [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-->
|
<!--te-->
|
||||||
|
|
||||||
|
|
@ -121,8 +124,6 @@ And a couple of brief demos:
|
||||||
- logging/stdout display
|
- logging/stdout display
|
||||||
- simple stable API for custom tooling (e.g. integrate with language server)
|
- 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
|
## Supported languages
|
||||||
|
|
||||||
The following table lists the languages that are "built-in" (along with their
|
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
|
# Installation
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
There are 2 installation methods:
|
There are 2 installation methods:
|
||||||
|
|
||||||
* Using a release tarball, or
|
* Using a release tarball and vim packages
|
||||||
* Manually
|
* Using a clone of the repo (e.g. package manager)
|
||||||
|
|
||||||
Release tarballs come with debug adapters for the default languages
|
Release tarballs come with debug adapters for the default languages
|
||||||
pre-packaged. To use a release tarball:
|
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 -
|
$ 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`)
|
3. Configure your project's debug profiles (create `.vimspector.json`)
|
||||||
|
|
||||||
Alternatively, you can clone the repo and select which gadgets are installed:
|
Alternatively, you can clone the repo and select which gadgets are installed:
|
||||||
|
|
||||||
1. Check the dependencies
|
1. Check the dependencies
|
||||||
1. Install the plugin as a Vim package. See `:help packages`.
|
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`)
|
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
|
## Dependencies
|
||||||
|
|
||||||
Vimspector requires:
|
Vimspector requires:
|
||||||
|
|
@ -239,20 +254,54 @@ The following features are not implemented for Windows:
|
||||||
|
|
||||||
* Tailing the vimspector log in the Output Window.
|
* 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
|
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
|
preference, so if you choose to use one, follow the plugin manager's
|
||||||
issues.
|
documentation. For example, for Vundle, use:
|
||||||
|
|
||||||
Install vimspector as a Vim package, either by cloning this repository into your
|
```viml
|
||||||
package path, like this:
|
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
|
$ 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
|
```viml
|
||||||
let g:vimspector_enable_mappings = 'HUMAN'
|
let g:vimspector_enable_mappings = 'HUMAN'
|
||||||
|
|
@ -265,7 +314,7 @@ let g:vimspector_enable_mappings = 'HUMAN'
|
||||||
packadd! vimspector
|
packadd! vimspector
|
||||||
```
|
```
|
||||||
|
|
||||||
See support/doc/example_vimrc.vim.
|
See support/doc/example_vimrc.vim for a minimal example.
|
||||||
|
|
||||||
## Install some gadgets
|
## Install some gadgets
|
||||||
|
|
||||||
|
|
@ -281,11 +330,10 @@ There are a few ways to do this:
|
||||||
installed for you.
|
installed for you.
|
||||||
* Using `:VimspectorInstall <adapter> <args...>` (use TAB `wildmenu` to see the
|
* Using `:VimspectorInstall <adapter> <args...>` (use TAB `wildmenu` to see the
|
||||||
options, also accepts any `install_gadget.py` option)
|
options, also accepts any `install_gadget.py` option)
|
||||||
* Alternatively, using `python3 install_gadget.py <args>` (use `--help` to see
|
* Using `python3 install_gadget.py <args>` (use `--help` to see all options)
|
||||||
all options)
|
* Attempting to launch a debug configuration; if the configured adapter
|
||||||
* When attempting to launch a debug configuration, if the configured adapter
|
can't be found, vimspector will suggest installing one.
|
||||||
can't be found, vimspector might suggest installing one.
|
* Using `:VimspectorUpdate` to install the latest supported versions of the
|
||||||
* Use `:VimspectorUpdate` to install the latest supported versions of the
|
|
||||||
gadgets.
|
gadgets.
|
||||||
|
|
||||||
Here's a demo of doing somee installs and an upgrade:
|
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)
|
[](https://asciinema.org/a/Hfu4ZvuyTZun8THNen9FQbTay)
|
||||||
|
|
||||||
Both `install_gadget.py` and `:VimspectorInstall` do the same set of things,
|
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:
|
they will:
|
||||||
|
|
||||||
* Download the relevant debug adapter at a version that's been tested from the
|
* Download the relevant debug adapter at a version that's been tested from the
|
||||||
|
|
@ -306,7 +354,7 @@ they will:
|
||||||
broken in this regard.
|
broken in this regard.
|
||||||
* Set up the `gadgetDir` symlinks for the platform.
|
* 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 |
|
| 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
|
format as `.gadgets.json` but are not overwritten when running
|
||||||
`install_gadget.py`.
|
`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
|
## Upgrade
|
||||||
|
|
||||||
After updating the Vimspector code (either via `git pull` or whatever package
|
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
|
## 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
|
The watches window is a prompt buffer, where that's available. Enter insert mode
|
||||||
to add a new watch expression.
|
to add a new watch expression.
|
||||||
|
|
||||||
|
|
@ -767,7 +794,7 @@ The watches are represented by the buffer `vimspector.StackTrace`.
|
||||||
|
|
||||||
### Watch autocompletion
|
### 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
|
calcualte completion for the current expression. This is trivailly used with
|
||||||
`<Ctrl-x><Ctrl-o>` (see `:help ins-completion`), or integrated with your
|
`<Ctrl-x><Ctrl-o>` (see `:help ins-completion`), or integrated with your
|
||||||
favourite completion system. The filetype in the buffer is set to
|
favourite completion system. The filetype in the buffer is set to
|
||||||
|
|
@ -783,8 +810,28 @@ let g:ycm_semantic_triggers = {
|
||||||
|
|
||||||
## Stack Traces
|
## 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 `<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()
|
py3 _vimspector_session.Pause()
|
||||||
endfunction
|
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
|
function! vimspector#Stop() abort
|
||||||
if !s:Enabled()
|
if !s:Enabled()
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ function! vimspector#internal#state#Reset() abort
|
||||||
catch /.*/
|
catch /.*/
|
||||||
echohl WarningMsg
|
echohl WarningMsg
|
||||||
echom 'Exception while loading vimspector:' v:exception
|
echom 'Exception while loading vimspector:' v:exception
|
||||||
|
echom 'From:' v:throwpoint
|
||||||
echom 'Vimspector unavailable: Requires Vim compiled with Python 3.6'
|
echom 'Vimspector unavailable: Requires Vim compiled with Python 3.6'
|
||||||
echohl None
|
echohl None
|
||||||
return v:false
|
return v:false
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class CodeView( object ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger )
|
||||||
|
|
||||||
|
# FIXME: This ID is by group, so should be module scope
|
||||||
self._next_sign_id = 1
|
self._next_sign_id = 1
|
||||||
self._breakpoints = defaultdict( list )
|
self._breakpoints = defaultdict( list )
|
||||||
self._signs = {
|
self._signs = {
|
||||||
|
|
|
||||||
|
|
@ -429,39 +429,90 @@ class DebugSession( object ):
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
self._stackTraceView.OnContinued()
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def StepInto( self ):
|
def StepInto( self ):
|
||||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
threadId = self._stackTraceView.GetCurrentThreadId()
|
||||||
|
if threadId is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._connection.DoRequest( None, {
|
def handler( *_ ):
|
||||||
|
self._stackTraceView.OnContinued( { 'threadId': threadId } )
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
|
self._connection.DoRequest( handler, {
|
||||||
'command': 'stepIn',
|
'command': 'stepIn',
|
||||||
'arguments': {
|
'arguments': {
|
||||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
'threadId': threadId
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
@IfConnected()
|
@IfConnected()
|
||||||
def StepOut( self ):
|
def StepOut( self ):
|
||||||
if self._stackTraceView.GetCurrentThreadId() is None:
|
threadId = self._stackTraceView.GetCurrentThreadId()
|
||||||
|
if threadId is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._connection.DoRequest( None, {
|
def handler( *_ ):
|
||||||
|
self._stackTraceView.OnContinued( { 'threadId': threadId } )
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
|
self._connection.DoRequest( handler, {
|
||||||
'command': 'stepOut',
|
'command': 'stepOut',
|
||||||
'arguments': {
|
'arguments': {
|
||||||
'threadId': self._stackTraceView.GetCurrentThreadId()
|
'threadId': threadId
|
||||||
},
|
},
|
||||||
} )
|
} )
|
||||||
|
|
||||||
|
|
||||||
def Continue( self ):
|
def Continue( self ):
|
||||||
if self._connection:
|
if not self._connection:
|
||||||
self._stackTraceView.Continue()
|
|
||||||
else:
|
|
||||||
self.Start()
|
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()
|
@IfConnected()
|
||||||
def Pause( self ):
|
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()
|
@IfConnected()
|
||||||
def ExpandVariable( self ):
|
def ExpandVariable( self ):
|
||||||
|
|
@ -1098,6 +1149,7 @@ class DebugSession( object ):
|
||||||
def OnEvent_exited( self, message ):
|
def OnEvent_exited( self, message ):
|
||||||
utils.UserMessage( 'The debugee exited with status code: {}'.format(
|
utils.UserMessage( 'The debugee exited with status code: {}'.format(
|
||||||
message[ 'body' ][ 'exitCode' ] ) )
|
message[ 'body' ][ 'exitCode' ] ) )
|
||||||
|
self.SetCurrentFrame( None )
|
||||||
|
|
||||||
def OnEvent_process( self, message ):
|
def OnEvent_process( self, message ):
|
||||||
utils.UserMessage( 'The debugee was started: {}'.format(
|
utils.UserMessage( 'The debugee was started: {}'.format(
|
||||||
|
|
@ -1107,7 +1159,8 @@ class DebugSession( object ):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def OnEvent_continued( self, message ):
|
def OnEvent_continued( self, message ):
|
||||||
pass
|
self._stackTraceView.OnContinued( message[ 'body' ] )
|
||||||
|
self._codeView.SetCurrentFrame( None )
|
||||||
|
|
||||||
def Clear( self ):
|
def Clear( self ):
|
||||||
self._codeView.Clear()
|
self._codeView.Clear()
|
||||||
|
|
@ -1142,6 +1195,7 @@ class DebugSession( object ):
|
||||||
def OnEvent_terminated( self, message ):
|
def OnEvent_terminated( self, message ):
|
||||||
# We will handle this when the server actually exists
|
# We will handle this when the server actually exists
|
||||||
utils.UserMessage( "Debugging was terminated by the server." )
|
utils.UserMessage( "Debugging was terminated by the server." )
|
||||||
|
self.SetCurrentFrame( None )
|
||||||
|
|
||||||
def OnEvent_output( self, message ):
|
def OnEvent_output( self, message ):
|
||||||
if self._outputView:
|
if self._outputView:
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,12 @@ DEFAULTS = {
|
||||||
|
|
||||||
# Signs
|
# Signs
|
||||||
'sign_priority': {
|
'sign_priority': {
|
||||||
'vimspectorPC': 200,
|
'vimspectorPC': 200,
|
||||||
'vimspectorPCBP': 200,
|
'vimspectorPCBP': 200,
|
||||||
'vimspectorBP': 9,
|
'vimspectorBP': 9,
|
||||||
'vimspectorBPCond': 9,
|
'vimspectorBPCond': 9,
|
||||||
'vimspectorBPDisabled': 9,
|
'vimspectorBPDisabled': 9,
|
||||||
|
'vimspectorCurrentThread': 200
|
||||||
},
|
},
|
||||||
|
|
||||||
# Installer
|
# Installer
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,75 @@
|
||||||
import vim
|
import vim
|
||||||
import os
|
import os
|
||||||
import logging
|
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 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 ):
|
def __init__( self, session, win ):
|
||||||
self._logger = logging.getLogger( __name__ )
|
self._logger = logging.getLogger( __name__ )
|
||||||
utils.SetUpLogging( self._logger )
|
utils.SetUpLogging( self._logger )
|
||||||
|
|
@ -37,25 +101,43 @@ class StackTraceView( object ):
|
||||||
self._sources = {}
|
self._sources = {}
|
||||||
self._scratch_buffers = []
|
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.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
|
||||||
utils.SetUpUIWindow( win )
|
utils.SetUpUIWindow( win )
|
||||||
|
|
||||||
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
with utils.LetCurrentWindow( win ):
|
||||||
':<C-U>call vimspector#GoToFrame()<CR>' )
|
vim.command( 'nnoremap <silent> <buffer> <CR> '
|
||||||
vim.command( 'nnoremap <silent> <buffer> <2-LeftMouse> '
|
':<C-U>call vimspector#GoToFrame()<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_frame = {}
|
||||||
self._line_to_thread = {}
|
self._line_to_thread = {}
|
||||||
|
|
||||||
# TODO: We really need a proper state model
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
#
|
self._pending_thread_request = None
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
def GetCurrentThreadId( self ):
|
def GetCurrentThreadId( self ):
|
||||||
|
|
@ -68,14 +150,19 @@ class StackTraceView( object ):
|
||||||
self._current_frame = None
|
self._current_frame = None
|
||||||
self._current_thread = None
|
self._current_thread = None
|
||||||
self._current_syntax = ""
|
self._current_syntax = ""
|
||||||
self._threads = []
|
self._threads.clear()
|
||||||
self._sources = {}
|
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 ):
|
with utils.ModifiableScratchBuffer( self._buf ):
|
||||||
utils.ClearBuffer( self._buf )
|
utils.ClearBuffer( self._buf )
|
||||||
|
|
||||||
def ConnectionUp( self, connection ):
|
def ConnectionUp( self, connection ):
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
self._requesting_threads = False
|
|
||||||
|
|
||||||
def ConnectionClosed( self ):
|
def ConnectionClosed( self ):
|
||||||
self.Clear()
|
self.Clear()
|
||||||
|
|
@ -86,47 +173,88 @@ class StackTraceView( object ):
|
||||||
utils.CleanUpHiddenBuffer( self._buf )
|
utils.CleanUpHiddenBuffer( self._buf )
|
||||||
for b in self._scratch_buffers:
|
for b in self._scratch_buffers:
|
||||||
utils.CleanUpHiddenBuffer( b )
|
utils.CleanUpHiddenBuffer( b )
|
||||||
|
|
||||||
self._scratch_buffers = []
|
self._scratch_buffers = []
|
||||||
self._buf = None
|
self._buf = None
|
||||||
|
|
||||||
def LoadThreads( self, infer_current_frame ):
|
def LoadThreads( self,
|
||||||
pending_request = False
|
infer_current_frame,
|
||||||
if self._requesting_threads:
|
reason = '',
|
||||||
pending_request = True
|
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
|
return
|
||||||
|
|
||||||
def consume_threads( message ):
|
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' ]:
|
self._requesting_threads = StackTraceView.ThreadRequestState.NO
|
||||||
if pending_request:
|
self._pending_thread_request = None
|
||||||
# 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 )
|
|
||||||
|
|
||||||
|
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()
|
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 )
|
self._threads.append( thread )
|
||||||
|
|
||||||
if infer_current_frame and thread[ 'id' ] == self._current_thread:
|
# If the threads were requested due to a stopped event, update any
|
||||||
self._LoadStackTrace( thread, True )
|
# stopped thread state. Note we have to do this here (rather than in the
|
||||||
elif infer_current_frame and self._current_thread is None:
|
# stopped event handler) because we must apply this event to any new
|
||||||
self._current_thread = thread[ 'id' ]
|
# threads that are received here.
|
||||||
self._LoadStackTrace( thread, True )
|
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 ):
|
def failure_handler( reason, msg ):
|
||||||
# Make sure we request them again if the request fails
|
# 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, {
|
self._connection.DoRequest( consume_threads, {
|
||||||
'command': 'threads',
|
'command': 'threads',
|
||||||
}, failure_handler )
|
}, failure_handler )
|
||||||
|
|
@ -135,28 +263,41 @@ class StackTraceView( object ):
|
||||||
self._line_to_frame.clear()
|
self._line_to_frame.clear()
|
||||||
self._line_to_thread.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 ):
|
with utils.ModifiableScratchBuffer( self._buf ):
|
||||||
utils.ClearBuffer( self._buf )
|
with utils.RestoreCursorPosition():
|
||||||
|
utils.ClearBuffer( self._buf )
|
||||||
|
|
||||||
for thread in self._threads:
|
for thread in self._threads:
|
||||||
icon = '+' if '_frames' not in thread else '-'
|
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(
|
if self._current_thread == thread.id:
|
||||||
self._buf,
|
signs.PlaceSign( self._next_sign_id,
|
||||||
'{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
|
'VimspectorStackTrace',
|
||||||
|
'vimspectorCurrentThread',
|
||||||
|
self._buf.name,
|
||||||
|
line )
|
||||||
|
|
||||||
self._line_to_thread[ line ] = thread
|
self._line_to_thread[ line ] = thread
|
||||||
|
self._DrawStackTrace( thread )
|
||||||
self._DrawStackTrace( thread )
|
|
||||||
|
|
||||||
def _LoadStackTrace( self,
|
def _LoadStackTrace( self,
|
||||||
thread,
|
thread: Thread,
|
||||||
infer_current_frame,
|
infer_current_frame,
|
||||||
reason = '' ):
|
reason = '' ):
|
||||||
|
|
||||||
def consume_stacktrace( message ):
|
def consume_stacktrace( message ):
|
||||||
thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ]
|
thread.Expand( message[ 'body' ][ 'stackFrames' ] )
|
||||||
if infer_current_frame:
|
if infer_current_frame:
|
||||||
for frame in thread[ '_frames' ]:
|
for frame in thread.stacktrace:
|
||||||
if self._JumpToFrame( frame, reason ):
|
if self._JumpToFrame( frame, reason ):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -165,30 +306,62 @@ class StackTraceView( object ):
|
||||||
self._connection.DoRequest( consume_stacktrace, {
|
self._connection.DoRequest( consume_stacktrace, {
|
||||||
'command': 'stackTrace',
|
'command': 'stackTrace',
|
||||||
'arguments': {
|
'arguments': {
|
||||||
'threadId': thread[ 'id' ],
|
'threadId': thread.id,
|
||||||
}
|
}
|
||||||
} )
|
} )
|
||||||
|
|
||||||
def ExpandFrameOrThread( self ):
|
|
||||||
|
def _GetSelectedThread( self ) -> Thread:
|
||||||
if vim.current.buffer != self._buf:
|
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
|
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:
|
if thread:
|
||||||
self._JumpToFrame( self._line_to_frame[ current_line ] )
|
if thread.IsExpanded():
|
||||||
elif current_line in self._line_to_thread:
|
thread.Collapse()
|
||||||
thread = self._line_to_thread[ current_line ]
|
self._DrawThreads()
|
||||||
if '_frames' in thread:
|
elif thread.CanExpand():
|
||||||
del thread[ '_frames' ]
|
|
||||||
with utils.RestoreCursorPosition():
|
|
||||||
self._DrawThreads()
|
|
||||||
else:
|
|
||||||
self._LoadStackTrace( thread, False )
|
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 _JumpToFrame( self, frame, reason = '' ):
|
||||||
def do_jump():
|
def do_jump():
|
||||||
if 'line' in frame and frame[ 'line' ] > 0:
|
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
|
self._current_frame = frame
|
||||||
return self._session.SetCurrentFrame( self._current_frame, reason )
|
return self._session.SetCurrentFrame( self._current_frame, reason )
|
||||||
return False
|
return False
|
||||||
|
|
@ -205,59 +378,85 @@ class StackTraceView( object ):
|
||||||
else:
|
else:
|
||||||
return do_jump()
|
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 ):
|
def OnStopped( self, event ):
|
||||||
if 'threadId' in event:
|
threadId = event.get( 'threadId' )
|
||||||
self._current_thread = event[ 'threadId' ]
|
allThreadsStopped = event.get( 'allThreadsStopped', False )
|
||||||
elif event.get( 'allThreadsStopped', False ) and self._threads:
|
|
||||||
self._current_thread = self._threads[ 0 ][ 'id' ]
|
|
||||||
|
|
||||||
if self._current_thread is not None:
|
# Work out if we should change the current thread
|
||||||
for thread in self._threads:
|
if threadId is not None:
|
||||||
if thread[ 'id' ] == self._current_thread:
|
self._current_thread = threadId
|
||||||
self._LoadStackTrace( thread, True, 'stopped' )
|
elif self._current_thread is None and allThreadsStopped and self._threads:
|
||||||
return
|
self._current_thread = self._threads[ 0 ].id
|
||||||
|
|
||||||
self.LoadThreads( True )
|
self.LoadThreads( True, 'stopped', event )
|
||||||
|
|
||||||
def OnThreadEvent( self, event ):
|
def OnThreadEvent( self, event ):
|
||||||
|
infer_current_frame = False
|
||||||
if event[ 'reason' ] == 'started' and self._current_thread is None:
|
if event[ 'reason' ] == 'started' and self._current_thread is None:
|
||||||
self._current_thread = event[ 'threadId' ]
|
self._current_thread = event[ 'threadId' ]
|
||||||
self.LoadThreads( True )
|
infer_current_frame = True
|
||||||
|
|
||||||
def Continue( self ):
|
if event[ 'reason' ] == 'exited':
|
||||||
if self._current_thread is None:
|
for thread in self._threads:
|
||||||
utils.UserMessage( 'No current thread', persist = True )
|
if thread.id == event[ 'threadId' ]:
|
||||||
|
thread.Exited()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.LoadThreads( infer_current_frame )
|
||||||
|
|
||||||
|
def _DrawStackTrace( self, thread: Thread ):
|
||||||
|
if not thread.IsExpanded():
|
||||||
return
|
return
|
||||||
|
|
||||||
self._session._connection.DoRequest( None, {
|
for frame in thread.stacktrace:
|
||||||
'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:
|
|
||||||
if frame.get( 'source' ):
|
if frame.get( 'source' ):
|
||||||
source = frame[ 'source' ]
|
source = frame[ 'source' ]
|
||||||
else:
|
else:
|
||||||
|
|
@ -281,7 +480,7 @@ class StackTraceView( object ):
|
||||||
source[ 'name' ],
|
source[ 'name' ],
|
||||||
frame[ 'line' ] ) )
|
frame[ 'line' ] ) )
|
||||||
|
|
||||||
self._line_to_frame[ line ] = frame
|
self._line_to_frame[ line ] = ( thread, frame )
|
||||||
|
|
||||||
def _ResolveSource( self, source, and_then ):
|
def _ResolveSource( self, source, and_then ):
|
||||||
source_reference = int( source[ 'sourceReference' ] )
|
source_reference = int( source[ 'sourceReference' ] )
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ var msg = 'Hello, world!'
|
||||||
var obj = {
|
var obj = {
|
||||||
test: 'testing',
|
test: 'testing',
|
||||||
toast: function() {
|
toast: function() {
|
||||||
return 'toasty' . this.test;
|
return 'toasty' + this.test;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
function! SetUp()
|
function! SetUp()
|
||||||
call vimspector#test#setup#SetUpWithMappings( v:none )
|
call vimspector#test#setup#SetUpWithMappings( v:none )
|
||||||
|
call ThisTestIsFlaky()
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! ClearDown()
|
function! ClearDown()
|
||||||
|
|
@ -684,6 +685,7 @@ function! Test_Custom_Breakpoint_Priority_Partial()
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
||||||
|
call ThisTestIsFlaky()
|
||||||
let moo = 'moo.py'
|
let moo = 'moo.py'
|
||||||
let cow = 'cow.py'
|
let cow = 'cow.py'
|
||||||
lcd ../support/test/python/multiple_files
|
lcd ../support/test/python/multiple_files
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
function! SetUp()
|
function! SetUp()
|
||||||
set ambiwidth=double
|
set ambiwidth=double
|
||||||
call vimspector#test#setup#SetUpWithMappings( v:none )
|
call vimspector#test#setup#SetUpWithMappings( v:none )
|
||||||
|
call ThisTestIsFlaky()
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! ClearDown()
|
function! ClearDown()
|
||||||
|
|
@ -695,6 +696,7 @@ endfunction
|
||||||
|
|
||||||
|
|
||||||
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
function! Test_Add_Line_BP_In_Other_File_While_Debugging()
|
||||||
|
call ThisTestIsFlaky()
|
||||||
let moo = 'moo.py'
|
let moo = 'moo.py'
|
||||||
let cow = 'cow.py'
|
let cow = 'cow.py'
|
||||||
lcd ../support/test/python/multiple_files
|
lcd ../support/test/python/multiple_files
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,19 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt install -y curl dirmngr apt-transport-https lsb-release ca-certificates \
|
apt-get install -y curl \
|
||||||
software-properties-common && \
|
dirmngr \
|
||||||
|
apt-transport-https \
|
||||||
|
lsb-release \
|
||||||
|
ca-certificates \
|
||||||
|
software-properties-common && \
|
||||||
curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
|
curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
|
||||||
add-apt-repository ppa:bartbes/love-stable -y && \
|
add-apt-repository ppa:bartbes/love-stable -y && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get -y dist-upgrade && \
|
apt-get -y dist-upgrade && \
|
||||||
apt-get -y install python3-dev \
|
apt-get -y install gcc-8 \
|
||||||
|
g++-8 \
|
||||||
|
python3-dev \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3-venv \
|
python3-venv \
|
||||||
ca-cacert \
|
ca-cacert \
|
||||||
|
|
@ -31,6 +37,9 @@ RUN apt-get update && \
|
||||||
RUN ln -fs /usr/share/zoneinfo/Europe/London /etc/localtime && \
|
RUN ln -fs /usr/share/zoneinfo/Europe/London /etc/localtime && \
|
||||||
dpkg-reconfigure --frontend noninteractive tzdata
|
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
|
## cleanup of files from setup
|
||||||
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,10 @@ func s:WaitForCommon(expr, assert, timeout)
|
||||||
let start = reltime()
|
let start = reltime()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let iters = 0
|
||||||
|
|
||||||
while 1
|
while 1
|
||||||
|
let iters += 1
|
||||||
let errors_before = len( v:errors )
|
let errors_before = len( v:errors )
|
||||||
if type(a:expr) == v:t_func
|
if type(a:expr) == v:t_func
|
||||||
let success = a:expr()
|
let success = a:expr()
|
||||||
|
|
@ -65,6 +68,10 @@ func s:WaitForCommon(expr, assert, timeout)
|
||||||
return slept
|
return slept
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if iters % 20 == 0
|
||||||
|
redraw!
|
||||||
|
endif
|
||||||
|
|
||||||
if slept >= a:timeout
|
if slept >= a:timeout
|
||||||
break
|
break
|
||||||
endif
|
endif
|
||||||
|
|
@ -91,3 +98,31 @@ endfunc
|
||||||
function! ThisTestIsFlaky()
|
function! ThisTestIsFlaky()
|
||||||
let g:test_is_flaky = v:true
|
let g:test_is_flaky = v:true
|
||||||
endfunction
|
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
|
variables
|
||||||
struct
|
struct
|
||||||
printer
|
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 {
|
return {
|
||||||
'flags': [
|
'flags': [
|
||||||
'-x', 'c++',
|
'-x', 'c++',
|
||||||
|
'-std=c++17',
|
||||||
'-Wextra', '-Werror', '-Wall'
|
'-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
|
.PHONY: all
|
||||||
|
|
||||||
TARGETS=simple variables struct printer
|
TARGETS=simple variables struct printer threads
|
||||||
|
LDLIBS=-lpthread
|
||||||
|
|
||||||
all: $(TARGETS)
|
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()
|
call vimspector#test#setup#ClearDown()
|
||||||
endfunction
|
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( ... )
|
function! s:StartDebugging( ... )
|
||||||
if a:0 == 0
|
if a:0 == 0
|
||||||
let config = #{
|
let config = #{
|
||||||
|
|
@ -222,7 +211,7 @@ function! Test_ExpandVariables()
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
|
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' \*- t (Test): {...}',
|
\ ' \*- t (Test): {...}',
|
||||||
|
|
@ -240,7 +229,7 @@ function! Test_ExpandVariables()
|
||||||
" Step - stays expanded
|
" Step - stays expanded
|
||||||
call vimspector#StepOver()
|
call vimspector#StepOver()
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' - t (Test): {...}',
|
\ ' - t (Test): {...}',
|
||||||
|
|
@ -289,7 +278,7 @@ function! Test_ExpandVariables()
|
||||||
call setpos( '.', [ 0, 2, 1 ] )
|
call setpos( '.', [ 0, 2, 1 ] )
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ '- Scope: Locals',
|
\ '- Scope: Locals',
|
||||||
\ ' - t (Test): {...}',
|
\ ' - t (Test): {...}',
|
||||||
|
|
@ -389,7 +378,7 @@ function! Test_ExpandWatch()
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
|
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
@ -408,7 +397,7 @@ function! Test_ExpandWatch()
|
||||||
" Step - stays expanded
|
" Step - stays expanded
|
||||||
call vimspector#StepOver()
|
call vimspector#StepOver()
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
@ -460,7 +449,7 @@ function! Test_ExpandWatch()
|
||||||
call setpos( '.', [ 0, 3, 1 ] )
|
call setpos( '.', [ 0, 3, 1 ] )
|
||||||
call feedkeys( "\<CR>", 'xt' )
|
call feedkeys( "\<CR>", 'xt' )
|
||||||
call WaitForAssert( {->
|
call WaitForAssert( {->
|
||||||
\ s:assert_match_list(
|
\ AssertMatchist(
|
||||||
\ [
|
\ [
|
||||||
\ 'Watches: ----',
|
\ 'Watches: ----',
|
||||||
\ 'Expression: t',
|
\ 'Expression: t',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue