Compare commits

..

1 commit

Author SHA1 Message Date
Ben Jackson
cf44cae98c Add powershell demo
Doeesn't work due to debugger crashing on stack trace request.

Related #69
2019-11-02 23:10:04 +00:00
172 changed files with 3718 additions and 21497 deletions

View file

@ -7,108 +7,52 @@ assignees: ''
--- ---
> DO NOT DELETE THIS TEMPLATE. IF YOU DELETE THIS TEMPLATE AND DO NOT COMPLETE IT, YOUR ISSUE WILL BE CLOSED. **Describe the bug**
A clear and concise description of what the bug is.
### Describe the bug **To Reproduce**
List of steps to reproduce
> Provide A clear and concise description of what the bug is. Vimspector config file:
### Minimal reproduciton
> Please answer the following questions
* Does your issue reproduce using `vim --clean -Nu /path/to/vimspector/support/minimal_vimrc` ? \[Yes/No]
* If you are using Neovim, does your issue reproduce using Vim? \[Yes/No]
> List of steps to reproduce:
> 1. Run `vim ---clean Nu /path/to/vimspector/support/minimal_vimrc`
> 2. Open _this project_...
> 3. Press _this sequence of keys_
> Use the following Vimspector config file:
``` ```
paste .vimspector.json here paste .vimspector.json here
``` ```
### Expected behaviour **Expected behavior**
A clear and concise description of what you expected to happen.
> Provide A clear and concise description of what you expected to happen. **Actual behaviour**
What actually happened, including output, log files etc.
### Actual behaviour Please include:
* Vimspector log (~/.vimspector.log)
* Output from any or all UI diagnostic tabs (Server, etc.)
> What actually happened, including output, log files etc. **Environemnt**
> Please include: NOTE: NeoVim is not supported.
> * Vimspector log (~/.vimspector.log) NOTE: Windows is not supported.
> * Output from any or all UI diagnostic tabs (Server, etc.)
### Environemnt * Output of `vim --version`
***NOTE***: NeoVim is supported only on a best-effort basis. Please check the README
for limitations of neovim. Don't be offended if I ask you to reproduce issues in
Vim.
***NOTE***: Windows support is experimental and best-efrort only. If you find an
issue related to Windows or windows-isms, consider sending a PR or
discussing on Gitter rather than raising an issue.
* Version of Vimspector: (e.g. output of `git rev-parse HEAD` if cloned or the
name of the tarball used to install otherwise)
* Output of `:VimspectorDebugInfo`
``` ```
paste here paste here
``` ```
* Output of `vim --version` or `nvim --version` * Output of `which vim`:
``` ```
paste here paste here
``` ```
* Output of `which vim` or `which nvim`: * Output of `:py3 pass`:
```
paste here
```
* Output of `:py3 print( __import__( 'sys' ).version )`:
```
paste here
```
* Output of `:py3 import vim`:
```
paste here
```
* Output of `:py3 import vimspector`:
```
paste here
```
* For neovim: output of `:checkhealth`
``` ```
paste here paste here
``` ```
* Operating system: <linux or macOS> and version * Operating system: <linux or macOS> and version
### Declaration
> Please complete the following declaration. If this declaration is not completed, your issue may be closed without comment.
* I have read and understood [CONTRIBUTING.md](https://github.com/puremourning/vimspector/blob/master/CONTRIBUTING.md) \[Yes/No]
### Additional information
**Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View file

@ -1,12 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Questions and support
url: http://gitter.im/vimspector/Lobby
about: Please ask and answer questions here.
- name: Discussions
url: https://github.com/puremourning/vimspector/discussions
about: Please post questions and useful hints here
- name: Support for additional languages
url: https://github.com/puremourning/vimspector/wiki/languages
about: Please see here for information on support for additional languages

View file

@ -1,200 +0,0 @@
name: Build
on:
push:
branches:
- master
pull_request:
branches:
- master
defaults:
run:
shell: bash
jobs:
PythonLint:
runs-on: ubuntu-18.04
container: 'puremourning/vimspector:test'
steps:
- uses: actions/checkout@v2
- name: 'Insatll requirements'
run: pip3 install --user -r dev_requirements.txt
- name: 'Run flake8'
run: '$HOME/.local/bin/flake8 python3/ *.py'
VimscriptLint:
runs-on: 'ubuntu-18.04'
container: 'puremourning/vimspector:test'
steps:
- uses: actions/checkout@v2
- name: 'Install requirements'
run: pip3 install --user -r dev_requirements.txt
- name: 'Run vint'
run: $HOME/.local/bin/vint autoload/ compiler/ plugin/ tests/ syntax/
Linux:
runs-on: 'ubuntu-18.04'
container:
image: 'puremourning/vimspector:test'
options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined
steps:
- uses: actions/checkout@v2
- run: |
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
go get -u github.com/go-delve/delve/cmd/dlv
name: 'Install Delve for Go'
- uses: actions/cache@v2
with:
key: v1-gadgets-${{ runner.os }}-${{ hashFiles( 'python3/vimspector/gadgets.py' ) }}
path: gadgets/linux/download
name: Cache gadgets
- run: vim --version
name: 'Print vim version information'
- run: |
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
export GOPATH=$HOME/go
./run_tests --install --update --report messages --quiet
name: 'Run the tests'
id: run_tests
env:
VIMSPECTOR_MIMODE: gdb
- name: "Upload test logs"
uses: actions/upload-artifact@v2
if: failure()
with:
name: 'test-logs-${{ runner.os }}'
path: 'tests/logs/**/*'
- run: ./make_package linux ${{ github.run_id }}
name: 'Package'
# TODO: test the tarball
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: 'package-linux'
path: 'package/linux-${{ github.run_id }}.tar.gz'
# - name: Start SSH session if failed
# uses: luchihoratiu/debug-via-ssh@main
# if: failure()
# with:
# NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }}
# SSH_PASS: ${{ secrets.SSH_PASS }}
MacOS:
runs-on: 'macos-10.15'
steps:
- uses: actions/checkout@v2
- run: |
brew update-reset
brew doctor || true
for p in python@3.8 tcl-tk llvm lua luajit love; do
brew install $p || brew outdated $p || brew upgrade $p
done
brew install --cask macvim
brew link --overwrite python@3.8
name: 'Install vim and deps'
- run: go get -u github.com/go-delve/delve/cmd/dlv
name: 'Install Delve for Go'
- uses: actions/cache@v2
with:
key: v1-gadgets-${{ runner.os }}-${{ hashFiles( 'python3/vimspector/gadgets.py' ) }}
path: gadgets/macos/download
name: Cache gadgets
- name: 'Install .NET Core SDK 3.1'
uses: actions/setup-dotnet@v1.7.2
with:
dotnet-version: 3.1
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ^11
name: "Switch to xcode 11 because of .NET debugging bug"
- run: vim --version
name: 'Print vim version information'
- run: ./run_tests --install --update --report messages --quiet
name: 'Run the tests'
id: run_tests
env:
VIMSPECTOR_MIMODE: lldb
- name: "Upload test logs"
uses: actions/upload-artifact@v2
if: failure()
with:
name: 'test-logs-${{ runner.os }}'
path: 'tests/logs'
- run: ./make_package macos ${{ github.run_id }}
name: 'Package'
# TODO: test the tarball
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: 'package-macos'
path: 'package/macos-${{ github.run_id }}.tar.gz'
# - name: Start SSH session if failed
# uses: luchihoratiu/debug-via-ssh@main
# if: failure()
# with:
# NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }}
# SSH_PASS: ${{ secrets.SSH_PASS }} # [V]imspector
PublishRelease:
runs-on: 'ubuntu-18.04'
needs:
- Linux
- MacOS
if: github.ref == 'refs/heads/master'
steps:
- name: 'Download artifacts'
id: download_artifacts
uses: actions/download-artifact@v2
- name: 'Create Release'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.run_id }}
release_name: Build ${{ github.run_id }}
draft: false
prerelease: true
- name: 'Upload Linux Package'
id: upload-release-asset-linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.download_artifacts.outputs.download-path }}/package-linux/linux-${{ github.run_id }}.tar.gz
asset_name: vimspector-linux-${{ github.run_id }}.tar.gz
asset_content_type: application/gzip
- name: 'Upload MacOS Package'
id: upload-release-asset-macos
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.download_artifacts.outputs.download-path }}/package-macos/macos-${{ github.run_id }}.tar.gz
asset_name: vimspector-macos-${{ github.run_id }}.tar.gz
asset_content_type: application/gzip

View file

@ -1,27 +0,0 @@
name: "Lock Old Issues"
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '60'
# issue-exclude-created-before: ''
# issue-exclude-labels: ''
# issue-lock-labels: ''
# issue-lock-comment: ''
# issue-lock-reason: 'resolved'
# pr-lock-inactive-days: '365'
# pr-exclude-created-before: ''
# pr-exclude-labels: ''
# pr-lock-labels: ''
# pr-lock-comment: ''
# pr-lock-reason: 'resolved'
process-only: 'issues'

6
.gitignore vendored
View file

@ -6,7 +6,6 @@ tests/*.res
tests/messages tests/messages
tests/debuglog tests/debuglog
test.log test.log
*.testlog
gadgets/ gadgets/
package/ package/
*.pyc *.pyc
@ -16,8 +15,3 @@ README.md.toc.*
.DS_Store .DS_Store
*.vimspector.log *.vimspector.log
support/test/csharp/*.exe* support/test/csharp/*.exe*
.neomake.log
configurations/
venv/
test-base/
tags

View file

@ -3,14 +3,11 @@ pull_request_rules:
conditions: conditions:
- author=puremourning - author=puremourning
- base=master - base=master
# Review
- status-success=code-review/reviewable - status-success=code-review/reviewable
- status-success=puremourning.vimspector # Azure pipeline
- "#changes-requested-reviews-by=0" - "#changes-requested-reviews-by=0"
# CI https://doc.mergify.io/conditions.html#github-actions
- status-success=PythonLint
- status-success=VimscriptLint
- status-success=Linux
- status-success=MacOS
actions: &merge-actions actions: &merge-actions
merge: merge:
method: merge method: merge
@ -21,16 +18,12 @@ pull_request_rules:
conditions: conditions:
- author!=puremourning - author!=puremourning
- base=master - base=master
# Review
- status-success=code-review/reviewable - status-success=code-review/reviewable
- status-success=puremourning.vimspector # Azure pipeline
- approved-reviews-by=puremourning
- "#approved-reviews-by>=1" - "#approved-reviews-by>=1"
- "#changes-requested-reviews-by=0" - "#changes-requested-reviews-by=0"
- approved-reviews-by=puremourning
# CI https://doc.mergify.io/conditions.html#github-actions
- status-success=PythonLint
- status-success=VimscriptLint
- status-success=Linux
- status-success=MacOS
actions: actions:
<<: *merge-actions <<: *merge-actions
comment: comment:

View file

@ -1,22 +1,138 @@
{ {
"configurations": { "adapters": {
"Python: Attach To Vim": { "lldb-mi": {
"variables": { "name": "lldb-mi",
"port": "5678", "command": [
"host": "localhost" "node",
"$HOME/.vscode/extensions/webfreak.debug-0.22.0/out/src/lldb.js"
]
}, },
"adapter": "multi-session", "cppdbg": {
"configuration": { "name": "cppdbg",
"request": "attach" "command": [ "$HOME/.vscode/extensions/ms-vscode.cpptools-0.20.1/debugAdapters/OpenDebugAD7" ],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
}
},
"python": {
"name": "python",
"command": [
"node",
"$HOME/.vscode/extensions/ms-python.python-2018.4.0/out/client/debugger/Main.js"
]
},
"bashdb": {
"name": "bashdb",
"command": [
"node",
"$HOME/.vscode/extensions/rogalmic.bash-debug-0.2.0/out/bashDebug.js"
]
},
"lldb": {
"name": "lldb",
"command": [
"lldb",
"-b",
"-O",
"command script import '$HOME/.vscode/extensions/vadimcn.vscode-lldb-0.8.7/adapter'",
"-O",
"script adapter.main.run_stdio_session()"
]
} }
}, },
"Python: Run current script": { "configurations": {
"adapter": "debugpy", "simple_c_program - lldb-mi Launch": {
"adapter": "lldb-mi",
"configuration": { "configuration": {
"request": "launch", "request": "launch",
"program": "${file}", "target": "support/test/cpp/simple_c_program/test",
"args": [ "*${args:--update-gadget-config}" ], "args": [],
"justMyCode#json": "${justMyCode:true}" "cwd": ".",
"lldbmipath": "$HOME/.vscode/extensions/ms-vscode.cpptools-0.20.1/debugAdapters/lldb/bin/lldb-mi",
"trace": true,
"logFilePath": "$HOME/.vimspector.protocol.log"
}
},
"simple_c_progra - ms Launch": {
"adapter": "cppdbg",
"configuration": {
"name": "ms Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/support/test/cpp/simple_c_program/test",
"args": [],
"cwd": "$HOME",
"environment": [],
"externalConsole": true,
"MIMode": "lldb"
}
},
"simple_python - launch": {
"adapter": "python",
"configuration": {
"name": "Python: Current File",
"type": "python",
"request": "launch",
"cwd": "${workspaceRoot}/support/test/python/simple_python",
"stopOnEntry": true,
"console": "externalTerminal",
"debugOptions": [],
"program": "${workspaceRoot}/support/test/python/simple_python/main.py"
}
},
"simple_c_program - MS Attach": {
"adapter": "cppdbg",
"configuration": {
"name": "(lldb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceRoot}/support/test/cpp/simple_c_program/test",
"MIMode": "lldb"
}
},
"bashdb": {
"adapter": "bashdb",
"configuration": {
"type": "bashdb",
"request": "launch",
"name": "Bash-Debug (simplest configuration)",
"program": "$HOME/.vim/bundle/YouCompleteMe/install.sh",
"args": [],
"cwd": "$HOME/.vim/bundle/YouCompleteMe",
"pathBash": "bash",
"pathBashdb": "bashdb",
"pathCat": "cat",
"pathMkfifo": "mkfifo",
"pathPkill": "pkill",
"showDebugOutput": true,
"trace": true
}
},
"lldb launch": {
"adapter": "lldb",
"configuration": {
"type": "lldb",
"request": "launch",
"name": "LLDB: Launch",
"program": "$HOME/Development/vim/src/vim",
"args": [],
"cwd": "$HOME/Development/vim"
}
},
"racerd": {
"adapter": "lldb",
"configuration": {
"type": "lldb",
"request": "launch",
"name": "LLDB: Launch",
"program": "$HOME/.vim/bundle/YouCompleteMe/third_party/ycmd/third_party/racerd/target/debug/racerd",
"args": [
"serve",
"--port=12345",
"--secret-file=secretfile"
],
"cwd": "$HOME/.vim/bundle/YouCompleteMe/third_party/ycmd"
} }
} }
} }

View file

@ -1,3 +0,0 @@
cmdargs:
color: true
severity: style_problem

View file

@ -1,61 +0,0 @@
try:
from ycmd.extra_conf_support import IgnoreExtraConf
except ImportError:
IgnoreExtraConf = None
import os.path as p
PATH_TO_THIS_DIR = p.dirname( p.abspath( __file__ ) )
def Settings( **kwargs ):
if kwargs[ 'language' ] == 'json':
return {
'ls': {
'json': {
'schemas': [
{
'fileMatch': [ '.vimspector.json' ],
'url':
f'file://{PATH_TO_THIS_DIR}/docs/schema/vimspector.schema.json'
},
{
'fileMatch': [ '.gadgets.json', '.gadgets.d/*.json' ],
'url':
f'file://{PATH_TO_THIS_DIR}/docs/schema/gadgets.schema.json'
}
]
}
},
'capabilities': {
'textDocument': {
'completion': {
'completionItem': {
'snippetSupport': True
}
}
}
}
}
if kwargs[ 'language' ] == 'python':
return {
'sys_path': [
p.join( PATH_TO_THIS_DIR, 'python3' )
],
'ls': {
'python': {
'analysis': {
'extraPaths': [
p.join( PATH_TO_THIS_DIR, 'python3' ),
],
'useLibraryCodeForTypes': True
}
}
}
}
if IgnoreExtraConf:
raise IgnoreExtraConf()
return None

View file

@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
`benjacksonbkg@gmail.com`. All complaints will be reviewed and investigated
promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View file

@ -1,235 +0,0 @@
# Contributing to Vimspector
Contributions to Vimspector are always welcome. Contributions can take many
forms, such as:
* Raising, responding to, or reacting to Issues or Pull Requests
* Testing new in-progress changes and providing feedback
* Discussing in the Gitter channel
* etc.
At all times the [code of conduct](#code-of-conduct) applies.
## Troubleshooting
It's not completely trivial to configure Vimspector and there is a fairly large
amount of documentation. I know full well that documentation isn't everything,
so the first step in troubleshooting is to try a sample project that's known to
work, to check if the problem is your project configuration rather than an
actual bug.
Therefore before raising an issue for a supported language, please check with
the sample projects in `support/test/<language>` and `tests/testdata/` to see if
the problem is with your project settings, rather than with vimspector.
Information on these is in [the README](README.md#trying-it-out).
If in doubt, ask on Gitter.
## Diagnostics
Whenever reporting any type of fault, or difficulty in making the plugin
work, please always include _all_ of the diagnostics requested in the
[issue template][issue-template]. Please do not be offended if your request
is ignored if it does not include the requested diagnostics.
The Vimspector log file contains a full trace of the communication between
Vimspector and the debug adapter. This is the primary source of diagnostic
information when something goes wrong that's not a clear Vim traceback.
If you just want to see the Vimspector log file, use `:VimspectorToggleLog`,
which will tail it in a little window (doesn't work on Windows).
## Issues
The GitHub issue tracker is for *bug reports* and *features requests* for the
Vimspector project, and on-topic comments and follow-ups to them. It is not for
general discussion, general support or for any other purpose.
Please **search the issue tracker for similar issues** before creating a new
one. There's no point in duplication; if an existing open issue addresses your
problem, please comment there instead of creating a duplicate. However, if the
issue you found is **closed as resolved** (e.g. with a PR or the original user's
problem was resolved), raise a **new issue**, because you've found a new
problem. Reference the original issue if you think that's useful information.
Closed issues which have been inactive for 60 days will be locked, this helps to
keep discussions focussed. If you believe you are still experiencing an issue
which has been closed, please raise a new issue, completing the issue template.
If you do find a similar _open_ issue, **don't just post 'me too' or similar**
responses. This almost never helps resolve the issue, and just causes noise for
the maintainers. Only post if it will aid the maintainers in solving the issue;
if there are existing diagnostics requested in the thread, perform
them and post the results.
Please do not be offended if your Issue or comment is closed or hidden, for any
of the following reasons:
* The [issue template][issue-template] was not completed
* The issue or comment is off-topic
* The issue does not represent a Vimspector bug or feature request
* The issue cannot be reasonably reproduced using the minimal vimrc
* The issue is a duplicate of an existing issue
* etc.
Issue titles are important. It's not usually helpful to write a title like
`Issue with Vimspector` or `Issue configuring` or even pasting an error message.
Spend a minute to come up with a consise summary of the problem. This helps with
management of issues, with triage, and above all with searching.
But above all else, please *please* complete the issue template. I know it is a
little tedious to get all the various diagnostics, but you *must* provide them,
*even if you think they are irrelevant*. This is important, because the
maintainer(s) can quickly cross-check theories by inspecting the provided
diagnostics without having to spend time asking for them, and waiting for the
response. This means *you get a better answer, faster*. So it's worth it,
honestly.
### Reproduce your issue with the minimal vimrc
Many problems can be caused by unexpected configuration or other plugins.
Therefore when raising an issue, you must attempt to reproduce your issue
with the minimal vimrc provided, and to provide any additional changes required
to that file in order to reproduce it. The purpose of this is to ensure that
the issue is not a conflict with another plugin, or a problem unique to your
configuration.
If your issue does _not_ reproduce with the minimal vimrc, then you must say so
in the issue report.
The minimal vimrc is in `support/test/minimal_vimrc` and can be used as follows:
```
vim --clean -Nu /path/to/vimspector/support/minimal_vimrc
```
## Pull Requests
Vimspector is open to all contributors with ideas great and small! However,
there is a limit to the intended scope of the plugin and the amount of time the
maintainer has to support and... well... maintain features. It's probably well
understood that the contributor's input typically ends when a PR is megred, but
the maintainers have to keep it working forever.
### Small changes
For bug fixes, documentation changes, gadget versin updates, etc. please just
send a PR, I'm super happy to merge these!
If you are unsure, or looking for some pointers, feel free to ask in Gitter, or
mention is in the PR.
### Larger changes
For larger features that might be in any way controvertial, or increase the
complexity of the overall plugin, please come to Gitter and talk to the
maintainer(s) first. This saves a lot of potential back-and-forth and makes sure
that we're "on the same page" about the idea and the ongoing maintenance.
In addition, if you like hacking, feel free to raise a PR tagged with `[RFC]` in
the title and we can discuss the idea. I still prefer to discuss these things on
Gitter rather than back-and-forth on GitHub, though.
Please don't be offended if the maintainer(s) request significant rework for (or
perhaps even dismiss) a PR that's not gone through this process.
Please also don't be offended if the maintainer(s) ask if you're willing to
provide ongoing support for the feature. As an OSS project manned entirely in
what little spare time the maintainer(s) have, we're always looking for
contributions and contributors who will help with support and maintenance of
larger new features.
### PR Guidelines
When contributing pull requests, I ask that:
* You provide a clear and complete summary of the change, the use case and how
the change was tested.
* You avoid using APIs that are not available in the versions listed in the
dependencies on README.md
* You add tests for your PR.
* You test your changes in both Vim and Neovim at the supported versions (and
state that in the PR).
* You follow the style of the code as-is; the python code is YCM-stye, it is
*not* PEP8, nor should it be.
### Running the tests locally
There are 2 ways:
1. In the docker container. The CI tests for linux run in a container, so as to
ensure a consistent test environment. The container is defined in
`./tests/ci/`. There is also a container in `./tests/manual` which can be
used to run the tests interractively. To do this install and start docker,
then run `./tests/manual/run`. This will drop you into a bash shell inside
the linux container with your local vimspector code mounted. You can then
follow the instructions for running tets directly.
1. Directly: Run `./install_gadget.py --all` and then `./run_tests`. Note that
this depends on your runtime environment and might not match CI. I recommend
running the tests in the docker container. If you have your own custom
gadgets and/or custom configurations (in `vimspector/configurations` and/or
`vimspector/gadget`, then consider using `./run_tests --install --basedir
/tmp/vimspector_test` (then delete `/tmp/vimspector_test`). This will install
the gadgets to that dir and use it for the gadget dir/config dir so that your
custom configuration won't interfere with the tess.
When tests fail, they dump a load of logs to a directory for each failed tests.
Usually the most useful output is `messages`, which tells you what actually
failed.
For more infomration on the test framework, see
[this article](https://vimways.org/2019/a-test-to-attest-to/), authored by the
Vimspector creator.
### Code Style
The code style of the Python code is "YCM" style, because that's how I like it.
`flake8` is used to check for certain errors and code style.
The code style of the Vimscript is largely the same, and it is linted by
`vint`.
To run them:
* (optional) Create and activate a virtual env:
`python3 -m venv venv ; source venv/bin/activate`
* Install the development dependencies: `pip install -r dev_requirements.txt`
* Run `flake8`: `flake8 python3/ *.py`
* Run `vint`: `vint autoload/ plugin/ tests/`
They're also run by CI, so please check for lint failures. The canonical
definition of the command to run is the command run in CI, i.e. in
`.git/workflows/build.yml`.
### Debugging Vimspector
You can debug vimspector's python code using vimspector! We can use debugpy,
from within Vim's embedded python and connect to it. Here's how:
1. In one instance of vim, run the following to get debugpy to start listening
for us to connect: `:py3 __import__( 'vimspector', fromlist=[ 'developer' ]
).developer.SetUpDebugpy()`
2. In another instance of Vim, set a breakpoint in the vimspector python code
you want to debug and launch vimspector (e.g. `<F5>`). Select the `Python:
attach to vim` profile. This will attach to the debugpy running in the other
vim.
3. Back in the first vim (the debuggee), trigger the vimspector code in
question, e.g. by starting to debug something else.
4. You'll see it pause, and the 2nd vim (the debugger), you should be able to
step through and inspect as with any other python remote debugging.
NB. It's also possible to debug the vimscript code using vimspector, but this
requires unreleased vim patches and a fair amount of faff. You can always use
`:debug` (see the help) for this though.
# Code of conduct
Please see [code of conduct](CODE_OF_CONDUCT.md).
[vint]: https://github.com/Vimjas/vint
[flake8]: https://flake8.pycqa.org/en/latest/
[issue-template]: https://github.com/puremourning/vimspector/blob/master/.github/ISSUE_TEMPLATE/bug_report.md

1890
README.md

File diff suppressed because it is too large Load diff

View file

@ -13,559 +13,103 @@
" See the License for the specific language governing permissions and " See the License for the specific language governing permissions and
" limitations under the License. " limitations under the License.
if !has( 'python3' )
finish
endif
" Boilerplate {{{ " Boilerplate {{{
let s:save_cpo = &cpoptions let s:save_cpo = &cpoptions
set cpoptions&vim set cpoptions&vim
" }}} " }}}
function! s:Debug( ... ) abort
py3 <<EOF
if _vimspector_session is not None:
_vimspector_session._logger.debug( *vim.eval( 'a:000' ) )
EOF
endfunction
call vimspector#internal#state#Reset()
let s:enabled = v:null function! vimspector#Launch() abort
py3 _vimspector_session.Start()
function! s:Initialised() abort
return s:enabled != v:null
endfunction
function! s:Enabled() abort
if !s:Initialised()
let s:enabled = vimspector#internal#state#Reset()
endif
return s:enabled
endfunction
function! vimspector#Launch( ... ) abort
if !s:Enabled()
return
endif
py3 _vimspector_session.Start( *vim.eval( 'a:000' ) )
endfunction endfunction
function! vimspector#LaunchWithSettings( settings ) abort function! vimspector#LaunchWithSettings( settings ) abort
if !s:Enabled()
return
endif
py3 _vimspector_session.Start( launch_variables = vim.eval( 'a:settings' ) ) py3 _vimspector_session.Start( launch_variables = vim.eval( 'a:settings' ) )
endfunction endfunction
function! vimspector#Reset( ... ) abort function! vimspector#Reset() abort
if !s:Enabled() py3 _vimspector_session.Reset()
return
endif
if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.Reset( **vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#Restart() abort function! vimspector#Restart() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.Restart() py3 _vimspector_session.Restart()
endfunction endfunction
function! vimspector#ClearBreakpoints() abort function! vimspector#ClearBreakpoints() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ClearBreakpoints() py3 _vimspector_session.ClearBreakpoints()
endfunction endfunction
function! vimspector#ToggleBreakpoint( ... ) abort function! vimspector#ToggleBreakpoint() abort
if !s:Enabled() py3 _vimspector_session.ToggleBreakpoint()
return
endif
if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.ToggleBreakpoint( vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#SetLineBreakpoint( file_name, line_num, ... ) abort function! vimspector#AddFunctionBreakpoint( function ) abort
if !s:Enabled() py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ) )
return
endif
if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.SetLineBreakpoint(
\ vim.eval( 'a:file_name' ),
\ int( vim.eval( 'a:line_num' ) ),
\ vim.eval( 'options' ) )
endfunction
function! vimspector#ClearLineBreakpoint( file_name, line_num ) abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ClearLineBreakpoint(
\ vim.eval( 'a:file_name' ),
\ int( vim.eval( 'a:line_num' ) ) )
endfunction
function! vimspector#RunToCursor() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.RunTo(
\ vim.eval( "expand( '%' )" ),
\ int( vim.eval( "line( '.' )" ) ) )
endfunction
function! vimspector#AddFunctionBreakpoint( function, ... ) abort
if !s:Enabled()
return
endif
if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.AddFunctionBreakpoint( vim.eval( 'a:function' ),
\ vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#StepOver() abort function! vimspector#StepOver() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.StepOver() py3 _vimspector_session.StepOver()
endfunction endfunction
function! vimspector#StepInto() abort function! vimspector#StepInto() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.StepInto() py3 _vimspector_session.StepInto()
endfunction endfunction
function! vimspector#StepOut() abort function! vimspector#StepOut() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.StepOut() py3 _vimspector_session.StepOut()
endfunction endfunction
function! vimspector#Continue() abort function! vimspector#Continue() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.Continue() py3 _vimspector_session.Continue()
endfunction endfunction
function! vimspector#Pause() abort function! vimspector#Pause() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.Pause() py3 _vimspector_session.Pause()
endfunction endfunction
function! vimspector#PauseContinueThread() abort function! vimspector#Stop() abort
if !s:Enabled() py3 _vimspector_session.Stop()
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
endif
if a:0 == 0
let options = {}
else
let options = a:1
endif
py3 _vimspector_session.Stop( **vim.eval( 'options' ) )
endfunction endfunction
function! vimspector#ExpandVariable() abort function! vimspector#ExpandVariable() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ExpandVariable() py3 _vimspector_session.ExpandVariable()
endfunction endfunction
function! vimspector#SetVariableValue( ... ) abort
if !s:Enabled()
return
endif
if a:0 == 0
py3 _vimspector_session.SetVariableValue()
else
py3 _vimspector_session.SetVariableValue( new_value = vim.eval( 'a:1' ) )
endif
endfunction
function! vimspector#DeleteWatch() abort function! vimspector#DeleteWatch() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.DeleteWatch() py3 _vimspector_session.DeleteWatch()
endfunction endfunction
function! vimspector#GoToFrame() abort function! vimspector#GoToFrame() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ExpandFrameOrThread() py3 _vimspector_session.ExpandFrameOrThread()
endfunction endfunction
function! vimspector#UpFrame() abort function! vimspector#AddWatch( expr ) abort
if !s:Enabled() py3 _vimspector_session.AddWatch( vim.eval( 'a:expr' ) )
return
endif
py3 _vimspector_session.UpFrame()
endfunction
function! vimspector#DownFrame() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.DownFrame()
endfunction
function! vimspector#AddWatch( ... ) abort
if !s:Enabled()
return
endif
if a:0 == 0
let expr = input( 'Enter watch expression: ',
\ '',
\ 'custom,vimspector#CompleteExpr' )
else
let expr = a:1
endif
if expr ==# ''
return
endif
py3 _vimspector_session.AddWatch( vim.eval( 'expr' ) )
endfunction endfunction
function! vimspector#AddWatchPrompt( expr ) abort function! vimspector#AddWatchPrompt( expr ) abort
if !s:Enabled()
return
endif
stopinsert stopinsert
setlocal nomodified setlocal nomodified
call vimspector#AddWatch( a:expr ) call vimspector#AddWatch( a:expr )
endfunction endfunction
function! vimspector#Evaluate( expr ) abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ShowOutput( 'Console' )
py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ), True )
endfunction
function! vimspector#EvaluateConsole( expr ) abort function! vimspector#EvaluateConsole( expr ) abort
if !s:Enabled()
return
endif
stopinsert stopinsert
setlocal nomodified setlocal nomodified
py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ), False ) py3 _vimspector_session.EvaluateConsole( vim.eval( 'a:expr' ) )
endfunction endfunction
function! vimspector#ShowOutput( ... ) abort function! vimspector#ShowOutput( category ) abort
if !s:Enabled() py3 _vimspector_session.ShowOutput( vim.eval( 'a:category' ) )
return
endif
if a:0 == 1
py3 _vimspector_session.ShowOutput( vim.eval( 'a:1' ) )
else
py3 _vimspector_session.ShowOutput( 'Console' )
endif
endfunction
function! vimspector#ShowOutputInWindow( win_id, category ) abort
if !s:Enabled()
return
endif
py3 __import__( 'vimspector',
\ fromlist = [ 'output' ] ).output.ShowOutputInWindow(
\ int( vim.eval( 'a:win_id' ) ),
\ vim.eval( 'a:category' ) )
endfunction
function! vimspector#ToggleLog() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ToggleLog()
endfunction endfunction
function! vimspector#ListBreakpoints() abort function! vimspector#ListBreakpoints() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.ListBreakpoints() py3 _vimspector_session.ListBreakpoints()
endfunction endfunction
function! vimspector#GetConfigurations() abort
if !s:Enabled()
return
endif
let configurations = py3eval(
\ 'list( _vimspector_session.GetConfigurations( {} )[ 1 ].keys() )'
\ . ' if _vimspector_session else []' )
return configurations
endfunction
function! vimspector#CompleteOutput( ArgLead, CmdLine, CursorPos ) abort
if !s:Enabled()
return
endif
let buffers = py3eval( '_vimspector_session.GetOutputBuffers() '
\ . ' if _vimspector_session else []' )
return join( buffers, "\n" )
endfunction
function! vimspector#CompleteExpr( ArgLead, CmdLine, CursorPos ) abort
if !s:Enabled()
return
endif
let col = len( a:ArgLead )
let prev_non_keyword_char = match( a:ArgLead[ 0 : col - 1 ], '\k*$' ) + 1
return join( py3eval( '_vimspector_session.GetCommandLineCompletions( '
\ . 'vim.eval( "a:ArgLead" ), '
\ . 'int( vim.eval( "prev_non_keyword_char" ) ) )' ),
\ "\n" )
endfunction
let s:latest_completion_request = {}
function! vimspector#CompleteFuncSync( prompt, find_start, query ) abort
if py3eval( 'not _vimspector_session' )
if a:find_start
return -3
endif
return v:none
endif
if a:find_start
" We're busy
if !empty( s:latest_completion_request )
return -3
endif
let line = getline( line( '.' ) )[ len( a:prompt ) : ]
let col = col( '.' ) - len( a:prompt )
" It seems that most servers don't implement the 'start' parameter, which is
" clearly necessary, as they all seem to assume a specific behaviour, which
" is undocumented.
let s:latest_completion_request.items =
\ py3eval( '_vimspector_session.GetCompletionsSync( '
\.' vim.eval( "line" ), '
\.' int( vim.eval( "col" ) ) )' )
let s:latest_completion_request.line = line
let s:latest_completion_request.col = col
let prev_non_keyword_char = match( line[ 0 : col - 1 ], '\k*$' ) + 1
let query_len = col - prev_non_keyword_char
let start_pos = col
for item in s:latest_completion_request.items
if !has_key( item, 'start' ) || !has_key( item, 'length' )
" The specification states that if start is not supplied, isertion
" should be at the requested column. But about 0 of the servers actually
" implement that
" (https://github.com/microsoft/debug-adapter-protocol/issues/138)
let item.start = prev_non_keyword_char
let item.length = query_len
else
" For some reason, the returned start value is 0-indexed even though we
" use columnsStartAt1
let item.start += 1
endif
if !has_key( item, 'text' )
let item.text = item.label
endif
if item.start < start_pos
let start_pos = item.start
endif
endfor
let s:latest_completion_request.start_pos = start_pos
let s:latest_completion_request.prompt = a:prompt
" call s:Debug( 'FindStart: %s', {
" \ 'line': line,
" \ 'col': col,
" \ 'prompt': len( a:prompt ),
" \ 'start_pos': start_pos,
" \ 'returning': ( start_pos + len( a:prompt ) ) - 1,
" \ } )
" start_pos is 1-based and the return of findstart is 0-based
return ( start_pos + len( a:prompt ) ) - 1
else
let items = []
let pfxlen = len( s:latest_completion_request.prompt )
for item in s:latest_completion_request.items
if item.start > s:latest_completion_request.start_pos
" fix up the text (insert anything that is already present in the line
" that would be erased by the fixed-up earlier start position)
"
" both start_pos and item.start are 1-based
let item.text = s:latest_completion_request.line[
\ s:latest_completion_request.start_pos + pfxlen - 1 :
\ item.start + pfxlen - 1 ] . item.text
endif
if item.length > len( a:query )
" call s:Debug( 'Rejecting %s, length is greater than %s',
" \ item,
" \ len( a:query ) )
continue
endif
call add( items, { 'word': item.text,
\ 'abbr': item.label,
\ 'menu': get( item, 'type', '' ),
\ 'icase': 1,
\ } )
endfor
let s:latest_completion_request = {}
" call s:Debug( 'Items: %s', items )
return { 'words': items, 'refresh': 'always' }
endif
endfunction
function! vimspector#OmniFuncWatch( find_start, query ) abort
return vimspector#CompleteFuncSync( 'Expression: ', a:find_start, a:query )
endfunction
function! vimspector#OmniFuncConsole( find_start, query ) abort
return vimspector#CompleteFuncSync( '> ', a:find_start, a:query )
endfunction
function! vimspector#Install( bang, ... ) abort
if !s:Enabled()
return
endif
let prefix = vimspector#internal#state#GetAPIPrefix()
py3 __import__( 'vimspector',
\ fromlist = [ 'installer' ] ).installer.RunInstaller(
\ vim.eval( 'prefix' ),
\ vim.eval( 'a:bang' ) == '!',
\ *vim.eval( 'a:000' ) )
endfunction
function! vimspector#CompleteInstall( ArgLead, CmdLine, CursorPos ) abort
if !s:Enabled()
return
endif
return py3eval( '"\n".join('
\ . '__import__( "vimspector", fromlist = [ "gadgets" ] )'
\ . '.gadgets.GADGETS.keys() '
\ . ')' )
endfunction
function! vimspector#Update( bang, ... ) abort
if !s:Enabled()
return
endif
let prefix = vimspector#internal#state#GetAPIPrefix()
py3 __import__( 'vimspector',
\ fromlist = [ 'installer' ] ).installer.RunUpdate(
\ vim.eval( 'prefix' ),
\ vim.eval( 'a:bang' ) == '!',
\ *vim.eval( 'a:000' ) )
endfunction
function! vimspector#AbortInstall() abort
if !s:Enabled()
return
endif
let prefix = vimspector#internal#state#GetAPIPrefix()
py3 __import__( 'vimspector', fromlist = [ 'installer' ] ).installer.Abort()
endfunction
function! vimspector#OnBufferCreated( file_name ) abort
if len( a:file_name ) == 0
return
endif
" Don't actually load up vimsepctor python in autocommands that trigger
" regularly. We'll only create the session obkect in s:Enabled()
if !s:Initialised()
return
endif
if !s:Enabled()
return
endif
py3 _vimspector_session.RefreshSigns( vim.eval( 'a:file_name' ) )
endfunction
function! vimspector#ShowEvalBalloon( is_visual ) abort
if a:is_visual
let expr = py3eval( '__import__( "vimspector", fromlist = [ "utils" ] )'
\ . '.utils.GetVisualSelection('
\ . ' int( vim.eval( "winbufnr( winnr() )" ) ) )' )
let expr = join( expr, '\n' )
else
let expr = expand( '<cexpr>' )
endif
return py3eval( '_vimspector_session.ShowEvalBalloon('
\ . ' int( vim.eval( "winnr()" ) ), "'
\ . expr
\ . '", 0 )' )
endfunction
function! vimspector#PrintDebugInfo() abort
if !s:Enabled()
return
endif
py3 _vimspector_session.PrintDebugInfo()
endfunction
" Boilerplate {{{ " Boilerplate {{{
let &cpoptions=s:save_cpo let &cpoptions=s:save_cpo
unlet s:save_cpo unlet s:save_cpo

View file

@ -19,323 +19,15 @@ let s:save_cpo = &cpoptions
set cpoptions&vim set cpoptions&vim
" }}} " }}}
scriptencoding utf-8 function! vimspector#internal#balloon#BalloonExpr() abort
" winnr + 1 because for *no good reason* winnr is 0 based here unlike
let s:popup_win_id = 0 " everywhere else
let s:nvim_border_win_id = 0 " int() because for *no good reason* winnr is a string.
" py3 _vimspector_session.ShowBalloon( int( vim.eval( 'v:beval_winnr' ) ) + 1,
" tooltip dimensions \ vim.eval( 'v:beval_text' ) )
let s:min_width = 1 return '...'
let s:min_height = 1
let s:max_width = 80
let s:max_height = 20
let s:is_neovim = has( 'nvim' )
" This is used as the balloonexpr in vim to show the Tooltip at the hover
" position
function! vimspector#internal#balloon#HoverTooltip() abort
return py3eval( '_vimspector_session.ShowEvalBalloon('
\ . ' int( vim.eval( "v:beval_winnr" ) ) + 1,'
\ . ' vim.eval( "v:beval_text"),'
\ . ' 1 )' )
endfunction endfunction
function! vimspector#internal#balloon#CreateTooltip( is_hover, ... ) abort
let body = []
if a:0 > 0
let body = a:1
endif
if s:popup_win_id != 0
call vimspector#internal#balloon#Close()
endif
if s:is_neovim
call s:CreateNeovimTooltip( body )
else
let config = {
\ 'wrap': 0,
\ 'filtermode': 'n',
\ 'maxwidth': s:max_width,
\ 'maxheight': s:max_height,
\ 'minwidth': s:min_width,
\ 'minheight': s:min_height,
\ 'scrollbar': 1,
\ 'border': [],
\ 'padding': [ 0, 1, 0, 1],
\ 'drag': 1,
\ 'resize': 1,
\ 'close': 'button',
\ 'callback': 'vimspector#internal#balloon#CloseCallback',
\ }
let config = vimspector#internal#popup#SetBorderChars( config )
if a:is_hover
let config[ 'filter' ] = 'vimspector#internal#balloon#MouseFilter'
let config[ 'mousemoved' ] = [ 0, 0, 0 ]
let s:popup_win_id = popup_beval( body, config )
else
let config[ 'filter' ] = 'vimspector#internal#balloon#CursorFilter'
let config[ 'moved' ] = 'any'
let config[ 'cursorline' ] = 1
let config[ 'mapping' ] = 0
let s:popup_win_id = popup_atcursor( body, config )
endif
endif
return s:popup_win_id
endfunction
" Filters for vim {{{
function! vimspector#internal#balloon#MouseFilter( winid, key ) abort
if a:key ==# "\<Esc>"
call vimspector#internal#balloon#Close()
return 0
endif
if index( [ "\<leftmouse>", "\<2-leftmouse>" ], a:key ) < 0
return 0
endif
let handled = 0
let mouse_coords = getmousepos()
" close the popup if mouse is clicked outside the window
if mouse_coords[ 'winid' ] != a:winid
call vimspector#internal#balloon#Close()
return 0
endif
" place the cursor according to the click
call win_execute( a:winid,
\ ':call cursor( '
\ . mouse_coords[ 'line' ]
\ . ', '
\ . mouse_coords[ 'column' ]
\ . ' )' )
" expand the variable if we got double click
if a:key ==? "\<2-leftmouse>"
call py3eval( '_vimspector_session.ExpandVariable('
\ . 'buf = vim.buffers[ ' . winbufnr( a:winid ) . ' ],'
\ . 'line_num = ' . line( '.', a:winid )
\ . ')' )
let handled = 1
endif
return handled
endfunction
function! s:MatchKey( key, candidates ) abort
for candidate in a:candidates
" If the mapping string looks like a special character, then try and
" expand it. This is... a hack. The whole thing only works if the mapping
" is a single key (anyway), and so we assume any string starting with < is a
" special key (which will be the common case) and try and map it. If it
" fails... it fails.
if candidate[ 0 ] == '<'
try
execute 'let candidate = "\' . candidate . '"'
endtry
endif
if candidate ==# a:key
return v:true
endif
endfor
return v:false
endfunction
function! vimspector#internal#balloon#CursorFilter( winid, key ) abort
let mappings = py3eval(
\ "__import__( 'vimspector',"
\." fromlist = [ 'settings' ] ).settings.Dict("
\." 'mappings' )[ 'variables' ]" )
if index( [ "\<LeftMouse>", "\<2-LeftMouse>" ], a:key ) >= 0
return vimspector#internal#balloon#MouseFilter( a:winid, a:key )
endif
if s:MatchKey( a:key, mappings.expand_collapse )
call py3eval( '_vimspector_session.ExpandVariable('
\ . 'buf = vim.buffers[ ' . winbufnr( a:winid ) . ' ],'
\ . 'line_num = ' . line( '.', a:winid )
\ . ')' )
return 1
elseif s:MatchKey( a:key, mappings.set_value )
call py3eval( '_vimspector_session.SetVariableValue('
\ . 'buf = vim.buffers[ ' . winbufnr( a:winid ) . ' ],'
\ . 'line_num = ' . line( '.', a:winid )
\ . ')' )
return 1
endif
return popup_filter_menu( a:winid, a:key )
endfunction
" }}}
" Closing {{{
function! vimspector#internal#balloon#CloseCallback( ... ) abort
let s:popup_win_id = 0
let s:nvim_border_win_id = 0
return py3eval( '_vimspector_session.CleanUpTooltip()' )
endfunction
function! vimspector#internal#balloon#Close() abort
if s:popup_win_id == 0
return
endif
if s:is_neovim
call nvim_win_close( s:popup_win_id, v:true )
call nvim_win_close( s:nvim_border_win_id, v:true )
call vimspector#internal#balloon#CloseCallback()
else
call popup_close(s:popup_win_id)
endif
endfunction
" }}}
" Neovim pollyfill {{{
function! vimspector#internal#balloon#ResizeTooltip() abort
if !s:is_neovim
" Vim does this for us
return
endif
if s:popup_win_id <= 0 || s:nvim_border_win_id <= 0
" nothing to resize
return
endif
noautocmd call win_gotoid( s:popup_win_id )
let buf_lines = getline( 1, '$' )
let width = s:min_width
let height = min( [ max( [ s:min_height, len( buf_lines ) ] ),
\ s:max_height ] )
" calculate the longest line
for l in buf_lines
let width = max( [ width, len( l ) ] )
endfor
let width = min( [ width, s:max_width ] )
let opts = {
\ 'width': width,
\ 'height': height,
\ }
" resize the content window
call nvim_win_set_config( s:popup_win_id, opts )
" resize the border window
let opts[ 'width' ] = width + 4
let opts[ 'height' ] = height + 2
call nvim_win_set_config( s:nvim_border_win_id, opts )
call nvim_buf_set_lines( nvim_win_get_buf( s:nvim_border_win_id ),
\ 0,
\ -1,
\ v:true,
\ s:GenerateBorder( width, height ) )
endfunction
" neovim doesn't have the border support, so we have to make our own.
" FIXME: This will likely break if the user has `ambiwidth=2`
function! s:GenerateBorder( width, height ) abort
let top = '╭' . repeat('─',a:width + 2) . '╮'
let mid = '│' . repeat(' ',a:width + 2) . '│'
let bot = '╰' . repeat('─',a:width + 2) . '╯'
let lines = [ top ] + repeat( [ mid ], a:height ) + [ bot ]
return lines
endfunction
function! s:CreateNeovimTooltip( body ) abort
" generate border for the float window by creating a background buffer and
" overlaying the content buffer
" see https://github.com/neovim/neovim/issues/9718#issuecomment-546603628
let buf_id = nvim_create_buf( v:false, v:true )
call nvim_buf_set_lines( buf_id,
\ 0,
\ -1,
\ v:true,
\ s:GenerateBorder( s:max_width, s:max_height ) )
" default the dimensions initially, then we'll calculate the real size and
" resize it.
let opts = {
\ 'relative': 'cursor',
\ 'width': s:max_width + 2,
\ 'height': s:max_height + 2,
\ 'col': 0,
\ 'row': 1,
\ 'anchor': 'NW',
\ 'style': 'minimal'
\ }
" this is the border window
let s:nvim_border_win_id = nvim_open_win( buf_id, 0, opts )
call nvim_win_set_option( s:nvim_border_win_id, 'signcolumn', 'no' )
call nvim_win_set_option( s:nvim_border_win_id, 'relativenumber', v:false )
call nvim_win_set_option( s:nvim_border_win_id, 'number', v:false )
" when calculating where to display the content window, we need to account
" for the border
let opts.row += 1
let opts.height -= 2
let opts.col += 2
let opts.width -= 4
" create the content window
let buf_id = nvim_create_buf( v:false, v:true )
call nvim_buf_set_lines( buf_id, 0, -1, v:true, a:body )
call nvim_buf_set_option( buf_id, 'modifiable', v:false )
let s:popup_win_id = nvim_open_win( buf_id, v:false, opts )
" Apparently none of these work, when 'style' is 'minimal'
call nvim_win_set_option( s:popup_win_id, 'wrap', v:false )
call nvim_win_set_option( s:popup_win_id, 'cursorline', v:true )
call nvim_win_set_option( s:popup_win_id, 'signcolumn', 'no' )
call nvim_win_set_option( s:popup_win_id, 'relativenumber', v:false )
call nvim_win_set_option( s:popup_win_id, 'number', v:false )
" Move the cursor into the popup window, as this is the only way we can
" interract with the popup in neovim
noautocmd call win_gotoid( s:popup_win_id )
nnoremap <silent> <buffer> <Esc> <cmd>quit<CR>
call py3eval( "__import__( 'vimspector', "
\." fromlist = [ 'variables' ] )."
\.' variables.AddExpandMappings()' )
" Close the popup whenever we leave this window
augroup vimspector#internal#balloon#nvim_float
autocmd!
autocmd WinLeave <buffer>
\ :call vimspector#internal#balloon#Close()
\ | autocmd! vimspector#internal#balloon#nvim_float
augroup END
call vimspector#internal#balloon#ResizeTooltip()
endfunction
" }}}
" Boilerplate {{{ " Boilerplate {{{
let &cpoptions=s:save_cpo let &cpoptions=s:save_cpo
unlet s:save_cpo unlet s:save_cpo

View file

@ -20,49 +20,42 @@ set cpoptions&vim
" }}} " }}}
function! s:_OnServerData( channel, data ) abort function! s:_OnServerData( channel, data ) abort
if !exists( 's:ch' ) || s:ch isnot a:channel
return
endif
py3 << EOF py3 << EOF
_vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) _vimspector_session.OnChannelData( vim.eval( 'a:data' ) )
EOF EOF
endfunction endfunction
function! s:_OnClose( channel ) abort function! s:_OnServerError( channel, data ) abort
if !exists( 's:ch' ) || s:ch isnot a:channel echom 'Channel received error: ' . a:data
return redraw
endif endfunction
function! s:_OnClose( channel ) abort
echom 'Channel closed' echom 'Channel closed'
redraw redraw
unlet s:ch unlet s:ch
py3 _vimspector_session.OnServerExit( 0 ) py3 _vimspector_session.OnServerExit( 0 )
endfunction endfunction
function! s:_Send( msg ) abort
call ch_sendraw( s:ch, a:msg )
return 1
endfunction
function! vimspector#internal#channel#Timeout( id ) abort
py3 << EOF
_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) )
EOF
endfunction
function! vimspector#internal#channel#StartDebugSession( config ) abort function! vimspector#internal#channel#StartDebugSession( config ) abort
if exists( 's:ch' ) if exists( 's:ch' )
echo 'Channel is already running' echo 'Channel is already running'
return v:false return v:none
endif endif
" If we _also_ have a command line, then start the actual job. This allows for let l:addr = 'localhost:' . a:config[ 'port' ]
" servers which start up and listen on some port
if has_key( a:config, 'command' )
let s:job = job_start( a:config[ 'command' ],
\ {
\ 'in_mode': 'raw',
\ 'out_mode': 'raw',
\ 'err_mode': 'raw',
\ 'stoponexit': 'term',
\ 'env': a:config[ 'env' ],
\ 'cwd': a:config[ 'cwd' ],
\ }
\ )
endif
let l:addr = get( a:config, 'host', '127.0.0.1' ) . ':' . a:config[ 'port' ]
echo 'Connecting to ' . l:addr . '... (waiting fo up to 10 seconds)' echo 'Connecting to ' . l:addr . '... (waiting fo up to 10 seconds)'
let s:ch = ch_open( l:addr, let s:ch = ch_open( l:addr,
@ -75,72 +68,44 @@ function! vimspector#internal#channel#StartDebugSession( config ) abort
\ ) \ )
if ch_status( s:ch ) !=# 'open' if ch_status( s:ch ) !=# 'open'
echom 'Unable to connect to' l:addr echom 'Unable to connect to debug adapter'
redraw redraw
return v:false return v:none
endif endif
return v:true return funcref( 's:_Send' )
endfunction
function! vimspector#internal#channel#Send( msg ) abort
call ch_sendraw( s:ch, a:msg )
return 1
endfunction
function! vimspector#internal#channel#Timeout( id ) abort
py3 << EOF
_vimspector_session.OnRequestTimeout( vim.eval( 'a:id' ) )
EOF
endfunction endfunction
function! vimspector#internal#channel#StopDebugSession() abort function! vimspector#internal#channel#StopDebugSession() abort
if !exists( 's:ch' )
return
endif
if exists( 's:job' ) if ch_status( s:ch ) ==# 'open'
" We started the job, so we need to kill it and wait to read all the data
" from the socket
if job_status( s:job ) ==# 'run'
call job_stop( s:job, 'term' )
endif
while job_status( s:job ) ==# 'run'
call job_stop( s:job, 'kill' )
endwhile
unlet s:job
if exists( 's:ch' ) && count( [ 'closed', 'fail' ], ch_status( s:ch ) ) == 0
" We're going to block on this channel reading, then manually call the
" close callback, so remove the automatic close callback to avoid tricky
" re-entrancy
call ch_setoptions( s:ch, { 'close_cb': '' } )
endif
elseif exists( 's:ch' ) &&
\ count( [ 'closed', 'fail' ], ch_status( s:ch ) ) == 0
" channel is open, close it and trigger the callback. The callback is _not_ " channel is open, close it and trigger the callback. The callback is _not_
" triggered when manually calling ch_close. if we get here and the channel " triggered when manually calling ch_close. if we get here and the channel
" is not open, then we there is a _OnClose callback waiting for us, so do " is not open, then we there is a _OnClose callback waiting for us, so do
" nothing. " nothing.
call ch_close( s:ch ) call ch_close( s:ch )
call s:_OnClose( s:ch )
endif endif
" block until we've read all data from the socket and handled it.
while count( [ 'open', 'buffered' ], ch_status( s:ch ) ) == 1
let data = ch_read( s:ch, { 'timeout': 10 } )
call s:_OnServerData( s:ch, data )
endwhile
call s:_OnClose( s:ch )
endfunction endfunction
function! vimspector#internal#channel#Reset() abort function! vimspector#internal#channel#Reset() abort
if exists( 's:ch' ) || exists( 's:job' ) if exists( 's:ch' )
call vimspector#internal#channel#StopDebugSession() call vimspector#internal#channel#StopDebugSession()
endif endif
endfunction endfunction
function! vimspector#internal#channel#ForceRead() abort
if exists( 's:ch' )
let data = ch_readraw( s:ch, { 'timeout': 1000 } )
if data !=# ''
call s:_OnServerData( s:ch, data )
endif
endif
endfunction
" Boilerplate {{{ " Boilerplate {{{
let &cpoptions=s:save_cpo let &cpoptions=s:save_cpo
unlet s:save_cpo unlet s:save_cpo

View file

@ -20,91 +20,26 @@ set cpoptions&vim
" }}} " }}}
function! s:_OnServerData( channel, data ) abort function! s:_OnServerData( channel, data ) abort
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Get data after process exit' )
return
endif
py3 _vimspector_session.OnChannelData( vim.eval( 'a:data' ) ) py3 _vimspector_session.OnChannelData( vim.eval( 'a:data' ) )
endfunction endfunction
function! s:_OnServerError( channel, data ) abort function! s:_OnServerError( channel, data ) abort
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Get data after process exit' )
return
endif
py3 _vimspector_session.OnServerStderr( vim.eval( 'a:data' ) ) py3 _vimspector_session.OnServerStderr( vim.eval( 'a:data' ) )
endfunction endfunction
" FIXME: We should wait until both the exit_cb _and_ the channel closed callback
" have been received before OnServerExit?
function! s:_OnExit( channel, status ) abort function! s:_OnExit( channel, status ) abort
if !exists( 's:job' ) || ch_getjob( a:channel ) isnot s:job
call ch_log( 'Unexpected exit callback' )
return
endif
echom 'Channel exit with status ' . a:status echom 'Channel exit with status ' . a:status
redraw redraw
if exists( 's:job' ) unlet s:job
unlet s:job
endif
py3 _vimspector_session.OnServerExit( vim.eval( 'a:status' ) ) py3 _vimspector_session.OnServerExit( vim.eval( 'a:status' ) )
endfunction endfunction
function! s:_OnClose( channel ) abort function! s:_OnClose( channel ) abort
if !exists( 's:job' ) || job_getchannel( s:job ) != a:channel
call ch_log( 'Channel closed after exit' )
return
endif
echom 'Channel closed' echom 'Channel closed'
redraw redraw
endfunction endfunction
function! vimspector#internal#job#StartDebugSession( config ) abort function! s:_Send( msg ) abort
if exists( 's:job' )
echom 'Not starting: Job is already running'
redraw
return v:false
endif
let s:job = job_start( a:config[ 'command' ],
\ {
\ 'in_mode': 'raw',
\ 'out_mode': 'raw',
\ 'err_mode': 'raw',
\ 'exit_cb': funcref( 's:_OnExit' ),
\ 'close_cb': funcref( 's:_OnClose' ),
\ 'out_cb': funcref( 's:_OnServerData' ),
\ 'err_cb': funcref( 's:_OnServerError' ),
\ 'stoponexit': 'term',
\ 'env': a:config[ 'env' ],
\ 'cwd': a:config[ 'cwd' ],
\ }
\ )
if !exists( 's:job' )
" The job died immediately after starting and we cleaned up
return v:false
endif
let status = job_status( s:job )
echom 'Started job, status is: ' . status
redraw
if status !=# 'run'
return v:false
endif
return v:true
endfunction
function! vimspector#internal#job#Send( msg ) abort
if ! exists( 's:job' ) if ! exists( 's:job' )
echom "Can't send message: Job was not initialised correctly" echom "Can't send message: Job was not initialised correctly"
redraw redraw
@ -128,6 +63,40 @@ function! vimspector#internal#job#Send( msg ) abort
return 1 return 1
endfunction endfunction
function! vimspector#internal#job#StartDebugSession( config ) abort
if exists( 's:job' )
echom 'Not starging: Job is already running'
redraw
return v:none
endif
let s:job = job_start( a:config[ 'command' ],
\ {
\ 'in_mode': 'raw',
\ 'out_mode': 'raw',
\ 'err_mode': 'raw',
\ 'exit_cb': funcref( 's:_OnExit' ),
\ 'close_cb': funcref( 's:_OnClose' ),
\ 'out_cb': funcref( 's:_OnServerData' ),
\ 'err_cb': funcref( 's:_OnServerError' ),
\ 'stoponexit': 'term',
\ 'env': a:config[ 'env' ],
\ 'cwd': a:config[ 'cwd' ],
\ }
\ )
echom 'Started job, status is: ' . job_status( s:job )
redraw
if job_status( s:job ) !=# 'run'
echom 'Unable to start job, status is: ' . job_status( s:job )
redraw
return v:none
endif
return funcref( 's:_Send' )
endfunction
function! vimspector#internal#job#StopDebugSession() abort function! vimspector#internal#job#StopDebugSession() abort
if !exists( 's:job' ) if !exists( 's:job' )
echom "Not stopping session: Job doesn't exist" echom "Not stopping session: Job doesn't exist"
@ -136,8 +105,8 @@ function! vimspector#internal#job#StopDebugSession() abort
endif endif
if job_status( s:job ) ==# 'run' if job_status( s:job ) ==# 'run'
echom 'Terminating job' echom 'Terminating job'
redraw redraw
call job_stop( s:job, 'kill' ) call job_stop( s:job, 'kill' )
endif endif
endfunction endfunction
@ -146,11 +115,13 @@ function! vimspector#internal#job#Reset() abort
call vimspector#internal#job#StopDebugSession() call vimspector#internal#job#StopDebugSession()
endfunction endfunction
function! s:_OnCommandExit( category, ch, code ) abort function! vimspector#internal#job#ForceRead() abort
py3 __import__( "vimspector", if exists( 's:job' )
\ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete( let data = ch_readraw( job_getchannel( s:job ), { 'timeout': 1000 } )
\ vim.eval( 'a:category' ), if data !=# ''
\ int( vim.eval( 'a:code' ) ) ) call s:_OnServerData( job_getchannel( s:job ), data )
endif
endif
endfunction endfunction
function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort
@ -164,30 +135,31 @@ function! vimspector#internal#job#StartCommandWithLog( cmd, category ) abort
let l:index = len( s:commands[ a:category ] ) let l:index = len( s:commands[ a:category ] )
let buf = '_vimspector_log_' . a:category
call add( s:commands[ a:category ], job_start( call add( s:commands[ a:category ], job_start(
\ a:cmd, \ a:cmd,
\ { \ {
\ 'out_io': 'buffer', \ 'out_io': 'buffer',
\ 'in_io': 'null',
\ 'err_io': 'buffer', \ 'err_io': 'buffer',
\ 'out_msg': 0, \ 'out_name': '_vimspector_log_' . a:category . '_out',
\ 'err_msg': 0, \ 'err_name': '_vimspector_log_' . a:category . '_err',
\ 'out_name': buf,
\ 'err_name': buf,
\ 'exit_cb': funcref( 's:_OnCommandExit', [ a:category ] ),
\ 'out_modifiable': 0, \ 'out_modifiable': 0,
\ 'err_modifiable': 0, \ 'err_modifiable': 0,
\ 'stoponexit': 'kill' \ 'stoponexit': 'kill'
\ } ) ) \ } ) )
if job_status( s:commands[ a:category ][ index ] ) !=# 'run' if job_status( s:commands[ a:category ][ index ] ) !=# 'run'
echom 'Unable to start job for ' . string( a:cmd ) echom 'Unable to start job for ' . a:cmd
redraw redraw
return v:none return v:none
endif endif
return bufnr( buf ) let l:stdout = ch_getbufnr(
\ job_getchannel( s:commands[ a:category ][ index ] ), 'out' )
let l:stderr = ch_getbufnr(
\ job_getchannel( s:commands[ a:category ][ index ] ), 'err' )
return [ l:stdout, l:stderr ]
endfunction endfunction

View file

@ -1,126 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2020 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
function! s:_OnEvent( chan_id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if !exists( 's:ch' ) || a:chan_id != s:ch
return
endif
if a:data == ['']
echom 'Channel closed'
redraw
unlet s:ch
py3 _vimspector_session.OnServerExit( 0 )
else
py3 _vimspector_session.OnChannelData( '\n'.join( vim.eval( 'a:data' ) ) )
endif
endfunction
function! vimspector#internal#neochannel#StartDebugSession( config ) abort
if exists( 's:ch' )
echom 'Not starting: Channel is already running'
redraw
return v:false
endif
" If we _also_ have a command line, then start the actual job. This allows for
" servers which start up and listen on some port
if has_key( a:config, 'command' )
let old_env={}
try
let old_env = vimspector#internal#neoterm#PrepareEnvironment(
\ a:config[ 'env' ] )
let s:job = jobstart( a:config[ 'command' ],
\ {
\ 'cwd': a:config[ 'cwd' ],
\ 'env': a:config[ 'env' ],
\ }
\ )
finally
call vimspector#internal#neoterm#ResetEnvironment( a:config[ 'env' ],
\ old_env )
endtry
endif
let l:addr = get( a:config, 'host', '127.0.0.1' ) . ':' . a:config[ 'port' ]
let attempt = 1
while attempt <= 10
echo 'Connecting to ' . l:addr . '... (attempt' attempt 'of 10)'
try
let s:ch = sockconnect( 'tcp',
\ addr,
\ { 'on_data': funcref( 's:_OnEvent' ) } )
redraw
return v:true
catch /connection refused/
sleep 1
endtry
let attempt += 1
endwhile
echom 'Unable to connect to' l:addr 'after 10 attempts'
redraw
return v:false
endfunction
function! vimspector#internal#neochannel#Send( msg ) abort
if ! exists( 's:ch' )
echom "Can't send message: Channel was not initialised correctly"
redraw
return 0
endif
call chansend( s:ch, a:msg )
return 1
endfunction
function! vimspector#internal#neochannel#StopDebugSession() abort
if exists( 's:ch' )
call chanclose( s:ch )
" It doesn't look like we get a callback after chanclos. Who knows if we
" will subsequently receive data callbacks.
call s:_OnEvent( s:ch, [ '' ], 'data' )
endif
if exists( 's:job' )
if vimspector#internal#neojob#JobIsRunning( s:job )
call jobstop( s:job )
endif
unlet s:job
endif
endfunction
function! vimspector#internal#neochannel#Reset() abort
call vimspector#internal#neochannel#StopDebugSession()
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

View file

@ -1,252 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2020 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
function! s:_OnEvent( chan_id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if !exists( 's:job' ) || a:chan_id != s:job
return
endif
" In neovim, the data argument is a list.
if a:event ==# 'stdout'
py3 _vimspector_session.OnChannelData( '\n'.join( vim.eval( 'a:data' ) ) )
elseif a:event ==# 'stderr'
py3 _vimspector_session.OnServerStderr( '\n'.join( vim.eval( 'a:data' ) ) )
elseif a:event ==# 'exit'
echom 'Channel exit with status ' . a:data
redraw
unlet s:job
py3 _vimspector_session.OnServerExit( vim.eval( 'a:data' ) )
endif
endfunction
function! vimspector#internal#neojob#StartDebugSession( config ) abort
if exists( 's:job' )
echom 'Not starging: Job is already running'
redraw
return v:false
endif
" HACK: Workaround for 'env' not being supported.
let old_env={}
try
let old_env = vimspector#internal#neoterm#PrepareEnvironment(
\ a:config[ 'env' ] )
let s:job = jobstart( a:config[ 'command' ],
\ {
\ 'on_stdout': funcref( 's:_OnEvent' ),
\ 'on_stderr': funcref( 's:_OnEvent' ),
\ 'on_exit': funcref( 's:_OnEvent' ),
\ 'cwd': a:config[ 'cwd' ],
\ 'env': a:config[ 'env' ],
\ }
\ )
finally
call vimspector#internal#neoterm#ResetEnvironment( a:config[ 'env' ],
\ old_env )
endtry
return v:true
endfunction
function! vimspector#internal#neojob#JobIsRunning( job ) abort
return jobwait( [ a:job ], 0 )[ 0 ] == -1
endfunction
function! vimspector#internal#neojob#Send( msg ) abort
if ! exists( 's:job' )
echom "Can't send message: Job was not initialised correctly"
redraw
return 0
endif
if !vimspector#internal#neojob#JobIsRunning( s:job )
echom "Can't send message: Job is not running"
redraw
return 0
endif
call chansend( s:job, a:msg )
return 1
endfunction
function! vimspector#internal#neojob#StopDebugSession() abort
if !exists( 's:job' )
return
endif
if vimspector#internal#neojob#JobIsRunning( s:job )
echom 'Terminating job'
redraw
call jobstop( s:job )
endif
endfunction
function! vimspector#internal#neojob#Reset() abort
call vimspector#internal#neojob#StopDebugSession()
endfunction
function! s:_OnCommandEvent( category, id, data, event ) abort
if v:exiting isnot# v:null
return
endif
if a:event ==# 'stdout' || a:event ==# 'stderr'
if a:data == ['']
return
endif
if !has_key( s:commands, a:category )
return
endif
if !has_key( s:commands[ a:category ], a:id )
return
endif
if a:event ==# 'stdout'
let buffer = s:commands[ a:category ][ a:id ].stdout
elseif a:event ==# 'stderr'
let buffer = s:commands[ a:category ][ a:id ].stderr
endif
try
call bufload( buffer )
catch /E325/
" Ignore E325/ATTENTION
endtry
let numlines = py3eval( "len( vim.buffers[ int( vim.eval( 'buffer' ) ) ] )" )
let last_line = getbufline( buffer, '$' )[ 0 ]
call s:MakeBufferWritable( buffer )
try
if numlines == 1 && last_line ==# ''
call setbufline( buffer, 1, a:data[ 0 ] )
else
call setbufline( buffer, '$', last_line . a:data[ 0 ] )
endif
call appendbufline( buffer, '$', a:data[ 1: ] )
finally
call s:MakeBufferReadOnly( buffer )
call setbufvar( buffer, '&modified', 0 )
endtry
" if the buffer is visible, scroll it, but don't allow autocommands to fire,
" as this may close the current window!
let w = bufwinnr( buffer )
if w > 0
let cw = winnr()
try
noautocmd execute w . 'wincmd w'
noautocmd normal! Gz-
finally
noautocmd execute cw . 'wincmd w'
endtry
endif
elseif a:event ==# 'exit'
py3 __import__( "vimspector",
\ fromlist = [ "utils" ] ).utils.OnCommandWithLogComplete(
\ vim.eval( 'a:category' ),
\ int( vim.eval( 'a:data' ) ) )
endif
endfunction
function! s:SetUpHiddenBuffer( buffer ) abort
call setbufvar( a:buffer, '&hidden', 1 )
call setbufvar( a:buffer, '&bufhidden', 'hide' )
call setbufvar( a:buffer, '&wrap', 0 )
call setbufvar( a:buffer, '&swapfile', 0 )
call setbufvar( a:buffer, '&textwidth', 0 )
call s:MakeBufferReadOnly( a:buffer )
endfunction
function! s:MakeBufferReadOnly( buffer ) abort
call setbufvar( a:buffer, '&modifiable', 0 )
call setbufvar( a:buffer, '&readonly', 1 )
endfunction
function! s:MakeBufferWritable( buffer ) abort
call setbufvar( a:buffer, '&readonly', 0 )
call setbufvar( a:buffer, '&modifiable', 1 )
endfunction
let s:commands = {}
function! vimspector#internal#neojob#StartCommandWithLog( cmd, category ) abort
if ! has_key( s:commands, a:category )
let s:commands[ a:category ] = {}
endif
let buf = bufnr( '_vimspector_log_' . a:category, v:true )
" FIXME: This largely duplicates the same stuff in the python layer, but we
" don't want to potentially mess up Vim behaviour where the job output is
" attached to a buffer set up by Vim. So we sort o mimic that here.
call s:SetUpHiddenBuffer( buf )
let id = jobstart(a:cmd,
\ {
\ 'on_stdout': funcref( 's:_OnCommandEvent',
\ [ a:category ] ),
\ 'on_stderr': funcref( 's:_OnCommandEvent',
\ [ a:category ] ),
\ 'on_exit': funcref( 's:_OnCommandEvent',
\ [ a:category ] ),
\ } )
let s:commands[ a:category ][ id ] = {
\ 'stdout': buf,
\ 'stderr': buf
\ }
return buf
endfunction
function! vimspector#internal#neojob#CleanUpCommand( category ) abort
if ! has_key( s:commands, a:category )
return
endif
for id in keys( s:commands[ a:category ] )
let id = str2nr( id )
if jobwait( [ id ], 0 )[ 0 ] == -1
call jobstop( id )
endif
call jobwait( [ id ], -1 )
endfor
unlet! s:commands[ a:category ]
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

View file

@ -1,137 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2018 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
" Neovim's float window API, like its job/channel API is painful to use
" compared to Vim's so we have to employ more hacks
" We can't seem to pass a Window handle back to the python, so we have to
" maintain yet another cached here
let s:db = {}
let s:next_id = 0
function! s:MessageToList( message ) abort
if type( a:message ) == type( [] )
let message = a:message
else
let message = [ a:message ]
endif
return message
endfunction
function! s:GetSplashConfig( message ) abort
let l = max( map( a:message, 'len( v:val )' ) )
let h = len( a:message )
return { 'relative': 'editor',
\ 'width': l,
\ 'height': h,
\ 'col': ( &columns / 2 ) - ( l / 2 ),
\ 'row': ( &lines / 2 ) - h / 2,
\ 'anchor': 'NW',
\ 'style': 'minimal',
\ 'focusable': v:false,
\ }
endfunction
function! vimspector#internal#neopopup#DisplaySplash( message ) abort
let message = s:MessageToList( a:message )
let buf = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf, 0, -1, v:true, message )
let win = nvim_open_win(buf, 0, s:GetSplashConfig( message ) )
call nvim_win_set_option(win, 'wrap', v:false)
call nvim_win_set_option(win, 'colorcolumn', '')
let id = s:next_id
let s:next_id += 1
let s:db[ id ] = { 'win': win, 'buf': buf }
return id
endfunction
function! vimspector#internal#neopopup#UpdateSplash( id, message ) abort
let splash = s:db[ a:id ]
let message = s:MessageToList( a:message )
call nvim_buf_set_lines( splash.buf, 0, -1, v:true, message )
call nvim_win_set_config( splash.win, s:GetSplashConfig( message ) )
return a:id
endfunction
function! vimspector#internal#neopopup#HideSplash( id ) abort
let splash = s:db[ a:id ]
call nvim_win_close( splash.win, v:true )
unlet s:db[ a:id ]
endfunction
function! vimspector#internal#neopopup#Confirm( confirm_id,
\ text,
\ options,
\ default_value,
\ keys ) abort
" Neovim doesn't have an equivalent of popup_dialog, and it's way too much
" effort to write one, so we just use confirm()...
" Annoyingly we can't use confirm() here because for some reason it doesn't
" render properly in a channel callback. So we use input() and mimic dialog
" behaviour.
let prompt = a:text
for opt in a:options
let prompt .= ' ' . opt
endfor
let prompt .= ': '
try
let result = input( prompt, a:keys[ a:default_value - 1 ] )
catch /.*/
let result = -1
endtry
" Map the results to what the vim popup stuff would return (s:ConfirmCallback
" in popup.vim), i.e.:
" - 1-based index of selected item, or
" - -1 or 0 for cancellation
if result == ''
" User pressed ESC/ctrl-c
let result = -1
else
let index = 1
for k in a:keys
if k ==? result
let result = index
break
endif
let index += 1
endfor
if index > len( a:keys )
let result = -1
endif
endif
py3 __import__( 'vimspector', fromlist = [ 'utils' ] ).utils.ConfirmCallback(
\ int( vim.eval( 'a:confirm_id' ) ),
\ int( vim.eval( 'result' ) ) )
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

View file

@ -1,106 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2018 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
" Ids are unique throughtout the life of neovim, but obviously buffer numbers
" aren't
"
" FIXME: Tidy this map when buffers are closed ?
let s:buffer_to_id = {}
function! vimspector#internal#neoterm#PrepareEnvironment( env ) abort
let old_env = {}
for key in keys( a:env )
if exists( '$' . key )
let old_env[ key ] = getenv( key )
endif
call setenv( key, a:env[ key ] )
endfor
return old_env
endfunction
function! vimspector#internal#neoterm#ResetEnvironment( env, old_env ) abort
for key in keys( a:env )
let value = get( a:old_env, key, v:null )
call setenv( key, value )
endfor
endfunction
function! vimspector#internal#neoterm#Start( cmd, opts ) abort
" Prepare current buffer to be turned into a term if curwin is not set
if ! get( a:opts, 'curwin', 0 )
let mods = 'rightbelow '
if get( a:opts, 'vertical', 0 )
let mods .= 'vertical '
let mods .= get( a:opts, 'term_cols', '' )
else
let mods .= get( a:opts, 'term_rows', '' )
endif
execute mods . 'new'
endif
" HACK: Neovim's termopen doesn't support env
let old_env={}
try
let old_env = vimspector#internal#neoterm#PrepareEnvironment(
\ a:opts[ 'env' ] )
setlocal nomodified
let id = termopen( a:cmd, {
\ 'cwd': a:opts[ 'cwd' ],
\ 'env': a:opts[ 'env' ],
\ } )
finally
call vimspector#internal#neoterm#ResetEnvironment( a:opts[ 'env' ],
\ old_env )
endtry
let bufnr = bufnr()
let s:buffer_to_id[ bufnr ] = id
return bufnr
endfunction
function! s:JobIsRunning( job ) abort
return jobwait( [ a:job ], 0 )[ 0 ] == -1
endfunction
function! vimspector#internal#neoterm#IsFinished( bufno ) abort
if !has_key( s:buffer_to_id, a:bufno )
return v:true
endif
return !s:JobIsRunning( s:buffer_to_id[ a:bufno ] )
endfunction
function! vimspector#internal#neoterm#GetPID( bufno ) abort
if !has_key( s:buffer_to_id, a:bufno )
return -1
endif
return jobpid( s:buffer_to_id[ a:bufno ] )
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

View file

@ -1,146 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2018 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
scriptencoding utf-8
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
function! vimspector#internal#popup#DisplaySplash( message ) abort
return popup_dialog( a:message, {} )
endfunction
function! vimspector#internal#popup#UpdateSplash( id, message ) abort
call popup_settext( a:id, a:message )
return a:id
endfunction
function! vimspector#internal#popup#HideSplash( id ) abort
call popup_hide( a:id )
endfunction
let s:current_selection = 0
let s:selections = []
let s:text = []
function! s:UpdatePopup( id ) abort
let buf = copy( s:text )
call extend( buf, s:DrawButtons() )
call popup_settext( a:id, buf )
endfunction
function! s:ConfirmKeyFilter( keys, id, key ) abort
if a:key ==# "\<CR>"
call popup_close( a:id, s:current_selection + 1 )
return 1
elseif index( [ "\<Tab>", "\<Right>" ], a:key ) >= 0
let s:current_selection = ( s:current_selection + 1 ) % len( s:selections )
call s:UpdatePopup( a:id )
return 1
elseif index( [ "\<S-Tab>", "\<Left>" ], a:key ) >= 0
let s:current_selection = s:current_selection == 0
\ ? len( s:selections ) - 1: s:current_selection - 1
call s:UpdatePopup( a:id )
return 1
elseif a:key ==# "\<Esc>" || a:key ==# "\<C-c>"
call popup_close( a:id, -1 )
return 1
endif
let index = 1
for key in a:keys
if a:key ==? key
call popup_close( a:id, index )
return 1
endif
let index += 1
endfor
endfunction
function! s:ConfirmCallback( confirm_id, id, result ) abort
py3 __import__( 'vimspector', fromlist = [ 'utils' ] ).utils.ConfirmCallback(
\ int( vim.eval( 'a:confirm_id' ) ),
\ int( vim.eval( 'a:result' ) ) )
endfunction
function! s:SelectionPosition( idx ) abort
return a:idx == 0 ? 0 : len( join( s:selections[ : a:idx - 1 ], ' ' ) ) + 1
endfunction
function! s:DrawButtons() abort
return [ {
\ 'text': join( s:selections, ' ' ),
\ 'props': [
\ {
\ 'col': s:SelectionPosition( s:current_selection ) + 1,
\ 'length': len( s:selections[ s:current_selection ] ),
\ 'type': 'VimspectorSelectedItem'
\ },
\ ]
\ } ]
endfunction
function! vimspector#internal#popup#Confirm(
\ confirm_id,
\ text,
\ options,
\ default_value,
\ keys ) abort
silent! call prop_type_add( 'VimspectorSelectedItem', {
\ 'highlight': 'PMenuSel'
\ } )
let lines = split( a:text, "\n", v:true )
let buf = []
for line in lines
call add( buf, { 'text': line, 'props': [] } )
endfor
call add( buf, { 'text': '', 'props': [] } )
let s:selections = a:options
let s:current_selection = ( a:default_value - 1 )
let s:text = copy( buf )
call extend( buf, s:DrawButtons() )
let config = {
\ 'callback': function( 's:ConfirmCallback', [ a:confirm_id ] ),
\ 'filter': function( 's:ConfirmKeyFilter', [ a:keys ] ),
\ 'mapping': v:false,
\ }
let config = vimspector#internal#popup#SetBorderChars( config )
return popup_dialog( buf, config )
endfunction
function! vimspector#internal#popup#SetBorderChars( config ) abort
" When ambiwidth is single, use prettier characters for the border. This
" would look silly when ambiwidth is double.
if &ambiwidth ==# 'single' && &encoding ==? 'utf-8'
let a:config[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '┛', '╰' ]
endif
return a:config
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

View file

@ -19,32 +19,11 @@ let s:save_cpo = &cpoptions
set cpoptions&vim set cpoptions&vim
" }}} " }}}
let s:prefix = ''
if has( 'nvim' )
let s:prefix='neo'
endif
function! vimspector#internal#state#Reset() abort function! vimspector#internal#state#Reset() abort
try py3 << EOF
py3 import vim from vimspector import debug_session
py3 _vimspector_session = __import__( _vimspector_session = debug_session.DebugSession()
\ "vimspector", EOF
\ fromlist=[ "debug_session" ] ).debug_session.DebugSession(
\ vim.eval( 's:prefix' ) )
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
endtry
return v:true
endfunction
function! vimspector#internal#state#GetAPIPrefix() abort
return s:prefix
endfunction endfunction
" Boilerplate {{{ " Boilerplate {{{

View file

@ -1,37 +0,0 @@
" vimspector - A multi-language debugging system for Vim
" Copyright 2018 Ben Jackson
"
" Licensed under the Apache License, Version 2.0 (the "License");
" you may not use this file except in compliance with the License.
" You may obtain a copy of the License at
"
" http://www.apache.org/licenses/LICENSE-2.0
"
" Unless required by applicable law or agreed to in writing, software
" distributed under the License is distributed on an "AS IS" BASIS,
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
" See the License for the specific language governing permissions and
" limitations under the License.
" Boilerplate {{{
let s:save_cpo = &cpoptions
set cpoptions&vim
" }}}
function! vimspector#internal#term#Start( cmd, opts ) abort
rightbelow return term_start( a:cmd, a:opts )
endfunction
function! vimspector#internal#term#IsFinished( bufno ) abort
return index( split( term_getstatus( a:bufno ), ',' ), 'finished' ) >= 0
endfunction
function! vimspector#internal#term#GetPID( bufno ) abort
return job_info( term_getjob( a:bufno ) ).process
endfunction
" Boilerplate {{{
let &cpoptions=s:save_cpo
unlet s:save_cpo
" }}}

144
azure-pipelines.yml Normal file
View file

@ -0,0 +1,144 @@
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
stages:
- stage: Build
jobs:
- job: 'PythonLint'
displayName: "Python Lint"
pool:
vmImage: 'ubuntu-16.04'
container: 'puremourning/vimspector:test'
steps:
- bash: pip3 install -r dev_requirements.txt
displayName: "Install requirements"
- bash: $HOME/.local/bin/flake8 python3/
displayName: "Run flake8"
- job: 'Vimscript'
displayName: "Vimscript Lint"
pool:
vmImage: 'ubuntu-16.04'
container: 'puremourning/vimspector:test'
steps:
- bash: pip3 install -r dev_requirements.txt
displayName: "Install requirements"
- bash: $HOME/.local/bin/vint autoload/ plugin/
displayName: "Run vint"
- job: 'linux'
pool:
vmImage: 'ubuntu-16.04'
container:
image: 'puremourning/vimspector:test'
options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined
steps:
- bash: |
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
go get -u github.com/go-delve/delve/cmd/dlv
displayName: 'Install Delve for Go'
- task: CacheBeta@0
inputs:
key: v1 | gadgets | $(Agent.OS) | install_gadget.py
path: gadgets/linux/download
displayName: Cache gadgets
- bash: python3 install_gadget.py --all
displayName: 'Install gadgets - python3'
- bash: vim --version
displayName: 'Print vim version information'
- bash: |
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
export GOPATH=$HOME/go
./run_tests
displayName: 'Run the tests'
env:
VIMSPECTOR_MIMODE: gdb
- bash: ./make_package linux $(Build.SourceVersion)
displayName: 'Package'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'package-linux'
targetPath: 'package/linux-$(Build.SourceVersion).tar.gz'
- job: 'macos'
pool:
vmImage: 'macOS-10.13'
steps:
- bash: |
brew unlink node@6
brew install macvim node@10
brew link --force --overwrite node@10
displayName: 'Install vim and node'
- bash: go get -u github.com/go-delve/delve/cmd/dlv
displayName: 'Install Delve for Go'
- task: CacheBeta@0
inputs:
key: v1 | gadgets | $(Agent.OS) | install_gadget.py
path: gadgets/macos/download
displayName: Cache gadgets
- bash: python3 install_gadget.py --all
displayName: 'Install gadgets - python3'
- bash: vim --version
displayName: 'Print vim version information'
- bash: ./run_tests
displayName: 'Run the tests'
env:
VIMSPECTOR_MIMODE: lldb
- bash: ./make_package macos $(Build.SourceVersion)
displayName: 'Package'
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'package-macos'
targetPath: 'package/macos-$(Build.SourceVersion).tar.gz'
- stage: "Publish"
dependsOn:
- "Build"
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
jobs:
- job: 'Publish'
pool:
vmImage: 'ubuntu-16.04'
steps:
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'package-linux'
targetPath: $(Build.ArtifactStagingDirectory)
- task: DownloadPipelineArtifact@0
inputs:
artifactName: 'package-macos'
targetPath: $(Build.ArtifactStagingDirectory)
- task: GitHubRelease@0
inputs:
gitHubConnection: puremourning
repositoryName: '$(Build.Repository.Name)'
action: 'create' # Options: create, edit, delete
target: '$(Build.SourceVersion)' # Required when action == Create || Action == Edit
tagSource: 'manual' # Required when action == Create# Options: auto, manual
tag: "$(Build.BuildId)"
#title: # Optional
#releaseNotesSource: 'file' # Optional. Options: file, input
#releaseNotesFile: # Optional
#releaseNotes: # Optional
#assets: '$(Build.ArtifactStagingDirectory)/*' # Optional
#assetUploadMode: 'delete' # Optional. Options: delete, replace
#isDraft: false # Optional
isPreRelease: true # Optional
#addChangeLog: true # Optional

View file

@ -13,14 +13,12 @@
" See the License for the specific language governing permissions and " See the License for the specific language governing permissions and
" limitations under the License. " limitations under the License.
scriptencoding utf-8
" Compiler plugin to help running vimspector tests " Compiler plugin to help running vimspector tests
if exists('current_compiler') if exists("current_compiler")
finish finish
endif endif
let current_compiler = 'vimspector_test' let current_compiler = "vimspector_test"
setlocal errorformat= setlocal errorformat=
\Found\ errors\ in\ %f:%.%#: \Found\ errors\ in\ %f:%.%#:
@ -37,71 +35,52 @@ if ! exists( ':' . s:make_cmd )
endif endif
function! VimGetCurrentFunction() function! VimGetCurrentFunction()
echom s:GetCurrentFunction()[ 0 ] echom s:GetCurrentFunction()
endfunction endfunction
function! s:GetCurrentFunction() function! s:GetCurrentFunction()
" Store the cursor position; we'll need to reset it " Store the cursor position; we'll need to reset it
let [ buf, row, col, offset ] = getpos( '.' ) let [ l:buf, l:row, l:col, l:offset ] = getpos( '.' )
let [ test_function, test_function_line ] = [ v:null, -1 ] let l:test_function = ''
let pattern = '\V\C\s\*func\%\(tion\)\?!\?\s\+\(\<\w\+\>\)\.\*\$' let l:pattern = '\V\C\s\*function!\?\s\+\(\<\w\+\>\)\.\*\$'
let lnum = prevnonblank( '.' ) let l:lnum = prevnonblank( '.' )
" Find the top-level method and class " Find the top-level method and class
while lnum > 0 while l:lnum > 0
call cursor( lnum, 1 ) call cursor( l:lnum, 1 )
let lnum = search( pattern, 'bcnWz' ) let l:lnum = search( l:pattern, 'bcnWz' )
if lnum <= 0 if l:lnum <= 0
call cursor( row, col ) call cursor( l:row, l:col )
return [ test_function, test_function_line ] return l:test_function
endif endif
let this_decl = substitute( getline( lnum ), pattern, '\1', '' ) let l:this_decl = substitute( getline( l:lnum ), l:pattern, '\1', '' )
let this_decl_is_test = match( this_decl, '\V\C\^Test_' ) >= 0 let l:this_decl_is_test = match( l:this_decl, '\V\C\^Test_' ) >= 0
if this_decl_is_test if l:this_decl_is_test
let [ test_function, test_function_line ] = [ this_decl, lnum ] let l:test_function = l:this_decl
if indent( lnum ) == 0 if indent( l:lnum ) == 0
call cursor( row, col ) call cursor( l:row, l:col )
return [ test_function, test_function_line ] return l:test_function
endif endif
endif endif
let lnum = prevnonblank( lnum - 1 ) let l:lnum = prevnonblank( l:lnum - 1 )
endwhile endwhile
return [ v:null, -1 ]
endfunction endfunction
function! s:RunTestUnderCursorInVimspector()
update
let l:test_func_name = s:GetCurrentFunction()[ 0 ]
if l:test_func_name ==# ''
echo 'No test method found'
return
endif
echo "Running test '" . l:test_func_name . "'"
call vimspector#LaunchWithSettings( {
\ 'configuration': 'Run test',
\ 'TestFunction': l:test_func_name
\ } )
endfunction
function! s:RunTestUnderCursor() function! s:RunTestUnderCursor()
update update
let l:test_func_name = s:GetCurrentFunction()[ 0 ] let l:test_func_name = s:GetCurrentFunction()
if l:test_func_name ==# '' if l:test_func_name ==# ''
echo 'No test method found' echo "No test method found"
return return
endif endif
@ -111,9 +90,7 @@ function! s:RunTestUnderCursor()
let l:cwd = getcwd() let l:cwd = getcwd()
execute 'lcd ' . s:root_dir execute 'lcd ' . s:root_dir
try try
execute s:make_cmd . ' --report messages ' execute s:make_cmd . ' ' . l:test_arg
\ . get( g:, 'vimspector_test_args', '' ) . ' '
\ . l:test_arg
finally finally
execute 'lcd ' . l:cwd execute 'lcd ' . l:cwd
endtry endtry
@ -124,9 +101,7 @@ function! s:RunTest()
let l:cwd = getcwd() let l:cwd = getcwd()
execute 'lcd ' . s:root_dir execute 'lcd ' . s:root_dir
try try
execute s:make_cmd . ' --report messages ' execute s:make_cmd . ' %:p:t'
\ . get( g:, 'vimspector_test_args', '' )
\ . ' %:p:t'
finally finally
execute 'lcd ' . l:cwd execute 'lcd ' . l:cwd
endtry endtry
@ -137,8 +112,7 @@ function! s:RunAllTests()
let l:cwd = getcwd() let l:cwd = getcwd()
execute 'lcd ' . s:root_dir execute 'lcd ' . s:root_dir
try try
execute s:make_cmd . ' --report messages ' execute s:make_cmd
\ . get( g:, 'vimspector_test_args', '' )
finally finally
execute 'lcd ' . l:cwd execute 'lcd ' . l:cwd
endtry endtry
@ -151,36 +125,10 @@ if ! has( 'gui_running' )
nnoremap <buffer> Â :call <SID>RunAllTests()<CR> nnoremap <buffer> Â :call <SID>RunAllTests()<CR>
" † is right-option+t " † is right-option+t
nnoremap <buffer> † :call <SID>RunTestUnderCursor()<CR> nnoremap <buffer> † :call <SID>RunTestUnderCursor()<CR>
nnoremap <buffer> <leader>† :call <SID>RunTestUnderCursorInVimspector()<CR>
" å is the right-option+q " å is the right-option+q
nnoremap <buffer> å :cfirst<CR> nnoremap <buffer> å :cfirst<CR>
" å is the right-option+a " å is the right-option+a
nnoremap <buffer> œ :FuncLine<CR> nnoremap <buffer> œ :cnext<CR>
" Ω is the right-option+z " Ω is the right-option+z
nnoremap <buffer> Ω :cprevious<CR> nnoremap <buffer> Ω :cprevious<CR>
endif endif
function! s:GoToCurrentFunctionLine( ... )
if a:0 < 1
call inputsave()
let lnum = str2nr( input( 'Enter line num: ' ) )
call inputrestore()
else
let lnum = a:1
endif
let [ f, l ] = s:GetCurrentFunction()
if f is v:null
return
endif
let lnum += l
echo 'Function' f 'at line' l '(jump to line ' lnum . ')'
call cursor( [ lnum, indent( lnum ) ] )
endfunction
command! -buffer -nargs=? -bar
\ FuncLine
\ :call s:GoToCurrentFunctionLine( <f-args> )

View file

@ -1,6 +1,2 @@
flake8==3.8.3 flake8==3.7.7
flake8-comprehensions==3.2.3 vim-vint==0.3.21
flake8-ycm>= 0.1.0
# Use fork of vint which is up to date
git+https://github.com/puremourning/vint

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

5
docs/.gitignore vendored
View file

@ -1,5 +0,0 @@
_site
.sass-cache
.jekyll-metadata
vendor/
.bundle/

View file

@ -1,24 +0,0 @@
---
layout: default
---
<style type="text/css" media="screen">
.container {
margin: 10px auto;
max-width: 600px;
text-align: center;
}
h1 {
margin: 30px 0;
font-size: 4em;
line-height: 1;
letter-spacing: -1px;
}
</style>
<div class="container">
<h1>404</h1>
<p><strong>Page not found :(</strong></p>
<p>The requested page could not be found.</p>
</div>

View file

@ -1,32 +0,0 @@
source "https://rubygems.org"
# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
# bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
#gem "jekyll", "~> 3.8.5"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
gem "minima", "~> 2.0"
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
gem "github-pages", group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem "jekyll-feed", "~> 0.6"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.0" if Gem.win_platform?
gem "webrick", "~> 1.7"

View file

@ -1,270 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (6.0.3.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.11.1)
colorator (1.1.0)
commonmarker (0.17.13)
ruby-enum (~> 0.5)
concurrent-ruby (1.1.8)
dnsruby (1.61.5)
simpleidn (~> 0.1)
em-websocket (0.5.2)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
ethon (0.12.0)
ffi (>= 1.3.0)
eventmachine (1.2.7)
execjs (2.7.0)
faraday (1.3.0)
faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
faraday-net_http (1.0.1)
ffi (1.15.0)
forwardable-extended (2.6.0)
gemoji (3.0.1)
github-pages (214)
github-pages-health-check (= 1.17.0)
jekyll (= 3.9.0)
jekyll-avatar (= 0.7.0)
jekyll-coffeescript (= 1.1.1)
jekyll-commonmark-ghpages (= 0.1.6)
jekyll-default-layout (= 0.1.4)
jekyll-feed (= 0.15.1)
jekyll-gist (= 1.5.0)
jekyll-github-metadata (= 2.13.0)
jekyll-mentions (= 1.6.0)
jekyll-optional-front-matter (= 0.3.2)
jekyll-paginate (= 1.1.0)
jekyll-readme-index (= 0.3.0)
jekyll-redirect-from (= 0.16.0)
jekyll-relative-links (= 0.6.1)
jekyll-remote-theme (= 0.4.3)
jekyll-sass-converter (= 1.5.2)
jekyll-seo-tag (= 2.7.1)
jekyll-sitemap (= 1.4.0)
jekyll-swiss (= 1.0.0)
jekyll-theme-architect (= 0.1.1)
jekyll-theme-cayman (= 0.1.1)
jekyll-theme-dinky (= 0.1.1)
jekyll-theme-hacker (= 0.1.2)
jekyll-theme-leap-day (= 0.1.1)
jekyll-theme-merlot (= 0.1.1)
jekyll-theme-midnight (= 0.1.1)
jekyll-theme-minimal (= 0.1.1)
jekyll-theme-modernist (= 0.1.1)
jekyll-theme-primer (= 0.5.4)
jekyll-theme-slate (= 0.1.1)
jekyll-theme-tactile (= 0.1.1)
jekyll-theme-time-machine (= 0.1.1)
jekyll-titles-from-headings (= 0.5.3)
jemoji (= 0.12.0)
kramdown (= 2.3.1)
kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.3)
mercenary (~> 0.3)
minima (= 2.5.1)
nokogiri (>= 1.10.4, < 2.0)
rouge (= 3.26.0)
terminal-table (~> 1.4)
github-pages-health-check (1.17.0)
addressable (~> 2.3)
dnsruby (~> 1.60)
octokit (~> 4.0)
public_suffix (>= 2.0.2, < 5.0)
typhoeus (~> 1.3)
html-pipeline (2.14.0)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.6.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
jekyll (3.9.0)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 0.7)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3)
liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
jekyll-avatar (0.7.0)
jekyll (>= 3.0, < 5.0)
jekyll-coffeescript (1.1.1)
coffee-script (~> 2.2)
coffee-script-source (~> 1.11.1)
jekyll-commonmark (1.3.1)
commonmarker (~> 0.14)
jekyll (>= 3.7, < 5.0)
jekyll-commonmark-ghpages (0.1.6)
commonmarker (~> 0.17.6)
jekyll-commonmark (~> 1.2)
rouge (>= 2.0, < 4.0)
jekyll-default-layout (0.1.4)
jekyll (~> 3.0)
jekyll-feed (0.15.1)
jekyll (>= 3.7, < 5.0)
jekyll-gist (1.5.0)
octokit (~> 4.2)
jekyll-github-metadata (2.13.0)
jekyll (>= 3.4, < 5.0)
octokit (~> 4.0, != 4.4.0)
jekyll-mentions (1.6.0)
html-pipeline (~> 2.3)
jekyll (>= 3.7, < 5.0)
jekyll-optional-front-matter (0.3.2)
jekyll (>= 3.0, < 5.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.3.0)
jekyll (>= 3.0, < 5.0)
jekyll-redirect-from (0.16.0)
jekyll (>= 3.3, < 5.0)
jekyll-relative-links (0.6.1)
jekyll (>= 3.3, < 5.0)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
rubyzip (>= 1.3.0, < 3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
jekyll-seo-tag (2.7.1)
jekyll (>= 3.8, < 5.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-swiss (1.0.0)
jekyll-theme-architect (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-cayman (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-dinky (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-hacker (0.1.2)
jekyll (> 3.5, < 5.0)
jekyll-seo-tag (~> 2.0)
jekyll-theme-leap-day (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-merlot (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-midnight (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-minimal (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-modernist (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-primer (0.5.4)
jekyll (> 3.5, < 5.0)
jekyll-github-metadata (~> 2.9)
jekyll-seo-tag (~> 2.0)
jekyll-theme-slate (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-tactile (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-theme-time-machine (0.1.1)
jekyll (~> 3.5)
jekyll-seo-tag (~> 2.0)
jekyll-titles-from-headings (0.5.3)
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
jemoji (0.12.0)
gemoji (~> 3.0)
html-pipeline (~> 2.2)
jekyll (>= 3.0, < 5.0)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.5.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6)
mini_portile2 (2.5.1)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.14.4)
multipart-post (2.1.1)
nokogiri (1.11.5)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
octokit (4.20.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.6)
racc (1.5.2)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (3.26.0)
ruby-enum (0.9.0)
i18n
ruby2_keywords (0.0.4)
rubyzip (2.3.0)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sawyer (0.8.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
simpleidn (0.2.1)
unf (~> 0.1.4)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (1.2.9)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrick (1.7.0)
zeitwerk (2.4.2)
PLATFORMS
ruby
DEPENDENCIES
github-pages
jekyll-feed (~> 0.6)
minima (~> 2.0)
tzinfo-data
webrick (~> 1.7)
BUNDLED WITH
2.2.3

View file

@ -1,13 +0,0 @@
To update/install:
gem install bundler
bundle install --path vendor/bundle
To run a local server/test the build
bundle exec jekyll serve
To update deps
bundle update
or bundle update github-pages

View file

@ -1,42 +0,0 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely edit after that. If you find
# yourself editing this file very often, consider using Jekyll's data files
# feature for the data you need to update frequently.
#
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
# Site settings
# These are used to personalize your new site. If you look in the HTML files,
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
# You can create any custom variable you would like, and they will be accessible
# in the templates via {{ site.myvariable }}.
title: Vimspector Documentation
description: |
Reference Documentation for Vimspector: A multi-language debugging front
end for Vim
# Build settings
markdown: kramdown
theme: minima
plugins:
- jekyll-feed
header_pages:
- index.md
- configuration.md
- schema/index.md
# Exclude from processing.
# The following items will not be processed, by default. Create a custom list
# to override the default setting.
# exclude:
# - Gemfile
# - Gemfile.lock
# - node_modules
# - vendor/bundle/
# - vendor/cache/
# - vendor/gems/
# - vendor/ruby/

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
---
title: Configuration
---
This document describes how to use vimspector's `install_gadget.py` to install
custom debug adapters. This can be useful as a way to get an adapter working
that isn't officially supported by Vimspector, but otherwise can be made to work
by simply downloading the VScode extension into the gadget directory.
## Usage
```
./install_gadget.py --enable-custom=/path/to/a.json \
--enable-custom=/path/to/b.json`
```
This tells `install_gadget.py` to read `a.json` and `b.json` as _gadget
definitions_ and download/unpack the specified gadgets into the gadget dir, just
like the supported adapters.
## Gadget Definitions
A _gadget definition_ is a file containing a single JSON object definition,
describing the debug adapter and how to download and install it. This mechanism
is crude but can be effective.

View file

@ -1,23 +0,0 @@
---
title: Vimspector Reference Guide
---
This section contains reference material for configuring and using
[Vimspector][vimspector]. It is intentionally technical in nature and should
serve as a reference guide, rather than a user guide or tutorial.
It complements the following:
* The [Vimspector README][readme], which contains a general overview and is the
main go-to for getting started, installation, etc.
* The [Vimspector Website][website], which contains a step-by-step tutorial and
some detail about the user interface.
## Contents
* [Configuring Vimspector for your projects](configuration.html)
* [Full JSON Schema reference](schema/)
[vimspector]: https://github.com/puremourning/vimspector
[readme]: https://github.com/puremourning/vimspector/blob/master/README.md
[website]: https://puremourning.github.io/vimspector-web

View file

@ -1,11 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://puremourning.github.io/vimspector/schema/gadgets.schema.json",
"type": "object",
"properties": {
"adapters": {
"type": "object",
"additionalProperties": { "$ref": "vimspector.schema.json#/definitions/adapter" }
}
}
}

View file

@ -1,21 +0,0 @@
---
title: Configuration Schema
---
This area contains the [JSON Schema][json-schema] representation of the
configuration objects used for configuring Vimspector. For more information on
JSON Schema, check out [Understanding JSON
Schema](http://json-schema.org/understanding-json-schema)
Vimsepctor specification is based on [Draft 7][draft-7] of JSON Schema
standard.
## The schemas
* [`vimspector.schema.json`](vimspector.schema.json) - contains the full
configuration defnition (`.vimspector.json`).
* [`gadgets.schema.json`](gadgets.schema.json) - contains the specification for
gadget-only objects (`.gadgets.json`).
[json-schema]: http://json-schema.org
[draft-7]: https://json-schema.org/specification-links.html#draft-7

View file

@ -1,298 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json",
"definitions": {
"variables": {
"description": "A mappoing of name/value pairs to set variables to be used elsewhere in the definition, or a list of such mappings.",
"properties": {
"variables": {
"oneOf": [
{ "$ref": "#/definitions/variables-mapping" },
{ "$ref": "#/definitions/variables-list" }
]
}
}
},
"variables-list": {
"type": "array",
"description": "A list of variable mappings. This can be useful where variable definitions rely on each other. By using a list, you can control the sequence in which variables are defined.",
"items": { "$ref": "#/definitions/variables-mapping" }
},
"variables-mapping": {
"type": "object",
"additionalProperties": {
"oneOf": [
{ "type": "string" },
{
"type": "object",
"required": [ "shell" ],
"properties": {
"shell": {
"type": [ "array", "string" ],
"description": "Command to run. If it's a string, it's split using Python's shelex splitting. Can contain other variable references."
},
"cwd": { "type": "string" },
"env": { "type": "object" }
}
}
]
}
},
"adapter-launchattach": {
"properties": {
"launch": {
"allOf": [
{ "$ref": "#/definitions/adapter-remote" },
{
"properties": {
"delay": {
"type": "string",
"description": "A time in the format understood by :help :sleep to wait after running the attachCommand(s)"
}
}
}
]
},
"attach": {
"allOf": [
{ "$ref": "#/definitions/adapter-remote" },
{
"type": "object",
"required": [ "pidSelect" ],
"properties": {
"pidSelect": {
"enum": [ "ask", "none" ]
},
"pidProperty": {
"type": "string",
"description": "The launch config property which the PID should be injected into. Required when 'pidSelect' is 'ask'."
},
"delay": {
"type": "string",
"description": "A time in the format understood by :help :sleep to wait after running the attachCommand(s)"
}
}
}
]
}
}
},
"adapter-remote": {
"type": "object",
"properties": {
"remote": {
"type": "object",
"description": "Configures how Vimspector will marshal remote debugging requests. When remote debugging, Vimspector will either ssh to 'account'@'host' or docker exec -it to 'container' and run 'pidCommand', 'attachCommands', 'runCommands', etc. based on the 'remote-command' option in the debug configuration. If 'remote-command' is 'launch', it runs 'runCommand(s)', otherwise (it's 'attach') vimspector runs 'pidCommand', followed by 'attachCommand(s)'.Then it starts up the debug adapter with the debug configuration as normal. Usually this is configured with an 'attach' request (whether we remotely 'launched' or not). Once the initialization exchange is complete, Vimspector runs the optional 'initCompleteCommand' which can be used to force the application to break, e.g. by sending it SIGINT. This is required on some platforms which have buggy gdbservers (for example)",
"allOf": [
{
"oneOf": [
{ "required": [ "host" ] },
{ "required": [ "container" ] }
]
},
{
"properties": {
"account": {
"type": "string",
"description": "Remote account name used when ssh'ing. Defaults to the current user account."
},
"host": {
"type": "string",
"description": "Name of the remote host to connect to (via passwordless SSH)."
},
"container": {
"type": "string",
"description": "Name or container id of the docker run container to connect to (via docker exec). Note the container must already be running (Vimspector will not start it) and it must have the port forwarded to the host if subsequently connecting via a port (for example <tt>docker run -p 8765:8765 -it simple_python</tt>)."
}
}
},
{
"oneOf": [
{
"allOf": [
{
"oneOf": [
{ "required": [ "attachCommand" ] },
{ "required": [ "attachCommands" ] }
]
},
{
"properties": {
"initCompleteCommand": {
"type": "array",
"items": { "type": "string" },
"description": "For remote-attach. Remote command to execute after initialization of the debug adapter. Can be used to work around buggy attach behaviour on certain platforms (advanced usage). Can contain the special token %PID% which is replaced with the PID returned by 'pidCommand'"
},
"pidCommand": {
"type": "array",
"items": { "type": "string" },
"description": "Required for remote-attach. Remote command to execute to return the PID to attach to."
},
"attachCommands": {
"type": [ "array" ],
"items": { "type": "array", "items": { "type": "string" } },
"description": "For remote-attach. List of commands to execute remotely to set up the attach. Can contain the special token %PID% which is replaced with the PID returned by the remote 'pidCommand'."
},
"attachCommand": {
"type": "array",
"items": { "type": "string" },
"description": "A single command to execute for remote-attach. Like attachCommands but for a single command. If attachCommands is supplied, this is not used."
}
}
}
]
},
{
"allOf": [
{
"oneOf": [
{ "required": [ "runCommand" ] },
{ "required": [ "runCommands" ] }
]
},
{
"properties": {
"runCommands": {
"type": [ "array" ],
"items": { "type": "array", "items": { "type": "string" } },
"description": "For remote-launch. List of commands to execute remotely to set up the launch. An entry in the array can be the special token '%CMD%' which is replaced with the evaluated 'remote-cmdLine' value in the debug configuration. This is useful to parameterize launcging remotely under something like gdbserver."
},
"runCommand": {
"type": "array",
"items": { "type": "string" },
"description": "A single command to execute for remote-launch. Like runCommands but for a single command."
}
}
}
]
}
]
}
]
}
}
},
"adapter": {
"allOf": [
{ "type": "object" },
{ "$ref": "#/definitions/variables" },
{
"properties": {
"name": {
"type": "string",
"description": "Passed to the adapter in the initialization request. Some adapters are particularly picky about what value goes here. Usually it can be omitted and Vimspector will send a generic value"
},
"configuration": {
"type": "object",
"description": "Base debug configuration. Can be used to set default values for all debug configurations. When reading individual debug configurations from 'configurations', those configurations are merged with this object. Definitions in the debug configuration override anything in this object. Typical usage for this is to set the 'type' parameter, which some debug adapters are very picky about, or to set e.g. the path to an underlying debugger."
}
}
},
{ "$ref": "#/definitions/adapter-launchattach" },
{
"anyOf": [
{ "required": [ "command" ] },
{ "required": [ "port" ] },
{ "required": [ "command", "port" ] }
]
},
{
"properties": {
"host": {
"type": "string",
"default": "127.0.0.1",
"description": "Connect to this host in multi-session mode"
},
"port": {
"oneOf": [
{ "type": "string" },
{ "type": "integer" }
],
"description": "If supplied, indicates that a socket connection should be made to this port on 'host'. If the value is 'ask', then the user is asked to enter the port number to connect to."
}
}
}
]
}
},
"type": "object",
"required": [ "configurations" ],
"properties": {
"adapters": {
"type": "object",
"additionalProperties": { "$ref": "#/definitions/adapter" }
},
"configurations": {
"type": "object",
"additionalProperties": {
"allOf": [
{ "$ref": "#/definitions/variables" },
{
"type": "object",
"properties": {
"adapter": {
"description": "Adapter configuration to use for this debug session",
"oneOf": [
{ "$ref": "#/definitions/adapter" },
{
"type": "string",
"description": "Name of an adapter in the 'adapters' mapping"
}
]
},
"remote-request": {
"enum": [ "launch", "attach" ],
"description": "When the 'remote' block is defined in the adapter configuration, this can be used to override the actual action taken (remotely). Usually the actual 'configuration' will contain 'request' of 'attach', but in order to remotely 'launch' the process (e.g. under gdbserver or equivalent), use remote-attach set to 'launch'"
},
"remote-cmdLine": {
"type": [ "string", "array" ],
"description": "Defines the value of the special token %CMD% in remote-launch 'runCommand(s)'. The value is inserted into the command line where an entry matching '%CMD%' is found in 'runCommand(s)' command array."
},
"configuration": {
"type": "object",
"required": [ "request" ],
"properties": {
"request": {
"enum": [ "launch", "attach" ],
"description": "Type of session - launch process or attach to process"
}
},
"additionalProperties": {
"description": "Additional properties are passed to the debug adapter in the 'launch' or 'attach' request and are specific to the debug adapter."
}
},
"breakpoints": {
"type": "object",
"properties": {
"line": {
"$comment": "TBA: Not supported yet"
},
"function": {
"$comment": "TBA: Not supported yet"
},
"exception": {
"type": "object",
"description": "Exception breakpoints configuration, mapping the server's exception filter to enabled/disable/default flag",
"additionalProperties": {
"oneOf": [
{
"type": "boolean",
"description": "true = enable, false = disable"
},
{
"type": "string",
"enum": [ "Y", "N", "" ],
"description": "Y = enable, N = disable, '' = default"
}
]
}
}
}
}
}
}
]
}
}
}
}

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# vimspector - A multi-language debugging system for Vim # vimspector - A multi-language debugging system for Vim
# Copyright 2019 Ben Jackson # Copyright 2019 Ben Jackson
@ -15,52 +15,533 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import sys try:
import urllib.request as urllib2
if sys.version_info.major < 3: except ImportError:
sys.exit( "You need to run this with python 3. Your version is " + import urllib2
'.'.join( map( str, sys.version_info[ :3 ] ) ) )
import argparse import argparse
import contextlib
import os import os
import string
import zipfile
import gzip
import shutil
import subprocess
import traceback
import tarfile
import hashlib
import sys
import json import json
import functools import functools
import operator import time
import glob
try:
from io import BytesIO ## for Python 3
except ImportError:
from BytesIO import BytesIO
# Include vimspector source, for utils # Include vimspector source, for utils
sys.path.insert( 1, os.path.join( os.path.dirname( __file__ ), sys.path.insert( 1, os.path.join( os.path.dirname( __file__ ),
'python3' ) ) 'python3' ) )
from vimspector import install, installer, gadgets from vimspector import install
from vimspector.vendor.json_minify import minify
# ------------------------------------------------------------------------------ GADGETS = {
# Entry point 'vscode-cpptools': {
# ------------------------------------------------------------------------------ 'language': 'c',
'download': {
'url': 'https://github.com/Microsoft/vscode-cpptools/releases/download/'
'${version}/${file_name}',
},
'do': lambda name, root: InstallCppTools( name, root ),
'all': {
'version': '0.23.1',
},
'linux': {
'file_name': 'cpptools-linux.vsix',
'checksum':
'c0f424bd6d5e016d70126587c80b92d981729c708ce524f2cce4c3f524b41d71'
},
'macos': {
'file_name': 'cpptools-osx.vsix',
'checksum':
'431692395ba243ea20428e083d5df3201a0dbda31a66eab7729da0f377def5fd',
},
'windows': {
'file_name': 'cpptools-win32.vsix',
'checksum': None,
},
"adapters": {
"vscode-cpptools": {
"name": "cppdbg",
"command": [
"${gadgetDir}/vscode-cpptools/debugAdapters/OpenDebugAD7"
],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
},
},
},
},
'vscode-python': {
'language': 'python',
'download': {
'url': 'https://github.com/Microsoft/vscode-python/releases/download/'
'${version}/${file_name}',
},
'all': {
'version': '2019.10.41019',
'file_name': 'ms-python-release.vsix',
'checksum':
'38e8bf782fc6d2dc904868add2e1e5dc66197a06a902f6d17e15f96d4e9bf16b',
},
'adapters': {
"vscode-python": {
"name": "vscode-python",
"command": [
"node",
"${gadgetDir}/vscode-python/out/client/debugger/debugAdapter/main.js",
],
}
},
},
'tclpro': {
'language': 'tcl',
'repo': {
'url': 'https://github.com/puremourning/TclProDebug',
'ref': 'f5c56b7067661ce84e205765060224076569ae0e', # master 26/10/2019
},
'do': lambda name, root: InstallTclProDebug( name, root )
},
'netcoredbg': {
'language': 'csharp',
'enabled': False,
'download': {
'url': 'https://github.com/Samsung/netcoredbg/releases/download/latest/'
'${file_name}',
'format': 'tar',
},
'all': {
'version': 'master'
},
'macos': {
'file_name': 'netcoredbg-osx-master.tar.gz',
'checksum': '',
},
'linux': {
'file_name': 'netcoredbg-linux-master.tar.gz',
'checksum': '',
},
'do': lambda name, root: MakeSymlink( gadget_dir,
name,
os.path.join( root, 'netcoredbg' ) ),
'adapters': {
'netcoredbg': {
"name": "netcoredbg",
"command": [
"${gadgetDir}/netcoredbg/netcoredbg",
"--interpreter=vscode"
],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
},
},
}
},
'vscode-mono-debug': {
'language': 'csharp',
'enabled': False,
'download': {
'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/'
'publishers/ms-vscode/vsextensions/mono-debug/${version}/'
'vspackage',
'target': 'vscode-mono-debug.vsix.gz',
'format': 'zip.gz',
},
'all': {
'file_name': 'vscode-mono-debug.vsix',
'version': '0.15.8',
'checksum':
'723eb2b621b99d65a24f215cb64b45f5fe694105613a900a03c859a62a810470',
},
'adapters': {
'vscode-mono-debug': {
"name": "mono-debug",
"command": [
"mono",
"${gadgetDir}/vscode-mono-debug/bin/Release/mono-debug.exe"
],
"attach": {
"pidSelect": "none"
},
},
}
},
'vscode-bash-debug': {
'language': 'bash',
'download': {
'url': 'https://github.com/rogalmic/vscode-bash-debug/releases/'
'download/${version}/${file_name}',
},
'all': {
'file_name': 'bash-debug-0.3.5.vsix',
'version': 'v0.3.5',
'checksum': '',
}
},
'vscode-go': {
'language': 'go',
'download': {
'url': 'https://github.com/microsoft/vscode-go/releases/download/'
'${version}/${file_name}'
},
'all': {
'version': '0.11.4',
'file_name': 'Go-0.11.4.vsix',
'checksum':
'ff7d7b944da5448974cb3a0086f4a2fd48e2086742d9c013d6964283d416027e'
},
'adapters': {
'vscode-go': {
'name': 'delve',
'command': [
'node',
'${gadgetDir}/vscode-go/out/src/debugAdapter/goDebug.js'
],
},
},
},
'vscode-node-debug2': {
'language': 'node',
'enabled': False,
'repo': {
'url': 'https://github.com/microsoft/vscode-node-debug2',
'ref': 'v1.39.1',
},
'do': lambda name, root: InstallNodeDebug( name, root ),
'adapters': {
'vscode-node': {
'name': 'node2',
'type': 'node2',
'command': [
'node',
'${gadgetDir}/vscode-node-debug2/out/src/nodeDebug.js'
]
},
},
},
'debugger-for-chrome': {
'language': 'chrome',
'enabled': False,
'download': {
'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/'
'publishers/msjsdiag/vsextensions/'
'debugger-for-chrome/${version}/vspackage',
'target': 'msjsdiag.debugger-for-chrome-4.12.0.vsix.gz',
'format': 'zip.gz',
},
'all': {
'version': '4.12.0',
'file_name': 'msjsdiag.debugger-for-chrome-4.12.0.vsix',
'checksum':
'0df2fe96d059a002ebb0936b0003e6569e5a5c35260dc3791e1657d27d82ccf5'
},
'adapters': {
'chrome': {
'name': 'debugger-for-chrome',
'type': 'chrome',
'command': [
'node',
'${gadgetDir}/debugger-for-chrome/out/src/chromeDebug.js'
],
},
},
},
'PowerShellEditorServices': {
'language': 'powershell',
'enabled': False,
'download': {
'url': 'https://github.com/PowerShell/PowerShellEditorServices/releases/'
'download/v${version}/PowerShellEditorServices.zip',
'format': 'zip',
},
'do': ( lambda name, root:
MakeSymlink( gadget_dir,
name,
os.path.join( root, 'PowerShellEditorServices' ) ) ),
'all': {
'version': '1.13.0',
'file_name': 'PowerShellEditorServices.zip',
'checksum':
'd10bda5c7d36795bb8f860b2d09a637f8f98ee401c60ac4c40a636f8606565b9'
},
'adapters': {
'PowerShell': {
'name': 'PowerShell',
'type': 'PowerShell',
'port': 'ask',
},
},
}
}
parser = argparse.ArgumentParser( @contextlib.contextmanager
formatter_class = argparse.RawDescriptionHelpFormatter, def CurrentWorkingDir( d ):
description = 'Install DAP Servers for use with Vimspector.', cur_d = os.getcwd()
epilog = try:
""" os.chdir( d )
If you're not sure, normally --all is enough to get started. yield
finally:
os.chdir( cur_d )
Custom server definitions can be defined in JSON files, allowing
installation of arbitrary servers packaged in one of the ways that this
installer understands.
The format of the file can be found on the Vimspector reference guide: def MakeExecutable( file_path ):
https://puremourning.github.io/vimspector # TODO: import stat and use them by _just_ adding the X bit.
print( 'Making executable: {}'.format( file_path ) )
os.chmod( file_path, 0o755 )
NOTE: This script should usually _not_ be run under `sudo` or as root. It
downloads and extracts things only to directories under its own path. No def InstallCppTools( name, root ):
system files or folders are chnaged by this script. If you really want to extension = os.path.join( root, 'extension' )
run under sudo, pass --sudo, but this is _almost certainly_ the wrong thing
to do. # It's hilarious, but the execute bits aren't set in the vsix. So they
""" # actually have javascript code which does this. It's just a horrible horrible
) # hack that really is not funny.
MakeExecutable( os.path.join( extension, 'debugAdapters', 'OpenDebugAD7' ) )
with open( os.path.join( extension, 'package.json' ) ) as f:
package = json.load( f )
runtime_dependencies = package[ 'runtimeDependencies' ]
for dependency in runtime_dependencies:
for binary in dependency.get( 'binaries' ):
file_path = os.path.abspath( os.path.join( extension, binary ) )
if os.path.exists( file_path ):
MakeExecutable( os.path.join( extension, binary ) )
MakeExtensionSymlink( name, root )
def InstallTclProDebug( name, root ):
configure = [ './configure' ]
if OS == 'macos':
# Apple removed the headers from system frameworks because they are
# determined to make life difficult. And the TCL configure scripts are super
# old so don't know about this. So we do their job for them and try and find
# a tclConfig.sh.
#
# NOTE however that in Apple's infinite wisdom, installing the "headers" in
# the other location is actually broken because the paths in the
# tclConfig.sh are pointing at the _old_ location. You actually do have to
# run the package installation which puts the headers back in order to work.
# This is why the below list is does not contain stuff from
# /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform
# '/Applications/Xcode.app/Contents/Developer/Platforms'
# '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System'
# '/Library/Frameworks/Tcl.framework',
# '/Applications/Xcode.app/Contents/Developer/Platforms'
# '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System'
# '/Library/Frameworks/Tcl.framework/Versions'
# '/Current',
for p in [ '/usr/local/opt/tcl-tk/lib' ]:
if os.path.exists( os.path.join( p, 'tclConfig.sh' ) ):
configure.append( '--with-tcl=' + p )
break
with CurrentWorkingDir( os.path.join( root, 'lib', 'tclparser' ) ):
subprocess.check_call( configure )
subprocess.check_call( [ 'make' ] )
MakeSymlink( gadget_dir, name, root )
def InstallNodeDebug( name, root ):
node_version = subprocess.check_output( [ 'node', '--version' ],
universal_newlines=True ).strip()
print( "Node.js version: {}".format( node_version ) )
if list( map( int, node_version[ 1: ].split( '.' ) ) ) >= [ 12, 0, 0 ]:
print( "Can't install vscode-debug-node2:" )
print( "Sorry, you appear to be running node 12 or later. That's not "
"compatible with the build system for this extension, and as far as "
"we know, there isn't a pre-built independent package." )
print( "My advice is to install nvm, then do:" )
print( " $ nvm install --lts 10" )
print( " $ nvm use --lts 10" )
print( " $ ./install_gadget.py --enable-node ..." )
raise RuntimeError( 'Invalid node environent for node debugger' )
with CurrentWorkingDir( root ):
subprocess.check_call( [ 'npm', 'install' ] )
subprocess.check_call( [ 'npm', 'run', 'build' ] )
MakeSymlink( gadget_dir, name, root )
def WithRetry( f ):
retries = 5
timeout = 1 # seconds
@functools.wraps( f )
def wrapper( *args, **kwargs ):
thrown = None
for _ in range( retries ):
try:
return f( *args, **kwargs )
except Exception as e:
thrown = e
print( "Failed - {}, will retry in {} seconds".format( e, timeout ) )
time.sleep( timeout )
raise thrown
return wrapper
@WithRetry
def UrlOpen( *args, **kwargs ):
return urllib2.urlopen( *args, **kwargs )
def DownloadFileTo( url, destination, file_name = None, checksum = None ):
if not file_name:
file_name = url.split( '/' )[ -1 ]
file_path = os.path.abspath( os.path.join( destination, file_name ) )
if not os.path.isdir( destination ):
os.makedirs( destination )
if os.path.exists( file_path ):
if checksum:
if ValidateCheckSumSHA256( file_path, checksum ):
print( "Checksum matches for {}, using it".format( file_path ) )
return file_path
else:
print( "Checksum doesn't match for {}, removing it".format(
file_path ) )
print( "Removing existing {}".format( file_path ) )
os.remove( file_path )
r = urllib2.Request( url, headers = { 'User-Agent': 'Vimspector' } )
print( "Downloading {} to {}/{}".format( url, destination, file_name ) )
with contextlib.closing( UrlOpen( r ) ) as u:
with open( file_path, 'wb' ) as f:
f.write( u.read() )
if checksum:
if not ValidateCheckSumSHA256( file_path, checksum ):
raise RuntimeError(
'Checksum for {} ({}) does not match expected {}'.format(
file_path,
GetChecksumSHA254( file_path ),
checksum ) )
else:
print( "Checksum for {}: {}".format( file_path,
GetChecksumSHA254( file_path ) ) )
return file_path
def GetChecksumSHA254( file_path ):
with open( file_path, 'rb' ) as existing_file:
return hashlib.sha256( existing_file.read() ).hexdigest()
def ValidateCheckSumSHA256( file_path, checksum ):
existing_sha256 = GetChecksumSHA254( file_path )
return existing_sha256 == checksum
def RemoveIfExists( destination ):
if os.path.exists( destination ) or os.path.islink( destination ):
if os.path.islink( destination ):
print( "Removing file {}".format( destination ) )
os.remove( destination )
else:
print( "Removing dir {}".format( destination ) )
shutil.rmtree( destination )
# Python's ZipFile module strips execute bits from files, for no good reason
# other than crappy code. Let's do it's job for it.
class ModePreservingZipFile( zipfile.ZipFile ):
def extract( self, member, path = None, pwd = None ):
if not isinstance( member, zipfile.ZipInfo ):
member = self.getinfo( member )
if path is None:
path = os.getcwd()
ret_val = self._extract_member( member, path, pwd )
attr = member.external_attr >> 16
if attr:
os.chmod( ret_val, attr )
return ret_val
def ExtractZipTo( file_path, destination, format ):
print( "Extracting {} to {}".format( file_path, destination ) )
RemoveIfExists( destination )
if format == 'zip':
with ModePreservingZipFile( file_path ) as f:
f.extractall( path = destination )
elif format == 'zip.gz':
with gzip.open( file_path, 'rb' ) as f:
file_contents = f.read()
with ModePreservingZipFile( BytesIO( file_contents ) ) as f:
f.extractall( path = destination )
elif format == 'tar':
try:
with tarfile.open( file_path ) as f:
f.extractall( path = destination )
except Exception:
# There seems to a bug in python's tarfile that means it can't read some
# windows-generated tar files
os.makedirs( destination )
with CurrentWorkingDir( destination ):
subprocess.check_call( [ 'tar', 'zxvf', file_path ] )
def MakeExtensionSymlink( name, root ):
MakeSymlink( gadget_dir, name, os.path.join( root, 'extension' ) ),
def MakeSymlink( in_folder, link, pointing_to ):
RemoveIfExists( os.path.join( in_folder, link ) )
in_folder = os.path.abspath( in_folder )
pointing_to = os.path.relpath( os.path.abspath( pointing_to ),
in_folder )
os.symlink( pointing_to, os.path.join( in_folder, link ) )
def CloneRepoTo( url, ref, destination ):
RemoveIfExists( destination )
subprocess.check_call( [ 'git', 'clone', url, destination ] )
subprocess.check_call( [ 'git', '-C', destination, 'checkout', ref ] )
subprocess.check_call( [ 'git', 'submodule', 'sync', '--recursive' ] )
subprocess.check_call( [ 'git',
'submodule',
'update',
'--init',
'--recursive' ] )
OS = install.GetOS()
gadget_dir = install.GetGadgetDir( os.path.dirname( __file__ ), OS )
print( 'OS = ' + OS )
print( 'gadget_dir = ' + gadget_dir )
parser = argparse.ArgumentParser()
parser.add_argument( '--all', parser.add_argument( '--all',
action = 'store_true', action = 'store_true',
help = 'Enable all supported completers' ) help = 'Enable all supported completers' )
@ -69,186 +550,103 @@ parser.add_argument( '--force-all',
action = 'store_true', action = 'store_true',
help = 'Enable all unsupported completers' ) help = 'Enable all unsupported completers' )
parser.add_argument( '--upgrade',
action = 'store_true',
help = 'Only update adapters changed from the manifest' )
parser.add_argument( '--quiet',
action = 'store_true',
help = 'Suppress installation output' )
parser.add_argument( '--verbose',
action = 'store_true',
help = 'Force installation output' )
parser.add_argument( '--basedir',
action = 'store',
help = 'Advanced option. '
'Base directory under which to keep gadgets, '
'configurations, etc.. Default: vimspector '
'installation dir. Useful for developers or '
'multi-user installations' )
parser.add_argument( '--no-gadget-config',
action = 'store_true',
help = "Don't write the .gagets.json, just install" )
parser.add_argument( '--update-gadget-config',
action = 'store_true',
help =
"Update the gadget config rather than overwrite it" )
parser.add_argument( '--enable-custom',
dest='custom_gadget_file',
action='append',
nargs='*',
default = [],
help = 'Read custom gadget from supplied file. This '
'can be supplied multiple times and each time '
'multiple files can be passed.' )
parser.add_argument( '--sudo',
action='store_true',
help = "If you're really really really sure you want to "
"run this as root via sudo, pass this flag." )
done_languages = set() done_languages = set()
for name, gadget in gadgets.GADGETS.items(): for name, gadget in GADGETS.items():
langs = gadget[ 'language' ] lang = gadget[ 'language' ]
if not isinstance( langs, list ): if lang in done_languages:
langs = [ langs ] continue
for lang in langs:
if lang in done_languages:
continue
done_languages.add( lang )
if not gadget.get( 'enabled', True ):
parser.add_argument(
'--force-enable-' + lang,
action = 'store_true',
help = 'Install the unsupported {} debug adapter for {} support'.format(
name,
lang ) )
continue
done_languages.add( lang )
if not gadget.get( 'enabled', True ):
parser.add_argument( parser.add_argument(
'--enable-' + lang, '--force-enable-' + lang,
action = 'store_true', action = 'store_true',
help = 'Install the {} debug adapter for {} support'.format( help = 'Install the unsupported {} debug adapter for {} support'.format(
name, name,
lang ) ) lang ) )
continue
parser.add_argument( parser.add_argument(
'--disable-' + lang, '--enable-' + lang,
action = 'store_true', action = 'store_true',
help = "Don't install the {} debug adapter for {} support " help = 'Install the {} debug adapter for {} support'.format(
'(when supplying --all)'.format( name, lang ) ) name,
lang ) )
parser.add_argument( parser.add_argument(
"--no-check-certificate", '--disable-' + lang,
action = "store_true", action = 'store_true',
help = "Do not verify SSL certificates for file downloads." help = "Don't install the {} debug adapter for {} support "
) '(when supplying --all)'.format( name, lang ) )
args = parser.parse_args() args = parser.parse_args()
installer.AbortIfSUperUser( args.sudo )
vimspector_base = os.path.dirname( __file__ )
if args.basedir:
vimspector_base = os.path.abspath( args.basedir )
install.MakeInstallDirs( vimspector_base )
installer.Configure( vimspector_base = vimspector_base,
quiet = args.quiet and not args.verbose,
no_check_certificate = args.no_check_certificate )
if args.force_all and not args.all: if args.force_all and not args.all:
args.all = True args.all = True
CUSTOM_GADGETS = {}
custom_files = glob.glob( os.path.join( vimspector_base,
'gadgets',
'custom',
'*.json' ) )
for custom_file_name in functools.reduce( operator.add,
args.custom_gadget_file,
custom_files ):
with open( custom_file_name, 'r' ) as custom_file:
CUSTOM_GADGETS.update( json.loads( minify( custom_file.read() ) ) )
failed = [] failed = []
succeeded = [] all_adapters = {}
all_adapters = installer.ReadAdapters( for name, gadget in GADGETS.items():
read_existing = args.update_gadget_config ) if not gadget.get( 'enabled', True ):
manifest = installer.Manifest() if ( not args.force_all
and not getattr( args, 'force_enable_' + gadget[ 'language' ] ) ):
continue
else:
if not args.all and not getattr( args, 'enable_' + gadget[ 'language' ] ):
continue
if getattr( args, 'disable_' + gadget[ 'language' ] ):
continue
for name, gadget in gadgets.GADGETS.items(): try:
langs = gadget[ 'language' ] v = {}
if not isinstance( langs, list ): v.update( gadget.get( 'all', {} ) )
langs = [ langs ] v.update( gadget.get( OS, {} ) )
skip = 0
for lang in langs: if 'download' in gadget:
if not gadget.get( 'enabled', True ): if 'file_name' not in v:
if ( not args.force_all raise RuntimeError( "Unsupported OS {} for gadget {}".format( OS,
and not getattr( args, 'force_enable_' + lang ) ): name ) )
skip = skip + 1
continue destination = os.path.join( gadget_dir, 'download', name, v[ 'version' ] )
url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( v )
file_path = DownloadFileTo(
url,
destination,
file_name = gadget[ 'download' ].get( 'target' ),
checksum = v.get( 'checksum' ) )
root = os.path.join( destination, 'root' )
ExtractZipTo( file_path,
root,
format = gadget[ 'download' ].get( 'format', 'zip' ) )
elif 'repo' in gadget:
url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( v )
ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( v )
destination = os.path.join( gadget_dir, 'download', name )
CloneRepoTo( url, ref, destination )
root = destination
if 'do' in gadget:
gadget[ 'do' ]( name, root )
else: else:
if not args.all and not getattr( args, 'enable_' + lang ): MakeExtensionSymlink( name, root )
skip = skip + 1
continue
if getattr( args, 'disable_' + lang ):
skip = skip + 1
continue
if skip == len( langs ):
continue
if not args.upgrade: all_adapters.update( gadget.get( 'adapters', {} ) )
manifest.Clear( name )
installer.InstallGagdet( name,
gadget,
manifest,
succeeded,
failed,
all_adapters )
for name, gadget in CUSTOM_GADGETS.items(): print( "Done installing {}".format( name ) )
if not args.upgrade: except Exception as e:
manifest.Clear( name ) traceback.print_exc()
failed.append( name )
print( "FAILED installing {}: {}".format( name, e ) )
installer.InstallGagdet( name,
gadget,
manifest,
succeeded,
failed,
all_adapters )
if args.no_gadget_config: with open( install.GetGadgetConfigFile( os.path.dirname( __file__ ) ),
print( "" ) 'w' ) as f:
print( "Would write the following gadgets: " ) json.dump( { 'adapters': all_adapters }, f, indent=2, sort_keys=True )
installer.WriteAdapters( all_adapters, to_file = sys.stdout )
else:
installer.WriteAdapters( all_adapters )
manifest.Write()
if args.basedir:
print( "" )
print( "***NOTE***: You set --basedir to " + args.basedir +
". Therefore you _must_ ensure this is in your vimrc:\n"
"let g:vimspector_base_dir='" + vimspector_base + "'" )
if succeeded:
print( "Done" )
print( "The following adapters were installed successfully:\n - {}".format(
'\n - '.join( succeeded ) ) )
if failed: if failed:
sys.exit( 'Failed to install adapters:\n * {}{}'.format( raise RuntimeError( 'Failed to install gadgets: {}'.format(
'\n * '.join( failed ), ','.join( failed ) ) )
"\nRe-run with --verbose for more info on failures"
if args.quiet and not args.verbose else '' ) )

View file

@ -16,20 +16,13 @@ mkdir -p ${PACK}
pushd ${PACK} pushd ${PACK}
mkdir -p vimspector/opt/vimspector mkdir -p vimspector/opt/vimspector
pushd vimspector/opt/vimspector pushd vimspector/opt/vimspector
for d in autoload plugin python3 vendor doc support; do for d in autoload plugin python3 vendor doc; do
if [[ -d ${ROOT}/$d ]]; then if [[ -d ${ROOT}/$d ]]; then
cp -r ${ROOT}/$d . cp -r ${ROOT}/$d .
fi fi
done done
mkdir -p gadgets mkdir -p gadgets
cp -r ${ROOT}/gadgets/${OS} gadgets/ cp -r ${ROOT}/gadgets/${OS} gadgets/
for f in install_gadget.py \
CODE_OF_CONDUCT.md \
CONTRIBUTING.md \
LICENCE \
README.md; do
cp ${ROOT}/${f} .
done
popd popd
popd popd

View file

@ -13,13 +13,6 @@
" See the License for the specific language governing permissions and " See the License for the specific language governing permissions and
" limitations under the License. " limitations under the License.
if !has( 'python3' )
echohl WarningMsg
echom 'Vimspector unavailable: Requires Vim compiled with +python3'
echohl None
finish
endif
" Boilerplate {{{ " Boilerplate {{{
let s:save_cpo = &cpoptions let s:save_cpo = &cpoptions
set cpoptions&vim set cpoptions&vim
@ -33,127 +26,38 @@ if exists( 'g:loaded_vimpector' )
call s:restore_cpo() call s:restore_cpo()
finish finish
endif endif
"}}}
" TODO:
" - Check Vim version (for jobs)
" - Check python support
" - Add commands/mappings/menus?
let g:loaded_vimpector = 1 let g:loaded_vimpector = 1
let g:vimspector_home = expand( '<sfile>:p:h:h' )
let s:mappings = get( g:, 'vimspector_enable_mappings', '' ) let s:mappings = get( g:, 'vimspector_enable_mappings', '' )
nnoremap <silent> <Plug>VimspectorContinue
\ :<c-u>call vimspector#Continue()<CR>
nnoremap <silent> <Plug>VimspectorLaunch
\ :<c-u>call vimspector#Launch( v:true )<CR>
nnoremap <silent> <Plug>VimspectorStop
\ :<c-u>call vimspector#Stop()<CR>
nnoremap <silent> <Plug>VimspectorRestart
\ :<c-u>call vimspector#Restart()<CR>
nnoremap <silent> <Plug>VimspectorPause
\ :<c-u>call vimspector#Pause()<CR>
nnoremap <silent> <Plug>VimspectorToggleBreakpoint
\ :<c-u>call vimspector#ToggleBreakpoint()<CR>
nnoremap <silent> <Plug>VimspectorToggleConditionalBreakpoint
\ :<c-u>call vimspector#ToggleBreakpoint(
\ { 'condition': input( 'Enter condition expression: ' ),
\ 'hitCondition': input( 'Enter hit count expression: ' ) }
\ )<CR>
nnoremap <silent> <Plug>VimspectorAddFunctionBreakpoint
\ :<c-u>call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR>
nnoremap <silent> <Plug>VimspectorStepOver
\ :<c-u>call vimspector#StepOver()<CR>
nnoremap <silent> <Plug>VimspectorStepInto
\ :<c-u>call vimspector#StepInto()<CR>
nnoremap <silent> <Plug>VimspectorStepOut
\ :<c-u>call vimspector#StepOut()<CR>
nnoremap <silent> <Plug>VimspectorRunToCursor
\ :<c-u>call vimspector#RunToCursor()<CR>
" Eval for normal mode
nnoremap <silent> <Plug>VimspectorBalloonEval
\ :<c-u>call vimspector#ShowEvalBalloon( 0 )<CR>
" And for visual modes
xnoremap <silent> <Plug>VimspectorBalloonEval
\ :<c-u>call vimspector#ShowEvalBalloon( 1 )<CR>
nnoremap <silent> <Plug>VimspectorUpFrame
\ :<c-u>call vimspector#UpFrame()<CR>
nnoremap <silent> <Plug>VimspectorDownFrame
\ :<c-u>call vimspector#DownFrame()<CR>
if s:mappings ==# 'VISUAL_STUDIO' if s:mappings ==# 'VISUAL_STUDIO'
nmap <F5> <Plug>VimspectorContinue nnoremap <F5> :call vimspector#Continue()<CR>
nmap <S-F5> <Plug>VimspectorStop nnoremap <S-F5> :call vimspector#Stop()<CR>
nmap <C-S-F5> <Plug>VimspectorRestart nnoremap <C-S-F5> :call vimspector#Restart()<CR>
nmap <F6> <Plug>VimspectorPause nnoremap <F6> :call vimspector#Pause()<CR>
nmap <F9> <Plug>VimspectorToggleBreakpoint nnoremap <F9> :call vimspector#ToggleBreakpoint()<CR>
nmap <S-F9> <Plug>VimspectorAddFunctionBreakpoint nnoremap <S-F9> :call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR>
nmap <F10> <Plug>VimspectorStepOver nnoremap <F10> :call vimspector#StepOver()<CR>
nmap <F11> <Plug>VimspectorStepInto nnoremap <F11> :call vimspector#StepInto()<CR>
nmap <S-F11> <Plug>VimspectorStepOut nnoremap <S-F11> :call vimspector#StepOut()<CR>
elseif s:mappings ==# 'HUMAN' elseif s:mappings ==# 'HUMAN'
nmap <F5> <Plug>VimspectorContinue nnoremap <F5> :call vimspector#Continue()<CR>
nmap <leader><F5> <Plug>VimspectorLaunch nnoremap <F3> :call vimspector#Stop()<CR>
nmap <F3> <Plug>VimspectorStop nnoremap <F4> :call vimspector#Restart()<CR>
nmap <F4> <Plug>VimspectorRestart nnoremap <F6> :call vimspector#Pause()<CR>
nmap <F6> <Plug>VimspectorPause nnoremap <F9> :call vimspector#ToggleBreakpoint()<CR>
nmap <F9> <Plug>VimspectorToggleBreakpoint nnoremap <F8> :call vimspector#AddFunctionBreakpoint( expand( '<cexpr>' ) )<CR>
nmap <leader><F9> <Plug>VimspectorToggleConditionalBreakpoint nnoremap <F10> :call vimspector#StepOver()<CR>
nmap <F8> <Plug>VimspectorAddFunctionBreakpoint nnoremap <F11> :call vimspector#StepInto()<CR>
nmap <leader><F8> <Plug>VimspectorRunToCursor nnoremap <F12> :call vimspector#StepOut()<CR>
nmap <F10> <Plug>VimspectorStepOver
nmap <F11> <Plug>VimspectorStepInto
nmap <F12> <Plug>VimspectorStepOut
endif endif
"}}}
command! -bar -nargs=1 -complete=custom,vimspector#CompleteExpr
\ VimspectorWatch
\ call vimspector#AddWatch( <f-args> )
command! -bar -nargs=? -complete=custom,vimspector#CompleteOutput
\ VimspectorShowOutput
\ call vimspector#ShowOutput( <f-args> )
command! -bar
\ VimspectorToggleLog
\ call vimspector#ToggleLog()
command! -bar
\ VimspectorDebugInfo
\ call vimspector#PrintDebugInfo()
command! -nargs=1 -complete=custom,vimspector#CompleteExpr
\ VimspectorEval
\ call vimspector#Evaluate( <f-args> )
command! -bar
\ VimspectorReset
\ call vimspector#Reset( { 'interactive': v:true } )
" Installer commands
command! -bar -bang -nargs=* -complete=custom,vimspector#CompleteInstall
\ VimspectorInstall
\ call vimspector#Install( <q-bang>, <f-args> )
command! -bar -bang -nargs=*
\ VimspectorUpdate
\ call vimspector#Update( <q-bang>, <f-args> )
command! -bar -nargs=0
\ VimspectorAbortInstall
\ call vimspector#AbortInstall()
" Dummy autocommands so that we can call this whenever
augroup VimspectorUserAutoCmds
autocmd!
autocmd User VimspectorUICreated silent
autocmd User VimspectorTerminalOpened silent
autocmd user VimspectorJumpedToFrame silent
autocmd user VimspectorDebugEnded silent
augroup END
" FIXME: Only register this _while_ debugging is active
augroup Vimspector
autocmd!
autocmd BufNew * call vimspector#OnBufferCreated( expand( '<afile>' ) )
augroup END
" boilerplate {{{
call s:restore_cpo() call s:restore_cpo()
" }}}

View file

@ -21,7 +21,7 @@ import os
import logging import logging
import json import json
from vimspector import utils, signs from vimspector import utils
class ServerBreakpointHandler( object ): class ServerBreakpointHandler( object ):
@ -44,7 +44,6 @@ class ProjectBreakpoints( object ):
self._line_breakpoints = defaultdict( list ) self._line_breakpoints = defaultdict( list )
self._func_breakpoints = [] self._func_breakpoints = []
self._exception_breakpoints = None self._exception_breakpoints = None
self._configured_breakpoints = {}
# FIXME: Remove this. Remove breakpoints nonesense from code.py # FIXME: Remove this. Remove breakpoints nonesense from code.py
self._breakpoints_handler = None self._breakpoints_handler = None
@ -52,23 +51,9 @@ class ProjectBreakpoints( object ):
self._next_sign_id = 1 self._next_sign_id = 1
if not signs.SignDefined( 'vimspectorBP' ): # TODO: Change to sign_define ?
signs.DefineSign( 'vimspectorBP', vim.command( 'sign define vimspectorBP text==> texthl=Error' )
text = '', vim.command( 'sign define vimspectorBPDisabled text=!> texthl=Warning' )
double_text = '',
texthl = 'WarningMsg' )
if not signs.SignDefined( 'vimspectorBPCond' ):
signs.DefineSign( 'vimspectorBPCond',
text = '',
double_text = '',
texthl = 'WarningMsg' )
if not signs.SignDefined( 'vimspectorBPDisabled' ):
signs.DefineSign( 'vimspectorBPDisabled',
text = '',
double_text = '',
texthl = 'LineNr' )
def ConnectionUp( self, connection ): def ConnectionUp( self, connection ):
@ -88,10 +73,8 @@ class ProjectBreakpoints( object ):
# NOTE: we don't reset self._exception_breakpoints because we don't want to # NOTE: we don't reset self._exception_breakpoints because we don't want to
# re-ask the user every time for the sane info. # re-ask the user every time for the sane info.
# FIXME: If the adapter type changes, we should probably forget this ?
def ListBreakpoints( self ):
def BreakpointsAsQuickFix( self ):
# FIXME: Handling of breakpoints is a mess, split between _codeView and this # FIXME: Handling of breakpoints is a mess, split between _codeView and this
# object. This makes no sense and should be centralised so that we don't # object. This makes no sense and should be centralised so that we don't
# have this duplication and bug factory. # have this duplication and bug factory.
@ -101,16 +84,14 @@ class ProjectBreakpoints( object ):
else: else:
for file_name, breakpoints in self._line_breakpoints.items(): for file_name, breakpoints in self._line_breakpoints.items():
for bp in breakpoints: for bp in breakpoints:
self._SignToLine( file_name, bp )
qf.append( { qf.append( {
'filename': file_name, 'filename': file_name,
'lnum': bp[ 'line' ], 'lnum': bp[ 'line' ],
'col': 1, 'col': 1,
'type': 'L', 'type': 'L',
'valid': 1 if bp[ 'state' ] == 'ENABLED' else 0, 'valid': 1 if bp[ 'state' ] == 'ENABLED' else 0,
'text': "Line breakpoint - {}: {}".format( 'text': "Line breakpoint - {}".format(
bp[ 'state' ], bp[ 'state' ] )
json.dumps( bp[ 'options' ] ) )
} ) } )
# I think this shows that the qf list is not right for this. # I think this shows that the qf list is not right for this.
for bp in self._func_breakpoints: for bp in self._func_breakpoints:
@ -120,152 +101,71 @@ class ProjectBreakpoints( object ):
'col': 1, 'col': 1,
'type': 'F', 'type': 'F',
'valid': 1, 'valid': 1,
'text': "Function breakpoint: {}: {}".format( bp[ 'function' ], 'text': "Function breakpoint: {}".format( bp[ 'function' ] ),
bp[ 'options' ] ),
} ) } )
return qf vim.eval( 'setqflist( {} )'.format( json.dumps( qf ) ) )
def ClearBreakpoints( self ): def ClearBreakpoints( self ):
# These are the user-entered breakpoints. # These are the user-entered breakpoints.
for file_name, breakpoints in self._line_breakpoints.items(): for file_name, breakpoints in self._line_breakpoints.items():
for bp in breakpoints: for bp in breakpoints:
self._SignToLine( file_name, bp )
if 'sign_id' in bp: if 'sign_id' in bp:
signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' ) vim.command( 'sign unplace {0} group=VimspectorBP'.format(
bp[ 'sign_id' ] ) )
self._line_breakpoints = defaultdict( list ) self._line_breakpoints = defaultdict( list )
self._func_breakpoints = [] self._func_breakpoints = []
self._exception_breakpoints = None
self.UpdateUI() self.UpdateUI()
def _FindLineBreakpoint( self, file_name, line ): def ToggleBreakpoint( self ):
file_name = os.path.abspath( file_name ) line, column = vim.current.window.cursor
for index, bp in enumerate( self._line_breakpoints[ file_name ] ):
self._SignToLine( file_name, bp )
if bp[ 'line' ] == line:
return bp, index
return None, None
def _PutLineBreakpoint( self, file_name, line, options ):
self._line_breakpoints[ os.path.abspath( file_name ) ].append( {
'state': 'ENABLED',
'line': line,
'options': options,
# 'sign_id': <filled in when placed>,
#
# Used by other breakpoint types (specified in options):
# 'condition': ...,
# 'hitCondition': ...,
# 'logMessage': ...
} )
def _DeleteLineBreakpoint( self, bp, file_name, index ):
if 'sign_id' in bp:
signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' )
del self._line_breakpoints[ os.path.abspath( file_name ) ][ index ]
def ToggleBreakpoint( self, options ):
line, _ = vim.current.window.cursor
file_name = vim.current.buffer.name file_name = vim.current.buffer.name
if not file_name: if not file_name:
return return
bp, index = self._FindLineBreakpoint( file_name, line ) found_bp = False
if bp is None: action = 'New'
# ADD for index, bp in enumerate( self._line_breakpoints[ file_name ] ):
self._PutLineBreakpoint( file_name, line, options ) if bp[ 'line' ] == line:
elif bp[ 'state' ] == 'ENABLED' and not self._connection: found_bp = True
# DISABLE if bp[ 'state' ] == 'ENABLED' and not self._connection:
bp[ 'state' ] = 'DISABLED' bp[ 'state' ] = 'DISABLED'
else: action = 'Disable'
# DELETE else:
self._DeleteLineBreakpoint( bp, file_name, index ) if 'sign_id' in bp:
vim.command( 'sign unplace {0} group=VimspectorBP'.format(
bp[ 'sign_id' ] ) )
del self._line_breakpoints[ file_name ][ index ]
action = 'Delete'
break
self._logger.debug( "Toggle found bp at {}:{} ? {} ({})".format(
file_name,
line,
found_bp,
action ) )
if not found_bp:
self._line_breakpoints[ file_name ].append( {
'state': 'ENABLED',
'line': line,
# 'sign_id': <filled in when placed>,
#
# Used by other breakpoint types:
# 'condition': ...,
# 'hitCondition': ...,
# 'logMessage': ...
} )
self.UpdateUI() self.UpdateUI()
def AddFunctionBreakpoint( self, function ):
def SetLineBreakpoint( self, file_name, line_num, options, then = None ):
bp, _ = self._FindLineBreakpoint( file_name, line_num )
if bp is not None:
bp[ 'options' ] = options
return
self._PutLineBreakpoint( file_name, line_num, options )
self.UpdateUI( then )
def ClearLineBreakpoint( self, file_name, line_num ):
bp, index = self._FindLineBreakpoint( file_name, line_num )
if bp is None:
return
self._DeleteLineBreakpoint( bp, file_name, index )
self.UpdateUI()
def ClearTemporaryBreakpoint( self, file_name, line_num ):
bp, index = self._FindLineBreakpoint( file_name, line_num )
if bp is None:
return
if bp[ 'options' ].get( 'temporary' ):
self._DeleteLineBreakpoint( bp, file_name, index )
self.UpdateUI()
def ClearTemporaryBreakpoints( self ):
to_delete = []
for file_name, breakpoints in self._line_breakpoints.items():
for index, bp in enumerate( breakpoints ):
if bp[ 'options' ].get( 'temporary' ):
to_delete.append( ( bp, file_name, index ) )
for entry in to_delete:
self._DeleteLineBreakpoint( *entry )
def _UpdateTemporaryBreakpoints( self, breakpoints, temp_idxs ):
# adjust any temporary breakpoints to match the server result
# TODO: Maybe now is the time to ditch the split breakpoints nonesense
for temp_idx, user_bp in temp_idxs:
if temp_idx >= len( breakpoints ):
# Just can't trust servers ?
self._logger.debug( "Server Error - invalid breakpoints list did not "
"contain entry for temporary breakpoint at index "
f"{ temp_idx } i.e. { user_bp }" )
continue
bp = breakpoints[ temp_idx ]
if 'line' not in bp or not bp[ 'verified' ]:
utils.UserMessage(
"Unable to set temporary breakpoint at line "
f"{ user_bp[ 'line' ] } execution will continue...",
persist = True,
error = True )
self._logger.debug( f"Updating temporary breakpoint { user_bp } line "
f"{ user_bp[ 'line' ] } to { bp[ 'line' ] }" )
# if it was moved, update the user-breakpoint so that we unset it
# again properly
user_bp[ 'line' ] = bp[ 'line' ]
def AddFunctionBreakpoint( self, function, options ):
self._func_breakpoints.append( { self._func_breakpoints.append( {
'state': 'ENABLED', 'state': 'ENABLED',
'function': function, 'function': function,
'options': options,
# Specified in options:
# 'condition': ...,
# 'hitCondition': ...,
} ) } )
# TODO: We don't really have aanything to update here, but if we're going to # TODO: We don't really have aanything to update here, but if we're going to
@ -273,13 +173,11 @@ class ProjectBreakpoints( object ):
self.UpdateUI() self.UpdateUI()
def UpdateUI( self, then = None ): def UpdateUI( self ):
if self._connection: if self._connection:
self.SendBreakpoints( then ) self.SendBreakpoints()
else: else:
self._ShowBreakpoints() self._ShowBreakpoints()
if then:
then()
def SetBreakpointsHandler( self, handler ): def SetBreakpointsHandler( self, handler ):
@ -287,10 +185,6 @@ class ProjectBreakpoints( object ):
self._breakpoints_handler = handler self._breakpoints_handler = handler
def SetConfiguredBreakpoints( self, configured_breakpoints ):
self._configured_breakpoints = configured_breakpoints
def SendBreakpoints( self, doneHandler = None ): def SendBreakpoints( self, doneHandler = None ):
assert self._breakpoints_handler is not None assert self._breakpoints_handler is not None
@ -299,59 +193,27 @@ class ProjectBreakpoints( object ):
awaiting = 0 awaiting = 0
def response_received( *failure_args ): def response_handler( source, msg ):
if msg:
self._breakpoints_handler.AddBreakpoints( source, msg )
nonlocal awaiting nonlocal awaiting
awaiting = awaiting - 1 awaiting = awaiting - 1
if failure_args and self._connection:
reason, msg = failure_args
utils.UserMessage( 'Unable to set breakpoint: {0}'.format( reason ),
persist = True,
error = True )
if awaiting == 0 and doneHandler: if awaiting == 0 and doneHandler:
doneHandler() doneHandler()
def response_handler( source, msg, temp_idxs = [] ):
if msg:
self._breakpoints_handler.AddBreakpoints( source, msg )
breakpoints = ( msg.get( 'body' ) or {} ).get( 'breakpoints' ) or []
self._UpdateTemporaryBreakpoints( breakpoints, temp_idxs )
response_received()
# NOTE: Must do this _first_ otherwise we might send requests and get
# replies before we finished sending all the requests.
if self._exception_breakpoints is None:
self._SetUpExceptionBreakpoints( self._configured_breakpoints )
# TODO: add the _configured_breakpoints to line_breakpoints
for file_name, line_breakpoints in self._line_breakpoints.items(): for file_name, line_breakpoints in self._line_breakpoints.items():
temp_idxs = []
breakpoints = [] breakpoints = []
for bp in line_breakpoints: for bp in line_breakpoints:
self._SignToLine( file_name, bp )
if 'sign_id' in bp: if 'sign_id' in bp:
signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' ) vim.command( 'sign unplace {0} group=VimspectorBP'.format(
bp[ 'sign_id' ] ) )
del bp[ 'sign_id' ] del bp[ 'sign_id' ]
if bp[ 'state' ] != 'ENABLED': if bp[ 'state' ] != 'ENABLED':
continue continue
dap_bp = {} breakpoints.append( { 'line': bp[ 'line' ] } )
dap_bp.update( bp[ 'options' ] )
dap_bp.update( { 'line': bp[ 'line' ] } )
dap_bp.pop( 'temporary', None )
if bp[ 'options' ].get( 'temporary' ):
temp_idxs.append( [ len( breakpoints ), bp ] )
breakpoints.append( dap_bp )
source = { source = {
'name': os.path.basename( file_name ), 'name': os.path.basename( file_name ),
@ -360,13 +222,7 @@ class ProjectBreakpoints( object ):
awaiting = awaiting + 1 awaiting = awaiting + 1
self._connection.DoRequest( self._connection.DoRequest(
# The source=source here is critical to ensure that we capture each lambda msg: response_handler( source, msg ),
# source in the iteration, rather than ending up passing the same source
# to each callback.
lambda msg, source=source, temp_idxs=temp_idxs: response_handler(
source,
msg,
temp_idxs = temp_idxs ),
{ {
'command': 'setBreakpoints', 'command': 'setBreakpoints',
'arguments': { 'arguments': {
@ -374,34 +230,27 @@ class ProjectBreakpoints( object ):
'breakpoints': breakpoints, 'breakpoints': breakpoints,
}, },
'sourceModified': False, # TODO: We can actually check this 'sourceModified': False, # TODO: We can actually check this
}, }
failure_handler = response_received
) )
# TODO: Add the _configured_breakpoints to function breakpoints
if self._server_capabilities.get( 'supportsFunctionBreakpoints' ): if self._server_capabilities.get( 'supportsFunctionBreakpoints' ):
awaiting = awaiting + 1 awaiting = awaiting + 1
breakpoints = []
for bp in self._func_breakpoints:
if bp[ 'state' ] != 'ENABLED':
continue
dap_bp = {}
dap_bp.update( bp[ 'options' ] )
dap_bp.update( { 'name': bp[ 'function' ] } )
breakpoints.append( dap_bp )
self._connection.DoRequest( self._connection.DoRequest(
lambda msg: response_handler( None, msg ), lambda msg: response_handler( None, msg ),
{ {
'command': 'setFunctionBreakpoints', 'command': 'setFunctionBreakpoints',
'arguments': { 'arguments': {
'breakpoints': breakpoints, 'breakpoints': [
{ 'name': bp[ 'function' ] }
for bp in self._func_breakpoints if bp[ 'state' ] == 'ENABLED'
],
} }
}, }
failure_handler = response_received
) )
if self._exception_breakpoints is None:
self._SetUpExceptionBreakpoints()
if self._exception_breakpoints: if self._exception_breakpoints:
awaiting = awaiting + 1 awaiting = awaiting + 1
self._connection.DoRequest( self._connection.DoRequest(
@ -409,15 +258,14 @@ class ProjectBreakpoints( object ):
{ {
'command': 'setExceptionBreakpoints', 'command': 'setExceptionBreakpoints',
'arguments': self._exception_breakpoints 'arguments': self._exception_breakpoints
}, }
failure_handler = response_received
) )
if awaiting == 0 and doneHandler: if awaiting == 0 and doneHandler:
doneHandler() doneHandler()
def _SetUpExceptionBreakpoints( self, configured_breakpoints ): def _SetUpExceptionBreakpoints( self ):
exception_breakpoint_filters = self._server_capabilities.get( exception_breakpoint_filters = self._server_capabilities.get(
'exceptionBreakpointFilters', 'exceptionBreakpointFilters',
[] ) [] )
@ -430,27 +278,14 @@ class ProjectBreakpoints( object ):
# trigger requesting threads etc.). See the note in # trigger requesting threads etc.). See the note in
# debug_session.py:_Initialise for more detials # debug_session.py:_Initialise for more detials
exception_filters = [] exception_filters = []
configured_filter_options = configured_breakpoints.get( 'exception', {} )
if exception_breakpoint_filters: if exception_breakpoint_filters:
for f in exception_breakpoint_filters: for f in exception_breakpoint_filters:
default_value = 'Y' if f.get( 'default' ) else 'N' default_value = 'Y' if f.get( 'default' ) else 'N'
if f[ 'filter' ] in configured_filter_options: result = utils.AskForInput(
result = configured_filter_options[ f[ 'filter' ] ] "Break on {} (Y/N/default: {})? ".format( f[ 'label' ],
default_value ),
if isinstance( result, bool ): default_value )
result = 'Y' if result else 'N'
if not isinstance( result, str ) or result not in ( 'Y', 'N', '' ):
raise ValueError(
f"Invalid value for exception breakpoint filter '{f}': "
f"'{result}'. Must be boolean, 'Y', 'N' or '' (default)" )
else:
result = utils.AskForInput(
"{}: Break on {} (Y/N/default: {})? ".format( f[ 'filter' ],
f[ 'label' ],
default_value ),
default_value )
if result == 'Y': if result == 'Y':
exception_filters.append( f[ 'filter' ] ) exception_filters.append( f[ 'filter' ] )
@ -467,46 +302,20 @@ class ProjectBreakpoints( object ):
# pay any attention to them anyway. # pay any attention to them anyway.
self._exception_breakpoints[ 'exceptionOptions' ] = [] self._exception_breakpoints[ 'exceptionOptions' ] = []
def Refresh( self, file_name ):
# TODO: Just this file ?
self._ShowBreakpoints()
def _ShowBreakpoints( self ): def _ShowBreakpoints( self ):
for file_name, line_breakpoints in self._line_breakpoints.items(): for file_name, line_breakpoints in self._line_breakpoints.items():
for bp in line_breakpoints: for bp in line_breakpoints:
self._SignToLine( file_name, bp )
if 'sign_id' in bp: if 'sign_id' in bp:
signs.UnplaceSign( bp[ 'sign_id' ], 'VimspectorBP' ) vim.command( 'sign unplace {0} group=VimspectorBP '.format(
bp[ 'sign_id' ] ) )
else: else:
bp[ 'sign_id' ] = self._next_sign_id bp[ 'sign_id' ] = self._next_sign_id
self._next_sign_id += 1 self._next_sign_id += 1
sign = ( 'vimspectorBPDisabled' if bp[ 'state' ] != 'ENABLED' vim.command(
else 'vimspectorBPCond' if 'condition' in bp[ 'options' ] 'sign place {0} group=VimspectorBP line={1} name={2} file={3}'.format(
else 'vimspectorBP' ) bp[ 'sign_id' ] ,
bp[ 'line' ],
if utils.BufferExists( file_name ): 'vimspectorBP' if bp[ 'state' ] == 'ENABLED'
signs.PlaceSign( bp[ 'sign_id' ], else 'vimspectorBPDisabled',
'VimspectorBP', file_name ) )
sign,
file_name,
bp[ 'line' ] )
def _SignToLine( self, file_name, bp ):
if 'sign_id' not in bp:
return bp[ 'line' ]
if not utils.BufferExists( file_name ):
return bp[ 'line' ]
signs = vim.eval( "sign_getplaced( '{}', {} )".format(
utils.Escape( file_name ),
json.dumps( { 'id': bp[ 'sign_id' ], 'group': 'VimspectorBP', } ) ) )
if len( signs ) == 1 and len( signs[ 0 ][ 'signs' ] ) == 1:
bp[ 'line' ] = int( signs[ 0 ][ 'signs' ][ 0 ][ 'lnum' ] )
return bp[ 'line' ]

View file

@ -18,133 +18,63 @@ import logging
import json import json
from collections import defaultdict from collections import defaultdict
from vimspector import utils, terminal, signs from vimspector import utils
class CodeView( object ): class CodeView( object ):
def __init__( self, window, api_prefix ): def __init__( self, window ):
self._window = window self._window = window
self._api_prefix = api_prefix
self._terminal = None self._terminal_window = None
self.current_syntax = None self._terminal_buffer_number = None
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 = {
'vimspectorPC': None, 'vimspectorPC': None,
'breakpoints': [] 'breakpoints': []
} }
self._current_frame = None
with utils.LetCurrentWindow( self._window ): with utils.LetCurrentWindow( self._window ):
if utils.UseWinBar(): vim.command( 'nnoremenu WinBar.Continue :call vimspector#Continue()<CR>' )
# Buggy neovim doesn't render correctly when the WinBar is defined: vim.command( 'nnoremenu WinBar.Next :call vimspector#StepOver()<CR>' )
# https://github.com/neovim/neovim/issues/12689 vim.command( 'nnoremenu WinBar.Step :call vimspector#StepInto()<CR>' )
vim.command( 'nnoremenu WinBar.■\\ Stop ' vim.command( 'nnoremenu WinBar.Finish :call vimspector#StepOut()<CR>' )
':call vimspector#Stop()<CR>' ) vim.command( 'nnoremenu WinBar.Pause :call vimspector#Pause()<CR>' )
vim.command( 'nnoremenu WinBar.▶\\ Cont ' vim.command( 'nnoremenu WinBar.Stop :call vimspector#Stop()<CR>' )
':call vimspector#Continue()<CR>' ) vim.command( 'nnoremenu WinBar.Restart :call vimspector#Restart()<CR>' )
vim.command( 'nnoremenu WinBar.▷\\ Pause ' vim.command( 'nnoremenu WinBar.Reset :call vimspector#Reset()<CR>' )
':call vimspector#Pause()<CR>' )
vim.command( 'nnoremenu WinBar.↷\\ Next '
':call vimspector#StepOver()<CR>' )
vim.command( 'nnoremenu WinBar.→\\ Step '
':call vimspector#StepInto()<CR>' )
vim.command( 'nnoremenu WinBar.←\\ Out '
':call vimspector#StepOut()<CR>' )
vim.command( 'nnoremenu WinBar.⟲: '
':call vimspector#Restart()<CR>' )
vim.command( 'nnoremenu WinBar.✕ '
':call vimspector#Reset()<CR>' )
if not signs.SignDefined( 'vimspectorPC' ): vim.command( 'sign define vimspectorPC text=-> texthl=Search' )
signs.DefineSign( 'vimspectorPC',
text = '',
double_text = '',
texthl = 'MatchParen',
linehl = 'CursorLine' )
if not signs.SignDefined( 'vimspectorPCBP' ):
signs.DefineSign( 'vimspectorPCBP',
text = '●▶',
double_text = '',
texthl = 'MatchParen',
linehl = 'CursorLine' )
def _UndisplayPC( self, clear_pc = True ):
if clear_pc:
self._current_frame = None
if self._signs[ 'vimspectorPC' ]:
signs.UnplaceSign( self._signs[ 'vimspectorPC' ], 'VimspectorCode' )
self._signs[ 'vimspectorPC' ] = None
def _DisplayPC( self ):
frame = self._current_frame
if not frame:
return
self._UndisplayPC( clear_pc = False )
# FIXME: Do we relly need to keep using up IDs ?
self._signs[ 'vimspectorPC' ] = self._next_sign_id
self._next_sign_id += 1
sign = 'vimspectorPC'
# If there's also a breakpoint on this line, use vimspectorPCBP
for bp in self._breakpoints.get( frame[ 'source' ][ 'path' ], [] ):
if 'line' not in bp:
continue
if bp[ 'line' ] == frame[ 'line' ]:
sign = 'vimspectorPCBP'
break
if utils.BufferExists( frame[ 'source' ][ 'path' ] ):
signs.PlaceSign( self._signs[ 'vimspectorPC' ],
'VimspectorCode',
sign,
frame[ 'source' ][ 'path' ],
frame[ 'line' ] )
def SetCurrentFrame( self, frame ): def SetCurrentFrame( self, frame ):
"""Returns True if the code window was updated with the frame, False if self._signs[ 'vimspectorPC' ]:
otherwise. False means either the frame is junk, we couldn't find the file vim.command( 'sign unplace {} group=VimspectorCode'.format(
(or don't have the data) or the code window no longer exits.""" self._signs[ 'vimspectorPC' ] ) )
self._signs[ 'vimspectorPC' ] = None
if not frame or not frame.get( 'source' ): if not frame or not frame.get( 'source' ):
self._UndisplayPC()
return False return False
if 'path' not in frame[ 'source' ]: if 'path' not in frame[ 'source' ]:
self._UndisplayPC()
return False
self._current_frame = frame
if not self._window.valid:
return False return False
utils.JumpToWindow( self._window ) utils.JumpToWindow( self._window )
try: try:
utils.OpenFileInCurrentWindow( frame[ 'source' ][ 'path' ] ) utils.OpenFileInCurrentWindow( frame[ 'source' ][ 'path' ] )
vim.command( 'doautocmd <nomodeline> User VimspectorJumpedToFrame' )
except vim.error: except vim.error:
self._logger.exception( 'Unexpected vim error opening file {}'.format( self._logger.exception( 'Unexpected vim error opening file {}'.format(
frame[ 'source' ][ 'path' ] ) ) frame[ 'source' ][ 'path' ] ) )
return False return False
# SIC: column is 0-based, line is 1-based in vim. Why? Nobody knows. # SIC: column is 0-based, line is 1-based in vim. Why? Nobody knows.
# Note: max() with 0 because some debug adapters (go) return 0 for the
# column.
try: try:
self._window.cursor = ( frame[ 'line' ], max( frame[ 'column' ] - 1, 0 ) ) self._window.cursor = ( frame[ 'line' ], frame[ 'column' ] - 1 )
except vim.error: except vim.error:
self._logger.exception( "Unable to jump to %s:%s in %s, maybe the file " self._logger.exception( "Unable to jump to %s:%s in %s, maybe the file "
"doesn't exist", "doesn't exist",
@ -153,21 +83,25 @@ class CodeView( object ):
frame[ 'source' ][ 'path' ] ) frame[ 'source' ][ 'path' ] )
return False return False
self.current_syntax = utils.ToUnicode( self._signs[ 'vimspectorPC' ] = self._next_sign_id
vim.current.buffer.options[ 'syntax' ] ) self._next_sign_id += 1
self.ShowBreakpoints() vim.command( 'sign place {0} group=VimspectorCode priority=20 '
'line={1} name=vimspectorPC '
'file={2}'.format(
self._signs[ 'vimspectorPC' ],
frame[ 'line' ],
frame[ 'source' ][ 'path' ] ) )
return True return True
def Clear( self ): def Clear( self ):
if self._signs[ 'vimspectorPC' ]: if self._signs[ 'vimspectorPC' ]:
signs.UnplaceSign( self._signs[ 'vimspectorPC' ], 'VimspectorCode' ) vim.command( 'sign unplace {} group=VimspectorCode'.format(
self._signs[ 'vimspectorPC' ] ) )
self._signs[ 'vimspectorPC' ] = None self._signs[ 'vimspectorPC' ] = None
self._UndisplayPC()
self._UndisplaySigns() self._UndisplaySigns()
self.current_syntax = None
def Reset( self ): def Reset( self ):
self.ClearBreakpoints() self.ClearBreakpoints()
@ -175,29 +109,25 @@ class CodeView( object ):
def AddBreakpoints( self, source, breakpoints ): def AddBreakpoints( self, source, breakpoints ):
for breakpoint in breakpoints: for breakpoint in breakpoints:
source = breakpoint.get( 'source' ) or source if 'source' not in breakpoint:
if not source or 'path' not in source: if source:
self._logger.warn( 'missing source/path in breakpoint {0}'.format( breakpoint[ 'source' ] = source
json.dumps( breakpoint ) ) ) else:
continue self._logger.warn( 'missing source in breakpoint {0}'.format(
json.dumps( breakpoint ) ) )
continue
breakpoint[ 'source' ] = source self._breakpoints[ breakpoint[ 'source' ][ 'path' ] ].append(
self._breakpoints[ source[ 'path' ] ].append( breakpoint ) breakpoint )
self._logger.debug( 'Breakpoints at this point: {0}'.format( self._logger.debug( 'Breakpoints at this point: {0}'.format(
json.dumps( self._breakpoints, indent = 2 ) ) ) json.dumps( self._breakpoints, indent = 2 ) ) )
self.ShowBreakpoints() self.ShowBreakpoints()
def AddBreakpoint( self, breakpoint ):
self.AddBreakpoints( None, [ breakpoint ] )
def UpdateBreakpoint( self, bp ): def UpdateBreakpoint( self, bp ):
if 'id' not in bp: if 'id' not in bp:
self.AddBreakpoint( bp ) self.AddBreakpoints( None, [ bp ] )
return
for _, breakpoint_list in self._breakpoints.items(): for _, breakpoint_list in self._breakpoints.items():
for index, breakpoint in enumerate( breakpoint_list ): for index, breakpoint in enumerate( breakpoint_list ):
@ -207,31 +137,11 @@ class CodeView( object ):
return return
# Not found. Assume new # Not found. Assume new
self.AddBreakpoint( bp ) self.AddBreakpoints( None, [ bp ] )
def RemoveBreakpoint( self, bp ):
for _, breakpoint_list in self._breakpoints.items():
found_index = None
for index, breakpoint in enumerate( breakpoint_list ):
if 'id' in breakpoint and breakpoint[ 'id' ] == bp[ 'id' ]:
found_index = index
break
if found_index is not None:
del breakpoint_list[ found_index ]
self.ShowBreakpoints()
return
def Refresh( self, file_name ):
# TODO: jsut the file ?
self.ShowBreakpoints()
def _UndisplaySigns( self ): def _UndisplaySigns( self ):
for sign_id in self._signs[ 'breakpoints' ]: for sign_id in self._signs[ 'breakpoints' ]:
signs.UnplaceSign( sign_id, 'VimspectorCode' ) vim.command( 'sign unplace {} group=VimspectorCode'.format( sign_id ) )
self._signs[ 'breakpoints' ] = [] self._signs[ 'breakpoints' ] = []
@ -250,17 +160,17 @@ class CodeView( object ):
sign_id = self._next_sign_id sign_id = self._next_sign_id
self._next_sign_id += 1 self._next_sign_id += 1
self._signs[ 'breakpoints' ].append( sign_id ) self._signs[ 'breakpoints' ].append( sign_id )
if utils.BufferExists( file_name ): vim.command(
signs.PlaceSign( sign_id, 'sign place {0} group=VimspectorCode priority=9 '
'VimspectorCode', 'line={1} '
'vimspectorBP' if breakpoint[ 'verified' ] 'name={2} '
else 'vimspectorBPDisabled', 'file={3}'.format(
file_name, sign_id,
breakpoint[ 'line' ] ) breakpoint[ 'line' ],
'vimspectorBP' if breakpoint[ 'verified' ]
else 'vimspectorBPDisabled',
file_name ) )
# We need to also check if there's a breakpoint on this PC line and chnge
# the PC
self._DisplayPC()
def BreakpointsAsQuickFix( self ): def BreakpointsAsQuickFix( self ):
qf = [] qf = []
@ -279,11 +189,50 @@ class CodeView( object ):
def LaunchTerminal( self, params ): def LaunchTerminal( self, params ):
self._terminal = terminal.LaunchTerminal( self._api_prefix, # kind = params.get( 'kind', 'integrated' )
params,
window_for_start = self._window,
existing_term = self._terminal )
# FIXME: Change this tor return the PID rather than having debug_session # FIXME: We don't support external terminals, and only open in the
# work that out # integrated one.
return self._terminal.buffer_number
cwd = params[ 'cwd' ]
args = params[ 'args' ]
env = params.get( 'env', {} )
options = {
'vertical': 1,
'norestore': 1,
'cwd': cwd,
'env': env,
}
window_for_start = self._window
if self._terminal_window is not None:
assert self._terminal_buffer_number
if ( self._terminal_window.buffer.number == self._terminal_buffer_number
and 'finished' in vim.eval( 'term_getstatus( {} )'.format(
self._terminal_buffer_number ) ) ):
window_for_start = self._terminal_window
options[ 'curwin' ] = 1
buffer_number = None
terminal_window = None
with utils.TemporaryVimOptions( { 'splitright': True,
'equalalways': False } ):
with utils.LetCurrentWindow( window_for_start ):
# TODO/FIXME: Do something about closing this when we reset ?
vim_cmd = 'term_start( {}, {} )'.format( json.dumps( args ),
json.dumps( options ) )
self._logger.debug( 'Start terminal: {}'.format( vim_cmd ) )
buffer_number = int( vim.eval( vim_cmd ) )
terminal_window = vim.current.window
if buffer_number is None or buffer_number <= 0:
# TODO: Do something better like reject the request?
raise ValueError( "Unable to start terminal" )
else:
self._terminal_window = terminal_window
self._terminal_buffer_number = buffer_number
return buffer_number

View file

@ -1,51 +0,0 @@
# vimspector - A multi-language debugging system for Vim
# Copyright 2021 Ben Jackson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from vimspector.debug_session import DebugSession
from vimspector import utils, settings
class JavaDebugAdapter( object ):
def __init__( self, debug_session: DebugSession ):
self.debug_session = debug_session
def OnEvent_hotcodereplace( self, message ):
# Hack for java debug server hot-code-replace
body = message.get( 'body' ) or {}
if body.get( 'type' ) != 'hotcodereplace':
return
if body.get( 'changeType' ) == 'BUILD_COMPLETE':
def handler( result ):
if result == 1:
self.debug_session._connection.DoRequest( None, {
'command': 'redefineClasses',
'arguments': {},
} )
mode = settings.Get( 'java_hotcodereplace_mode' )
if mode == 'ask':
utils.Confirm( self.debug_session._api_prefix,
'Code has changed, hot reload?',
handler,
default_value = 1 )
elif mode == 'always':
self.debug_session._connection.DoRequest( None, {
'command': 'redefineClasses',
'arguments': {},
} )
elif body.get( 'message' ):
utils.UserMessage( 'Hot code replace: ' + body[ 'message' ] )

View file

@ -29,14 +29,14 @@ class PendingRequest( object ):
class DebugAdapterConnection( object ): class DebugAdapterConnection( object ):
def __init__( self, handlers, send_func ): def __init__( self, handler, send_func ):
self._logger = logging.getLogger( __name__ ) self._logger = logging.getLogger( __name__ )
utils.SetUpLogging( self._logger ) utils.SetUpLogging( self._logger )
self._Write = send_func self._Write = send_func
self._SetState( 'READ_HEADER' ) self._SetState( 'READ_HEADER' )
self._buffer = bytes() self._buffer = bytes()
self._handlers = handlers self._handler = handler
self._next_message_id = 0 self._next_message_id = 0
self._outstanding_requests = {} self._outstanding_requests = {}
@ -65,33 +65,6 @@ class DebugAdapterConnection( object ):
if not self._SendMessage( msg ): if not self._SendMessage( msg ):
self._AbortRequest( request, 'Unable to send message' ) self._AbortRequest( request, 'Unable to send message' )
def DoRequestSync( self, msg, timeout = 5000 ):
result = {}
def handler( msg ):
result[ 'response' ] = msg
def failure_handler( reason, msg ):
result[ 'response' ] = msg
result[ 'exception' ] = RuntimeError( reason )
self.DoRequest( handler, msg, failure_handler, timeout )
bug_catcher = 1000
while not result and bug_catcher >= 0:
vim.command( 'sleep 10m' )
bug_catcher -= 10
if result.get( 'exception' ) is not None:
raise result[ 'exception' ]
if result.get( 'response' ) is None:
raise RuntimeError( "No response" )
return result[ 'response' ]
def OnRequestTimeout( self, timer_id ): def OnRequestTimeout( self, timer_id ):
request_id = None request_id = None
for seq, request in self._outstanding_requests.items(): for seq, request in self._outstanding_requests.items():
@ -124,7 +97,7 @@ class DebugAdapterConnection( object ):
def Reset( self ): def Reset( self ):
self._Write = None self._Write = None
self._handlers = None self._handler = None
while self._outstanding_requests: while self._outstanding_requests:
_, request = self._outstanding_requests.popitem() _, request = self._outstanding_requests.popitem()
@ -169,10 +142,6 @@ class DebugAdapterConnection( object ):
self._headers = {} self._headers = {}
def _SendMessage( self, msg ): def _SendMessage( self, msg ):
if not self._Write:
# Connection was destroyed
return False
msg = json.dumps( msg ) msg = json.dumps( msg )
self._logger.debug( 'Sending Message: {0}'.format( msg ) ) self._logger.debug( 'Sending Message: {0}'.format( msg ) )
@ -226,12 +195,7 @@ class DebugAdapterConnection( object ):
# self._logger.debug( 'Message received (raw): %s', payload ) # self._logger.debug( 'Message received (raw): %s', payload )
try: message = json.loads( payload )
message = json.loads( payload, strict = False )
except Exception:
self._logger.exception( "Invalid message received: %s", payload )
self._SetState( 'READ_HEADER' )
raise
self._logger.debug( 'Message received: {0}'.format( message ) ) self._logger.debug( 'Message received: {0}'.format( message ) )
@ -242,7 +206,7 @@ class DebugAdapterConnection( object ):
def _OnMessageReceived( self, message ): def _OnMessageReceived( self, message ):
if not self._handlers: if not self._handler:
return return
if message[ 'type' ] == 'response': if message[ 'type' ] == 'response':
@ -275,21 +239,25 @@ class DebugAdapterConnection( object ):
self._logger.error( 'Request failed: {0}'.format( reason ) ) self._logger.error( 'Request failed: {0}'.format( reason ) )
if request.failure_handler: if request.failure_handler:
request.failure_handler( reason, message ) request.failure_handler( reason, message )
elif 'OnFailure' in dir( self._handler ):
self._handler.OnFailure( reason, message )
else: else:
for h in self._handlers: utils.UserMessage( 'Request failed: {0}'.format( reason ) )
if 'OnFailure' in dir( h ):
h.OnFailure( reason, request.msg, message )
elif message[ 'type' ] == 'event': elif message[ 'type' ] == 'event':
method = 'OnEvent_' + message[ 'event' ] method = 'OnEvent_' + message[ 'event' ]
for h in self._handlers: if method in dir( self._handler ):
if method in dir( h ): getattr( self._handler, method )( message )
getattr( h, method )( message ) else:
utils.UserMessage( 'Unhandled event: {0}'.format( message[ 'event' ] ),
persist = True )
elif message[ 'type' ] == 'request': elif message[ 'type' ] == 'request':
method = 'OnRequest_' + message[ 'command' ] method = 'OnRequest_' + message[ 'command' ]
for h in self._handlers: if method in dir( self._handler ):
if method in dir( h ): getattr( self._handler, method )( message )
getattr( h, method )( message ) else:
utils.UserMessage(
'Unhandled request: {0}'.format( message[ 'command' ] ),
persist = True )
def _KillTimer( request ): def _KillTimer( request ):

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
# vimspector - A multi-language debugging system for Vim
# Copyright 2020 Ben Jackson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
from vimspector import install, utils, installer
def SetUpDebugpy( wait=False, port=5678 ):
sys.path.insert(
1,
os.path.join( install.GetGadgetDir( utils.GetVimspectorBase() ),
'debugpy',
'build',
'lib' ) )
import debugpy
exe = sys.executable
try:
# debugpy uses sys.executable (which is `vim`, so we hack it)
sys.executable = installer.PathToAnyWorkingPython3()
debugpy.listen( port )
finally:
sys.executable = exe
if wait:
debugpy.wait_for_client()

View file

@ -1,475 +0,0 @@
# vimspector - A multi-language debugging system for Vim
# Copyright 2020 Ben Jackson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from vimspector import installer
import sys
import os
GADGETS = {
'vscode-cpptools': {
'language': [ 'c', 'cpp', 'rust' ],
'download': {
'url': 'https://github.com/Microsoft/vscode-cpptools/releases/download/'
'${version}/${file_name}',
},
'do': lambda name, root, gadget: installer.InstallCppTools( name,
root,
gadget ),
'all': {
'version': '1.6.0',
"adapters": {
"vscode-cpptools": {
"name": "cppdbg",
"command": [
"${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7"
],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
},
"configuration": {
"type": "cppdbg",
"args": [],
"cwd": "${workspaceRoot}",
"environment": [],
}
},
},
},
'linux': {
'file_name': 'cpptools-linux.vsix',
'checksum':
'c25299bcfb46b22d41aa3f125df7184e6282a35ff9fb69c47def744cb4778f55',
},
'macos': {
'file_name': 'cpptools-osx-arm64.vsix',
'checksum':
'ceb3e8cdaa2b5bb45af50913ddd8402089969748af8d70f5d46480408287ba6f',
},
'windows': {
'file_name': 'cpptools-win32.vsix',
'checksum':
'ef7ac5831874a3c7dbf0feb826bfda2be579aff9b6d990622fff1d0d4ede00d1',
"adapters": {
"vscode-cpptools": {
"name": "cppdbg",
"command": [
"${gadgetDir}/vscode-cpptools/debugAdapters/bin/OpenDebugAD7.exe"
],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
},
"configuration": {
"type": "cppdbg",
"args": [],
"cwd": "${workspaceRoot}",
"environment": [],
"MIMode": "gdb",
"MIDebuggerPath": "gdb.exe"
}
},
},
},
},
'debugpy': {
'language': 'python',
'download': {
'url': 'https://github.com/microsoft/debugpy/archive/${file_name}'
},
'all': {
'version': '1.2.1',
'file_name': 'v1.2.1.zip',
'checksum':
'29a6c5d1053d2b6f3b1a63e1a8ecff93f951d3cc0b7548431592e9e3007239e6'
},
'do': lambda name, root, gadget: installer.InstallDebugpy( name,
root,
gadget ),
'adapters': {
'debugpy': {
"command": [
sys.executable, # TODO: Will this work from within Vim ?
"${gadgetDir}/debugpy/build/lib/debugpy/adapter"
],
"name": "debugpy",
"configuration": {
"python": sys.executable, # TODO: Will this work from within Vim ?
# Don't debug into subprocesses, as this leads to problems (vimspector
# doesn't support the custom messages)
# https://github.com/puremourning/vimspector/issues/141
"subProcess": False,
}
}
},
},
'vscode-java-debug': {
'language': 'java',
'enabled': False,
'download': {
'url': 'https://github.com/microsoft/vscode-java-debug/releases/download/'
'${version}/${file_name}',
},
'all': {
'version': '0.26.0',
'file_name': 'vscjava.vscode-java-debug-0.26.0.vsix',
'checksum':
'de49116ff3a3c941dad0c36d9af59baa62cd931e808a2ab392056cbb235ad5ef',
},
'adapters': {
"vscode-java": {
"name": "vscode-java",
"port": "${DAPPort}",
"configuration": {
"cwd": "${workspaceRoot}"
},
'custom_handler': 'vimspector.custom.java.JavaDebugAdapter'
}
},
},
'java-language-server': {
'language': 'javac',
'enabled': False,
'download': {
'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/'
'publishers/georgewfraser/vsextensions/vscode-javac/${version}/'
'vspackage',
'target': 'georgewfraser.vscode-javac-0.2.31.vsix.gz',
'format': 'zip.gz',
},
'all': {
'version': '0.2.31',
'file_name': 'georgewfraser.vscode-javac-0.2.31.vsix.gz',
'checksum':
'5b0248ec1198d3ece9a9c6b9433b30c22e308f0ae6e4c7bd09cd943c454e3e1d',
},
'adapters': {
"vscode-javac": {
"name": "vscode-javac",
"type": "vscode-javac",
"command": [
"${gadgetDir}/java-language-server/dist/debug_adapter_mac.sh"
],
"attach": {
"pidSelect": "none"
}
}
},
},
'tclpro': {
'language': 'tcl',
'repo': {
'url': 'https://github.com/puremourning/TclProDebug',
'ref': 'v1.0.0'
},
'do': lambda name, root, gadget: installer.InstallTclProDebug( name,
root,
gadget ),
'adapters': {
"tclpro": {
"name": "tclpro",
"type": "tclpro",
"command": [
"${gadgetDir}/tclpro/bin/debugadapter"
],
"attach": {
"pidSelect": "none"
},
"configuration": {
"target": "${file}",
"args": [ "*${args}" ],
"tclsh": "tclsh",
"cwd": "${workspaceRoot}",
"extensionDirs": [
"${workspaceRoot}/.tclpro/extensions",
"${HOME}/.tclpro/extensions",
]
}
}
},
},
'netcoredbg': {
'language': [ 'csharp', 'fsharp', 'vbnet' ],
'enabled': False,
'download': {
'url': ( 'https://github.com/Samsung/netcoredbg/releases/download/'
'${version}/${file_name}' ),
'format': 'tar',
},
'all': {
'version': '1.2.0-782'
},
'macos': {
'file_name': 'netcoredbg-osx.tar.gz',
'checksum':
'',
},
'linux': {
'file_name': 'netcoredbg-linux-bionic-amd64.tar.gz',
'checksum': '',
},
'windows': {
'file_name': 'netcoredbg-win64.zip',
'checksum': '',
},
'do': lambda name, root, gadget: installer.MakeSymlink(
name,
os.path.join( root, 'netcoredbg' ) ),
'adapters': {
'netcoredbg': {
"name": "netcoredbg",
"command": [
"${gadgetDir}/netcoredbg/netcoredbg",
"--interpreter=vscode"
],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
},
"configuration": {
"cwd": "${workspaceRoot}"
}
},
}
},
'vscode-bash-debug': {
'language': 'bash',
'download': {
'url': 'https://github.com/rogalmic/vscode-bash-debug/releases/'
'download/${version}/${file_name}',
},
'all': {
'file_name': 'bash-debug-0.3.7.vsix',
'version': 'v0.3.7',
'checksum':
'7b73e5b4604375df8658fb5a72c645c355785a289aa785a986e508342c014bb4',
},
'do': lambda name, root, gadget: installer.InstallBashDebug( name,
root,
gadget ),
'adapters': {
"vscode-bash": {
"name": "bashdb",
"command": [
"node",
"${gadgetDir}/vscode-bash-debug/out/bashDebug.js"
],
"variables": {
"BASHDB_HOME": "${gadgetDir}/vscode-bash-debug/bashdb_dir"
},
"configuration": {
"request": "launch",
"type": "bashdb",
"program": "${file}",
"args": [],
"env": {},
"pathBash": "bash",
"pathBashdb": "${BASHDB_HOME}/bashdb",
"pathBashdbLib": "${BASHDB_HOME}",
"pathCat": "cat",
"pathMkfifo": "mkfifo",
"pathPkill": "pkill",
"cwd": "${workspaceRoot}",
"terminalKind": "integrated",
}
}
}
},
'vscode-go': {
'language': 'go',
'download': {
'url': 'https://github.com/golang/vscode-go/releases/download/'
'v${version}/${file_name}'
},
'all': {
'version': '0.19.1',
'file_name': 'go-0.19.1.vsix',
'checksum':
'7f9dc014245b030d9f562b28f3ea9b1fd6e2708fac996c53ff6a707f8204ec64',
},
'adapters': {
'vscode-go': {
'name': 'delve',
'command': [
'node',
'${gadgetDir}/vscode-go/dist/debugAdapter.js'
],
"configuration": {
"cwd": "${workspaceRoot}",
}
},
},
},
'vscode-php-debug': {
'language': 'php',
'enabled': False,
'download': {
'url':
'https://github.com/xdebug/vscode-php-debug/releases/download/'
'${version}/${file_name}',
},
'all': {
'version': 'v1.17.0',
'file_name': 'php-debug-1.17.0.vsix',
'checksum':
'd0fff272503414b6696cc737bc2e18e060fdd5e5dc4bcaf38ae7373afd8d8bc9',
},
'adapters': {
'vscode-php-debug': {
'name': "php-debug",
'command': [
'node',
"${gadgetDir}/vscode-php-debug/out/phpDebug.js",
]
}
}
},
'vscode-node-debug2': {
'language': 'node',
'enabled': False,
'repo': {
'url': 'https://github.com/microsoft/vscode-node-debug2',
'ref': 'v1.42.5'
},
'do': lambda name, root, gadget: installer.InstallNodeDebug( name,
root,
gadget ),
'adapters': {
'vscode-node': {
'name': 'node2',
'type': 'node2',
'command': [
'node',
'${gadgetDir}/vscode-node-debug2/out/src/nodeDebug.js'
]
},
},
},
'debugger-for-chrome': {
'language': 'chrome',
'enabled': False,
'download': {
'url': 'https://marketplace.visualstudio.com/_apis/public/gallery/'
'publishers/msjsdiag/vsextensions/'
'debugger-for-chrome/${version}/vspackage',
'target': 'msjsdiag.debugger-for-chrome-4.12.10.vsix.gz',
'format': 'zip.gz',
},
'all': {
'version': '4.12.10',
'file_name': 'msjsdiag.debugger-for-chrome-4.12.10.vsix',
'checksum':
''
},
'adapters': {
'chrome': {
'name': 'debugger-for-chrome',
'type': 'chrome',
'command': [
'node',
'${gadgetDir}/debugger-for-chrome/out/src/chromeDebug.js'
],
},
},
},
'CodeLLDB': {
'language': 'rust',
'enabled': True,
'download': {
'url': 'https://github.com/vadimcn/vscode-lldb/releases/download/'
'${version}/${file_name}',
},
'all': {
'version': 'v1.6.6',
},
'macos': {
'file_name': 'codelldb-aarch64-darwin.vsix',
'checksum':
'5adc3b9139eabdafd825bd5efc55df4424a203fb2b6087b425cd434956e7ec58',
'make_executable': [
'adapter/codelldb',
'lldb/bin/debugserver',
'lldb/bin/lldb',
'lldb/bin/lldb-argdumper',
],
},
'linux': {
'file_name': 'codelldb-x86_64-linux.vsix',
'checksum':
'eda2cd9b3089dcc0524c273e91ffb5875fe08c930bf643739a2cd1846e1f98d6',
'make_executable': [
'adapter/codelldb',
'lldb/bin/lldb',
'lldb/bin/lldb-server',
'lldb/bin/lldb-argdumper',
],
},
'windows': {
'file_name': 'codelldb-x86_64-windows.vsix',
'checksum':
'8ddebe8381a3d22dc3d95139c3797fda06b5cc34aadf300e13b1c516b9da95fe',
'make_executable': []
},
'adapters': {
'CodeLLDB': {
'name': 'CodeLLDB',
'type': 'CodeLLDB',
"command": [
"${gadgetDir}/CodeLLDB/adapter/codelldb",
"--port", "${unusedLocalPort}"
],
"port": "${unusedLocalPort}",
"configuration": {
"type": "lldb",
"name": "lldb",
"cargo": {},
"args": [],
"cwd": "${workspaceRoot}",
"env": {},
"terminal": "integrated",
}
},
},
},
'local-lua-debugger-vscode': {
'language': 'lua',
'enabled': True,
'repo': {
'url': 'https://github.com/tomblind/local-lua-debugger-vscode.git',
'ref': 'release-${version}'
},
'all': {
'version': '0.2.0',
},
'do': lambda name, root, gadget: installer.InstallLuaLocal( name,
root,
gadget ),
'adapters': {
'lua-local': {
'command': [
'node',
'${gadgetDir}/local-lua-debugger-vscode/extension/debugAdapter.js'
],
'name': 'lua-local',
'configuration': {
'interpreter': 'lua',
'extensionPath': '${gadgetDir}/local-lua-debugger-vscode'
}
}
},
},
}

View file

@ -26,40 +26,10 @@ def GetOS():
return 'linux' return 'linux'
def mkdirs( p ): def GetGadgetDir( vimspector_base, OS ):
try: return os.path.join( os.path.abspath( vimspector_base ), 'gadgets', OS )
os.makedirs( p )
except FileExistsError:
pass
def MakeInstallDirs( vimspector_base ):
mkdirs( GetGadgetConfigDir( vimspector_base ) )
mkdirs( GetConfigDirForFiletype( vimspector_base, '_all' ) )
def GetGadgetDir( vimspector_base ):
return os.path.join( os.path.abspath( vimspector_base ), 'gadgets', GetOS() )
def GetManifestFile( vimspector_base ):
return os.path.join( GetGadgetDir( vimspector_base ),
'.gadgets.manifest.json' )
def GetGadgetConfigFile( vimspector_base ): def GetGadgetConfigFile( vimspector_base ):
return os.path.join( GetGadgetDir( vimspector_base ), '.gadgets.json' ) return os.path.join( GetGadgetDir( vimspector_base, GetOS() ),
'.gadgets.json' )
def GetGadgetConfigDir( vimspector_base ):
return os.path.join( GetGadgetDir( vimspector_base ), '.gadgets.d' )
def GetConfigDirForFiletype( vimspector_base, filetype ):
if not filetype:
filetype = 'default'
return os.path.join( os.path.abspath( vimspector_base ),
'configurations',
GetOS(),
filetype )

View file

@ -1,754 +0,0 @@
#!/usr/bin/env python3
# vimspector - A multi-language debugging system for Vim
# Copyright 2019 Ben Jackson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from urllib import request
import contextlib
import functools
import gzip
import hashlib
import io
import os
import shutil
import ssl
import string
import subprocess
import sys
import tarfile
import time
import traceback
import zipfile
import json
from vimspector import install, gadgets
OUTPUT_VIEW = None
class Options:
vimspector_base = None
no_check_certificate = False
quiet = False
options = Options()
def Configure( **kwargs ):
for k, v in kwargs.items():
setattr( options, k, v )
def Print( *args, **kwargs ):
if not options.quiet:
print( *args, **kwargs )
class MissingExecutable( Exception ):
pass
def GetPATHAsList():
paths = os.environ[ 'PATH' ].split( os.pathsep )
if install.GetOS() == 'windows':
paths.insert( 0, os.getcwd() )
return paths
def FindExecutable( executable: str, paths=None ):
if not paths:
paths = GetPATHAsList()
if install.GetOS() == 'windows':
extensions = [ '.exe', '.bat', '.cmd' ]
else:
extensions = [ '' ]
for extension in extensions:
if executable.endswith( extension ):
candidate = executable
else:
candidate = executable + extension
for path in paths:
filename = os.path.abspath( os.path.join( path, candidate ) )
if not os.path.isfile( filename ):
continue
if not os.access( filename, os.F_OK | os.X_OK ):
continue
return filename
raise MissingExecutable( f"Unable to find executable { executable } in path" )
def CheckCall( cmd, *args, **kwargs ):
cmd[ 0 ] = FindExecutable( cmd[ 0 ] )
if options.quiet:
try:
subprocess.check_output( cmd, *args, stderr=subprocess.STDOUT, **kwargs )
except subprocess.CalledProcessError as e:
print( e.output.decode( 'utf-8' ) )
raise
else:
subprocess.check_call( cmd, *args, **kwargs )
def PathToAnyWorkingPython3():
# We can't rely on sys.executable because it's usually 'vim' (fixme, not with
# neovim?)
paths = GetPATHAsList()
if install.GetOS() == 'windows':
candidates = [ os.path.join( sys.exec_prefix, 'python.exe' ),
'python.exe' ]
else:
candidates = [ os.path.join( sys.exec_prefix, 'bin', 'python3' ),
'python3',
'python' ]
for candidate in candidates:
try:
return FindExecutable( candidate, paths=paths )
except MissingExecutable:
pass
raise RuntimeError( "Unable to find a working python3" )
def RunInstaller( api_prefix, leave_open, *args, **kwargs ):
from vimspector import utils, output, settings
import vim
if not args:
args = settings.List( 'install_gadgets' )
if not args:
return
args = GadgetListToInstallerArgs( *args )
vimspector_home = utils.GetVimValue( vim.vars, 'vimspector_home' )
vimspector_base_dir = utils.GetVimspectorBase()
global OUTPUT_VIEW
_ResetInstaller()
with utils.RestoreCurrentWindow():
vim.command( f'botright { settings.Int( "bottombar_height" ) }new' )
win = vim.current.window
OUTPUT_VIEW = output.OutputView( win, api_prefix )
cmd = [
PathToAnyWorkingPython3(),
'-u',
os.path.join( vimspector_home, 'install_gadget.py' ),
'--quiet',
'--update-gadget-config',
]
if not vimspector_base_dir == vimspector_home:
cmd.extend( [ '--basedir', vimspector_base_dir ] )
cmd.extend( args )
def handler( exit_code ):
if exit_code == 0:
if not leave_open:
_ResetInstaller()
utils.UserMessage( "Vimspector gadget installation complete!" )
vim.command( 'silent doautocmd User VimspectorInstallSuccess' )
if 'then' in kwargs:
kwargs[ 'then' ]()
else:
utils.UserMessage( 'Vimspector gadget installation reported errors',
error = True )
vim.command( 'silent doautocmd User VimspectorInstallFailed' )
OUTPUT_VIEW.RunJobWithOutput( 'Installer',
cmd,
completion_handler = handler,
syntax = 'vimspector-installer' )
OUTPUT_VIEW.ShowOutput( 'Installer' )
def RunUpdate( api_prefix, leave_open, *args ):
from vimspector import utils, settings
Configure( vimspector_base = utils.GetVimspectorBase() )
insatller_args = list( args )
insatller_args.extend( settings.List( 'install_gadgets' ) )
current_adapters = ReadAdapters( read_existing = True )
for adapter_name in current_adapters.keys():
insatller_args.extend( FindGadgetForAdapter( adapter_name ) )
if insatller_args:
insatller_args.append( '--upgrade' )
RunInstaller( api_prefix, leave_open, *insatller_args )
def _ResetInstaller():
global OUTPUT_VIEW
if OUTPUT_VIEW:
OUTPUT_VIEW.Reset()
OUTPUT_VIEW = None
def Abort():
_ResetInstaller()
from vimspector import utils
utils.UserMessage( 'Vimspector installation aborted',
persist = True,
error = True )
def GadgetListToInstallerArgs( *gadget_list ):
installer_args = []
for name in gadget_list:
if name.startswith( '-' ):
installer_args.append( name )
continue
try:
gadget = gadgets.GADGETS[ name ]
except KeyError:
continue
lang = gadget[ "language" ]
if isinstance( lang, list ):
lang = lang[ 0 ]
if not gadget.get( 'enabled', True ):
installer_args.append( f'--force-enable-{lang}' )
else:
installer_args.append( f'--enable-{lang}' )
return installer_args
def FindGadgetForAdapter( adapter_name ):
candidates = []
for name, gadget in gadgets.GADGETS.items():
v = {}
v.update( gadget.get( 'all', {} ) )
v.update( gadget.get( install.GetOS(), {} ) )
adapters = {}
adapters.update( v.get( 'adapters', {} ) )
adapters.update( gadget.get( 'adapters', {} ) )
if adapter_name in adapters:
candidates.append( name )
return candidates
class Manifest:
manifest: dict
def __init__( self ):
self.manifest = {}
self.Read()
def Read( self ):
try:
with open( install.GetManifestFile( options.vimspector_base ), 'r' ) as f:
self.manifest = json.load( f )
except OSError:
pass
def Write( self ):
with open( install.GetManifestFile( options.vimspector_base ), 'w' ) as f:
json.dump( self.manifest, f )
def Clear( self, name: str ):
try:
del self.manifest[ name ]
except KeyError:
pass
def Update( self, name: str, gadget_spec: dict ):
self.manifest[ name ] = gadget_spec
def RequiresUpdate( self, name: str, gadget_spec: dict ):
try:
current_spec = self.manifest[ name ]
except KeyError:
# It's new.
return True
# If anything changed in the spec, update
if not current_spec == gadget_spec:
return True
# Always update if the version string is 'master'. Probably a git repo
# that pulls master (which tbh we shouldn't have)
if current_spec.get( 'version' ) in ( 'master', '' ):
return True
if current_spec.get( 'repo', {} ).get( 'ref' ) == 'master':
return True
return False
def ReadAdapters( read_existing = True ):
all_adapters = {}
if read_existing:
try:
with open( install.GetGadgetConfigFile( options.vimspector_base ),
'r' ) as f:
all_adapters = json.load( f ).get( 'adapters', {} )
except OSError:
pass
# Include "built-in" adapter for multi-session mode
all_adapters.update( {
'multi-session': {
'port': '${port}',
'host': '${host}'
},
} )
return all_adapters
def WriteAdapters( all_adapters, to_file=None ):
adapter_config = json.dumps ( { 'adapters': all_adapters },
indent=2,
sort_keys=True )
if to_file:
to_file.write( adapter_config )
else:
with open( install.GetGadgetConfigFile( options.vimspector_base ),
'w' ) as f:
f.write( adapter_config )
def InstallGeneric( name, root, gadget ):
extension_path = gadget.get( 'extension_path', 'extension' )
extension = os.path.join( root, extension_path )
for f in gadget.get( 'make_executable', [] ):
MakeExecutable( os.path.join( extension, f ) )
MakeExtensionSymlink( name, root, extension_path )
def InstallCppTools( name, root, gadget ):
extension = os.path.join( root, 'extension' )
# It's hilarious, but the execute bits aren't set in the vsix. So they
# actually have javascript code which does this. It's just a horrible horrible
# hack that really is not funny.
MakeExecutable(
os.path.join( extension, 'debugAdapters', 'bin', 'OpenDebugAD7' ) )
with open( os.path.join( extension, 'package.json' ) ) as f:
package = json.load( f )
runtime_dependencies = package[ 'runtimeDependencies' ]
for dependency in runtime_dependencies:
for binary in dependency.get( 'binaries' ):
file_path = os.path.abspath( os.path.join( extension, binary ) )
if os.path.exists( file_path ):
MakeExecutable( os.path.join( extension, binary ) )
MakeExtensionSymlink( name, root )
def InstallBashDebug( name, root, gadget ):
MakeExecutable( os.path.join( root, 'extension', 'bashdb_dir', 'bashdb' ) )
MakeExtensionSymlink( name, root )
def InstallDebugpy( name, root, gadget ):
wd = os.getcwd()
root = os.path.join( root, 'debugpy-{}'.format( gadget[ 'version' ] ) )
os.chdir( root )
try:
CheckCall( [ sys.executable, 'setup.py', 'build' ] )
finally:
os.chdir( wd )
MakeSymlink( name, root )
def InstallTclProDebug( name, root, gadget ):
configure = [ 'sh', './configure' ]
if install.GetOS() == 'macos':
# Apple removed the headers from system frameworks because they are
# determined to make life difficult. And the TCL configure scripts are super
# old so don't know about this. So we do their job for them and try and find
# a tclConfig.sh.
#
# NOTE however that in Apple's infinite wisdom, installing the "headers" in
# the other location is actually broken because the paths in the
# tclConfig.sh are pointing at the _old_ location. You actually do have to
# run the package installation which puts the headers back in order to work.
# This is why the below list is does not contain stuff from
# /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform
# '/Applications/Xcode.app/Contents/Developer/Platforms'
# '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System'
# '/Library/Frameworks/Tcl.framework',
# '/Applications/Xcode.app/Contents/Developer/Platforms'
# '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System'
# '/Library/Frameworks/Tcl.framework/Versions'
# '/Current',
for p in [ '/usr/local/opt/tcl-tk/lib' ]:
if os.path.exists( os.path.join( p, 'tclConfig.sh' ) ):
configure.append( '--with-tcl=' + p )
break
with CurrentWorkingDir( os.path.join( root, 'lib', 'tclparser' ) ):
CheckCall( configure )
CheckCall( [ 'make' ] )
MakeSymlink( name, root )
def InstallNodeDebug( name, root, gadget ):
with CurrentWorkingDir( root ):
CheckCall( [ 'npm', 'install' ] )
CheckCall( [ 'npm', 'run', 'build' ] )
MakeSymlink( name, root )
def InstallLuaLocal( name, root, gadget ):
with CurrentWorkingDir( root ):
CheckCall( [ 'npm', 'install' ] )
CheckCall( [ 'npm', 'run', 'build' ] )
MakeSymlink( name, root )
def InstallGagdet( name: str,
gadget: dict,
manifest: Manifest,
succeeded: list,
failed: list,
all_adapters: dict ):
try:
# Spec is an os-specific definition of the gadget
spec = {}
spec.update( gadget.get( 'all', {} ) )
spec.update( gadget.get( install.GetOS(), {} ) )
def save_adapters():
# allow per-os adapter overrides. v already did that for us...
all_adapters.update( spec.get( 'adapters', {} ) )
# add any other "all" adapters
all_adapters.update( gadget.get( 'adapters', {} ) )
if 'download' in gadget:
if 'file_name' not in spec:
raise RuntimeError( "Unsupported OS {} for gadget {}".format(
install.GetOS(),
name ) )
print( f"Installing {name}@{spec[ 'version' ]}..." )
spec[ 'download' ] = gadget[ 'download' ]
if not manifest.RequiresUpdate( name, spec ):
save_adapters()
print( " - Skip - up to date" )
return
destination = os.path.join(
install.GetGadgetDir( options.vimspector_base ),
'download',
name,
spec[ 'version' ] )
url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( spec )
file_path = DownloadFileTo(
url,
destination,
file_name = gadget[ 'download' ].get( 'target' ),
checksum = spec.get( 'checksum' ),
check_certificate = not options.no_check_certificate )
root = os.path.join( destination, 'root' )
ExtractZipTo(
file_path,
root,
format = gadget[ 'download' ].get( 'format', 'zip' ) )
elif 'repo' in gadget:
url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( spec )
ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( spec )
print( f"Installing {name}@{ref}..." )
spec[ 'repo' ] = gadget[ 'repo' ]
if not manifest.RequiresUpdate( name, spec ):
save_adapters()
print( " - Skip - up to date" )
return
destination = os.path.join(
install.GetGadgetDir( options.vimspector_base ),
'download',
name )
CloneRepoTo( url, ref, destination )
root = destination
if 'do' in gadget:
gadget[ 'do' ]( name, root, spec )
else:
InstallGeneric( name, root, spec )
save_adapters()
manifest.Update( name, spec )
succeeded.append( name )
print( f" - Done installing {name}" )
except Exception as e:
if not options.quiet:
traceback.print_exc()
failed.append( name )
print( f" - FAILED installing {name}: {e}".format( name, e ) )
@contextlib.contextmanager
def CurrentWorkingDir( d ):
cur_d = os.getcwd()
try:
os.chdir( d )
yield
finally:
os.chdir( cur_d )
def MakeExecutable( file_path ):
# TODO: import stat and use them by _just_ adding the X bit.
Print( 'Making executable: {}'.format( file_path ) )
os.chmod( file_path, 0o755 )
def WithRetry( f ):
retries = 5
timeout = 1 # seconds
@functools.wraps( f )
def wrapper( *args, **kwargs ):
thrown = None
for _ in range( retries ):
try:
return f( *args, **kwargs )
except Exception as e:
thrown = e
Print( "Failed - {}, will retry in {} seconds".format( e, timeout ) )
time.sleep( timeout )
raise thrown
return wrapper
@WithRetry
def UrlOpen( *args, **kwargs ):
return request.urlopen( *args, **kwargs )
def DownloadFileTo( url,
destination,
file_name = None,
checksum = None,
check_certificate = True ):
if not file_name:
file_name = url.split( '/' )[ -1 ]
file_path = os.path.abspath( os.path.join( destination, file_name ) )
if not os.path.isdir( destination ):
os.makedirs( destination )
if os.path.exists( file_path ):
if checksum:
if ValidateCheckSumSHA256( file_path, checksum ):
Print( "Checksum matches for {}, using it".format( file_path ) )
return file_path
else:
Print( "Checksum doesn't match for {}, removing it".format(
file_path ) )
Print( "Removing existing {}".format( file_path ) )
os.remove( file_path )
r = request.Request( url, headers = { 'User-Agent': 'Vimspector' } )
Print( "Downloading {} to {}/{}".format( url, destination, file_name ) )
if not check_certificate:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
kwargs = { "context": context }
else:
kwargs = {}
with contextlib.closing( UrlOpen( r, **kwargs ) ) as u:
with open( file_path, 'wb' ) as f:
f.write( u.read() )
if checksum:
if not ValidateCheckSumSHA256( file_path, checksum ):
raise RuntimeError(
'Checksum for {} ({}) does not match expected {}'.format(
file_path,
GetChecksumSHA254( file_path ),
checksum ) )
else:
Print( "Checksum for {}: {}".format( file_path,
GetChecksumSHA254( file_path ) ) )
return file_path
def GetChecksumSHA254( file_path ):
with open( file_path, 'rb' ) as existing_file:
return hashlib.sha256( existing_file.read() ).hexdigest()
def ValidateCheckSumSHA256( file_path, checksum ):
existing_sha256 = GetChecksumSHA254( file_path )
return existing_sha256 == checksum
def RemoveIfExists( destination ):
try:
os.remove( destination )
Print( "Removed file {}".format( destination ) )
return
except OSError:
pass
N = 1
def BackupDir():
return "{}.{}".format( destination, N )
while os.path.isdir( BackupDir() ):
Print( "Removing old dir {}".format( BackupDir() ) )
try:
shutil.rmtree( BackupDir() )
Print ( "OK, removed it" )
break
except OSError as e:
Print ( f"FAILED to remove {BackupDir()}: {e}" )
N = N + 1
if os.path.exists( destination ):
Print( "Removing dir {}".format( destination ) )
try:
shutil.rmtree( destination )
except OSError:
Print( "FAILED, moving {} to dir {}".format( destination, BackupDir() ) )
os.rename( destination, BackupDir() )
# Python's ZipFile module strips execute bits from files, for no good reason
# other than crappy code. Let's do it's job for it.
class ModePreservingZipFile( zipfile.ZipFile ):
def extract( self, member, path = None, pwd = None ):
if not isinstance( member, zipfile.ZipInfo ):
member = self.getinfo( member )
if path is None:
path = os.getcwd()
ret_val = self._extract_member( member, path, pwd )
attr = member.external_attr >> 16
os.chmod( ret_val, attr )
return ret_val
def ExtractZipTo( file_path, destination, format ):
Print( "Extracting {} to {}".format( file_path, destination ) )
RemoveIfExists( destination )
if format == 'zip':
with ModePreservingZipFile( file_path ) as f:
f.extractall( path = destination )
elif format == 'zip.gz':
with gzip.open( file_path, 'rb' ) as f:
file_contents = f.read()
with ModePreservingZipFile( io.BytesIO( file_contents ) ) as f:
f.extractall( path = destination )
elif format == 'tar':
try:
with tarfile.open( file_path ) as f:
f.extractall( path = destination )
except Exception:
# There seems to a bug in python's tarfile that means it can't read some
# windows-generated tar files
os.makedirs( destination )
with CurrentWorkingDir( destination ):
CheckCall( [ 'tar', 'zxvf', file_path ] )
def MakeExtensionSymlink( name, root, extension_path = 'extension' ):
MakeSymlink( name, os.path.join( root, extension_path ) ),
def MakeSymlink( link, pointing_to, in_folder = None ):
if not in_folder:
in_folder = install.GetGadgetDir( options.vimspector_base )
RemoveIfExists( os.path.join( in_folder, link ) )
in_folder = os.path.abspath( in_folder )
pointing_to_relative = os.path.relpath( os.path.abspath( pointing_to ),
in_folder )
link_path = os.path.join( in_folder, link )
if install.GetOS() == 'windows':
# While symlinks do exist on Windows, they require elevated privileges, so
# let's use a directory junction which is all we need.
link_path = os.path.abspath( link_path )
if os.path.isdir( link_path ):
os.rmdir( link_path )
CheckCall( [ 'cmd.exe', '/c', 'mklink', '/J', link_path, pointing_to ] )
else:
os.symlink( pointing_to_relative, link_path )
def CloneRepoTo( url, ref, destination ):
RemoveIfExists( destination )
git_in_repo = [ 'git', '-C', destination ]
CheckCall( [ 'git', 'clone', url, destination ] )
CheckCall( git_in_repo + [ 'checkout', ref ] )
CheckCall( git_in_repo + [ 'submodule', 'sync', '--recursive' ] )
CheckCall( git_in_repo + [ 'submodule', 'update', '--init', '--recursive' ] )
def AbortIfSUperUser( force_sudo ):
# TODO: We should probably check the effective uid too
is_su = False
if 'SUDO_COMMAND' in os.environ:
is_su = True
if is_su:
if force_sudo:
print( "*** RUNNING AS SUPER USER DUE TO force_sudo! "
" All bets are off. ***" )
else:
sys.exit( "This script should *not* be run as super user. Aborting." )

View file

@ -13,11 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from vimspector import utils, install from vimspector import utils
import vim import vim
import json import json
import typing
class TabBuffer( object ): class TabBuffer( object ):
@ -26,15 +25,13 @@ class TabBuffer( object ):
self.index = index self.index = index
self.flag = False self.flag = False
self.is_job = False self.is_job = False
self.syntax = None
BUFFER_MAP = { BUFFER_MAP = {
'console': 'Console', 'console': 'Console',
'stdout': 'Console', 'stdout': 'Console',
'output': 'Console',
'stderr': 'stderr', 'stderr': 'stderr',
'telemetry': None, 'telemetry': 'Telemetry',
} }
@ -42,34 +39,23 @@ def CategoryToBuffer( category ):
return BUFFER_MAP.get( category, category ) return BUFFER_MAP.get( category, category )
VIEWS = set()
def ShowOutputInWindow( win_id, category ):
for view in VIEWS:
if view._window.valid and utils.WindowID( view._window ) == win_id:
view.ShowOutput( category )
return
raise ValueError( f'Unable to find output object for win id {win_id}!' )
class OutputView( object ): class OutputView( object ):
"""Container for a 'tabbed' window of buffers that can be used to display def __init__( self, connection, window ):
files or the output of commands."""
_buffers: typing.Dict[ str, TabBuffer ]
def __init__( self, window, api_prefix ):
self._window = window self._window = window
self._connection = connection
self._buffers = {} self._buffers = {}
self._api_prefix = api_prefix
VIEWS.add( self )
def Print( self, category, text: typing.Union[ str, list ] ): for b in set( BUFFER_MAP.values() ):
if not isinstance( text, list ): self._CreateBuffer( b )
text = text.splitlines()
self._Print( category, text ) self._CreateBuffer(
'Vimspector',
file_name = vim.eval( 'expand( "~/.vimspector.log" )' ) )
self._ShowOutput( 'Console' )
def Print( self, categroy, text ):
self._Print( 'server', text.splitlines() )
def OnOutput( self, event ): def OnOutput( self, event ):
category = CategoryToBuffer( event.get( 'category' ) or 'output' ) category = CategoryToBuffer( event.get( 'category' ) or 'output' )
@ -81,10 +67,6 @@ class OutputView( object ):
self._Print( category, text_lines ) self._Print( category, text_lines )
def _Print( self, category, text_lines ): def _Print( self, category, text_lines ):
if category is None:
# This category is supressed
return
if category not in self._buffers: if category not in self._buffers:
self._CreateBuffer( category ) self._CreateBuffer( category )
@ -96,187 +78,9 @@ class OutputView( object ):
self._ToggleFlag( category, True ) self._ToggleFlag( category, True )
# Scroll the buffer # Scroll the buffer
if self._window.valid: with utils.RestoreCurrentWindow():
with utils.RestoreCurrentWindow(): with utils.RestoreCurrentBuffer( self._window ):
with utils.RestoreCurrentBuffer( self._window ):
self._ShowOutput( category )
def Reset( self ):
self.Clear()
VIEWS.remove( self )
def Clear( self ):
for category, tab_buffer in self._buffers.items():
self._CleanUpBuffer( category, tab_buffer )
# FIXME: nunmenu the WinBar ?
self._buffers = {}
def ClearCategory( self, category: str ):
if category not in self._buffers:
return
self._CleanUpBuffer( category, self._buffers[ category ] )
def _CleanUpBuffer( self, category: str, tab_buffer: TabBuffer ):
if tab_buffer.is_job:
utils.CleanUpCommand( category, self._api_prefix )
utils.CleanUpHiddenBuffer( tab_buffer.buf )
def WindowIsValid( self ):
return self._window.valid
def UseWindow( self, win ):
assert not self._window.valid
self._window = win
# TODO: Sorting of the WinBar ?
for category, _ in self._buffers.items():
self._RenderWinBar( category )
def _ShowOutput( self, category ):
if not self._window.valid:
return
utils.JumpToWindow( self._window )
vim.current.buffer = self._buffers[ category ].buf
vim.command( 'normal G' )
def ShowOutput( self, category ):
self._ToggleFlag( category, False )
self._ShowOutput( category )
def _ToggleFlag( self, category, flag ):
if self._buffers[ category ].flag != flag:
self._buffers[ category ].flag = flag
if self._window.valid:
with utils.LetCurrentWindow( self._window ):
self._RenderWinBar( category )
def RunJobWithOutput( self, category, cmd, **kwargs ):
self._CreateBuffer( category, cmd = cmd, **kwargs )
def _CreateBuffer( self,
category,
file_name = None,
cmd = None,
completion_handler = None,
syntax = None ):
buf_to_delete = None
if ( not self._buffers
and self._window is not None
and self._window.valid
and not self._window.buffer.name ):
# If there's an empty buffer in the current window that we're not using,
# delete it. We could try and use it, but that complicates the call to
# SetUpCommandBuffer
buf_to_delete = self._window.buffer
if file_name is not None:
assert cmd is None
if install.GetOS() == "windows":
# FIXME: Can't display fiels in windows (yet?)
return
cmd = [ 'tail', '-F', '-n', '+1', '--', file_name ]
if cmd is not None:
out = utils.SetUpCommandBuffer(
cmd,
category,
self._api_prefix,
completion_handler = completion_handler )
self._buffers[ category ] = TabBuffer( out, len( self._buffers ) )
self._buffers[ category ].is_job = True
self._RenderWinBar( category )
else:
if category == 'Console':
name = 'vimspector.Console'
else:
name = 'vimspector.Output:{0}'.format( category )
tab_buffer = TabBuffer( utils.NewEmptyBuffer(), len( self._buffers ) )
self._buffers[ category ] = tab_buffer
if category == 'Console':
utils.SetUpPromptBuffer( tab_buffer.buf,
name,
'> ',
'vimspector#EvaluateConsole',
'vimspector#OmniFuncConsole' )
else:
utils.SetUpHiddenBuffer( tab_buffer.buf, name )
self._RenderWinBar( category )
self._buffers[ category ].syntax = utils.SetSyntax(
self._buffers[ category ].syntax,
syntax,
self._buffers[ category ].buf )
if buf_to_delete:
with utils.RestoreCurrentWindow():
self._ShowOutput( category ) self._ShowOutput( category )
utils.CleanUpHiddenBuffer( buf_to_delete )
def _RenderWinBar( self, category ):
if not utils.UseWinBar():
return
if not self._window.valid:
return
with utils.LetCurrentWindow( self._window ):
tab_buffer = self._buffers[ category ]
try:
if tab_buffer.flag:
vim.command( 'nunmenu WinBar.{}'.format( utils.Escape( category ) ) )
else:
vim.command( 'nunmenu WinBar.{}*'.format( utils.Escape( category ) ) )
except vim.error as e:
# E329 means the menu doesn't exist; ignore that.
if 'E329' not in str( e ):
raise
vim.command(
"nnoremenu <silent> 1.{0} WinBar.{1}{2} "
":call vimspector#ShowOutputInWindow( {3}, '{1}' )<CR>".format(
tab_buffer.index,
utils.Escape( category ),
'*' if tab_buffer.flag else '',
utils.WindowID( self._window ) ) )
def GetCategories( self ):
return list( self._buffers.keys() )
def AddLogFileView( self, file_name = utils.LOG_FILE ):
self._CreateBuffer( 'Vimspector', file_name = file_name )
class DAPOutputView( OutputView ):
"""Specialised OutputView which adds the DAP Console (REPL)"""
def __init__( self, *args ):
super().__init__( *args )
self._connection = None
for b in set( BUFFER_MAP.values() ):
if b is not None:
self._CreateBuffer( b )
self.AddLogFileView()
self._ShowOutput( 'Console' )
def ConnectionUp( self, connection ): def ConnectionUp( self, connection ):
self._connection = connection self._connection = connection
@ -285,30 +89,119 @@ class DAPOutputView( OutputView ):
# Don't clear because output is probably still useful # Don't clear because output is probably still useful
self._connection = None self._connection = None
def Evaluate( self, frame, expression, verbose ): def Reset( self ):
if verbose: self.Clear()
self._Print( 'Console', f"Evaluating: { expression }" )
def Clear( self ):
for category, tab_buffer in self._buffers.items():
if tab_buffer.is_job:
utils.CleanUpCommand( category )
try:
vim.command( 'bdelete! {0}'.format( tab_buffer.buf.number ) )
except vim.error as e:
# FIXME: For now just ignore the "no buffers were deleted" error
if 'E516' not in e:
raise
self._buffers = {}
def _ShowOutput( self, category ):
utils.JumpToWindow( self._window )
vim.command( 'bu {0}'.format( self._buffers[ category ].buf.name ) )
vim.command( 'normal G' )
def ShowOutput( self, category ):
self._ToggleFlag( category, False )
self._ShowOutput( category )
def Evaluate( self, frame, expression ):
if not frame:
self.Print( 'Console', 'There is no current stack frame' )
return
console = self._buffers[ 'Console' ].buf
utils.AppendToBuffer( console, 'Evaluating: ' + expression )
def print_result( message ): def print_result( message ):
utils.AppendToBuffer( console,
'Evaluated: ' + expression )
result = message[ 'body' ][ 'result' ] result = message[ 'body' ][ 'result' ]
if result is None: if result is None:
result = '<no result>' result = 'null'
self._Print( 'Console', result.splitlines() )
def print_failure( reason, msg ): utils.AppendToBuffer( console, ' Result: ' + result )
self._Print( 'Console', reason.splitlines() )
request = { self._connection.DoRequest( print_result, {
'command': 'evaluate', 'command': 'evaluate',
'arguments': { 'arguments': {
'expression': expression, 'expression': expression,
'context': 'repl', 'context': 'repl',
'frameId': frame[ 'id' ],
} }
} } )
if frame: def _ToggleFlag( self, category, flag ):
request[ 'arguments' ][ 'frameId' ] = frame[ 'id' ] if self._buffers[ category ].flag != flag:
self._buffers[ category ].flag = flag
with utils.LetCurrentWindow( self._window ):
self._RenderWinBar( category )
self._connection.DoRequest( print_result,
request, def RunJobWithOutput( self, category, cmd ):
print_failure ) self._CreateBuffer( category, cmd = cmd )
def _CreateBuffer( self, category, file_name = None, cmd = None ):
with utils.LetCurrentWindow( self._window ):
with utils.RestoreCurrentBuffer( self._window ):
if file_name is not None:
assert cmd is None
cmd = [ 'tail', '-F', '-n', '+1', '--', file_name ]
if cmd is not None:
out, err = utils.SetUpCommandBuffer( cmd, category )
self._buffers[ category + '-out' ] = TabBuffer( out,
len( self._buffers ) )
self._buffers[ category + '-out' ].is_job = True
self._buffers[ category + '-err' ] = TabBuffer( err,
len( self._buffers ) )
self._buffers[ category + '-err' ].is_job = False
self._RenderWinBar( category + '-out' )
self._RenderWinBar( category + '-err' )
else:
vim.command( 'enew' )
tab_buffer = TabBuffer( vim.current.buffer, len( self._buffers ) )
self._buffers[ category ] = tab_buffer
if category == 'Console':
utils.SetUpPromptBuffer( tab_buffer.buf,
'vimspector.Console',
'> ',
'vimspector#EvaluateConsole',
hidden=True )
else:
utils.SetUpHiddenBuffer(
tab_buffer.buf,
'vimspector.Output:{0}'.format( category ) )
self._RenderWinBar( category )
def _RenderWinBar( self, category ):
tab_buffer = self._buffers[ category ]
try:
if tab_buffer.flag:
vim.command( 'nunmenu WinBar.{}'.format( utils.Escape( category ) ) )
else:
vim.command( 'nunmenu WinBar.{}*'.format( utils.Escape( category ) ) )
except vim.error as e:
# E329 means the menu doesn't exist; ignore that.
if 'E329' not in str( e ):
raise
vim.command( "nnoremenu 1.{0} WinBar.{1}{2} "
":call vimspector#ShowOutput( '{1}' )<CR>".format(
tab_buffer.index,
utils.Escape( category ),
'*' if tab_buffer.flag else '' ) )

View file

@ -1,138 +0,0 @@
# vimspector - A multi-language debugging system for Vim
# Copyright 2020 Ben Jackson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import vim
import builtins
from vimspector import utils
DEFAULTS = {
# UI
'ui_mode': 'auto',
'bottombar_height': 10,
# For ui_mode = 'horizontal':
'sidebar_width': 50,
'code_minwidth': 82,
'terminal_maxwidth': 80,
'terminal_minwidth': 10,
# For ui_mode = 'vertical':
'topbar_height': 15,
'code_minheight': 20,
'terminal_maxheight': 15,
'terminal_minheight': 5,
# Signs
'sign_priority': {
'vimspectorPC': 200,
'vimspectorPCBP': 200,
'vimspectorBP': 9,
'vimspectorBPCond': 9,
'vimspectorBPDisabled': 9,
'vimspectorCurrentThread': 200,
'vimspectorCurrentFrame': 200,
},
# Installer
'install_gadgets': [],
# Mappings
'mappings': {
'variables': {
'expand_collapse': [ '<CR>', '<2-LeftMouse>' ],
'delete': [ '<Del>' ],
'set_value': [ '<C-CR>', '<leader><CR>' ]
},
'stack_trace': {
'expand_or_jump': [ '<CR>', '<2-LeftMouse>' ],
'focus_thread': [ '<leader><CR>' ],
}
},
# Custom
'java_hotcodereplace_mode': 'ask',
}
def Get( option: str, default=None, cls=str ):
return cls( utils.GetVimValue( vim.vars,
f'vimspector_{ option }',
DEFAULTS.get( option, cls() ) ) )
def Int( option: str ):
return Get( option, cls=builtins.int )
def List( option: str ):
return utils.GetVimList( vim.vars,
f'vimspector_{ option }',
DEFAULTS.get( option, [] ) )
# FIXME:
# In Vim, we must use vim.Dictionary because this sorts out the annoying
# keys-as-bytes discrepancy, making things awkward. That said, we still have the
# problem where the _values_ are potentially bytes. It's very tempting to just
# make a deep copy to antive str type here.
# Of course in neovim, it's totally different and you actually get a dict type
# back (though for once, neovim is making life somewhat easier for a change).
DICT_TYPE = dict
if hasattr( vim, 'Dictionary' ):
DICT_TYPE = vim.Dictionary
def Dict( option ):
return _UpdateDict( DICT_TYPE( DEFAULTS.get( option, {} ) ),
vim.vars.get( f'vimspector_{ option }', DICT_TYPE() ) )
def _UpdateDict( target, override ):
"""Apply the updates in |override| to the dict |target|. This is like
dict.update, but recursive. i.e. if the existing element is a dict, then
override elements of the sub-dict rather than wholesale replacing.
e.g.
UpdateDict(
{
'outer': { 'inner': { 'key': 'oldValue', 'existingKey': True } }
},
{
'outer': { 'inner': { 'key': 'newValue' } },
'newKey': { 'newDict': True },
}
)
yields:
{
'outer': {
'inner': {
'key': 'newValue',
'existingKey': True
}
},
'newKey': { newDict: True }
}
"""
for key, value in override.items():
current_value = target.get( key )
if not isinstance( current_value, DICT_TYPE ):
target[ key ] = value
elif isinstance( value, DICT_TYPE ):
target[ key ] = _UpdateDict( current_value, value )
else:
target[ key ] = value
return target

View file

@ -1,46 +0,0 @@
import vim
from vimspector import settings, utils
def SignDefined( name ):
if utils.Exists( "*sign_getdefined" ):
return int(
vim.eval( f"len( sign_getdefined( '{ utils.Escape( name ) }' ) )" )
)
return False
def DefineSign( name, text, double_text, texthl, col = 'right', **kwargs ):
if utils.GetVimValue( vim.options, 'ambiwidth', '' ) == 'double':
text = double_text
if col == 'right':
if int( utils.Call( 'strdisplaywidth', text ) ) < 2:
text = ' ' + text
text = text.replace( ' ', r'\ ' )
cmd = f'sign define { name } text={ text } texthl={ texthl }'
for key, value in kwargs.items():
cmd += f' { key }={ value }'
vim.command( cmd )
def PlaceSign( sign_id, group, name, file_name, line ):
priority = settings.Dict( 'sign_priority' )[ name ]
cmd = ( f'sign place { sign_id } '
f'group={ group } '
f'name={ name } '
f'priority={ priority } '
f'line={ line } '
f'file={ file_name }' )
vim.command( cmd )
def UnplaceSign( sign_id, group ):
vim.command( f'sign unplace { sign_id } group={ group }' )

View file

@ -16,168 +16,59 @@
import vim import vim
import os import os
import logging import logging
import typing
from vimspector import utils, signs, settings from vimspector import utils
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: def __init__( self, session, connection, buf ):
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__ ) self._logger = logging.getLogger( __name__ )
utils.SetUpLogging( self._logger ) utils.SetUpLogging( self._logger )
self._buf = win.buffer self._buf = buf
self._session = session self._session = session
self._connection = None self._connection = connection
self._current_thread = None self._currentThread = None
self._current_frame = None self._currentFrame = None
self._current_syntax = ""
self._threads = [] self._threads = []
self._sources = {} self._sources = {}
self._scratch_buffers = []
# FIXME: This ID is by group, so should be module scope utils.SetUpScratchBuffer( self._buf, 'vimspector.StackTrace' )
self._current_thread_sign_id = 0 # 1 when used vim.current.buffer = self._buf
self._current_frame_sign_id = 0 # 2 when used vim.command( 'nnoremap <buffer> <CR> :call vimspector#GoToFrame()<CR>' )
utils.SetUpHiddenBuffer( self._buf, 'vimspector.StackTrace' )
utils.SetUpUIWindow( win )
mappings = settings.Dict( 'mappings' )[ 'stack_trace' ]
with utils.LetCurrentWindow( win ):
for mapping in utils.GetVimList( mappings, 'expand_or_jump' ):
vim.command( f'nnoremap <silent> <buffer> { mapping } '
':<C-U>call vimspector#GoToFrame()<CR>' )
for mapping in utils.GetVimList( mappings, 'focus_thread' ):
vim.command( f'nnoremap <silent> <buffer> { mapping } '
':<C-U>call vimspector#SetCurrentThread()<CR>' )
if utils.UseWinBar():
vim.command( 'nnoremenu <silent> 1.1 WinBar.Pause/Continue '
':call vimspector#PauseContinueThread()<CR>' )
vim.command( 'nnoremenu <silent> 1.2 WinBar.Expand/Collapse '
':call vimspector#GoToFrame()<CR>' )
vim.command( 'nnoremenu <silent> 1.3 WinBar.Focus '
':call vimspector#SetCurrentThread()<CR>' )
win.options[ 'cursorline' ] = False
win.options[ 'signcolumn' ] = 'auto'
if not signs.SignDefined( 'vimspectorCurrentThread' ):
signs.DefineSign( 'vimspectorCurrentThread',
text = '',
double_text = '',
texthl = 'MatchParen',
linehl = 'CursorLine' )
if not signs.SignDefined( 'vimspectorCurrentFrame' ):
signs.DefineSign( 'vimspectorCurrentFrame',
text = '',
double_text = '',
texthl = 'Special',
linehl = 'CursorLine' )
self._line_to_frame = {} self._line_to_frame = {}
self._line_to_thread = {} self._line_to_thread = {}
self._requesting_threads = StackTraceView.ThreadRequestState.NO # TODO: We really need a proper state model
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 ):
return self._current_thread return self._currentThread
def GetCurrentFrame( self ): def GetCurrentFrame( self ):
return self._current_frame return self._currentFrame
def Clear( self ): def Clear( self ):
self._current_frame = None self._currentFrame = None
self._current_thread = None self._currentThread = None
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._current_thread_sign_id:
signs.UnplaceSign( self._current_thread_sign_id, 'VimspectorStackTrace' )
self._current_thread_sign_id = 0
if self._current_frame_sign_id:
signs.UnplaceSign( self._current_frame_sign_id, 'VimspectorStackTrace' )
self._current_frame_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()
@ -185,142 +76,69 @@ class StackTraceView( object ):
def Reset( self ): def Reset( self ):
self.Clear() self.Clear()
utils.CleanUpHiddenBuffer( self._buf ) # TODO: delete the buffer ?
for b in self._scratch_buffers:
utils.CleanUpHiddenBuffer( b )
self._scratch_buffers = []
self._buf = None
def LoadThreads( self, def LoadThreads( self, infer_current_frame ):
infer_current_frame, pending_request = False
reason = '', if self._requesting_threads:
stopEvent = None ): pending_request = True
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 ):
requesting = False 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 )
requesting = True
self._requesting_threads = StackTraceView.ThreadRequestState.NO if not message[ 'body' ][ 'threads' ]:
self._pending_thread_request = None 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 )
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()
if stopEvent is not None: for thread in message[ 'body' ][ 'threads' ]:
stoppedThreadId = stopEvent.get( 'threadId' )
allThreadsStopped = stopEvent.get( 'allThreadsStopped', 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 the threads were requested due to a stopped event, update any if infer_current_frame and thread[ 'id' ] == self._currentThread:
# stopped thread state. Note we have to do this here (rather than in the self._LoadStackTrace( thread, True )
# stopped event handler) because we must apply this event to any new elif infer_current_frame and self._currentThread is None:
# threads that are received here. self._currentThread = thread[ 'id' ]
if stopEvent: self._LoadStackTrace( thread, True )
if allThreadsStopped:
thread.Paused( stopEvent )
elif stoppedThreadId is not None and thread.id == stoppedThreadId:
thread.Paused( stopEvent )
# If this is a stopped event, load the stack trace for the "current" self._DrawThreads()
# thread. Don't do this on other thrads requests because some servers
# just break when that happens.
#
# Don't do this if we're also satisfying a cached request already (we'll
# do it then)
if infer_current_frame and not requesting:
if thread.id == self._current_thread:
if thread.CanExpand():
self._LoadStackTrace( thread, True, reason )
requesting = True
elif self._current_thread is None:
self._current_thread = thread.id
if thread.CanExpand():
self._LoadStackTrace( thread, True, reason )
requesting = True
if not requesting: self._requesting_threads = True
self._DrawThreads()
def failure_handler( reason, msg ):
# Make sure we request them again if the request fails
self._requesting_threads = StackTraceView.ThreadRequestState.NO
self._pending_thread_request = None
self._requesting_threads = StackTraceView.ThreadRequestState.REQUESTING
self._connection.DoRequest( consume_threads, { self._connection.DoRequest( consume_threads, {
'command': 'threads', 'command': 'threads',
}, failure_handler ) } )
def _DrawThreads( self ): def _DrawThreads( self ):
self._line_to_frame.clear() self._line_to_frame.clear()
self._line_to_thread.clear() self._line_to_thread.clear()
if self._current_thread_sign_id:
signs.UnplaceSign( self._current_thread_sign_id, 'VimspectorStackTrace' )
else:
self._current_thread_sign_id = 1
with utils.ModifiableScratchBuffer( self._buf ): with utils.ModifiableScratchBuffer( self._buf ):
with utils.RestoreCursorPosition(): utils.ClearBuffer( self._buf )
utils.ClearBuffer( self._buf )
for thread in self._threads: for thread in self._threads:
icon = '+' if not thread.IsExpanded() else '-' icon = '+' if '_frames' not in thread else '-'
line = utils.AppendToBuffer(
self._buf,
f'{icon} Thread {thread.id}: {thread.thread["name"]} '
f'({thread.State()})' )
if self._current_thread == thread.id: line = utils.AppendToBuffer(
signs.PlaceSign( self._current_thread_sign_id, self._buf,
'VimspectorStackTrace', '{0} Thread: {1}'.format( icon, thread[ 'name' ] ) )
'vimspectorCurrentThread',
self._buf.name,
line )
self._line_to_thread[ line ] = thread self._line_to_thread[ line ] = thread
self._DrawStackTrace( thread )
def _LoadStackTrace( self, self._DrawStackTrace( thread )
thread: Thread,
infer_current_frame,
reason = '' ):
def _LoadStackTrace( self, thread, infer_current_frame ):
def consume_stacktrace( message ): def consume_stacktrace( message ):
thread.Expand( message[ 'body' ][ 'stackFrames' ] ) thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ]
if infer_current_frame: if infer_current_frame:
for frame in thread.stacktrace: for frame in thread[ '_frames' ]:
if self._JumpToFrame( frame, reason ): if self._JumpToFrame( frame ):
break break
self._DrawThreads() self._DrawThreads()
@ -328,116 +146,34 @@ 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 _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" )
def ExpandFrameOrThread( self ): def ExpandFrameOrThread( self ):
thread = self._GetSelectedThread() if vim.current.buffer != self._buf:
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 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 )
current_line = vim.current.window.cursor[ 0 ]
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:
self._LoadStackTrace( thread, False )
def _GetFrameOffset( self, delta ): def _JumpToFrame( self, frame ):
thread: Thread
for thread in self._threads:
if thread.id != self._current_thread:
continue
if not thread.stacktrace:
return
frame_idx = None
for index, frame in enumerate( thread.stacktrace ):
if frame == self._current_frame:
frame_idx = index
break
if frame_idx is not None:
target_idx = frame_idx + delta
if target_idx >= 0 and target_idx < len( thread.stacktrace ):
return thread.stacktrace[ target_idx ]
break
def UpFrame( self ):
frame = self._GetFrameOffset( 1 )
if not frame:
utils.UserMessage( 'Top of stack' )
else:
self._JumpToFrame( frame, 'up' )
def DownFrame( self ):
frame = self._GetFrameOffset( -1 )
if not frame:
utils.UserMessage( 'Bottom of stack' )
else:
self._JumpToFrame( frame, 'down' )
def AnyThreadsRunning( self ):
for thread in self._threads:
if thread.state != Thread.TERMINATED:
return True
return False
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' ]:
# Should this set the current _Thread_ too ? If i jump to a frame in self._currentFrame = frame
# Thread 2, should that become the focussed thread ? return self._session.SetCurrentFrame( self._currentFrame )
self._current_frame = frame
self._DrawThreads()
return self._session.SetCurrentFrame( self._current_frame, reason )
return False
source = frame.get( 'source' ) or {} source = frame[ 'source' ]
if source.get( 'sourceReference', 0 ) > 0: if source.get( 'sourceReference', 0 ) > 0:
def handle_resolved_source( resolved_source ): def handle_resolved_source( resolved_source ):
frame[ 'source' ] = resolved_source frame[ 'source' ] = resolved_source
@ -449,94 +185,59 @@ 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 ):
threadId = event.get( 'threadId' ) if 'threadId' in event:
allThreadsStopped = event.get( 'allThreadsStopped', False ) self._currentThread = event[ 'threadId' ]
elif event.get( 'allThreadsStopped', False ) and self._threads:
self._currentThread = self._threads[ 0 ][ 'id' ]
# Work out if we should change the current thread if self._currentThread is not None:
if threadId is not None: for thread in self._threads:
self._current_thread = threadId if thread[ 'id' ] == self._currentThread:
elif self._current_thread is None and allThreadsStopped and self._threads: self._LoadStackTrace( thread, True )
self._current_thread = self._threads[ 0 ].id return
self.LoadThreads( True, 'stopped', event ) self.LoadThreads( True )
def OnThreadEvent( self, event ): def OnThreadEvent( self, event ):
infer_current_frame = False if event[ 'reason' ] == 'started' and self._currentThread is None:
if event[ 'reason' ] == 'started' and self._current_thread is None: self._currentThread = event[ 'threadId' ]
self._current_thread = event[ 'threadId' ] self.LoadThreads( True )
infer_current_frame = True
if event[ 'reason' ] == 'exited': def Continue( self ):
for thread in self._threads: if self._currentThread is None:
if thread.id == event[ 'threadId' ]: utils.UserMessage( 'No current thread', persist = True )
thread.Exited()
break
self.LoadThreads( infer_current_frame )
def OnExited( self, event ):
for thread in self._threads:
thread.Exited()
def _DrawStackTrace( self, thread: Thread ):
if not thread.IsExpanded():
return return
if self._current_frame_sign_id: self._session._connection.DoRequest( None, {
signs.UnplaceSign( self._current_frame_sign_id, 'VimspectorStackTrace' ) 'command': 'continue',
else: 'arguments': {
self._current_frame_sign_id = 2 'threadId': self._currentThread,
},
} )
for frame in thread.stacktrace: self._session.ClearCurrentFrame()
self.LoadThreads( True )
def Pause( self ):
if self._currentThread is None:
utils.UserMessage( 'No current thread', persist = True )
return
self._session._connection.DoRequest( None, {
'command': 'pause',
'arguments': {
'threadId': self._currentThread,
},
} )
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:
@ -560,15 +261,7 @@ class StackTraceView( object ):
source[ 'name' ], source[ 'name' ],
frame[ 'line' ] ) ) frame[ 'line' ] ) )
if ( self._current_frame is not None and self._line_to_frame[ line ] = frame
self._current_frame[ 'id' ] == frame[ 'id' ] ):
signs.PlaceSign( self._current_frame_sign_id,
'VimspectorStackTrace',
'vimspectorCurrentFrame',
self._buf.name,
line )
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' ] )
@ -581,14 +274,12 @@ class StackTraceView( object ):
def consume_source( msg ): def consume_source( msg ):
self._sources[ source_reference ] = source self._sources[ source_reference ] = source
buf_name = os.path.join( '_vimspector_tmp', buf_name = os.path.join( '_vimspector_tmp', source[ 'name' ] )
source.get( 'path', source[ 'name' ] ) )
self._logger.debug( "Received source %s: %s", buf_name, msg ) self._logger.debug( "Received source %s: %s", buf_name, msg )
buf = utils.BufferForFile( buf_name ) buf = utils.BufferForFile( buf_name )
self._scratch_buffers.append( buf ) utils.SetUpScratchBuffer( buf, buf_name )
utils.SetUpHiddenBuffer( buf, buf_name )
source[ 'path' ] = buf_name source[ 'path' ] = buf_name
with utils.ModifiableScratchBuffer( buf ): with utils.ModifiableScratchBuffer( buf ):
utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] ) utils.SetBufferContents( buf, msg[ 'body' ][ 'content' ] )
@ -602,8 +293,3 @@ class StackTraceView( object ):
'source': source 'source': source
} }
} ) } )
def SetSyntax( self, syntax ):
self._current_syntax = utils.SetSyntax( self._current_syntax,
syntax,
self._buf )

View file

@ -1,134 +0,0 @@
from vimspector import utils, settings
import os
import vim
class Terminal:
window = None
buffer_number: int = None
def LaunchTerminal( api_prefix,
params,
window_for_start,
existing_term ):
if not existing_term:
term = Terminal()
else:
term = existing_term
cwd = params[ 'cwd' ] or os.getcwd()
args = params[ 'args' ] or []
env = params.get( 'env' ) or {}
term_options = {
'norestore': 1,
'cwd': cwd,
'env': env,
}
if settings.Get( 'ui_mode' ) == 'horizontal':
# force-horizontal
term_options[ 'vertical' ] = 1
elif utils.GetVimValue( vim.vars[ 'vimspector_session_windows' ],
'mode' ) == 'horizontal':
# horizontal, which means that we should have enough space for:
# - sidebar
# - code min
# - term min width
# - + 2 vertical spaders
# - + 3 columns for signs
term_options[ 'vertical' ] = 1
# if we don't have enough space for terminal_maxwidth, then see if we have
# enough vertically for terminal_maxheight, in which case,
# that seems a better fit
term_horiz_max = ( settings.Int( 'sidebar_width' ) +
1 + 2 + 3 +
settings.Int( 'code_minwidth' ) +
1 + settings.Int( 'terminal_maxwidth' ) )
term_vert_max = ( settings.Int( 'bottombar_height' ) + 1 +
settings.Int( 'code_minheight' ) + 1 +
settings.Int( 'terminal_minheight' ) )
if ( vim.options[ 'columns' ] < term_horiz_max and
vim.options[ 'lines' ] >= term_vert_max ):
# Looks like it, let's try that layout
term_options[ 'vertical' ] = 0
else:
# vertical - we need enough space horizontally for the code+terminal, but we
# may fit better with code above terminal
term_options[ 'vertical' ] = 0
term_horiz_max = ( settings.Int( 'code_minwidth' ) + 3 +
settings.Int( 'terminal_maxwidth' ) + 1 )
if vim.options[ 'columns' ] > term_horiz_max:
term_options[ 'vertical' ] = 1
if not window_for_start or not window_for_start.valid:
# TOOD: Where? Maybe we should just use botright vertical ...
window_for_start = vim.current.window
if term.window is not None and term.window.valid:
assert term.buffer_number
window_for_start = term.window
if ( term.window.buffer.number == term.buffer_number
and int( utils.Call( 'vimspector#internal#{}term#IsFinished'.format(
api_prefix ),
term.buffer_number ) ) ):
term_options[ 'curwin' ] = 1
else:
term_options[ 'vertical' ] = 0
buffer_number = None
terminal_window = None
with utils.LetCurrentWindow( window_for_start ):
# If we're making a vertical split from the code window, make it no more
# than 80 columns and no fewer than 10. Also try and keep the code window
# at least 82 columns
if term_options.get( 'curwin', 0 ):
pass
elif term_options[ 'vertical' ]:
term_options[ 'term_cols' ] = max(
min ( int( vim.eval( 'winwidth( 0 )' ) )
- settings.Int( 'code_minwidth' ),
settings.Int( 'terminal_maxwidth' ) ),
settings.Int( 'terminal_minwidth' )
)
else:
term_options[ 'term_rows' ] = max(
min ( int( vim.eval( 'winheight( 0 )' ) )
- settings.Int( 'code_minheight' ),
settings.Int( 'terminal_maxheight' ) ),
settings.Int( 'terminal_minheight' )
)
buffer_number = int(
utils.Call(
'vimspector#internal#{}term#Start'.format( api_prefix ),
args,
term_options ) )
terminal_window = vim.current.window
if buffer_number is None or buffer_number <= 0:
# TODO: Do something better like reject the request?
raise ValueError( "Unable to start terminal" )
term.window = terminal_window
term.buffer_number = buffer_number
vim.vars[ 'vimspector_session_windows' ][ 'terminal' ] = utils.WindowID(
term.window,
vim.current.tabpage )
with utils.RestoreCursorPosition():
with utils.RestoreCurrentWindow():
with utils.RestoreCurrentBuffer( vim.current.window ):
vim.command( 'doautocmd User VimspectorTerminalOpened' )
return term

View file

@ -19,18 +19,10 @@ import os
import contextlib import contextlib
import vim import vim
import json import json
import functools import string
import subprocess
import shlex
import collections
import re
import typing
LOG_FILE = os.path.expanduser( os.path.join( '~', '.vimspector.log' ) )
_log_handler = logging.FileHandler( LOG_FILE, mode = 'w' )
_log_handler = logging.FileHandler( os.path.expanduser( '~/.vimspector.log' ),
mode = 'w' )
_log_handler.setFormatter( _log_handler.setFormatter(
logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) ) logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) )
@ -45,38 +37,18 @@ _logger = logging.getLogger( __name__ )
SetUpLogging( _logger ) SetUpLogging( _logger )
def BufferNumberForFile( file_name, create = True ): def BufferNumberForFile( file_name ):
return int( vim.eval( "bufnr( '{0}', {1} )".format( return int( vim.eval( 'bufnr( "{0}", 1 )'.format( file_name ) ) )
Escape( file_name ),
int( create ) ) ) )
def BufferForFile( file_name ): def BufferForFile( file_name ):
return vim.buffers[ BufferNumberForFile( file_name ) ] return vim.buffers[ BufferNumberForFile( file_name ) ]
def BufferExists( file_name ):
return bool( int ( vim.eval( f"bufexists( '{ Escape( file_name ) }' )" ) ) )
def NewEmptyBuffer():
bufnr = int( vim.eval( 'bufadd("")' ) )
Call( 'bufload', bufnr )
return vim.buffers[ bufnr ]
def WindowForBuffer( buf ):
for w in vim.current.tabpage.windows:
if w.buffer == buf:
return w
return None
def OpenFileInCurrentWindow( file_name ): def OpenFileInCurrentWindow( file_name ):
buffer_number = BufferNumberForFile( file_name ) buffer_number = BufferNumberForFile( file_name )
try: try:
vim.current.buffer = vim.buffers[ buffer_number ] vim.command( 'bu {0}'.format( buffer_number ) )
except vim.error as e: except vim.error as e:
if 'E325' not in str( e ): if 'E325' not in str( e ):
raise raise
@ -84,50 +56,36 @@ def OpenFileInCurrentWindow( file_name ):
return vim.buffers[ buffer_number ] return vim.buffers[ buffer_number ]
COMMAND_HANDLERS = {} def SetUpCommandBuffer( cmd, name ):
bufs = vim.bindeval(
'vimspector#internal#job#StartCommandWithLog( {}, "{}" )'.format(
json.dumps( cmd ),
name ) )
if bufs is None:
def OnCommandWithLogComplete( name, exit_code ):
cb = COMMAND_HANDLERS.get( name )
if cb:
cb( exit_code )
def SetUpCommandBuffer( cmd, name, api_prefix, completion_handler = None ):
COMMAND_HANDLERS[ name ] = completion_handler
buf = Call( f'vimspector#internal#{api_prefix}job#StartCommandWithLog',
cmd,
name )
if buf is None:
raise RuntimeError( "Unable to start job {}: {}".format( cmd, name ) ) raise RuntimeError( "Unable to start job {}: {}".format( cmd, name ) )
elif int( buf ) <= 0: elif not all( b > 0 for b in bufs ):
raise RuntimeError( "Unable to get all streams for job {}: {}".format( raise RuntimeError( "Unable to get all streams for job {}: {}".format(
name, name,
cmd ) ) cmd ) )
return vim.buffers[ int( buf ) ] return [ vim.buffers[ b ] for b in bufs ]
def CleanUpCommand( name, api_prefix ): def CleanUpCommand( name ):
return vim.eval( 'vimspector#internal#{}job#CleanUpCommand( "{}" )'.format( return vim.eval( 'vimspector#internal#job#CleanUpCommand( "{}" )'.format(
api_prefix,
name ) ) name ) )
def CleanUpHiddenBuffer( buf ):
try:
vim.command( 'bdelete! {}'.format( buf.number ) )
except vim.error as e:
# FIXME: For now just ignore the "no buffers were deleted" error
if 'E516' not in str( e ):
raise
def SetUpScratchBuffer( buf, name ): def SetUpScratchBuffer( buf, name ):
SetUpHiddenBuffer( buf, name ) buf.options[ 'buftype' ] = 'nofile'
buf.options[ 'swapfile' ] = False
buf.options[ 'modifiable' ] = False
buf.options[ 'modified' ] = False
buf.options[ 'readonly' ] = True
buf.options[ 'buflisted' ] = False
buf.options[ 'bufhidden' ] = 'wipe' buf.options[ 'bufhidden' ] = 'wipe'
buf.name = name
def SetUpHiddenBuffer( buf, name ): def SetUpHiddenBuffer( buf, name ):
@ -141,10 +99,10 @@ def SetUpHiddenBuffer( buf, name ):
buf.name = name buf.name = name
def SetUpPromptBuffer( buf, name, prompt, callback, omnifunc ): def SetUpPromptBuffer( buf, name, prompt, callback, hidden=False ):
# This feature is _super_ new, so only enable when available # This feature is _super_ new, so only enable when available
if not Exists( '*prompt_setprompt' ): if not int( vim.eval( "exists( '*prompt_setprompt' )" ) ):
return SetUpHiddenBuffer( buf, name ) return SetUpScratchBuffer( buf, name )
buf.options[ 'buftype' ] = 'prompt' buf.options[ 'buftype' ] = 'prompt'
buf.options[ 'swapfile' ] = False buf.options[ 'swapfile' ] = False
@ -152,9 +110,7 @@ def SetUpPromptBuffer( buf, name, prompt, callback, omnifunc ):
buf.options[ 'modified' ] = False buf.options[ 'modified' ] = False
buf.options[ 'readonly' ] = False buf.options[ 'readonly' ] = False
buf.options[ 'buflisted' ] = False buf.options[ 'buflisted' ] = False
buf.options[ 'bufhidden' ] = 'hide' buf.options[ 'bufhidden' ] = 'wipe' if not hidden else 'hide'
buf.options[ 'textwidth' ] = 0
buf.options[ 'omnifunc' ] = omnifunc
buf.name = name buf.name = name
vim.eval( "prompt_setprompt( {0}, '{1}' )".format( buf.number, vim.eval( "prompt_setprompt( {0}, '{1}' )".format( buf.number,
@ -163,20 +119,6 @@ def SetUpPromptBuffer( buf, name, prompt, callback, omnifunc ):
buf.number, buf.number,
Escape( callback ) ) ) Escape( callback ) ) )
# This serves a few purposes, mainly to ensure that completion systems have
# something to work with. In particular it makes YCM use its identifier engine
# and you can config ycm to trigger semantic (annoyingly, synchronously) using
# some let g:ycm_auto_trggier
Call( 'setbufvar', buf.number, '&filetype', 'VimspectorPrompt' )
def SetUpUIWindow( win ):
win.options[ 'wrap' ] = False
win.options[ 'number' ] = False
win.options[ 'relativenumber' ] = False
win.options[ 'signcolumn' ] = 'no'
win.options[ 'spell' ] = False
win.options[ 'list' ] = False
@contextlib.contextmanager @contextlib.contextmanager
@ -213,40 +155,20 @@ def RestoreCurrentWindow():
try: try:
yield yield
finally: finally:
if old_tabpage.valid and old_window.valid: vim.current.tabpage = old_tabpage
vim.current.tabpage = old_tabpage vim.current.window = old_window
vim.current.window = old_window
@contextlib.contextmanager @contextlib.contextmanager
def RestoreCurrentBuffer( window ): def RestoreCurrentBuffer( window ):
# TODO: Don't trigger autoccommands when shifting buffers
old_buffer = window.buffer old_buffer = window.buffer
try: try:
yield yield
finally: finally:
if window.valid: with RestoreCurrentWindow():
with RestoreCurrentWindow(): vim.current.window = window
vim.current.window = window vim.current.buffer = old_buffer
vim.current.buffer = old_buffer
@contextlib.contextmanager
def AnyWindowForBuffer( buf ):
# Only checks the current tab page, which is what we want
current_win = WindowForBuffer( buf )
if current_win is not None:
with LetCurrentWindow( current_win ):
yield
else:
with LetCurrentBuffer( buf ):
yield
@contextlib.contextmanager
def LetCurrentTabpage( tabpage ):
with RestoreCurrentWindow():
vim.current.tabpage = tabpage
yield
@contextlib.contextmanager @contextlib.contextmanager
@ -256,14 +178,6 @@ def LetCurrentWindow( window ):
yield yield
@contextlib.contextmanager
def LetCurrentBuffer( buf ):
with RestoreCursorPosition():
with RestoreCurrentBuffer( vim.current.window ):
vim.current.buffer = buf
yield
def JumpToWindow( window ): def JumpToWindow( window ):
vim.current.tabpage = window.tabpage vim.current.tabpage = window.tabpage
vim.current.window = window vim.current.window = window
@ -293,12 +207,8 @@ def TemporaryVimOption( opt, value ):
vim.options[ opt ] = old_value vim.options[ opt ] = old_value
def PathToConfigFile( file_name, from_directory = None ): def PathToConfigFile( file_name ):
if not from_directory: p = os.getcwd()
p = os.getcwd()
else:
p = os.path.abspath( os.path.realpath( from_directory ) )
while True: while True:
candidate = os.path.join( p, file_name ) candidate = os.path.join( p, file_name )
if os.path.exists( candidate ): if os.path.exists( candidate ):
@ -314,21 +224,16 @@ def Escape( msg ):
return msg.replace( "'", "''" ) return msg.replace( "'", "''" )
def UserMessage( msg, persist=False, error=False ): def UserMessage( msg, persist=False ):
if persist: if persist:
_logger.warning( 'User Msg: ' + msg ) _logger.warning( 'User Msg: ' + msg )
else: else:
_logger.info( 'User Msg: ' + msg ) _logger.info( 'User Msg: ' + msg )
cmd = 'echom' if persist else 'echo'
vim.command( 'redraw' ) vim.command( 'redraw' )
try: cmd = 'echom' if persist else 'echo'
if error: for line in msg.split( '\n' ):
vim.command( "echohl WarningMsg" ) vim.command( "{0} '{1}'".format( cmd, Escape( line ) ) )
for line in msg.split( '\n' ):
vim.command( "{0} '{1}'".format( cmd, Escape( line ) ) )
finally:
vim.command( 'echohl None' ) if error else None
vim.command( 'redraw' ) vim.command( 'redraw' )
@ -352,69 +257,25 @@ def SelectFromList( prompt, options ):
if selection < 0 or selection >= len( options ): if selection < 0 or selection >= len( options ):
return None return None
return options[ selection ] return options[ selection ]
except ( KeyboardInterrupt, vim.error ): except KeyboardInterrupt:
return None return None
def AskForInput( prompt, default_value = None, completion = None ): def AskForInput( prompt, default_value = None ):
if default_value is None: if default_value is None:
default_value = '' default_option = ''
else:
args = [ prompt, default_value ] default_option = ", '{}'".format( Escape( default_value ) )
if completion is not None:
if completion == 'expr':
args.append( 'custom,vimspector#CompleteExpr' )
else:
args.append( completion )
with InputSave(): with InputSave():
try: try:
return Call( 'input', *args ) return vim.eval( "input( '{}' {} )".format( Escape( prompt ),
except ( KeyboardInterrupt, vim.error ): default_option ) )
return None except KeyboardInterrupt:
return ''
CONFIRM = {}
CONFIRM_ID = 0
def ConfirmCallback( confirm_id, result ):
try:
handler = CONFIRM.pop( confirm_id )
except KeyError:
UserMessage( f"Internal error: unexpected callback id { confirm_id }",
persist = True,
error = True )
return
handler( result )
def Confirm( api_prefix,
prompt,
handler,
default_value = 2,
options: list = None,
keys: list = None ):
if not options:
options = [ '(Y)es', '(N)o' ]
if not keys:
keys = [ 'y', 'n' ]
global CONFIRM_ID
CONFIRM_ID += 1
CONFIRM[ CONFIRM_ID ] = handler
Call( f'vimspector#internal#{ api_prefix }popup#Confirm',
CONFIRM_ID,
prompt,
options,
default_value,
keys )
def AppendToBuffer( buf, line_or_lines, modified=False ): def AppendToBuffer( buf, line_or_lines, modified=False ):
line = 1
try: try:
# After clearing the buffer (using buf[:] = None) there is always a single # After clearing the buffer (using buf[:] = None) there is always a single
# empty line in the buffer object and no "is empty" method. # empty line in the buffer object and no "is empty" method.
@ -441,10 +302,8 @@ def AppendToBuffer( buf, line_or_lines, modified=False ):
def ClearBuffer( buf, modified = False ): def ClearBuffer( buf ):
buf[ : ] = None buf[ : ] = None
if not modified:
buf.options[ 'modified' ] = False
def SetBufferContents( buf, lines, modified=False ): def SetBufferContents( buf, lines, modified=False ):
@ -452,7 +311,7 @@ def SetBufferContents( buf, lines, modified=False ):
if not isinstance( lines, list ): if not isinstance( lines, list ):
lines = lines.splitlines() lines = lines.splitlines()
buf[ : ] = lines buf[:] = lines
finally: finally:
buf.options[ 'modified' ] = modified buf.options[ 'modified' ] = modified
@ -461,195 +320,77 @@ def IsCurrent( window, buf ):
return vim.current.window == window and vim.current.window.buffer == buf return vim.current.window == window and vim.current.window.buffer == buf
def ExpandReferencesInObject( obj, mapping, calculus, user_choices ): # TODO: Should we just run the substitution on the whole JSON string instead?
if isinstance( obj, dict ): # That woul dallow expansion in bool and number values, such as ports etc. ?
ExpandReferencesInDict( obj, mapping, calculus, user_choices ) def ExpandReferencesInDict( obj, mapping, user_choices ):
elif isinstance( obj, list ): def expand_refs_in_string( orig_s ):
j_offset = 0 s = os.path.expanduser( orig_s )
obj_copy = list( obj ) s = os.path.expandvars( s )
for i, _ in enumerate( obj_copy ): # Parse any variables passed in in mapping, and ask for any that weren't,
j = i + j_offset # storing the result in mapping
if ( isinstance( obj_copy[ i ], str ) and bug_catcher = 0
len( obj_copy[ i ] ) > 2 and while bug_catcher < 100:
obj_copy[ i ][ 0:2 ] == '*$' ): ++bug_catcher
# *${something} - expand list in place
value = ExpandReferencesInString( obj_copy[ i ][ 1: ],
mapping,
calculus,
user_choices )
obj.pop( j )
j_offset -= 1
for opt_index, opt in enumerate( shlex.split( value ) ):
obj.insert( j + opt_index, opt )
j_offset += 1
else:
obj[ j ] = ExpandReferencesInObject( obj_copy[ i ],
mapping,
calculus,
user_choices )
elif isinstance( obj, str ):
obj = ExpandReferencesInString( obj, mapping, calculus, user_choices )
return obj
# Based on the python standard library string.Template().substitue, enhanced to
# add ${name:default} parsing, and to remove the unnecessary generality.
VAR_MATCH = re.compile(
r"""
\$(?: # A dollar, followed by...
(?P<escaped>\$) | # Another doller = escaped
(?P<named>[_a-z][_a-z0-9]*) | # or An identifier - named param
{(?P<braced>[_a-z][_a-z0-9]*)} | # or An {identifier} - braced param
{(?P<braceddefault> # or An {id:default} - default param, as
(?P<defname>[_a-z][_a-z0-9]*) # an ID
: # then a colon
(?P<default>(?:\\}|[^}])*) # then anything up to }, or a \}
)} | # then a }
(?P<invalid>) # or Something else - invalid
)
""",
re.IGNORECASE | re.VERBOSE )
class MissingSubstitution( Exception ):
def __init__( self, name, default_value = None ):
self.name = name
self.default_value = default_value
def _Substitute( template, mapping ):
def convert( mo ):
# Check the most common path first.
named = mo.group( 'named' ) or mo.group( 'braced' )
if named is not None:
if named not in mapping:
raise MissingSubstitution( named )
return str( mapping[ named ] )
if mo.group( 'escaped' ) is not None:
return '$'
if mo.group( 'braceddefault' ) is not None:
named = mo.group( 'defname' )
if named not in mapping:
raise MissingSubstitution(
named,
mo.group( 'default' ).replace( '\\}', '}' ) )
return str( mapping[ named ] )
if mo.group( 'invalid' ) is not None:
raise ValueError( f"Invalid placeholder in string { template }" )
raise ValueError( 'Unrecognized named group in pattern', VAR_MATCH )
return VAR_MATCH.sub( convert, template )
def ExpandReferencesInString( orig_s,
mapping,
calculus,
user_choices ):
s = os.path.expanduser( orig_s )
s = os.path.expandvars( s )
# Parse any variables passed in in mapping, and ask for any that weren't,
# storing the result in mapping
bug_catcher = 0
while bug_catcher < 100:
++bug_catcher
try:
s = _Substitute( s, mapping )
break
except MissingSubstitution as e:
key = e.name
if key in calculus:
mapping[ key ] = calculus[ key ]()
else:
default_value = user_choices.get( key )
# Allow _one_ level of additional substitution. This allows a very real
# use case of "program": ${prgram:${file\\}}
if default_value is None and e.default_value is not None:
try:
default_value = _Substitute( e.default_value, mapping )
except MissingSubstitution as e2:
if e2.name in calculus:
default_value = calculus[ e2.name ]()
else:
default_value = e.default_value
try:
s = string.Template( s ).substitute( mapping )
break
except KeyError as e:
# HACK: This is seemingly the only way to get the key. str( e ) returns
# the key surrounded by '' for unknowable reasons.
key = e.args[ 0 ]
default_value = user_choices.get( key, None )
mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ), mapping[ key ] = AskForInput( 'Enter value for {}: '.format( key ),
default_value ) default_value )
if mapping[ key ] is None:
raise KeyboardInterrupt
user_choices[ key ] = mapping[ key ] user_choices[ key ] = mapping[ key ]
_logger.debug( "Value for %s not set in %s (from %s): set to %s", _logger.debug( "Value for %s not set in %s (from %s): set to %s",
key, key,
s, s,
orig_s, orig_s,
mapping[ key ] ) mapping[ key ] )
except ValueError as e: except ValueError as e:
UserMessage( 'Invalid $ in string {}: {}'.format( s, e ), UserMessage( 'Invalid $ in string {}: {}'.format( s, e ),
persist = True ) persist = True )
break break
return s return s
def expand_refs_in_object( obj ):
if isinstance( obj, dict ):
ExpandReferencesInDict( obj, mapping, user_choices )
elif isinstance( obj, list ):
for i, _ in enumerate( obj ):
# FIXME: We are assuming that it is a list of string, but could be a
# list of list of a list of dict, etc.
obj[ i ] = expand_refs_in_object( obj[ i ] )
elif isinstance( obj, str ):
obj = expand_refs_in_string( obj )
return obj
for k in obj.keys():
obj[ k ] = expand_refs_in_object( obj[ k ] )
def CoerceType( mapping: typing.Dict[ str, typing.Any ], key: str ): def ParseVariables( variables_list, mapping, user_choices ):
DICT_TYPES = {
'json': json.loads,
's': str
}
parts = key.split( '#' )
if len( parts ) > 1 and parts[ -1 ] in DICT_TYPES.keys():
value = mapping.pop( key )
new_type = parts[ -1 ]
key = '#'.join( parts[ 0 : -1 ] )
mapping[ key ] = DICT_TYPES[ new_type ]( value )
# TODO: Should we just run the substitution on the whole JSON string instead?
# That woul dallow expansion in bool and number values, such as ports etc. ?
def ExpandReferencesInDict( obj, mapping, calculus, user_choices ):
for k in list( obj.keys() ):
obj[ k ] = ExpandReferencesInObject( obj[ k ],
mapping,
calculus,
user_choices )
CoerceType( obj, k )
def ParseVariables( variables_list,
mapping,
calculus,
user_choices ):
new_variables = {} new_variables = {}
new_mapping = mapping.copy() new_mapping = mapping.copy()
if not isinstance( variables_list, list ): if not isinstance( variables_list, list ):
variables_list = [ variables_list ] variables_list = [ variables_list ]
variables: typing.Dict[ str, typing.Any ]
for variables in variables_list: for variables in variables_list:
new_mapping.update( new_variables ) new_mapping.update( new_variables )
for n, v in list( variables.items() ): for n, v in variables.items():
if isinstance( v, dict ): if isinstance( v, dict ):
if 'shell' in v: if 'shell' in v:
import subprocess
import shlex
new_v = v.copy() new_v = v.copy()
# Bit of a hack. Allows environment variables to be used. # Bit of a hack. Allows environment variables to be used.
ExpandReferencesInDict( new_v, ExpandReferencesInDict( new_v, new_mapping, user_choices )
new_mapping,
calculus,
user_choices )
env = os.environ.copy() env = os.environ.copy()
env.update( new_v.get( 'env' ) or {} ) env.update( new_v.get( 'env' ) or {} )
@ -671,29 +412,17 @@ def ParseVariables( variables_list,
raise ValueError( raise ValueError(
"Unsupported variable defn {}: Missing 'shell'".format( n ) ) "Unsupported variable defn {}: Missing 'shell'".format( n ) )
else: else:
new_variables[ n ] = ExpandReferencesInObject( v, new_variables[ n ] = v
mapping,
calculus,
user_choices )
CoerceType( new_variables, n )
return new_variables return new_variables
def DisplayBalloon( is_term, display, is_hover = False ): def DisplayBaloon( is_term, display ):
if not is_term: if not is_term:
# To enable the Windows GUI to display the balloon correctly
# Refer https://github.com/vim/vim/issues/1512#issuecomment-492070685
display = '\n'.join( display ) display = '\n'.join( display )
created_win_id = int( vim.eval( vim.eval( "balloon_show( {0} )".format(
"vimspector#internal#balloon#CreateTooltip({}, {})".format( json.dumps( display ) ) )
int( is_hover ), json.dumps( display )
)
) )
return created_win_id
def GetBufferFilepath( buf ): def GetBufferFilepath( buf ):
@ -701,166 +430,3 @@ def GetBufferFilepath( buf ):
return '' return ''
return os.path.normpath( buf.name ) return os.path.normpath( buf.name )
def ToUnicode( b ):
if isinstance( b, bytes ):
return b.decode( 'utf-8' )
return b
# Call a vimscript function with suplied arguments.
def Call( vimscript_function, *args ):
call = vimscript_function + '('
for index, arg in enumerate( args ):
if index > 0:
call += ', '
arg_name = 'vimspector_internal_arg_{}'.format( index )
vim.vars[ arg_name ] = arg
call += 'g:' + arg_name
call += ')'
return vim.eval( call )
MEMO = {}
def memoize( func ):
global MEMO
@functools.wraps( func )
def wrapper( *args, **kwargs ):
dct = MEMO.setdefault( func, {} )
key = ( args, frozenset( kwargs.items() ) )
try:
return dct[ key ]
except KeyError:
result = func( *args, **kwargs )
dct[ key ] = result
return result
return wrapper
@memoize
def Exists( expr ):
return int( vim.eval( f'exists( "{ expr }" )' ) )
def SetSyntax( current_syntax, syntax, *args ):
if not syntax:
syntax = ''
if current_syntax == syntax:
return syntax
# We use set syn= because just setting vim.Buffer.options[ 'syntax' ]
# doesn't actually trigger the Syntax autocommand, and i'm not sure that
# 'doautocmd Syntax' is the right solution or not
for buf in args:
Call( 'setbufvar', buf.number, '&syntax', syntax )
return syntax
def GetBufferFiletypes( buf ):
ft = ToUnicode( vim.eval( f"getbufvar( {buf.number}, '&ft' )" ) )
return ft.split( '.' )
def GetVisualSelection( bufnr ):
start_line, start_col = vim.current.buffer.mark( "<" )
end_line, end_col = vim.current.buffer.mark( ">" )
# lines are 1 based, but columns are 0 based
# don't ask me why...
start_line -= 1
end_line -= 1
lines = vim.buffers[ bufnr ][ start_line : end_line + 1 ]
# Do end first, in case it's on the same line as start (as doing start first
# would change the offset)
lines[ -1 ] = lines[ -1 ][ : end_col + 1 ]
lines[ 0 ] = lines[ 0 ][ start_col : ]
_logger.debug( f'Visual selection: { lines } from '
f'{ start_line }/{ start_col } -> { end_line }/{ end_col }' )
return lines
def DisplaySplash( api_prefix, splash, text ):
if splash:
return Call( f'vimspector#internal#{api_prefix}popup#UpdateSplash',
splash,
text )
else:
return Call( f'vimspector#internal#{api_prefix}popup#DisplaySplash',
text )
def HideSplash( api_prefix, splash ):
if splash:
Call( f'vimspector#internal#{api_prefix}popup#HideSplash', splash )
return None
def GetVimValue( vim_dict, name, default=None ):
# FIXME: use 'encoding' ?
try:
value = vim_dict[ name ]
except ( KeyError, vim.error ):
return default
if isinstance( value, bytes ):
return value.decode( 'utf-8' )
return value
def GetVimList( vim_dict, name, default=None ):
try:
value = vim_dict[ name ]
except ( KeyError, vim.error ):
return default
if not isinstance( value, collections.abc.Iterable ):
raise ValueError( f"Expected a list for { name }, but found "
f"{ type( value ) }" )
return [ i.decode( 'utf-8' ) if isinstance( i, bytes ) else i for i in value ]
def GetVimspectorBase():
return GetVimValue( vim.vars,
'vimspector_base_dir',
os.path.abspath(
os.path.join( os.path.dirname( __file__ ),
'..',
'..' ) ) )
def GetUnusedLocalPort():
import socket
sock = socket.socket()
# This tells the OS to give us any free port in the range [1024 - 65535]
sock.bind( ( '', 0 ) )
port = sock.getsockname()[ 1 ]
sock.close()
return port
def WindowID( window, tab=None ):
if tab is None:
tab = window.tabpage
return int( Call( 'win_getid', window.number, tab.number ) )
def UseWinBar():
# Buggy neovim doesn't render correctly when the WinBar is defined:
# https://github.com/neovim/neovim/issues/12689
return not int( Call( 'has', 'nvim' ) )

View file

@ -13,208 +13,57 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import abc
import vim import vim
import logging import logging
from collections import namedtuple
from functools import partial from functools import partial
import typing
from vimspector import utils, settings from vimspector import utils
View = namedtuple( 'View', [ 'win', 'lines', 'draw' ] )
class Expandable:
EXPANDED_BY_USER = 2
EXPANDED_BY_US = 1
COLLAPSED_BY_USER = 0
COLLAPSED_BY_DEFAULT = None
"""Base for anything which might contain a hierarchy of values represented by
a 'variablesReference' to be resolved by the 'variables' request. Records the
current state expanded/collapsed. Implementations just implement
VariablesReference to get the variables."""
def __init__( self, container: 'Expandable' = None ):
self.variables: typing.List[ 'Variable' ] = None
self.container: Expandable = container
# None is Falsy and represents collapsed _by default_. WHen set to False,
# this means the user explicitly collapsed it. When True, the user expanded
# it (or we expanded it by default).
self.expanded: int = Expandable.COLLAPSED_BY_DEFAULT
def IsExpanded( self ):
return bool( self.expanded )
def ShouldDrawDrillDown( self ):
return self.IsExpanded() and self.variables is not None
def IsExpandable( self ):
return self.VariablesReference() > 0
def IsContained( self ):
return self.container is not None
@abc.abstractmethod
def VariablesReference( self ):
assert False
class Scope( Expandable ):
"""Holds an expandable scope (a DAP scope dict), with expand/collapse state"""
def __init__( self, scope: dict ):
super().__init__()
self.scope = scope
def VariablesReference( self ):
return self.scope.get( 'variablesReference', 0 )
def Update( self, scope ):
self.scope = scope
class WatchResult( Expandable ):
"""Holds the result of a Watch expression with expand/collapse."""
def __init__( self, result: dict ):
super().__init__()
self.result = result
# A new watch result is marked as changed
self.changed = True
def VariablesReference( self ):
return self.result.get( 'variablesReference', 0 )
def Update( self, result ):
self.changed = False
if self.result[ 'result' ] != result[ 'result' ]:
self.changed = True
self.result = result
class WatchFailure( WatchResult ):
def __init__( self, reason ):
super().__init__( { 'result': reason } )
self.changed = True
class Variable( Expandable ):
"""Holds one level of an expanded value tree. Also itself expandable."""
def __init__( self, container: Expandable, variable: dict ):
super().__init__( container = container )
self.variable = variable
# A new variable appearing is marked as changed
self.changed = True
def VariablesReference( self ):
return self.variable.get( 'variablesReference', 0 )
def Update( self, variable ):
self.changed = False
if self.variable[ 'value' ] != variable[ 'value' ]:
self.changed = True
self.variable = variable
class Watch:
"""Holds a user watch expression (DAP request) and the result (WatchResult)"""
def __init__( self, expression: dict ):
self.result: WatchResult
self.line = None
self.expression = expression
self.result = None
@staticmethod
def New( frame, expression, context ):
watch = {
'expression': expression,
'context': context,
}
if frame:
watch[ 'frameId' ] = frame[ 'id' ]
return Watch( watch )
class View:
lines: typing.Dict[ int, Expandable ]
draw: typing.Callable
syntax: str
def __init__( self, win, lines, draw ):
self.lines = lines
self.draw = draw
self.syntax = None
if win is not None:
self.buf = win.buffer
utils.SetUpUIWindow( win )
class BufView( View ):
def __init__( self, buf, lines, draw ):
super().__init__( None, lines, draw )
self.buf = buf
def AddExpandMappings( mappings = None ):
if mappings is None:
mappings = settings.Dict( 'mappings' )[ 'variables' ]
for mapping in utils.GetVimList( mappings, 'expand_collapse' ):
vim.command( f'nnoremap <silent> <buffer> { mapping } '
':<C-u>call vimspector#ExpandVariable()<CR>' )
for mapping in utils.GetVimList( mappings, 'set_value' ):
vim.command( f'nnoremap <silent> <buffer> { mapping } '
':<C-u>call vimspector#SetVariableValue()<CR>' )
class VariablesView( object ): class VariablesView( object ):
def __init__( self, variables_win, watches_win ): def __init__( self, connection, variables_win, watches_win ):
self._logger = logging.getLogger( __name__ ) self._logger = logging.getLogger( __name__ )
utils.SetUpLogging( self._logger ) utils.SetUpLogging( self._logger )
self._connection = None
self._current_syntax = ''
self._server_capabilities = None
self._variable_eval: Scope = None
self._variable_eval_view: View = None
mappings = settings.Dict( 'mappings' )[ 'variables' ]
# Set up the "Variables" buffer in the variables_win
self._scopes: typing.List[ Scope ] = []
self._vars = View( variables_win, {}, self._DrawScopes ) self._vars = View( variables_win, {}, self._DrawScopes )
utils.SetUpHiddenBuffer( self._vars.buf, 'vimspector.Variables' )
with utils.LetCurrentWindow( variables_win ):
if utils.UseWinBar():
vim.command( 'nnoremenu <silent> 1.1 WinBar.Set '
':call vimspector#SetVariableValue()<CR>' )
AddExpandMappings( mappings )
# Set up the "Watches" buffer in the watches_win (and create a WinBar in
# there)
self._watches: typing.List[ Watch ] = []
self._watch = View( watches_win, {}, self._DrawWatches ) self._watch = View( watches_win, {}, self._DrawWatches )
utils.SetUpPromptBuffer( self._watch.buf, self._connection = connection
# Allows us to hit <CR> to expand/collapse variables
with utils.LetCurrentWindow( self._vars.win ):
vim.command(
'nnoremap <buffer> <CR> :call vimspector#ExpandVariable()<CR>' )
# This is actually the tree (scopes are alwyas the root)
# it's just a list of DAP scope dicts, with one magic key (_variables)
# _variables is a list of DAP variable with the same magic key
#
# If _variables is present, then we have requested and should display the
# children. Otherwise, we haven't or shouldn't.
self._scopes = []
# This is similar to scopes, but the top level is an "expression" (request)
# containing a special '_result' key which is the response. The response
# structure con contain _variables and is handled identically to the scopes
# above. It also has a special _line key which is where we printed it (last)
self._watches = []
# Allows us to hit <CR> to expand/collapse variables
with utils.LetCurrentWindow( self._watch.win ):
vim.command(
'nnoremap <buffer> <CR> :call vimspector#ExpandVariable()<CR>' )
vim.command(
'nnoremap <buffer> <DEL> :call vimspector#DeleteWatch()<CR>' )
utils.SetUpScratchBuffer( self._vars.win.buffer, 'vimspector.Variables' )
utils.SetUpPromptBuffer( self._watch.win.buffer,
'vimspector.Watches', 'vimspector.Watches',
'Expression: ', 'Expression: ',
'vimspector#AddWatchPrompt', 'vimspector#AddWatchPrompt' )
'vimspector#OmniFuncWatch' )
with utils.LetCurrentWindow( watches_win ):
AddExpandMappings( mappings )
for mapping in utils.GetVimList( mappings, 'delete' ):
vim.command(
f'nnoremap <buffer> { mapping } :call vimspector#DeleteWatch()<CR>' )
if utils.UseWinBar():
vim.command( 'nnoremenu <silent> 1.1 WinBar.New '
':call vimspector#AddWatch()<CR>' )
vim.command( 'nnoremenu <silent> 1.2 WinBar.Expand/Collapse '
':call vimspector#ExpandVariable()<CR>' )
vim.command( 'nnoremenu <silent> 1.3 WinBar.Delete '
':call vimspector#DeleteWatch()<CR>' )
vim.command( 'nnoremenu <silent> 1.1 WinBar.Set '
':call vimspector#SetVariableValue()<CR>' )
# Set the (global!) balloon expr if supported
has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) ) has_balloon = int( vim.eval( "has( 'balloon_eval' )" ) )
has_balloon_term = int( vim.eval( "has( 'balloon_eval_term' )" ) ) has_balloon_term = int( vim.eval( "has( 'balloon_eval_term' )" ) )
@ -224,9 +73,7 @@ class VariablesView( object ):
'balloonexpr': vim.options[ 'balloonexpr' ], 'balloonexpr': vim.options[ 'balloonexpr' ],
'balloondelay': vim.options[ 'balloondelay' ], 'balloondelay': vim.options[ 'balloondelay' ],
} }
vim.options[ 'balloonexpr' ] = ( "vimspector#internal#" vim.options[ 'balloonexpr' ] = 'vimspector#internal#balloon#BalloonExpr()'
"balloon#HoverTooltip()" )
vim.options[ 'balloondelay' ] = 250 vim.options[ 'balloondelay' ] = 250
if has_balloon: if has_balloon:
@ -240,76 +87,49 @@ class VariablesView( object ):
self._is_term = not bool( int( vim.eval( "has( 'gui_running' )" ) ) ) self._is_term = not bool( int( vim.eval( "has( 'gui_running' )" ) ) )
def Clear( self ): def Clear( self ):
with utils.ModifiableScratchBuffer( self._vars.buf ): with utils.ModifiableScratchBuffer( self._vars.win.buffer ):
utils.ClearBuffer( self._vars.buf ) utils.ClearBuffer( self._vars.win.buffer )
with utils.ModifiableScratchBuffer( self._watch.buf ): with utils.ModifiableScratchBuffer( self._watch.win.buffer ):
utils.ClearBuffer( self._watch.buf ) utils.ClearBuffer( self._watch.win.buffer )
self.ClearTooltip()
self._current_syntax = ''
def ConnectionUp( self, connection ): def ConnectionUp( self, connection ):
self._connection = connection self._connection = connection
def SetServerCapabilities( self, capabilities ):
self._server_capabilities = capabilities
def ConnectionClosed( self ): def ConnectionClosed( self ):
self.Clear() self.Clear()
self._connection = None self._connection = None
self._server_capabilities = None
def Reset( self ): def Reset( self ):
self._server_capabilities = None
for k, v in self._oldoptions.items(): for k, v in self._oldoptions.items():
vim.options[ k ] = v vim.options[ k ] = v
utils.CleanUpHiddenBuffer( self._vars.buf )
utils.CleanUpHiddenBuffer( self._watch.buf )
self.ClearTooltip()
def LoadScopes( self, frame ): def LoadScopes( self, frame ):
def scopes_consumer( message ): def scopes_consumer( message ):
new_scopes = [] old_scopes = self._scopes
expanded_some_scope = False self._scopes = []
for scope_body in message[ 'body' ][ 'scopes' ]:
# Find it in the scopes list
found = False
for index, s in enumerate( self._scopes ):
if s.scope[ 'name' ] == scope_body[ 'name' ]:
found = True
scope = s
break
if not found: for i, scope in enumerate( message[ 'body' ][ 'scopes' ] ):
scope = Scope( scope_body ) if ( i < len( old_scopes ) and
old_scopes[ i ][ 'name' ] == scope[ 'name' ] ):
scope[ '_expanded' ] = old_scopes[ i ].get( '_expanded', False )
scope[ '_old_variables' ] = old_scopes[ i ].get( '_variables', [] )
elif not scope.get( 'expensive' ):
# Expand any non-expensive scope unless manually collapsed
scope[ '_expanded' ] = True
else: else:
scope.Update( scope_body ) scope[ '_expanded' ] = False
new_scopes.append( scope ) self._scopes.append( scope )
if scope[ '_expanded' ]:
# Expand the first non-expensive scope which is not manually collapsed
if ( not expanded_some_scope
and not scope.scope.get( 'expensive' )
and scope.expanded is not Expandable.COLLAPSED_BY_USER ):
scope.expanded = Expandable.EXPANDED_BY_US
expanded_some_scope = True
elif ( expanded_some_scope and scope.expanded is
Expandable.EXPANDED_BY_US ):
scope.expanded = Expandable.COLLAPSED_BY_DEFAULT
if scope.IsExpanded():
self._connection.DoRequest( partial( self._ConsumeVariables, self._connection.DoRequest( partial( self._ConsumeVariables,
self._DrawScopes, self._DrawScopes,
scope ), { scope ), {
'command': 'variables', 'command': 'variables',
'arguments': { 'arguments': {
'variablesReference': scope.VariablesReference(), 'variablesReference': scope[ 'variablesReference' ]
}, },
} ) } )
self._scopes = new_scopes
self._DrawScopes() self._DrawScopes()
self._connection.DoRequest( scopes_consumer, { self._connection.DoRequest( scopes_consumer, {
@ -319,413 +139,237 @@ class VariablesView( object ):
}, },
} ) } )
def _DrawBalloonEval( self ):
watch = self._variable_eval
view = self._variable_eval_view
with utils.RestoreCursorPosition():
with utils.ModifiableScratchBuffer( view.buf ):
utils.ClearBuffer( view.buf )
view.syntax = utils.SetSyntax( view.syntax,
self._current_syntax,
view.buf )
self._DrawWatchResult( view,
0,
watch,
is_short = True )
vim.eval( "vimspector#internal#balloon#ResizeTooltip()" )
def ClearTooltip( self ):
# This will actually end up calling CleanUpTooltip via the popup close
# callback
vim.eval( 'vimspector#internal#balloon#Close()' )
def CleanUpTooltip( self ) :
# remove reference to old tooltip window
self._variable_eval_view = None
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = None
def VariableEval( self, frame, expression, is_hover ):
"""Callback to display variable under cursor `:h ballonexpr`"""
if not self._connection:
return ''
def handler( message ):
watch = self._variable_eval
if watch.result is None:
watch.result = WatchResult( message[ 'body' ] )
else:
watch.result.Update( message[ 'body' ] )
popup_win_id = utils.DisplayBalloon( self._is_term, [], is_hover )
# record the global eval window id
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = int( popup_win_id )
popup_bufnr = int( vim.eval( "winbufnr({})".format( popup_win_id ) ) )
# We don't need to do any UI window setup here, as it's already done as
# part of the popup creation, so just pass the buffer to the View instance
self._variable_eval_view = BufView(
vim.buffers[ popup_bufnr ],
{},
self._DrawBalloonEval
)
if watch.result.IsExpandable():
# Always expand the first level
watch.result.expanded = Expandable.EXPANDED_BY_US
if watch.result.IsExpanded():
self._connection.DoRequest( partial( self._ConsumeVariables,
self._variable_eval_view.draw,
watch.result ), {
'command': 'variables',
'arguments': {
'variablesReference': watch.result.VariablesReference(),
},
} )
self._DrawBalloonEval()
def failure_handler( reason, message ):
display = [ reason ]
float_win_id = utils.DisplayBalloon( self._is_term, display, is_hover )
# record the global eval window id
vim.vars[ 'vimspector_session_windows' ][ 'eval' ] = int( float_win_id )
self._variable_eval = Watch.New( frame,
expression,
'hover' )
# Send async request
self._connection.DoRequest( handler, {
'command': 'evaluate',
'arguments': self._variable_eval.expression,
}, failure_handler )
# Return working (meanwhile)
return ''
def AddWatch( self, frame, expression ): def AddWatch( self, frame, expression ):
self._watches.append( Watch.New( frame, expression, 'watch' ) ) watch = {
'expression': expression,
'frameId': frame[ 'id' ],
'context': 'watch',
}
self._watches.append( watch )
self.EvaluateWatches() self.EvaluateWatches()
def DeleteWatch( self ): def DeleteWatch( self ):
if vim.current.buffer != self._watch.buf: if vim.current.window != self._watch.win:
utils.UserMessage( 'Not a watch buffer' ) utils.UserMessage( 'Not a watch window' )
return return
current_line = vim.current.window.cursor[ 0 ] current_line = vim.current.window.cursor[ 0 ]
best_index = -1
for index, watch in enumerate( self._watches ): for index, watch in enumerate( self._watches ):
if ( watch.line is not None if '_line' in watch and watch[ '_line' ] == current_line:
and watch.line <= current_line del self._watches[ index ]
and watch.line > best_index ): utils.UserMessage( 'Deleted' )
best_index = index self._DrawWatches()
return
if best_index >= 0:
del self._watches[ best_index ]
utils.UserMessage( 'Deleted' )
self._DrawWatches()
return
utils.UserMessage( 'No watch found' ) utils.UserMessage( 'No watch found' )
def EvaluateWatches( self ): def EvaluateWatches( self ):
for watch in self._watches: for watch in self._watches:
self._connection.DoRequest( self._connection.DoRequest( partial( self._UpdateWatchExpression,
partial( self._UpdateWatchExpression, watch ), watch ), {
{ 'command': 'evaluate',
'command': 'evaluate', 'arguments': watch,
'arguments': watch.expression, } )
},
failure_handler = lambda reason, msg, watch=watch:
self._WatchExpressionFailed( reason, watch ) )
def _UpdateWatchExpression( self, watch: Watch, message: dict ): def _UpdateWatchExpression( self, watch, message ):
if watch.result is not None: old_result = None
watch.result.Update( message[ 'body' ] ) if '_result' in watch:
else: old_result = watch[ '_result' ]
watch.result = WatchResult( message[ 'body' ] )
if ( watch.result.IsExpandable() and result = message[ 'body' ]
watch.result.IsExpanded() ): watch[ '_result' ] = result
if old_result:
if '_expanded' in old_result:
result[ '_expanded' ] = old_result[ '_expanded' ]
result[ '_old_variables' ] = old_result.get( '_variables', [] )
if ( result.get( 'variablesReference', 0 ) > 0 and
result.get( '_expanded', False ) ):
self._connection.DoRequest( partial( self._ConsumeVariables, self._connection.DoRequest( partial( self._ConsumeVariables,
self._watch.draw, self._watch.draw,
watch.result ), { result ), {
'command': 'variables', 'command': 'variables',
'arguments': { 'arguments': {
'variablesReference': watch.result.VariablesReference(), 'variablesReference': result[ 'variablesReference' ]
}, },
} ) } )
self._DrawWatches() self._DrawWatches()
def _WatchExpressionFailed( self, reason: str, watch: Watch ): def ExpandVariable( self ):
if watch.result is not None: if vim.current.window == self._vars.win:
# We already have a result for this watch. Wut ?
return
watch.result = WatchFailure( reason )
self._DrawWatches()
def _GetVariable( self, buf = None, line_num = None ):
none = ( None, None )
if buf is None:
buf = vim.current.buffer
if line_num is None:
line_num = vim.current.window.cursor[ 0 ]
if buf == self._vars.buf:
view = self._vars view = self._vars
elif buf == self._watch.buf: elif vim.current.window == self._watch.win:
view = self._watch view = self._watch
elif ( self._variable_eval_view is not None
and buf == self._variable_eval_view.buf ):
view = self._variable_eval_view
else: else:
return none
if line_num not in view.lines:
return none
return view.lines[ line_num ], view
def ExpandVariable( self, buf = None, line_num = None ):
variable, view = self._GetVariable( buf, line_num )
if variable is None:
return return
if variable.IsExpanded(): current_line = vim.current.window.cursor[ 0 ]
if current_line not in view.lines:
return
variable = view.lines[ current_line ]
if '_variables' in variable:
# Collapse # Collapse
variable.expanded = Expandable.COLLAPSED_BY_USER del variable[ '_variables' ]
variable[ '_expanded' ] = False
view.draw() view.draw()
return return
if not variable.IsExpandable(): if variable.get( 'variablesReference', 0 ) <= 0:
return return
variable.expanded = Expandable.EXPANDED_BY_USER variable[ '_expanded' ] = True
self._connection.DoRequest( partial( self._ConsumeVariables, self._connection.DoRequest( partial( self._ConsumeVariables,
view.draw, view.draw,
variable ), { variable ), {
'command': 'variables', 'command': 'variables',
'arguments': { 'arguments': {
'variablesReference': variable.VariablesReference() 'variablesReference': variable[ 'variablesReference' ]
}, },
} ) } )
def SetVariableValue( self, new_value = None, buf = None, line_num = None ): def _DrawVariables( self, view, variables, indent ):
variable: Variable
view: View
if not self._server_capabilities.get( 'supportsSetVariable' ):
return
variable, view = self._GetVariable( buf, line_num )
if variable is None:
return
if not variable.IsContained():
return
if new_value is None:
new_value = utils.AskForInput( 'New Value: ',
variable.variable.get( 'value', '' ),
completion = 'expr' )
if new_value is None:
return
def handler( message ):
# Annoyingly the response to setVariable request doesn't return a
# Variable, but some part of it, so take a copy of the existing Variable
# dict and update it, then call its update method with the updated copy.
new_variable = dict( variable.variable )
new_variable.update( message[ 'body' ] )
# Clear any existing known children (FIXME: Is this the right thing to do)
variable.variables = None
# If the variable is expanded, re-request its children
if variable.IsExpanded():
self._connection.DoRequest( partial( self._ConsumeVariables,
view.draw,
variable ), {
'command': 'variables',
'arguments': {
'variablesReference': variable.VariablesReference()
},
} )
variable.Update( new_variable )
view.draw()
def failure_handler( reason, message ):
utils.UserMessage( f'Cannot set value: { reason }', error = True )
self._connection.DoRequest( handler, {
'command': 'setVariable',
'arguments': {
'variablesReference': variable.container.VariablesReference(),
'name': variable.variable[ 'name' ],
'value': new_value
},
}, failure_handler = failure_handler )
def _DrawVariables( self, view, variables, indent, is_short = False ):
assert indent > 0
for variable in variables: for variable in variables:
text = ''
if is_short:
text = '{indent}{icon} {name}: {value}'.format(
# We borrow 1 space of indent to draw the change marker
indent = ' ' * ( indent - 1 ),
icon = '+' if ( variable.IsExpandable()
and not variable.IsExpanded() ) else '-',
name = variable.variable.get( 'name', '' ),
value = variable.variable.get( 'value', '<unknown>' )
)
else:
text = '{indent}{marker}{icon} {name} ({type_}): {value}'.format(
# We borrow 1 space of indent to draw the change marker
indent = ' ' * ( indent - 1 ),
marker = '*' if variable.changed else ' ',
icon = '+' if ( variable.IsExpandable()
and not variable.IsExpanded() ) else '-',
name = variable.variable.get( 'name', '' ),
type_ = variable.variable.get( 'type', '' ),
value = variable.variable.get( 'value', '<unknown>' )
)
line = utils.AppendToBuffer( line = utils.AppendToBuffer(
view.buf, view.win.buffer,
text.split( '\n' ) '{indent}{icon} {name} ({type_}): {value}'.format(
) indent = ' ' * indent,
icon = '+' if ( variable.get( 'variablesReference', 0 ) > 0 and
'_variables' not in variable ) else '-',
name = variable[ 'name' ],
type_ = variable.get( 'type', '<unknown type>' ),
value = variable.get( 'value', '<unknown value>' ) ).split( '\n' ) )
view.lines[ line ] = variable view.lines[ line ] = variable
if variable.ShouldDrawDrillDown(): if '_variables' in variable:
self._DrawVariables( view, variable.variables, indent + 2, is_short ) self._DrawVariables( view, variable[ '_variables' ], indent + 2 )
def _DrawScopes( self ): def _DrawScopes( self ):
# FIXME: The drawing is dumb and draws from scratch every time. This is # FIXME: The drawing is dumb and draws from scratch every time. This is
# simple and works and makes sure the line-map is always correct. # simple and works and makes sure the line-map is always correct.
# However it is pretty inefficient. # However it is really inefficient, and makes it so that expanded results
# are collapsed on every step.
self._vars.lines.clear() self._vars.lines.clear()
with utils.RestoreCursorPosition(): with utils.RestoreCursorPosition():
with utils.ModifiableScratchBuffer( self._vars.buf ): with utils.ModifiableScratchBuffer( self._vars.win.buffer ):
utils.ClearBuffer( self._vars.buf ) utils.ClearBuffer( self._vars.win.buffer )
for scope in self._scopes: for scope in self._scopes:
self._DrawScope( 0, scope ) self._DrawScope( 0, scope )
def _DrawWatches( self ): def _DrawWatches( self ):
# FIXME: The drawing is dumb and draws from scratch every time. This is # FIXME: The drawing is dumb and draws from scratch every time. This is
# simple and works and makes sure the line-map is always correct. # simple and works and makes sure the line-map is always correct.
# However it is pretty inefficient. # However it is really inefficient, and makes it so that expanded results
# are collapsed on every step.
self._watch.lines.clear() self._watch.lines.clear()
with utils.RestoreCursorPosition(): with utils.RestoreCursorPosition():
with utils.ModifiableScratchBuffer( self._watch.buf ): with utils.ModifiableScratchBuffer( self._watch.win.buffer ):
utils.ClearBuffer( self._watch.buf ) utils.ClearBuffer( self._watch.win.buffer )
utils.AppendToBuffer( self._watch.buf, 'Watches: ----' ) utils.AppendToBuffer( self._watch.win.buffer, 'Watches: ----' )
for watch in self._watches: for watch in self._watches:
line = utils.AppendToBuffer( self._watch.buf, line = utils.AppendToBuffer( self._watch.win.buffer,
'Expression: ' 'Expression: ' + watch[ 'expression' ] )
+ watch.expression[ 'expression' ] ) watch[ '_line' ] = line
watch.line = line self._DrawWatchResult( 2, watch )
self._DrawWatchResult( self._watch, 2, watch )
def _DrawScope( self, indent, scope ): def _DrawScope( self, indent, scope ):
icon = '+' if scope.IsExpandable() and not scope.IsExpanded() else '-' icon = '+' if ( scope.get( 'variablesReference', 0 ) > 0 and
'_variables' not in scope ) else '-'
line = utils.AppendToBuffer( self._vars.buf, line = utils.AppendToBuffer( self._vars.win.buffer,
'{0}{1} Scope: {2}'.format( '{0}{1} Scope: {2}'.format( ' ' * indent,
' ' * indent, icon,
icon, scope[ 'name' ] ) )
scope.scope[ 'name' ] ) )
self._vars.lines[ line ] = scope self._vars.lines[ line ] = scope
if scope.ShouldDrawDrillDown(): if '_variables' in scope:
indent += 2 indent += 2
self._DrawVariables( self._vars, scope.variables, indent ) self._DrawVariables( self._vars, scope[ '_variables' ], indent )
def _DrawWatchResult( self, view, indent, watch, is_short = False ): def _DrawWatchResult( self, indent, watch ):
if not watch.result: if '_result' not in watch:
return return
assert is_short or indent > 0 result = watch[ '_result' ]
if is_short: icon = '+' if ( result.get( 'variablesReference', 0 ) > 0 and
# The first result is always expanded in a hover (short format) '_variables' not in result ) else '-'
icon = ''
marker = ''
leader = ''
else:
icon = '+' if ( watch.result.IsExpandable() and
not watch.result.IsExpanded() ) else '-'
marker = '*' if watch.result.changed else ' '
leader = ' Result: '
line = '{indent}{marker}{icon}{leader}{result}'.format( result_str = result[ 'result' ]
# We borrow 1 space of indent to draw the change marker if result_str is None:
indent = ' ' * ( indent - 1 ), result_str = 'null'
marker = marker,
icon = icon,
leader = leader,
result = watch.result.result.get( 'result', '<unknown>' ) )
line = utils.AppendToBuffer( view.buf, line.split( '\n' ) ) line = '{0}{1} Result: {2} '.format( ' ' * indent, icon, result_str )
view.lines[ line ] = watch.result line = utils.AppendToBuffer( self._watch.win.buffer, line.split( '\n' ) )
self._watch.lines[ line ] = result
if watch.result.ShouldDrawDrillDown(): if '_variables' in result:
self._DrawVariables( view, watch.result.variables, indent + 2, is_short ) indent = 4
self._DrawVariables( self._watch, result[ '_variables' ], indent )
def _ConsumeVariables( self, draw, parent, message ): def _ConsumeVariables( self, draw, parent, message ):
new_variables = [] for variable in message[ 'body' ][ 'variables' ]:
for variable_body in message[ 'body' ][ 'variables' ]: if '_variables' not in parent:
if parent.variables is None: parent[ '_variables' ] = []
parent.variables = []
parent[ '_variables' ].append( variable )
# If the variable was previously expanded, expand it again
for index, v in enumerate( parent.get( '_old_variables', [] ) ):
if v[ 'name' ] == variable[ 'name' ]:
if ( v.get( '_expanded', False ) and
variable.get( 'variablesReference', 0 ) > 0 ):
variable[ '_expanded' ] = True
variable[ '_old_variables' ] = v.get( '_variables', [] )
self._connection.DoRequest( partial( self._ConsumeVariables,
draw,
variable ), {
'command': 'variables',
'arguments': {
'variablesReference': variable[ 'variablesReference' ]
},
} )
# Find the variable in parent
found = False
for index, v in enumerate( parent.variables ):
if v.variable[ 'name' ] == variable_body[ 'name' ]:
variable = v
found = True
break break
if not found:
variable = Variable( parent, variable_body )
else:
variable.Update( variable_body )
new_variables.append( variable ) if '_old_variables' in parent:
del parent[ '_old_variables' ]
if variable.IsExpandable() and variable.IsExpanded():
self._connection.DoRequest( partial( self._ConsumeVariables,
draw,
variable ), {
'command': 'variables',
'arguments': {
'variablesReference': variable.VariablesReference()
},
} )
parent.variables = new_variables
draw() draw()
def SetSyntax( self, syntax ): def ShowBalloon( self, frame, expression ):
# TODO: Switch to View.syntax if not self._connection:
self._current_syntax = utils.SetSyntax( self._current_syntax, return
syntax,
self._vars.buf, def handler( message ):
self._watch.buf ) # TODO: this result count be expandable, but we have no way to allow the
# vim: sw=2 # user to interact with the balloon to expand it.
body = message[ 'body' ]
result = body[ 'result' ]
if result is None:
result = 'null'
display = [
'Type: ' + body.get( 'type', '<unknown>' ),
'Value: ' + result
]
utils.DisplayBaloon( self._is_term, display )
def failure_handler( reason, message ):
display = [ reason ]
utils.DisplayBaloon( self._is_term, display )
self._connection.DoRequest( handler, {
'command': 'evaluate',
'arguments': {
'expression': expression,
'frameId': frame[ 'id' ],
'context': 'hover',
}
}, failure_handler )

View file

@ -1,97 +0,0 @@
# Copyright © 2020 Kyle Simpson <getify@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
"""A port of the `JSON-minify` utility to the Python language.
Based on JSON.minify.js: https://github.com/getify/JSON.minify
Contributers:
- Gerald Storer
- Contributed original version
- Felipe Machado
- Performance optimization
- Pradyun S. Gedam
- Conditions and variable names changed
- Reformatted tests and moved to separate file
- Made into a PyPI Package
"""
import re
# Vimspector modification: strip_space defaults to False; we don't actually want
# to minify - we just want to strip comments.
def minify(string, strip_space=False):
tokenizer = re.compile('"|(/\*)|(\*/)|(//)|\n|\r')
end_slashes_re = re.compile(r'(\\)*$')
in_string = False
in_multi = False
in_single = False
new_str = []
index = 0
for match in re.finditer(tokenizer, string):
if not (in_multi or in_single):
tmp = string[index:match.start()]
if not in_string and strip_space:
# replace white space as defined in standard
tmp = re.sub('[ \t\n\r]+', '', tmp)
new_str.append(tmp)
elif not strip_space:
# Replace comments with white space so that the JSON parser reports
# the correct column numbers on parsing errors.
new_str.append(' ' * (match.start() - index))
index = match.end()
val = match.group()
if val == '"' and not (in_multi or in_single):
escaped = end_slashes_re.search(string, 0, match.start())
# start of string or unescaped quote character to end string
if not in_string or (escaped is None or len(escaped.group()) % 2 == 0): # noqa
in_string = not in_string
index -= 1 # include " character in next catch
elif not (in_string or in_multi or in_single):
if val == '/*':
in_multi = True
elif val == '//':
in_single = True
elif val == '*/' and in_multi and not (in_string or in_single):
in_multi = False
if not strip_space:
new_str.append(' ' * len(val))
elif val in '\r\n' and not (in_multi or in_string) and in_single:
in_single = False
elif not ((in_multi or in_single) or (val in ' \r\n\t' and strip_space)): # noqa
new_str.append(val)
if not strip_space:
if val in '\r\n':
new_str.append(val)
elif in_multi or in_single:
new_str.append(' ' * len(val))
new_str.append(string[index:])
return ''.join(new_str)

190
run_tests
View file

@ -1,130 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
SetBaseDir() { RUN_VIM="vim --clean --not-a-term"
BASEDIR=$1
if [[ ! $BASEDIR = /* ]]; then
# Relative
BASEDIR=$(pwd)/${BASEDIR}
fi
}
SetBaseDir $(dirname $0)
INSTALL=0
UPDATE=0
INSTALLER_ARGS=''
RUN_VIM="vim -N --clean --not-a-term"
RUN_TEST="${RUN_VIM} -S lib/run_test.vim" RUN_TEST="${RUN_VIM} -S lib/run_test.vim"
BASEDIR_CMD='py3 pass'
# 1 is stdout
out_fd=1
while [ -n "$1" ]; do
case "$1" in
"--basedir"|"--base-dir"|"--test-base")
shift
SetBaseDir $1
shift
BASEDIR_CMD="let g:vimspector_base_dir='${BASEDIR}'"
;;
"--install")
INSTALL=1
shift
;;
"--install-method")
shift
INSTALL=$1
shift
;;
"--update"|"--upgrade")
UPDATE=1
shift
;;
"--report")
shift
VIMSPECTOR_TEST_STDOUT=$1
shift
;;
"--quiet")
shift
# send the output to /dev/null
# https://stackoverflow.com/a/47553900
# Note we can't use {out_fd} here because the bash version in CI is too
# old on macOS
out_fd=3
exec 3>/dev/null
INSTALLER_ARGS="${INSTALLER_ARGS} --quiet"
;;
"--")
shift
break
;;
"--help")
shift
echo "$(basename $0) [--basedir <basedir>] [--report output] [--quiet] [--install] <optional list of tests in form file:func>"
echo ""
echo " --basedir <basedir> path to runtime directory like the optino to install_gadget.py"
echo " --install run install_gadget.py, useful with --basedir"
echo " --report <messages|all> which logs to dump to stdout after a test"
echo " --quiet suppress vim's stdout"
echo "e.g.: "
echo " - run all tests: $0"
echo " - run specific tests script: $0 signature_help.test.vim"
echo " - run specific tests fun: $0 signature_help.test.vim:Test_signatures_TopLine\(\)"
echo " - run all tests in a clean env: $0 --basedir \$(mktemp -d) --install"
exit 0
;;
*)
break
;;
esac
done
# We use fd 3 for vim's output. Send it to stdout if not already redirected
# Note: can't use ${out_fd} in a redirect because redirects happen before
# variable substitution
if [ "${out_fd}" = "1" ]; then
exec 3>&1
fi
if [ "$INSTALL" = "1" ] || [ "$INSTALL" = "script" ]; then
if ! python3 $(dirname $0)/install_gadget.py \
--basedir ${BASEDIR} \
${INSTALLER_ARGS} \
--all \
--force-enable-csharp; then
echo "Script installation reported errors" >&2
exit 1
fi
fi
if [ "$INSTALL" = "1" ] || [ "$INSTALL" = "vim" ]; then
if ! $RUN_VIM -u $(dirname $0)/tests/vimrc \
--cmd "${BASEDIR_CMD}" \
-c 'autocmd User VimspectorInstallSuccess qa!' \
-c 'autocmd User VimspectorInstallFailed cquit!' \
-c "VimspectorInstall --all netcoredbg"; then
echo "Vim installation reported errors" >&2
exit 1
fi
fi
if [ "$UPDATE" = "1" ]; then
if ! $RUN_VIM -u $(dirname $0)/tests/vimrc \
--cmd "${BASEDIR_CMD}" \
-c 'autocmd User VimspectorInstallSuccess qa!' \
-c 'autocmd User VimspectorInstallFailed cquit!' \
-c "VimspectorUpdate"; then
echo "Vim update reported errors" >&2
exit 1
fi
fi
if [ -z "$VIMSPECTOR_MIMODE" ]; then if [ -z "$VIMSPECTOR_MIMODE" ]; then
if which lldb >/dev/null 2>&1; then if which -s lldb; then
export VIMSPECTOR_MIMODE=lldb export VIMSPECTOR_MIMODE=lldb
elif which gdb >/dev/null 2>&1; then elif which -s gdb; then
export VIMSPECTOR_MIMODE=gdb export VIMSPECTOR_MIMODE=gdb
else else
echo "Couldn't guess VIMSPECTOR_MIMODE. Need lldb or gdb in path" echo "Couldn't guess VIMSPECTOR_MIMODE. Need lldb or gdb in path"
@ -132,27 +14,18 @@ if [ -z "$VIMSPECTOR_MIMODE" ]; then
fi fi
fi fi
echo "Testing with:" echo "Testing with VIMSPECTOR_MIMODE=$VIMSPECTOR_MIMODE"
echo " * VIMSPECTOR_MIMODE=$VIMSPECTOR_MIMODE"
echo " * RUN_VIM=$RUN_VIM"
echo " * RUN_TEST=$RUN_TEST"
echo " * BASEDIR=$BASEDIR"
echo " * BASEDIR_CMD=$BASEDIR_CMD"
echo "%SETUP - Building test programs..." echo "%SETUP - Building test programs..."
set -e set -e
pushd tests/testdata/cpp/simple pushd tests/testdata/cpp/simple
make clean all make clean simple
popd
pushd support/test/csharp
dotnet build
popd popd
set +e set +e
echo "%DONE - built test programs" echo "%DONE - built test programs"
# Start pushd tests > /dev/null
pushd $(dirname $0)/tests > /dev/null
echo "Running Vimspector Vim tests" echo "Running Vimspector Vim tests"
RESULT=0 RESULT=0
@ -166,62 +39,23 @@ fi
for t in ${TESTS}; do for t in ${TESTS}; do
echo "" echo ""
echo "%RUN: $t" echo "%RUN: $t"
rm -f messages debuglog
# split on : into fileName and testName # split on : into fileName and testName
IFS=: read -s t T <<< "$t" IFS=: read -s t T <<< "$t"
TESTLOGDIR=${BASEDIR}/tests/logs/$t if ${RUN_TEST} --cmd 'au SwapExists * let v:swapchoice = "e"' $t $T; then
if ${RUN_TEST} --cmd "${BASEDIR_CMD}" \
--cmd 'au SwapExists * let v:swapchoice = "e"' $t $T \
>&3\
&& [ -f $t.res ]; then
echo "%PASS: $t PASSED" echo "%PASS: $t PASSED"
else else
echo "%FAIL: $t FAILED - see $TESTLOGDIR" cat messages
echo "%FAIL: $t FAILED"
RESULT=1 RESULT=1
fi fi
rm -rf $TESTLOGDIR
mkdir -p $TESTLOGDIR
${RUN_VIM} --version > ${TESTLOGDIR}/vimversion
if [ "$VIMSPECTOR_TEST_STDOUT" = "messages" ]; then
if [ -f messages ]; then
echo "%MESSAGES"
cat messages
echo "%END"
else
echo "%MESSAGES"
echo "No messages found"
echo "%END"
fi
fi
for l in messages debuglog test.log *.testlog; do
# In CI we can't view the output files, so we just have to cat them
if [ -f $l ]; then
if [ "$VIMSPECTOR_TEST_STDOUT" = "all" ]; then
echo ""
echo ""
echo "*** START: $l ***"
cat $l
echo "*** END: $l ***"
fi
mv $l $TESTLOGDIR
fi
done
rm -f $t.res
done done
# close out_fd if it's not stdout/stderr/
(( out_fd > 2 )) && exec 3>&-
echo "Done running tests"
popd > /dev/null popd > /dev/null
echo "" echo ""
echo "All done. Exit with ${RESULT}" echo "All done."
exit $RESULT exit $RESULT

View file

@ -1,161 +0,0 @@
" setup boilerplate to make this file usable with vim -Nu <tihs file> {{{
scriptencoding utf-8
execute 'source' expand( '<sfile>:p:h' ) . '/minimal_vimrc'
set noequalalways
let mapleader = ','
let maplocalleader = "\<Space>"
" }}}
" Custom Layout {{{
function! s:CustomiseUI()
let wins = g:vimspector_session_windows
" Close the Variables window
if has( 'nvim' )
" No win_execute in neovim
call win_gotoid( wins.variables )
quit
else
call win_execute( wins.variables, 'q' )
endif
" Put the stack trace at the top of the "left bar" (rotate)
call win_gotoid( wins.stack_trace )
wincmd r
" Make the left column at least 70 chars
70wincmd |
" Make the code window at least 80 chars
call win_gotoid( wins.code )
80wincmd |
" Make the output window 10 lines high and right at the top of the screen
call win_gotoid( wins.output )
10wincmd _
wincmd K
endfunction
function s:SetUpTerminal()
if !has_key( g:vimspector_session_windows, 'terminal' )
" There's a neovim bug which means that this doesn't work in neovim
return
endif
let terminal_win = g:vimspector_session_windows.terminal
" Make the terminal window at most 80 columns wide, ensuring there is enough
" sapce for our code window (80 columns) and the left bar (70 columns)
" Padding is 2 for the 2 vertical split markers and 2 for the sign column in
" the code window.
let left_bar = 70
let code = 80
let padding = 4
let cols = max( [ min( [ &columns - left_bar - code - padding, 80 ] ), 10 ] )
call win_gotoid( terminal_win )
execute string(cols) . 'wincmd |'
endfunction
function! s:CustomiseWinBar()
call win_gotoid( g:vimspector_session_windows.code)
aunmenu WinBar
nnoremenu WinBar.▷\ ᶠ⁵ :call vimspector#Continue()<CR>
nnoremenu WinBar.↷\ ᶠ¹⁰ :call vimspector#StepOver()<CR>
nnoremenu WinBar.↓\ ᶠ¹¹ :call vimspector#StepInto()<CR>
nnoremenu WinBar.↑\ ˢᶠ¹¹ :call vimspector#StepOut()<CR>
nnoremenu WinBar.❘❘\ ᶠ⁶ :call vimspector#Pause()<CR>
nnoremenu WinBar.□\ ˢᶠ⁵ :call vimspector#Stop()<CR>
nnoremenu WinBar.⟲\ ᶜˢᶠ⁵ :call vimspector#Restart()<CR>
nnoremenu WinBar.✕\ ᶠ⁸ :call vimspector#Reset()<CR>
endfunction
augroup TestUICustomistaion
autocmd!
autocmd User VimspectorUICreated call s:CustomiseUI()
autocmd User VimspectorTerminalOpened call s:SetUpTerminal()
autocmd User VimspectorUICreated call s:CustomiseWinBar()
augroup END
" }}}
" Custom sign priority {{{
let g:vimspector_sign_priority = {
\ 'vimspectorBP': 3,
\ 'vimspectorBPCond': 2,
\ 'vimspectorBPDisabled': 1,
\ 'vimspectorPC': 999,
\ }
" }}}
" Custom mappings while debuggins {{{
let s:mapped = {}
function! s:OnJumpToFrame() abort
if has_key( s:mapped, string( bufnr() ) )
return
endif
nmap <silent> <buffer> <LocalLeader>dn <Plug>VimspectorStepOver
nmap <silent> <buffer> <LocalLeader>ds <Plug>VimspectorStepInto
nmap <silent> <buffer> <LocalLeader>df <Plug>VimspectorStepOut
nmap <silent> <buffer> <LocalLeader>dc <Plug>VimspectorContinue
nmap <silent> <buffer> <LocalLeader>di <Plug>VimspectorBalloonEval
xmap <silent> <buffer> <LocalLeader>di <Plug>VimspectorBalloonEval
let s:mapped[ string( bufnr() ) ] = { 'modifiable': &modifiable }
setlocal nomodifiable
endfunction
function! s:OnDebugEnd() abort
let original_buf = bufnr()
let hidden = &hidden
try
set hidden
for bufnr in keys( s:mapped )
try
execute 'noautocmd buffer' bufnr
silent! nunmap <buffer> <LocalLeader>dn
silent! nunmap <buffer> <LocalLeader>ds
silent! nunmap <buffer> <LocalLeader>df
silent! nunmap <buffer> <LocalLeader>dc
silent! nunmap <buffer> <LocalLeader>di
silent! xunmap <buffer> <LocalLeader>di
let &l:modifiable = s:mapped[ bufnr ][ 'modifiable' ]
endtry
endfor
finally
execute 'noautocmd buffer' original_buf
let &hidden = hidden
endtry
let s:mapped = {}
endfunction
augroup TestCustomMappings
au!
autocmd User VimspectorJumpedToFrame call s:OnJumpToFrame()
autocmd User VimspectorDebugEnded call s:OnDebugEnd()
augroup END
" }}}
" Custom mappings for special buffers {{{
let g:vimspector_mappings = {
\ 'stack_trace': {},
\ 'variables': {
\ 'set_value': [ '<Tab>', '<C-CR>', 'C' ],
\ }
\ }
" }}}
" vim: foldmethod=marker

View file

@ -1,8 +0,0 @@
# Manually updating shipped gadgets
Download the gadget files manuall from their official source into this dir.
Run `./checksum.py <list of files>` to get the checksums.
Update ../../python3/vimspector/gadgets.py with the new version and the
checksums.

View file

@ -1,13 +0,0 @@
#!/usr/bin/env python3
import hashlib
import sys
def GetChecksumSHA254( file_path ):
with open( file_path, 'rb' ) as existing_file:
return hashlib.sha256( existing_file.read() ).hexdigest()
for arg in sys.argv[ 1: ]:
print( f"{ arg } = { GetChecksumSHA254( arg ) }" )

View file

@ -1,4 +0,0 @@
let s:vimspector_path = expand( '<sfile>:p:h:h' )
let g:vimspector_enable_mappings = 'HUMAN'
exe 'source ' . s:vimspector_path . '/tests/vimrc'

View file

@ -1,15 +0,0 @@
{
"$schema": "https://puremourning.github.io/vimspector/schema/vimspector.schema.json",
"configurations": {
"Run Current Script": {
"adapter": "vscode-bash",
"autoselect": false,
"configuration": {
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"args": [ "*${args}" ]
}
}
}
}

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
function Test() {
echo $1
}
for i in "$@"; do
Test $i
done

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
php -S localhost:1234 -t www

View file

@ -6,12 +6,5 @@ $( document ).ready( function() {
return msg; return msg;
}; };
var obj = { alert( 'test: ' + getMessage() );
test: getMessage(),
toast: function() { return 'egg'; },
spam: 'ham'
};
alert( 'test: ' + obj.test );
alert( 'toast: ' + obj.toast() );
} ); } );

View file

@ -1,27 +1,36 @@
{ {
"adapters": {
"cppdbg": {
"name": "cppdbg",
"command": [ "$HOME/.vscode/extensions/ms-vscode.cpptools-0.20.1/debugAdapters/OpenDebugAD7" ],
"attach": {
"pidProperty": "processId",
"pidSelect": "ask"
}
}
},
"configurations": { "configurations": {
"CodeLLDB": { "simple_c_program - Launch": {
"adapter": "CodeLLDB", "adapter": "cppdbg",
"configuration": { "configuration": {
"name": "ms Launch",
"type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceRoot}/test", "program": "${workspaceRoot}/test",
"args": [],
"cwd": "${workspaceRoot}",
"environment": [],
"MIMode": "lldb",
"stopAtEntry": true "stopAtEntry": true
} }
}, },
"lldb-vscode": { "simple_c_program - Attach": {
"adapter": "lldb-vscode", "adapter": "cppdbg",
"configuration": { "configuration": {
"request": "launch", "name": "(lldb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "${workspaceRoot}/test", "program": "${workspaceRoot}/test",
"stopAtEntry": true
}
},
"cpptools": {
"adapter": "vscode-cpptools",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/test",
"stopOnEntry": true,
"MIMode": "lldb" "MIMode": "lldb"
} }
} }

View file

@ -1,4 +1,4 @@
def Settings( **kwargs ): def Settings( **kwargs ):
return { return {
'flags': [ '-x', 'c++', '-Wall', '-Wextra', '-std=c++17' ] 'flags': [ '-x', 'c++', '-Wall', '-Wextra' ]
} }

View file

@ -1,5 +1,3 @@
#include <cstdio>
#include <cstdlib>
#include <iostream> #include <iostream>
namespace Test namespace Test
@ -35,8 +33,6 @@ int main ( int argc, char ** argv )
{ {
int x{ 10 }; int x{ 10 };
printf( "HOME: %s\n", getenv( "HOME" ) );
Test::TestStruct t{ true, {99} }; Test::TestStruct t{ true, {99} };
foo( t ); foo( t );
} }

View file

@ -1,3 +1,2 @@
bin/ bin/
obj/Debug obj/Debug
obj/

View file

@ -1,57 +1,21 @@
{ {
"adapters": { "configurations": {
"netcoredbg-debuglog": { "launch - netcoredbg": {
"attach": { "adapter": "netcoredbg",
"pidProperty": "processId", "configuration": {
"pidSelect": "ask" "request": "launch",
"program": "${workspaceRoot}/bin/Debug/netcoreapp2.2/csharp.dll",
"args": [],
"stopAtEntry": true
}
}, },
"command": [ "launch - mono": {
"${gadgetDir}/netcoredbg/netcoredbg", "adapter": "vscode-mono-debug",
"--interpreter=vscode", "configuration": {
"--engineLogging=${workspaceRoot}/netcoredbg.engine.log", "request": "launch",
"--log=${workspaceRoot}/netcoredbg.log" "program": "${workspaceRoot}/Program.exe"
], }
"configuration": {
"cwd": "${workspaceRoot}"
},
"name": "netcoredbg"
}
},
"configurations": {
//
// NOTE:
// If you add to this, you must update tests/get_configurations.test.vim
//
"launch - netcoredbg": {
"adapter": "netcoredbg",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/bin/Debug/netcoreapp3.1/csharp.dll",
"args": [],
"stopAtEntry": false
}
},
"launch - netcoredbg - with debug log": {
"adapter": "netcoredbg-debuglog",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/bin/Debug/netcoreapp3.1/csharp.dll",
"args": [],
"stopAtEntry": false
}
},
"launch - mono": {
"adapter": "vscode-mono-debug",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/Program.exe",
"console": "integratedTerminal",
"cwd": "${workspaceRoot}",
"args": [],
"env": {}
} }
} }
}
} }

View file

@ -2,36 +2,11 @@
namespace csharp namespace csharp
{ {
class Program class Program
{
string toaster = "Making round of toast";
static int max_bread = 100;
int bread = max_bread;
void PrintToast( int r ) {
int this_round = ( max_bread - bread - r);
Console.WriteLine( this.toaster + ": " + this_round );
}
void MakeToast( int rounds ) {
if (this.bread - rounds < 0) {
throw new Exception( "No moar bread!" );
}
this.bread -= rounds;
for (int r = 0; r < rounds; ++r) {
this.PrintToast( r );
}
Console.WriteLine( "Got only " + this.bread + " left" );
}
static void Main(string[] args)
{ {
Program p = new Program(); static void Main(string[] args)
for (int x = 1; x < 10; ++ x) { {
p.MakeToast( x ); Console.WriteLine("Hello World!");
} }
} }
}
} }

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26124.0 VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp", "csharp.csproj", "{91DB205F-E422-430B-BBB8-955110C7B3B6}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -17,18 +15,4 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|x64.ActiveCfg = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|x64.Build.0 = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Debug|x86.Build.0 = Debug|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|Any CPU.Build.0 = Release|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|x64.ActiveCfg = Release|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|x64.Build.0 = Release|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|x86.ActiveCfg = Release|Any CPU
{91DB205F-E422-430B-BBB8-955110C7B3B6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal EndGlobal

View file

@ -0,0 +1,5 @@
{
"version": 1,
"dgSpecHash": "6/vdr7YprlSIoQecv/nNuLNflFpO0X7eN7jHUinZTsgian9nYpmHMWirsDWMi5l+29TH+Qy8O/QfaB/48QtjRQ==",
"success": true
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">/Users/ben/.vim/bundle/vimspector/support/test/csharp/obj/project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/Users/ben/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/Users/ben/.nuget/packages/;/usr/local/share/dotnet/sdk/NuGetFallbackFolder</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">4.9.4</NuGetToolVersion>
</PropertyGroup>
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.2.0/build/netcoreapp2.2/Microsoft.NETCore.App.props" Condition="Exists('/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.2.0/build/netcoreapp2.2/Microsoft.NETCore.App.props')" />
</ImportGroup>
</Project>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="/usr/local/share/dotnet/sdk/NuGetFallbackFolder/netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets" Condition="Exists('/usr/local/share/dotnet/sdk/NuGetFallbackFolder/netstandard.library/2.0.3/build/netstandard2.0/NETStandard.Library.targets')" />
<Import Project="/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.2.0/build/netcoreapp2.2/Microsoft.NETCore.App.targets" Condition="Exists('/usr/local/share/dotnet/sdk/NuGetFallbackFolder/microsoft.netcore.app/2.2.0/build/netcoreapp2.2/Microsoft.NETCore.App.targets')" />
</ImportGroup>
</Project>

View file

@ -0,0 +1,742 @@
{
"version": 3,
"targets": {
".NETCoreApp,Version=v2.2": {
"Microsoft.NETCore.App/2.2.0": {
"type": "package",
"dependencies": {
"Microsoft.NETCore.DotNetHostPolicy": "2.2.0",
"Microsoft.NETCore.Platforms": "2.2.0",
"Microsoft.NETCore.Targets": "2.0.0",
"NETStandard.Library": "2.0.3"
},
"compile": {
"ref/netcoreapp2.2/Microsoft.CSharp.dll": {},
"ref/netcoreapp2.2/Microsoft.VisualBasic.dll": {},
"ref/netcoreapp2.2/Microsoft.Win32.Primitives.dll": {},
"ref/netcoreapp2.2/System.AppContext.dll": {},
"ref/netcoreapp2.2/System.Buffers.dll": {},
"ref/netcoreapp2.2/System.Collections.Concurrent.dll": {},
"ref/netcoreapp2.2/System.Collections.Immutable.dll": {},
"ref/netcoreapp2.2/System.Collections.NonGeneric.dll": {},
"ref/netcoreapp2.2/System.Collections.Specialized.dll": {},
"ref/netcoreapp2.2/System.Collections.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.Annotations.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.DataAnnotations.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.EventBasedAsync.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.Primitives.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.TypeConverter.dll": {},
"ref/netcoreapp2.2/System.ComponentModel.dll": {},
"ref/netcoreapp2.2/System.Configuration.dll": {},
"ref/netcoreapp2.2/System.Console.dll": {},
"ref/netcoreapp2.2/System.Core.dll": {},
"ref/netcoreapp2.2/System.Data.Common.dll": {},
"ref/netcoreapp2.2/System.Data.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.Contracts.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.Debug.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.DiagnosticSource.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.FileVersionInfo.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.Process.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.StackTrace.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.TextWriterTraceListener.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.Tools.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.TraceSource.dll": {},
"ref/netcoreapp2.2/System.Diagnostics.Tracing.dll": {},
"ref/netcoreapp2.2/System.Drawing.Primitives.dll": {},
"ref/netcoreapp2.2/System.Drawing.dll": {},
"ref/netcoreapp2.2/System.Dynamic.Runtime.dll": {},
"ref/netcoreapp2.2/System.Globalization.Calendars.dll": {},
"ref/netcoreapp2.2/System.Globalization.Extensions.dll": {},
"ref/netcoreapp2.2/System.Globalization.dll": {},
"ref/netcoreapp2.2/System.IO.Compression.Brotli.dll": {},
"ref/netcoreapp2.2/System.IO.Compression.FileSystem.dll": {},
"ref/netcoreapp2.2/System.IO.Compression.ZipFile.dll": {},
"ref/netcoreapp2.2/System.IO.Compression.dll": {},
"ref/netcoreapp2.2/System.IO.FileSystem.DriveInfo.dll": {},
"ref/netcoreapp2.2/System.IO.FileSystem.Primitives.dll": {},
"ref/netcoreapp2.2/System.IO.FileSystem.Watcher.dll": {},
"ref/netcoreapp2.2/System.IO.FileSystem.dll": {},
"ref/netcoreapp2.2/System.IO.IsolatedStorage.dll": {},
"ref/netcoreapp2.2/System.IO.MemoryMappedFiles.dll": {},
"ref/netcoreapp2.2/System.IO.Pipes.dll": {},
"ref/netcoreapp2.2/System.IO.UnmanagedMemoryStream.dll": {},
"ref/netcoreapp2.2/System.IO.dll": {},
"ref/netcoreapp2.2/System.Linq.Expressions.dll": {},
"ref/netcoreapp2.2/System.Linq.Parallel.dll": {},
"ref/netcoreapp2.2/System.Linq.Queryable.dll": {},
"ref/netcoreapp2.2/System.Linq.dll": {},
"ref/netcoreapp2.2/System.Memory.dll": {},
"ref/netcoreapp2.2/System.Net.Http.dll": {},
"ref/netcoreapp2.2/System.Net.HttpListener.dll": {},
"ref/netcoreapp2.2/System.Net.Mail.dll": {},
"ref/netcoreapp2.2/System.Net.NameResolution.dll": {},
"ref/netcoreapp2.2/System.Net.NetworkInformation.dll": {},
"ref/netcoreapp2.2/System.Net.Ping.dll": {},
"ref/netcoreapp2.2/System.Net.Primitives.dll": {},
"ref/netcoreapp2.2/System.Net.Requests.dll": {},
"ref/netcoreapp2.2/System.Net.Security.dll": {},
"ref/netcoreapp2.2/System.Net.ServicePoint.dll": {},
"ref/netcoreapp2.2/System.Net.Sockets.dll": {},
"ref/netcoreapp2.2/System.Net.WebClient.dll": {},
"ref/netcoreapp2.2/System.Net.WebHeaderCollection.dll": {},
"ref/netcoreapp2.2/System.Net.WebProxy.dll": {},
"ref/netcoreapp2.2/System.Net.WebSockets.Client.dll": {},
"ref/netcoreapp2.2/System.Net.WebSockets.dll": {},
"ref/netcoreapp2.2/System.Net.dll": {},
"ref/netcoreapp2.2/System.Numerics.Vectors.dll": {},
"ref/netcoreapp2.2/System.Numerics.dll": {},
"ref/netcoreapp2.2/System.ObjectModel.dll": {},
"ref/netcoreapp2.2/System.Reflection.DispatchProxy.dll": {},
"ref/netcoreapp2.2/System.Reflection.Emit.ILGeneration.dll": {},
"ref/netcoreapp2.2/System.Reflection.Emit.Lightweight.dll": {},
"ref/netcoreapp2.2/System.Reflection.Emit.dll": {},
"ref/netcoreapp2.2/System.Reflection.Extensions.dll": {},
"ref/netcoreapp2.2/System.Reflection.Metadata.dll": {},
"ref/netcoreapp2.2/System.Reflection.Primitives.dll": {},
"ref/netcoreapp2.2/System.Reflection.TypeExtensions.dll": {},
"ref/netcoreapp2.2/System.Reflection.dll": {},
"ref/netcoreapp2.2/System.Resources.Reader.dll": {},
"ref/netcoreapp2.2/System.Resources.ResourceManager.dll": {},
"ref/netcoreapp2.2/System.Resources.Writer.dll": {},
"ref/netcoreapp2.2/System.Runtime.CompilerServices.VisualC.dll": {},
"ref/netcoreapp2.2/System.Runtime.Extensions.dll": {},
"ref/netcoreapp2.2/System.Runtime.Handles.dll": {},
"ref/netcoreapp2.2/System.Runtime.InteropServices.RuntimeInformation.dll": {},
"ref/netcoreapp2.2/System.Runtime.InteropServices.WindowsRuntime.dll": {},
"ref/netcoreapp2.2/System.Runtime.InteropServices.dll": {},
"ref/netcoreapp2.2/System.Runtime.Loader.dll": {},
"ref/netcoreapp2.2/System.Runtime.Numerics.dll": {},
"ref/netcoreapp2.2/System.Runtime.Serialization.Formatters.dll": {},
"ref/netcoreapp2.2/System.Runtime.Serialization.Json.dll": {},
"ref/netcoreapp2.2/System.Runtime.Serialization.Primitives.dll": {},
"ref/netcoreapp2.2/System.Runtime.Serialization.Xml.dll": {},
"ref/netcoreapp2.2/System.Runtime.Serialization.dll": {},
"ref/netcoreapp2.2/System.Runtime.dll": {},
"ref/netcoreapp2.2/System.Security.Claims.dll": {},
"ref/netcoreapp2.2/System.Security.Cryptography.Algorithms.dll": {},
"ref/netcoreapp2.2/System.Security.Cryptography.Csp.dll": {},
"ref/netcoreapp2.2/System.Security.Cryptography.Encoding.dll": {},
"ref/netcoreapp2.2/System.Security.Cryptography.Primitives.dll": {},
"ref/netcoreapp2.2/System.Security.Cryptography.X509Certificates.dll": {},
"ref/netcoreapp2.2/System.Security.Principal.dll": {},
"ref/netcoreapp2.2/System.Security.SecureString.dll": {},
"ref/netcoreapp2.2/System.Security.dll": {},
"ref/netcoreapp2.2/System.ServiceModel.Web.dll": {},
"ref/netcoreapp2.2/System.ServiceProcess.dll": {},
"ref/netcoreapp2.2/System.Text.Encoding.Extensions.dll": {},
"ref/netcoreapp2.2/System.Text.Encoding.dll": {},
"ref/netcoreapp2.2/System.Text.RegularExpressions.dll": {},
"ref/netcoreapp2.2/System.Threading.Overlapped.dll": {},
"ref/netcoreapp2.2/System.Threading.Tasks.Dataflow.dll": {},
"ref/netcoreapp2.2/System.Threading.Tasks.Extensions.dll": {},
"ref/netcoreapp2.2/System.Threading.Tasks.Parallel.dll": {},
"ref/netcoreapp2.2/System.Threading.Tasks.dll": {},
"ref/netcoreapp2.2/System.Threading.Thread.dll": {},
"ref/netcoreapp2.2/System.Threading.ThreadPool.dll": {},
"ref/netcoreapp2.2/System.Threading.Timer.dll": {},
"ref/netcoreapp2.2/System.Threading.dll": {},
"ref/netcoreapp2.2/System.Transactions.Local.dll": {},
"ref/netcoreapp2.2/System.Transactions.dll": {},
"ref/netcoreapp2.2/System.ValueTuple.dll": {},
"ref/netcoreapp2.2/System.Web.HttpUtility.dll": {},
"ref/netcoreapp2.2/System.Web.dll": {},
"ref/netcoreapp2.2/System.Windows.dll": {},
"ref/netcoreapp2.2/System.Xml.Linq.dll": {},
"ref/netcoreapp2.2/System.Xml.ReaderWriter.dll": {},
"ref/netcoreapp2.2/System.Xml.Serialization.dll": {},
"ref/netcoreapp2.2/System.Xml.XDocument.dll": {},
"ref/netcoreapp2.2/System.Xml.XPath.XDocument.dll": {},
"ref/netcoreapp2.2/System.Xml.XPath.dll": {},
"ref/netcoreapp2.2/System.Xml.XmlDocument.dll": {},
"ref/netcoreapp2.2/System.Xml.XmlSerializer.dll": {},
"ref/netcoreapp2.2/System.Xml.dll": {},
"ref/netcoreapp2.2/System.dll": {},
"ref/netcoreapp2.2/WindowsBase.dll": {},
"ref/netcoreapp2.2/mscorlib.dll": {},
"ref/netcoreapp2.2/netstandard.dll": {}
},
"build": {
"build/netcoreapp2.2/Microsoft.NETCore.App.props": {},
"build/netcoreapp2.2/Microsoft.NETCore.App.targets": {}
}
},
"Microsoft.NETCore.DotNetAppHost/2.2.0": {
"type": "package"
},
"Microsoft.NETCore.DotNetHostPolicy/2.2.0": {
"type": "package",
"dependencies": {
"Microsoft.NETCore.DotNetHostResolver": "2.2.0"
}
},
"Microsoft.NETCore.DotNetHostResolver/2.2.0": {
"type": "package",
"dependencies": {
"Microsoft.NETCore.DotNetAppHost": "2.2.0"
}
},
"Microsoft.NETCore.Platforms/2.2.0": {
"type": "package",
"compile": {
"lib/netstandard1.0/_._": {}
},
"runtime": {
"lib/netstandard1.0/_._": {}
}
},
"Microsoft.NETCore.Targets/2.0.0": {
"type": "package",
"compile": {
"lib/netstandard1.0/_._": {}
},
"runtime": {
"lib/netstandard1.0/_._": {}
}
},
"NETStandard.Library/2.0.3": {
"type": "package",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
},
"compile": {
"lib/netstandard1.0/_._": {}
},
"runtime": {
"lib/netstandard1.0/_._": {}
},
"build": {
"build/netstandard2.0/NETStandard.Library.targets": {}
}
}
}
},
"libraries": {
"Microsoft.NETCore.App/2.2.0": {
"sha512": "7z5l8Jp324S8bU8+yyWeYHXUFYvKyiI5lqS1dXgTzOx1H69Qbf6df12kCKlNX45LpMfCMd4U3M6p7Rl5Zk7SLA==",
"type": "package",
"path": "microsoft.netcore.app/2.2.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"Microsoft.NETCore.App.versions.txt",
"THIRD-PARTY-NOTICES.TXT",
"build/netcoreapp2.2/Microsoft.NETCore.App.PlatformManifest.txt",
"build/netcoreapp2.2/Microsoft.NETCore.App.props",
"build/netcoreapp2.2/Microsoft.NETCore.App.targets",
"microsoft.netcore.app.2.2.0.nupkg.sha512",
"microsoft.netcore.app.nuspec",
"ref/netcoreapp2.2/Microsoft.CSharp.dll",
"ref/netcoreapp2.2/Microsoft.CSharp.xml",
"ref/netcoreapp2.2/Microsoft.VisualBasic.dll",
"ref/netcoreapp2.2/Microsoft.VisualBasic.xml",
"ref/netcoreapp2.2/Microsoft.Win32.Primitives.dll",
"ref/netcoreapp2.2/Microsoft.Win32.Primitives.xml",
"ref/netcoreapp2.2/System.AppContext.dll",
"ref/netcoreapp2.2/System.Buffers.dll",
"ref/netcoreapp2.2/System.Buffers.xml",
"ref/netcoreapp2.2/System.Collections.Concurrent.dll",
"ref/netcoreapp2.2/System.Collections.Concurrent.xml",
"ref/netcoreapp2.2/System.Collections.Immutable.dll",
"ref/netcoreapp2.2/System.Collections.Immutable.xml",
"ref/netcoreapp2.2/System.Collections.NonGeneric.dll",
"ref/netcoreapp2.2/System.Collections.NonGeneric.xml",
"ref/netcoreapp2.2/System.Collections.Specialized.dll",
"ref/netcoreapp2.2/System.Collections.Specialized.xml",
"ref/netcoreapp2.2/System.Collections.dll",
"ref/netcoreapp2.2/System.Collections.xml",
"ref/netcoreapp2.2/System.ComponentModel.Annotations.dll",
"ref/netcoreapp2.2/System.ComponentModel.Annotations.xml",
"ref/netcoreapp2.2/System.ComponentModel.DataAnnotations.dll",
"ref/netcoreapp2.2/System.ComponentModel.EventBasedAsync.dll",
"ref/netcoreapp2.2/System.ComponentModel.EventBasedAsync.xml",
"ref/netcoreapp2.2/System.ComponentModel.Primitives.dll",
"ref/netcoreapp2.2/System.ComponentModel.Primitives.xml",
"ref/netcoreapp2.2/System.ComponentModel.TypeConverter.dll",
"ref/netcoreapp2.2/System.ComponentModel.TypeConverter.xml",
"ref/netcoreapp2.2/System.ComponentModel.dll",
"ref/netcoreapp2.2/System.ComponentModel.xml",
"ref/netcoreapp2.2/System.Configuration.dll",
"ref/netcoreapp2.2/System.Console.dll",
"ref/netcoreapp2.2/System.Console.xml",
"ref/netcoreapp2.2/System.Core.dll",
"ref/netcoreapp2.2/System.Data.Common.dll",
"ref/netcoreapp2.2/System.Data.Common.xml",
"ref/netcoreapp2.2/System.Data.dll",
"ref/netcoreapp2.2/System.Diagnostics.Contracts.dll",
"ref/netcoreapp2.2/System.Diagnostics.Contracts.xml",
"ref/netcoreapp2.2/System.Diagnostics.Debug.dll",
"ref/netcoreapp2.2/System.Diagnostics.Debug.xml",
"ref/netcoreapp2.2/System.Diagnostics.DiagnosticSource.dll",
"ref/netcoreapp2.2/System.Diagnostics.DiagnosticSource.xml",
"ref/netcoreapp2.2/System.Diagnostics.FileVersionInfo.dll",
"ref/netcoreapp2.2/System.Diagnostics.FileVersionInfo.xml",
"ref/netcoreapp2.2/System.Diagnostics.Process.dll",
"ref/netcoreapp2.2/System.Diagnostics.Process.xml",
"ref/netcoreapp2.2/System.Diagnostics.StackTrace.dll",
"ref/netcoreapp2.2/System.Diagnostics.StackTrace.xml",
"ref/netcoreapp2.2/System.Diagnostics.TextWriterTraceListener.dll",
"ref/netcoreapp2.2/System.Diagnostics.TextWriterTraceListener.xml",
"ref/netcoreapp2.2/System.Diagnostics.Tools.dll",
"ref/netcoreapp2.2/System.Diagnostics.Tools.xml",
"ref/netcoreapp2.2/System.Diagnostics.TraceSource.dll",
"ref/netcoreapp2.2/System.Diagnostics.TraceSource.xml",
"ref/netcoreapp2.2/System.Diagnostics.Tracing.dll",
"ref/netcoreapp2.2/System.Diagnostics.Tracing.xml",
"ref/netcoreapp2.2/System.Drawing.Primitives.dll",
"ref/netcoreapp2.2/System.Drawing.Primitives.xml",
"ref/netcoreapp2.2/System.Drawing.dll",
"ref/netcoreapp2.2/System.Dynamic.Runtime.dll",
"ref/netcoreapp2.2/System.Globalization.Calendars.dll",
"ref/netcoreapp2.2/System.Globalization.Extensions.dll",
"ref/netcoreapp2.2/System.Globalization.dll",
"ref/netcoreapp2.2/System.IO.Compression.Brotli.dll",
"ref/netcoreapp2.2/System.IO.Compression.FileSystem.dll",
"ref/netcoreapp2.2/System.IO.Compression.ZipFile.dll",
"ref/netcoreapp2.2/System.IO.Compression.ZipFile.xml",
"ref/netcoreapp2.2/System.IO.Compression.dll",
"ref/netcoreapp2.2/System.IO.Compression.xml",
"ref/netcoreapp2.2/System.IO.FileSystem.DriveInfo.dll",
"ref/netcoreapp2.2/System.IO.FileSystem.DriveInfo.xml",
"ref/netcoreapp2.2/System.IO.FileSystem.Primitives.dll",
"ref/netcoreapp2.2/System.IO.FileSystem.Watcher.dll",
"ref/netcoreapp2.2/System.IO.FileSystem.Watcher.xml",
"ref/netcoreapp2.2/System.IO.FileSystem.dll",
"ref/netcoreapp2.2/System.IO.FileSystem.xml",
"ref/netcoreapp2.2/System.IO.IsolatedStorage.dll",
"ref/netcoreapp2.2/System.IO.IsolatedStorage.xml",
"ref/netcoreapp2.2/System.IO.MemoryMappedFiles.dll",
"ref/netcoreapp2.2/System.IO.MemoryMappedFiles.xml",
"ref/netcoreapp2.2/System.IO.Pipes.dll",
"ref/netcoreapp2.2/System.IO.Pipes.xml",
"ref/netcoreapp2.2/System.IO.UnmanagedMemoryStream.dll",
"ref/netcoreapp2.2/System.IO.dll",
"ref/netcoreapp2.2/System.Linq.Expressions.dll",
"ref/netcoreapp2.2/System.Linq.Expressions.xml",
"ref/netcoreapp2.2/System.Linq.Parallel.dll",
"ref/netcoreapp2.2/System.Linq.Parallel.xml",
"ref/netcoreapp2.2/System.Linq.Queryable.dll",
"ref/netcoreapp2.2/System.Linq.Queryable.xml",
"ref/netcoreapp2.2/System.Linq.dll",
"ref/netcoreapp2.2/System.Linq.xml",
"ref/netcoreapp2.2/System.Memory.dll",
"ref/netcoreapp2.2/System.Memory.xml",
"ref/netcoreapp2.2/System.Net.Http.dll",
"ref/netcoreapp2.2/System.Net.Http.xml",
"ref/netcoreapp2.2/System.Net.HttpListener.dll",
"ref/netcoreapp2.2/System.Net.HttpListener.xml",
"ref/netcoreapp2.2/System.Net.Mail.dll",
"ref/netcoreapp2.2/System.Net.Mail.xml",
"ref/netcoreapp2.2/System.Net.NameResolution.dll",
"ref/netcoreapp2.2/System.Net.NameResolution.xml",
"ref/netcoreapp2.2/System.Net.NetworkInformation.dll",
"ref/netcoreapp2.2/System.Net.NetworkInformation.xml",
"ref/netcoreapp2.2/System.Net.Ping.dll",
"ref/netcoreapp2.2/System.Net.Ping.xml",
"ref/netcoreapp2.2/System.Net.Primitives.dll",
"ref/netcoreapp2.2/System.Net.Primitives.xml",
"ref/netcoreapp2.2/System.Net.Requests.dll",
"ref/netcoreapp2.2/System.Net.Requests.xml",
"ref/netcoreapp2.2/System.Net.Security.dll",
"ref/netcoreapp2.2/System.Net.Security.xml",
"ref/netcoreapp2.2/System.Net.ServicePoint.dll",
"ref/netcoreapp2.2/System.Net.ServicePoint.xml",
"ref/netcoreapp2.2/System.Net.Sockets.dll",
"ref/netcoreapp2.2/System.Net.Sockets.xml",
"ref/netcoreapp2.2/System.Net.WebClient.dll",
"ref/netcoreapp2.2/System.Net.WebClient.xml",
"ref/netcoreapp2.2/System.Net.WebHeaderCollection.dll",
"ref/netcoreapp2.2/System.Net.WebHeaderCollection.xml",
"ref/netcoreapp2.2/System.Net.WebProxy.dll",
"ref/netcoreapp2.2/System.Net.WebProxy.xml",
"ref/netcoreapp2.2/System.Net.WebSockets.Client.dll",
"ref/netcoreapp2.2/System.Net.WebSockets.Client.xml",
"ref/netcoreapp2.2/System.Net.WebSockets.dll",
"ref/netcoreapp2.2/System.Net.WebSockets.xml",
"ref/netcoreapp2.2/System.Net.dll",
"ref/netcoreapp2.2/System.Numerics.Vectors.dll",
"ref/netcoreapp2.2/System.Numerics.Vectors.xml",
"ref/netcoreapp2.2/System.Numerics.dll",
"ref/netcoreapp2.2/System.ObjectModel.dll",
"ref/netcoreapp2.2/System.ObjectModel.xml",
"ref/netcoreapp2.2/System.Reflection.DispatchProxy.dll",
"ref/netcoreapp2.2/System.Reflection.DispatchProxy.xml",
"ref/netcoreapp2.2/System.Reflection.Emit.ILGeneration.dll",
"ref/netcoreapp2.2/System.Reflection.Emit.ILGeneration.xml",
"ref/netcoreapp2.2/System.Reflection.Emit.Lightweight.dll",
"ref/netcoreapp2.2/System.Reflection.Emit.Lightweight.xml",
"ref/netcoreapp2.2/System.Reflection.Emit.dll",
"ref/netcoreapp2.2/System.Reflection.Emit.xml",
"ref/netcoreapp2.2/System.Reflection.Extensions.dll",
"ref/netcoreapp2.2/System.Reflection.Metadata.dll",
"ref/netcoreapp2.2/System.Reflection.Metadata.xml",
"ref/netcoreapp2.2/System.Reflection.Primitives.dll",
"ref/netcoreapp2.2/System.Reflection.Primitives.xml",
"ref/netcoreapp2.2/System.Reflection.TypeExtensions.dll",
"ref/netcoreapp2.2/System.Reflection.TypeExtensions.xml",
"ref/netcoreapp2.2/System.Reflection.dll",
"ref/netcoreapp2.2/System.Resources.Reader.dll",
"ref/netcoreapp2.2/System.Resources.ResourceManager.dll",
"ref/netcoreapp2.2/System.Resources.ResourceManager.xml",
"ref/netcoreapp2.2/System.Resources.Writer.dll",
"ref/netcoreapp2.2/System.Resources.Writer.xml",
"ref/netcoreapp2.2/System.Runtime.CompilerServices.VisualC.dll",
"ref/netcoreapp2.2/System.Runtime.CompilerServices.VisualC.xml",
"ref/netcoreapp2.2/System.Runtime.Extensions.dll",
"ref/netcoreapp2.2/System.Runtime.Extensions.xml",
"ref/netcoreapp2.2/System.Runtime.Handles.dll",
"ref/netcoreapp2.2/System.Runtime.InteropServices.RuntimeInformation.dll",
"ref/netcoreapp2.2/System.Runtime.InteropServices.RuntimeInformation.xml",
"ref/netcoreapp2.2/System.Runtime.InteropServices.WindowsRuntime.dll",
"ref/netcoreapp2.2/System.Runtime.InteropServices.WindowsRuntime.xml",
"ref/netcoreapp2.2/System.Runtime.InteropServices.dll",
"ref/netcoreapp2.2/System.Runtime.InteropServices.xml",
"ref/netcoreapp2.2/System.Runtime.Loader.dll",
"ref/netcoreapp2.2/System.Runtime.Loader.xml",
"ref/netcoreapp2.2/System.Runtime.Numerics.dll",
"ref/netcoreapp2.2/System.Runtime.Numerics.xml",
"ref/netcoreapp2.2/System.Runtime.Serialization.Formatters.dll",
"ref/netcoreapp2.2/System.Runtime.Serialization.Formatters.xml",
"ref/netcoreapp2.2/System.Runtime.Serialization.Json.dll",
"ref/netcoreapp2.2/System.Runtime.Serialization.Json.xml",
"ref/netcoreapp2.2/System.Runtime.Serialization.Primitives.dll",
"ref/netcoreapp2.2/System.Runtime.Serialization.Primitives.xml",
"ref/netcoreapp2.2/System.Runtime.Serialization.Xml.dll",
"ref/netcoreapp2.2/System.Runtime.Serialization.Xml.xml",
"ref/netcoreapp2.2/System.Runtime.Serialization.dll",
"ref/netcoreapp2.2/System.Runtime.dll",
"ref/netcoreapp2.2/System.Runtime.xml",
"ref/netcoreapp2.2/System.Security.Claims.dll",
"ref/netcoreapp2.2/System.Security.Claims.xml",
"ref/netcoreapp2.2/System.Security.Cryptography.Algorithms.dll",
"ref/netcoreapp2.2/System.Security.Cryptography.Algorithms.xml",
"ref/netcoreapp2.2/System.Security.Cryptography.Csp.dll",
"ref/netcoreapp2.2/System.Security.Cryptography.Csp.xml",
"ref/netcoreapp2.2/System.Security.Cryptography.Encoding.dll",
"ref/netcoreapp2.2/System.Security.Cryptography.Encoding.xml",
"ref/netcoreapp2.2/System.Security.Cryptography.Primitives.dll",
"ref/netcoreapp2.2/System.Security.Cryptography.Primitives.xml",
"ref/netcoreapp2.2/System.Security.Cryptography.X509Certificates.dll",
"ref/netcoreapp2.2/System.Security.Cryptography.X509Certificates.xml",
"ref/netcoreapp2.2/System.Security.Principal.dll",
"ref/netcoreapp2.2/System.Security.Principal.xml",
"ref/netcoreapp2.2/System.Security.SecureString.dll",
"ref/netcoreapp2.2/System.Security.dll",
"ref/netcoreapp2.2/System.ServiceModel.Web.dll",
"ref/netcoreapp2.2/System.ServiceProcess.dll",
"ref/netcoreapp2.2/System.Text.Encoding.Extensions.dll",
"ref/netcoreapp2.2/System.Text.Encoding.Extensions.xml",
"ref/netcoreapp2.2/System.Text.Encoding.dll",
"ref/netcoreapp2.2/System.Text.RegularExpressions.dll",
"ref/netcoreapp2.2/System.Text.RegularExpressions.xml",
"ref/netcoreapp2.2/System.Threading.Overlapped.dll",
"ref/netcoreapp2.2/System.Threading.Overlapped.xml",
"ref/netcoreapp2.2/System.Threading.Tasks.Dataflow.dll",
"ref/netcoreapp2.2/System.Threading.Tasks.Dataflow.xml",
"ref/netcoreapp2.2/System.Threading.Tasks.Extensions.dll",
"ref/netcoreapp2.2/System.Threading.Tasks.Extensions.xml",
"ref/netcoreapp2.2/System.Threading.Tasks.Parallel.dll",
"ref/netcoreapp2.2/System.Threading.Tasks.Parallel.xml",
"ref/netcoreapp2.2/System.Threading.Tasks.dll",
"ref/netcoreapp2.2/System.Threading.Tasks.xml",
"ref/netcoreapp2.2/System.Threading.Thread.dll",
"ref/netcoreapp2.2/System.Threading.Thread.xml",
"ref/netcoreapp2.2/System.Threading.ThreadPool.dll",
"ref/netcoreapp2.2/System.Threading.ThreadPool.xml",
"ref/netcoreapp2.2/System.Threading.Timer.dll",
"ref/netcoreapp2.2/System.Threading.Timer.xml",
"ref/netcoreapp2.2/System.Threading.dll",
"ref/netcoreapp2.2/System.Threading.xml",
"ref/netcoreapp2.2/System.Transactions.Local.dll",
"ref/netcoreapp2.2/System.Transactions.Local.xml",
"ref/netcoreapp2.2/System.Transactions.dll",
"ref/netcoreapp2.2/System.ValueTuple.dll",
"ref/netcoreapp2.2/System.Web.HttpUtility.dll",
"ref/netcoreapp2.2/System.Web.HttpUtility.xml",
"ref/netcoreapp2.2/System.Web.dll",
"ref/netcoreapp2.2/System.Windows.dll",
"ref/netcoreapp2.2/System.Xml.Linq.dll",
"ref/netcoreapp2.2/System.Xml.ReaderWriter.dll",
"ref/netcoreapp2.2/System.Xml.ReaderWriter.xml",
"ref/netcoreapp2.2/System.Xml.Serialization.dll",
"ref/netcoreapp2.2/System.Xml.XDocument.dll",
"ref/netcoreapp2.2/System.Xml.XDocument.xml",
"ref/netcoreapp2.2/System.Xml.XPath.XDocument.dll",
"ref/netcoreapp2.2/System.Xml.XPath.XDocument.xml",
"ref/netcoreapp2.2/System.Xml.XPath.dll",
"ref/netcoreapp2.2/System.Xml.XPath.xml",
"ref/netcoreapp2.2/System.Xml.XmlDocument.dll",
"ref/netcoreapp2.2/System.Xml.XmlSerializer.dll",
"ref/netcoreapp2.2/System.Xml.XmlSerializer.xml",
"ref/netcoreapp2.2/System.Xml.dll",
"ref/netcoreapp2.2/System.dll",
"ref/netcoreapp2.2/WindowsBase.dll",
"ref/netcoreapp2.2/mscorlib.dll",
"ref/netcoreapp2.2/netstandard.dll",
"runtime.json"
]
},
"Microsoft.NETCore.DotNetAppHost/2.2.0": {
"sha512": "DrhaKInRKKvN6Ns2VNIlC7ZffLOp9THf8cO6X4fytPRJovJUbF49/zzx4WfgX9E44FMsw9hT8hrKiIqDSHvGvA==",
"type": "package",
"path": "microsoft.netcore.dotnetapphost/2.2.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"microsoft.netcore.dotnetapphost.2.2.0.nupkg.sha512",
"microsoft.netcore.dotnetapphost.nuspec",
"runtime.json"
]
},
"Microsoft.NETCore.DotNetHostPolicy/2.2.0": {
"sha512": "FJie7IoPZFaPgNDxhZGmDBQP/Bs5vPdfca/G2Wf9gd6LIvMYkZcibtmJwB4tcf4KXkaOYfIOo4Cl9sEPMsSzkw==",
"type": "package",
"path": "microsoft.netcore.dotnethostpolicy/2.2.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"microsoft.netcore.dotnethostpolicy.2.2.0.nupkg.sha512",
"microsoft.netcore.dotnethostpolicy.nuspec",
"runtime.json"
]
},
"Microsoft.NETCore.DotNetHostResolver/2.2.0": {
"sha512": "spDm3AJYmebthDNhzY17YLPtvbc+Y1lCLVeiIH1uLJ/hZaM+40pBiPefFR8J1u66Ndkqi8ipR2tEbqPnYnjRhw==",
"type": "package",
"path": "microsoft.netcore.dotnethostresolver/2.2.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"microsoft.netcore.dotnethostresolver.2.2.0.nupkg.sha512",
"microsoft.netcore.dotnethostresolver.nuspec",
"runtime.json"
]
},
"Microsoft.NETCore.Platforms/2.2.0": {
"sha512": "T/J+XZo+YheFTJh8/4uoeJDdz5qOmOMkjg6/VL8mHJ9AnP8+fmV/kcbxeXsob0irRNiChf+V0ig1MCRLp/+Kog==",
"type": "package",
"path": "microsoft.netcore.platforms/2.2.0",
"files": [
".nupkg.metadata",
".signature.p7s",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"lib/netstandard1.0/_._",
"microsoft.netcore.platforms.2.2.0.nupkg.sha512",
"microsoft.netcore.platforms.nuspec",
"runtime.json",
"useSharedDesignerContext.txt",
"version.txt"
]
},
"Microsoft.NETCore.Targets/2.0.0": {
"sha512": "odP/tJj1z6GylFpNo7pMtbd/xQgTC3Ex2If63dRTL38bBNMwsBnJ+RceUIyHdRBC0oik/3NehYT+oECwBhIM3Q==",
"type": "package",
"path": "microsoft.netcore.targets/2.0.0",
"files": [
".nupkg.metadata",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"lib/netstandard1.0/_._",
"microsoft.netcore.targets.2.0.0.nupkg.sha512",
"microsoft.netcore.targets.nuspec",
"runtime.json",
"useSharedDesignerContext.txt",
"version.txt"
]
},
"NETStandard.Library/2.0.3": {
"sha512": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"type": "package",
"path": "netstandard.library/2.0.3",
"files": [
".nupkg.metadata",
"LICENSE.TXT",
"THIRD-PARTY-NOTICES.TXT",
"build/netstandard2.0/NETStandard.Library.targets",
"build/netstandard2.0/ref/Microsoft.Win32.Primitives.dll",
"build/netstandard2.0/ref/System.AppContext.dll",
"build/netstandard2.0/ref/System.Collections.Concurrent.dll",
"build/netstandard2.0/ref/System.Collections.NonGeneric.dll",
"build/netstandard2.0/ref/System.Collections.Specialized.dll",
"build/netstandard2.0/ref/System.Collections.dll",
"build/netstandard2.0/ref/System.ComponentModel.Composition.dll",
"build/netstandard2.0/ref/System.ComponentModel.EventBasedAsync.dll",
"build/netstandard2.0/ref/System.ComponentModel.Primitives.dll",
"build/netstandard2.0/ref/System.ComponentModel.TypeConverter.dll",
"build/netstandard2.0/ref/System.ComponentModel.dll",
"build/netstandard2.0/ref/System.Console.dll",
"build/netstandard2.0/ref/System.Core.dll",
"build/netstandard2.0/ref/System.Data.Common.dll",
"build/netstandard2.0/ref/System.Data.dll",
"build/netstandard2.0/ref/System.Diagnostics.Contracts.dll",
"build/netstandard2.0/ref/System.Diagnostics.Debug.dll",
"build/netstandard2.0/ref/System.Diagnostics.FileVersionInfo.dll",
"build/netstandard2.0/ref/System.Diagnostics.Process.dll",
"build/netstandard2.0/ref/System.Diagnostics.StackTrace.dll",
"build/netstandard2.0/ref/System.Diagnostics.TextWriterTraceListener.dll",
"build/netstandard2.0/ref/System.Diagnostics.Tools.dll",
"build/netstandard2.0/ref/System.Diagnostics.TraceSource.dll",
"build/netstandard2.0/ref/System.Diagnostics.Tracing.dll",
"build/netstandard2.0/ref/System.Drawing.Primitives.dll",
"build/netstandard2.0/ref/System.Drawing.dll",
"build/netstandard2.0/ref/System.Dynamic.Runtime.dll",
"build/netstandard2.0/ref/System.Globalization.Calendars.dll",
"build/netstandard2.0/ref/System.Globalization.Extensions.dll",
"build/netstandard2.0/ref/System.Globalization.dll",
"build/netstandard2.0/ref/System.IO.Compression.FileSystem.dll",
"build/netstandard2.0/ref/System.IO.Compression.ZipFile.dll",
"build/netstandard2.0/ref/System.IO.Compression.dll",
"build/netstandard2.0/ref/System.IO.FileSystem.DriveInfo.dll",
"build/netstandard2.0/ref/System.IO.FileSystem.Primitives.dll",
"build/netstandard2.0/ref/System.IO.FileSystem.Watcher.dll",
"build/netstandard2.0/ref/System.IO.FileSystem.dll",
"build/netstandard2.0/ref/System.IO.IsolatedStorage.dll",
"build/netstandard2.0/ref/System.IO.MemoryMappedFiles.dll",
"build/netstandard2.0/ref/System.IO.Pipes.dll",
"build/netstandard2.0/ref/System.IO.UnmanagedMemoryStream.dll",
"build/netstandard2.0/ref/System.IO.dll",
"build/netstandard2.0/ref/System.Linq.Expressions.dll",
"build/netstandard2.0/ref/System.Linq.Parallel.dll",
"build/netstandard2.0/ref/System.Linq.Queryable.dll",
"build/netstandard2.0/ref/System.Linq.dll",
"build/netstandard2.0/ref/System.Net.Http.dll",
"build/netstandard2.0/ref/System.Net.NameResolution.dll",
"build/netstandard2.0/ref/System.Net.NetworkInformation.dll",
"build/netstandard2.0/ref/System.Net.Ping.dll",
"build/netstandard2.0/ref/System.Net.Primitives.dll",
"build/netstandard2.0/ref/System.Net.Requests.dll",
"build/netstandard2.0/ref/System.Net.Security.dll",
"build/netstandard2.0/ref/System.Net.Sockets.dll",
"build/netstandard2.0/ref/System.Net.WebHeaderCollection.dll",
"build/netstandard2.0/ref/System.Net.WebSockets.Client.dll",
"build/netstandard2.0/ref/System.Net.WebSockets.dll",
"build/netstandard2.0/ref/System.Net.dll",
"build/netstandard2.0/ref/System.Numerics.dll",
"build/netstandard2.0/ref/System.ObjectModel.dll",
"build/netstandard2.0/ref/System.Reflection.Extensions.dll",
"build/netstandard2.0/ref/System.Reflection.Primitives.dll",
"build/netstandard2.0/ref/System.Reflection.dll",
"build/netstandard2.0/ref/System.Resources.Reader.dll",
"build/netstandard2.0/ref/System.Resources.ResourceManager.dll",
"build/netstandard2.0/ref/System.Resources.Writer.dll",
"build/netstandard2.0/ref/System.Runtime.CompilerServices.VisualC.dll",
"build/netstandard2.0/ref/System.Runtime.Extensions.dll",
"build/netstandard2.0/ref/System.Runtime.Handles.dll",
"build/netstandard2.0/ref/System.Runtime.InteropServices.RuntimeInformation.dll",
"build/netstandard2.0/ref/System.Runtime.InteropServices.dll",
"build/netstandard2.0/ref/System.Runtime.Numerics.dll",
"build/netstandard2.0/ref/System.Runtime.Serialization.Formatters.dll",
"build/netstandard2.0/ref/System.Runtime.Serialization.Json.dll",
"build/netstandard2.0/ref/System.Runtime.Serialization.Primitives.dll",
"build/netstandard2.0/ref/System.Runtime.Serialization.Xml.dll",
"build/netstandard2.0/ref/System.Runtime.Serialization.dll",
"build/netstandard2.0/ref/System.Runtime.dll",
"build/netstandard2.0/ref/System.Security.Claims.dll",
"build/netstandard2.0/ref/System.Security.Cryptography.Algorithms.dll",
"build/netstandard2.0/ref/System.Security.Cryptography.Csp.dll",
"build/netstandard2.0/ref/System.Security.Cryptography.Encoding.dll",
"build/netstandard2.0/ref/System.Security.Cryptography.Primitives.dll",
"build/netstandard2.0/ref/System.Security.Cryptography.X509Certificates.dll",
"build/netstandard2.0/ref/System.Security.Principal.dll",
"build/netstandard2.0/ref/System.Security.SecureString.dll",
"build/netstandard2.0/ref/System.ServiceModel.Web.dll",
"build/netstandard2.0/ref/System.Text.Encoding.Extensions.dll",
"build/netstandard2.0/ref/System.Text.Encoding.dll",
"build/netstandard2.0/ref/System.Text.RegularExpressions.dll",
"build/netstandard2.0/ref/System.Threading.Overlapped.dll",
"build/netstandard2.0/ref/System.Threading.Tasks.Parallel.dll",
"build/netstandard2.0/ref/System.Threading.Tasks.dll",
"build/netstandard2.0/ref/System.Threading.Thread.dll",
"build/netstandard2.0/ref/System.Threading.ThreadPool.dll",
"build/netstandard2.0/ref/System.Threading.Timer.dll",
"build/netstandard2.0/ref/System.Threading.dll",
"build/netstandard2.0/ref/System.Transactions.dll",
"build/netstandard2.0/ref/System.ValueTuple.dll",
"build/netstandard2.0/ref/System.Web.dll",
"build/netstandard2.0/ref/System.Windows.dll",
"build/netstandard2.0/ref/System.Xml.Linq.dll",
"build/netstandard2.0/ref/System.Xml.ReaderWriter.dll",
"build/netstandard2.0/ref/System.Xml.Serialization.dll",
"build/netstandard2.0/ref/System.Xml.XDocument.dll",
"build/netstandard2.0/ref/System.Xml.XPath.XDocument.dll",
"build/netstandard2.0/ref/System.Xml.XPath.dll",
"build/netstandard2.0/ref/System.Xml.XmlDocument.dll",
"build/netstandard2.0/ref/System.Xml.XmlSerializer.dll",
"build/netstandard2.0/ref/System.Xml.dll",
"build/netstandard2.0/ref/System.dll",
"build/netstandard2.0/ref/mscorlib.dll",
"build/netstandard2.0/ref/netstandard.dll",
"build/netstandard2.0/ref/netstandard.xml",
"lib/netstandard1.0/_._",
"netstandard.library.2.0.3.nupkg.sha512",
"netstandard.library.nuspec"
]
}
},
"projectFileDependencyGroups": {
".NETCoreApp,Version=v2.2": [
"Microsoft.NETCore.App >= 2.2.0"
]
},
"packageFolders": {
"/Users/ben/.nuget/packages/": {},
"/usr/local/share/dotnet/sdk/NuGetFallbackFolder": {}
},
"project": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/Users/ben/.vim/bundle/vimspector/support/test/csharp/csharp.csproj",
"projectName": "csharp",
"projectPath": "/Users/ben/.vim/bundle/vimspector/support/test/csharp/csharp.csproj",
"packagesPath": "/Users/ben/.nuget/packages/",
"outputPath": "/Users/ben/.vim/bundle/vimspector/support/test/csharp/obj/",
"projectStyle": "PackageReference",
"fallbackFolders": [
"/usr/local/share/dotnet/sdk/NuGetFallbackFolder"
],
"configFilePaths": [
"/Users/ben/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"netcoreapp2.2"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"netcoreapp2.2": {
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
}
},
"frameworks": {
"netcoreapp2.2": {
"dependencies": {
"Microsoft.NETCore.App": {
"suppressParent": "All",
"target": "Package",
"version": "[2.2.0, )",
"autoReferenced": true
}
},
"imports": [
"net461"
],
"assetTargetFallback": true,
"warn": true
}
}
}
}

View file

@ -1,13 +0,0 @@
if argc() < 2
echom 'Usage:' v:argv[ 0 ] 'processName binary'
cquit!
endif
setfiletype cpp
call vimspector#LaunchWithSettings( #{
\ configuration: "C++ - Attach Local Process",
\ processName: argv( 0 ),
\ binary: argv( 1 ),
\ } )
1,2argd

View file

@ -1,24 +0,0 @@
{
"configurations": {
"C++ - Attach Local Process": {
"adapter": "vscode-cpptools",
"variables": {
"PID": {
"shell": [ "GetPIDForProcess", "${processName}" ]
}
},
"configuration": {
"name": "test",
"request": "attach",
"program": "${binary}",
"processId": "${PID}",
"type": "cppdbg",
"stopAtEntry": true,
"setupCommands": [
{ "text": "source ${initFile}", "ignoreFailures": true }
]
}
}
}
}

View file

@ -1,58 +1,13 @@
{ {
"adapters": {
"dlv-dap": {
"variables": {
"port": "${unusedLocalPort}"
},
"command": [
"$HOME/go/bin/dlv",
"dap",
"--listen",
"127.0.0.1:${port}"
],
"port": "${port}"
}
},
"configurations": { "configurations": {
"run": { "run": {
"adapter": "vscode-go", "adapter": "vscode-go",
"default": true,
"configuration": { "configuration": {
"request": "launch", "request": "launch",
"program": "${workspaceRoot}/hello-world.go", "program": "${workspaceRoot}/hello-world.go",
"mode": "debug", "mode": "debug",
"dlvToolPath": "$HOME/go/bin/dlv", "dlvToolPath": "$HOME/go/bin/dlv",
"trace": true, "trace": true
"env": { "GO111MODULE": "off" }
}
},
"run-dap": {
"adapter": "dlv-dap",
"configuration": {
"request": "launch",
"env": { "GO111MODULE": "off" },
"mode": "debug", // debug|test
"program": "${workspaceRoot}/hello-world.go"
// "args": [],
// "buildFlags": ...
// "stackTraceDepth": ...,
// "showGlobalVariables": true,
}
},
"run-exec": {
// NOTE: To use this you _must_ disable optimistaion:
// go build -o hello_world -gcflags="all=-N -l"
// https://github.com/golang/vscode-go/blob/master/docs/debugging.md#troubleshooting
"adapter": "vscode-go",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/hello-world",
"mode": "exec",
"dlvToolPath": "$HOME/go/bin/dlv",
"trace": true,
"env": { "GO111MODULE": "off" }
} }
} }
} }

View file

@ -1,29 +0,0 @@
{
"configurations": {
"run-cmd": {
"adapter": "vscode-go",
"configuration": {
"request": "launch",
"program": "${workspaceRoot}/cmd/namestartswithvowel/main.go",
"mode": "debug",
"dlvToolPath": "$HOME/go/bin/dlv",
"dlvLoadConfig": {
"maxArrayValues": 1000,
"maxStringLen": 1000
}
}
},
"test-current-file": {
"adapter": "vscode-go",
"configuration": {
"request": "launch",
"mode": "test",
"program": "${fileDirname}",
"cwd": "${fileDirname}",
"dlvToolPath": "$GOPATH/bin/dlv",
"env": {},
"args": []
}
}
}
}

View file

@ -1,33 +0,0 @@
# Purpose
This example comes with two example vimspector configs for the Go programming language.
1) `run-cmd` will launch the main programme under `cmd/namestartswithvowel`.
1) `test-current-file` will run the tests in the current file in debug mode.
## Example use-cases
### run-cmd
* Open `cmd/namestartswithvowel/main.go`
* Add a breakpoint somewhere within the programme
* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping)
* Select the first launch configuration (`1: run-cmd`)
### test-current-file
* Open `internal/vowels/vowels_test.go`
* Add a breakpoint somewhere within the test
* Start the debugger (`:call vimspector#Continue()` or your relevant keymapping)
* Select the second launch configuration (`2: test-current-file`)
## Additional Configuration
There are two additional configuration options specified under `run-cmd`; these parameters configure the maximum string/array size to be shown while debugging.
```
"dlvLoadConfig": {
"maxArrayValues": 1000,
"maxStringLen": 1000
}
```

View file

@ -1,20 +0,0 @@
package main
import (
"fmt"
"example.com/internal/vowels"
)
func main() {
names := []string{"Simon", "Bob", "Jennifer", "Amy", "Duke", "Elizabeth"}
for _, n := range names {
if vowels.NameStartsWithVowel(n) {
fmt.Printf("%s starts with a vowel!\n", n)
continue
}
fmt.Printf("%s does not start with a vowel!\n", n)
}
}

View file

@ -1,3 +0,0 @@
module example.com
go 1.16

View file

@ -1,9 +0,0 @@
package vowels
import "strings"
func NameStartsWithVowel(name string) bool {
s := strings.Split(strings.ToLower(name), "")
return s[0] == "a" || s[0] == "e" || s[0] == "i" || s[0] == "o" || s[0] == "u"
}

View file

@ -1,30 +0,0 @@
package vowels
import (
"fmt"
"testing"
)
func TestNameStartsWithVowel(t *testing.T) {
testCases := []struct {
input string
expectedOutput bool
}{
{
input: "Simon",
expectedOutput: false,
},
{
input: "Andy",
expectedOutput: true,
},
}
for _, tt := range testCases {
t.Run(fmt.Sprintf("%s should product %t", tt.input, tt.expectedOutput), func(t *testing.T) {
out := NameStartsWithVowel(tt.input)
if out != tt.expectedOutput {
t.Errorf("%s produced %t, when %t was expected", tt.input, out, tt.expectedOutput)
}
})
}
}

View file

@ -1,7 +1,13 @@
{ {
"adapters": {
"java-debug-server": {
"name": "vscode-java",
"port": "ask"
}
},
"configurations": { "configurations": {
"Java Launch": { "Java Launch": {
"adapter": "vscode-java", "adapter": "java-debug-server",
"configuration": { "configuration": {
"request": "launch", "request": "launch",
"mainClass": "com.vimspector.test.TestApplication", "mainClass": "com.vimspector.test.TestApplication",
@ -9,33 +15,7 @@
"classPaths": [ "${workspaceRoot}/target/classes" ], "classPaths": [ "${workspaceRoot}/target/classes" ],
"args": "hello world!", "args": "hello world!",
"stopOnEntry": true, "stopOnEntry": true,
"console": "integratedTerminal", "console": "integratedTerminal"
"stepFilters": {
"skipClasses": [ "$$JDK" ]
}
}
},
"Java Attach": {
"adapter": "vscode-java",
"configuration": {
"request": "attach",
"sourcePaths": [ "${workspaceRoot}/src/main/java" ],
"stopOnEntry": true,
"hostName": "localhost",
"port": "${JVMDebugPort}",
"stepFilters": {
"skipClasses": [ "$$JDK" ]
}
}
},
"Attach with vscode-javac": {
"adapter": "vscode-javac",
"configuration": {
"request": "attach",
"port": "${debugPort}",
"sourceRoots": [
"${workspaceRoot}/src/main/java"
]
} }
} }
} }

View file

@ -1,24 +0,0 @@
let g:ycm_java_jdtls_extension_path = [
\ expand( '<sfile>:p:h:h:h:h:h' ) . '/gadgets/macos'
\ ]
let s:jdt_ls_debugger_port = 0
function! s:StartDebugging()
if s:jdt_ls_debugger_port <= 0
" Get the DAP port
let s:jdt_ls_debugger_port = youcompleteme#GetCommandResponse(
\ 'ExecuteCommand',
\ 'vscode.java.startDebugSession' )
if s:jdt_ls_debugger_port == ''
echom "Unable to get DAP port - is YCM initialized?"
let s:jdt_ls_debugger_port = 0
return
endif
endif
" Start debugging with the DAP port
call vimspector#LaunchWithSettings( { 'DAPPort': s:jdt_ls_debugger_port } )
endfunction
nnoremap <silent> <buffer> <Leader><F5> :call <SID>StartDebugging()<CR>

View file

@ -4,7 +4,7 @@
<artifactId>TestApplication</artifactId> <artifactId>TestApplication</artifactId>
<version>1</version> <version>1</version>
<properties> <properties>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
</project> </project>

View file

@ -1,11 +0,0 @@
package com.vimspector.test;
public class Base
{
public String DoSomething()
{
String s = new String();
s.replace( "A", "B" );
return s;
}
}

View file

@ -13,22 +13,6 @@ public class TestApplication {
return list; return list;
} }
private static class Bass extends Base {
String bass = "Pump";
@Override
public String DoSomething() {
if ( Math.random() % 3 == 0 ) {
return bass;
}
return super.DoSomething();
}
}
private static <T extends Base> void DoGeneric( T b ) {
TestGeneric<T> foo = new TestGeneric<>( b );
foo.DoSomethingUseful();
}
public static void main( String[] args ) { public static void main( String[] args ) {
int numEntries = 0; int numEntries = 0;
for ( String s : args ) { for ( String s : args ) {
@ -40,8 +24,6 @@ public class TestApplication {
++numEntries; ++numEntries;
} }
System.out.println( "Number of entries: " + numEntries ); System.out.println( "Number of entries: " + numEntries );
DoGeneric( new Bass() );
} }
} }

View file

@ -1,16 +0,0 @@
package com.vimspector.test;
class TestGeneric<T extends Base> {
T base;
public TestGeneric( T b )
{
this.base = b;
}
public String DoSomethingUseful() {
String s = "A B C" + base.DoSomething();
return s.replace( "B", "C" );
}
}

View file

@ -1,2 +0,0 @@
.gradle
build

View file

@ -1,21 +0,0 @@
{
"configurations": {
"kotlin-debug-adapter launch": {
"adapter": "cust_kotlin-debug-adapter",
"configuration": {
"request": "launch",
"projectRoot": "${workspaceFolder}",
"mainClass": "vimspector/test/ApplicationKt"
}
},
"kotlin-debug-adapter attach": {
"adapter": "cust_kotlin-debug-adapter",
"configuration": {
"request": "attach",
"projectRoot": "${workspaceFolder}",
"hostName": "${hostName}",
"port": "${port}"
}
}
}
}

View file

@ -1,25 +0,0 @@
plugins {
kotlin("jvm") version "1.4.0"
application
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
}
dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
application {
// Define the main class for the application.
mainClassName = "vimspector.test.ApplicationKt"
}

View file

@ -1 +0,0 @@
rootProject.name = "vimspector-test"

Some files were not shown because too many files have changed in this diff Show more