Update coc.nvim.
This commit is contained in:
parent
225f27e9d6
commit
16d930cf30
375 changed files with 506 additions and 93652 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,22 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
[*.{js,ts}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.vim]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
|
@ -1,5 +0,0 @@
|
|||
node_modules
|
||||
coverage
|
||||
build
|
||||
lib
|
||||
typings
|
|
@ -1,339 +0,0 @@
|
|||
module.exports = {
|
||||
"root": true,
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"jsdoc",
|
||||
"jest",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"comma-dangle": [
|
||||
0
|
||||
],
|
||||
"guard-for-in": [
|
||||
0
|
||||
],
|
||||
"no-dupe-class-members": [
|
||||
0
|
||||
],
|
||||
"prefer-spread": [
|
||||
0
|
||||
],
|
||||
"prefer-rest-params": [
|
||||
0
|
||||
],
|
||||
"func-names": [
|
||||
0
|
||||
],
|
||||
"require-atomic-updates": [
|
||||
0
|
||||
],
|
||||
"no-empty": "off",
|
||||
"no-console": "off",
|
||||
"linebreak-style": [
|
||||
1,
|
||||
"unix"
|
||||
],
|
||||
"no-prototype-builtins": [
|
||||
0
|
||||
],
|
||||
"no-unused-vars": [
|
||||
0
|
||||
],
|
||||
"no-async-promise-executor": [
|
||||
0
|
||||
],
|
||||
"constructor-super": "error",
|
||||
"for-direction": [
|
||||
"error"
|
||||
],
|
||||
"getter-return": [
|
||||
"error"
|
||||
],
|
||||
"no-case-declarations": [
|
||||
"error"
|
||||
],
|
||||
"no-class-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-compare-neg-zero": [
|
||||
"error"
|
||||
],
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-constant-condition": [
|
||||
"error"
|
||||
],
|
||||
"no-control-regex": [
|
||||
"error"
|
||||
],
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": [
|
||||
"error"
|
||||
],
|
||||
"no-dupe-args": [
|
||||
"error"
|
||||
],
|
||||
"no-dupe-keys": [
|
||||
"error"
|
||||
],
|
||||
"no-duplicate-case": [
|
||||
"error"
|
||||
],
|
||||
"no-empty-character-class": [
|
||||
"error"
|
||||
],
|
||||
"no-empty-pattern": [
|
||||
"error"
|
||||
],
|
||||
"no-ex-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-extra-boolean-cast": [
|
||||
"error"
|
||||
],
|
||||
"no-extra-semi": [
|
||||
"error"
|
||||
],
|
||||
"no-fallthrough": "off",
|
||||
"no-func-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-global-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-inner-declarations": [
|
||||
"error"
|
||||
],
|
||||
"no-invalid-regexp": [
|
||||
"error"
|
||||
],
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-misleading-character-class": [
|
||||
"error"
|
||||
],
|
||||
"no-mixed-spaces-and-tabs": [
|
||||
"error"
|
||||
],
|
||||
"no-new-symbol": [
|
||||
"error"
|
||||
],
|
||||
"no-obj-calls": [
|
||||
"error"
|
||||
],
|
||||
"no-octal": [
|
||||
"error"
|
||||
],
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": [
|
||||
"error"
|
||||
],
|
||||
"no-self-assign": [
|
||||
"error"
|
||||
],
|
||||
"no-shadow-restricted-names": [
|
||||
"error"
|
||||
],
|
||||
"no-sparse-arrays": "error",
|
||||
"no-this-before-super": [
|
||||
"error"
|
||||
],
|
||||
"no-undef": [
|
||||
"off"
|
||||
],
|
||||
"no-unexpected-multiline": [
|
||||
"error"
|
||||
],
|
||||
"no-unreachable": [
|
||||
"warn"
|
||||
],
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unsafe-negation": [
|
||||
"error"
|
||||
],
|
||||
"no-unused-labels": "error",
|
||||
"no-useless-catch": [
|
||||
"error"
|
||||
],
|
||||
"no-useless-escape": [
|
||||
"error"
|
||||
],
|
||||
"no-with": [
|
||||
"error"
|
||||
],
|
||||
"require-yield": [
|
||||
"error"
|
||||
],
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "off",
|
||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "off",
|
||||
"@typescript-eslint/prefer-string-starts-ends-with": "off",
|
||||
"@typescript-eslint/prefer-regexp-exec": "off",
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
"@typescript-eslint/array-type": "off",
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{
|
||||
"accessibility": "explicit",
|
||||
"overrides": {
|
||||
"accessors": "explicit",
|
||||
"constructors": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/member-ordering": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/no-for-in-array": "error",
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-unsafe-argument": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/prefer-for-of": "off",
|
||||
"@typescript-eslint/prefer-function-type": "off",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/quotes": "off",
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"@typescript-eslint/triple-slash-reference": [
|
||||
"error",
|
||||
{
|
||||
"path": "always",
|
||||
"types": "prefer-import",
|
||||
"lib": "always"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
"@typescript-eslint/unified-signatures": "error",
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": [
|
||||
"error",
|
||||
"as-needed"
|
||||
],
|
||||
"camelcase": "off",
|
||||
"complexity": "off",
|
||||
"curly": "off",
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": [
|
||||
"off",
|
||||
"always"
|
||||
],
|
||||
"id-blacklist": [
|
||||
"error",
|
||||
"any",
|
||||
"Number",
|
||||
"number",
|
||||
"String",
|
||||
"string",
|
||||
"Boolean",
|
||||
"boolean",
|
||||
"Undefined"
|
||||
],
|
||||
"id-match": "error",
|
||||
"jsdoc/check-alignment": "error",
|
||||
"jsdoc/check-indentation": "error",
|
||||
"jsdoc/newline-after-description": "error",
|
||||
"max-classes-per-file": "off",
|
||||
"new-parens": "error",
|
||||
"no-bitwise": "off",
|
||||
"no-caller": "error",
|
||||
"no-eval": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-magic-numbers": "off",
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"no-new-wrappers": "error",
|
||||
"no-shadow": [
|
||||
"off",
|
||||
{
|
||||
"hoist": "all"
|
||||
}
|
||||
],
|
||||
"no-template-curly-in-string": "off",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-unused-expressions": "off",
|
||||
"no-var": "error",
|
||||
"no-void": "off",
|
||||
"object-shorthand": "error",
|
||||
"one-var": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"prefer-const": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": [
|
||||
"error",
|
||||
"as-needed"
|
||||
],
|
||||
"radix": "error",
|
||||
"space-before-function-paren": [
|
||||
"error",
|
||||
{
|
||||
"anonymous": "never",
|
||||
"asyncArrow": "always",
|
||||
"named": "never"
|
||||
}
|
||||
],
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"markers": [
|
||||
"/"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
coverage:
|
||||
status:
|
||||
patch: off
|
|
@ -1,4 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
open_collective: cocnvim
|
||||
patreon: chemzqm
|
|
@ -1,45 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
|
||||
<!--
|
||||
**Warning: We will close the bug issue without the issue template and the reproduce ways.**
|
||||
|
||||
If you have question, please ask at https://gitter.im/neoclide/coc.nvim
|
||||
|
||||
If the problem related to specific language server, please checkout: https://git.io/fjCEM
|
||||
|
||||
If your have performance issue, checkout: https://git.io/fjCEX & https://git.io/Jfe00
|
||||
-->
|
||||
|
||||
## Result from CocInfo
|
||||
|
||||
<!--Run `:CocInfo` command and paste the content below.-->
|
||||
|
||||
## Describe the bug
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Reproduce the bug
|
||||
|
||||
**We will close your issue when you don't provide minimal vimrc and we can't
|
||||
reproduce it**
|
||||
|
||||
- Create file `mini.vim` with:
|
||||
|
||||
```vim
|
||||
set nocompatible
|
||||
set runtimepath^=/path/to/coc.nvim
|
||||
filetype plugin indent on
|
||||
syntax on
|
||||
set hidden
|
||||
```
|
||||
|
||||
- Start (neo)vim with command: `vim -u mini.vim`
|
||||
|
||||
- Operate vim.
|
||||
|
||||
## Screenshots (optional)
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
|
@ -1,17 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -1,73 +0,0 @@
|
|||
name: Dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.event.pull_request.draft == false
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- "16"
|
||||
- "14"
|
||||
nvim:
|
||||
- "0.7.0"
|
||||
include:
|
||||
# only enable coverage on the fastest job
|
||||
- node: "16"
|
||||
ENABLE_CODE_COVERAGE: true
|
||||
|
||||
env:
|
||||
NODE_ENV: test
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v2.4.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: "yarn"
|
||||
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
cache: 'pip'
|
||||
- run: pip install pynvim
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
yarn global add typescript
|
||||
yarn install --frozen-lockfile
|
||||
|
||||
- name: Setup nvim ${{ matrix.nvim }}
|
||||
run: |
|
||||
sudo apt-get install -y xclip ripgrep ctags
|
||||
xclip -version
|
||||
rg --version
|
||||
ctags --version
|
||||
curl -LO https://github.com/neovim/neovim/releases/download/v${{ matrix.nvim }}/nvim-linux64.tar.gz
|
||||
tar xzf nvim-linux64.tar.gz
|
||||
export PATH="${PATH}:node_modules/.bin:$(pwd)/nvim-linux64/bin"
|
||||
nvim --version
|
||||
yarn test-build --maxWorkers=2
|
||||
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
if: ${{ matrix.ENABLE_CODE_COVERAGE }}
|
||||
with:
|
||||
fail_ci_if_error: true
|
|
@ -1,35 +0,0 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.4.0
|
||||
with:
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Check Types by TSC
|
||||
run: yarn lint:typecheck
|
||||
|
||||
- name: Lint ESLint
|
||||
run: yarn lint
|
||||
|
||||
- name: Check Lock File Changes
|
||||
run: yarn && echo "Listing changed files:" && git diff --name-only --exit-code && echo "No files changed during lint."
|
15
sources_non_forked/coc.nvim/.gitignore
vendored
15
sources_non_forked/coc.nvim/.gitignore
vendored
|
@ -1,16 +1,13 @@
|
|||
lib
|
||||
.cache
|
||||
*.map
|
||||
coverage
|
||||
__pycache__
|
||||
.pyc
|
||||
.log
|
||||
build
|
||||
doc/tags
|
||||
typings/package.json
|
||||
node_modules
|
||||
src
|
||||
publish.sh
|
||||
release.sh
|
||||
!src/__tests__/tags
|
||||
src/__tests__/extensions/db.json
|
||||
package-lock.json
|
||||
doc/tags
|
||||
doc/tags-cn
|
||||
node_modules
|
||||
src/__tests__/tags
|
||||
typings
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
lib
|
|
@ -1,16 +0,0 @@
|
|||
*.map
|
||||
.cache
|
||||
lib/extensions
|
||||
lib/__tests__
|
||||
plugin
|
||||
autoload
|
||||
rplugin
|
||||
src
|
||||
.github
|
||||
build
|
||||
coverage
|
||||
data
|
||||
tslint.json
|
||||
tsconfig.json
|
||||
.zip
|
||||
.DS_Store
|
|
@ -1 +0,0 @@
|
|||
src/
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"sourceMaps": false,
|
||||
"module": {
|
||||
"type": "es6"
|
||||
},
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": false,
|
||||
"dynamicImport": false,
|
||||
"decorators": false
|
||||
},
|
||||
"loose": true,
|
||||
"target": "es2016"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"eslint.validate": ["typescript"],
|
||||
"eslint.lintTask.options": [".", "--ext", ".ts"],
|
||||
"typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
|
||||
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
|
||||
"typescript.suggestionActions.enabled": false
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
# Backers
|
||||
|
||||
❤️ coc.nvim? Help us keep it alive by [donating funds](https://www.bountysource.com/teams/coc-nvim)😘!
|
||||
|
||||
<a href="https://github.com/oblitum" target="_blank" title="oblitum">
|
||||
<img src="https://github.com/oblitum.png?size=64" width="64" height="64" alt="oblitum">
|
||||
</a>
|
||||
<a href="https://github.com/free-easy" target="_blank" title="free-easy">
|
||||
<img src="https://github.com/free-easy.png?size=64" width="64" height="64" alt="free-easy">
|
||||
</a>
|
||||
<a href="https://github.com/ruanyl" target="_blank" title="ruanyl">
|
||||
<img src="https://github.com/ruanyl.png?size=64" width="64" height="64" alt="ruanyl">
|
||||
</a>
|
||||
<a href="https://github.com/robjuffermans" target="_blank" title="robjuffermans">
|
||||
<img src="https://github.com/robjuffermans.png?size=64" width="64" height="64" alt="robjuffermans">
|
||||
</a>
|
||||
<a href="https://github.com/iamcco" target="_blank" title="iamcco">
|
||||
<img src="https://github.com/iamcco.png?size=64" width="64" height="64" alt="iamcco">
|
||||
</a>
|
||||
<a href="https://github.com/phcerdan" target="_blank" title="phcerdan">
|
||||
<img src="https://github.com/phcerdan.png?size=64" width="64" height="64" alt="phcerdan">
|
||||
</a>
|
||||
<a href="https://github.com/sarene" target="_blank" title="sarene">
|
||||
<img src="https://github.com/sarene.png?size=64" width="64" height="64" alt="sarene">
|
||||
</a>
|
||||
<a href="https://github.com/robtrac" target="_blank" title="robtrac">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals89_puer8v.png" width="64" height="64" alt="robtrac">
|
||||
</a>
|
||||
<a href="https://github.com/raidou" target="_blank" title="raidou">
|
||||
<img src="https://github.com/raidou.png?size=64" width="64" height="64" alt="raidou">
|
||||
</a>
|
||||
<a href="https://github.com/tomspeak" target="_blank" title="tomspeak">
|
||||
<img src="https://github.com/tomspeak.png?size=64" width="64" height="64" alt="tomspeak">
|
||||
</a>
|
||||
<a href="https://github.com/taigacute" target="_blank" title="taigacute">
|
||||
<img src="https://github.com/taigacute.png?size=64" width="64" height="64" alt="taigacute">
|
||||
</a>
|
||||
<a href="https://github.com/weirongxu" target="_blank" title="weirongxu">
|
||||
<img src="https://github.com/weirongxu.png?size=64" width="64" height="64" alt="weirongxu">
|
||||
</a>
|
||||
<a href="https://github.com/tbo" target="_blank" title="tbo">
|
||||
<img src="https://github.com/tbo.png?size=64" width="64" height="64" alt="tbo">
|
||||
</a>
|
||||
<a href="https://github.com/darthShadow" target="_blank" title="darthShadow">
|
||||
<img src="https://github.com/darthShadow.png?size=64" width="64" height="64" alt="darthShadow">
|
||||
</a>
|
||||
<a href="https://github.com/yatli" target="_blank" title="yatli">
|
||||
<img src="https://github.com/yatli.png?size=64" width="64" height="64" alt="yatli">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/f8fbc5df2432deac7557cf5e111439f2" width="64" height="64" alt="Matt Greer">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars0.githubusercontent.com/u/2914269?v=4&s=100&s=400" width="64" height="64" alt="malob">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/a8b8103b9131cdf694bea446881c05fb" width="64" height="64" alt="Emigre">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals27_bjhsl8.png" width="64" height="64" alt="OkanEsen">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals57_yatmux.png" width="64" height="64" alt="Lennaert Meijvogel">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars2.githubusercontent.com/u/557201?s=400&u=ac96c9da87099c27f094eec935a627cb32fdfdf2&v=4&s=400" width="64" height="64" alt="Nils Landt">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals10_mjtuws.png" width="64" height="64" alt="dlants">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals45_ecgl95.png" width="64" height="64" alt="RCVU">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals71_wi5cvo.png" width="64" height="64" alt="yatli">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/2986e67e29cf2ad3de088f9f8bc131cf" width="64" height="64" alt="mikker">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/8703a88e1c178112625bcb6970ed40e4" width="64" height="64" alt="Velovix">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals51_byhedz.png" width="64" height="64" alt="stCarolas">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals67_rzqguf.png" width="64" height="64" alt="Robbie Clarken">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/svdunc4lofagkaeobpar.png" width="64" height="64" alt="hallettj">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars0.githubusercontent.com/u/6803419?v=4&s=100&s=400" width="64" height="64" alt="appelgriebsch">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals75_a0xqeq.png" width="64" height="64" alt="cosminadrianpopescu">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars3.githubusercontent.com/u/301015?v=4&s=100&s=400" width="64" height="64" alt="partizan">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals24_s1h7ax.png" width="64" height="64" alt="ksaldana1">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals63_olgqd6.png" width="64" height="64" alt="jesperryom">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals70_t5kjmo.png" width="64" height="64" alt="JackCA">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals38_vwccce.png" width="64" height="64" alt="peymanmortazavi">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals92_htl0if.png" width="64" height="64" alt="jonaustin">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals33_ch4hs0.png" width="64" height="64" alt="Yuriy Ivanyuk">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals26_knlvug.png" width="64" height="64" alt="abenz1267">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals100_g8py5g.png" width="64" height="64" alt="Sh3Rm4n">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals14_bnuacq.png" width="64" height="64" alt="mwcz">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals78_hleldd.png" width="64" height="64" alt="Philipp-M">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals37_sikg8d.png" width="64" height="64" alt="gvelchuru">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals62_hxul6y.png" width="64" height="64" alt="JSamir">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals19_zafwti.png" width="64" height="64" alt="toby de havilland">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals97_iuw00n.png" width="64" height="64" alt="viniciusarcanjo">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals70_t5kjmo.png" width="64" height="64" alt="Mike Hearn">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals87_vnmrie.png" width="64" height="64" alt="darsto">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars2.githubusercontent.com/u/145502?v=4&s=100&s=400" width="64" height="64" alt="pyrho">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals102_hqrga7.png" width="64" height="64" alt="Frydac">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals90_qlafi0.png" width="64" height="64" alt="gsa9">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals16_qlob5k.png" width="64" height="64" alt="_andys8">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals27_bjhsl8.png" width="64" height="64" alt="iago-lito">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals44_xa5xwi.png" width="64" height="64" alt="ddaletski">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals83_ryixly.png" width="64" height="64" alt="jonatan-branting">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://avatars3.githubusercontent.com/u/8683947?v=4&s=100&s=400" width="64" height="64" alt="yutakatay">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals87_vnmrie.png" width="64" height="64" alt="kevinrambaud">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals76_g3jfjp.png" width="64" height="64" alt="tomaskallup">
|
||||
</a>
|
||||
<a href="#Backers">
|
||||
<img src="https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals46_qe2ye0.png" width="64" height="64" alt="LewisSteele">
|
||||
</a>
|
||||
|
||||
## 微信扫码赞助者
|
||||
|
||||
- free-easy
|
||||
- sarene
|
||||
- tomspeak
|
||||
- robtrac
|
||||
- 葫芦小金刚
|
||||
- leo 陶
|
||||
- 飞翔的白斩鸡
|
||||
- mark_ll
|
||||
- 火冷
|
||||
- Solomon
|
||||
- 李宇星
|
||||
- Yus
|
||||
- IndexXuan
|
||||
- Sniper
|
||||
- 陈达野
|
||||
- 胖听
|
||||
- Jimmy
|
||||
- lightxue
|
||||
- 小亦俊
|
||||
- 周慎敏
|
||||
- 凤鸣
|
||||
- Wilson
|
||||
- Abel
|
|
@ -1,142 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
## How do I... <a name="toc"></a>
|
||||
|
||||
- [Use This Guide](#introduction)?
|
||||
- Make Something? 🤓👩🏽💻📜🍳
|
||||
- [Project Setup](#project-setup)
|
||||
- [Contribute Documentation](#contribute-documentation)
|
||||
- [Contribute Code](#contribute-code)
|
||||
- Manage Something ✅🙆🏼💃👔
|
||||
- [Provide Support on Issues](#provide-support-on-issues)
|
||||
- [Review Pull Requests](#review-pull-requests)
|
||||
- [Join the Project Team](#join-the-project-team)
|
||||
|
||||
## Introduction
|
||||
|
||||
Thank you so much for your interest in contributing!. All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them!📝
|
||||
|
||||
The [Project Team](#join-the-project-team) looks forward to your contributions. 🙌🏾✨
|
||||
|
||||
## Project Setup
|
||||
|
||||
So you wanna contribute some code! That's great! This project uses GitHub Pull Requests to manage contributions, so [read up on how to fork a GitHub project and file a PR](https://guides.github.com/activities/forking) if you've never done it before.
|
||||
|
||||
If this seems like a lot or you aren't able to do all this setup, you might also be able to [edit the files directly](https://help.github.com/articles/editing-files-in-another-user-s-repository/) without having to do any of this setup. Yes, [even code](#contribute-code).
|
||||
|
||||
If you want to go the usual route and run the project locally, though:
|
||||
|
||||
- [Install Node.js](https://nodejs.org/en/download/)
|
||||
- [Install Yarn](https://yarnpkg.com)
|
||||
- [Fork the project](https://guides.github.com/activities/forking/#fork)
|
||||
|
||||
Then in your terminal:
|
||||
|
||||
- Add coc.nvim to your vim's rtp by `set runtimepath^=/path/to/coc.nvim`
|
||||
- `cd path/to/your/coc.nvim`
|
||||
- `yarn install`
|
||||
- Install [coc-tsserver](https://github.com/neoclide/coc-tsserver) by
|
||||
`:CocInstall coc-tsserver` in your vim
|
||||
- Install [coc-tslint-plugin](https://github.com/neoclide/coc-tslint-plugin) by
|
||||
`:CocInstall coc-tslint-plugin` in your vim.
|
||||
|
||||
And you should be ready to go!
|
||||
|
||||
## Contribute Documentation
|
||||
|
||||
Documentation is a super important, critical part of this project. Docs are how we keep track of what we're doing, how, and why. It's how we stay on the same page about our policies. And it's how we tell others everything they need in order to be able to use this project -- or contribute to it. So thank you in advance.
|
||||
|
||||
Documentation contributions of any size are welcome! Feel free to file a PR even if you're just rewording a sentence to be more clear, or fixing a spelling mistake!
|
||||
|
||||
To contribute documentation:
|
||||
|
||||
- [Set up the project](#project-setup).
|
||||
- Edit or add any relevant documentation.
|
||||
- Make sure your changes are formatted correctly and consistently with the rest of the documentation.
|
||||
- Re-read what you wrote, and run a spellchecker on it to make sure you didn't miss anything.
|
||||
- In your commit message(s), begin the first line with `docs:`. For example: `docs: Adding a doc contrib section to CONTRIBUTING.md`.
|
||||
- Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). Documentation commits should use `docs(<component>): <message>`.
|
||||
- Go to https://github.com/neoclide/coc.nvim/pulls and open a new pull request with your changes.
|
||||
- If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing.
|
||||
|
||||
## Contribute Code
|
||||
|
||||
We like code commits a lot! They're super handy, and they keep the project going and doing the work it needs to do to be useful to others.
|
||||
|
||||
Code contributions of just about any size are acceptable!
|
||||
|
||||
The main difference between code contributions and documentation contributions is that contributing code requires inclusion of relevant tests for the code being added or changed. Contributions without accompanying tests will be held off until a test is added, unless the maintainers consider the specific tests to be either impossible, or way too much of a burden for such a contribution.
|
||||
|
||||
To contribute code:
|
||||
|
||||
- [Set up the project](#project-setup).
|
||||
- Make any necessary changes to the source code.
|
||||
- Include any [additional documentation](#contribute-documentation) the changes might need.
|
||||
- Make sure the code doesn't have lint issue by command `yarn lint` in your
|
||||
terminal.
|
||||
- Write tests that verify that your contribution works as expected when necessary.
|
||||
- Make sure all tests passed by command `yarn jest` in your terminal.
|
||||
- Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md).
|
||||
- Dependency updates, additions, or removals must be in individual commits, and the message must use the format: `<prefix>(deps): PKG@VERSION`, where `<prefix>` is any of the usual `conventional-changelog` prefixes, at your discretion.
|
||||
- Go to https://github.com/neoclide/coc.nvim/pulls and open a new pull request with your changes.
|
||||
- If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing.
|
||||
|
||||
Once you've filed the PR:
|
||||
|
||||
- Barring special circumstances, maintainers will not review PRs until all checks pass (Travis, AppVeyor, etc).
|
||||
- One or more maintainers will use GitHub's review feature to review your PR.
|
||||
- If the maintainer asks for any changes, edit your changes, push, and ask for another review. Additional tags (such as `needs-tests`) will be added depending on the review.
|
||||
- If the maintainer decides to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. That's ok! We still really appreciate you taking the time to do it, and we don't take that lightly. 💚
|
||||
- If your PR gets accepted, it will be marked as such, and merged into the `latest` branch soon after. Your contribution will be distributed to the masses next time the maintainers [tag a release](#tag-a-release)
|
||||
|
||||
## Provide Support on Issues
|
||||
|
||||
[Needs Collaborator](#join-the-project-team): none
|
||||
|
||||
Helping out other users with their questions is a really awesome way of contributing to any community. It's not uncommon for most of the issues on an open source projects being support-related questions by users trying to understand something they ran into, or find their way around a known bug.
|
||||
|
||||
Sometimes, the `support` label will be added to things that turn out to actually be other things, like bugs or feature requests. In that case, suss out the details with the person who filed the original issue, add a comment explaining what the bug is, and change the label from `support` to `bug` or `feature`. If you can't do this yourself, @mention a maintainer so they can do it.
|
||||
|
||||
In order to help other folks out with their questions:
|
||||
|
||||
- Go to the issue tracker and [filter open issues by the `support` label](https://github.com/neoclide/coc.nvim/issues?q=is%3Aopen+is%3Aissue+label%3Asupport).
|
||||
- Read through the list until you find something that you're familiar enough with to give an answer to.
|
||||
- Respond to the issue with whatever details are needed to clarify the question, or get more details about what's going on.
|
||||
- Once the discussion wraps up and things are clarified, either close the issue, or ask the original issue filer (or a maintainer) to close it for you.
|
||||
|
||||
Some notes on picking up support issues:
|
||||
|
||||
- Avoid responding to issues you don't know you can answer accurately.
|
||||
- As much as possible, try to refer to past issues with accepted answers. Link to them from your replies with the `#123` format.
|
||||
- Be kind and patient with users -- often, folks who have run into confusing things might be upset or impatient. This is ok. Try to understand where they're coming from, and if you're too uncomfortable with the tone, feel free to stay away or withdraw from the issue. (note: if the user is outright hostile or is violating the CoC, [refer to the Code of Conduct](CODE_OF_CONDUCT.md) to resolve the conflict).
|
||||
|
||||
## Review Pull Requests
|
||||
|
||||
[Needs Collaborator](#join-the-project-team): Issue Tracker
|
||||
|
||||
While anyone can comment on a PR, add feedback, etc, PRs are only _approved_ by team members with Issue Tracker or higher permissions.
|
||||
|
||||
PR reviews use [GitHub's own review feature](https://help.github.com/articles/about-pull-request-reviews/), which manages comments, approval, and review iteration.
|
||||
|
||||
Some notes:
|
||||
|
||||
- You may ask for minor changes ("nitpicks"), but consider whether they are really blockers to merging: try to err on the side of "approve, with comments".
|
||||
- _ALL PULL REQUESTS_ should be covered by a test: either by a previously-failing test, an existing test that covers the entire functionality of the submitted code, or new tests to verify any new/changed behavior. All tests must also pass and follow established conventions. Test coverage should not drop, unless the specific case is considered reasonable by maintainers.
|
||||
- Please make sure you're familiar with the code or documentation being updated, unless it's a minor change (spellchecking, minor formatting, etc). You may @mention another project member who you think is better suited for the review, but still provide a non-approving review of your own.
|
||||
- Be extra kind: people who submit code/doc contributions are putting themselves in a pretty vulnerable position, and have put time and care into what they've done (even if that's not obvious to you!) -- always respond with respect, be understanding, but don't feel like you need to sacrifice your standards for their sake, either. Just don't be a jerk about it?
|
||||
|
||||
## Join the Project Team
|
||||
|
||||
### Ways to Join
|
||||
|
||||
There are many ways to contribute! Most of them don't require any official status unless otherwise noted. That said, there's a couple of positions that grant special repository abilities, and this section describes how they're granted and what they do.
|
||||
|
||||
All of the below positions are granted based on the project team's needs, as well as their consensus opinion about whether they would like to work with the person and think that they would fit well into that position. The process is relatively informal, and it's likely that people who express interest in participating can just be granted the permissions they'd like.
|
||||
|
||||
You can spot a collaborator on the repo by looking for the `[Collaborator]` or `[Owner]` tags next to their names.
|
||||
|
||||
| Permission | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Issue Tracker | Granted to contributors who express a strong interest in spending time on the project's issue tracker. These tasks are mainly [labeling issues](#label-issues), [cleaning up old ones](#clean-up-issues-and-prs), and [reviewing pull requests](#review-pull-requests), as well as all the usual things non-team-member contributors can do. Issue handlers should not merge pull requests, tag releases, or directly commit code themselves: that should still be done through the usual pull request process. Becoming an Issue Handler means the project team trusts you to understand enough of the team's process and context to implement it on the issue tracker. |
|
||||
| Committer | Granted to contributors who want to handle the actual pull request merges, tagging new versions, etc. Committers should have a good level of familiarity with the codebase, and enough context to understand the implications of various changes, as well as a good sense of the will and expectations of the project team. |
|
||||
| Admin/Owner | Granted to people ultimately responsible for the project, its community, etc. |
|
|
@ -1,46 +1,7 @@
|
|||
Copyright (c) <2022> <chemzqm@gmail.com>
|
||||
Copyright 2018-2018 by Qiming Zhao <chemzqm@gmail.com>aaa
|
||||
|
||||
"Anti 996" License Version 1.0 (Draft)
|
||||
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:
|
||||
|
||||
Permission is hereby granted to any individual or legal entity
|
||||
obtaining a copy of this licensed work (including the source code,
|
||||
documentation and/or related items, hereinafter collectively referred
|
||||
to as the "licensed work"), free of charge, to deal with the licensed
|
||||
work for any purpose, including without limitation, the rights to use,
|
||||
reproduce, modify, prepare derivative works of, distribute, publish
|
||||
and sublicense the licensed work, 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.
|
||||
|
||||
1. The individual or the legal entity must conspicuously display,
|
||||
without modification, this License and the notice on each redistributed
|
||||
or derivative copy of the Licensed Work.
|
||||
|
||||
2. The individual or the legal entity must strictly comply with all
|
||||
applicable laws, regulations, rules and standards of the jurisdiction
|
||||
relating to labor and employment where the individual is physically
|
||||
located or where the individual was born or naturalized; or where the
|
||||
legal entity is registered or is operating (whichever is stricter). In
|
||||
case that the jurisdiction has no such laws, regulations, rules and
|
||||
standards or its laws, regulations, rules and standards are
|
||||
unenforceable, the individual or the legal entity are required to
|
||||
comply with Core International Labor Standards.
|
||||
|
||||
3. The individual or the legal entity shall not induce, suggest or force
|
||||
its employee(s), whether full-time or part-time, or its independent
|
||||
contractor(s), in any methods, to agree in oral or written form, to
|
||||
directly or indirectly restrict, weaken or relinquish his or her
|
||||
rights or remedies under such laws, regulations, rules and standards
|
||||
relating to labor and employment as mentioned above, no matter whether
|
||||
such written or oral agreements are enforceable under the laws of the
|
||||
said jurisdiction, nor shall such individual or the legal entity
|
||||
limit, in any methods, the rights of its employee(s) or independent
|
||||
contractor(s) from reporting or complaining to the copyright holder or
|
||||
relevant authorities monitoring the compliance of the license about
|
||||
its violation(s) of the said license.
|
||||
|
||||
THE LICENSED WORK 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 COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE
|
||||
LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.
|
||||
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.
|
||||
|
|
148
sources_non_forked/coc.nvim/autoload/coc/helper.vim
Normal file
148
sources_non_forked/coc.nvim/autoload/coc/helper.vim
Normal file
|
@ -0,0 +1,148 @@
|
|||
scriptencoding utf-8
|
||||
" Helper methods for viml
|
||||
|
||||
function! coc#helper#get_charactor(line, col) abort
|
||||
return strchars(strpart(a:line, 0, a:col - 1))
|
||||
endfunction
|
||||
|
||||
function! coc#helper#last_character(line) abort
|
||||
return strcharpart(a:line, strchars(a:line) - 1, 1)
|
||||
endfunction
|
||||
|
||||
function! coc#helper#obj_equal(one, two) abort
|
||||
for key in keys(a:one)
|
||||
if a:one[key] != a:two[key]
|
||||
return 0
|
||||
endif
|
||||
endfor
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
" get change between two lines
|
||||
function! coc#helper#str_diff(curr, previous, col) abort
|
||||
let end = strpart(a:curr, a:col - 1)
|
||||
let start = strpart(a:curr, 0, a:col -1)
|
||||
let endOffset = 0
|
||||
let startOffset = 0
|
||||
let currLen = strchars(a:curr)
|
||||
let prevLen = strchars(a:previous)
|
||||
if len(end)
|
||||
let endLen = strchars(end)
|
||||
for i in range(min([prevLen, endLen]))
|
||||
if strcharpart(end, endLen - 1 - i, 1) ==# strcharpart(a:previous, prevLen -1 -i, 1)
|
||||
let endOffset = endOffset + 1
|
||||
else
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
let remain = endOffset == 0 ? a:previous : strcharpart(a:previous, 0, prevLen - endOffset)
|
||||
if len(remain)
|
||||
for i in range(min([strchars(remain), strchars(start)]))
|
||||
if strcharpart(remain, i, 1) ==# strcharpart(start, i ,1)
|
||||
let startOffset = startOffset + 1
|
||||
else
|
||||
break
|
||||
endif
|
||||
endfor
|
||||
endif
|
||||
return {
|
||||
\ 'start': startOffset,
|
||||
\ 'end': prevLen - endOffset,
|
||||
\ 'text': strcharpart(a:curr, startOffset, currLen - startOffset - endOffset)
|
||||
\ }
|
||||
endfunction
|
||||
|
||||
function! coc#helper#str_apply(content, diff) abort
|
||||
let totalLen = strchars(a:content)
|
||||
let endLen = totalLen - a:diff['end']
|
||||
return strcharpart(a:content, 0, a:diff['start']).a:diff['text'].strcharpart(a:content, a:diff['end'], endLen)
|
||||
endfunction
|
||||
|
||||
" insert inserted to line at position, use ... when result is too long
|
||||
" line should only contains character has strwidth equals 1
|
||||
function! coc#helper#str_compose(line, position, inserted) abort
|
||||
let width = strwidth(a:line)
|
||||
let text = a:inserted
|
||||
let res = a:line
|
||||
let need_truncate = a:position + strwidth(text) + 1 > width
|
||||
if need_truncate
|
||||
let remain = width - a:position - 3
|
||||
if remain < 2
|
||||
" use text for full line, use first & end of a:line, ignore position
|
||||
let res = strcharpart(a:line, 0, 1)
|
||||
let w = strwidth(res)
|
||||
for i in range(strchars(text))
|
||||
let c = strcharpart(text, i, 1)
|
||||
let a = strwidth(c)
|
||||
if w + a <= width - 1
|
||||
let w = w + a
|
||||
let res = res.c
|
||||
endif
|
||||
endfor
|
||||
let res = res.strcharpart(a:line, w)
|
||||
else
|
||||
let res = strcharpart(a:line, 0, a:position)
|
||||
let w = strwidth(res)
|
||||
for i in range(strchars(text))
|
||||
let c = strcharpart(text, i, 1)
|
||||
let a = strwidth(c)
|
||||
if w + a <= width - 3
|
||||
let w = w + a
|
||||
let res = res.c
|
||||
endif
|
||||
endfor
|
||||
let res = res.'..'
|
||||
let w = w + 2
|
||||
let res = res.strcharpart(a:line, w)
|
||||
endif
|
||||
else
|
||||
let first = strcharpart(a:line, 0, a:position)
|
||||
let res = first.text.strcharpart(a:line, a:position + strwidth(text))
|
||||
endif
|
||||
return res
|
||||
endfunction
|
||||
|
||||
" Return new dict with keys removed
|
||||
function! coc#helper#dict_omit(dict, keys) abort
|
||||
let res = {}
|
||||
for key in keys(a:dict)
|
||||
if index(a:keys, key) == -1
|
||||
let res[key] = a:dict[key]
|
||||
endif
|
||||
endfor
|
||||
return res
|
||||
endfunction
|
||||
|
||||
" Return new dict with keys only
|
||||
function! coc#helper#dict_pick(dict, keys) abort
|
||||
let res = {}
|
||||
for key in keys(a:dict)
|
||||
if index(a:keys, key) != -1
|
||||
let res[key] = a:dict[key]
|
||||
endif
|
||||
endfor
|
||||
return res
|
||||
endfunction
|
||||
|
||||
" support for float values
|
||||
function! coc#helper#min(first, ...) abort
|
||||
let val = a:first
|
||||
for i in range(0, len(a:000) - 1)
|
||||
if a:000[i] < val
|
||||
let val = a:000[i]
|
||||
endif
|
||||
endfor
|
||||
return val
|
||||
endfunction
|
||||
|
||||
" support for float values
|
||||
function! coc#helper#max(first, ...) abort
|
||||
let val = a:first
|
||||
for i in range(0, len(a:000) - 1)
|
||||
if a:000[i] > val
|
||||
let val = a:000[i]
|
||||
endif
|
||||
endfor
|
||||
return val
|
||||
endfunction
|
|
@ -292,9 +292,8 @@ function! coc#notify#close(winid) abort
|
|||
endif
|
||||
call coc#window#set_var(a:winid, 'closing', 1)
|
||||
call s:cancel(a:winid)
|
||||
let winblend = coc#window#get_var(a:winid, 'winblend', 0)
|
||||
let curr = s:is_vim ? {'row': row} : {'winblend': winblend}
|
||||
let dest = s:is_vim ? {'row': row + 1} : {'winblend': winblend == 0 ? 0 : 60}
|
||||
let curr = s:is_vim ? {'row': row} : {'winblend': coc#window#get_var(a:winid, 'winblend', 30)}
|
||||
let dest = s:is_vim ? {'row': row + 1} : {'winblend': 60}
|
||||
call s:animate(a:winid, curr, dest, 0, 1)
|
||||
endfunction
|
||||
|
||||
|
|
|
@ -182,10 +182,14 @@ function! coc#util#jump(cmd, filepath, ...) abort
|
|||
else
|
||||
exec 'drop '.fnameescape(file)
|
||||
endif
|
||||
elseif a:cmd == 'edit' && bufloaded(file)
|
||||
exe 'b '.bufnr(file)
|
||||
elseif a:cmd == 'edit'
|
||||
if bufloaded(file)
|
||||
exe 'b '.bufnr(file)
|
||||
else
|
||||
exe a:cmd.' '.fnameescape(file)
|
||||
endif
|
||||
else
|
||||
call s:safer_open(a:cmd, file)
|
||||
exe a:cmd.' '.fnameescape(file)
|
||||
endif
|
||||
if !empty(get(a:, 1, []))
|
||||
let line = getline(a:1[0] + 1)
|
||||
|
@ -204,32 +208,6 @@ function! coc#util#jump(cmd, filepath, ...) abort
|
|||
endif
|
||||
endfunction
|
||||
|
||||
function! s:safer_open(cmd, file) abort
|
||||
" How to support :pedit and :drop?
|
||||
let is_supported_cmd = index(["edit", "split", "vsplit", "tabe"], a:cmd) >= 0
|
||||
|
||||
" Use special handling only for URI.
|
||||
let looks_like_uri = match(a:file, "^.*://") >= 0
|
||||
|
||||
if looks_like_uri && is_supported_cmd && has('win32') && exists('*bufadd')
|
||||
" Workaround a bug for Win32 paths.
|
||||
"
|
||||
" reference:
|
||||
" - https://github.com/vim/vim/issues/541
|
||||
" - https://github.com/neoclide/coc-java/issues/82
|
||||
" - https://github.com/vim-jp/issues/issues/6
|
||||
let buf = bufadd(a:file)
|
||||
if a:cmd != 'edit'
|
||||
" Open split, tab, etc. by a:cmd.
|
||||
exe a:cmd
|
||||
endif
|
||||
" Set current buffer to the file
|
||||
exe 'keepjumps buffer ' . buf
|
||||
else
|
||||
exe a:cmd.' '.fnameescape(a:file)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! coc#util#variables(bufnr) abort
|
||||
let info = getbufinfo(a:bufnr)
|
||||
let variables = empty(info) ? {} : copy(info[0]['variables'])
|
||||
|
|
337
sources_non_forked/coc.nvim/build/index.js
Normal file
337
sources_non_forked/coc.nvim/build/index.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,77 +0,0 @@
|
|||
const cp = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
let revision = 'master'
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
try {
|
||||
let res = cp.execSync(`git log -1 --date=iso --pretty=format:'"%h","%ad"'`, {encoding: 'utf8'})
|
||||
revision = res.replaceAll('"', '').replace(',', ' ')
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
let envPlugin = {
|
||||
name: 'env',
|
||||
setup(build) {
|
||||
build.onResolve({filter: /\/appenders$/}, args => {
|
||||
let fullpath = path.join(args.resolveDir, args.path)
|
||||
return {
|
||||
path: path.relative(__dirname, fullpath).replace(/\\/g, '/'),
|
||||
namespace: 'env-ns'
|
||||
}
|
||||
})
|
||||
build.onLoad({filter: /^node_modules\/log4js\/lib\/appenders$/, namespace: 'env-ns'}, args => {
|
||||
let content = fs.readFileSync(path.join(args.path, 'index.js'), 'utf8')
|
||||
return {
|
||||
contents: content.replace(/require\.main/g, '""'),
|
||||
resolveDir: args.path
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function start(watch) {
|
||||
await require('esbuild').build({
|
||||
entryPoints: ['src/main.ts'],
|
||||
bundle: true,
|
||||
watch,
|
||||
minify: process.env.NODE_ENV === 'production',
|
||||
sourcemap: process.env.NODE_ENV === 'development',
|
||||
define: {REVISION: '"' + revision + '"', ESBUILD: 'true'},
|
||||
mainFields: ['module', 'main'],
|
||||
platform: 'node',
|
||||
target: 'node12.12',
|
||||
outfile: 'build/index.js',
|
||||
banner: {
|
||||
js: `(function () {
|
||||
var v = process.version
|
||||
var parts = v.slice(1).split('.')
|
||||
var major = parseInt(parts[0], 10)
|
||||
var minor = parseInt(parts[1], 10)
|
||||
if (major < 12 || (major == 12 && minor < 12)) {
|
||||
throw new Error('coc.nvim requires node >= v12.12.0, current version: ' + v)
|
||||
}
|
||||
})(); `
|
||||
},
|
||||
plugins: [envPlugin]
|
||||
})
|
||||
}
|
||||
|
||||
let watch = false
|
||||
if (process.argv.includes('--watch')) {
|
||||
console.log('watching...')
|
||||
watch = {
|
||||
onRebuild(error) {
|
||||
if (error) {
|
||||
console.error('watch build failed:', error)
|
||||
} else {
|
||||
console.log('watch build succeeded')
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
start(watch).catch(e => {
|
||||
console.error(e)
|
||||
})
|
|
@ -1,7 +1,3 @@
|
|||
# 2022-06-14
|
||||
|
||||
- Add highlight groups `CocListLine` and `CocListSearch`.
|
||||
|
||||
# 2022-06-11
|
||||
|
||||
- Add configuration "notification.disabledProgressSources"
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
const path = require('path')
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
let msg = 'Uncaught exception: ' + err.stack
|
||||
console.error(msg)
|
||||
})
|
||||
|
||||
process.on('exit', () => {
|
||||
fs.rmdirSync(process.env.TMPDIR, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
module.exports = async () => {
|
||||
let dataHome = path.join(os.tmpdir(), `coc-test/${process.pid}`)
|
||||
fs.mkdirSync(dataHome, { recursive: true })
|
||||
process.env.NODE_ENV = 'test'
|
||||
process.env.COC_DATA_HOME = dataHome
|
||||
process.env.COC_VIMCONFIG = path.join(__dirname, 'src/__tests__')
|
||||
process.env.TMPDIR = '/tmp/coc-test'
|
||||
}
|
|
@ -1,124 +1,17 @@
|
|||
{
|
||||
"name": "coc.nvim-master",
|
||||
"name": "coc.nvim-release",
|
||||
"version": "0.0.81",
|
||||
"description": "LSP based intellisense engine for neovim & vim8.",
|
||||
"main": "./build/index.js",
|
||||
"engines": {
|
||||
"node": ">=12.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts --quiet",
|
||||
"lint:typecheck": "tsc -p tsconfig.json",
|
||||
"build": "node esbuild.js",
|
||||
"test": "./node_modules/.bin/jest --forceExit",
|
||||
"test-build": "./node_modules/.bin/jest --coverage --forceExit",
|
||||
"prepare": "node esbuild.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/neoclide/coc.nvim.git"
|
||||
},
|
||||
"keywords": [
|
||||
"complete",
|
||||
"neovim"
|
||||
],
|
||||
"author": "Qiming Zhao <chemzqm@gmail.com>",
|
||||
"bugs": {
|
||||
"url": "https://github.com/neoclide/coc.nvim/issues"
|
||||
},
|
||||
"homepage": "https://github.com/neoclide/coc.nvim#readme",
|
||||
"jest": {
|
||||
"globals": {
|
||||
"__TEST__": true
|
||||
},
|
||||
"projects": [
|
||||
"<rootDir>"
|
||||
],
|
||||
"watchman": false,
|
||||
"clearMocks": true,
|
||||
"globalSetup": "./jest.js",
|
||||
"testEnvironment": "node",
|
||||
"coveragePathIgnorePatterns": [
|
||||
"<rootDir>/src/__tests__/*"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"json",
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": [
|
||||
"@swc/jest"
|
||||
]
|
||||
},
|
||||
"testRegex": "src/__tests__/.*\\.(test|spec)\\.ts$",
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"lcov"
|
||||
],
|
||||
"coverageDirectory": "./coverage/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.2.183",
|
||||
"@swc/jest": "^0.2.21",
|
||||
"@types/cli-table": "^0.3.0",
|
||||
"@types/debounce": "^3.0.0",
|
||||
"@types/fb-watchman": "^2.0.0",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@types/marked": "^4.0.1",
|
||||
"@types/minimatch": "^3.0.3",
|
||||
"@types/mkdirp": "^1.0.1",
|
||||
"@types/node": "12.12.12",
|
||||
"@types/semver": "^7.3.4",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@types/which": "^1.3.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.20.0",
|
||||
"@typescript-eslint/parser": "^5.20.0",
|
||||
"bser": "^2.1.1",
|
||||
"esbuild": "^0.14.25",
|
||||
"eslint": "^8.14.0",
|
||||
"eslint-plugin-jest": "^26.1.5",
|
||||
"eslint-plugin-jsdoc": "^39.2.8",
|
||||
"jest": "27.4.5",
|
||||
"typescript": "^4.6.3",
|
||||
"vscode-languageserver": "7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chemzqm/neovim": "^5.7.9",
|
||||
"@chemzqm/string-width": "^5.1.2",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"bytes": "^3.1.0",
|
||||
"cli-table": "^0.3.4",
|
||||
"content-disposition": "^0.5.3",
|
||||
"debounce": "^1.2.0",
|
||||
"decompress-response": "^6.0.0",
|
||||
"fast-diff": "^1.2.0",
|
||||
"fb-watchman": "^2.0.1",
|
||||
"follow-redirects": "^1.14.8",
|
||||
"fs-extra": "^9.0.1",
|
||||
"glob": "^7.2.0",
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"isuri": "^2.0.3",
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"log4js": "^6.4.0",
|
||||
"marked": "^4.0.12",
|
||||
"minimatch": "^3.0.4",
|
||||
"semver": "^7.3.2",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"tar": "^6.1.9",
|
||||
"tslib": "^2.0.3",
|
||||
"unidecode": "^0.1.8",
|
||||
"unzip-stream": "^0.3.1",
|
||||
"uuid": "^7.0.3",
|
||||
"vscode-languageserver-protocol": "^3.16.0",
|
||||
"vscode-languageserver-textdocument": "^1.0.3",
|
||||
"vscode-languageserver-types": "^3.16.0",
|
||||
"vscode-uri": "^2.1.2",
|
||||
"which": "^2.0.2"
|
||||
}
|
||||
"homepage": "https://github.com/neoclide/coc.nvim#readme"
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
" vim source for emails
|
||||
function! coc#source#email#init() abort
|
||||
return {
|
||||
\ 'priority': 9,
|
||||
\ 'shortcut': 'Email',
|
||||
\ 'triggerCharacters': ['@']
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! coc#source#email#should_complete(opt) abort
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! coc#source#email#complete(opt, cb) abort
|
||||
let items = ['foo@gmail.com', 'bar@yahoo.com']
|
||||
call a:cb(items)
|
||||
endfunction
|
|
@ -1,62 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import helper from '../helper'
|
||||
// import * as assert from 'assert'
|
||||
import fs from 'fs'
|
||||
import * as lsclient from '../../language-client'
|
||||
import * as path from 'path'
|
||||
import { URI } from 'vscode-uri'
|
||||
// import which from 'which'
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Client integration', () => {
|
||||
|
||||
it('should send file change notification', (done) => {
|
||||
if (global.hasOwnProperty('__TEST__')) return done()
|
||||
let serverModule = path.join(__dirname, './server/testFileWatcher.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
module: serverModule,
|
||||
transport: lsclient.TransportKind.ipc
|
||||
}
|
||||
let clientOptions: lsclient.LanguageClientOptions = {
|
||||
documentSelector: ['css'],
|
||||
synchronize: {}, initializationOptions: {},
|
||||
middleware: {
|
||||
}
|
||||
}
|
||||
let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)
|
||||
let disposable = client.start()
|
||||
|
||||
client.onReady().then(_ => {
|
||||
setTimeout(async () => {
|
||||
let file = path.join(__dirname, 'test.js')
|
||||
fs.writeFileSync(file, '', 'utf8')
|
||||
await helper.wait(300)
|
||||
let res = await client.sendRequest('custom/received')
|
||||
expect(res).toEqual({
|
||||
changes: [{
|
||||
uri: URI.file(file).toString(),
|
||||
type: 1
|
||||
}]
|
||||
})
|
||||
fs.unlinkSync(file)
|
||||
disposable.dispose()
|
||||
done()
|
||||
}, 200)
|
||||
}, e => {
|
||||
disposable.dispose()
|
||||
done(e)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,139 +0,0 @@
|
|||
import { Duplex } from 'stream'
|
||||
import { createProtocolConnection, ProgressType, DocumentSymbolParams, DocumentSymbolRequest, InitializeParams, InitializeRequest, InitializeResult, ProtocolConnection, StreamMessageReader, StreamMessageWriter } from 'vscode-languageserver-protocol/node'
|
||||
import { SymbolInformation, SymbolKind } from 'vscode-languageserver-types'
|
||||
import { NullLogger } from '../../language-client/client'
|
||||
|
||||
class TestStream extends Duplex {
|
||||
public _write(chunk: string, _encoding: string, done: () => void): void {
|
||||
this.emit('data', chunk)
|
||||
done()
|
||||
}
|
||||
|
||||
public _read(_size: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
let serverConnection: ProtocolConnection
|
||||
let clientConnection: ProtocolConnection
|
||||
let progressType: ProgressType<any> = new ProgressType()
|
||||
|
||||
beforeEach(() => {
|
||||
const up = new TestStream()
|
||||
const down = new TestStream()
|
||||
const logger = new NullLogger()
|
||||
serverConnection = createProtocolConnection(new StreamMessageReader(up), new StreamMessageWriter(down), logger)
|
||||
clientConnection = createProtocolConnection(new StreamMessageReader(down), new StreamMessageWriter(up), logger)
|
||||
serverConnection.listen()
|
||||
clientConnection.listen()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
serverConnection.dispose()
|
||||
clientConnection.dispose()
|
||||
})
|
||||
|
||||
describe('Connection Tests', () => {
|
||||
it('should ensure proper param passing', async () => {
|
||||
let paramsCorrect = false
|
||||
serverConnection.onRequest(InitializeRequest.type, params => {
|
||||
paramsCorrect = !Array.isArray(params)
|
||||
let result: InitializeResult = {
|
||||
capabilities: {
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const init: InitializeParams = {
|
||||
rootUri: 'file:///home/dirkb',
|
||||
processId: 1,
|
||||
capabilities: {},
|
||||
workspaceFolders: null,
|
||||
}
|
||||
await clientConnection.sendRequest(InitializeRequest.type, init)
|
||||
expect(paramsCorrect).toBe(true)
|
||||
})
|
||||
|
||||
it('should provide token', async () => {
|
||||
serverConnection.onRequest(DocumentSymbolRequest.type, params => {
|
||||
expect(params.partialResultToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')
|
||||
return []
|
||||
})
|
||||
|
||||
const params: DocumentSymbolParams = {
|
||||
textDocument: { uri: 'file:///abc.txt' },
|
||||
partialResultToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'
|
||||
}
|
||||
await clientConnection.sendRequest(DocumentSymbolRequest.type, params)
|
||||
})
|
||||
|
||||
it('should report result', async () => {
|
||||
let result: SymbolInformation = {
|
||||
name: 'abc',
|
||||
kind: SymbolKind.Class,
|
||||
location: {
|
||||
uri: 'file:///abc.txt',
|
||||
range: { start: { line: 0, character: 1 }, end: { line: 2, character: 3 } }
|
||||
}
|
||||
}
|
||||
serverConnection.onRequest(DocumentSymbolRequest.type, params => {
|
||||
expect(params.partialResultToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')
|
||||
serverConnection.sendProgress(progressType, params.partialResultToken, [result])
|
||||
return []
|
||||
})
|
||||
|
||||
const params: DocumentSymbolParams = {
|
||||
textDocument: { uri: 'file:///abc.txt' },
|
||||
partialResultToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'
|
||||
}
|
||||
let progressOK = false
|
||||
clientConnection.onProgress(progressType, '3b1db4c9-e011-489e-a9d1-0653e64707c2', values => {
|
||||
progressOK = (values !== undefined && values.length === 1)
|
||||
})
|
||||
await clientConnection.sendRequest(DocumentSymbolRequest.type, params)
|
||||
expect(progressOK).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should provide workDoneToken', async () => {
|
||||
serverConnection.onRequest(DocumentSymbolRequest.type, params => {
|
||||
expect(params.workDoneToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')
|
||||
return []
|
||||
})
|
||||
|
||||
const params: DocumentSymbolParams = {
|
||||
textDocument: { uri: 'file:///abc.txt' },
|
||||
workDoneToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'
|
||||
}
|
||||
await clientConnection.sendRequest(DocumentSymbolRequest.type, params)
|
||||
})
|
||||
|
||||
it('should report work done progress', async () => {
|
||||
serverConnection.onRequest(DocumentSymbolRequest.type, params => {
|
||||
expect(params.workDoneToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')
|
||||
serverConnection.sendProgress(progressType, params.workDoneToken, {
|
||||
kind: 'begin',
|
||||
title: 'progress'
|
||||
})
|
||||
serverConnection.sendProgress(progressType, params.workDoneToken, {
|
||||
kind: 'report',
|
||||
message: 'message'
|
||||
})
|
||||
serverConnection.sendProgress(progressType, params.workDoneToken, {
|
||||
kind: 'end',
|
||||
message: 'message'
|
||||
})
|
||||
return []
|
||||
})
|
||||
|
||||
const params: DocumentSymbolParams = {
|
||||
textDocument: { uri: 'file:///abc.txt' },
|
||||
workDoneToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'
|
||||
}
|
||||
let result = ''
|
||||
clientConnection.onProgress(progressType, '3b1db4c9-e011-489e-a9d1-0653e64707c2', value => {
|
||||
result += value.kind
|
||||
})
|
||||
await clientConnection.sendRequest(DocumentSymbolRequest.type, params)
|
||||
expect(result).toBe('beginreportend')
|
||||
})
|
||||
})
|
|
@ -1,86 +0,0 @@
|
|||
import { CompletionTriggerKind, Position, TextDocumentItem, TextDocumentSaveReason } from 'vscode-languageserver-protocol'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import { URI } from 'vscode-uri'
|
||||
import * as cv from '../../language-client/utils/converter'
|
||||
|
||||
describe('converter', () => {
|
||||
|
||||
function createDocument(): TextDocument {
|
||||
return TextDocument.create('file:///1', 'css', 1, '')
|
||||
}
|
||||
|
||||
it('should convertToTextDocumentItem', () => {
|
||||
let doc = createDocument()
|
||||
expect(cv.convertToTextDocumentItem(doc).uri).toBe(doc.uri)
|
||||
expect(TextDocumentItem.is(cv.convertToTextDocumentItem(doc))).toBe(true)
|
||||
})
|
||||
|
||||
it('should asCloseTextDocumentParams', () => {
|
||||
let doc = createDocument()
|
||||
expect(cv.asCloseTextDocumentParams(doc).textDocument.uri).toBe(doc.uri)
|
||||
})
|
||||
|
||||
it('should asChangeTextDocumentParams', () => {
|
||||
let doc = createDocument()
|
||||
expect(cv.asChangeTextDocumentParams(doc).textDocument.uri).toBe(doc.uri)
|
||||
})
|
||||
|
||||
it('should asWillSaveTextDocumentParams', () => {
|
||||
let res = cv.asWillSaveTextDocumentParams({ document: createDocument(), reason: TextDocumentSaveReason.Manual, waitUntil: () => {} })
|
||||
expect(res.textDocument).toBeDefined()
|
||||
expect(res.reason).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asVersionedTextDocumentIdentifier', () => {
|
||||
let res = cv.asVersionedTextDocumentIdentifier(createDocument())
|
||||
expect(res.uri).toBeDefined()
|
||||
expect(res.version).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asSaveTextDocumentParams', () => {
|
||||
let res = cv.asSaveTextDocumentParams(createDocument(), true)
|
||||
expect(res.textDocument.uri).toBeDefined()
|
||||
expect(res.text).toBeDefined()
|
||||
res = cv.asSaveTextDocumentParams(createDocument(), false)
|
||||
expect(res.text).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should asUri', () => {
|
||||
let uri = URI.file('/tmp/a')
|
||||
expect(cv.asUri(uri)).toBe(uri.toString())
|
||||
})
|
||||
|
||||
it('should asCompletionParams', () => {
|
||||
let params = cv.asCompletionParams(createDocument(), Position.create(0, 0), { triggerKind: CompletionTriggerKind.Invoked })
|
||||
expect(params.textDocument).toBeDefined()
|
||||
expect(params.position).toBeDefined()
|
||||
expect(params.context).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asTextDocumentPositionParams', () => {
|
||||
let params = cv.asTextDocumentPositionParams(createDocument(), Position.create(0, 0))
|
||||
expect(params.textDocument).toBeDefined()
|
||||
expect(params.position).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asTextDocumentIdentifier', () => {
|
||||
let doc = cv.asTextDocumentIdentifier(createDocument())
|
||||
expect(doc.uri).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asReferenceParams', () => {
|
||||
let params = cv.asReferenceParams(createDocument(), Position.create(0, 0), { includeDeclaration: false })
|
||||
expect(params.textDocument.uri).toBeDefined()
|
||||
expect(params.position).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asDocumentSymbolParams', () => {
|
||||
let doc = cv.asDocumentSymbolParams(createDocument())
|
||||
expect(doc.textDocument.uri).toBeDefined()
|
||||
})
|
||||
|
||||
it('should asCodeLensParams', () => {
|
||||
let doc = cv.asCodeLensParams(createDocument())
|
||||
expect(doc.textDocument.uri).toBeDefined()
|
||||
})
|
||||
})
|
|
@ -1,72 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import assert from 'assert'
|
||||
import { Delayer } from '../../language-client/utils/async'
|
||||
import { wait } from '../../util/index'
|
||||
|
||||
test('Delayer', () => {
|
||||
let count = 0
|
||||
let factory = () => {
|
||||
return Promise.resolve(++count)
|
||||
}
|
||||
|
||||
let delayer = new Delayer(0)
|
||||
let promises: Thenable<any>[] = []
|
||||
|
||||
assert(!delayer.isTriggered())
|
||||
|
||||
promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))
|
||||
assert(delayer.isTriggered())
|
||||
|
||||
promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))
|
||||
assert(delayer.isTriggered())
|
||||
|
||||
promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))
|
||||
assert(delayer.isTriggered())
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
assert(!delayer.isTriggered())
|
||||
}).finally(() => {
|
||||
delayer.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
test('Delayer - forceDelivery', async () => {
|
||||
let count = 0
|
||||
let factory = () => {
|
||||
return Promise.resolve(++count)
|
||||
}
|
||||
|
||||
let delayer = new Delayer(150)
|
||||
delayer.forceDelivery()
|
||||
delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) })
|
||||
await wait(10)
|
||||
delayer.forceDelivery()
|
||||
expect(count).toBe(1)
|
||||
void delayer.trigger(factory)
|
||||
await wait(10)
|
||||
delayer.cancel()
|
||||
expect(count).toBe(1)
|
||||
})
|
||||
|
||||
test('Delayer - last task should be the one getting called', function() {
|
||||
let factoryFactory = (n: number) => () => {
|
||||
return Promise.resolve(n)
|
||||
}
|
||||
|
||||
let delayer = new Delayer(0)
|
||||
let promises: Thenable<any>[] = []
|
||||
|
||||
assert(!delayer.isTriggered())
|
||||
|
||||
promises.push(delayer.trigger(factoryFactory(1)).then((n) => { assert.equal(n, 3) }))
|
||||
promises.push(delayer.trigger(factoryFactory(2)).then((n) => { assert.equal(n, 3) }))
|
||||
promises.push(delayer.trigger(factoryFactory(3)).then((n) => { assert.equal(n, 3) }))
|
||||
|
||||
const p = Promise.all(promises).then(() => {
|
||||
assert(!delayer.isTriggered())
|
||||
})
|
||||
|
||||
assert(delayer.isTriggered())
|
||||
|
||||
return p
|
||||
})
|
File diff suppressed because it is too large
Load diff
|
@ -1,123 +0,0 @@
|
|||
/* eslint-disable */
|
||||
import helper from '../helper'
|
||||
import * as assert from 'assert'
|
||||
import * as lsclient from '../../language-client'
|
||||
import path from 'path'
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
async function testLanguageServer(serverOptions: lsclient.ServerOptions): Promise<lsclient.LanguageClient> {
|
||||
let clientOptions: lsclient.LanguageClientOptions = {
|
||||
documentSelector: ['css'],
|
||||
synchronize: {},
|
||||
initializationOptions: {}
|
||||
}
|
||||
let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)
|
||||
client.start()
|
||||
await client.onReady()
|
||||
expect(client.initializeResult).toBeDefined()
|
||||
return client
|
||||
}
|
||||
|
||||
describe('Client integration', () => {
|
||||
|
||||
it('should initialize use IPC channel', (done) => {
|
||||
let serverModule = path.join(__dirname, './server/testInitializeResult.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
run: { module: serverModule, transport: lsclient.TransportKind.ipc },
|
||||
debug: { module: serverModule, transport: lsclient.TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }
|
||||
}
|
||||
let clientOptions: lsclient.LanguageClientOptions = {
|
||||
documentSelector: ['css'],
|
||||
synchronize: {}, initializationOptions: {},
|
||||
middleware: {
|
||||
handleDiagnostics: (uri, diagnostics, next) => {
|
||||
assert.equal(uri, "uri:/test.ts")
|
||||
assert.ok(Array.isArray(diagnostics))
|
||||
assert.equal(diagnostics.length, 0)
|
||||
next(uri, diagnostics)
|
||||
}
|
||||
}
|
||||
}
|
||||
let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)
|
||||
client.start()
|
||||
|
||||
assert.equal(client.initializeResult, undefined)
|
||||
|
||||
client.onReady().then(_ => {
|
||||
try {
|
||||
let expected = {
|
||||
capabilities: {
|
||||
textDocumentSync: 1,
|
||||
completionProvider: { resolveProvider: true, triggerCharacters: ['"', ':'] },
|
||||
hoverProvider: true,
|
||||
renameProvider: {
|
||||
prepareProvider: true
|
||||
}
|
||||
},
|
||||
customResults: {
|
||||
"hello": "world"
|
||||
}
|
||||
}
|
||||
assert.deepEqual(client.initializeResult, expected)
|
||||
setTimeout(async () => {
|
||||
await client.stop()
|
||||
done()
|
||||
}, 50)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
}, e => {
|
||||
done(e)
|
||||
})
|
||||
})
|
||||
|
||||
it('should initialize use stdio', async () => {
|
||||
let serverModule = path.join(__dirname, './server/testInitializeResult.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
module: serverModule,
|
||||
transport: lsclient.TransportKind.stdio
|
||||
}
|
||||
let client = await testLanguageServer(serverOptions)
|
||||
await client.stop()
|
||||
})
|
||||
|
||||
it('should initialize use pipe', async () => {
|
||||
let serverModule = path.join(__dirname, './server/testInitializeResult.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
module: serverModule,
|
||||
transport: lsclient.TransportKind.pipe
|
||||
}
|
||||
let client = await testLanguageServer(serverOptions)
|
||||
await client.stop()
|
||||
})
|
||||
|
||||
it('should initialize use socket', async () => {
|
||||
let serverModule = path.join(__dirname, './server/testInitializeResult.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
module: serverModule,
|
||||
transport: {
|
||||
kind: lsclient.TransportKind.socket,
|
||||
port: 8088
|
||||
}
|
||||
}
|
||||
let client = await testLanguageServer(serverOptions)
|
||||
await client.stop()
|
||||
})
|
||||
|
||||
it('should initialize as command', async () => {
|
||||
let serverModule = path.join(__dirname, './server/testInitializeResult.js')
|
||||
let serverOptions: lsclient.ServerOptions = {
|
||||
command: 'node',
|
||||
args: [serverModule, '--stdio']
|
||||
}
|
||||
let client = await testLanguageServer(serverOptions)
|
||||
await client.stop()
|
||||
})
|
||||
})
|
|
@ -1,35 +0,0 @@
|
|||
const languageserver = require('vscode-languageserver')
|
||||
let connection = languageserver.createConnection()
|
||||
let documents = new languageserver.TextDocuments()
|
||||
documents.listen(connection)
|
||||
|
||||
connection.onInitialize(() => {
|
||||
let capabilities = {
|
||||
textDocumentSync: documents.syncKind
|
||||
}
|
||||
return { capabilities }
|
||||
})
|
||||
|
||||
connection.onInitialized(() => {
|
||||
connection.sendRequest('client/registerCapability', {
|
||||
registrations: [{
|
||||
id: 'didChangeWatchedFiles',
|
||||
method: 'workspace/didChangeWatchedFiles',
|
||||
registerOptions: {
|
||||
watchers: [{ globPattern: "**" }]
|
||||
}
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
let received
|
||||
|
||||
connection.onNotification('workspace/didChangeWatchedFiles', params => {
|
||||
received = params
|
||||
})
|
||||
|
||||
connection.onRequest('custom/received', async () => {
|
||||
return received
|
||||
})
|
||||
|
||||
connection.listen()
|
|
@ -1,36 +0,0 @@
|
|||
'use strict'
|
||||
Object.defineProperty(exports, "__esModule", {value: true})
|
||||
const tslib_1 = require("tslib")
|
||||
const assert = tslib_1.__importStar(require("assert"))
|
||||
const vscode_languageserver_1 = require("vscode-languageserver")
|
||||
let connection = vscode_languageserver_1.createConnection()
|
||||
|
||||
let documents = new vscode_languageserver_1.TextDocuments()
|
||||
documents.listen(connection)
|
||||
connection.onInitialize((params) => {
|
||||
assert.equal(params.capabilities.workspace.applyEdit, true)
|
||||
assert.equal(params.capabilities.workspace.workspaceEdit.documentChanges, true)
|
||||
assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, [vscode_languageserver_1.ResourceOperationKind.Create, vscode_languageserver_1.ResourceOperationKind.Rename, vscode_languageserver_1.ResourceOperationKind.Delete])
|
||||
assert.equal(params.capabilities.workspace.workspaceEdit.failureHandling, vscode_languageserver_1.FailureHandlingKind.Undo)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.deprecatedSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.preselectSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.signatureHelp.signatureInformation.parameterInformation.labelOffsetSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.rename.prepareSupport, true)
|
||||
let valueSet = params.capabilities.textDocument.completion.completionItemKind.valueSet
|
||||
assert.equal(valueSet[0], 1)
|
||||
assert.equal(valueSet[valueSet.length - 1], vscode_languageserver_1.CompletionItemKind.TypeParameter)
|
||||
let capabilities = {
|
||||
textDocumentSync: documents.syncKind,
|
||||
completionProvider: {resolveProvider: true, triggerCharacters: ['"', ':']},
|
||||
hoverProvider: true,
|
||||
renameProvider: {
|
||||
prepareProvider: true
|
||||
}
|
||||
}
|
||||
return {capabilities, customResults: {"hello": "world"}}
|
||||
})
|
||||
connection.onInitialized(() => {
|
||||
connection.sendDiagnostics({uri: "uri:/test.ts", diagnostics: []})
|
||||
})
|
||||
// Listen on the connection
|
||||
connection.listen()
|
|
@ -1,409 +0,0 @@
|
|||
const assert = require('assert')
|
||||
const {URI} = require('vscode-uri')
|
||||
const {
|
||||
createConnection, CompletionItemKind, ResourceOperationKind, FailureHandlingKind,
|
||||
DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureInformation, ParameterInformation,
|
||||
Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink,
|
||||
ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress,
|
||||
WorkDoneProgressCreateRequest} = require('vscode-languageserver')
|
||||
|
||||
const {
|
||||
DidCreateFilesNotification,
|
||||
DidRenameFilesNotification,
|
||||
DidDeleteFilesNotification,
|
||||
WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest
|
||||
} = require('vscode-languageserver-protocol')
|
||||
|
||||
let connection = createConnection()
|
||||
|
||||
console.log = connection.console.log.bind(connection.console)
|
||||
console.error = connection.console.error.bind(connection.console)
|
||||
|
||||
connection.onInitialize(params => {
|
||||
assert.equal((params.capabilities.workspace).applyEdit, true)
|
||||
assert.equal(params.capabilities.workspace.workspaceEdit.documentChanges, true)
|
||||
assert.equal(params.capabilities.workspace.workspaceEdit.failureHandling, FailureHandlingKind.Undo)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.deprecatedSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.preselectSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.tagSupport.valueSet.length, 1)
|
||||
assert.equal(params.capabilities.textDocument.completion.completionItem.tagSupport.valueSet[0], CompletionItemTag.Deprecated)
|
||||
assert.equal(params.capabilities.textDocument.signatureHelp.signatureInformation.parameterInformation.labelOffsetSupport, true)
|
||||
// assert.equal(params.capabilities.textDocument.definition.linkSupport, true)
|
||||
// assert.equal(params.capabilities.textDocument.declaration.linkSupport, true)
|
||||
// assert.equal(params.capabilities.textDocument.implementation.linkSupport, true)
|
||||
// assert.equal(params.capabilities.textDocument.typeDefinition.linkSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.rename.prepareSupport, true)
|
||||
assert.equal(params.capabilities.textDocument.publishDiagnostics.relatedInformation, true)
|
||||
assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet.length, 2)
|
||||
assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet[0], DiagnosticTag.Unnecessary)
|
||||
assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet[1], DiagnosticTag.Deprecated)
|
||||
assert.equal(params.capabilities.textDocument.documentLink.tooltipSupport, true)
|
||||
let valueSet = params.capabilities.textDocument.completion.completionItemKind.valueSet
|
||||
assert.equal(valueSet[0], 1)
|
||||
assert.equal(valueSet[valueSet.length - 1], CompletionItemKind.TypeParameter)
|
||||
assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, [ResourceOperationKind.Create, ResourceOperationKind.Rename, ResourceOperationKind.Delete])
|
||||
assert.equal(params.capabilities.workspace.fileOperations.willCreate, true)
|
||||
|
||||
let capabilities = {
|
||||
textDocumentSync: TextDocumentSyncKind.Full,
|
||||
definitionProvider: true,
|
||||
hoverProvider: true,
|
||||
completionProvider: {resolveProvider: true, triggerCharacters: ['"', ':']},
|
||||
signatureHelpProvider: {
|
||||
triggerCharacters: [':'],
|
||||
retriggerCharacters: [':']
|
||||
},
|
||||
referencesProvider: true,
|
||||
documentHighlightProvider: true,
|
||||
codeActionProvider: {
|
||||
resolveProvider: true
|
||||
},
|
||||
documentFormattingProvider: true,
|
||||
documentRangeFormattingProvider: true,
|
||||
documentOnTypeFormattingProvider: {
|
||||
firstTriggerCharacter: ':'
|
||||
},
|
||||
renameProvider: {
|
||||
prepareProvider: true
|
||||
},
|
||||
documentLinkProvider: {
|
||||
resolveProvider: true
|
||||
},
|
||||
colorProvider: true,
|
||||
declarationProvider: true,
|
||||
foldingRangeProvider: true,
|
||||
implementationProvider: true,
|
||||
selectionRangeProvider: true,
|
||||
typeDefinitionProvider: true,
|
||||
callHierarchyProvider: true,
|
||||
semanticTokensProvider: {
|
||||
legend: {
|
||||
tokenTypes: [],
|
||||
tokenModifiers: []
|
||||
},
|
||||
range: true,
|
||||
full: {
|
||||
delta: true
|
||||
}
|
||||
},
|
||||
workspace: {
|
||||
fileOperations: {
|
||||
// Static reg is folders + .txt files with operation kind in the path
|
||||
didCreate: {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}]
|
||||
},
|
||||
didRename: {
|
||||
filters: [
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}},
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}}
|
||||
]
|
||||
},
|
||||
didDelete: {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}]
|
||||
},
|
||||
willCreate: {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}]
|
||||
},
|
||||
willRename: {
|
||||
filters: [
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}},
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}}
|
||||
]
|
||||
},
|
||||
willDelete: {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}]
|
||||
},
|
||||
},
|
||||
},
|
||||
linkedEditingRangeProvider: true
|
||||
}
|
||||
return {capabilities, customResults: {hello: 'world'}}
|
||||
})
|
||||
|
||||
connection.onInitialized(() => {
|
||||
// Dynamic reg is folders + .js files with operation kind in the path
|
||||
connection.client.register(DidCreateFilesNotification.type, {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}]
|
||||
})
|
||||
connection.client.register(DidRenameFilesNotification.type, {
|
||||
filters: [
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}},
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}}
|
||||
]
|
||||
})
|
||||
connection.client.register(DidDeleteFilesNotification.type, {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}]
|
||||
})
|
||||
connection.client.register(WillCreateFilesRequest.type, {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}]
|
||||
})
|
||||
connection.client.register(WillRenameFilesRequest.type, {
|
||||
filters: [
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}},
|
||||
{scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}}
|
||||
]
|
||||
})
|
||||
connection.client.register(WillDeleteFilesRequest.type, {
|
||||
filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}]
|
||||
})
|
||||
})
|
||||
|
||||
connection.onDeclaration((params) => {
|
||||
assert.equal(params.position.line, 1)
|
||||
assert.equal(params.position.character, 1)
|
||||
return {uri: params.textDocument.uri, range: {start: {line: 1, character: 1}, end: {line: 1, character: 2}}}
|
||||
})
|
||||
|
||||
connection.onDefinition((params) => {
|
||||
assert.equal(params.position.line, 1)
|
||||
assert.equal(params.position.character, 1)
|
||||
return {uri: params.textDocument.uri, range: {start: {line: 0, character: 0}, end: {line: 0, character: 1}}}
|
||||
})
|
||||
|
||||
connection.onHover((_params) => {
|
||||
return {
|
||||
contents: {
|
||||
kind: MarkupKind.PlainText,
|
||||
value: 'foo'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
connection.onCompletion((_params) => {
|
||||
return [
|
||||
{label: 'item', insertText: 'text'}
|
||||
]
|
||||
})
|
||||
|
||||
connection.onCompletionResolve((item) => {
|
||||
item.detail = 'detail'
|
||||
return item
|
||||
})
|
||||
|
||||
connection.onSignatureHelp((_params) => {
|
||||
const result = {
|
||||
signatures: [
|
||||
SignatureInformation.create('label', 'doc', ParameterInformation.create('label', 'doc'))
|
||||
],
|
||||
activeSignature: 1,
|
||||
activeParameter: 1
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
connection.onReferences((params) => {
|
||||
return [
|
||||
Location.create(params.textDocument.uri, Range.create(0, 0, 0, 0)),
|
||||
Location.create(params.textDocument.uri, Range.create(1, 1, 1, 1))
|
||||
]
|
||||
})
|
||||
|
||||
connection.onDocumentHighlight((_params) => {
|
||||
return [
|
||||
DocumentHighlight.create(Range.create(2, 2, 2, 2), DocumentHighlightKind.Read)
|
||||
]
|
||||
})
|
||||
|
||||
connection.onCodeAction((_params) => {
|
||||
return [
|
||||
CodeAction.create('title', Command.create('title', 'id'))
|
||||
]
|
||||
})
|
||||
|
||||
connection.onCodeActionResolve((codeAction) => {
|
||||
codeAction.title = 'resolved'
|
||||
return codeAction
|
||||
})
|
||||
|
||||
connection.onDocumentFormatting((_params) => {
|
||||
return [
|
||||
TextEdit.insert(Position.create(0, 0), 'insert')
|
||||
]
|
||||
})
|
||||
|
||||
connection.onDocumentRangeFormatting((_params) => {
|
||||
return [
|
||||
TextEdit.del(Range.create(1, 1, 1, 2))
|
||||
]
|
||||
})
|
||||
|
||||
connection.onDocumentOnTypeFormatting((_params) => {
|
||||
return [
|
||||
TextEdit.replace(Range.create(2, 2, 2, 3), 'replace')
|
||||
]
|
||||
})
|
||||
|
||||
connection.onPrepareRename((_params) => {
|
||||
return Range.create(1, 1, 1, 2)
|
||||
})
|
||||
|
||||
connection.onRenameRequest((_params) => {
|
||||
return {documentChanges: []}
|
||||
})
|
||||
|
||||
connection.onDocumentLinks((_params) => {
|
||||
return [
|
||||
DocumentLink.create(Range.create(1, 1, 1, 2))
|
||||
]
|
||||
})
|
||||
|
||||
connection.onDocumentLinkResolve((link) => {
|
||||
link.target = URI.file('/target.txt').toString()
|
||||
return link
|
||||
})
|
||||
|
||||
connection.onDocumentColor((_params) => {
|
||||
return [
|
||||
ColorInformation.create(Range.create(1, 1, 1, 2), Color.create(1, 1, 1, 1))
|
||||
]
|
||||
})
|
||||
|
||||
connection.onColorPresentation((_params) => {
|
||||
return [
|
||||
ColorPresentation.create('label')
|
||||
]
|
||||
})
|
||||
|
||||
connection.onFoldingRanges((_params) => {
|
||||
return [
|
||||
FoldingRange.create(1, 2)
|
||||
]
|
||||
})
|
||||
|
||||
connection.onImplementation((params) => {
|
||||
assert.equal(params.position.line, 1)
|
||||
assert.equal(params.position.character, 1)
|
||||
return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}}
|
||||
})
|
||||
|
||||
connection.onSelectionRanges((_params) => {
|
||||
return [
|
||||
SelectionRange.create(Range.create(1, 2, 3, 4))
|
||||
]
|
||||
})
|
||||
|
||||
let lastFileOperationRequest
|
||||
connection.workspace.onDidCreateFiles((params) => {lastFileOperationRequest = {type: 'create', params}})
|
||||
connection.workspace.onDidRenameFiles((params) => {lastFileOperationRequest = {type: 'rename', params}})
|
||||
connection.workspace.onDidDeleteFiles((params) => {lastFileOperationRequest = {type: 'delete', params}})
|
||||
|
||||
connection.onRequest(
|
||||
new ProtocolRequestType('testing/lastFileOperationRequest'),
|
||||
() => {
|
||||
return lastFileOperationRequest
|
||||
},
|
||||
)
|
||||
|
||||
connection.workspace.onWillCreateFiles((params) => {
|
||||
const createdFilenames = params.files.map((f) => `${f.uri}`).join('\n')
|
||||
return {
|
||||
documentChanges: [{
|
||||
textDocument: {uri: '/dummy-edit', version: null},
|
||||
edits: [
|
||||
TextEdit.insert(Position.create(0, 0), `WILL CREATE:\n${createdFilenames}`),
|
||||
]
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
connection.workspace.onWillRenameFiles((params) => {
|
||||
const renamedFilenames = params.files.map((f) => `${f.oldUri} -> ${f.newUri}`).join('\n')
|
||||
return {
|
||||
documentChanges: [{
|
||||
textDocument: {uri: '/dummy-edit', version: null},
|
||||
edits: [
|
||||
TextEdit.insert(Position.create(0, 0), `WILL RENAME:\n${renamedFilenames}`),
|
||||
]
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
connection.workspace.onWillDeleteFiles((params) => {
|
||||
const deletedFilenames = params.files.map((f) => `${f.uri}`).join('\n')
|
||||
return {
|
||||
documentChanges: [{
|
||||
textDocument: {uri: '/dummy-edit', version: null},
|
||||
edits: [
|
||||
TextEdit.insert(Position.create(0, 0), `WILL DELETE:\n${deletedFilenames}`),
|
||||
]
|
||||
}],
|
||||
}
|
||||
})
|
||||
|
||||
connection.onTypeDefinition((params) => {
|
||||
assert.equal(params.position.line, 1)
|
||||
assert.equal(params.position.character, 1)
|
||||
return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}}
|
||||
})
|
||||
|
||||
connection.languages.callHierarchy.onPrepare((params) => {
|
||||
return [
|
||||
{
|
||||
kind: SymbolKind.Function,
|
||||
name: 'name',
|
||||
range: Range.create(1, 1, 1, 1),
|
||||
selectionRange: Range.create(2, 2, 2, 2),
|
||||
uri: params.textDocument.uri
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
connection.languages.callHierarchy.onIncomingCalls((params) => {
|
||||
return [
|
||||
{
|
||||
from: params.item,
|
||||
fromRanges: [Range.create(1, 1, 1, 1)]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
connection.languages.callHierarchy.onOutgoingCalls((params) => {
|
||||
return [
|
||||
{
|
||||
to: params.item,
|
||||
fromRanges: [Range.create(1, 1, 1, 1)]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
connection.languages.semanticTokens.onRange(() => {
|
||||
return {
|
||||
resultId: '1',
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
connection.languages.semanticTokens.on(() => {
|
||||
return {
|
||||
resultId: '2',
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
connection.languages.semanticTokens.onDelta(() => {
|
||||
return {
|
||||
resultId: '3',
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
connection.languages.onLinkedEditingRange(() => {
|
||||
return {
|
||||
ranges: [Range.create(1, 1, 1, 1)],
|
||||
wordPattern: '\\w'
|
||||
}
|
||||
})
|
||||
|
||||
connection.onRequest(
|
||||
new ProtocolRequestType('testing/sendSampleProgress'),
|
||||
async (_, __) => {
|
||||
const progressToken = 'TEST-PROGRESS-TOKEN'
|
||||
await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken})
|
||||
connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'begin', title: 'Test Progress'})
|
||||
connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'report', percentage: 50, message: 'Halfway!'})
|
||||
connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'end', message: 'Completed!'})
|
||||
},
|
||||
)
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen()
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"suggest.timeout": 5000,
|
||||
"tslint.enable": false
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,198 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import Floating from '../../completion/floating'
|
||||
import sources from '../../sources'
|
||||
import { CompleteResult, FloatConfig, ISource, SourceType } from '../../types'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let source: ISource
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
source = {
|
||||
name: 'float',
|
||||
priority: 10,
|
||||
enable: true,
|
||||
sourceType: SourceType.Native,
|
||||
doComplete: (): Promise<CompleteResult> => Promise.resolve({
|
||||
items: [{
|
||||
word: 'foo',
|
||||
info: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||
}, {
|
||||
word: 'foot',
|
||||
info: 'foot'
|
||||
}, {
|
||||
word: 'football',
|
||||
}]
|
||||
})
|
||||
}
|
||||
sources.addSource(source)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
sources.removeSource(source)
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('completion float', () => {
|
||||
it('should not show float window when disabled', async () => {
|
||||
helper.updateConfiguration('suggest.floatEnable', false)
|
||||
await helper.edit()
|
||||
await nvim.input('if')
|
||||
await helper.visible('foo', 'float')
|
||||
let hasFloat = await nvim.call('coc#float#has_float')
|
||||
expect(hasFloat).toBe(0)
|
||||
})
|
||||
|
||||
it('should cancel float window', async () => {
|
||||
await helper.edit()
|
||||
await nvim.input('if')
|
||||
await helper.visible('foo', 'float')
|
||||
let items = await helper.getItems()
|
||||
expect(items[0].word).toBe('foo')
|
||||
expect(items[0].info.length > 0).toBeTruthy()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.wait(30)
|
||||
let hasFloat = await nvim.call('coc#float#has_float')
|
||||
expect(hasFloat).toBe(0)
|
||||
})
|
||||
|
||||
it('should adjust float window position', async () => {
|
||||
await helper.edit()
|
||||
await nvim.setLine(' '.repeat(70))
|
||||
await nvim.input('Af')
|
||||
await helper.visible('foo', 'float')
|
||||
await nvim.input('<C-n>')
|
||||
await helper.wait(100)
|
||||
let floatWin = await helper.getFloat()
|
||||
let config = await floatWin.getConfig()
|
||||
expect(config.col + config.width).toBeLessThan(180)
|
||||
})
|
||||
|
||||
it('should redraw float window on item change', async () => {
|
||||
await helper.edit()
|
||||
await nvim.setLine(' '.repeat(70))
|
||||
await nvim.input('Af')
|
||||
await helper.visible('foo', 'float')
|
||||
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-n>')
|
||||
await helper.wait(100)
|
||||
let floatWin = await helper.getFloat()
|
||||
let buf = await floatWin.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines.length).toBeGreaterThan(0)
|
||||
expect(lines[0]).toMatch('foot')
|
||||
})
|
||||
|
||||
it('should hide float window when item info is empty', async () => {
|
||||
await helper.edit()
|
||||
await nvim.setLine(' '.repeat(70))
|
||||
await nvim.input('Af')
|
||||
await helper.visible('foo', 'float')
|
||||
await nvim.call('nvim_select_popupmenu_item', [0, false, false, {}])
|
||||
await helper.wait(10)
|
||||
await nvim.input('<C-n>')
|
||||
await helper.wait(10)
|
||||
await nvim.input('<C-n>')
|
||||
await helper.wait(100)
|
||||
let hasFloat = await nvim.call('coc#float#has_float')
|
||||
expect(hasFloat).toBe(0)
|
||||
})
|
||||
|
||||
it('should hide float window after completion', async () => {
|
||||
await helper.edit()
|
||||
await nvim.setLine(' '.repeat(70))
|
||||
await nvim.input('Af')
|
||||
await helper.visible('foo', 'float')
|
||||
await nvim.input('<C-n>')
|
||||
await helper.wait(100)
|
||||
await nvim.input('<C-y>')
|
||||
await helper.wait(30)
|
||||
let hasFloat = await nvim.call('coc#float#has_float')
|
||||
expect(hasFloat).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('float config', () => {
|
||||
beforeEach(async () => {
|
||||
await nvim.setLine('foob foot')
|
||||
await nvim.input('of')
|
||||
await nvim.input('<C-n>')
|
||||
})
|
||||
|
||||
async function createFloat(config: Partial<FloatConfig>, docs = [{ filetype: 'txt', content: 'doc' }], isVim = false): Promise<Floating> {
|
||||
let floating = new Floating(nvim, isVim)
|
||||
let bounding = { col: 6, row: 2, height: 3, width: 16, scrollbar: false }
|
||||
await floating.show(docs, bounding, Object.assign({
|
||||
excludeImages: true,
|
||||
border: false,
|
||||
}, config))
|
||||
return floating
|
||||
}
|
||||
|
||||
async function getFloat(): Promise<number> {
|
||||
let ids = await nvim.call('coc#float#get_float_win_list')
|
||||
return Array.isArray(ids) ? ids[0] || -1 : -1
|
||||
}
|
||||
|
||||
async function getRelated(winid: number, kind: string): Promise<number> {
|
||||
if (!winid || winid == -1) return -1
|
||||
let win = nvim.createWindow(winid)
|
||||
let related = await win.getVar('related') as number[]
|
||||
if (!related || !related.length) return -1
|
||||
for (let id of related) {
|
||||
let w = nvim.createWindow(id)
|
||||
let v = await w.getVar('kind')
|
||||
if (v == kind) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
it('should not shown with empty lines', async () => {
|
||||
await createFloat({}, [{ filetype: 'txt', content: '' }])
|
||||
let winid = await nvim.call('GetFloatWin')
|
||||
expect(winid).toBe(0)
|
||||
})
|
||||
|
||||
it('should shown on vim', async () => {
|
||||
let float = await createFloat({}, [{ filetype: 'txt', content: 'ff' }], true)
|
||||
let winid = await nvim.call('GetFloatWin')
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
float.close()
|
||||
})
|
||||
|
||||
it('should show window with border', async () => {
|
||||
await createFloat({ border: true })
|
||||
let winid = await getFloat()
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
let id = await getRelated(winid, 'border')
|
||||
expect(id).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should change window highlights', async () => {
|
||||
await createFloat({ border: true, highlight: 'WarningMsg', borderhighlight: 'MoreMsg' })
|
||||
let winid = await getFloat()
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
let win = nvim.createWindow(winid)
|
||||
let res = await win.getOption('winhl') as string
|
||||
expect(res).toMatch('WarningMsg')
|
||||
let id = await getRelated(winid, 'border')
|
||||
expect(id).toBeGreaterThan(0)
|
||||
win = nvim.createWindow(id)
|
||||
res = await win.getOption('winhl') as string
|
||||
expect(res).toMatch('MoreMsg')
|
||||
})
|
||||
|
||||
it('should add shadow and winblend', async () => {
|
||||
await createFloat({ shadow: true, winblend: 30 })
|
||||
let winid = await getFloat()
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
|
@ -1,317 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import { CompletionItem, CompletionList, InsertTextFormat, Position, Range, TextEdit } from 'vscode-languageserver-types'
|
||||
import languages from '../../languages'
|
||||
import { CompletionItemProvider } from '../../provider'
|
||||
import snippetManager from '../../snippets/manager'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('language source', () => {
|
||||
describe('additionalTextEdits', () => {
|
||||
it('should fix cursor position with plain text on additionalTextEdits', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'foo',
|
||||
filterText: 'foo',
|
||||
additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'a\nbar')]
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('if')
|
||||
await helper.waitPopup()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.waitFor('getline', ['.'], 'barfoo')
|
||||
})
|
||||
|
||||
it('should fix cursor position with snippet on additionalTextEdits', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'if',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
textEdit: { range: Range.create(0, 0, 0, 1), newText: 'if($1)' },
|
||||
additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],
|
||||
preselect: true
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('ii')
|
||||
await helper.waitPopup()
|
||||
let res = await helper.getItems()
|
||||
let idx = res.findIndex(o => o.menu == '[edit]')
|
||||
await helper.selectCompleteItem(idx)
|
||||
await helper.waitFor('col', ['.'], 8)
|
||||
})
|
||||
|
||||
it('should fix cursor position with plain text snippet on additionalTextEdits', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'if',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
textEdit: { range: Range.create(0, 0, 0, 2), newText: 'do$0' },
|
||||
additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],
|
||||
preselect: true
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('iif')
|
||||
await helper.waitPopup()
|
||||
let items = await helper.getItems()
|
||||
let idx = items.findIndex(o => o.word == 'do' && o.menu == '[edit]')
|
||||
await helper.selectCompleteItem(idx)
|
||||
await helper.waitFor('getline', ['.'], 'bar do')
|
||||
await helper.waitFor('col', ['.'], 7)
|
||||
})
|
||||
|
||||
it('should fix cursor position with nested snippet on additionalTextEdits', async () => {
|
||||
let res = await snippetManager.insertSnippet('func($1)$0')
|
||||
expect(res).toBe(true)
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'if',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
insertText: 'do$0',
|
||||
additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],
|
||||
preselect: true
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('if')
|
||||
await helper.waitPopup()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.waitFor('getline', ['.'], 'bar func(do)')
|
||||
let [, lnum, col] = await nvim.call('getcurpos')
|
||||
expect(lnum).toBe(1)
|
||||
expect(col).toBe(12)
|
||||
})
|
||||
|
||||
it('should fix cursor position and keep placeholder with snippet on additionalTextEdits', async () => {
|
||||
let text = 'foo0bar1'
|
||||
await nvim.setLine(text)
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'var',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
textEdit: { range: Range.create(0, text.length + 1, 0, text.length + 1), newText: '${1:foo} = foo0bar1' },
|
||||
additionalTextEdits: [TextEdit.del(Range.create(0, 0, 0, text.length + 1))],
|
||||
preselect: true
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))
|
||||
await nvim.input('A.')
|
||||
await helper.waitPopup()
|
||||
let res = await helper.getItems()
|
||||
let idx = res.findIndex(o => o.menu == '[edit]')
|
||||
await helper.selectCompleteItem(idx)
|
||||
await helper.waitFor('getline', ['.'], 'foo = foo0bar1')
|
||||
await helper.wait(50)
|
||||
expect(snippetManager.session).toBeDefined()
|
||||
let [, lnum, col] = await nvim.call('getcurpos')
|
||||
expect(lnum).toBe(1)
|
||||
expect(col).toBe(3)
|
||||
})
|
||||
|
||||
it('should cancel current snippet session when additionalTextEdits inside snippet', async () => {
|
||||
await nvim.input('i')
|
||||
await snippetManager.insertSnippet('foo($1, $2)$0', true)
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'bar',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
textEdit: { range: Range.create(0, 4, 0, 5), newText: 'bar($1)' },
|
||||
additionalTextEdits: [TextEdit.del(Range.create(0, 0, 0, 3))]
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))
|
||||
await nvim.input('b')
|
||||
await helper.waitPopup()
|
||||
let res = await helper.getItems()
|
||||
let idx = res.findIndex(o => o.menu == '[edit]')
|
||||
await helper.selectCompleteItem(idx)
|
||||
await helper.waitFor('getline', ['.'], '(bar(), )')
|
||||
let col = await nvim.call('col', ['.'])
|
||||
expect(col).toBe(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('filterText', () => {
|
||||
it('should fix input for snippet item', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'foo',
|
||||
filterText: 'foo',
|
||||
insertText: '${1:foo}($2)',
|
||||
insertTextFormat: InsertTextFormat.Snippet,
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('snippets-test', 'st', null, provider))
|
||||
await nvim.input('if')
|
||||
await helper.waitPopup()
|
||||
await nvim.input('<C-n>')
|
||||
await helper.waitFor('getline', ['.'], 'foo')
|
||||
})
|
||||
|
||||
it('should fix filterText of complete item', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'name',
|
||||
sortText: '11',
|
||||
textEdit: {
|
||||
range: Range.create(0, 1, 0, 2),
|
||||
newText: '?.name'
|
||||
}
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('name', 'N', null, provider, ['.']))
|
||||
await nvim.setLine('t')
|
||||
await nvim.input('A.')
|
||||
await helper.waitPopup()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.waitFor('getline', ['.'], 't?.name')
|
||||
})
|
||||
})
|
||||
|
||||
describe('inComplete result', () => {
|
||||
it('should filter in complete request', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (doc, pos, token, context): Promise<CompletionList> => {
|
||||
let option = (context as any).option
|
||||
if (context.triggerCharacter == '.') {
|
||||
return {
|
||||
isIncomplete: true,
|
||||
items: [
|
||||
{
|
||||
label: 'foo'
|
||||
}, {
|
||||
label: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
if (option.input == 'f') {
|
||||
if (token.isCancellationRequested) return
|
||||
return {
|
||||
isIncomplete: true,
|
||||
items: [
|
||||
{
|
||||
label: 'foo'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
if (option.input == 'fo') {
|
||||
if (token.isCancellationRequested) return
|
||||
return {
|
||||
isIncomplete: false,
|
||||
items: [
|
||||
{
|
||||
label: 'foo'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))
|
||||
await nvim.input('i.')
|
||||
await helper.waitPopup()
|
||||
await nvim.input('fo')
|
||||
await helper.wait(50)
|
||||
let res = await helper.getItems()
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('textEdit', () => {
|
||||
it('should fix bad range', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: 'foo',
|
||||
filterText: 'foo',
|
||||
textEdit: { range: Range.create(0, 0, 0, 0), newText: 'foo' },
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('if')
|
||||
await helper.waitPopup()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.waitFor('getline', ['.'], 'foo')
|
||||
})
|
||||
|
||||
it('should applyEdits for empty word', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (): Promise<CompletionItem[]> => [{
|
||||
label: '',
|
||||
filterText: '!',
|
||||
textEdit: { range: Range.create(0, 0, 0, 1), newText: 'foo' },
|
||||
data: { word: '' }
|
||||
}]
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['!']))
|
||||
await nvim.input('i!')
|
||||
await helper.waitPopup()
|
||||
await helper.selectCompleteItem(0)
|
||||
await helper.waitFor('getline', ['.'], 'foo')
|
||||
})
|
||||
|
||||
it('should provide word when textEdit after startcol', async () => {
|
||||
// some LS would send textEdit after first character,
|
||||
// need fix the word from newText
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (_, position): Promise<CompletionItem[]> => {
|
||||
if (position.line != 0) return null
|
||||
return [{
|
||||
label: 'bar',
|
||||
filterText: 'ar',
|
||||
textEdit: {
|
||||
range: Range.create(0, 1, 0, 1),
|
||||
newText: 'ar'
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))
|
||||
await nvim.input('ib')
|
||||
await helper.waitPopup()
|
||||
let context = await nvim.getVar('coc#_context') as any
|
||||
expect(context.start).toBe(1)
|
||||
expect(context.candidates[0].word).toBe('ar')
|
||||
})
|
||||
|
||||
it('should adjust completion position by textEdit start position', async () => {
|
||||
let provider: CompletionItemProvider = {
|
||||
provideCompletionItems: async (_document, _position, _token, context): Promise<CompletionItem[]> => {
|
||||
if (!context.triggerCharacter) return
|
||||
return [{
|
||||
label: 'foo',
|
||||
textEdit: {
|
||||
range: Range.create(0, 0, 0, 1),
|
||||
newText: '?foo'
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
disposables.push(languages.registerCompletionItemProvider('fix', 'f', null, provider, ['?']))
|
||||
await nvim.input('i?')
|
||||
await helper.waitPopup()
|
||||
await nvim.eval('feedkeys("\\<C-n>", "in")')
|
||||
await helper.waitFor('getline', ['.'], '?foo')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,65 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import helper from '../helper'
|
||||
import { ISource, SourceType, CompleteResult } from '../../types'
|
||||
import sources from '../../sources'
|
||||
import workspace from '../../workspace'
|
||||
|
||||
let nvim: Neovim
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('native sources', () => {
|
||||
|
||||
it('should works for around source', async () => {
|
||||
let doc = await workspace.document
|
||||
await nvim.setLine('foo ')
|
||||
await doc.synchronize()
|
||||
let { mode } = await nvim.mode
|
||||
expect(mode).toBe('n')
|
||||
await nvim.input('Af')
|
||||
await helper.waitPopup()
|
||||
let res = await helper.visible('foo', 'around')
|
||||
expect(res).toBe(true)
|
||||
await nvim.input('<esc>')
|
||||
})
|
||||
|
||||
it('should works for buffer source', async () => {
|
||||
await helper.createDocument()
|
||||
await nvim.command('set hidden')
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('other')
|
||||
await nvim.command('bp')
|
||||
await doc.synchronize()
|
||||
let { mode } = await nvim.mode
|
||||
expect(mode).toBe('n')
|
||||
await nvim.input('io')
|
||||
let res = await helper.visible('other', 'buffer')
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
|
||||
it('should works with file source', async () => {
|
||||
await helper.edit()
|
||||
await nvim.input('i/')
|
||||
await helper.waitPopup()
|
||||
let items = await helper.getItems()
|
||||
expect(items.length).toBeGreaterThan(0)
|
||||
let res = await helper.visible(items[0].word, 'file')
|
||||
expect(res).toBe(true)
|
||||
await nvim.input('<esc>')
|
||||
await nvim.input('o./')
|
||||
await helper.waitPopup()
|
||||
items = await helper.getItems()
|
||||
let item = items.find(o => o.word == 'vimrc')
|
||||
expect(item).toBeTruthy()
|
||||
})
|
||||
})
|
|
@ -1,144 +0,0 @@
|
|||
import { CompletionItemKind, TextEdit, Position } from 'vscode-languageserver-types'
|
||||
import { matchScore, matchScoreWithPositions } from '../../completion/match'
|
||||
import { shouldStop } from '../../completion/util'
|
||||
import { getCharCodes } from '../../util/fuzzy'
|
||||
import { getStartColumn, getKindString } from '../../sources/source-language'
|
||||
import { CompleteOption } from '../../types'
|
||||
|
||||
describe('getKindString()', () => {
|
||||
it('should get kind text', async () => {
|
||||
let map = new Map()
|
||||
map.set(CompletionItemKind.Enum, 'E')
|
||||
let res = getKindString(CompletionItemKind.Enum, map, '')
|
||||
expect(res).toBe('E')
|
||||
})
|
||||
|
||||
it('should get default value', async () => {
|
||||
let map = new Map()
|
||||
let res = getKindString(CompletionItemKind.Enum, map, 'D')
|
||||
expect(res).toBe('D')
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldStop', () => {
|
||||
function createOption(bufnr: number, linenr: number, line: string, colnr: number): Pick<CompleteOption, 'bufnr' | 'linenr' | 'line' | 'colnr'> {
|
||||
return { bufnr, linenr, line, colnr }
|
||||
}
|
||||
|
||||
it('should check stop', async () => {
|
||||
let opt = createOption(1, 1, 'a', 2)
|
||||
expect(shouldStop(1, 'foo', { line: '', col: 2, lnum: 1, changedtick: 1, pre: '' }, opt)).toBe(true)
|
||||
expect(shouldStop(1, 'foo', { line: '', col: 2, lnum: 1, changedtick: 1, pre: ' ' }, opt)).toBe(true)
|
||||
expect(shouldStop(1, 'foo', { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'fo' }, opt)).toBe(true)
|
||||
expect(shouldStop(2, 'foo', { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'foob' }, opt)).toBe(true)
|
||||
expect(shouldStop(1, 'foo', { line: '', col: 2, lnum: 2, changedtick: 1, pre: 'foob' }, opt)).toBe(true)
|
||||
expect(shouldStop(1, 'foo', { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'barb' }, opt)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getStartColumn()', () => {
|
||||
it('should get start col', async () => {
|
||||
expect(getStartColumn('', [{ label: 'foo' }])).toBe(undefined)
|
||||
expect(getStartColumn('', [
|
||||
{ label: 'foo', textEdit: TextEdit.insert(Position.create(0, 0), 'a') },
|
||||
{ label: 'bar' }])).toBe(undefined)
|
||||
expect(getStartColumn('foo', [
|
||||
{ label: 'foo', textEdit: TextEdit.insert(Position.create(0, 0), 'a') },
|
||||
{ label: 'bar', textEdit: TextEdit.insert(Position.create(0, 1), 'b') }])).toBe(undefined)
|
||||
expect(getStartColumn('foo', [
|
||||
{ label: 'foo', textEdit: TextEdit.insert(Position.create(0, 2), 'a') },
|
||||
{ label: 'bar', textEdit: TextEdit.insert(Position.create(0, 2), 'b') }])).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchScore', () => {
|
||||
function score(word: string, input: string): number {
|
||||
return matchScore(word, getCharCodes(input))
|
||||
}
|
||||
|
||||
it('should match score for last letter', () => {
|
||||
expect(score('#!3', '3')).toBe(1)
|
||||
expect(score('bar', 'f')).toBe(0)
|
||||
})
|
||||
|
||||
it('should match first letter', () => {
|
||||
expect(score('abc', 'a')).toBe(5)
|
||||
expect(score('Abc', 'a')).toBe(2.5)
|
||||
expect(score('__abc', 'a')).toBe(2)
|
||||
expect(score('$Abc', 'a')).toBe(1)
|
||||
expect(score('$Abc', 'A')).toBe(2)
|
||||
expect(score('$Abc', '$A')).toBe(6)
|
||||
expect(score('$Abc', '$a')).toBe(5.5)
|
||||
expect(score('foo_bar', 'b')).toBe(2)
|
||||
expect(score('foo_Bar', 'b')).toBe(1)
|
||||
expect(score('_foo_Bar', 'b')).toBe(0.5)
|
||||
expect(score('_foo_Bar', 'f')).toBe(2)
|
||||
expect(score('bar', 'a')).toBe(1)
|
||||
expect(score('fooBar', 'B')).toBe(2)
|
||||
expect(score('fooBar', 'b')).toBe(1)
|
||||
})
|
||||
|
||||
it('should match follow letters', () => {
|
||||
expect(score('abc', 'ab')).toBe(6)
|
||||
expect(score('adB', 'ab')).toBe(5.75)
|
||||
expect(score('adb', 'ab')).toBe(5.1)
|
||||
expect(score('adCB', 'ab')).toBe(5.05)
|
||||
expect(score('a_b_c', 'ab')).toBe(6)
|
||||
expect(score('FooBar', 'fb')).toBe(3.25)
|
||||
expect(score('FBar', 'fb')).toBe(3)
|
||||
expect(score('FooBar', 'FB')).toBe(6)
|
||||
expect(score('FBar', 'FB')).toBe(6)
|
||||
expect(score('a__b', 'a__b')).toBe(8)
|
||||
expect(score('aBc', 'ab')).toBe(5.5)
|
||||
expect(score('a_B_c', 'ab')).toBe(5.75)
|
||||
expect(score('abc', 'abc')).toBe(7)
|
||||
expect(score('abc', 'aC')).toBe(0)
|
||||
expect(score('abc', 'ac')).toBe(5.1)
|
||||
expect(score('abC', 'ac')).toBe(5.75)
|
||||
expect(score('abC', 'aC')).toBe(6)
|
||||
})
|
||||
|
||||
it('should only allow search once', () => {
|
||||
expect(score('foobar', 'fbr')).toBe(5.2)
|
||||
expect(score('foobaRow', 'fbr')).toBe(5.85)
|
||||
expect(score('foobaRow', 'fbR')).toBe(6.1)
|
||||
expect(score('foobar', 'fa')).toBe(5.1)
|
||||
})
|
||||
|
||||
it('should have higher score for strict match', () => {
|
||||
expect(score('language-client-protocol', 'lct')).toBe(6.1)
|
||||
expect(score('language-client-types', 'lct')).toBe(7)
|
||||
})
|
||||
|
||||
it('should find highest score', () => {
|
||||
expect(score('ArrayRotateTail', 'art')).toBe(3.6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('matchScoreWithPositions', () => {
|
||||
function assertMatch(word: string, input: string, res: [number, ReadonlyArray<number>] | undefined): void {
|
||||
let result = matchScoreWithPositions(word, getCharCodes(input))
|
||||
if (!res) {
|
||||
expect(result).toBeUndefined()
|
||||
} else {
|
||||
expect(result).toEqual(res)
|
||||
}
|
||||
}
|
||||
|
||||
it('should return undefined when not match found', async () => {
|
||||
assertMatch('a', 'abc', undefined)
|
||||
assertMatch('a', '', undefined)
|
||||
assertMatch('ab', 'ac', undefined)
|
||||
})
|
||||
|
||||
it('should find matches by position fix', async () => {
|
||||
assertMatch('this', 'tih', [5.6, [0, 1, 2]])
|
||||
assertMatch('globalThis', 'tihs', [2.6, [6, 7, 8, 9]])
|
||||
})
|
||||
|
||||
it('should find matched positions', async () => {
|
||||
assertMatch('this', 'th', [6, [0, 1]])
|
||||
assertMatch('foo_bar', 'fb', [6, [0, 4]])
|
||||
assertMatch('assertMatch', 'am', [5.75, [0, 6]])
|
||||
})
|
||||
})
|
|
@ -1,68 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('setupDynamicAutocmd()', () => {
|
||||
it('should setup autocmd on vim', async () => {
|
||||
await nvim.setLine('foo')
|
||||
let fn = nvim.hasFunction
|
||||
nvim.hasFunction = () => {
|
||||
return false
|
||||
}
|
||||
let called = false
|
||||
workspace.registerAutocmd({
|
||||
event: 'CursorMoved',
|
||||
request: true,
|
||||
callback: () => {
|
||||
called = true
|
||||
}
|
||||
})
|
||||
await helper.wait(50)
|
||||
await nvim.command('normal! $')
|
||||
await helper.wait(100)
|
||||
nvim.hasFunction = fn
|
||||
expect(called).toBe(true)
|
||||
nvim.command(`augroup coc_dynamic_autocmd| autocmd!|augroup end`, true)
|
||||
})
|
||||
|
||||
it('should setup user autocmd', async () => {
|
||||
let called = false
|
||||
workspace.registerAutocmd({
|
||||
event: 'User CocJumpPlaceholder',
|
||||
request: true,
|
||||
callback: () => {
|
||||
called = true
|
||||
}
|
||||
})
|
||||
workspace.autocmds.setupDynamicAutocmd(true)
|
||||
await helper.wait(50)
|
||||
await nvim.command('doautocmd <nomodeline> User CocJumpPlaceholder')
|
||||
await helper.wait(100)
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doAutocmd()', () => {
|
||||
it('should not throw when command id does not exist', async () => {
|
||||
await workspace.autocmds.doAutocmd(999, [])
|
||||
})
|
||||
|
||||
it('should dispose', async () => {
|
||||
workspace.autocmds.dispose()
|
||||
})
|
||||
})
|
|
@ -1,85 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import Documents from '../../core/documents'
|
||||
import events from '../../events'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let documents: Documents
|
||||
let nvim: Neovim
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
documents = workspace.documentsManager
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
describe('documents', () => {
|
||||
it('should get document', async () => {
|
||||
await helper.createDocument('bar')
|
||||
let doc = await helper.createDocument('foo')
|
||||
let res = documents.getDocument(doc.uri)
|
||||
expect(res.uri).toBe(doc.uri)
|
||||
})
|
||||
|
||||
it('should create document', async () => {
|
||||
await helper.createDocument()
|
||||
let bufnrs = await nvim.call('coc#ui#open_files', [[__filename]]) as number[]
|
||||
let bufnr = bufnrs[0]
|
||||
let doc = workspace.getDocument(bufnr)
|
||||
expect(doc).toBeUndefined()
|
||||
doc = await documents.createDocument(bufnr)
|
||||
expect(doc).toBeDefined()
|
||||
})
|
||||
|
||||
it('should check buffer rename on save', async () => {
|
||||
let doc = await workspace.document
|
||||
let bufnr = doc.bufnr
|
||||
let name = `${uuid()}.vim`
|
||||
let tmpfile = path.join(os.tmpdir(), name)
|
||||
await nvim.command(`write ${tmpfile}`)
|
||||
doc = workspace.getDocument(bufnr)
|
||||
expect(doc).toBeDefined()
|
||||
expect(doc.filetype).toBe('vim')
|
||||
expect(doc.bufname).toMatch(name)
|
||||
fs.unlinkSync(tmpfile)
|
||||
})
|
||||
|
||||
it('should get current document', async () => {
|
||||
let p1 = workspace.document
|
||||
let p2 = workspace.document
|
||||
let arr = await Promise.all([p1, p2])
|
||||
expect(arr[0]).toBe(arr[1])
|
||||
})
|
||||
|
||||
it('should get bufnrs', async () => {
|
||||
await workspace.document
|
||||
let bufnrs = documents.bufnrs
|
||||
expect(bufnrs.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should get uri', async () => {
|
||||
let doc = await workspace.document
|
||||
expect(documents.uri).toBe(doc.uri)
|
||||
})
|
||||
|
||||
it('should attach events on vim', async () => {
|
||||
await documents.attach(nvim, workspace.env)
|
||||
let env = Object.assign(workspace.env, { isVim: true })
|
||||
documents.detach()
|
||||
await documents.attach(nvim, env)
|
||||
documents.detach()
|
||||
await events.fire('CursorMoved', [1, [1, 1]])
|
||||
})
|
||||
})
|
|
@ -1,169 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import Editors, { TextEditor } from '../../core/editors'
|
||||
import workspace from '../../workspace'
|
||||
import window from '../../window'
|
||||
import events from '../../events'
|
||||
import helper from '../helper'
|
||||
import { disposeAll } from '../../util'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
|
||||
let editors: Editors
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
editors = workspace.editors
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
describe('editors', () => {
|
||||
|
||||
function assertEditor(editor: TextEditor, tabpagenr: number, winid: number) {
|
||||
expect(editor).toBeDefined()
|
||||
expect(editor.tabpagenr).toBe(tabpagenr)
|
||||
expect(editor.winid).toBe(winid)
|
||||
}
|
||||
|
||||
it('should have active editor', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
let editor = window.activeTextEditor
|
||||
assertEditor(editor, 1, winid)
|
||||
let editors = window.visibleTextEditors
|
||||
expect(editors.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should change active editor on split', async () => {
|
||||
let promise = new Promise<TextEditor>(resolve => {
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
resolve(e)
|
||||
}, null, disposables)
|
||||
})
|
||||
await nvim.command('vnew')
|
||||
let editor = await promise
|
||||
let winid = await nvim.call('win_getid')
|
||||
expect(editor.winid).toBe(winid)
|
||||
})
|
||||
|
||||
it('should change active editor on tabe', async () => {
|
||||
let promise = new Promise<TextEditor>(resolve => {
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.includes('foo')) {
|
||||
resolve(e)
|
||||
}
|
||||
}, null, disposables)
|
||||
})
|
||||
await nvim.command('tabe a | tabe b | tabe foo')
|
||||
let editor = await promise
|
||||
let winid = await nvim.call('win_getid')
|
||||
expect(editor.winid).toBe(winid)
|
||||
})
|
||||
|
||||
it('should change active editor on edit', async () => {
|
||||
await nvim.call('win_getid')
|
||||
let fn = jest.fn()
|
||||
window.onDidChangeVisibleTextEditors(() => {
|
||||
fn()
|
||||
}, null, disposables)
|
||||
let promise = new Promise<TextEditor>(resolve => {
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
resolve(e)
|
||||
})
|
||||
})
|
||||
await nvim.command('edit foo')
|
||||
let editor = await promise
|
||||
expect(editor.document.uri).toMatch('foo')
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should change active editor on window switch', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
await nvim.command('vs foo')
|
||||
await nvim.command('wincmd p')
|
||||
let curr = editors.activeTextEditor
|
||||
expect(curr.winid).toBe(winid)
|
||||
expect(editors.visibleTextEditors.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should not create editor for float window', async () => {
|
||||
let fn = jest.fn()
|
||||
await nvim.call('win_getid')
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
fn()
|
||||
})
|
||||
let res = await nvim.call('coc#float#create_float_win', [0, 0, {
|
||||
relative: 'editor',
|
||||
row: 1,
|
||||
col: 1,
|
||||
width: 10,
|
||||
height: 1,
|
||||
lines: ['foo']
|
||||
}])
|
||||
await nvim.call('win_gotoid', [res[0]])
|
||||
await events.fire('CursorHold', [res[1]])
|
||||
await nvim.command('wincmd p')
|
||||
expect(fn).toBeCalledTimes(0)
|
||||
expect(editors.visibleTextEditors.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should cleanup on CursorHold', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
let promise = new Promise<TextEditor>(resolve => {
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.includes('foo')) {
|
||||
resolve(e)
|
||||
}
|
||||
}, null, disposables)
|
||||
})
|
||||
await nvim.command('tabe foo')
|
||||
await promise
|
||||
await nvim.call('win_execute', [winid, 'noa close'])
|
||||
let bufnr = await nvim.eval("bufnr('%')")
|
||||
await events.fire('CursorHold', [bufnr])
|
||||
expect(editors.visibleTextEditors.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should cleanup on create', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
let promise = new Promise<TextEditor>(resolve => {
|
||||
editors.onDidChangeActiveTextEditor(e => {
|
||||
if (e.document.uri.includes('foo')) {
|
||||
resolve(e)
|
||||
}
|
||||
}, null, disposables)
|
||||
})
|
||||
await nvim.command('tabe foo')
|
||||
await promise
|
||||
await nvim.call('win_execute', [winid, 'noa close'])
|
||||
await nvim.command('edit bar')
|
||||
expect(editors.visibleTextEditors.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should have corrent tabnr after tab changed', async () => {
|
||||
await nvim.command('tabe')
|
||||
await helper.waitValue(() => {
|
||||
return editors.visibleTextEditors.length
|
||||
}, 2)
|
||||
let editor = editors.visibleTextEditors.find(o => o.tabpagenr == 2)
|
||||
await nvim.command('normal! 1gt')
|
||||
await nvim.command('tabe')
|
||||
await helper.waitValue(() => {
|
||||
return editors.visibleTextEditors.length
|
||||
}, 3)
|
||||
expect(editor.tabpagenr).toBe(3)
|
||||
await nvim.command('tabc')
|
||||
await helper.waitValue(() => {
|
||||
return editors.visibleTextEditors.length
|
||||
}, 2)
|
||||
expect(editor.tabpagenr).toBe(2)
|
||||
})
|
||||
})
|
|
@ -1,380 +0,0 @@
|
|||
import bser from 'bser'
|
||||
import fs from 'fs'
|
||||
import net from 'net'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import Watchman, { FileChangeItem, isValidWatchRoot } from '../../core/watchman'
|
||||
import helper from '../helper'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import Configurations from '../../configuration/index'
|
||||
import WorkspaceFolderController from '../../core/workspaceFolder'
|
||||
import { FileSystemWatcherManager, FileSystemWatcher } from '../../core/fileSystemWatcher'
|
||||
import { disposeAll } from '../../util'
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
let server: net.Server
|
||||
let client: net.Socket
|
||||
const cwd = process.cwd()
|
||||
const sockPath = path.join(os.tmpdir(), `watchman-fake-${process.pid}`)
|
||||
process.env.WATCHMAN_SOCK = sockPath
|
||||
|
||||
let workspaceFolder: WorkspaceFolderController
|
||||
let watcherManager: FileSystemWatcherManager
|
||||
let configurations: Configurations
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
function wait(ms: number): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined)
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
|
||||
function createFileChange(file: string, isNew = true, exists = true): FileChangeItem {
|
||||
return {
|
||||
size: 1,
|
||||
name: file,
|
||||
exists,
|
||||
new: isNew,
|
||||
type: 'f',
|
||||
mtime_ms: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
function sendResponse(data: any): void {
|
||||
client.write(bser.dumpToBuffer(data))
|
||||
}
|
||||
|
||||
function sendSubscription(uid: string, root: string, files: FileChangeItem[]): void {
|
||||
client.write(bser.dumpToBuffer({
|
||||
subscription: uid,
|
||||
root,
|
||||
files
|
||||
}))
|
||||
}
|
||||
|
||||
let capabilities: any
|
||||
let watchResponse: any
|
||||
beforeAll(done => {
|
||||
let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')
|
||||
configurations = new Configurations(userConfigFile, {
|
||||
$removeConfigurationOption: () => {},
|
||||
$updateConfigurationOption: () => {}
|
||||
})
|
||||
workspaceFolder = new WorkspaceFolderController(configurations)
|
||||
watcherManager = new FileSystemWatcherManager(workspaceFolder, '')
|
||||
watcherManager.attach(helper.createNullChannel())
|
||||
// create a mock sever for watchman
|
||||
server = net.createServer(c => {
|
||||
client = c
|
||||
c.on('data', data => {
|
||||
let obj = bser.loadFromBuffer(data)
|
||||
if (obj[0] == 'watch-project') {
|
||||
sendResponse(watchResponse || { watch: obj[1], warning: 'warning' })
|
||||
} else if (obj[0] == 'unsubscribe') {
|
||||
sendResponse({ path: obj[1] })
|
||||
} else if (obj[0] == 'clock') {
|
||||
sendResponse({ clock: 'clock' })
|
||||
} else if (obj[0] == 'version') {
|
||||
let { optional, required } = obj[1]
|
||||
let res = {}
|
||||
for (let key of optional) {
|
||||
res[key] = true
|
||||
}
|
||||
for (let key of required) {
|
||||
res[key] = true
|
||||
}
|
||||
sendResponse({ capabilities: capabilities || res })
|
||||
} else if (obj[0] == 'subscribe') {
|
||||
sendResponse({ subscribe: obj[2] })
|
||||
} else {
|
||||
sendResponse({})
|
||||
}
|
||||
})
|
||||
})
|
||||
server.on('error', err => {
|
||||
throw err
|
||||
})
|
||||
server.listen(sockPath, () => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
capabilities = undefined
|
||||
watchResponse = undefined
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
watcherManager.dispose()
|
||||
server.removeAllListeners()
|
||||
server.close()
|
||||
if (fs.existsSync(sockPath)) {
|
||||
fs.unlinkSync(sockPath)
|
||||
}
|
||||
})
|
||||
|
||||
describe('watchman', () => {
|
||||
it('should throw error when not watching', async () => {
|
||||
let client = new Watchman(null)
|
||||
disposables.push(client)
|
||||
let fn = async () => {
|
||||
await client.subscribe('**/*', () => {})
|
||||
}
|
||||
await expect(fn()).rejects.toThrow(/not watching/)
|
||||
})
|
||||
|
||||
it('should checkCapability', async () => {
|
||||
let client = new Watchman(null)
|
||||
let res = await client.checkCapability()
|
||||
expect(res).toBe(true)
|
||||
capabilities = { relative_root: false }
|
||||
res = await client.checkCapability()
|
||||
expect(res).toBe(false)
|
||||
client.dispose()
|
||||
})
|
||||
|
||||
it('should watchProject', async () => {
|
||||
let client = new Watchman(null)
|
||||
disposables.push(client)
|
||||
let res = await client.watchProject(__dirname)
|
||||
expect(res).toBe(true)
|
||||
client.dispose()
|
||||
})
|
||||
|
||||
it('should unsubscribe', async () => {
|
||||
let client = new Watchman(null)
|
||||
disposables.push(client)
|
||||
await client.watchProject(cwd)
|
||||
let fn = jest.fn()
|
||||
let disposable = await client.subscribe(`${cwd}/*`, fn)
|
||||
disposable.dispose()
|
||||
client.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Watchman#subscribe', () => {
|
||||
|
||||
it('should subscribe file change', async () => {
|
||||
let client = new Watchman(null, helper.createNullChannel())
|
||||
disposables.push(client)
|
||||
await client.watchProject(cwd)
|
||||
let fn = jest.fn()
|
||||
let disposable = await client.subscribe(`${cwd}/*`, fn)
|
||||
let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]
|
||||
sendSubscription(disposable.subscribe, cwd, changes)
|
||||
await wait(30)
|
||||
expect(fn).toBeCalled()
|
||||
let call = fn.mock.calls[0][0]
|
||||
disposable.dispose()
|
||||
expect(call.root).toBe(cwd)
|
||||
client.dispose()
|
||||
})
|
||||
|
||||
it('should subscribe with relative_path', async () => {
|
||||
let client = new Watchman(null, helper.createNullChannel())
|
||||
watchResponse = { watch: cwd, relative_path: 'foo' }
|
||||
await client.watchProject(cwd)
|
||||
let fn = jest.fn()
|
||||
let disposable = await client.subscribe(`${cwd}/*`, fn)
|
||||
let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]
|
||||
sendSubscription(disposable.subscribe, cwd, changes)
|
||||
await wait(30)
|
||||
expect(fn).toBeCalled()
|
||||
let call = fn.mock.calls[0][0]
|
||||
disposable.dispose()
|
||||
expect(call.root).toBe(path.join(cwd, 'foo'))
|
||||
client.dispose()
|
||||
})
|
||||
|
||||
it('should not subscribe invalid response', async () => {
|
||||
let c = new Watchman(null, helper.createNullChannel())
|
||||
disposables.push(c)
|
||||
watchResponse = { watch: cwd, relative_path: 'foo' }
|
||||
await c.watchProject(cwd)
|
||||
let fn = jest.fn()
|
||||
let disposable = await c.subscribe(`${cwd}/*`, fn)
|
||||
let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]
|
||||
sendSubscription('uuid', cwd, changes)
|
||||
await wait(10)
|
||||
sendSubscription(disposable.subscribe, cwd, [])
|
||||
await wait(10)
|
||||
client.write(bser.dumpToBuffer({
|
||||
subscription: disposable.subscribe,
|
||||
root: cwd
|
||||
}))
|
||||
await wait(10)
|
||||
expect(fn).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Watchman#createClient', () => {
|
||||
it('should not create client when capabilities not match', async () => {
|
||||
capabilities = { relative_root: false }
|
||||
let client = await Watchman.createClient(null, cwd)
|
||||
expect(client).toBe(null)
|
||||
})
|
||||
|
||||
it('should not create when watch failed', async () => {
|
||||
watchResponse = {}
|
||||
let client = await Watchman.createClient(null, cwd)
|
||||
expect(client).toBe(null)
|
||||
})
|
||||
|
||||
it('should create client', async () => {
|
||||
let client = await Watchman.createClient(null, cwd)
|
||||
disposables.push(client)
|
||||
expect(client).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not create client for root', async () => {
|
||||
let client = await Watchman.createClient(null, '/')
|
||||
expect(client).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isValidWatchRoot()', () => {
|
||||
it('should check valid root', async () => {
|
||||
expect(isValidWatchRoot('/')).toBe(false)
|
||||
expect(isValidWatchRoot(os.homedir())).toBe(false)
|
||||
expect(isValidWatchRoot('/tmp/a/b/c')).toBe(false)
|
||||
expect(isValidWatchRoot(os.tmpdir())).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fileSystemWatcher', () => {
|
||||
|
||||
function createWatcher(pattern: string, ignoreCreateEvents = false, ignoreChangeEvents = false, ignoreDeleteEvents = false): FileSystemWatcher {
|
||||
let watcher = watcherManager.createFileSystemWatcher(
|
||||
pattern,
|
||||
ignoreCreateEvents,
|
||||
ignoreChangeEvents,
|
||||
ignoreDeleteEvents
|
||||
)
|
||||
disposables.push(watcher)
|
||||
return watcher
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
workspaceFolder.addWorkspaceFolder(cwd, true)
|
||||
await watcherManager.waitClient(cwd)
|
||||
})
|
||||
|
||||
it('should watch for file create', async () => {
|
||||
let watcher = createWatcher('**/*', false, true, true)
|
||||
let fn = jest.fn()
|
||||
watcher.onDidCreate(fn)
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [createFileChange(`a`)]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should watch for file delete', async () => {
|
||||
let watcher = createWatcher('**/*', true, true, false)
|
||||
let fn = jest.fn()
|
||||
watcher.onDidDelete(fn)
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [createFileChange(`a`, false, false)]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should watch for file change', async () => {
|
||||
let watcher = createWatcher('**/*', false, false, false)
|
||||
let fn = jest.fn()
|
||||
watcher.onDidChange(fn)
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [createFileChange(`a`, false, true)]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should watch for file rename', async () => {
|
||||
let watcher = createWatcher('**/*', false, false, false)
|
||||
let fn = jest.fn()
|
||||
watcher.onDidRename(fn)
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [
|
||||
createFileChange(`a`, false, false),
|
||||
createFileChange(`b`, true, true),
|
||||
]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should not watch for events', async () => {
|
||||
let watcher = createWatcher('**/*', true, true, true)
|
||||
let called = false
|
||||
let onChange = () => { called = true }
|
||||
watcher.onDidCreate(onChange)
|
||||
watcher.onDidChange(onChange)
|
||||
watcher.onDidDelete(onChange)
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [
|
||||
createFileChange(`a`, false, false),
|
||||
createFileChange(`b`, true, true),
|
||||
createFileChange(`c`, false, true),
|
||||
]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.wait(50)
|
||||
expect(called).toBe(false)
|
||||
})
|
||||
|
||||
it('should watch for folder rename', async () => {
|
||||
let watcher = createWatcher('**/*')
|
||||
let newFiles: string[] = []
|
||||
let count = 0
|
||||
watcher.onDidRename(e => {
|
||||
count++
|
||||
newFiles.push(e.newUri.fsPath)
|
||||
})
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [
|
||||
createFileChange(`a/1`, false, false),
|
||||
createFileChange(`a/2`, false, false),
|
||||
createFileChange(`b/1`, true, true),
|
||||
createFileChange(`b/2`, true, true),
|
||||
]
|
||||
sendSubscription(watcher.subscribe, cwd, changes)
|
||||
await helper.waitValue(() => {
|
||||
return count
|
||||
}, 2)
|
||||
})
|
||||
|
||||
it('should watch for new folder', async () => {
|
||||
let watcher = createWatcher('**/*')
|
||||
expect(watcher).toBeDefined()
|
||||
workspaceFolder.renameWorkspaceFolder(cwd, __dirname)
|
||||
await helper.wait(50)
|
||||
let uri: URI
|
||||
watcher.onDidCreate(e => {
|
||||
uri = e
|
||||
})
|
||||
await helper.wait(50)
|
||||
let changes: FileChangeItem[] = [createFileChange(`a`)]
|
||||
sendSubscription(watcher.subscribe, __dirname, changes)
|
||||
await helper.wait(50)
|
||||
expect(uri.fsPath).toEqual(path.join(__dirname, 'a'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('create FileSystemWatcherManager', () => {
|
||||
it('should attach to existing workspace folder', async () => {
|
||||
let workspaceFolder = new WorkspaceFolderController(configurations)
|
||||
workspaceFolder.addWorkspaceFolder(cwd, false)
|
||||
let watcherManager = new FileSystemWatcherManager(workspaceFolder, '')
|
||||
watcherManager.attach(helper.createNullChannel())
|
||||
await helper.wait(100)
|
||||
await watcherManager.createClient(os.tmpdir())
|
||||
await watcherManager.createClient(cwd)
|
||||
await watcherManager.waitClient(cwd)
|
||||
watcherManager.dispose()
|
||||
})
|
||||
})
|
|
@ -1,813 +0,0 @@
|
|||
import { Buffer, Neovim } from '@chemzqm/neovim'
|
||||
import fs from 'fs-extra'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'
|
||||
import { CreateFile, DeleteFile, Position, Range, RenameFile, TextDocumentEdit, TextEdit, VersionedTextDocumentIdentifier, WorkspaceEdit } from 'vscode-languageserver-types'
|
||||
import { URI } from 'vscode-uri'
|
||||
import { RecoverFunc } from '../../model/editInspect'
|
||||
import RelativePattern from '../../model/relativePattern'
|
||||
import { disposeAll } from '../../util'
|
||||
import { readFile } from '../../util/fs'
|
||||
import window from '../../window'
|
||||
import workspace from '../../workspace'
|
||||
import events from '../../events'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
})
|
||||
|
||||
describe('RelativePattern', () => {
|
||||
function testThrow(fn: () => void) {
|
||||
let err
|
||||
try {
|
||||
fn()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
}
|
||||
|
||||
it('should throw for invalid arguments', async () => {
|
||||
testThrow(() => {
|
||||
new RelativePattern('', undefined)
|
||||
})
|
||||
testThrow(() => {
|
||||
new RelativePattern({ uri: undefined } as any, '')
|
||||
})
|
||||
})
|
||||
|
||||
it('should create relativePattern', async () => {
|
||||
for (let base of [__filename, URI.file(__filename), { uri: URI.file(__dirname).toString(), name: 'test' }]) {
|
||||
let p = new RelativePattern(base, '**/*')
|
||||
expect(URI.isUri(p.baseUri)).toBe(true)
|
||||
expect(p.toJSON()).toBeDefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('findFiles()', () => {
|
||||
beforeEach(() => {
|
||||
workspace.workspaceFolderControl.setWorkspaceFolders([__dirname])
|
||||
})
|
||||
|
||||
it('should use glob pattern', async () => {
|
||||
let res = await workspace.findFiles('**/*.ts')
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should use relativePattern', async () => {
|
||||
let relativePattern = new RelativePattern(URI.file(__dirname), '**/*.ts')
|
||||
let res = await workspace.findFiles(relativePattern)
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should respect exclude as glob pattern', async () => {
|
||||
let arr = await workspace.findFiles('**/*.ts', 'files*')
|
||||
let res = arr.find(o => path.relative(__dirname, o.fsPath).startsWith('files'))
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should respect exclude as relativePattern', async () => {
|
||||
let relativePattern = new RelativePattern(URI.file(__dirname), 'files*')
|
||||
let arr = await workspace.findFiles('**/*.ts', relativePattern)
|
||||
let res = arr.find(o => path.relative(__dirname, o.fsPath).startsWith('files'))
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should respect maxResults', async () => {
|
||||
let arr = await workspace.findFiles('**/*.ts', undefined, 1)
|
||||
expect(arr.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should respect token', async () => {
|
||||
let source = new CancellationTokenSource()
|
||||
source.cancel()
|
||||
let arr = await workspace.findFiles('**/*.ts', undefined, 1, source.token)
|
||||
expect(arr.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should cancel findFiles', async () => {
|
||||
let source = new CancellationTokenSource()
|
||||
let p = workspace.findFiles('**/*.ts', undefined, 1, source.token)
|
||||
source.cancel()
|
||||
let arr = await p
|
||||
expect(arr.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyEdits()', () => {
|
||||
it('should not throw when unable to undo & redo', async () => {
|
||||
await workspace.files.undoWorkspaceEdit()
|
||||
await workspace.files.redoWorkspaceEdit()
|
||||
})
|
||||
|
||||
it('should apply TextEdit of documentChanges', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let versioned = VersionedTextDocumentIdentifier.create(doc.uri, doc.version)
|
||||
let edit = TextEdit.insert(Position.create(0, 0), 'bar')
|
||||
let change = TextDocumentEdit.create(versioned, [edit])
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [change]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('bar')
|
||||
})
|
||||
|
||||
it('should apply edit with out change buffers', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('bar')
|
||||
await doc.synchronize()
|
||||
let version = doc.version
|
||||
let versioned = VersionedTextDocumentIdentifier.create(doc.uri, doc.version)
|
||||
let edit = TextEdit.replace(Range.create(0, 0, 0, 3), 'bar')
|
||||
let change = TextDocumentEdit.create(versioned, [edit])
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [change]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
expect(doc.version).toBe(version)
|
||||
})
|
||||
|
||||
it('should not apply TextEdit if version miss match', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let versioned = VersionedTextDocumentIdentifier.create(doc.uri, 10)
|
||||
let edit = TextEdit.insert(Position.create(0, 0), 'bar')
|
||||
let change = TextDocumentEdit.create(versioned, [edit])
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [change]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should apply edits with changes to buffer', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let changes = {
|
||||
[doc.uri]: [TextEdit.insert(Position.create(0, 0), 'bar')]
|
||||
}
|
||||
let workspaceEdit: WorkspaceEdit = { changes }
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('bar')
|
||||
})
|
||||
|
||||
it('should apply edits with changes to file not in buffer list', async () => {
|
||||
let filepath = await createTmpFile('bar')
|
||||
let uri = URI.file(filepath).toString()
|
||||
let changes = {
|
||||
[uri]: [TextEdit.insert(Position.create(0, 0), 'foo')]
|
||||
}
|
||||
let res = await workspace.applyEdit({ changes })
|
||||
expect(res).toBe(true)
|
||||
let doc = workspace.getDocument(uri)
|
||||
let content = doc.getDocumentContent()
|
||||
expect(content).toMatch(/^foobar/)
|
||||
await nvim.command('silent! %bwipeout!')
|
||||
})
|
||||
|
||||
it('should apply edits when file does not exist', async () => {
|
||||
let filepath = path.join(__dirname, 'not_exists')
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
if (fs.existsSync(filepath)) {
|
||||
fs.unlinkSync(filepath)
|
||||
}
|
||||
}
|
||||
})
|
||||
let uri = URI.file(filepath).toString()
|
||||
let changes = {
|
||||
[uri]: [TextEdit.insert(Position.create(0, 0), 'foo')]
|
||||
}
|
||||
let res = await workspace.applyEdit({ changes })
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
|
||||
it('should adjust cursor position after applyEdits', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let pos = await window.getCursorPosition()
|
||||
expect(pos).toEqual({ line: 0, character: 0 })
|
||||
let edit = TextEdit.insert(Position.create(0, 0), 'foo\n')
|
||||
let versioned = VersionedTextDocumentIdentifier.create(doc.uri, null)
|
||||
let documentChanges = [TextDocumentEdit.create(versioned, [edit])]
|
||||
let res = await workspace.applyEdit({ documentChanges })
|
||||
expect(res).toBe(true)
|
||||
pos = await window.getCursorPosition()
|
||||
expect(pos).toEqual({ line: 1, character: 0 })
|
||||
})
|
||||
|
||||
it('should support null version of documentChanges', async () => {
|
||||
let file = path.join(__dirname, 'foo')
|
||||
await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })
|
||||
let uri = URI.file(file).toString()
|
||||
let versioned = VersionedTextDocumentIdentifier.create(uri, null)
|
||||
let edit = TextEdit.insert(Position.create(0, 0), 'bar')
|
||||
let change = TextDocumentEdit.create(versioned, [edit])
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [change]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
await nvim.command('wa')
|
||||
let content = await readFile(file, 'utf8')
|
||||
expect(content).toMatch(/^bar/)
|
||||
await workspace.deleteFile(file, { ignoreIfNotExists: true })
|
||||
})
|
||||
|
||||
it('should support CreateFile edit', async () => {
|
||||
let file = path.join(__dirname, 'foo')
|
||||
let uri = URI.file(file).toString()
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [CreateFile.create(uri, { overwrite: true })]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
await workspace.deleteFile(file, { ignoreIfNotExists: true })
|
||||
})
|
||||
|
||||
it('should support DeleteFile edit', async () => {
|
||||
let file = path.join(__dirname, 'foo')
|
||||
await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })
|
||||
let uri = URI.file(file).toString()
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [DeleteFile.create(uri)]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
|
||||
it('should check uri for CreateFile edit', async () => {
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [CreateFile.create('term://.', { overwrite: true })]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should support RenameFile edit', async () => {
|
||||
let file = path.join(__dirname, 'foo')
|
||||
await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })
|
||||
let newFile = path.join(__dirname, 'bar')
|
||||
let uri = URI.file(file).toString()
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [RenameFile.create(uri, URI.file(newFile).toString())]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
await workspace.deleteFile(newFile, { ignoreIfNotExists: true })
|
||||
})
|
||||
|
||||
it('should support changes with edit and rename', async () => {
|
||||
let fsPath = await createTmpFile('test')
|
||||
let doc = await helper.createDocument(fsPath)
|
||||
let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)
|
||||
let newUri = URI.file(newFile).toString()
|
||||
let edit: WorkspaceEdit = {
|
||||
documentChanges: [
|
||||
{
|
||||
textDocument: {
|
||||
version: null,
|
||||
uri: doc.uri,
|
||||
},
|
||||
edits: [
|
||||
{
|
||||
range: {
|
||||
start: {
|
||||
line: 0,
|
||||
character: 0
|
||||
},
|
||||
end: {
|
||||
line: 0,
|
||||
character: 4
|
||||
}
|
||||
},
|
||||
newText: 'bar'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
oldUri: doc.uri,
|
||||
newUri,
|
||||
kind: 'rename'
|
||||
}
|
||||
]
|
||||
}
|
||||
let res = await workspace.applyEdit(edit)
|
||||
expect(res).toBe(true)
|
||||
let curr = await workspace.document
|
||||
expect(curr.uri).toBe(newUri)
|
||||
expect(curr.getline(0)).toBe('bar')
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('bar')
|
||||
})
|
||||
|
||||
it('should support edit new file with CreateFile', async () => {
|
||||
let file = path.join(os.tmpdir(), 'foo')
|
||||
let uri = URI.file(file).toString()
|
||||
let workspaceEdit: WorkspaceEdit = {
|
||||
documentChanges: [
|
||||
CreateFile.create(uri, { overwrite: true }),
|
||||
TextDocumentEdit.create({ uri, version: 0 }, [
|
||||
TextEdit.insert(Position.create(0, 0), 'foo bar')
|
||||
])
|
||||
]
|
||||
}
|
||||
let res = await workspace.applyEdit(workspaceEdit)
|
||||
expect(res).toBe(true)
|
||||
let doc = workspace.getDocument(uri)
|
||||
expect(doc).toBeDefined()
|
||||
let line = doc.getline(0)
|
||||
expect(line).toBe('foo bar')
|
||||
await workspace.deleteFile(file, { ignoreIfNotExists: true })
|
||||
})
|
||||
|
||||
it('should undo and redo workspace edit', async () => {
|
||||
const folder = path.join(os.tmpdir(), uuid())
|
||||
const pathone = path.join(folder, 'a')
|
||||
const pathtwo = path.join(folder, 'b')
|
||||
await workspace.files.createFile(pathone, { overwrite: true })
|
||||
await workspace.files.createFile(pathtwo, { overwrite: true })
|
||||
let uris = [URI.file(pathone).toString(), URI.file(pathtwo).toString()]
|
||||
const assertContent = (one: string, two: string) => {
|
||||
let doc = workspace.getDocument(uris[0])
|
||||
expect(doc.getDocumentContent()).toBe(one)
|
||||
doc = workspace.getDocument(uris[1])
|
||||
expect(doc.getDocumentContent()).toBe(two)
|
||||
}
|
||||
let edits: TextDocumentEdit[] = []
|
||||
edits.push(TextDocumentEdit.create({ uri: uris[0], version: null }, [
|
||||
TextEdit.insert(Position.create(0, 0), 'foo')
|
||||
]))
|
||||
edits.push(TextDocumentEdit.create({ uri: uris[1], version: null }, [
|
||||
TextEdit.insert(Position.create(0, 0), 'bar')
|
||||
]))
|
||||
await workspace.applyEdit({ documentChanges: edits })
|
||||
assertContent('foo\n', 'bar\n')
|
||||
await workspace.files.undoWorkspaceEdit()
|
||||
assertContent('\n', '\n')
|
||||
await workspace.files.redoWorkspaceEdit()
|
||||
assertContent('foo\n', 'bar\n')
|
||||
})
|
||||
|
||||
it('should should support annotations', async () => {
|
||||
async function assertEdit(confirm: boolean): Promise<void> {
|
||||
let doc = await helper.createDocument(uuid())
|
||||
let edit: WorkspaceEdit = {
|
||||
documentChanges: [
|
||||
{
|
||||
textDocument: { version: doc.version, uri: doc.uri },
|
||||
edits: [
|
||||
{
|
||||
range: Range.create(0, 0, 0, 0),
|
||||
newText: 'bar',
|
||||
annotationId: '85bc78e2-5ef0-4949-b10c-13f476faf430'
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
changeAnnotations: {
|
||||
'85bc78e2-5ef0-4949-b10c-13f476faf430': {
|
||||
needsConfirmation: true,
|
||||
label: 'Text changes',
|
||||
description: 'description'
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = workspace.files.applyEdit(edit)
|
||||
await helper.waitPrompt()
|
||||
if (confirm) {
|
||||
await nvim.input('<cr>')
|
||||
} else {
|
||||
await nvim.input('<esc>')
|
||||
}
|
||||
await p
|
||||
let content = doc.getDocumentContent()
|
||||
if (confirm) {
|
||||
expect(content).toBe('bar\n')
|
||||
} else {
|
||||
expect(content).toBe('\n')
|
||||
}
|
||||
}
|
||||
await assertEdit(true)
|
||||
await assertEdit(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('inspectEdit', () => {
|
||||
async function inspect(edit: WorkspaceEdit): Promise<Buffer> {
|
||||
await workspace.applyEdit(edit)
|
||||
await workspace.files.inspectEdit()
|
||||
let buf = await nvim.buffer
|
||||
return buf
|
||||
}
|
||||
|
||||
it('should show wanring when edit not exists', async () => {
|
||||
(workspace.files as any).editState = undefined
|
||||
await workspace.files.inspectEdit()
|
||||
})
|
||||
|
||||
it('should render with changes', async () => {
|
||||
let fsPath = await createTmpFile('foo\n1\n2\nbar')
|
||||
let doc = await helper.createDocument(fsPath)
|
||||
let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)
|
||||
let newUri = URI.file(newFile).toString()
|
||||
let createFile = path.join(os.tmpdir(), `coc-${process.pid}/create-${uuid()}`)
|
||||
let deleteFile = await createTmpFile('delete')
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(newFile)) fs.unlinkSync(newFile)
|
||||
if (fs.existsSync(createFile)) fs.unlinkSync(createFile)
|
||||
if (fs.existsSync(deleteFile)) fs.unlinkSync(deleteFile)
|
||||
}))
|
||||
let edit: WorkspaceEdit = {
|
||||
documentChanges: [
|
||||
{
|
||||
textDocument: { version: null, uri: doc.uri, },
|
||||
edits: [
|
||||
TextEdit.del(Range.create(0, 0, 1, 0)),
|
||||
TextEdit.replace(Range.create(3, 0, 3, 3), 'xyz'),
|
||||
]
|
||||
},
|
||||
{
|
||||
kind: 'rename',
|
||||
oldUri: doc.uri,
|
||||
newUri
|
||||
}, {
|
||||
kind: 'create',
|
||||
uri: URI.file(createFile).toString()
|
||||
}, {
|
||||
kind: 'delete',
|
||||
uri: URI.file(deleteFile).toString()
|
||||
}
|
||||
]
|
||||
}
|
||||
let buf = await inspect(edit)
|
||||
let lines = await buf.lines
|
||||
let content = lines.join('\n')
|
||||
expect(content).toMatch('Change')
|
||||
expect(content).toMatch('Rename')
|
||||
expect(content).toMatch('Create')
|
||||
expect(content).toMatch('Delete')
|
||||
await nvim.command('exe 5')
|
||||
await nvim.input('<CR>')
|
||||
await helper.waitFor('expand', ['%:p'], newFile)
|
||||
let line = await nvim.call('line', ['.'])
|
||||
expect(line).toBe(3)
|
||||
})
|
||||
|
||||
it('should render annotation label', async () => {
|
||||
let doc = await helper.createDocument(uuid())
|
||||
let edit: WorkspaceEdit = {
|
||||
documentChanges: [
|
||||
{
|
||||
textDocument: { version: doc.version, uri: doc.uri },
|
||||
edits: [
|
||||
{
|
||||
range: Range.create(0, 0, 0, 0),
|
||||
newText: 'bar',
|
||||
annotationId: 'dd866f37-a24c-4503-9c35-c139fb28e25b'
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
changeAnnotations: {
|
||||
'dd866f37-a24c-4503-9c35-c139fb28e25b': {
|
||||
needsConfirmation: false,
|
||||
label: 'Text changes'
|
||||
}
|
||||
}
|
||||
}
|
||||
let buf = await inspect(edit)
|
||||
await events.fire('BufUnload', [buf.id + 1])
|
||||
let winid = await nvim.call('win_getid')
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toBe('Text changes')
|
||||
await nvim.command('exe 1')
|
||||
await nvim.input('<CR>')
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(bufnr).toBe(buf.id)
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<CR>')
|
||||
let fsPath = URI.parse(doc.uri).fsPath
|
||||
await helper.waitFor('expand', ['%:p'], fsPath)
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await nvim.input('<esc>')
|
||||
await helper.wait(10)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createFile()', () => {
|
||||
it('should create and revert parent folder', async () => {
|
||||
const folder = path.join(os.tmpdir(), uuid())
|
||||
const filepath = path.join(folder, 'bar')
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(folder)) fs.removeSync(folder)
|
||||
}))
|
||||
let fns: RecoverFunc[] = []
|
||||
expect(fs.existsSync(folder)).toBe(false)
|
||||
await workspace.files.createFile(filepath, {}, fns)
|
||||
expect(fs.existsSync(filepath)).toBe(true)
|
||||
for (let i = fns.length - 1; i >= 0; i--) {
|
||||
await fns[i]()
|
||||
}
|
||||
expect(fs.existsSync(folder)).toBe(false)
|
||||
})
|
||||
|
||||
it('should throw when file already exists', async () => {
|
||||
let filepath = await createTmpFile('foo', disposables)
|
||||
let fn = async () => {
|
||||
await workspace.createFile(filepath, {})
|
||||
}
|
||||
await expect(fn()).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
it('should not create file if file exists with ignoreIfExists', async () => {
|
||||
let file = await createTmpFile('foo')
|
||||
await workspace.createFile(file, { ignoreIfExists: true })
|
||||
let content = fs.readFileSync(file, 'utf8')
|
||||
expect(content).toBe('foo')
|
||||
})
|
||||
|
||||
it('should create file if does not exist', async () => {
|
||||
await helper.edit()
|
||||
let filepath = path.join(__dirname, 'foo')
|
||||
await workspace.createFile(filepath, { ignoreIfExists: true })
|
||||
let exists = fs.existsSync(filepath)
|
||||
expect(exists).toBe(true)
|
||||
fs.unlinkSync(filepath)
|
||||
})
|
||||
|
||||
it('should revert file create', async () => {
|
||||
let filepath = path.join(os.tmpdir(), uuid())
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(filepath)) fs.unlinkSync(filepath)
|
||||
}))
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.createFile(filepath, { overwrite: true }, fns)
|
||||
expect(fs.existsSync(filepath)).toBe(true)
|
||||
let bufnr = await nvim.call('bufnr', [filepath])
|
||||
expect(bufnr).toBeGreaterThan(0)
|
||||
let doc = workspace.getDocument(bufnr)
|
||||
expect(doc).toBeDefined()
|
||||
for (let fn of fns) {
|
||||
await fn()
|
||||
}
|
||||
expect(fs.existsSync(filepath)).toBe(false)
|
||||
let loaded = await nvim.call('bufloaded', [filepath])
|
||||
expect(loaded).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('renameFile', () => {
|
||||
it('should throw when oldPath not exists', async () => {
|
||||
let filepath = path.join(__dirname, 'not_exists_file')
|
||||
let newPath = path.join(__dirname, 'bar')
|
||||
let fn = async () => {
|
||||
await workspace.renameFile(filepath, newPath)
|
||||
}
|
||||
await expect(fn()).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
it('should rename file on disk', async () => {
|
||||
let filepath = await createTmpFile('test')
|
||||
let newPath = path.join(path.dirname(filepath), 'new_file')
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(newPath)) fs.unlinkSync(newPath)
|
||||
if (fs.existsSync(filepath)) fs.unlinkSync(filepath)
|
||||
}))
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.renameFile(filepath, newPath, { overwrite: true }, fns)
|
||||
expect(fs.existsSync(newPath)).toBe(true)
|
||||
for (let fn of fns) {
|
||||
await fn()
|
||||
}
|
||||
expect(fs.existsSync(newPath)).toBe(false)
|
||||
expect(fs.existsSync(filepath)).toBe(true)
|
||||
})
|
||||
|
||||
it('should rename if file does not exist', async () => {
|
||||
let filepath = path.join(__dirname, 'foo')
|
||||
let newPath = path.join(__dirname, 'bar')
|
||||
await workspace.createFile(filepath)
|
||||
await workspace.renameFile(filepath, newPath)
|
||||
expect(fs.existsSync(newPath)).toBe(true)
|
||||
expect(fs.existsSync(filepath)).toBe(false)
|
||||
fs.unlinkSync(newPath)
|
||||
})
|
||||
|
||||
it('should rename current buffer with same bufnr', async () => {
|
||||
let file = await createTmpFile('test')
|
||||
let doc = await helper.createDocument(file)
|
||||
await nvim.setLine('bar')
|
||||
await doc.patchChange()
|
||||
let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(newFile)) fs.unlinkSync(newFile)
|
||||
}))
|
||||
await workspace.renameFile(file, newFile)
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(bufnr).toBe(doc.bufnr)
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('bar')
|
||||
let exists = fs.existsSync(newFile)
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
it('should overwrite if file exists', async () => {
|
||||
let filepath = path.join(os.tmpdir(), uuid())
|
||||
let newPath = path.join(os.tmpdir(), uuid())
|
||||
await workspace.createFile(filepath)
|
||||
await workspace.createFile(newPath)
|
||||
await workspace.renameFile(filepath, newPath, { overwrite: true })
|
||||
expect(fs.existsSync(newPath)).toBe(true)
|
||||
expect(fs.existsSync(filepath)).toBe(false)
|
||||
fs.unlinkSync(newPath)
|
||||
})
|
||||
|
||||
it('should rename buffer in directory and revert', async () => {
|
||||
let folder = path.join(os.tmpdir(), uuid())
|
||||
let newFolder = path.join(os.tmpdir(), uuid())
|
||||
fs.mkdirSync(folder)
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(folder)) fs.removeSync(folder)
|
||||
if (fs.existsSync(newFolder)) fs.removeSync(newFolder)
|
||||
}))
|
||||
let filepath = path.join(folder, 'new_file')
|
||||
await workspace.createFile(filepath)
|
||||
let bufnr = await nvim.call('bufnr', [filepath])
|
||||
expect(bufnr).toBeGreaterThan(0)
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.renameFile(folder, newFolder, { overwrite: true }, fns)
|
||||
bufnr = await nvim.call('bufnr', [path.join(newFolder, 'new_file')])
|
||||
expect(bufnr).toBeGreaterThan(0)
|
||||
for (let i = fns.length - 1; i >= 0; i--) {
|
||||
await fns[i]()
|
||||
}
|
||||
bufnr = await nvim.call('bufnr', [filepath])
|
||||
expect(bufnr).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadResource()', () => {
|
||||
it('should load file as hidden buffer', async () => {
|
||||
helper.updateConfiguration('workspace.openResourceCommand', '')
|
||||
let filepath = await createTmpFile('foo')
|
||||
let uri = URI.file(filepath).toString()
|
||||
let doc = await workspace.files.loadResource(uri)
|
||||
let bufnrs = await nvim.call('coc#window#bufnrs') as number[]
|
||||
expect(bufnrs.indexOf(doc.bufnr)).toBe(-1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteFile()', () => {
|
||||
it('should throw when file not exists', async () => {
|
||||
let filepath = path.join(__dirname, 'not_exists')
|
||||
let fn = async () => {
|
||||
await workspace.deleteFile(filepath)
|
||||
}
|
||||
await expect(fn()).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
it('should ignore when ignoreIfNotExists set', async () => {
|
||||
let filepath = path.join(__dirname, 'not_exists')
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.deleteFile(filepath, { ignoreIfNotExists: true }, fns)
|
||||
expect(fns.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should unload loaded buffer', async () => {
|
||||
let filepath = await createTmpFile('file to delete')
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(filepath)) fs.unlinkSync(filepath)
|
||||
}))
|
||||
await workspace.files.loadResource(URI.file(filepath).toString())
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.deleteFile(filepath, {}, fns)
|
||||
let loaded = await nvim.call('bufloaded', [filepath])
|
||||
expect(loaded).toBe(0)
|
||||
for (let i = fns.length - 1; i >= 0; i--) {
|
||||
await fns[i]()
|
||||
}
|
||||
expect(fs.existsSync(filepath)).toBe(true)
|
||||
loaded = await nvim.call('bufloaded', [filepath])
|
||||
expect(loaded).toBe(1)
|
||||
})
|
||||
|
||||
it('should delete and recover folder', async () => {
|
||||
let folder = path.join(os.tmpdir(), uuid())
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(folder)) fs.rmdirSync(folder)
|
||||
}))
|
||||
fs.mkdirSync(folder)
|
||||
expect(fs.existsSync(folder)).toBe(true)
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.deleteFile(folder, {}, fns)
|
||||
expect(fs.existsSync(folder)).toBe(false)
|
||||
for (let i = fns.length - 1; i >= 0; i--) {
|
||||
await fns[i]()
|
||||
}
|
||||
expect(fs.existsSync(folder)).toBe(true)
|
||||
await workspace.files.deleteFile(folder, {})
|
||||
})
|
||||
|
||||
it('should delete and recover folder recursive', async () => {
|
||||
let folder = path.join(os.tmpdir(), uuid())
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(folder)) fs.removeSync(folder)
|
||||
}))
|
||||
fs.mkdirSync(folder)
|
||||
await fs.writeFile(path.join(folder, 'new_file'), '', 'utf8')
|
||||
let fns: RecoverFunc[] = []
|
||||
await workspace.files.deleteFile(folder, { recursive: true }, fns)
|
||||
expect(fs.existsSync(folder)).toBe(false)
|
||||
for (let i = fns.length - 1; i >= 0; i--) {
|
||||
await fns[i]()
|
||||
}
|
||||
expect(fs.existsSync(folder)).toBe(true)
|
||||
expect(fs.existsSync(path.join(folder, 'new_file'))).toBe(true)
|
||||
await workspace.files.deleteFile(folder, { recursive: true })
|
||||
})
|
||||
|
||||
it('should delete file if exists', async () => {
|
||||
let filepath = path.join(__dirname, 'foo')
|
||||
await workspace.createFile(filepath)
|
||||
expect(fs.existsSync(filepath)).toBe(true)
|
||||
await workspace.deleteFile(filepath)
|
||||
expect(fs.existsSync(filepath)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadFile()', () => {
|
||||
it('should single loadFile', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let newFile = URI.file(path.join(__dirname, 'abc')).toString()
|
||||
let document = await workspace.loadFile(newFile)
|
||||
let bufnr = await nvim.call('bufnr', '%')
|
||||
expect(document.uri.endsWith('abc')).toBe(true)
|
||||
expect(bufnr).toBe(doc.bufnr)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadFiles', () => {
|
||||
it('should loadFiles', async () => {
|
||||
let files = ['a', 'b', 'c'].map(key => URI.file(path.join(__dirname, key)).toString())
|
||||
let docs = await workspace.loadFiles(files)
|
||||
let uris = docs.map(o => o.uri)
|
||||
expect(uris).toEqual(files)
|
||||
})
|
||||
|
||||
it('should load empty files array', async () => {
|
||||
await workspace.loadFiles([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('openTextDocument()', () => {
|
||||
it('should open document already exists', async () => {
|
||||
let doc = await helper.createDocument('a')
|
||||
await nvim.command('enew')
|
||||
await workspace.openTextDocument(URI.parse(doc.uri))
|
||||
let curr = await workspace.document
|
||||
expect(curr.uri).toBe(doc.uri)
|
||||
})
|
||||
|
||||
it('should throw when file does not exist', async () => {
|
||||
let err
|
||||
try {
|
||||
await workspace.openTextDocument('/a/b/c')
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should open untitled document', async () => {
|
||||
let doc = await workspace.openTextDocument(URI.parse(`untitled:///a/b.js`))
|
||||
expect(doc.uri).toBe('file:///a/b.js')
|
||||
})
|
||||
|
||||
it('should load file that exists', async () => {
|
||||
let doc = await workspace.openTextDocument(URI.file(__filename))
|
||||
expect(URI.parse(doc.uri).fsPath).toBe(__filename)
|
||||
let curr = await workspace.document
|
||||
expect(curr.uri).toBe(doc.uri)
|
||||
})
|
||||
})
|
|
@ -1,94 +0,0 @@
|
|||
import os from 'os'
|
||||
import path from 'path'
|
||||
import Configurations from '../../configuration/index'
|
||||
import * as funcs from '../../core/funcs'
|
||||
let configurations: Configurations
|
||||
|
||||
beforeAll(async () => {
|
||||
let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')
|
||||
configurations = new Configurations(userConfigFile, {
|
||||
$removeConfigurationOption: () => {},
|
||||
$updateConfigurationOption: () => {}
|
||||
})
|
||||
})
|
||||
|
||||
describe('has()', () => {
|
||||
it('should throw for invalid argument', async () => {
|
||||
let env = {
|
||||
isVim: true,
|
||||
version: '8023956'
|
||||
}
|
||||
let err
|
||||
try {
|
||||
expect(funcs.has(env, '0.5.0')).toBe(true)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should detect version on vim8', async () => {
|
||||
let env = {
|
||||
isVim: true,
|
||||
version: '8023956'
|
||||
}
|
||||
expect(funcs.has(env, 'patch-7.4.248')).toBe(true)
|
||||
expect(funcs.has(env, 'patch-8.5.1')).toBe(false)
|
||||
})
|
||||
|
||||
it('should delete version on neovim', async () => {
|
||||
let env = {
|
||||
isVim: false,
|
||||
version: '0.6.1'
|
||||
}
|
||||
expect(funcs.has(env, 'nvim-0.5.0')).toBe(true)
|
||||
expect(funcs.has(env, 'nvim-0.7.0')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createNameSpace()', () => {
|
||||
it('should create namespace', async () => {
|
||||
let nr = funcs.createNameSpace('ns')
|
||||
expect(nr).toBeDefined()
|
||||
expect(nr).toBe(funcs.createNameSpace('ns'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWatchmanPath()', () => {
|
||||
it('should get watchman path', async () => {
|
||||
let res = funcs.getWatchmanPath(configurations)
|
||||
expect(typeof res === 'string' || res == null).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('findUp()', () => {
|
||||
it('should return null when can not find ', async () => {
|
||||
let nvim: any = {
|
||||
call: () => {
|
||||
return __filename
|
||||
}
|
||||
}
|
||||
let res = await funcs.findUp(nvim, os.homedir(), ['file_not_exists'])
|
||||
expect(res).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when unable find cwd in cwd', async () => {
|
||||
let nvim: any = {
|
||||
call: () => {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
let res = await funcs.findUp(nvim, os.homedir(), ['file_not_exists'])
|
||||
expect(res).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('score()', () => {
|
||||
it('should return score', async () => {
|
||||
expect(funcs.score(undefined, 'untitled:///1', '')).toBe(0)
|
||||
expect(funcs.score({ scheme: '*' }, 'untitled:///1', '')).toBe(3)
|
||||
expect(funcs.score('vim', 'untitled:///1', 'vim')).toBe(10)
|
||||
expect(funcs.score('*', 'untitled:///1', '')).toBe(5)
|
||||
expect(funcs.score('', 'untitled:///1', 'vim')).toBe(0)
|
||||
})
|
||||
})
|
|
@ -1,79 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import workspace from '../../workspace'
|
||||
import Keymaps from '../../core/keymaps'
|
||||
import helper from '../helper'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import { disposeAll } from '../../util'
|
||||
|
||||
let nvim: Neovim
|
||||
let keymaps: Keymaps
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
keymaps = workspace.keymaps
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('doKeymap()', () => {
|
||||
it('should not throw when key not mapped', async () => {
|
||||
await keymaps.doKeymap('<C-a>', '', '{C-a}')
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerKeymap()', () => {
|
||||
it('should throw for invalid key', async () => {
|
||||
let err
|
||||
try {
|
||||
keymaps.registerKeymap(['i'], '', jest.fn())
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should throw for duplicated key', async () => {
|
||||
keymaps.registerKeymap(['i'], 'tmp', jest.fn())
|
||||
let err
|
||||
try {
|
||||
keymaps.registerKeymap(['i'], 'tmp', jest.fn())
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should register insert key mapping', async () => {
|
||||
let fn = jest.fn()
|
||||
disposables.push(keymaps.registerKeymap(['i'], 'test', fn))
|
||||
await helper.wait(10)
|
||||
let res = await nvim.call('execute', ['verbose imap <Plug>(coc-test)'])
|
||||
expect(res).toMatch('coc#_insert_key')
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerExprKeymap()', () => {
|
||||
it('should visual key mapping', async () => {
|
||||
await nvim.setLine('foo')
|
||||
let called = false
|
||||
let fn = () => {
|
||||
called = true
|
||||
return ''
|
||||
}
|
||||
disposables.push(keymaps.registerExprKeymap('x', 'x', fn, true))
|
||||
await helper.wait(50)
|
||||
await nvim.command('normal! viw')
|
||||
await nvim.input('x<esc>')
|
||||
await helper.wait(50)
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,148 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { Location, Range } from 'vscode-languageserver-protocol'
|
||||
import { URI } from 'vscode-uri'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
await nvim.command(`source ${path.join(process.cwd(), 'autoload/coc/ui.vim')}`)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
function createLocations(): Location[] {
|
||||
let uri = URI.file(__filename).toString()
|
||||
return [Location.create(uri, Range.create(0, 0, 1, 0)), Location.create(uri, Range.create(2, 0, 3, 0))]
|
||||
}
|
||||
|
||||
describe('showLocations()', () => {
|
||||
it('should show location list by default', async () => {
|
||||
let locations = createLocations()
|
||||
await workspace.showLocations(locations)
|
||||
await helper.waitFor('bufname', ['%'], 'list:///location')
|
||||
})
|
||||
|
||||
it('should fire autocmd when location list disabled', async () => {
|
||||
Object.assign(workspace.env, {
|
||||
locationlist: false
|
||||
})
|
||||
await nvim.exec(`
|
||||
function OnLocationsChange()
|
||||
let g:called = 1
|
||||
endfunction
|
||||
autocmd User CocLocationsChange :call OnLocationsChange()`)
|
||||
let locations = createLocations()
|
||||
await workspace.showLocations(locations)
|
||||
await helper.waitFor('eval', [`get(g:,'called',0)`], 1)
|
||||
})
|
||||
|
||||
it('should show quickfix when quickfix enabled', async () => {
|
||||
helper.updateConfiguration('coc.preferences.useQuickfixForLocations', true)
|
||||
let locations = createLocations()
|
||||
await workspace.showLocations(locations)
|
||||
await helper.waitFor('eval', [`&buftype`], 'quickfix')
|
||||
})
|
||||
|
||||
it('should use customized quickfix open command', async () => {
|
||||
await nvim.setVar('coc_quickfix_open_command', 'copen 1')
|
||||
helper.updateConfiguration('coc.preferences.useQuickfixForLocations', true)
|
||||
let locations = createLocations()
|
||||
await workspace.showLocations(locations)
|
||||
await helper.waitFor('eval', [`&buftype`], 'quickfix')
|
||||
let win = await nvim.window
|
||||
let height = await win.height
|
||||
expect(height).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('jumpTo()', () => {
|
||||
it('should jumpTo position', async () => {
|
||||
let uri = URI.file('/tmp/foo').toString()
|
||||
await workspace.jumpTo(uri, { line: 1, character: 1 })
|
||||
await nvim.command('setl buftype=nofile')
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toMatch('/foo')
|
||||
await buf.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
await workspace.jumpTo(uri, { line: 1, character: 1 })
|
||||
let pos = await nvim.call('getcurpos')
|
||||
expect(pos.slice(1, 3)).toEqual([2, 2])
|
||||
})
|
||||
|
||||
it('should jumpTo uri without normalize', async () => {
|
||||
let uri = 'zipfile:///tmp/clojure-1.9.0.jar::clojure/core.clj'
|
||||
await workspace.jumpTo(uri)
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toBe(uri)
|
||||
})
|
||||
|
||||
it('should jump without position', async () => {
|
||||
let uri = URI.file('/tmp/foo').toString()
|
||||
await workspace.jumpTo(uri)
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toMatch('/foo')
|
||||
})
|
||||
|
||||
it('should jumpTo custom uri scheme', async () => {
|
||||
let uri = 'jdt://foo'
|
||||
await workspace.jumpTo(uri, { line: 1, character: 1 })
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toBe(uri)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('openResource()', () => {
|
||||
it('should open resource', async () => {
|
||||
let uri = URI.file(path.join(os.tmpdir(), 'bar')).toString()
|
||||
await workspace.openResource(uri)
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toMatch('bar')
|
||||
})
|
||||
|
||||
it('should open none file uri', async () => {
|
||||
workspace.registerTextDocumentContentProvider('jd', {
|
||||
provideTextDocumentContent: () => 'jd'
|
||||
})
|
||||
let uri = 'jd://abc'
|
||||
await workspace.openResource(uri)
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toBe('jd://abc')
|
||||
})
|
||||
|
||||
it('should open opened buffer', async () => {
|
||||
let buf = await helper.edit()
|
||||
let doc = workspace.getDocument(buf.id)
|
||||
await workspace.openResource(doc.uri)
|
||||
await helper.wait(30)
|
||||
let bufnr = await nvim.call('bufnr', '%')
|
||||
expect(bufnr).toBe(buf.id)
|
||||
})
|
||||
|
||||
it('should open url', async () => {
|
||||
await helper.mockFunction('coc#ui#open_url', 0)
|
||||
let buf = await helper.edit()
|
||||
let uri = 'http://example.com'
|
||||
await workspace.openResource(uri)
|
||||
await helper.wait(30)
|
||||
let bufnr = await nvim.call('bufnr', '%')
|
||||
expect(bufnr).toBe(buf.id)
|
||||
})
|
||||
})
|
|
@ -1,136 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import which from 'which'
|
||||
import Terminals from '../../core/terminals'
|
||||
import window from '../../window'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let terminals: Terminals
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
terminals = new Terminals()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
terminals.reset()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
describe('create terminal', () => {
|
||||
it('should use cleaned env', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash'),
|
||||
strictEnv: true
|
||||
})
|
||||
await helper.wait(50)
|
||||
terminal.sendText(`echo $NODE_ENV`, true)
|
||||
await helper.wait(50)
|
||||
let buf = nvim.createBuffer(terminal.bufnr)
|
||||
let lines = await buf.lines
|
||||
expect(lines.includes('test')).toBe(false)
|
||||
})
|
||||
|
||||
it('should use custom shell command', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash')
|
||||
})
|
||||
let bufnr = terminal.bufnr
|
||||
let bufname = await nvim.call('bufname', [bufnr]) as string
|
||||
expect(bufname.includes('bash')).toBe(true)
|
||||
})
|
||||
|
||||
it('should use custom cwd', async () => {
|
||||
let basename = path.basename(os.tmpdir())
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
cwd: os.tmpdir()
|
||||
})
|
||||
let bufnr = terminal.bufnr
|
||||
let bufname = await nvim.call('bufname', [bufnr]) as string
|
||||
expect(bufname.includes(basename)).toBe(true)
|
||||
})
|
||||
|
||||
it('should have exit code', async () => {
|
||||
let exitStatus
|
||||
terminals.onDidCloseTerminal(terminal => {
|
||||
exitStatus = terminal.exitStatus
|
||||
})
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash'),
|
||||
strictEnv: true
|
||||
})
|
||||
await helper.wait(50)
|
||||
terminal.sendText('exit', true)
|
||||
await helper.waitFor('bufloaded', [terminal.bufnr], 0)
|
||||
await helper.wait(50)
|
||||
expect(exitStatus).toBeDefined()
|
||||
expect(exitStatus.code).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not throw when show & hide disposed terminal', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash')
|
||||
})
|
||||
terminal.dispose()
|
||||
await terminal.show()
|
||||
await terminal.hide()
|
||||
})
|
||||
|
||||
it('should show terminal on current window', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash')
|
||||
})
|
||||
let winid = await nvim.call('bufwinid', [terminal.bufnr])
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await terminal.show()
|
||||
})
|
||||
|
||||
it('should show terminal that shown', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash')
|
||||
})
|
||||
let res = await terminal.show(true)
|
||||
expect(res).toBe(true)
|
||||
expect(terminal.bufnr).toBeDefined()
|
||||
let winid = await nvim.call('bufwinid', [terminal.bufnr])
|
||||
let curr = await nvim.call('win_getid', [])
|
||||
expect(winid != curr).toBe(true)
|
||||
})
|
||||
|
||||
it('should show hidden terminal', async () => {
|
||||
let terminal = await terminals.createTerminal(nvim, {
|
||||
name: 'test',
|
||||
shellPath: which.sync('bash')
|
||||
})
|
||||
await terminal.hide()
|
||||
await helper.wait(30)
|
||||
let res = await terminal.show()
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
|
||||
it('should create terminal', async () => {
|
||||
let terminal = await window.createTerminal({
|
||||
name: 'test',
|
||||
})
|
||||
expect(terminal).toBeDefined()
|
||||
expect(terminal.processId).toBeDefined()
|
||||
expect(terminal.name).toBeDefined()
|
||||
terminal.dispose()
|
||||
await helper.wait(30)
|
||||
expect(terminal.exitStatus).toEqual({ code: undefined })
|
||||
})
|
||||
})
|
|
@ -1,92 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Position, Range } from 'vscode-languageserver-types'
|
||||
import * as ui from '../../core/ui'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('getCursorPosition()', () => {
|
||||
it('should get cursor position', async () => {
|
||||
await nvim.call('cursor', [1, 1])
|
||||
let res = await ui.getCursorPosition(nvim)
|
||||
expect(res).toEqual({
|
||||
line: 0,
|
||||
character: 0
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('moveTo()', () => {
|
||||
it('should moveTo position', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await ui.moveTo(nvim, Position.create(0, 1), true)
|
||||
let res = await ui.getCursorPosition(nvim)
|
||||
expect(res).toEqual({ line: 0, character: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCursorScreenPosition()', () => {
|
||||
it('should get cursor screen position', async () => {
|
||||
let res = await ui.getCursorScreenPosition(nvim)
|
||||
expect(res).toBeDefined()
|
||||
expect(typeof res.row).toBe('number')
|
||||
expect(typeof res.col).toBe('number')
|
||||
})
|
||||
})
|
||||
|
||||
describe('showMessage()', () => {
|
||||
it('should showMessage on vim', async () => {
|
||||
ui.showMessage(nvim, 'my message', 'MoreMsg', true)
|
||||
await helper.wait(100)
|
||||
let cmdline = await helper.getCmdline()
|
||||
expect(cmdline).toMatch(/my message/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelection()', () => {
|
||||
it('should return null when no selection exists', async () => {
|
||||
let res = await ui.getSelection(nvim, 'v')
|
||||
expect(res).toBeNull()
|
||||
})
|
||||
|
||||
it('should return range for line selection', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await nvim.input('V')
|
||||
await nvim.input('<esc>')
|
||||
let res = await ui.getSelection(nvim, 'V')
|
||||
expect(res).toEqual({ start: { line: 0, character: 0 }, end: { line: 1, character: 0 } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectRange()', () => {
|
||||
it('should select range #1', async () => {
|
||||
await nvim.call('setline', [1, ['foo', 'b']])
|
||||
await nvim.command('set selection=inclusive')
|
||||
await nvim.command('set virtualedit=onemore')
|
||||
await ui.selectRange(nvim, Range.create(0, 0, 1, 1), true)
|
||||
await nvim.input('<esc>')
|
||||
let res = await ui.getSelection(nvim, 'v')
|
||||
expect(res).toEqual(Range.create(0, 0, 1, 1))
|
||||
})
|
||||
|
||||
it('should select range #2', async () => {
|
||||
await nvim.call('setline', [1, ['foo', 'b']])
|
||||
await ui.selectRange(nvim, Range.create(0, 0, 1, 0), true)
|
||||
await nvim.input('<esc>')
|
||||
let res = await ui.getSelection(nvim, 'v')
|
||||
expect(res).toEqual(Range.create(0, 0, 0, 3))
|
||||
})
|
||||
})
|
|
@ -1,290 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { Disposable, WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'
|
||||
import { URI } from 'vscode-uri'
|
||||
import Configurations from '../../configuration/index'
|
||||
import WorkspaceFolderController from '../../core/workspaceFolder'
|
||||
import { PatternType } from '../../types'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let workspaceFolder: WorkspaceFolderController
|
||||
let configurations: Configurations
|
||||
let disposables: Disposable[] = []
|
||||
let nvim: Neovim
|
||||
|
||||
function updateConfiguration(key: string, value: any, defaults: any): void {
|
||||
configurations.updateUserConfig({ [key]: value })
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
configurations.updateUserConfig({ [key]: defaults })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')
|
||||
configurations = new Configurations(userConfigFile, {
|
||||
$removeConfigurationOption: () => {},
|
||||
$updateConfigurationOption: () => {}
|
||||
})
|
||||
workspaceFolder = new WorkspaceFolderController(configurations)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
workspaceFolder.reset()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
describe('WorkspaceFolderController', () => {
|
||||
describe('asRelativePath()', () => {
|
||||
function assertAsRelativePath(input: string, expected: string, includeWorkspace?: boolean) {
|
||||
const actual = workspaceFolder.getRelativePath(input, includeWorkspace)
|
||||
expect(actual).toBe(expected)
|
||||
}
|
||||
|
||||
it('should get relative path', async () => {
|
||||
workspaceFolder.addWorkspaceFolder(`/Coding/Applications/NewsWoWBot`, false)
|
||||
assertAsRelativePath('/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot')
|
||||
assertAsRelativePath('/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart',
|
||||
'/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart')
|
||||
assertAsRelativePath('', '')
|
||||
assertAsRelativePath('/foo/bar', '/foo/bar')
|
||||
assertAsRelativePath('in/out', 'in/out')
|
||||
})
|
||||
|
||||
it('should asRelativePath, same paths, #11402', async () => {
|
||||
const root = '/home/aeschli/workspaces/samples/docker'
|
||||
const input = '/home/aeschli/workspaces/samples/docker'
|
||||
workspaceFolder.addWorkspaceFolder(root, false)
|
||||
assertAsRelativePath(input, input)
|
||||
const input2 = '/home/aeschli/workspaces/samples/docker/a.file'
|
||||
assertAsRelativePath(input2, 'a.file')
|
||||
})
|
||||
|
||||
it('should asRelativePath, not workspaceFolder', async () => {
|
||||
expect(workspace.getRelativePath('')).toBe('')
|
||||
assertAsRelativePath('/foo/bar', '/foo/bar')
|
||||
})
|
||||
|
||||
it('should asRelativePath, multiple folders', () => {
|
||||
workspaceFolder.addWorkspaceFolder(`/Coding/One`, false)
|
||||
workspaceFolder.addWorkspaceFolder(`/Coding/Two`, false)
|
||||
assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt')
|
||||
assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt')
|
||||
assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt')
|
||||
})
|
||||
|
||||
it('should slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', async () => {
|
||||
workspaceFolder.addWorkspaceFolder(`/Coding/One`, false)
|
||||
workspaceFolder.addWorkspaceFolder(`/Coding/Two`, false)
|
||||
|
||||
assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt')
|
||||
assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt', true)
|
||||
assertAsRelativePath('/Coding/One/file.txt', 'file.txt', false)
|
||||
assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt')
|
||||
assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt', true)
|
||||
assertAsRelativePath('/Coding/Two/files/out.txt', 'files/out.txt', false)
|
||||
assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt')
|
||||
assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true)
|
||||
assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setWorkspaceFolders()', () => {
|
||||
it('should set valid folders', async () => {
|
||||
workspaceFolder.setWorkspaceFolders([os.tmpdir(), '/a/not_exists'])
|
||||
let folders = workspaceFolder.workspaceFolders
|
||||
expect(folders.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWorkspaceFolder()', () => {
|
||||
it('should get workspaceFolder by uri', async () => {
|
||||
let res = workspaceFolder.getWorkspaceFolder(URI.parse('untitled://1'))
|
||||
expect(res).toBeUndefined()
|
||||
res = workspaceFolder.getWorkspaceFolder(URI.file('/a/b'))
|
||||
expect(res).toBeUndefined()
|
||||
let filepath = path.join(process.cwd(), 'a/b')
|
||||
workspaceFolder.setWorkspaceFolders([process.cwd()])
|
||||
res = workspaceFolder.getWorkspaceFolder(URI.file(filepath))
|
||||
expect(URI.parse(res.uri).fsPath).toBe(process.cwd())
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRootPatterns()', () => {
|
||||
it('should get patterns from b:coc_root_patterns', async () => {
|
||||
await nvim.command('edit t.vim | let b:coc_root_patterns=["foo"]')
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.getRootPatterns(doc, PatternType.Buffer)
|
||||
expect(res).toEqual(['foo'])
|
||||
})
|
||||
|
||||
it('should get patterns from languageserver', async () => {
|
||||
updateConfiguration('languageserver', {
|
||||
test: {
|
||||
filetypes: ['vim'],
|
||||
rootPatterns: ['bar']
|
||||
}
|
||||
}, {})
|
||||
workspaceFolder.addRootPattern('vim', ['foo'])
|
||||
await nvim.command('edit t.vim')
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.getRootPatterns(doc, PatternType.LanguageServer)
|
||||
expect(res).toEqual(['bar', 'foo'])
|
||||
})
|
||||
|
||||
it('should get patterns from user configuration', async () => {
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.getRootPatterns(doc, PatternType.Global)
|
||||
expect(res.includes('.git')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveRoot()', () => {
|
||||
const cwd = process.cwd()
|
||||
const expand = (input: string) => {
|
||||
return workspace.expand(input)
|
||||
}
|
||||
|
||||
it('should resolve to cwd for file in cwd', async () => {
|
||||
updateConfiguration('coc.preferences.rootPatterns', [], ['.git', '.hg', '.projections.json'])
|
||||
let file = path.join(os.tmpdir(), 'foo')
|
||||
await nvim.command(`edit ${file}`)
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, os.tmpdir(), false, expand)
|
||||
expect(res).toBe(os.tmpdir())
|
||||
})
|
||||
|
||||
it('should not fallback to cwd as workspace folder', async () => {
|
||||
updateConfiguration('coc.preferences.rootPatterns', [], ['.git', '.hg', '.projections.json'])
|
||||
updateConfiguration('workspace.workspaceFolderFallbackCwd', false, true)
|
||||
let file = path.join(os.tmpdir(), 'foo')
|
||||
await nvim.command(`edit ${file}`)
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, os.tmpdir(), false, expand)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return null for untitled buffer', async () => {
|
||||
await nvim.command('enew')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, cwd, false, expand)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should respect ignored filetypes', async () => {
|
||||
updateConfiguration('workspace.ignoredFiletypes', ['vim'], [])
|
||||
await nvim.command('edit t.vim')
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, cwd, false, expand)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should respect workspaceFolderCheckCwd', async () => {
|
||||
let called = 0
|
||||
disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(() => {
|
||||
called++
|
||||
}))
|
||||
workspaceFolder.addRootPattern('vim', ['.vim'])
|
||||
await nvim.command('edit a/.vim/t.vim')
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, cwd, true, expand)
|
||||
expect(res).toBe(process.cwd())
|
||||
await nvim.command('edit a/foo')
|
||||
doc = await workspace.document
|
||||
res = workspaceFolder.resolveRoot(doc, cwd, true, expand)
|
||||
expect(res).toBe(process.cwd())
|
||||
expect(called).toBe(1)
|
||||
})
|
||||
|
||||
it('should respect ignored folders', async () => {
|
||||
updateConfiguration('workspace.ignoredFolders', ['$HOME/foo', '$HOME'], [])
|
||||
let file = path.join(os.homedir(), '.vim/bar')
|
||||
workspaceFolder.addRootPattern('vim', ['.vim'])
|
||||
await nvim.command(`edit ${file}`)
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, path.join(os.homedir(), 'foo'), true, expand)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
describe('bottomUpFileTypes', () => {
|
||||
it('should respect specific filetype', async () => {
|
||||
updateConfiguration('coc.preferences.rootPatterns', ['.vim'], ['.git', '.hg', '.projections.json'])
|
||||
updateConfiguration('workspace.bottomUpFiletypes', ['vim'], [])
|
||||
let root = path.join(os.tmpdir(), 'a')
|
||||
let dir = path.join(root, '.vim')
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
let file = path.join(dir, 'foo')
|
||||
await nvim.command(`edit ${file}`)
|
||||
await nvim.command('setf vim')
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, file, true, expand)
|
||||
expect(res).toBe(root)
|
||||
})
|
||||
|
||||
it('should respect wildcard', async () => {
|
||||
updateConfiguration('coc.preferences.rootPatterns', ['.vim'], ['.git', '.hg', '.projections.json'])
|
||||
updateConfiguration('workspace.bottomUpFiletypes', ['*'], [])
|
||||
let root = path.join(os.tmpdir(), 'a')
|
||||
let dir = path.join(root, '.vim')
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
await helper.wait(30)
|
||||
}
|
||||
let file = path.join(dir, 'foo')
|
||||
await nvim.command(`edit ${file}`)
|
||||
let doc = await workspace.document
|
||||
let res = workspaceFolder.resolveRoot(doc, file, true, expand)
|
||||
expect(res).toBe(root)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('renameWorkspaceFolder()', () => {
|
||||
it('should rename workspaceFolder', async () => {
|
||||
let e: WorkspaceFoldersChangeEvent
|
||||
disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(ev => {
|
||||
e = ev
|
||||
}))
|
||||
let cwd = process.cwd()
|
||||
workspaceFolder.addWorkspaceFolder(cwd, false)
|
||||
workspaceFolder.addWorkspaceFolder(cwd, false)
|
||||
workspaceFolder.renameWorkspaceFolder(cwd, path.join(cwd, '.vim'))
|
||||
expect(e.removed.length).toBe(1)
|
||||
expect(e.added.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeWorkspaceFolder()', () => {
|
||||
it('should remote workspaceFolder', async () => {
|
||||
let e: WorkspaceFoldersChangeEvent
|
||||
disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(ev => {
|
||||
e = ev
|
||||
}))
|
||||
let cwd = process.cwd()
|
||||
workspaceFolder.addWorkspaceFolder(cwd, false)
|
||||
workspaceFolder.removeWorkspaceFolder(cwd)
|
||||
workspaceFolder.removeWorkspaceFolder('/a/b')
|
||||
expect(e.removed.length).toBe(1)
|
||||
expect(e.added.length).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,7 +0,0 @@
|
|||
exports.activate = async context => {
|
||||
return {
|
||||
getContext: () => {
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "global",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"coc": "^0.0.46"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"global": ">=1.0.0",
|
||||
"test": ">=1.0.0"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
exports.activate = context => {
|
||||
return {
|
||||
root: () => {
|
||||
return context.extensionPath
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
exports.activate = async context => {
|
||||
return {
|
||||
asAbsolutePath: p => {
|
||||
return context.asAbsolutePath(p)
|
||||
},
|
||||
getContext: () => {
|
||||
return context
|
||||
},
|
||||
echo: x => {
|
||||
return x
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"coc": "^0.0.46"
|
||||
},
|
||||
"contributes": {
|
||||
"rootPatterns": [
|
||||
{
|
||||
"filetype": "javascript",
|
||||
"patterns": [
|
||||
"package.json",
|
||||
"jsconfig.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"title": "Test",
|
||||
"command": "test.run"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"properties": {
|
||||
"test.enable": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
exports.activate = async context => {
|
||||
return {
|
||||
getContext: () => {
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "local",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"coc": "^0.0.46"
|
||||
}
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, CallHierarchyItem, SymbolKind, Range, SymbolTag } from 'vscode-languageserver-protocol'
|
||||
import CallHierarchyHandler from '../../handler/callHierarchy'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import { URI } from 'vscode-uri'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let callHierarchy: CallHierarchyHandler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
callHierarchy = helper.plugin.getHandler().callHierarchy
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
function createCallItem(name: string, kind: SymbolKind, uri: string, range: Range): CallHierarchyItem {
|
||||
return {
|
||||
name,
|
||||
kind,
|
||||
uri,
|
||||
range,
|
||||
selectionRange: range
|
||||
}
|
||||
}
|
||||
|
||||
describe('CallHierarchy', () => {
|
||||
it('should throw for when provider does not exist', async () => {
|
||||
let err
|
||||
try {
|
||||
await callHierarchy.getIncoming()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should get undefined when prepare failed', async () => {
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return undefined
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
let res = await callHierarchy.getOutgoing()
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should get incoming & outgoing callHierarchy items', async () => {
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, 'test:///foo', Range.create(0, 0, 0, 5))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return [{
|
||||
from: createCallItem('bar', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),
|
||||
fromRanges: [Range.create(0, 0, 0, 5)]
|
||||
}]
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
return [{
|
||||
to: createCallItem('bar', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),
|
||||
fromRanges: [Range.create(1, 0, 1, 5)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
let res = await callHierarchy.getIncoming()
|
||||
expect(res.length).toBe(1)
|
||||
expect(res[0].from.name).toBe('bar')
|
||||
let outgoing = await callHierarchy.getOutgoing()
|
||||
expect(outgoing.length).toBe(1)
|
||||
res = await callHierarchy.getIncoming(outgoing[0].to)
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should show message when provider does not exist', async () => {
|
||||
await callHierarchy.showCallHierarchyTree('incoming')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toMatch('callHierarchy provider not found')
|
||||
await nvim.command('wincmd p')
|
||||
})
|
||||
|
||||
it('should no results when no result returned.', async () => {
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('incoming')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toBe('No results')
|
||||
await nvim.command('wincmd p')
|
||||
})
|
||||
|
||||
it('should render description and support default action', async () => {
|
||||
let doc = await workspace.document
|
||||
let bufnr = doc.bufnr
|
||||
await doc.buffer.setLines(['foo'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(1, 0, 1, 3))
|
||||
item.detail = 'Detail'
|
||||
item.tags = [SymbolTag.Deprecated]
|
||||
return [{
|
||||
from: item,
|
||||
fromRanges: [Range.create(2, 0, 2, 5)]
|
||||
}]
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('incoming')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'INCOMING CALLS',
|
||||
'- c foo',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('t')
|
||||
await helper.waitFor('getline', ['.'], ' - c bar Detail')
|
||||
await nvim.input('<cr>')
|
||||
await helper.waitFor('expand', ['%:p'], fsPath)
|
||||
let res = await nvim.call('coc#cursor#position')
|
||||
expect(res).toEqual([1, 0])
|
||||
let matches = await nvim.call('getmatches') as any[]
|
||||
expect(matches.length).toBe(2)
|
||||
await nvim.command(`b ${bufnr}`)
|
||||
await helper.wait(50)
|
||||
matches = await nvim.call('getmatches')
|
||||
expect(matches.length).toBe(0)
|
||||
await nvim.command(`wincmd o`)
|
||||
await helper.wait(50)
|
||||
})
|
||||
|
||||
it('should invoke open in new tab action', async () => {
|
||||
let doc = await workspace.document
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))
|
||||
item.detail = 'Detail'
|
||||
return [{
|
||||
to: item,
|
||||
fromRanges: [Range.create(1, 0, 1, 3)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
let win = await nvim.window
|
||||
let tab = await nvim.call('tabpagenr')
|
||||
await callHierarchy.showCallHierarchyTree('outgoing')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(100)
|
||||
await nvim.input('<cr>')
|
||||
await helper.wait(200)
|
||||
let newTab = await nvim.call('tabpagenr')
|
||||
expect(newTab != tab).toBe(true)
|
||||
doc = await workspace.document
|
||||
expect(doc.uri).toBe(uri)
|
||||
let res = await nvim.call('getmatches', [win.id])
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should invoke show incoming calls action', async () => {
|
||||
let doc = await workspace.document
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return [{
|
||||
from: createCallItem('test', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),
|
||||
fromRanges: [Range.create(0, 0, 0, 5)]
|
||||
}]
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))
|
||||
item.detail = 'Detail'
|
||||
return [{
|
||||
to: item,
|
||||
fromRanges: [Range.create(1, 0, 1, 3)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('outgoing')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('2')
|
||||
await helper.wait(200)
|
||||
lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'INCOMING CALLS',
|
||||
'- c bar Detail',
|
||||
' + c test'
|
||||
])
|
||||
await nvim.command('bd!')
|
||||
})
|
||||
|
||||
it('should invoke show outgoing calls action', async () => {
|
||||
let doc = await workspace.document
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return [{
|
||||
from: createCallItem('test', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),
|
||||
fromRanges: [Range.create(0, 0, 0, 5)]
|
||||
}]
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))
|
||||
item.detail = 'Detail'
|
||||
return [{
|
||||
to: item,
|
||||
fromRanges: [Range.create(1, 0, 1, 3)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('incoming')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'INCOMING CALLS',
|
||||
'- c foo',
|
||||
' + c test'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('3')
|
||||
await helper.wait(200)
|
||||
lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c test',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('bd!')
|
||||
})
|
||||
|
||||
it('should invoke dismiss action #1', async () => {
|
||||
let doc = await workspace.document
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))
|
||||
item.detail = 'Detail'
|
||||
return [{
|
||||
to: item,
|
||||
fromRanges: [Range.create(1, 0, 1, 3)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('outgoing')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('4')
|
||||
await helper.wait(200)
|
||||
lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo'
|
||||
])
|
||||
await nvim.command('wincmd c')
|
||||
})
|
||||
|
||||
it('should invoke dismiss action #2', async () => {
|
||||
let doc = await workspace.document
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let fsPath = await createTmpFile('foo\nbar\ncontent\n')
|
||||
let uri = URI.file(fsPath).toString()
|
||||
disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {
|
||||
prepareCallHierarchy() {
|
||||
return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))
|
||||
},
|
||||
provideCallHierarchyIncomingCalls() {
|
||||
return []
|
||||
},
|
||||
provideCallHierarchyOutgoingCalls() {
|
||||
let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))
|
||||
item.detail = 'Detail'
|
||||
return [{
|
||||
to: item,
|
||||
fromRanges: [Range.create(1, 0, 1, 3)]
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await callHierarchy.showCallHierarchyTree('outgoing')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo',
|
||||
' + c bar Detail'
|
||||
])
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('t')
|
||||
await helper.wait(50)
|
||||
await nvim.command('exe 4')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('4')
|
||||
await helper.wait(200)
|
||||
lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTGOING CALLS',
|
||||
'- c foo',
|
||||
' - c bar Detail'
|
||||
])
|
||||
await nvim.command('wincmd c')
|
||||
})
|
||||
})
|
|
@ -1,414 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationToken, CodeAction, Command, CodeActionContext, CodeActionKind, TextEdit, Disposable, Range, Position } from 'vscode-languageserver-protocol'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import commands from '../../commands'
|
||||
import ActionsHandler from '../../handler/codeActions'
|
||||
import languages from '../../languages'
|
||||
import { ProviderResult } from '../../provider'
|
||||
import { disposeAll } from '../../util'
|
||||
import { rangeInRange } from '../../util/position'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
let codeActions: ActionsHandler
|
||||
let currActions: CodeAction[]
|
||||
let resolvedAction: CodeAction
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
codeActions = helper.plugin.getHandler().codeActions
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: (
|
||||
_document: TextDocument,
|
||||
_range: Range,
|
||||
_context: CodeActionContext,
|
||||
_token: CancellationToken
|
||||
) => currActions,
|
||||
resolveCodeAction: (
|
||||
_action: CodeAction,
|
||||
_token: CancellationToken
|
||||
): ProviderResult<CodeAction> => resolvedAction
|
||||
}, undefined))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('handler codeActions', () => {
|
||||
describe('organizeImport', () => {
|
||||
it('should throw error when organize import action not found', async () => {
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
let err
|
||||
try {
|
||||
await codeActions.organizeImport()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should perform organize import action', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.replace(Range.create(0, 0, 0, 3), 'bar'))
|
||||
edits.push(TextEdit.replace(Range.create(1, 0, 1, 3), 'foo'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('organize import', edit, CodeActionKind.SourceOrganizeImports)
|
||||
currActions = [action, CodeAction.create('another action')]
|
||||
await codeActions.organizeImport()
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar', 'foo'])
|
||||
})
|
||||
|
||||
it('should register editor.action.organizeImport command', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.replace(Range.create(0, 0, 0, 3), 'bar'))
|
||||
edits.push(TextEdit.replace(Range.create(1, 0, 1, 3), 'foo'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('organize import', edit, CodeActionKind.SourceOrganizeImports)
|
||||
currActions = [action, CodeAction.create('another action')]
|
||||
await commands.executeCommand('editor.action.organizeImport')
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar', 'foo'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('codeActionRange', () => {
|
||||
it('should show warning when no action available', async () => {
|
||||
await helper.createDocument()
|
||||
currActions = []
|
||||
await codeActions.codeActionRange(1, 2, CodeActionKind.QuickFix)
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toMatch(/No quickfix code action/)
|
||||
})
|
||||
|
||||
it('should apply chosen action', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
currActions = [action]
|
||||
let p = codeActions.codeActionRange(1, 2, CodeActionKind.QuickFix)
|
||||
await helper.wait(100)
|
||||
await nvim.input('<CR>')
|
||||
await p
|
||||
let buf = nvim.createBuffer(doc.bufnr)
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toBe('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCodeActions', () => {
|
||||
it('should get empty actions', async () => {
|
||||
currActions = []
|
||||
let doc = await helper.createDocument()
|
||||
let res = await codeActions.getCodeActions(doc)
|
||||
expect(res.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should not filter disabled actions', async () => {
|
||||
currActions = []
|
||||
let action = CodeAction.create('foo', CodeActionKind.QuickFix)
|
||||
action.disabled = { reason: 'disabled' }
|
||||
currActions.push(action)
|
||||
action = CodeAction.create('foo', CodeActionKind.QuickFix)
|
||||
action.disabled = { reason: 'disabled' }
|
||||
currActions.push(action)
|
||||
let doc = await helper.createDocument()
|
||||
let res = await codeActions.getCodeActions(doc)
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should get all actions', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.setLines(['', '', ''], { start: 0, end: -1, strictIndexing: false })
|
||||
let action = CodeAction.create('curr action', CodeActionKind.Empty)
|
||||
currActions = [action]
|
||||
let range: Range
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: (
|
||||
_document: TextDocument,
|
||||
r: Range,
|
||||
_context: CodeActionContext, _token: CancellationToken
|
||||
) => {
|
||||
range = r
|
||||
return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c')]
|
||||
},
|
||||
}, undefined))
|
||||
let res = await codeActions.getCodeActions(doc)
|
||||
expect(range).toEqual(Range.create(0, 0, 3, 0))
|
||||
expect(res.length).toBe(4)
|
||||
})
|
||||
|
||||
it('should filter actions by range', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.setLines(['', '', ''], { start: 0, end: -1, strictIndexing: false })
|
||||
currActions = []
|
||||
let range: Range
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: (
|
||||
_document: TextDocument,
|
||||
r: Range,
|
||||
_context: CodeActionContext, _token: CancellationToken
|
||||
) => {
|
||||
range = r
|
||||
if (rangeInRange(r, Range.create(0, 0, 1, 0))) return [CodeAction.create('a')]
|
||||
return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c')]
|
||||
},
|
||||
}, undefined))
|
||||
let res = await codeActions.getCodeActions(doc, Range.create(0, 0, 0, 0))
|
||||
expect(range).toEqual(Range.create(0, 0, 0, 0))
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should filter actions by kind prefix', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let action = CodeAction.create('my action', CodeActionKind.SourceFixAll)
|
||||
currActions = [action]
|
||||
let res = await codeActions.getCodeActions(doc, undefined, [CodeActionKind.Source])
|
||||
expect(res.length).toBe(1)
|
||||
expect(res[0].kind).toBe(CodeActionKind.SourceFixAll)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCurrentCodeActions', () => {
|
||||
let range: Range
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: (
|
||||
_document: TextDocument,
|
||||
r: Range,
|
||||
_context: CodeActionContext, _token: CancellationToken
|
||||
) => {
|
||||
range = r
|
||||
return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c')]
|
||||
},
|
||||
}, undefined))
|
||||
})
|
||||
|
||||
it('should get codeActions by line', async () => {
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
let res = await codeActions.getCurrentCodeActions('line')
|
||||
expect(range).toEqual(Range.create(0, 0, 1, 0))
|
||||
expect(res.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should get codeActions by cursor', async () => {
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
let res = await codeActions.getCurrentCodeActions('cursor')
|
||||
expect(range).toEqual(Range.create(0, 0, 0, 0))
|
||||
expect(res.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should get codeActions by visual mode', async () => {
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! 0v$')
|
||||
await nvim.input('<esc>')
|
||||
let res = await codeActions.getCurrentCodeActions('v')
|
||||
expect(range).toEqual(Range.create(0, 0, 0, 3))
|
||||
expect(res.length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doCodeAction', () => {
|
||||
it('should not throw when no action exists', async () => {
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
let err
|
||||
try {
|
||||
await codeActions.doCodeAction(undefined)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should apply single code action when only is title', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
currActions = [action]
|
||||
await codeActions.doCodeAction(undefined, 'code fix')
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
})
|
||||
|
||||
it('should apply single code action when only is codeAction array', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
currActions = [action]
|
||||
await codeActions.doCodeAction(undefined, [CodeActionKind.QuickFix])
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
})
|
||||
|
||||
it('should show disabled code action', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let refactorAction = CodeAction.create('code refactor', edit, CodeActionKind.Refactor)
|
||||
refactorAction.disabled = { reason: 'invalid position' }
|
||||
let fixAction = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
currActions = [refactorAction, fixAction]
|
||||
let p = codeActions.doCodeAction(undefined)
|
||||
let winid = await helper.waitFloat()
|
||||
let win = nvim.createWindow(winid)
|
||||
let buf = await win.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines.length).toBe(2)
|
||||
expect(lines[1]).toMatch(/code refactor/)
|
||||
await nvim.input('2')
|
||||
await helper.wait(50)
|
||||
await nvim.input('j')
|
||||
await nvim.input('<cr>')
|
||||
await helper.wait(50)
|
||||
let valid = await win.valid
|
||||
expect(valid).toBe(true)
|
||||
let cmdline = await helper.getCmdline()
|
||||
expect(cmdline).toMatch(/invalid position/)
|
||||
await nvim.input('<esc>')
|
||||
})
|
||||
|
||||
it('should action dialog to choose action', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
currActions = [action, CodeAction.create('foo')]
|
||||
let promise = codeActions.doCodeAction(null)
|
||||
await helper.wait(50)
|
||||
let ids = await nvim.call('coc#float#get_float_win_list') as number[]
|
||||
expect(ids.length).toBeGreaterThan(0)
|
||||
await nvim.input('<CR>')
|
||||
await promise
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
})
|
||||
|
||||
it('should choose code actions by range', async () => {
|
||||
let range: Range
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: (
|
||||
_document: TextDocument,
|
||||
r: Range,
|
||||
_context: CodeActionContext, _token: CancellationToken
|
||||
) => {
|
||||
range = r
|
||||
return [CodeAction.create('my title'), CodeAction.create('b'), CodeAction.create('c')]
|
||||
},
|
||||
}, undefined))
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('abc')
|
||||
await nvim.command('normal! 0v$')
|
||||
await nvim.input('<esc>')
|
||||
await codeActions.doCodeAction('v', 'my title')
|
||||
expect(range).toEqual({ start: { line: 0, character: 0 }, end: { line: 0, character: 3 } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('doQuickfix', () => {
|
||||
it('should throw when quickfix action does not exist', async () => {
|
||||
let err
|
||||
currActions = []
|
||||
await helper.createDocument()
|
||||
try {
|
||||
await codeActions.doQuickfix()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should do preferred quickfix action', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)
|
||||
action.isPreferred = true
|
||||
currActions = [CodeAction.create('foo', CodeActionKind.QuickFix), action, CodeAction.create('bar')]
|
||||
await codeActions.doQuickfix()
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyCodeAction', () => {
|
||||
it('should resolve codeAction', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', CodeActionKind.QuickFix)
|
||||
action.isPreferred = true
|
||||
currActions = [action]
|
||||
resolvedAction = Object.assign({ edit }, action)
|
||||
let arr = await codeActions.getCurrentCodeActions('line', [CodeActionKind.QuickFix])
|
||||
await codeActions.applyCodeAction(arr[0])
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
})
|
||||
|
||||
it('should throw for disabled action', async () => {
|
||||
let action: any = CodeAction.create('my action', CodeActionKind.Empty)
|
||||
action.disabled = { reason: 'disabled', providerId: 'x' }
|
||||
let err
|
||||
try {
|
||||
await codeActions.applyCodeAction(action)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should invoke registered command after apply edit', async () => {
|
||||
let called
|
||||
disposables.push(commands.registerCommand('test.execute', async (s: string) => {
|
||||
called = s
|
||||
await nvim.command(s)
|
||||
}))
|
||||
let doc = await helper.createDocument()
|
||||
let edits: TextEdit[] = []
|
||||
edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))
|
||||
let edit = { changes: { [doc.uri]: edits } }
|
||||
let action = CodeAction.create('code fix', CodeActionKind.QuickFix)
|
||||
action.isPreferred = true
|
||||
currActions = [action]
|
||||
resolvedAction = Object.assign({
|
||||
edit,
|
||||
command: Command.create('run vim command', 'test.execute', 'normal! $')
|
||||
}, action)
|
||||
let arr = await codeActions.getCurrentCodeActions('line', [CodeActionKind.QuickFix])
|
||||
await codeActions.applyCodeAction(arr[0])
|
||||
let lines = await doc.buffer.lines
|
||||
expect(lines).toEqual(['bar'])
|
||||
expect(called).toBe('normal! $')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,316 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Command, CodeLens, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'
|
||||
import commands from '../../commands'
|
||||
import events from '../../events'
|
||||
import CodeLensHandler from '../../handler/codelens/index'
|
||||
import CodeLensBuffer, { getCommands } from '../../handler/codelens/buffer'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
import workspace from '../../workspace'
|
||||
|
||||
let nvim: Neovim
|
||||
let codeLens: CodeLensHandler
|
||||
let disposables: Disposable[] = []
|
||||
let srcId: number
|
||||
|
||||
jest.setTimeout(10000)
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
srcId = await nvim.createNamespace('coc-codelens')
|
||||
codeLens = helper.plugin.getHandler().codeLens
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
helper.updateConfiguration('codeLens.enable', true)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
})
|
||||
|
||||
async function createBufferWithCodeLens(): Promise<CodeLensBuffer> {
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}]
|
||||
},
|
||||
resolveCodeLens: codeLens => {
|
||||
codeLens.command = Command.create('save', '__save', 1, 2, 3)
|
||||
return codeLens
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('e.js')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await doc.synchronize()
|
||||
await codeLens.checkProvider()
|
||||
return codeLens.buffers.getItem(doc.bufnr)
|
||||
}
|
||||
|
||||
describe('codeLenes featrue', () => {
|
||||
it('should do codeLenes request and resolve codeLenes', async () => {
|
||||
let buf = await createBufferWithCodeLens()
|
||||
let doc = await workspace.document
|
||||
let codelens = buf.currentCodeLens
|
||||
expect(codelens).toBeDefined()
|
||||
expect(codelens[0].command).toBeDefined()
|
||||
let markers = await helper.getMarkers(doc.bufnr, srcId)
|
||||
expect(markers.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should refresh on empty changes', async () => {
|
||||
await createBufferWithCodeLens()
|
||||
let doc = await workspace.document
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await doc.synchronize()
|
||||
let markers = await helper.getMarkers(doc.bufnr, srcId)
|
||||
expect(markers.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should work with empty codeLens', async () => {
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: () => {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('t.js')
|
||||
let buf = codeLens.buffers.getItem(doc.bufnr)
|
||||
let codelens = buf.currentCodeLens
|
||||
expect(codelens).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should change codeLenes position', async () => {
|
||||
let fn = jest.fn()
|
||||
helper.updateConfiguration('codeLens.position', 'eol')
|
||||
disposables.push(commands.registerCommand('__save', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}]
|
||||
},
|
||||
resolveCodeLens: codeLens => {
|
||||
codeLens.command = Command.create('save', '__save', 1, 2, 3)
|
||||
return codeLens
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('example.js')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await codeLens.checkProvider()
|
||||
let res = await doc.buffer.getExtMarks(srcId, 0, -1, { details: true })
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
let arr = res[0][3]['virt_text']
|
||||
expect(arr[0][0]).toBe('save')
|
||||
})
|
||||
|
||||
it('should refresh codeLens on CursorHold', async () => {
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: document => {
|
||||
let n = document.lineCount
|
||||
let arr: any[] = []
|
||||
for (let i = 0; i <= n - 2; i++) {
|
||||
arr.push({
|
||||
range: Range.create(i, 0, i, 1),
|
||||
command: Command.create('save', '__save', i)
|
||||
})
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('example.js')
|
||||
await helper.wait(100)
|
||||
let markers = await helper.getMarkers(doc.bufnr, srcId)
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await doc.synchronize()
|
||||
await events.fire('CursorHold', [doc.bufnr])
|
||||
await helper.wait(200)
|
||||
markers = await helper.getMarkers(doc.bufnr, srcId)
|
||||
expect(markers.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should cancel codeLenes request on document change', async () => {
|
||||
let cancelled = false
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: (_, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
cancelled = true
|
||||
clearTimeout(timer)
|
||||
resolve(null)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
resolve([{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}, {
|
||||
range: Range.create(1, 0, 1, 1)
|
||||
}])
|
||||
}, 2000)
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
resolveCodeLens: codeLens => {
|
||||
codeLens.command = Command.create('save', '__save')
|
||||
return codeLens
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('codelens.js')
|
||||
await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'a\nb\nc')])
|
||||
expect(cancelled).toBe(true)
|
||||
})
|
||||
|
||||
it('should resolve on CursorMoved', async () => {
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(90, 0, 90, 1)
|
||||
}, {
|
||||
range: Range.create(91, 0, 91, 1)
|
||||
}]
|
||||
},
|
||||
resolveCodeLens: async codeLens => {
|
||||
codeLens.command = Command.create('save', '__save')
|
||||
return codeLens
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('example.js')
|
||||
let arr = new Array(100)
|
||||
arr.fill('')
|
||||
await nvim.call('setline', [1, arr])
|
||||
await doc.synchronize()
|
||||
await codeLens.checkProvider()
|
||||
await nvim.command('normal! gg')
|
||||
await nvim.command('normal! G')
|
||||
await helper.wait(100)
|
||||
let buf = codeLens.buffers.getItem(doc.bufnr)
|
||||
let codelens = buf.currentCodeLens
|
||||
expect(codelens).toBeDefined()
|
||||
expect(codelens[0].command).toBeDefined()
|
||||
expect(codelens[1].command).toBeDefined()
|
||||
})
|
||||
|
||||
it('should invoke codeLenes action', async () => {
|
||||
let fn = jest.fn()
|
||||
disposables.push(commands.registerCommand('__save', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
await createBufferWithCodeLens()
|
||||
await helper.doAction('codeLensAction')
|
||||
expect(fn).toBeCalledWith(1, 2, 3)
|
||||
await nvim.command('normal! G')
|
||||
await helper.doAction('codeLensAction')
|
||||
})
|
||||
|
||||
it('should use picker for multiple codeLenses', async () => {
|
||||
let fn = jest.fn()
|
||||
disposables.push(commands.registerCommand('__save', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
disposables.push(commands.registerCommand('__delete', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1),
|
||||
command: Command.create('save', '__save', 1, 2, 3)
|
||||
}, {
|
||||
range: Range.create(0, 1, 0, 2),
|
||||
command: Command.create('save', '__delete', 4, 5, 6)
|
||||
}]
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('example.js')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await doc.synchronize()
|
||||
await codeLens.checkProvider()
|
||||
let p = helper.doAction('codeLensAction')
|
||||
await helper.waitFloat()
|
||||
await nvim.input('<cr>')
|
||||
await p
|
||||
expect(fn).toBeCalledWith(1, 2, 3)
|
||||
})
|
||||
|
||||
it('should refresh for failed codeLens request', async () => {
|
||||
let called = 0
|
||||
let fn = jest.fn()
|
||||
disposables.push(commands.registerCommand('__save', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
disposables.push(commands.registerCommand('__foo', (...args) => {
|
||||
fn(...args)
|
||||
}))
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {
|
||||
provideCodeLenses: () => {
|
||||
called++
|
||||
if (called == 1) {
|
||||
return null
|
||||
}
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1),
|
||||
command: Command.create('foo', '__foo')
|
||||
}]
|
||||
}
|
||||
}))
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1),
|
||||
command: Command.create('save', '__save')
|
||||
}]
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('example.js')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await codeLens.checkProvider()
|
||||
let markers = await helper.getMarkers(doc.buffer.id, srcId)
|
||||
expect(markers.length).toBeGreaterThan(0)
|
||||
let codeLensBuffer = codeLens.buffers.getItem(doc.buffer.id)
|
||||
await codeLensBuffer.forceFetch()
|
||||
let curr = codeLensBuffer.currentCodeLens
|
||||
expect(curr.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should use custom separator & position', async () => {
|
||||
helper.updateConfiguration('codeLens.separator', '|')
|
||||
helper.updateConfiguration('codeLens.position', 'eol')
|
||||
let doc = await helper.createDocument('example.js')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await doc.synchronize()
|
||||
disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {
|
||||
provideCodeLenses: () => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 1, 0),
|
||||
command: Command.create('save', '__save')
|
||||
}, {
|
||||
range: Range.create(0, 0, 1, 0),
|
||||
command: Command.create('save', '__save')
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await codeLens.checkProvider()
|
||||
let res = await doc.buffer.getExtMarks(srcId, 0, -1, { details: true })
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should get commands from codeLenses', async () => {
|
||||
expect(getCommands(1, undefined)).toEqual([])
|
||||
let codeLenses = [CodeLens.create(Range.create(0, 0, 0, 0))]
|
||||
expect(getCommands(0, codeLenses)).toEqual([])
|
||||
codeLenses = [CodeLens.create(Range.create(0, 0, 1, 0)), CodeLens.create(Range.create(2, 0, 3, 0))]
|
||||
codeLenses[0].command = Command.create('save', '__save')
|
||||
expect(getCommands(0, codeLenses).length).toEqual(1)
|
||||
})
|
||||
})
|
|
@ -1,264 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationToken, Color, ColorInformation, ColorPresentation, Disposable, Position, Range } from 'vscode-languageserver-protocol'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import commands from '../../commands'
|
||||
import { toHexString } from '../../util/color'
|
||||
import Colors from '../../handler/colors/index'
|
||||
import languages from '../../languages'
|
||||
import { ProviderResult } from '../../provider'
|
||||
import { disposeAll } from '../../util'
|
||||
import path from 'path'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let state = 'normal'
|
||||
let colors: Colors
|
||||
let disposables: Disposable[] = []
|
||||
let colorPresentations: ColorPresentation[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
await nvim.command(`source ${path.join(process.cwd(), 'autoload/coc/color.vim')}`)
|
||||
colors = helper.plugin.getHandler().colors
|
||||
disposables.push(languages.registerDocumentColorProvider([{ language: '*' }], {
|
||||
provideColorPresentations: (
|
||||
_color: Color,
|
||||
_context: { document: TextDocument; range: Range },
|
||||
_token: CancellationToken
|
||||
): ColorPresentation[] => colorPresentations,
|
||||
provideDocumentColors: (
|
||||
document: TextDocument,
|
||||
_token: CancellationToken
|
||||
): ProviderResult<ColorInformation[]> => {
|
||||
if (state == 'empty') return []
|
||||
if (state == 'error') return Promise.reject(new Error('no color'))
|
||||
let matches = Array.from((document.getText() as any).matchAll(/#\w{6}/g)) as any
|
||||
return matches.map(o => {
|
||||
let start = document.positionAt(o.index)
|
||||
let end = document.positionAt(o.index + o[0].length)
|
||||
return {
|
||||
range: Range.create(start, end),
|
||||
color: getColor(255, 255, 255)
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
helper.updateConfiguration('colors.filetypes', ['*'])
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
colorPresentations = []
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
function getColor(r: number, g: number, b: number): Color {
|
||||
return { red: r / 255, green: g / 255, blue: b / 255, alpha: 1 }
|
||||
}
|
||||
|
||||
describe('Colors', () => {
|
||||
describe('utils', () => {
|
||||
it('should get hex string', () => {
|
||||
let color = getColor(255, 255, 255)
|
||||
let hex = toHexString(color)
|
||||
expect(hex).toBe('ffffff')
|
||||
})
|
||||
})
|
||||
|
||||
describe('configuration', () => {
|
||||
it('should toggle enable state on configuration change', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
helper.updateConfiguration('colors.filetypes', [])
|
||||
let enabled = colors.isEnabled(doc.bufnr)
|
||||
helper.updateConfiguration('colors.filetypes', ['*'])
|
||||
expect(enabled).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('commands', () => {
|
||||
it('should register editor.action.pickColor command', async () => {
|
||||
await helper.mockFunction('coc#color#pick_color', [0, 0, 0])
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
await commands.executeCommand('editor.action.pickColor')
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('#000000')
|
||||
})
|
||||
|
||||
it('should register editor.action.colorPresentation command', async () => {
|
||||
colorPresentations = [ColorPresentation.create('red'), ColorPresentation.create('#ff0000')]
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
let p = commands.executeCommand('editor.action.colorPresentation')
|
||||
await helper.wait(100)
|
||||
await nvim.input('1<enter>')
|
||||
await p
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('doHighlight', () => {
|
||||
it('should clearHighlight on empty result', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
state = 'empty'
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
let res = colors.hasColor(doc.bufnr)
|
||||
expect(res).toBe(false)
|
||||
state = 'normal'
|
||||
})
|
||||
|
||||
it('should not highlight on error result', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
state = 'error'
|
||||
let err
|
||||
try {
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
state = 'normal'
|
||||
})
|
||||
|
||||
it('should highlight after document changed', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
expect(colors.hasColor(doc.bufnr)).toBe(false)
|
||||
expect(colors.hasColorAtPosition(doc.bufnr, Position.create(0, 1))).toBe(false)
|
||||
await nvim.setLine('#ffffff #ff0000')
|
||||
await doc.synchronize()
|
||||
await helper.wait(100)
|
||||
expect(colors.hasColorAtPosition(doc.bufnr, Position.create(0, 1))).toBe(true)
|
||||
expect(colors.hasColor(doc.bufnr)).toBe(true)
|
||||
})
|
||||
|
||||
it('should clearHighlight on clearHighlight', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff #ff0000')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
expect(colors.hasColor(doc.bufnr)).toBe(true)
|
||||
colors.clearHighlight(doc.bufnr)
|
||||
expect(colors.hasColor(doc.bufnr)).toBe(false)
|
||||
})
|
||||
|
||||
it('should highlight colors', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
let exists = await nvim.call('hlexists', 'BGffffff')
|
||||
expect(exists).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasColor()', () => {
|
||||
it('should return false when bufnr does not exist', async () => {
|
||||
let res = colors.hasColor(99)
|
||||
colors.clearHighlight(99)
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getColorInformation()', () => {
|
||||
it('should return null when highlighter does not exist', async () => {
|
||||
let res = await colors.getColorInformation(99)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return null when color not found', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff foo ')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
await nvim.call('cursor', [1, 12])
|
||||
let res = await colors.getColorInformation(doc.bufnr)
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasColorAtPosition()', () => {
|
||||
it('should return false when bufnr does not exist', async () => {
|
||||
let res = colors.hasColorAtPosition(99, Position.create(0, 0))
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('pickPresentation()', () => {
|
||||
it('should show warning when color does not exist', async () => {
|
||||
await helper.createDocument()
|
||||
await colors.pickPresentation()
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('Color not found')
|
||||
})
|
||||
|
||||
it('should not throw when presentations do not exist', async () => {
|
||||
colorPresentations = []
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(99)
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
await helper.doAction('colorPresentation')
|
||||
})
|
||||
|
||||
it('should pick presentations', async () => {
|
||||
colorPresentations = [ColorPresentation.create('red'), ColorPresentation.create('#ff0000')]
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
let p = helper.doAction('colorPresentation')
|
||||
await helper.wait(100)
|
||||
await nvim.input('1<enter>')
|
||||
await p
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('red')
|
||||
})
|
||||
})
|
||||
|
||||
describe('pickColor()', () => {
|
||||
it('should show warning when color does not exist', async () => {
|
||||
await helper.createDocument()
|
||||
await colors.pickColor()
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('not found')
|
||||
})
|
||||
|
||||
it('should pickColor', async () => {
|
||||
await helper.mockFunction('coc#color#pick_color', [0, 0, 0])
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
await helper.doAction('pickColor')
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('#000000')
|
||||
})
|
||||
|
||||
it('should not throw when pick color return 0', async () => {
|
||||
await helper.mockFunction('coc#color#pick_color', 0)
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine('#ffffff')
|
||||
doc.forceSync()
|
||||
await colors.doHighlight(doc.bufnr)
|
||||
await helper.doAction('pickColor')
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('#ffffff')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,82 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import CommandsHandler from '../../handler/commands'
|
||||
import commandManager from '../../commands'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let commands: CommandsHandler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
commands = (helper.plugin as any).handler.commands
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.createDocument()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Commands', () => {
|
||||
describe('addVimCommand', () => {
|
||||
it('should register global vim commands', async () => {
|
||||
await commandManager.executeCommand('vim.config')
|
||||
await helper.wait(50)
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toMatch('coc-settings.json')
|
||||
let list = commands.getCommandList()
|
||||
expect(list.includes('vim.config')).toBe(true)
|
||||
})
|
||||
|
||||
it('should add vim command with title', async () => {
|
||||
commands.addVimCommand({ id: 'list', cmd: 'CocList', title: 'list of coc.nvim' })
|
||||
let res = commandManager.titles.get('vim.list')
|
||||
expect(res).toBe('list of coc.nvim')
|
||||
commandManager.unregister('vim.list')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCommands', () => {
|
||||
it('should get command items', async () => {
|
||||
let res = commands.getCommands()
|
||||
let idx = res.findIndex(o => o.id == 'workspace.showOutput')
|
||||
expect(idx != -1).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('repeat', () => {
|
||||
it('should repeat command', async () => {
|
||||
// let buf = await nvim.buffer
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await nvim.call('cursor', [1, 1])
|
||||
commands.addVimCommand({ id: 'remove', cmd: 'normal! dd' })
|
||||
await commands.runCommand('vim.remove')
|
||||
await helper.wait(50)
|
||||
let res = await nvim.call('getline', [1, '$'])
|
||||
expect(res).toEqual(['b', 'c'])
|
||||
await commands.repeat()
|
||||
await helper.wait(50)
|
||||
res = await nvim.call('getline', [1, '$'])
|
||||
expect(res).toEqual(['c'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('runCommand', () => {
|
||||
it('should open command list without id', async () => {
|
||||
await commands.runCommand()
|
||||
await helper.wait(100)
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toBe('list:///commands')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,77 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationTokenSource, Disposable, FoldingRange, Range } from 'vscode-languageserver-protocol'
|
||||
import FoldHandler from '../../handler/fold'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let folds: FoldHandler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
folds = (helper.plugin as any).handler.fold
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.createDocument()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Folds', () => {
|
||||
it('should return null when provider does not exist', async () => {
|
||||
let doc = await workspace.document
|
||||
let token = (new CancellationTokenSource()).token
|
||||
expect(await languages.provideFoldingRanges(doc.textDocument, {}, token)).toBe(null)
|
||||
})
|
||||
|
||||
it('should return false when no fold ranges found', async () => {
|
||||
disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {
|
||||
provideFoldingRanges(_doc) {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
let res = await folds.fold()
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should fold all fold ranges', async () => {
|
||||
disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {
|
||||
provideFoldingRanges(_doc) {
|
||||
return [FoldingRange.create(1, 3), FoldingRange.create(4, 6, 0, 0, 'comment')]
|
||||
}
|
||||
}))
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']])
|
||||
let res = await folds.fold()
|
||||
expect(res).toBe(true)
|
||||
let closed = await nvim.call('foldclosed', [2])
|
||||
expect(closed).toBe(2)
|
||||
closed = await nvim.call('foldclosed', [5])
|
||||
expect(closed).toBe(5)
|
||||
})
|
||||
|
||||
it('should fold comment ranges', async () => {
|
||||
disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {
|
||||
provideFoldingRanges(_doc) {
|
||||
return [FoldingRange.create(1, 3), FoldingRange.create(4, 6, 0, 0, 'comment')]
|
||||
}
|
||||
}))
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']])
|
||||
let res = await folds.fold('comment')
|
||||
expect(res).toBe(true)
|
||||
let closed = await nvim.call('foldclosed', [2])
|
||||
expect(closed).toBe(-1)
|
||||
closed = await nvim.call('foldclosed', [5])
|
||||
expect(closed).toBe(5)
|
||||
})
|
||||
})
|
|
@ -1,252 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationTokenSource, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'
|
||||
import Format from '../../handler/format'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import window from '../../window'
|
||||
import workspace from '../../workspace'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
let format: Format
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
format = helper.plugin.getHandler().format
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
helper.updateConfiguration('coc.preferences.formatOnType', true)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
})
|
||||
|
||||
describe('format handler', () => {
|
||||
describe('documentFormat', () => {
|
||||
it('should throw when provider not found', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let err
|
||||
try {
|
||||
await format.documentFormat(doc)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return false when get empty edits ', async () => {
|
||||
disposables.push(languages.registerDocumentFormatProvider(['*'], {
|
||||
provideDocumentFormattingEdits: () => {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument()
|
||||
let res = await format.documentFormat(doc)
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatOnSave', () => {
|
||||
it('should not throw when provider not found', async () => {
|
||||
helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['javascript'])
|
||||
let filepath = await createTmpFile('')
|
||||
await helper.edit(filepath)
|
||||
await nvim.command('setf javascript')
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('silent w')
|
||||
await helper.wait(100)
|
||||
})
|
||||
|
||||
it('should invoke format on save', async () => {
|
||||
helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['text'])
|
||||
disposables.push(languages.registerDocumentFormatProvider(['text'], {
|
||||
provideDocumentFormattingEdits: document => {
|
||||
let lines = document.getText().replace(/\n$/, '').split(/\n/)
|
||||
let edits: TextEdit[] = []
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let text = lines[i]
|
||||
if (!text.startsWith(' ')) {
|
||||
edits.push(TextEdit.insert(Position.create(i, 0), ' '))
|
||||
}
|
||||
}
|
||||
return edits
|
||||
}
|
||||
}))
|
||||
let filepath = await createTmpFile('a\nb\nc\n')
|
||||
let buf = await helper.edit(filepath)
|
||||
await nvim.command('setf text')
|
||||
await nvim.command('w')
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([' a', ' b', ' c'])
|
||||
})
|
||||
|
||||
it('should cancel when timeout', async () => {
|
||||
helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['*'])
|
||||
disposables.push(languages.registerDocumentFormatProvider(['*'], {
|
||||
provideDocumentFormattingEdits: () => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(undefined)
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
}))
|
||||
let filepath = await createTmpFile('a\nb\nc\n')
|
||||
await helper.edit(filepath)
|
||||
let n = Date.now()
|
||||
await nvim.command('w')
|
||||
expect(Date.now() - n).toBeLessThan(1000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rangeFormat', () => {
|
||||
it('should return null when provider does not exist', async () => {
|
||||
let doc = (await workspace.document).textDocument
|
||||
let range = Range.create(0, 0, 1, 0)
|
||||
let options = await workspace.getFormatOptions()
|
||||
let token = (new CancellationTokenSource()).token
|
||||
expect(await languages.provideDocumentRangeFormattingEdits(doc, range, options, token)).toBe(null)
|
||||
expect(languages.hasProvider('onTypeEdit', doc)).toBe(false)
|
||||
let edits = await languages.provideDocumentFormattingEdits(doc, options, token)
|
||||
expect(edits).toBe(null)
|
||||
})
|
||||
|
||||
it('should invoke range format', async () => {
|
||||
disposables.push(languages.registerDocumentRangeFormatProvider(['text'], {
|
||||
provideDocumentRangeFormattingEdits: (_document, range) => {
|
||||
let lines: number[] = []
|
||||
for (let i = range.start.line; i <= range.end.line; i++) {
|
||||
lines.push(i)
|
||||
}
|
||||
return lines.map(i => {
|
||||
return TextEdit.insert(Position.create(i, 0), ' ')
|
||||
})
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await nvim.command('setf text')
|
||||
await nvim.command('normal! ggvG')
|
||||
await nvim.input('<esc>')
|
||||
expect(languages.hasFormatProvider(doc.textDocument)).toBe(true)
|
||||
expect(languages.hasProvider('format', doc.textDocument)).toBe(true)
|
||||
await helper.doAction('formatSelected', 'v')
|
||||
let buf = nvim.createBuffer(doc.bufnr)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([' a', ' b', ' c'])
|
||||
let options = await workspace.getFormatOptions(doc.uri)
|
||||
let token = (new CancellationTokenSource()).token
|
||||
let edits = await languages.provideDocumentFormattingEdits(doc.textDocument, options, token)
|
||||
expect(edits.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should format range by formatexpr option', async () => {
|
||||
let range: Range
|
||||
disposables.push(languages.registerDocumentRangeFormatProvider(['text'], {
|
||||
provideDocumentRangeFormattingEdits: (_document, r) => {
|
||||
range = r
|
||||
return []
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await nvim.command('setf text')
|
||||
await nvim.command(`setl formatexpr=CocAction('formatSelected')`)
|
||||
await nvim.command('normal! ggvGgq')
|
||||
expect(range).toEqual({
|
||||
start: { line: 0, character: 0 }, end: { line: 3, character: 0 }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatOnType', () => {
|
||||
it('should invoke format', async () => {
|
||||
disposables.push(languages.registerDocumentFormatProvider(['text'], {
|
||||
provideDocumentFormattingEdits: () => {
|
||||
return [TextEdit.insert(Position.create(0, 0), ' ')]
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('setf text')
|
||||
await helper.doAction('format')
|
||||
let line = await nvim.line
|
||||
expect(line).toEqual(' foo')
|
||||
})
|
||||
|
||||
it('should does format on type', async () => {
|
||||
disposables.push(languages.registerOnTypeFormattingEditProvider(['text'], {
|
||||
provideOnTypeFormattingEdits: () => {
|
||||
return [TextEdit.insert(Position.create(0, 0), ' ')]
|
||||
}
|
||||
}, ['|']))
|
||||
await helper.edit()
|
||||
await nvim.command('setf text')
|
||||
await nvim.input('i|')
|
||||
await helper.wait(200)
|
||||
let line = await nvim.line
|
||||
expect(line).toBe(' |')
|
||||
let cursor = await window.getCursorPosition()
|
||||
expect(cursor).toEqual({ line: 0, character: 3 })
|
||||
})
|
||||
|
||||
it('should adjust cursor after format on type', async () => {
|
||||
disposables.push(languages.registerOnTypeFormattingEditProvider(['text'], {
|
||||
provideOnTypeFormattingEdits: () => {
|
||||
return [
|
||||
TextEdit.insert(Position.create(0, 0), ' '),
|
||||
TextEdit.insert(Position.create(0, 2), 'end')
|
||||
]
|
||||
}
|
||||
}, ['|']))
|
||||
await helper.edit()
|
||||
await nvim.command('setf text')
|
||||
await nvim.setLine('"')
|
||||
await nvim.input('i|')
|
||||
await helper.wait(100)
|
||||
let line = await nvim.line
|
||||
expect(line).toBe(' |"end')
|
||||
let cursor = await window.getCursorPosition()
|
||||
expect(cursor).toEqual({ line: 0, character: 3 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('bracketEnterImprove', () => {
|
||||
afterEach(() => {
|
||||
nvim.command('iunmap <CR>', true)
|
||||
})
|
||||
|
||||
it('should format vim file on enter', async () => {
|
||||
let buf = await helper.edit('foo.vim')
|
||||
await nvim.command(`inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm() : "\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>"`)
|
||||
await nvim.setLine('let foo={}')
|
||||
await nvim.command(`normal! gg$`)
|
||||
await nvim.input('i')
|
||||
await nvim.eval(`feedkeys("\\<CR>", 'im')`)
|
||||
await helper.wait(100)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['let foo={', ' \\ ', ' \\ }'])
|
||||
})
|
||||
|
||||
it('should add new line between bracket', async () => {
|
||||
let buf = await helper.edit()
|
||||
await nvim.command(`inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm() : "\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>"`)
|
||||
await nvim.setLine(' {}')
|
||||
await nvim.command(`normal! gg$`)
|
||||
await nvim.input('i')
|
||||
await nvim.eval(`feedkeys("\\<CR>", 'im')`)
|
||||
await helper.wait(100)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([' {', ' ', ' }'])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,140 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, DocumentHighlightKind, Position, Range } from 'vscode-languageserver-protocol'
|
||||
import Highlights from '../../handler/highlights'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
let highlights: Highlights
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
highlights = helper.plugin.getHandler().documentHighlighter
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
})
|
||||
|
||||
function registProvider(): void {
|
||||
disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {
|
||||
provideDocumentHighlights: async document => {
|
||||
let word = await nvim.eval('expand("<cword>")')
|
||||
// let word = document.get
|
||||
let matches = Array.from((document.getText() as any).matchAll(/\w+/g)) as any[]
|
||||
let filtered = matches.filter(o => o[0] == word)
|
||||
return filtered.map((o, i) => {
|
||||
let start = document.positionAt(o.index)
|
||||
let end = document.positionAt(o.index + o[0].length)
|
||||
return {
|
||||
range: Range.create(start, end),
|
||||
kind: i % 2 == 0 ? DocumentHighlightKind.Read : DocumentHighlightKind.Write
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
describe('document highlights', () => {
|
||||
|
||||
function registerTimerProvider(fn: Function, timeout: number): void {
|
||||
disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {
|
||||
provideDocumentHighlights: (_document, _position, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
fn()
|
||||
resolve([])
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
resolve([{ range: Range.create(0, 0, 0, 3) }])
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
it('should return null when highlights provide does not exist', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let res = await highlights.getHighlights(doc, Position.create(0, 0))
|
||||
expect(res).toBeNull()
|
||||
})
|
||||
|
||||
it('should cancel request on CursorMoved', async () => {
|
||||
let fn = jest.fn()
|
||||
registerTimerProvider(fn, 3000)
|
||||
await helper.edit()
|
||||
await nvim.setLine('foo')
|
||||
let p = highlights.highlight()
|
||||
await helper.wait(50)
|
||||
await nvim.call('cursor', [1, 2])
|
||||
await p
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should cancel on timeout', async () => {
|
||||
helper.updateConfiguration('documentHighlight.timeout', 10)
|
||||
let fn = jest.fn()
|
||||
registerTimerProvider(fn, 3000)
|
||||
await helper.edit()
|
||||
await nvim.setLine('foo')
|
||||
await highlights.highlight()
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should add highlights to symbols', async () => {
|
||||
registProvider()
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo bar foo')
|
||||
await helper.doAction('highlight')
|
||||
let winid = await nvim.call('win_getid') as number
|
||||
expect(highlights.hasHighlights(winid)).toBe(true)
|
||||
})
|
||||
|
||||
it('should return highlight ranges', async () => {
|
||||
registProvider()
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo bar foo')
|
||||
let res = await helper.doAction('symbolRanges')
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should return null when cursor not in word range', async () => {
|
||||
disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {
|
||||
provideDocumentHighlights: () => {
|
||||
return [{ range: Range.create(0, 0, 0, 3) }]
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.setLine(' oo')
|
||||
await nvim.call('cursor', [1, 2])
|
||||
let res = await highlights.getHighlights(doc, Position.create(0, 0))
|
||||
expect(res).toBeNull()
|
||||
})
|
||||
|
||||
it('should not throw when document is command line', async () => {
|
||||
await nvim.call('feedkeys', ['q:', 'in'])
|
||||
let doc = await workspace.document
|
||||
expect(doc.isCommandLine).toBe(true)
|
||||
await highlights.highlight()
|
||||
await nvim.input('<C-c>')
|
||||
})
|
||||
|
||||
it('should not throw when provider not found', async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.createDocument()
|
||||
await nvim.setLine(' oo')
|
||||
await nvim.call('cursor', [1, 2])
|
||||
await highlights.highlight()
|
||||
})
|
||||
})
|
|
@ -1,178 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, MarkedString, Hover, Range, TextEdit, Position } from 'vscode-languageserver-protocol'
|
||||
import HoverHandler from '../../handler/hover'
|
||||
import { URI } from 'vscode-uri'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let hover: HoverHandler
|
||||
let disposables: Disposable[] = []
|
||||
let hoverResult: Hover
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
hover = helper.plugin.getHandler().hover
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.createDocument()
|
||||
disposables.push(languages.registerHoverProvider([{ language: '*' }], {
|
||||
provideHover: (_doc, _pos, _token) => {
|
||||
return hoverResult
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
async function getDocumentText(): Promise<string> {
|
||||
let lines = await nvim.call('getbufline', ['coc://document', 1, '$']) as string[]
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
describe('Hover', () => {
|
||||
describe('onHover', () => {
|
||||
it('should return false when hover not found', async () => {
|
||||
hoverResult = null
|
||||
let res = await hover.onHover('preview')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should show MarkupContent hover', async () => {
|
||||
hoverResult = { contents: { kind: 'plaintext', value: 'my hover' } }
|
||||
await hover.onHover('preview')
|
||||
let res = await getDocumentText()
|
||||
expect(res).toMatch('my hover')
|
||||
})
|
||||
|
||||
it('should show MarkedString hover', async () => {
|
||||
hoverResult = { contents: 'string hover' }
|
||||
disposables.push(languages.registerHoverProvider([{ language: '*' }], {
|
||||
provideHover: (_doc, _pos, _token) => {
|
||||
return { contents: { language: 'typescript', value: 'language hover' } }
|
||||
}
|
||||
}))
|
||||
await hover.onHover('preview')
|
||||
let res = await getDocumentText()
|
||||
expect(res).toMatch('string hover')
|
||||
expect(res).toMatch('language hover')
|
||||
})
|
||||
|
||||
it('should show MarkedString hover array', async () => {
|
||||
hoverResult = { contents: ['foo', { language: 'typescript', value: 'bar' }] }
|
||||
await hover.onHover('preview')
|
||||
let res = await getDocumentText()
|
||||
expect(res).toMatch('foo')
|
||||
expect(res).toMatch('bar')
|
||||
})
|
||||
|
||||
it('should highlight hover range', async () => {
|
||||
await nvim.setLine('var')
|
||||
await nvim.command('normal! 0')
|
||||
hoverResult = { contents: ['foo'], range: Range.create(0, 0, 0, 3) }
|
||||
await hover.onHover('preview')
|
||||
let res = await nvim.call('getmatches') as any[]
|
||||
expect(res.length).toBe(1)
|
||||
expect(res[0].group).toBe('CocHoverRange')
|
||||
await helper.wait(600)
|
||||
res = await nvim.call('getmatches')
|
||||
expect(res.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('previewHover', () => {
|
||||
it('should echo hover message', async () => {
|
||||
hoverResult = { contents: ['foo'] }
|
||||
let res = await hover.onHover('echo')
|
||||
expect(res).toBe(true)
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('foo')
|
||||
})
|
||||
|
||||
it('should show hover in float window', async () => {
|
||||
hoverResult = { contents: { kind: 'markdown', value: '```typescript\nconst foo:number\n```' } }
|
||||
await hover.onHover('float')
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await nvim.eval(`getbufline(winbufnr(${win.id}),1,'$')`)
|
||||
expect(lines).toEqual(['const foo:number'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getHover', () => {
|
||||
it('should get hover from MarkedString array', async () => {
|
||||
hoverResult = { contents: ['foo', { language: 'typescript', value: 'bar' }] }
|
||||
disposables.push(languages.registerHoverProvider([{ language: '*' }], {
|
||||
provideHover: (_doc, _pos, _token) => {
|
||||
return { contents: { language: 'typescript', value: 'MarkupContent hover' } }
|
||||
}
|
||||
}))
|
||||
disposables.push(languages.registerHoverProvider([{ language: '*' }], {
|
||||
provideHover: (_doc, _pos, _token) => {
|
||||
return { contents: MarkedString.fromPlainText('MarkedString hover') }
|
||||
}
|
||||
}))
|
||||
let res = await hover.getHover()
|
||||
expect(res.includes('foo')).toBe(true)
|
||||
expect(res.includes('bar')).toBe(true)
|
||||
expect(res.includes('MarkupContent hover')).toBe(true)
|
||||
expect(res.includes('MarkedString hover')).toBe(true)
|
||||
})
|
||||
|
||||
it('should filter empty hover message', async () => {
|
||||
hoverResult = { contents: [''] }
|
||||
let res = await hover.getHover()
|
||||
expect(res.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('definitionHover', () => {
|
||||
it('should load definition from buffer', async () => {
|
||||
hoverResult = { contents: 'string hover' }
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.call('cursor', [1, 1])
|
||||
await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\nbar')])
|
||||
disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {
|
||||
provideDefinition() {
|
||||
return [{
|
||||
targetUri: doc.uri,
|
||||
targetRange: Range.create(0, 0, 1, 3),
|
||||
targetSelectionRange: Range.create(0, 0, 0, 3),
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await hover.definitionHover('preview')
|
||||
let res = await getDocumentText()
|
||||
expect(res).toBe('string hover\n\nfoo\nbar')
|
||||
})
|
||||
|
||||
it('should load definition link from file', async () => {
|
||||
let fsPath = await createTmpFile('foo\nbar\n')
|
||||
hoverResult = { contents: 'string hover' }
|
||||
let doc = await helper.createDocument()
|
||||
await nvim.call('cursor', [1, 1])
|
||||
await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\nbar')])
|
||||
disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {
|
||||
provideDefinition() {
|
||||
return [{
|
||||
targetUri: URI.file(fsPath).toString(),
|
||||
targetRange: Range.create(0, 0, 1, 3),
|
||||
targetSelectionRange: Range.create(0, 0, 0, 3),
|
||||
}]
|
||||
}
|
||||
}))
|
||||
await hover.definitionHover('preview')
|
||||
let res = await getDocumentText()
|
||||
expect(res).toBe('string hover\n\nfoo\nbar')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,93 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import Handler from '../../handler/index'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let handler: Handler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
handler = (helper.plugin as any).handler
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.createDocument()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Handler', () => {
|
||||
describe('hasProvider', () => {
|
||||
it('should check provider for document', async () => {
|
||||
let res = await handler.hasProvider('definition')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkProvier', () => {
|
||||
it('should throw error when provider not found', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let err
|
||||
try {
|
||||
handler.checkProvier('definition', doc.textDocument)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('withRequestToken', () => {
|
||||
it('should cancel previous request when called again', async () => {
|
||||
let cancelled = false
|
||||
let p = handler.withRequestToken('test', token => {
|
||||
return new Promise(s => {
|
||||
token.onCancellationRequested(() => {
|
||||
cancelled = true
|
||||
clearTimeout(timer)
|
||||
s(undefined)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
s(undefined)
|
||||
}, 3000)
|
||||
})
|
||||
}, false)
|
||||
setTimeout(async () => {
|
||||
await handler.withRequestToken('test', () => {
|
||||
return Promise.resolve(undefined)
|
||||
}, false)
|
||||
}, 50)
|
||||
await p
|
||||
expect(cancelled).toBe(true)
|
||||
})
|
||||
|
||||
it('should cancel request on insert start', async () => {
|
||||
let cancelled = false
|
||||
let p = handler.withRequestToken('test', token => {
|
||||
return new Promise(s => {
|
||||
token.onCancellationRequested(() => {
|
||||
cancelled = true
|
||||
clearTimeout(timer)
|
||||
s(undefined)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
s(undefined)
|
||||
}, 3000)
|
||||
})
|
||||
}, false)
|
||||
await nvim.input('i')
|
||||
await p
|
||||
expect(cancelled).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,233 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationTokenSource, Disposable, Position, Range } from 'vscode-languageserver-protocol'
|
||||
import InlayHintHandler from '../../handler/inlayHint/index'
|
||||
import { InlayHint } from '../../inlayHint'
|
||||
import languages from '../../languages'
|
||||
import { isValidInlayHint, sameHint } from '../../provider/inlayHintManager'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let handler: InlayHintHandler
|
||||
let disposables: Disposable[] = []
|
||||
let ns: number
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
handler = helper.plugin.getHandler().inlayHintHandler
|
||||
ns = await nvim.createNamespace('coc-inlayHint')
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('InlayHint', () => {
|
||||
describe('utils', () => {
|
||||
it('should check same hint', () => {
|
||||
let hint = InlayHint.create(Position.create(0, 0), 'foo')
|
||||
expect(sameHint(hint, InlayHint.create(Position.create(0, 0), 'bar'))).toBe(false)
|
||||
expect(sameHint(hint, InlayHint.create(Position.create(0, 0), [{ value: 'foo' }]))).toBe(true)
|
||||
})
|
||||
|
||||
it('should check valid hint', () => {
|
||||
let hint = InlayHint.create(Position.create(0, 0), 'foo')
|
||||
expect(isValidInlayHint(hint, Range.create(0, 0, 1, 0))).toBe(true)
|
||||
expect(isValidInlayHint(InlayHint.create(Position.create(0, 0), ''), Range.create(0, 0, 1, 0))).toBe(false)
|
||||
expect(isValidInlayHint(InlayHint.create(Position.create(3, 0), 'foo'), Range.create(0, 0, 1, 0))).toBe(false)
|
||||
expect(isValidInlayHint({ label: 'f' } as any, Range.create(0, 0, 1, 0))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('provideInlayHints', () => {
|
||||
it('should not throw when failed', async () => {
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: () => {
|
||||
return Promise.reject(new Error('Test failure'))
|
||||
}
|
||||
}))
|
||||
let doc = await workspace.document
|
||||
let tokenSource = new CancellationTokenSource()
|
||||
let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)
|
||||
expect(res).toEqual([])
|
||||
})
|
||||
|
||||
it('should merge provide results', async () => {
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: () => {
|
||||
return [InlayHint.create(Position.create(0, 0), 'foo')]
|
||||
}
|
||||
}))
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: () => {
|
||||
return [
|
||||
InlayHint.create(Position.create(0, 0), 'foo'),
|
||||
InlayHint.create(Position.create(1, 0), 'bar'),
|
||||
InlayHint.create(Position.create(5, 0), 'bad')]
|
||||
}
|
||||
}))
|
||||
let doc = await workspace.document
|
||||
let tokenSource = new CancellationTokenSource()
|
||||
let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 3, 0), tokenSource.token)
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should resolve inlay hint', async () => {
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: () => {
|
||||
return [InlayHint.create(Position.create(0, 0), 'foo')]
|
||||
},
|
||||
resolveInlayHint: hint => {
|
||||
hint.tooltip = 'tooltip'
|
||||
return hint
|
||||
}
|
||||
}))
|
||||
let doc = await workspace.document
|
||||
let tokenSource = new CancellationTokenSource()
|
||||
let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)
|
||||
let resolved = await languages.resolveInlayHint(res[0], tokenSource.token)
|
||||
expect(resolved.tooltip).toBe('tooltip')
|
||||
resolved = await languages.resolveInlayHint(resolved, tokenSource.token)
|
||||
expect(resolved.tooltip).toBe('tooltip')
|
||||
})
|
||||
|
||||
it('should not resolve when cancelled', async () => {
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: () => {
|
||||
return [InlayHint.create(Position.create(0, 0), 'foo')]
|
||||
},
|
||||
resolveInlayHint: (hint, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
resolve(null)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
resolve(Object.assign({}, hint, { tooltip: 'tooltip' }))
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
}))
|
||||
let doc = await workspace.document
|
||||
let tokenSource = new CancellationTokenSource()
|
||||
let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)
|
||||
let p = languages.resolveInlayHint(res[0], tokenSource.token)
|
||||
tokenSource.cancel()
|
||||
let resolved = await p
|
||||
expect(resolved.tooltip).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('setVirtualText', () => {
|
||||
async function registerProvider(content: string): Promise<Disposable> {
|
||||
let doc = await workspace.document
|
||||
let disposable = languages.registerInlayHintsProvider([{ language: '*' }], {
|
||||
provideInlayHints: (document, range) => {
|
||||
let content = document.getText(range)
|
||||
let lines = content.split(/\r?\n/)
|
||||
let hints: InlayHint[] = []
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i]
|
||||
if (!line.length) continue
|
||||
let parts = line.split(/\s+/)
|
||||
hints.push(...parts.map(s => InlayHint.create(Position.create(range.start.line + i, line.length), s)))
|
||||
}
|
||||
return hints
|
||||
}
|
||||
})
|
||||
await doc.buffer.setLines(content.split(/\n/), { start: 0, end: -1 })
|
||||
await doc.synchronize()
|
||||
return disposable
|
||||
}
|
||||
|
||||
async function waitRefresh(bufnr: number) {
|
||||
let buf = handler.getItem(bufnr)
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let timer = setTimeout(() => {
|
||||
reject(new Error('not refresh after 1s'))
|
||||
}, 1000)
|
||||
buf.onDidRefresh(() => {
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
it('should not refresh when languageId not match', async () => {
|
||||
let doc = await workspace.document
|
||||
disposables.push(languages.registerInlayHintsProvider([{ language: 'javascript' }], {
|
||||
provideInlayHints: () => {
|
||||
let hint = InlayHint.create(Position.create(0, 0), 'foo')
|
||||
return [hint]
|
||||
}
|
||||
}))
|
||||
await nvim.setLine('foo')
|
||||
await doc.synchronize()
|
||||
await helper.wait(30)
|
||||
let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should refresh on text change', async () => {
|
||||
let buf = await nvim.buffer
|
||||
let disposable = await registerProvider('foo')
|
||||
disposables.push(disposable)
|
||||
await waitRefresh(buf.id)
|
||||
await buf.setLines(['a', 'b', 'c'], { start: 0, end: -1 })
|
||||
await waitRefresh(buf.id)
|
||||
let markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBe(3)
|
||||
let item = handler.getItem(buf.id)
|
||||
await item.renderRange()
|
||||
expect(item.current.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should refresh on provider dispose', async () => {
|
||||
let buf = await nvim.buffer
|
||||
let disposable = await registerProvider('foo bar')
|
||||
await waitRefresh(buf.id)
|
||||
disposable.dispose()
|
||||
let markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBe(0)
|
||||
let item = handler.getItem(buf.id)
|
||||
expect(item.current.length).toBe(0)
|
||||
await item.renderRange()
|
||||
expect(item.current.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should refresh on scroll', async () => {
|
||||
let arr = new Array(200)
|
||||
let content = arr.fill('foo').join('\n')
|
||||
let buf = await nvim.buffer
|
||||
let disposable = await registerProvider(content)
|
||||
disposables.push(disposable)
|
||||
await waitRefresh(buf.id)
|
||||
let markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
let len = markers.length
|
||||
await nvim.command('normal! G')
|
||||
await waitRefresh(buf.id)
|
||||
await nvim.input('<C-y>')
|
||||
await waitRefresh(buf.id)
|
||||
markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBeGreaterThan(len)
|
||||
})
|
||||
|
||||
it('should cancel previous render', async () => {
|
||||
let buf = await nvim.buffer
|
||||
let disposable = await registerProvider('foo')
|
||||
disposables.push(disposable)
|
||||
await waitRefresh(buf.id)
|
||||
let item = handler.getItem(buf.id)
|
||||
await item.renderRange()
|
||||
await item.renderRange()
|
||||
expect(item.current.length).toBe(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, Range, Position } from 'vscode-languageserver-protocol'
|
||||
import LinkedEditingHandler from '../../handler/linkedEditing'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let handler: LinkedEditingHandler
|
||||
let disposables: Disposable[] = []
|
||||
let wordPattern: string | undefined
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
handler = helper.plugin.getHandler().linkedEditingHandler
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
helper.updateConfiguration('coc.preferences.enableLinkedEditing', true)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
async function registerProvider(content: string, position: Position): Promise<void> {
|
||||
let doc = await workspace.document
|
||||
disposables.push(languages.registerLinkedEditingRangeProvider([{ language: '*' }], {
|
||||
provideLinkedEditingRanges: (doc, pos) => {
|
||||
let document = workspace.getDocument(doc.uri)
|
||||
let range = document.getWordRangeAtPosition(pos)
|
||||
if (!range) return null
|
||||
let text = doc.getText(range)
|
||||
let ranges: Range[] = document.getSymbolRanges(text)
|
||||
return { ranges, wordPattern }
|
||||
}
|
||||
}))
|
||||
await nvim.setLine(content)
|
||||
await doc.synchronize()
|
||||
await handler.enable(doc, position)
|
||||
}
|
||||
|
||||
async function assertMatches(len: number): Promise<void> {
|
||||
let res = await nvim.call('getmatches') as any[]
|
||||
res = res.filter(o => o.group === 'CocLinkedEditing')
|
||||
expect(res.length).toBe(len)
|
||||
}
|
||||
|
||||
describe('LinkedEditing', () => {
|
||||
it('should active and cancel on cursor moved', async () => {
|
||||
await registerProvider('foo foo a ', Position.create(0, 0))
|
||||
await assertMatches(2)
|
||||
await nvim.command(`normal! $`)
|
||||
await helper.wait(50)
|
||||
await assertMatches(0)
|
||||
})
|
||||
|
||||
it('should active when moved to another word', async () => {
|
||||
await registerProvider('foo foo bar bar bar', Position.create(0, 0))
|
||||
await nvim.call('cursor', [1, 9])
|
||||
await helper.wait(50)
|
||||
await assertMatches(3)
|
||||
})
|
||||
|
||||
it('should active on text change', async () => {
|
||||
let doc = await workspace.document
|
||||
await registerProvider('foo foo a ', Position.create(0, 0))
|
||||
await assertMatches(2)
|
||||
await nvim.call('cursor', [1, 1])
|
||||
await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 0, 0, 0, ['i']])
|
||||
await doc.synchronize()
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('ifoo ifoo a ')
|
||||
await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 0, 0, 1, []])
|
||||
await doc.synchronize()
|
||||
line = await nvim.line
|
||||
expect(line).toBe('foo foo a ')
|
||||
})
|
||||
|
||||
it('should cancel when change out of range', async () => {
|
||||
let doc = await workspace.document
|
||||
await registerProvider('foo foo bar', Position.create(0, 0))
|
||||
await assertMatches(2)
|
||||
await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 9, 0, 10, ['']])
|
||||
await doc.synchronize()
|
||||
await assertMatches(0)
|
||||
})
|
||||
|
||||
it('should cancel on editor change', async () => {
|
||||
await registerProvider('foo foo a ', Position.create(0, 0))
|
||||
await nvim.command(`enew`)
|
||||
await helper.wait(50)
|
||||
await assertMatches(0)
|
||||
})
|
||||
|
||||
it('should cancel when insert none word character', async () => {
|
||||
await registerProvider('foo foo a ', Position.create(0, 0))
|
||||
await nvim.call('cursor', [1, 4])
|
||||
await nvim.input('i')
|
||||
await nvim.input('a')
|
||||
await helper.wait(50)
|
||||
await assertMatches(2)
|
||||
await nvim.input('i')
|
||||
await nvim.input('@')
|
||||
await helper.wait(50)
|
||||
await assertMatches(0)
|
||||
})
|
||||
|
||||
it('should cancel when insert not match wordPattern', async () => {
|
||||
wordPattern = '[A-Z]'
|
||||
await registerProvider('foo foo a ', Position.create(0, 0))
|
||||
await nvim.call('cursor', [1, 4])
|
||||
await nvim.input('i')
|
||||
await nvim.input('A')
|
||||
await helper.wait(50)
|
||||
await assertMatches(2)
|
||||
await nvim.input('i')
|
||||
await nvim.input('3')
|
||||
await helper.wait(50)
|
||||
await assertMatches(0)
|
||||
})
|
||||
|
||||
it('should cancel request on cursor moved', async () => {
|
||||
disposables.push(languages.registerLinkedEditingRangeProvider([{ language: '*' }], {
|
||||
provideLinkedEditingRanges: (doc, pos, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
resolve(null)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
let document = workspace.getDocument(doc.uri)
|
||||
let range = document.getWordRangeAtPosition(pos)
|
||||
if (!range) return resolve(null)
|
||||
let text = doc.getText(range)
|
||||
let ranges: Range[] = document.getSymbolRanges(text)
|
||||
resolve({ ranges, wordPattern })
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
}))
|
||||
let doc = await workspace.document
|
||||
await nvim.setLine('foo foo ')
|
||||
await doc.synchronize()
|
||||
await nvim.call('cursor', [1, 2])
|
||||
await helper.wait(30)
|
||||
await nvim.call('cursor', [1, 9])
|
||||
await helper.wait(30)
|
||||
await assertMatches(0)
|
||||
})
|
||||
})
|
|
@ -1,137 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, DocumentLink, Range } from 'vscode-languageserver-protocol'
|
||||
import LinksHandler from '../../handler/links'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import events from '../../events'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let links: LinksHandler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
links = helper.plugin.getHandler().links
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Links', () => {
|
||||
it('should get document links', async () => {
|
||||
disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {
|
||||
provideDocumentLinks: (_doc, _token) => {
|
||||
return [
|
||||
DocumentLink.create(Range.create(0, 0, 0, 5), 'test:///foo'),
|
||||
DocumentLink.create(Range.create(1, 0, 1, 5), 'test:///bar')
|
||||
]
|
||||
}
|
||||
}))
|
||||
let res = await links.getLinks()
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should throw error when link target not resolved', async () => {
|
||||
disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {
|
||||
provideDocumentLinks(_doc, _token) {
|
||||
return [
|
||||
DocumentLink.create(Range.create(0, 0, 0, 5))
|
||||
]
|
||||
},
|
||||
resolveDocumentLink(link) {
|
||||
return link
|
||||
}
|
||||
}))
|
||||
let res = await links.getLinks()
|
||||
let err
|
||||
try {
|
||||
await links.openLink(res[0])
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should open link at current position', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! 0')
|
||||
disposables.push(workspace.registerTextDocumentContentProvider('test', {
|
||||
provideTextDocumentContent: () => {
|
||||
return 'test'
|
||||
}
|
||||
}))
|
||||
disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {
|
||||
provideDocumentLinks(_doc, _token) {
|
||||
return [
|
||||
DocumentLink.create(Range.create(0, 0, 0, 5)),
|
||||
]
|
||||
},
|
||||
resolveDocumentLink(link) {
|
||||
link.target = 'test:///foo'
|
||||
return link
|
||||
}
|
||||
}))
|
||||
await links.openCurrentLink()
|
||||
let bufname = await nvim.call('bufname', '%')
|
||||
expect(bufname).toBe('test:///foo')
|
||||
await nvim.call('setline', [1, ['a', 'b', 'c']])
|
||||
await nvim.call('cursor', [3, 1])
|
||||
let res = await links.openCurrentLink()
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false when current links not found', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! 0')
|
||||
disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {
|
||||
provideDocumentLinks(_doc, _token) {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
let res = await links.openCurrentLink()
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should show tooltip', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await nvim.call('cursor', [1, 1])
|
||||
disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {
|
||||
provideDocumentLinks(_doc, _token) {
|
||||
let link = DocumentLink.create(Range.create(0, 0, 0, 5))
|
||||
link.tooltip = 'test'
|
||||
return [link]
|
||||
},
|
||||
resolveDocumentLink(link) {
|
||||
link.target = 'http://example.com'
|
||||
return link
|
||||
}
|
||||
}))
|
||||
await links.showTooltip()
|
||||
let win = await helper.getFloat()
|
||||
let buf = await win.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toMatch('test')
|
||||
})
|
||||
|
||||
it('should enable tooltip on CursorHold', async () => {
|
||||
let doc = await workspace.document
|
||||
helper.updateConfiguration('links.tooltip', true)
|
||||
await nvim.setLine('http://www.baidu.com')
|
||||
await nvim.call('cursor', [1, 1])
|
||||
let link = await links.getCurrentLink()
|
||||
expect(link).toBeDefined()
|
||||
await events.fire('CursorHold', [doc.bufnr])
|
||||
let win = await helper.getFloat()
|
||||
let buf = await win.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toMatch('Press')
|
||||
})
|
||||
})
|
|
@ -1,323 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, LocationLink, Location, Range, Position, CancellationTokenSource } from 'vscode-languageserver-protocol'
|
||||
import LocationHandler from '../../handler/locations'
|
||||
import languages from '../../languages'
|
||||
import services from '../../services'
|
||||
import workspace from '../../workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
let nvim: Neovim
|
||||
let locations: LocationHandler
|
||||
let disposables: Disposable[] = []
|
||||
let currLocations: Location[]
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
Object.assign(workspace.env, {
|
||||
locationlist: false
|
||||
})
|
||||
locations = helper.plugin.getHandler().locations
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await helper.createDocument()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
function createLocation(name: string, sl: number, sc: number, el: number, ec: number): Location {
|
||||
return Location.create(`test://${name}`, Range.create(sl, sc, el, ec))
|
||||
}
|
||||
|
||||
describe('locations', () => {
|
||||
describe('no provider', () => {
|
||||
it('should return null when provider does not exist', async () => {
|
||||
let doc = (await workspace.document).textDocument
|
||||
let pos = Position.create(0, 0)
|
||||
let tokenSource = new CancellationTokenSource()
|
||||
let token = tokenSource.token
|
||||
expect(await languages.getDefinition(doc, pos, token)).toBe(null)
|
||||
expect(await languages.getDefinitionLinks(doc, pos, token)).toBe(null)
|
||||
expect(await languages.getDeclaration(doc, pos, token)).toBe(null)
|
||||
expect(await languages.getTypeDefinition(doc, pos, token)).toBe(null)
|
||||
expect(await languages.getImplementation(doc, pos, token)).toBe(null)
|
||||
expect(await languages.getReferences(doc, { includeDeclaration: false }, pos, token)).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('reference', () => {
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerReferencesProvider([{ language: '*' }], {
|
||||
provideReferences: () => {
|
||||
return currLocations
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should get references', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]
|
||||
let res = await locations.references()
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should jump to references', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0)]
|
||||
let res = await locations.gotoReferences('edit', true)
|
||||
expect(res).toBe(true)
|
||||
let name = await nvim.call('bufname', ['%'])
|
||||
expect(name).toBe('test://foo')
|
||||
})
|
||||
|
||||
it('should return false when references not found', async () => {
|
||||
currLocations = []
|
||||
let res = await locations.gotoReferences('edit', true)
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('definition', () => {
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {
|
||||
provideDefinition: () => {
|
||||
return currLocations
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should get definitions', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]
|
||||
let res = await locations.definitions()
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should jump to definitions', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0)]
|
||||
let res = await locations.gotoDefinition('edit')
|
||||
expect(res).toBe(true)
|
||||
let name = await nvim.call('bufname', ['%'])
|
||||
expect(name).toBe('test://foo')
|
||||
})
|
||||
|
||||
it('should return false when definitions not found', async () => {
|
||||
currLocations = []
|
||||
let res = await locations.gotoDefinition('edit')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('declaration', () => {
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerDeclarationProvider([{ language: '*' }], {
|
||||
provideDeclaration: () => {
|
||||
return currLocations
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should get declarations', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]
|
||||
let res = await locations.declarations() as Location[]
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should jump to declaration', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0)]
|
||||
let res = await locations.gotoDeclaration('edit')
|
||||
expect(res).toBe(true)
|
||||
let name = await nvim.call('bufname', ['%'])
|
||||
expect(name).toBe('test://foo')
|
||||
})
|
||||
|
||||
it('should return false when declaration not found', async () => {
|
||||
currLocations = []
|
||||
let res = await locations.gotoDeclaration('edit')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('typeDefinition', () => {
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerTypeDefinitionProvider([{ language: '*' }], {
|
||||
provideTypeDefinition: () => {
|
||||
return currLocations
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should get type definition', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]
|
||||
let res = await locations.typeDefinitions() as Location[]
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should jump to type definition', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0)]
|
||||
let res = await locations.gotoTypeDefinition('edit')
|
||||
expect(res).toBe(true)
|
||||
let name = await nvim.call('bufname', ['%'])
|
||||
expect(name).toBe('test://foo')
|
||||
})
|
||||
|
||||
it('should return false when type definition not found', async () => {
|
||||
currLocations = []
|
||||
let res = await locations.gotoTypeDefinition('edit')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('implementation', () => {
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerImplementationProvider([{ language: '*' }], {
|
||||
provideImplementation: () => {
|
||||
return currLocations
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
it('should get implementations', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]
|
||||
let res = await locations.implementations() as Location[]
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should jump to implementation', async () => {
|
||||
currLocations = [createLocation('foo', 0, 0, 0, 0)]
|
||||
let res = await locations.gotoImplementation('edit')
|
||||
expect(res).toBe(true)
|
||||
let name = await nvim.call('bufname', ['%'])
|
||||
expect(name).toBe('test://foo')
|
||||
})
|
||||
|
||||
it('should return false when implementation not found', async () => {
|
||||
currLocations = []
|
||||
let res = await locations.gotoImplementation('edit')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTagList', () => {
|
||||
it('should return null when cword does not exist', async () => {
|
||||
let res = await locations.getTagList()
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return null when provider does not exist', async () => {
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! ^')
|
||||
let res = await locations.getTagList()
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return null when result is empty', async () => {
|
||||
disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {
|
||||
provideDefinition: () => {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! ^')
|
||||
let res = await locations.getTagList()
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return tag definitions', async () => {
|
||||
disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {
|
||||
provideDefinition: () => {
|
||||
return [createLocation('bar', 2, 0, 2, 5), Location.create(URI.file('/foo').toString(), Range.create(1, 0, 1, 5))]
|
||||
}
|
||||
}))
|
||||
await nvim.setLine('foo')
|
||||
await nvim.command('normal! ^')
|
||||
let res = await locations.getTagList()
|
||||
expect(res).toEqual([
|
||||
{
|
||||
name: 'foo',
|
||||
cmd: 'keepjumps 3 | normal 1|',
|
||||
filename: 'test://bar'
|
||||
},
|
||||
{ name: 'foo', cmd: 'keepjumps 2 | normal 1|', filename: '/foo' }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('findLocations', () => {
|
||||
// hook result
|
||||
let fn
|
||||
let result: any
|
||||
beforeAll(() => {
|
||||
fn = services.sendRequest
|
||||
services.sendRequest = () => {
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
services.sendRequest = fn
|
||||
})
|
||||
|
||||
it('should handle locations from language client', async () => {
|
||||
result = [createLocation('bar', 2, 0, 2, 5)]
|
||||
await locations.findLocations('foo', 'mylocation', {}, false)
|
||||
let res = await nvim.getVar('coc_jump_locations')
|
||||
expect(res).toEqual([{
|
||||
uri: 'test://bar',
|
||||
lnum: 3,
|
||||
end_lnum: 3,
|
||||
col: 1,
|
||||
end_col: 6,
|
||||
filename: 'test://bar',
|
||||
text: '',
|
||||
range: Range.create(2, 0, 2, 5)
|
||||
}])
|
||||
})
|
||||
|
||||
it('should handle nested locations', async () => {
|
||||
let location: any = {
|
||||
location: createLocation('file', 0, 0, 0, 0),
|
||||
children: [{
|
||||
location: createLocation('foo', 3, 0, 3, 5),
|
||||
children: []
|
||||
}, {
|
||||
location: createLocation('bar', 4, 0, 4, 5),
|
||||
children: []
|
||||
}]
|
||||
}
|
||||
result = location
|
||||
await locations.findLocations('foo', 'mylocation', {}, false)
|
||||
let res = await nvim.getVar('coc_jump_locations') as any[]
|
||||
expect(res.length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('handleLocations', () => {
|
||||
it('should not throw when location is undefined', async () => {
|
||||
await locations.handleLocations(null)
|
||||
})
|
||||
|
||||
it('should not throw when locations is empty array', async () => {
|
||||
await locations.handleLocations([])
|
||||
})
|
||||
|
||||
it('should handle single location', async () => {
|
||||
await locations.handleLocations(createLocation('single', 0, 0, 0, 0))
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toBe('test://single')
|
||||
})
|
||||
|
||||
it('should handle location link', async () => {
|
||||
let link = LocationLink.create('test://link', Range.create(0, 0, 0, 3), Range.create(1, 0, 1, 3))
|
||||
await locations.handleLocations([link])
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toBe('test://link')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,467 +0,0 @@
|
|||
import { Buffer, Neovim } from '@chemzqm/neovim'
|
||||
import { CodeAction, CodeActionKind, Disposable, DocumentSymbol, Range, SymbolKind, SymbolTag, TextEdit } from 'vscode-languageserver-protocol'
|
||||
import events from '../../events'
|
||||
import Symbols from '../../handler/symbols/index'
|
||||
import languages from '../../languages'
|
||||
import { ProviderResult } from '../../provider'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
import Parser from './parser'
|
||||
|
||||
let nvim: Neovim
|
||||
let symbols: Symbols
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
symbols = helper.plugin.getHandler().symbols
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerDocumentSymbolProvider([{ language: 'javascript' }], {
|
||||
provideDocumentSymbols: document => {
|
||||
let content = document.getText()
|
||||
let showDetail = content.includes('detail')
|
||||
let parser = new Parser(content, showDetail)
|
||||
let res: DocumentSymbol[] = parser.parse()
|
||||
if (res.length) {
|
||||
res[0].tags = [SymbolTag.Deprecated]
|
||||
}
|
||||
return Promise.resolve(res)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
await helper.reset()
|
||||
await nvim.command(`let w:cocViewId = ''`)
|
||||
|
||||
})
|
||||
|
||||
async function getOutlineBuffer(): Promise<Buffer | undefined> {
|
||||
let winid = await nvim.call('coc#window#find', ['cocViewId', 'OUTLINE'])
|
||||
if (winid == -1) return undefined
|
||||
let bufnr = await nvim.call('winbufnr', [winid])
|
||||
if (bufnr == -1) return undefined
|
||||
return nvim.createBuffer(bufnr)
|
||||
}
|
||||
|
||||
describe('symbols outline', () => {
|
||||
|
||||
let defaultCode = `class myClass {
|
||||
fun1() { }
|
||||
fun2() {}
|
||||
}`
|
||||
|
||||
async function createBuffer(code = defaultCode): Promise<Buffer> {
|
||||
await helper.edit()
|
||||
let buf = await nvim.buffer
|
||||
await nvim.command('setf javascript')
|
||||
await buf.setLines(code.split('\n'), { start: 0, end: -1, strictIndexing: false })
|
||||
let doc = await workspace.document
|
||||
await doc.synchronize()
|
||||
return buf
|
||||
}
|
||||
|
||||
describe('configuration', () => {
|
||||
it('should follow cursor', async () => {
|
||||
await createBuffer()
|
||||
let curr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(0)
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await nvim.command('wincmd p')
|
||||
await nvim.command('exe 3')
|
||||
await events.fire('CursorHold', [curr])
|
||||
await helper.wait(50)
|
||||
let buf = nvim.createBuffer(bufnr)
|
||||
let lines = await buf.getLines()
|
||||
expect(lines.slice(1)).toEqual([
|
||||
'- c myClass 1', ' m fun1 2', ' m fun2 3'
|
||||
])
|
||||
let signs = await buf.getSigns({ group: 'CocTree' })
|
||||
expect(signs.length).toBe(1)
|
||||
expect(signs[0]).toEqual({
|
||||
lnum: 2,
|
||||
id: 3001,
|
||||
name: 'CocTreeSelected',
|
||||
priority: 10,
|
||||
group: 'CocTree'
|
||||
})
|
||||
})
|
||||
|
||||
it('should not follow cursor', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.followCursor': false,
|
||||
})
|
||||
await createBuffer()
|
||||
let curr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(0)
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await nvim.command('wincmd p')
|
||||
await nvim.command('exe 3')
|
||||
await events.fire('CursorHold', [curr])
|
||||
await helper.wait(50)
|
||||
let buf = nvim.createBuffer(bufnr)
|
||||
let signs = await buf.getSigns({ group: 'CocTree' })
|
||||
expect(signs.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should keep current window', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.keepWindow': true,
|
||||
})
|
||||
await createBuffer()
|
||||
let curr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline()
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(curr).toBe(bufnr)
|
||||
})
|
||||
|
||||
it('should check on buffer switch', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.checkBufferSwitch': true,
|
||||
})
|
||||
await createBuffer()
|
||||
await symbols.showOutline(1)
|
||||
await helper.edit('unnamed')
|
||||
await helper.wait(200)
|
||||
let buf = await getOutlineBuffer()
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toMatch('Document symbol provider not found')
|
||||
})
|
||||
|
||||
it('should not check on buffer switch', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.checkBufferSwitch': false
|
||||
})
|
||||
await helper.wait(30)
|
||||
await createBuffer()
|
||||
await symbols.showOutline(1)
|
||||
await helper.edit('unnamed')
|
||||
await helper.wait(100)
|
||||
let buf = await getOutlineBuffer()
|
||||
let lines = await buf.lines
|
||||
expect(lines.slice(1)).toEqual([
|
||||
'- c myClass 1', ' m fun1 2', ' m fun2 3'
|
||||
])
|
||||
})
|
||||
|
||||
it('should not check on buffer reload', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.checkBufferSwitch': false
|
||||
})
|
||||
await symbols.showOutline(1)
|
||||
await helper.wait(50)
|
||||
await createBuffer()
|
||||
await helper.wait(50)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeDefined()
|
||||
})
|
||||
|
||||
it('should sort by position', async () => {
|
||||
let code = `class myClass {
|
||||
fun2() { }
|
||||
fun1() {}
|
||||
}`
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.sortBy': 'position',
|
||||
})
|
||||
await createBuffer(code)
|
||||
await symbols.showOutline(1)
|
||||
let buf = await getOutlineBuffer()
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTLINE Position', '- c myClass 1', ' m fun2 2', ' m fun1 3'
|
||||
])
|
||||
})
|
||||
|
||||
it('should sort by name', async () => {
|
||||
let code = `class myClass {
|
||||
fun2() {}
|
||||
fun1() {}
|
||||
}`
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.sortBy': 'name',
|
||||
})
|
||||
await createBuffer(code)
|
||||
await symbols.showOutline(1)
|
||||
let buf = await getOutlineBuffer()
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'OUTLINE Name', '- c myClass 1', ' m fun1 3', ' m fun2 2'
|
||||
])
|
||||
})
|
||||
|
||||
it('should change sort method', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.detailAsDescription': false
|
||||
})
|
||||
let code = `class detail {
|
||||
fun2() {}
|
||||
fun1() {}
|
||||
}`
|
||||
await createBuffer(code)
|
||||
await symbols.showOutline(0)
|
||||
await helper.wait(30)
|
||||
await nvim.input('<C-s>')
|
||||
await helper.waitFloat()
|
||||
await nvim.input('<esc>')
|
||||
await helper.wait(30)
|
||||
await nvim.input('<C-s>')
|
||||
await helper.waitFloat()
|
||||
await nvim.input('3')
|
||||
await helper.waitFor('getline', [1], 'OUTLINE Position')
|
||||
})
|
||||
|
||||
it('should show detail as description', async () => {
|
||||
workspace.configurations.updateUserConfig({
|
||||
'outline.detailAsDescription': true
|
||||
})
|
||||
let code = `class detail {
|
||||
fun2() {}
|
||||
}`
|
||||
await createBuffer(code)
|
||||
await symbols.showOutline(1)
|
||||
let buf = await getOutlineBuffer()
|
||||
let lines = await buf.lines
|
||||
expect(lines.slice(1)).toEqual([
|
||||
'- c detail 1', ' m fun2 () 2'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('events', () => {
|
||||
|
||||
it('should not close TreeView on buffer reload', async () => {
|
||||
await createBuffer()
|
||||
await symbols.showOutline(0)
|
||||
await nvim.command('edit')
|
||||
await helper.wait(30)
|
||||
let winid = await nvim.call('coc#window#find', ['cocViewId', 'OUTLINE'])
|
||||
expect(winid).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should dispose on buffer unload', async () => {
|
||||
await createBuffer()
|
||||
let curr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(0)
|
||||
await nvim.command('tabe')
|
||||
await nvim.command(`bd! ${curr}`)
|
||||
await helper.wait(30)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should check current window on BufEnter', async () => {
|
||||
await createBuffer()
|
||||
await symbols.showOutline(1)
|
||||
let winid = await nvim.call('win_getid', [])
|
||||
await nvim.command('enew')
|
||||
await helper.wait(100)
|
||||
let win = await nvim.window
|
||||
expect(win.id).toBe(winid)
|
||||
})
|
||||
|
||||
it('should recreated when original window exists', async () => {
|
||||
await symbols.showOutline(1)
|
||||
await helper.wait(50)
|
||||
await createBuffer()
|
||||
await helper.wait(50)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeDefined()
|
||||
})
|
||||
|
||||
it('should keep old outline when new buffer not attached', async () => {
|
||||
await createBuffer()
|
||||
await symbols.showOutline(1)
|
||||
await nvim.command(`vnew +setl\\ buftype=nofile`)
|
||||
await helper.wait(50)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeDefined()
|
||||
let lines = await buf.lines
|
||||
expect(lines.slice(1)).toEqual([
|
||||
'- c myClass 1', ' m fun1 2', ' m fun2 3'
|
||||
])
|
||||
})
|
||||
|
||||
it('should not reload when switch to original buffer', async () => {
|
||||
await createBuffer()
|
||||
await symbols.showOutline(0)
|
||||
let buf = await getOutlineBuffer()
|
||||
let name = await buf.name
|
||||
await nvim.command('wincmd p')
|
||||
await helper.wait(50)
|
||||
buf = await getOutlineBuffer()
|
||||
let curr = await buf.name
|
||||
expect(curr).toBe(name)
|
||||
})
|
||||
})
|
||||
|
||||
describe('show()', () => {
|
||||
it('should not throw when document not attached', async () => {
|
||||
await nvim.command(`edit +setl\\ buftype=nofile t`)
|
||||
await workspace.document
|
||||
await symbols.showOutline(1)
|
||||
})
|
||||
|
||||
it('should not throw when provider does not exist', async () => {
|
||||
await symbols.showOutline(1)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not throw when symbols is empty', async () => {
|
||||
await createBuffer('')
|
||||
await symbols.showOutline(1)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeDefined()
|
||||
})
|
||||
|
||||
it('should jump to selected symbol', async () => {
|
||||
await createBuffer()
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(0)
|
||||
await helper.waitFor('getline', [3], ' m fun1 2')
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<cr>')
|
||||
await helper.wait(50)
|
||||
let curr = await nvim.call('bufnr', ['%'])
|
||||
expect(curr).toBe(bufnr)
|
||||
let cursor = await nvim.call('coc#cursor#position')
|
||||
expect(cursor).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('should update symbols', async () => {
|
||||
await createBuffer()
|
||||
let doc = await workspace.document
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(1)
|
||||
await helper.wait(10)
|
||||
let buf = nvim.createBuffer(bufnr)
|
||||
let code = 'class foo{}'
|
||||
await buf.setLines(code.split('\n'), {
|
||||
start: 0,
|
||||
end: -1,
|
||||
strictIndexing: false
|
||||
})
|
||||
await doc.synchronize()
|
||||
buf = await getOutlineBuffer()
|
||||
await helper.waitFor('eval', [`getbufline(${buf.id},1)[0]`], /No\sresults/)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual([
|
||||
'No results',
|
||||
'',
|
||||
'OUTLINE Category'
|
||||
])
|
||||
})
|
||||
|
||||
it('should show label in description', async () => {
|
||||
disposables.push(languages.registerDocumentSymbolProvider([{ language: 'vim' }], {
|
||||
meta: {
|
||||
label: 'vimlsp'
|
||||
},
|
||||
provideDocumentSymbols: _ => {
|
||||
let res: DocumentSymbol[] = [{
|
||||
name: 'let',
|
||||
range: Range.create(0, 0, 0, 3),
|
||||
kind: SymbolKind.Constant,
|
||||
selectionRange: Range.create(0, 0, 0, 3),
|
||||
tags: [SymbolTag.Deprecated]
|
||||
}]
|
||||
return Promise.resolve(res)
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('t.vim')
|
||||
await nvim.command('setf vim')
|
||||
let buf = await nvim.buffer
|
||||
await buf.setLines(['let'], { start: 0, end: -1, strictIndexing: false })
|
||||
await doc.synchronize()
|
||||
await symbols.showOutline(0)
|
||||
await helper.waitFor('getline', [1], 'OUTLINE vimlsp')
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions', () => {
|
||||
it('should invoke visual select', async () => {
|
||||
await createBuffer()
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(0)
|
||||
await helper.waitFor('getline', [3], /fun1/)
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.waitFloat()
|
||||
await nvim.input('<cr>')
|
||||
await helper.waitFor('mode', [], 'v')
|
||||
let buf = await nvim.buffer
|
||||
expect(buf.id).toBe(bufnr)
|
||||
})
|
||||
|
||||
it('should invoke selected code action', async () => {
|
||||
const codeAction = CodeAction.create('my action', CodeActionKind.Refactor)
|
||||
let uri: string
|
||||
disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {
|
||||
provideCodeActions: () => [codeAction],
|
||||
resolveCodeAction: (action): ProviderResult<CodeAction> => {
|
||||
action.edit = {
|
||||
changes: {
|
||||
[uri]: [TextEdit.del(Range.create(0, 0, 0, 5))]
|
||||
}
|
||||
}
|
||||
return action
|
||||
}
|
||||
}, undefined))
|
||||
await createBuffer()
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
let doc = workspace.getDocument(bufnr)
|
||||
uri = doc.uri
|
||||
await symbols.showOutline(0)
|
||||
await helper.wait(200)
|
||||
await nvim.command('exe 3')
|
||||
await nvim.input('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('<cr>')
|
||||
await helper.wait(200)
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toBe(' myClass {')
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide()', () => {
|
||||
it('should hide outline', async () => {
|
||||
await createBuffer('')
|
||||
await symbols.showOutline(1)
|
||||
await helper.wait(50)
|
||||
await symbols.hideOutline()
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should not throw when outline does not exist', async () => {
|
||||
await symbols.hideOutline()
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose provider and views', async () => {
|
||||
await createBuffer('')
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await symbols.showOutline(1)
|
||||
symbols.dispose()
|
||||
await helper.wait(50)
|
||||
expect(symbols.hasOutline(bufnr)).toBe(false)
|
||||
let buf = await getOutlineBuffer()
|
||||
expect(buf).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,118 +0,0 @@
|
|||
import { DocumentSymbol, Range, SymbolKind } from 'vscode-languageserver-protocol'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
|
||||
/**
|
||||
* A syntax parser that parse `class` and `method` only.
|
||||
*/
|
||||
export default class Parser {
|
||||
private _curr = 0
|
||||
private _symbols: DocumentSymbol[] = []
|
||||
private currSymbol: DocumentSymbol | undefined
|
||||
private len: number
|
||||
private textDocument: TextDocument
|
||||
constructor(private _content: string, private showDetail = false) {
|
||||
this.len = _content.length
|
||||
this.textDocument = TextDocument.create('test:///a', 'txt', 1, _content)
|
||||
}
|
||||
|
||||
public parse(): DocumentSymbol[] {
|
||||
while (this._curr <= this.len - 1) {
|
||||
this.parseToken()
|
||||
}
|
||||
return this._symbols
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a symbol, reset currSymbol & _curr
|
||||
*/
|
||||
private parseToken(): void {
|
||||
this.skipSpaces()
|
||||
if (this.currSymbol) {
|
||||
let endOffset = this.textDocument.offsetAt(this.currSymbol.range.end)
|
||||
if (this._curr > endOffset) {
|
||||
this.currSymbol = undefined
|
||||
}
|
||||
}
|
||||
let remain = this.getLineRemain()
|
||||
let ms = remain.match(/^(class)\s(\w+)\s\{\s*/)
|
||||
if (ms) {
|
||||
// find class
|
||||
let start = this._curr + 6
|
||||
let end = start + ms[2].length
|
||||
let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))
|
||||
let endPosition = this.findMatchedIndex(this._curr + ms[0].length)
|
||||
let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))
|
||||
let symbolInfo: DocumentSymbol = {
|
||||
range,
|
||||
selectionRange,
|
||||
kind: SymbolKind.Class,
|
||||
name: ms[2],
|
||||
children: []
|
||||
}
|
||||
if (this.currSymbol && this.currSymbol.children) {
|
||||
this.currSymbol.children.push(symbolInfo)
|
||||
} else {
|
||||
this._symbols.push(symbolInfo)
|
||||
}
|
||||
this.currSymbol = symbolInfo
|
||||
} else if (this.currSymbol && this.currSymbol.kind == SymbolKind.Class) {
|
||||
let ms = remain.match(/(\w+)\((.*)\)\s*\{/)
|
||||
if (ms) {
|
||||
// find method
|
||||
let start = this._curr
|
||||
let end = start + ms[1].length
|
||||
let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))
|
||||
let endPosition = this.findMatchedIndex(this._curr + ms[0].length)
|
||||
let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))
|
||||
let symbolInfo: DocumentSymbol = {
|
||||
range,
|
||||
selectionRange,
|
||||
kind: SymbolKind.Method,
|
||||
detail: this.showDetail ? `(${ms[2]})` : undefined,
|
||||
name: ms[1]
|
||||
}
|
||||
if (this.currSymbol && this.currSymbol.children) {
|
||||
this.currSymbol.children.push(symbolInfo)
|
||||
} else {
|
||||
this._symbols.push(symbolInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
this._curr = this._curr + remain.length + 1
|
||||
}
|
||||
|
||||
private findMatchedIndex(start: number): number {
|
||||
let level = 0
|
||||
for (let i = start; i < this.len; i++) {
|
||||
let ch = this._content[i]
|
||||
if (ch == '{') {
|
||||
level = level + 1
|
||||
}
|
||||
if (ch == '}') {
|
||||
if (level == 0) return i
|
||||
level = level - 1
|
||||
}
|
||||
}
|
||||
throw new Error(`Can't find matched }`)
|
||||
}
|
||||
|
||||
private getLineRemain(): string {
|
||||
let chars = ''
|
||||
for (let i = this._curr; i < this.len; i++) {
|
||||
let ch = this._content[i]
|
||||
if (ch == '\n') break
|
||||
chars = chars + ch
|
||||
}
|
||||
return chars
|
||||
}
|
||||
|
||||
private skipSpaces(): void {
|
||||
for (let i = this._curr; i < this.len; i++) {
|
||||
let ch = this._content[i]
|
||||
if (!ch || /\S/.test(ch)) {
|
||||
this._curr = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,677 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from '@chemzqm/neovim/lib/api/Buffer'
|
||||
import fs from 'fs'
|
||||
import { Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'
|
||||
import { URI } from 'vscode-uri'
|
||||
import RefactorBuffer, { FileItemDef, fixChangeParams } from '../../handler/refactor/buffer'
|
||||
import Changes from '../../handler/refactor/changes'
|
||||
import Refactor from '../../handler/refactor/index'
|
||||
import languages from '../../languages'
|
||||
import { DidChangeTextDocumentParams } from '../../types'
|
||||
import workspace from '../../workspace'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let refactor: Refactor
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
refactor = helper.plugin.getHandler().refactor
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
refactor.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
function createEdit(uri: string): WorkspaceEdit {
|
||||
let edit = TextEdit.insert(Position.create(0, 0), 'a')
|
||||
let doc = { uri, version: null }
|
||||
return { documentChanges: [TextDocumentEdit.create(doc, [edit])] }
|
||||
}
|
||||
|
||||
// assert ranges is expected.
|
||||
async function assertSynchronized(buf: RefactorBuffer) {
|
||||
let buffer = nvim.createBuffer(buf.bufnr)
|
||||
let lines = await buffer.lines
|
||||
let items: { lnum: number, lines: string[] }[] = []
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i]
|
||||
if (line.includes('\u3000') && line.length > 1) {
|
||||
items.push({ lnum: i + 1, lines: [] })
|
||||
}
|
||||
}
|
||||
let curr: { lnum: number, lines: string[] }[] = []
|
||||
buf.fileItems.forEach(item => {
|
||||
item.ranges.forEach(r => {
|
||||
curr.push({ lnum: r.lnum, lines: [] })
|
||||
})
|
||||
})
|
||||
curr.sort((a, b) => a.lnum - b.lnum)
|
||||
expect(items).toEqual(curr)
|
||||
}
|
||||
|
||||
describe('fixChangeParams', () => {
|
||||
function createChangeParams(range: Range, text: string, original: string, originalLines: ReadonlyArray<string>): DidChangeTextDocumentParams {
|
||||
return {
|
||||
textDocument: {
|
||||
uri: 'untitled:/1',
|
||||
version: 1,
|
||||
},
|
||||
originalLines,
|
||||
original,
|
||||
bufnr: 1,
|
||||
contentChanges: [{ range, text }]
|
||||
}
|
||||
}
|
||||
|
||||
it('should fix delete change params', async () => {
|
||||
let e = createChangeParams(Range.create(0, 4, 2, 4), '', 'x\nfoo\n\u3000bar', [
|
||||
'\u3000barx',
|
||||
'foo',
|
||||
'\u3000bara'
|
||||
])
|
||||
e = fixChangeParams(e)
|
||||
expect(e.original).toBe('\u3000barx\nfoo\n')
|
||||
expect(e.contentChanges[0].range).toEqual(Range.create(0, 0, 2, 0))
|
||||
})
|
||||
|
||||
it('should fix insert change params', async () => {
|
||||
let e = createChangeParams(Range.create(0, 4, 0, 4), 'x\nfoo\n\u3000bar', '', [
|
||||
'\u3000bara'
|
||||
])
|
||||
e = fixChangeParams(e)
|
||||
expect(e.original).toBe('')
|
||||
let change = e.contentChanges[0]
|
||||
expect(change.range).toEqual(Range.create(0, 0, 0, 0))
|
||||
expect(change.text).toBe('\u3000barx\nfoo\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('refactor', () => {
|
||||
describe('checkInsert()', () => {
|
||||
it('should check inserted ranges', async () => {
|
||||
let c = new Changes()
|
||||
expect(c.checkInsert([1])).toBeUndefined()
|
||||
c.add([{ filepath: __filename, start: 1, lnum: 1, lines: [''] }])
|
||||
expect(c.checkInsert([2])).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFileRange()', () => {
|
||||
it('should throw when range does not exist', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let locations = [{ uri, range: Range.create(0, 0, 0, 6) }]
|
||||
let buf = await refactor.fromLocations(locations)
|
||||
let fn = () => {
|
||||
buf.getFileRange(1)
|
||||
}
|
||||
expect(fn).toThrow(Error)
|
||||
})
|
||||
|
||||
it('should find file range', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let locations = [{ uri, range: Range.create(0, 0, 0, 6) }]
|
||||
let buf = await refactor.fromLocations(locations)
|
||||
let res = buf.getFileRange(4)
|
||||
expect(res).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getRange()', () => {
|
||||
it('should get delete range', async () => {
|
||||
let filename = await createTmpFile('foo\n\nbar\n')
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: filename,
|
||||
ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]
|
||||
}
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
await buf.addFileItems([fileItem])
|
||||
let res = buf.getFileRange(4)
|
||||
let r = buf.getDeleteRange(res)
|
||||
expect(r).toEqual(Range.create(3, 0, 6, 0))
|
||||
res = buf.getFileRange(7)
|
||||
r = buf.getDeleteRange(res)
|
||||
expect(r).toEqual(Range.create(6, 0, 8, 0))
|
||||
})
|
||||
|
||||
it('should get replace range', async () => {
|
||||
let filename = await createTmpFile('foo\n\nbar\n')
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: filename,
|
||||
ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]
|
||||
}
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
await buf.addFileItems([fileItem])
|
||||
let res = buf.getFileRange(4)
|
||||
let r = buf.getReplaceRange(res)
|
||||
expect(r).toEqual(Range.create(4, 0, 4, 3))
|
||||
res = buf.getFileRange(7)
|
||||
r = buf.getReplaceRange(res)
|
||||
expect(r).toEqual(Range.create(7, 0, 7, 3))
|
||||
})
|
||||
})
|
||||
|
||||
describe('fromWorkspaceEdit()', () => {
|
||||
it('should not create from invalid workspaceEdit', async () => {
|
||||
let res = await refactor.fromWorkspaceEdit(undefined)
|
||||
expect(res).toBeUndefined()
|
||||
res = await refactor.fromWorkspaceEdit({ documentChanges: [] })
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should create from document changes', async () => {
|
||||
let edit = createEdit(URI.file(__filename).toString())
|
||||
let buf = await refactor.fromWorkspaceEdit(edit)
|
||||
let shown = await buf.valid
|
||||
expect(shown).toBe(true)
|
||||
let items = buf.fileItems
|
||||
expect(items.length).toBe(1)
|
||||
await nvim.command(`bd! ${buf.bufnr}`)
|
||||
await helper.wait(30)
|
||||
let has = refactor.has(buf.bufnr)
|
||||
expect(has).toBe(false)
|
||||
})
|
||||
|
||||
it('should create from workspaceEdit', async () => {
|
||||
let changes = {
|
||||
[URI.file(__filename).toString()]: [{
|
||||
range: Range.create(0, 0, 0, 6),
|
||||
newText: ''
|
||||
}, {
|
||||
range: Range.create(1, 0, 1, 6),
|
||||
newText: ''
|
||||
}, {
|
||||
range: Range.create(50, 0, 50, 1),
|
||||
newText: ' '
|
||||
}, {
|
||||
range: Range.create(60, 0, 60, 1),
|
||||
newText: ' '
|
||||
}]
|
||||
}
|
||||
let edit: WorkspaceEdit = { changes }
|
||||
let buf = await refactor.fromWorkspaceEdit(edit)
|
||||
let shown = await buf.valid
|
||||
expect(shown).toBe(true)
|
||||
let items = buf.fileItems
|
||||
expect(items.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fromLocations()', () => {
|
||||
it('should create from locations', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let locations = [{
|
||||
uri,
|
||||
range: Range.create(0, 0, 0, 6),
|
||||
}, {
|
||||
uri,
|
||||
range: Range.create(1, 0, 1, 6),
|
||||
}]
|
||||
let buf = await refactor.fromLocations(locations)
|
||||
let shown = await buf.valid
|
||||
expect(shown).toBe(true)
|
||||
let items = buf.fileItems
|
||||
expect(items.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should not create from empty locations', async () => {
|
||||
let buf = await refactor.fromLocations([])
|
||||
expect(buf).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('onChange()', () => {
|
||||
async function setup(): Promise<RefactorBuffer> {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let locations = [{
|
||||
uri,
|
||||
range: Range.create(0, 0, 0, 6),
|
||||
}, {
|
||||
uri,
|
||||
range: Range.create(1, 0, 1, 6),
|
||||
}, {
|
||||
uri,
|
||||
range: Range.create(10, 0, 10, 6),
|
||||
}]
|
||||
return await refactor.fromLocations(locations)
|
||||
}
|
||||
|
||||
it('should refresh on empty text change', async () => {
|
||||
let buf = await setup()
|
||||
let line = await nvim.call('getline', [4])
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
await nvim.call('setline', [4, line])
|
||||
doc._forceSync()
|
||||
let srcId = await nvim.createNamespace('coc-refactor')
|
||||
let markers = await helper.getMarkers(doc.bufnr, srcId)
|
||||
expect(markers.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should detect range delete and undo', async () => {
|
||||
let buf = await setup()
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
let r = buf.getFileRange(4)
|
||||
let end = r.lnum + r.lines.length
|
||||
await nvim.command(`${r.lnum},${end + 1}d`)
|
||||
await doc.synchronize()
|
||||
await assertSynchronized(buf)
|
||||
await nvim.command('undo')
|
||||
await doc.synchronize()
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
|
||||
it('should detect normal delete', async () => {
|
||||
let buf = await setup()
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
let r = buf.getFileRange(4)
|
||||
await nvim.command(`${r.lnum + 1},${r.lnum + 1}d`)
|
||||
await doc.synchronize()
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
|
||||
it('should detect insert', async () => {
|
||||
let buf = await setup()
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
let buffer = nvim.createBuffer(buf.bufnr)
|
||||
await buffer.append(['foo'])
|
||||
await doc.synchronize()
|
||||
await assertSynchronized(buf)
|
||||
await buffer.append(['foo', '\u3000'])
|
||||
await doc.synchronize()
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
})
|
||||
|
||||
describe('onDocumentChange()', () => {
|
||||
it('should ignore when change after range', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.append(['foo', 'bar'])
|
||||
await doc.synchronize()
|
||||
let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(0, 0, 0, 3) }])
|
||||
let lines = await nvim.call('getline', [1, '$'])
|
||||
await doc.buffer.append(['def'])
|
||||
await doc.synchronize()
|
||||
let newLines = await nvim.call('getline', [1, '$'])
|
||||
expect(lines).toEqual(newLines)
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
|
||||
it('should adjust when change before range', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.append(['', '', '', '', 'foo', 'bar'])
|
||||
await doc.synchronize()
|
||||
let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])
|
||||
await doc.buffer.setLines(['def'], { start: 0, end: 0, strictIndexing: false })
|
||||
await doc.synchronize()
|
||||
let fileRange = buf.getFileRange(4)
|
||||
expect(fileRange.start).toBe(2)
|
||||
expect(fileRange.lines.length).toBe(6)
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
|
||||
it('should remove ranges when lines empty', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.append(['', '', '', '', 'foo', 'bar'])
|
||||
await doc.synchronize()
|
||||
let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])
|
||||
await doc.buffer.setLines([], { start: 0, end: -1, strictIndexing: false })
|
||||
await doc.synchronize()
|
||||
let lines = await nvim.call('getline', [1, '$'])
|
||||
expect(lines.length).toBe(3)
|
||||
let items = buf.fileItems
|
||||
expect(items.length).toBe(0)
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
|
||||
it('should change when liens changed', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.append(['', '', '', '', 'foo', 'bar'])
|
||||
await doc.synchronize()
|
||||
let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])
|
||||
await doc.buffer.setLines(['def', 'def'], { start: 5, end: 6, strictIndexing: false })
|
||||
await doc.synchronize()
|
||||
let lines = await nvim.call('getline', [1, '$'])
|
||||
expect(lines[lines.length - 2]).toBe('def')
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getFileChanges()', () => {
|
||||
it('should get changes #1', async () => {
|
||||
await helper.createDocument()
|
||||
let lines = `
|
||||
Save current buffer to make changes
|
||||
\u3000
|
||||
\u3000
|
||||
\u3000/a.ts
|
||||
})
|
||||
} `
|
||||
let buf = await refactor.fromLines(lines.split('\n'))
|
||||
let changes = await buf.getFileChanges()
|
||||
expect(changes).toEqual([{ lnum: 5, filepath: '/a.ts', lines: [' })', ' } '] }])
|
||||
})
|
||||
|
||||
it('should get changes #2', async () => {
|
||||
let lines = `
|
||||
\u3000/a.ts
|
||||
})
|
||||
} `
|
||||
let buf = await refactor.fromLines(lines.split('\n'))
|
||||
let changes = await buf.getFileChanges()
|
||||
expect(changes).toEqual([{ lnum: 2, filepath: '/a.ts', lines: [' })', ' } '] }])
|
||||
})
|
||||
|
||||
it('should get changes #3', async () => {
|
||||
let lines = `
|
||||
\u3000/a.ts
|
||||
})
|
||||
}
|
||||
\u3000`
|
||||
let buf = await refactor.fromLines(lines.split('\n'))
|
||||
let changes = await buf.getFileChanges()
|
||||
expect(changes).toEqual([{ lnum: 2, filepath: '/a.ts', lines: [' })', ' }'] }])
|
||||
})
|
||||
|
||||
it('should get changes #4', async () => {
|
||||
let lines = `
|
||||
\u3000/a.ts
|
||||
foo
|
||||
\u3000/b.ts
|
||||
bar
|
||||
\u3000`
|
||||
let buf = await refactor.fromLines(lines.split('\n'))
|
||||
let changes = await buf.getFileChanges()
|
||||
expect(changes).toEqual([
|
||||
{ filepath: '/a.ts', lnum: 2, lines: ['foo'] },
|
||||
{ filepath: '/b.ts', lnum: 4, lines: ['bar'] }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('createRefactorBuffer()', () => {
|
||||
it('should create refactor buffer', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let curr = await nvim.call('win_getid')
|
||||
expect(curr).toBeGreaterThan(winid)
|
||||
let valid = await buf.valid
|
||||
expect(valid).toBe(true)
|
||||
buf = await refactor.createRefactorBuffer('vim')
|
||||
valid = await buf.valid
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it('should use conceal for line numbers', async () => {
|
||||
let buf = await refactor.createRefactorBuffer(undefined, true)
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]
|
||||
}
|
||||
await buf.addFileItems([fileItem])
|
||||
let arr = await nvim.call('getmatches') as any[]
|
||||
arr = arr.filter(o => o.group == 'Conceal')
|
||||
expect(arr.length).toBeGreaterThan(0)
|
||||
await buf.addFileItems([{
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 1, end: 3 }]
|
||||
}])
|
||||
await nvim.command('normal! ggdG')
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
await doc.synchronize()
|
||||
let b = nvim.createBuffer(buf.bufnr)
|
||||
let res = await b.getVar('line_infos')
|
||||
expect(res).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('splitOpen()', () => {
|
||||
async function setup(): Promise<RefactorBuffer> {
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]
|
||||
}
|
||||
await buf.addFileItems([fileItem])
|
||||
await nvim.call('cursor', [5, 1])
|
||||
return buf
|
||||
}
|
||||
|
||||
it('should jump to position by <CR>', async () => {
|
||||
let buf = await setup()
|
||||
await buf.splitOpen()
|
||||
let line = await nvim.eval('line(".")')
|
||||
let bufname = await nvim.eval('bufname("%")')
|
||||
expect(bufname).toMatch('refactor.test.ts')
|
||||
expect(line).toBe(11)
|
||||
})
|
||||
|
||||
it('should jump split window when original window not valid', async () => {
|
||||
let win = await nvim.window
|
||||
let buf = await setup()
|
||||
await nvim.call('nvim_win_close', [win.id, true])
|
||||
await buf.splitOpen()
|
||||
let line = await nvim.eval('line(".")')
|
||||
let bufname = await nvim.eval('bufname("%")')
|
||||
expect(bufname).toMatch('refactor.test.ts')
|
||||
expect(line).toBe(11)
|
||||
})
|
||||
})
|
||||
|
||||
describe('showMenu()', () => {
|
||||
async function setup(): Promise<RefactorBuffer> {
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]
|
||||
}
|
||||
await buf.addFileItems([fileItem])
|
||||
await nvim.call('cursor', [5, 1])
|
||||
return buf
|
||||
}
|
||||
|
||||
it('should do nothing when cancelled or range not found', async () => {
|
||||
let buf = await setup()
|
||||
let p = buf.showMenu()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<esc>')
|
||||
await p
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(bufnr).toBe(buf.bufnr)
|
||||
await nvim.call('cursor', [1, 1])
|
||||
p = buf.showMenu()
|
||||
await helper.wait(50)
|
||||
await nvim.input('1')
|
||||
await p
|
||||
bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(bufnr).toBe(buf.bufnr)
|
||||
})
|
||||
|
||||
it('should open file in new tab', async () => {
|
||||
let buf = await setup()
|
||||
await nvim.call('cursor', [4, 1])
|
||||
let p = buf.showMenu()
|
||||
await helper.wait(30)
|
||||
await nvim.input('1')
|
||||
await p
|
||||
let nr = await nvim.call('tabpagenr')
|
||||
expect(nr).toBe(2)
|
||||
let lnum = await nvim.call('line', ['.'])
|
||||
expect(lnum).toBe(11)
|
||||
})
|
||||
|
||||
it('should remove current block', async () => {
|
||||
let buf = await setup()
|
||||
await nvim.call('cursor', [4, 1])
|
||||
let p = buf.showMenu()
|
||||
await helper.wait(30)
|
||||
await nvim.input('2')
|
||||
await p
|
||||
let items = buf.fileItems
|
||||
expect(items[0].ranges.length).toBe(1)
|
||||
await assertSynchronized(buf)
|
||||
})
|
||||
})
|
||||
|
||||
describe('saveRefactor()', () => {
|
||||
it('should adjust line ranges after change', async () => {
|
||||
let filename = await createTmpFile('foo\n\nbar\n')
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: filename,
|
||||
ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]
|
||||
}
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
const getRanges = () => {
|
||||
let items = buf.fileItems
|
||||
let item = items.find(o => o.filepath == filename)
|
||||
return item.ranges.map(o => {
|
||||
return [o.start, o.start + o.lines.length]
|
||||
})
|
||||
}
|
||||
await buf.addFileItems([fileItem, {
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 1, end: 5 }]
|
||||
}])
|
||||
expect(getRanges()).toEqual([[0, 1], [2, 3]])
|
||||
nvim.pauseNotification()
|
||||
nvim.call('setline', [5, ['xyoo']], true)
|
||||
nvim.command('undojoin', true)
|
||||
nvim.call('append', [5, ['de']], true)
|
||||
nvim.command('undojoin', true)
|
||||
nvim.call('setline', [9, ['b']], true)
|
||||
await nvim.resumeNotification()
|
||||
let doc = workspace.getDocument(buf.bufnr)
|
||||
await doc.synchronize()
|
||||
let res = await refactor.save(buf.buffer.id)
|
||||
expect(res).toBe(true)
|
||||
expect(getRanges()).toEqual([[0, 2], [3, 4]])
|
||||
let content = fs.readFileSync(filename, 'utf8')
|
||||
expect(content).toBe('xyoo\nde\n\nb\n')
|
||||
})
|
||||
|
||||
it('should not save when no change made', async () => {
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: __filename,
|
||||
ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]
|
||||
}
|
||||
await buf.addFileItems([fileItem])
|
||||
let res = await buf.save()
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should sync buffer change to file', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.buffer.replace(['foo', 'bar', 'line'], 0)
|
||||
await helper.wait(30)
|
||||
let filename = URI.parse(doc.uri).fsPath
|
||||
let fileItem: FileItemDef = {
|
||||
filepath: filename,
|
||||
ranges: [{ start: 0, end: 2 }]
|
||||
}
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
await buf.addFileItems([fileItem])
|
||||
await nvim.call('setline', [5, 'changed'])
|
||||
let res = await buf.save()
|
||||
expect(res).toBe(true)
|
||||
expect(fs.existsSync(filename)).toBe(true)
|
||||
let content = fs.readFileSync(filename, 'utf8')
|
||||
let lines = content.split('\n')
|
||||
expect(lines).toEqual(['changed', 'bar', 'line', ''])
|
||||
fs.unlinkSync(filename)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doRefactor', () => {
|
||||
let disposable: Disposable
|
||||
|
||||
afterEach(() => {
|
||||
if (disposable) disposable.dispose()
|
||||
disposable = null
|
||||
})
|
||||
|
||||
it('should throw when rename provider not found', async () => {
|
||||
await helper.createDocument()
|
||||
let err
|
||||
try {
|
||||
await refactor.doRefactor()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should show message when prepare failed', async () => {
|
||||
await helper.createDocument()
|
||||
disposable = languages.registerRenameProvider(['*'], {
|
||||
prepareRename: () => {
|
||||
return undefined
|
||||
},
|
||||
provideRenameEdits: () => {
|
||||
return null
|
||||
}
|
||||
})
|
||||
await refactor.doRefactor()
|
||||
let res = await helper.getCmdline()
|
||||
expect(res).toMatch(/unable to rename/)
|
||||
})
|
||||
|
||||
it('should show message when returned edits is null', async () => {
|
||||
await helper.createDocument()
|
||||
disposable = languages.registerRenameProvider(['*'], {
|
||||
provideRenameEdits: () => {
|
||||
return null
|
||||
}
|
||||
})
|
||||
await refactor.doRefactor()
|
||||
let res = await helper.getCmdline()
|
||||
expect(res).toMatch(/returns null/)
|
||||
})
|
||||
|
||||
it('should open refactor window when edits is valid', async () => {
|
||||
let filepath = __filename
|
||||
disposable = languages.registerRenameProvider(['*'], {
|
||||
provideRenameEdits: () => {
|
||||
let changes = {
|
||||
[URI.file(filepath).toString()]: [{
|
||||
range: Range.create(0, 0, 0, 6),
|
||||
newText: ''
|
||||
}, {
|
||||
range: Range.create(1, 0, 1, 6),
|
||||
newText: ''
|
||||
}]
|
||||
}
|
||||
let edit: WorkspaceEdit = { changes }
|
||||
return edit
|
||||
}
|
||||
})
|
||||
await helper.createDocument(filepath)
|
||||
let winid = await nvim.call('win_getid')
|
||||
await refactor.doRefactor()
|
||||
let currWin = await nvim.call('win_getid')
|
||||
expect(currWin - winid).toBeGreaterThan(0)
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
let b = refactor.getBuffer(bufnr)
|
||||
expect(b).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
it('should open refactor buffer from search result', async () => {
|
||||
let escaped = await nvim.call('fnameescape', [__dirname])
|
||||
await nvim.command(`cd ${escaped}`)
|
||||
await helper.createDocument()
|
||||
await refactor.search(['registerRenameProvider'])
|
||||
let buf = await nvim.buffer
|
||||
let name = await buf.name
|
||||
expect(name).toMatch(/__coc_refactor__/)
|
||||
let lines = await buf.lines
|
||||
expect(lines[0]).toMatch(/Save current buffer/)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,262 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument'
|
||||
import Rename from '../../handler/rename'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
let rename: Rename
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
rename = helper.plugin.getHandler().rename
|
||||
})
|
||||
|
||||
function getWordRangeAtPosition(doc: TextDocument, position: Position): Range | null {
|
||||
let lines = doc.getText().split(/\r?\n/)
|
||||
let line = lines[position.line]
|
||||
if (line.length == 0 || position.character >= line.length) return null
|
||||
if (!/\w/.test(line[position.character])) return null
|
||||
let start = position.character
|
||||
let end = position.character + 1
|
||||
if (!/\w/.test(line[start])) {
|
||||
return Range.create(position, { line: position.line, character: position.character + 1 })
|
||||
}
|
||||
while (start >= 0) {
|
||||
let ch = line[start - 1]
|
||||
if (!ch || !/\w/.test(ch)) break
|
||||
start = start - 1
|
||||
}
|
||||
while (end <= line.length) {
|
||||
let ch = line[end]
|
||||
if (!ch || !/\w/.test(ch)) break
|
||||
end = end + 1
|
||||
}
|
||||
return Range.create(position.line, start, position.line, end)
|
||||
}
|
||||
|
||||
function getSymbolRanges(textDocument: TextDocument, word: string): Range[] {
|
||||
let res: Range[] = []
|
||||
let str = ''
|
||||
let content = textDocument.getText()
|
||||
for (let i = 0, l = content.length; i < l; i++) {
|
||||
let ch = content[i]
|
||||
if ('-' == ch && str.length == 0) {
|
||||
continue
|
||||
}
|
||||
let isKeyword = /\w/.test(ch)
|
||||
if (isKeyword) {
|
||||
str = str + ch
|
||||
}
|
||||
if (str.length > 0 && !isKeyword && str == word) {
|
||||
res.push(Range.create(textDocument.positionAt(i - str.length), textDocument.positionAt(i)))
|
||||
}
|
||||
if (!isKeyword) {
|
||||
str = ''
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerRenameProvider([{ language: 'javascript' }], {
|
||||
provideRenameEdits: (doc, position: Position, newName: string) => {
|
||||
let range = getWordRangeAtPosition(doc, position)
|
||||
if (range) {
|
||||
let word = doc.getText(range)
|
||||
if (word) {
|
||||
let ranges = getSymbolRanges(doc, word)
|
||||
return {
|
||||
changes: {
|
||||
[doc.uri]: ranges.map(o => TextEdit.replace(o, newName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
prepareRename: (doc, position) => {
|
||||
let range = getWordRangeAtPosition(doc, position)
|
||||
return range ? { range, placeholder: doc.getText(range) } : null
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
})
|
||||
|
||||
describe('rename handler', () => {
|
||||
describe('getWordEdit', () => {
|
||||
it('should not throw when provider not found', async () => {
|
||||
await helper.edit()
|
||||
let res = await rename.getWordEdit()
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return null when prepare failed', async () => {
|
||||
let doc = await helper.createDocument('t.js')
|
||||
await nvim.setLine('你')
|
||||
await doc.synchronize()
|
||||
let res = await rename.getWordEdit()
|
||||
expect(res).toBe(null)
|
||||
})
|
||||
|
||||
it('should return workspace edit', async () => {
|
||||
let doc = await helper.createDocument('t.js')
|
||||
await nvim.setLine('foo foo')
|
||||
await doc.synchronize()
|
||||
let res = await rename.getWordEdit()
|
||||
expect(res).toBeDefined()
|
||||
expect(res.changes[doc.uri].length).toBe(2)
|
||||
})
|
||||
|
||||
it('should extract words from buffer', async () => {
|
||||
let doc = await helper.createDocument('t')
|
||||
await nvim.setLine('你 你 你')
|
||||
await doc.synchronize()
|
||||
let res = await rename.getWordEdit()
|
||||
expect(res).toBeDefined()
|
||||
expect(res.changes[doc.uri].length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rename', () => {
|
||||
it('should throw when provider not found', async () => {
|
||||
await helper.edit()
|
||||
let err
|
||||
try {
|
||||
await rename.rename('foo')
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return false for invalid position', async () => {
|
||||
await helper.createDocument('t.js')
|
||||
let res = await rename.rename('foo')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should use newName from placeholder', async () => {
|
||||
await helper.createDocument('t.js')
|
||||
await nvim.setLine('foo foo foo')
|
||||
let p = rename.rename()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-u>')
|
||||
await helper.wait(10)
|
||||
await nvim.input('bar')
|
||||
await nvim.input('<cr>')
|
||||
let res = await p
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for empty name', async () => {
|
||||
await helper.createDocument('t.js')
|
||||
await nvim.setLine('foo foo foo')
|
||||
let p = rename.rename()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-u>')
|
||||
await helper.wait(20)
|
||||
await nvim.input('<cr>')
|
||||
let res = await p
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should use newName from range', async () => {
|
||||
disposables.push(languages.registerRenameProvider([{ language: '*' }], {
|
||||
provideRenameEdits: (doc, position: Position, newName: string) => {
|
||||
let range = getWordRangeAtPosition(doc, position)
|
||||
if (range) {
|
||||
let word = doc.getText(range)
|
||||
if (word) {
|
||||
let ranges = getSymbolRanges(doc, word)
|
||||
return {
|
||||
changes: {
|
||||
[doc.uri]: ranges.map(o => TextEdit.replace(o, newName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
prepareRename: (doc, position) => {
|
||||
let range = getWordRangeAtPosition(doc, position)
|
||||
return range ? range : null
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo foo foo')
|
||||
let p = rename.rename()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-u>')
|
||||
await helper.wait(10)
|
||||
await nvim.input('bar')
|
||||
await nvim.input('<cr>')
|
||||
let res = await p
|
||||
expect(res).toBe(true)
|
||||
await helper.waitFor('getline', ['.'], 'bar bar bar')
|
||||
})
|
||||
|
||||
it('should use newName from cword', async () => {
|
||||
disposables.push(languages.registerRenameProvider([{ language: '*' }], {
|
||||
provideRenameEdits: (doc, position: Position, newName: string) => {
|
||||
let range = getWordRangeAtPosition(doc, position)
|
||||
if (range) {
|
||||
let word = doc.getText(range)
|
||||
if (word) {
|
||||
let ranges = getSymbolRanges(doc, word)
|
||||
return {
|
||||
changes: {
|
||||
[doc.uri]: ranges.map(o => TextEdit.replace(o, newName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo foo foo')
|
||||
let p = rename.rename()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-u>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('bar')
|
||||
await nvim.input('<cr>')
|
||||
let res = await p
|
||||
expect(res).toBe(true)
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toBe('bar bar bar')
|
||||
})
|
||||
|
||||
it('should return false when result is empty', async () => {
|
||||
disposables.push(languages.registerRenameProvider([{ language: '*' }], {
|
||||
provideRenameEdits: () => {
|
||||
return null
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.setLine('foo foo foo')
|
||||
let p = rename.rename()
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-u>')
|
||||
await helper.wait(10)
|
||||
await nvim.input('bar')
|
||||
await nvim.input('<cr>')
|
||||
let res = await p
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,101 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import Refactor from '../../handler/refactor'
|
||||
import Search, { getPathFromArgs } from '../../handler/refactor/search'
|
||||
import helper from '../helper'
|
||||
import path from 'path'
|
||||
|
||||
let nvim: Neovim
|
||||
let refactor: Refactor
|
||||
// use fake rg command
|
||||
let cmd = path.resolve(__dirname, '../rg')
|
||||
let cwd = process.cwd()
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
refactor = helper.plugin.getHandler().refactor
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
refactor.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('getPathFromArgs', () => {
|
||||
it('should get undefined path', async () => {
|
||||
let res = getPathFromArgs(['a'])
|
||||
expect(res).toBeUndefined()
|
||||
res = getPathFromArgs(['a', 'b', '-c'])
|
||||
expect(res).toBeUndefined()
|
||||
res = getPathFromArgs(['a', '-b', 'c'])
|
||||
expect(res).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
|
||||
it('should open refactor window', async () => {
|
||||
let search = new Search(nvim, cmd)
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
await search.run([], cwd, buf)
|
||||
await helper.wait(50)
|
||||
let fileItems = buf.fileItems
|
||||
expect(fileItems.length).toBe(2)
|
||||
expect(fileItems[0].ranges.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should abort task', async () => {
|
||||
let search = new Search(nvim, cmd)
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let p = search.run(['--sleep', '1000'], cwd, buf)
|
||||
search.abort()
|
||||
await p
|
||||
let fileItems = buf.fileItems
|
||||
expect(fileItems.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should work with CocAction search', async () => {
|
||||
await helper.doAction('search', ['CocAction'])
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
let buf = refactor.getBuffer(bufnr)
|
||||
expect(buf).toBeDefined()
|
||||
})
|
||||
|
||||
it('should fail on invalid command', async () => {
|
||||
let search = new Search(nvim, 'rrg')
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
let err
|
||||
try {
|
||||
await search.run([], cwd, buf)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch(/Error on command "rrg"/)
|
||||
})
|
||||
|
||||
it('should show empty result when no result found', async () => {
|
||||
await helper.doAction('search', ['should found ' + ' no result'])
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
let buf = refactor.getBuffer(bufnr)
|
||||
expect(buf).toBeDefined()
|
||||
let buffer = await nvim.buffer
|
||||
let lines = await buffer.lines
|
||||
expect(lines[1]).toMatch(/No match found/)
|
||||
})
|
||||
|
||||
it('should use corrent search folder for rg', async () => {
|
||||
let search = new Search(nvim, 'rg')
|
||||
await helper.createDocument()
|
||||
let buf = await refactor.createRefactorBuffer()
|
||||
await search.run(['-w', 'createRefactorBuffer', 'src/__tests__'], cwd, buf)
|
||||
let buffer = await nvim.buffer
|
||||
let lines = await buffer.lines
|
||||
expect(lines[1].startsWith('Files: ')).toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,144 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'
|
||||
import SelectionRange from '../../handler/selectionRange'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import window from '../../window'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
let selection: SelectionRange
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
selection = helper.plugin.getHandler().selectionRange
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
})
|
||||
|
||||
describe('selectionRange', () => {
|
||||
describe('getSelectionRanges()', () => {
|
||||
it('should throw error when selectionRange provider does not exist', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.synchronize()
|
||||
let err
|
||||
try {
|
||||
await selection.getSelectionRanges()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should return ranges', async () => {
|
||||
await helper.createDocument()
|
||||
disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {
|
||||
provideSelectionRanges: _doc => {
|
||||
return [{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}]
|
||||
}
|
||||
}))
|
||||
let res = await selection.getSelectionRanges()
|
||||
expect(res).toBeDefined()
|
||||
expect(Array.isArray(res)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectRange()', () => {
|
||||
async function getSelectedRange(): Promise<Range> {
|
||||
let m = await nvim.mode
|
||||
expect(m.mode).toBe('v')
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
await nvim.input('<esc>')
|
||||
let doc = workspace.getDocument(bufnr)
|
||||
let res = await window.getSelectedRange('v')
|
||||
return res
|
||||
}
|
||||
|
||||
it('should select ranges forward', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let called = 0
|
||||
await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\nbar\ntest\n')])
|
||||
await nvim.call('cursor', [1, 1])
|
||||
disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {
|
||||
provideSelectionRanges: _doc => {
|
||||
called += 1
|
||||
let arr = [{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}, {
|
||||
range: Range.create(0, 0, 0, 3)
|
||||
}, {
|
||||
range: Range.create(0, 0, 1, 3)
|
||||
}]
|
||||
return arr
|
||||
}
|
||||
}))
|
||||
await doc.synchronize()
|
||||
await selection.selectRange('', false)
|
||||
await selection.selectRange('', true)
|
||||
expect(called).toBe(1)
|
||||
let res = await getSelectedRange()
|
||||
expect(res).toEqual(Range.create(0, 0, 0, 1))
|
||||
await selection.selectRange('v', true)
|
||||
expect(called).toBe(2)
|
||||
res = await getSelectedRange()
|
||||
expect(res).toEqual(Range.create(0, 0, 0, 3))
|
||||
await selection.selectRange('v', true)
|
||||
expect(called).toBe(3)
|
||||
res = await getSelectedRange()
|
||||
expect(res).toEqual(Range.create(0, 0, 1, 3))
|
||||
await selection.selectRange('v', true)
|
||||
expect(called).toBe(4)
|
||||
let m = await nvim.mode
|
||||
expect(m.mode).toBe('n')
|
||||
})
|
||||
|
||||
it('should select ranges backward', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\nbar\ntest\n')])
|
||||
await nvim.call('cursor', [1, 1])
|
||||
disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {
|
||||
provideSelectionRanges: _doc => {
|
||||
let arr = [{
|
||||
range: Range.create(0, 0, 0, 1)
|
||||
}, {
|
||||
range: Range.create(0, 0, 0, 3)
|
||||
}, {
|
||||
range: Range.create(0, 0, 1, 3)
|
||||
}]
|
||||
return arr
|
||||
}
|
||||
}))
|
||||
await doc.synchronize()
|
||||
await selection.selectRange('', true)
|
||||
let mode = await nvim.call('mode')
|
||||
expect(mode).toBe('v')
|
||||
await nvim.input('<esc>')
|
||||
await window.selectRange(Range.create(0, 0, 1, 3))
|
||||
await nvim.input('<esc>')
|
||||
await selection.selectRange('v', false)
|
||||
let r = await getSelectedRange()
|
||||
expect(r).toEqual(Range.create(0, 0, 0, 3))
|
||||
await nvim.input('<esc>')
|
||||
await selection.selectRange('v', false)
|
||||
r = await getSelectedRange()
|
||||
expect(r).toEqual(Range.create(0, 0, 0, 1))
|
||||
await nvim.input('<esc>')
|
||||
await selection.selectRange('v', false)
|
||||
mode = await nvim.call('mode')
|
||||
expect(mode).toBe('n')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,584 +0,0 @@
|
|||
import { Buffer, Neovim } from '@chemzqm/neovim'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { Disposable, Range, SemanticTokensLegend } from 'vscode-languageserver-protocol'
|
||||
import { URI } from 'vscode-uri'
|
||||
import commandManager from '../../commands'
|
||||
import SemanticTokens from '../../handler/semanticTokens/index'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import window from '../../window'
|
||||
import workspace from '../../workspace'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let ns: number
|
||||
let disposables: Disposable[] = []
|
||||
let highlighter: SemanticTokens
|
||||
let legend: SemanticTokensLegend = {
|
||||
tokenTypes: [
|
||||
"comment",
|
||||
"keyword",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"namespace",
|
||||
"type",
|
||||
"struct",
|
||||
"class",
|
||||
"interface",
|
||||
"enum",
|
||||
"enumMember",
|
||||
"typeParameter",
|
||||
"function",
|
||||
"method",
|
||||
"property",
|
||||
"macro",
|
||||
"variable",
|
||||
"parameter",
|
||||
"angle",
|
||||
"arithmetic",
|
||||
"attribute",
|
||||
"bitwise",
|
||||
"boolean",
|
||||
"brace",
|
||||
"bracket",
|
||||
"builtinType",
|
||||
"character",
|
||||
"colon",
|
||||
"comma",
|
||||
"comparison",
|
||||
"constParameter",
|
||||
"dot",
|
||||
"escapeSequence",
|
||||
"formatSpecifier",
|
||||
"generic",
|
||||
"label",
|
||||
"lifetime",
|
||||
"logical",
|
||||
"operator",
|
||||
"parenthesis",
|
||||
"punctuation",
|
||||
"selfKeyword",
|
||||
"semicolon",
|
||||
"typeAlias",
|
||||
"union",
|
||||
"unresolvedReference"
|
||||
],
|
||||
tokenModifiers: [
|
||||
"documentation",
|
||||
"declaration",
|
||||
"definition",
|
||||
"static",
|
||||
"abstract",
|
||||
"deprecated",
|
||||
"readonly",
|
||||
"constant",
|
||||
"controlFlow",
|
||||
"injected",
|
||||
"mutable",
|
||||
"consuming",
|
||||
"async",
|
||||
"library",
|
||||
"public",
|
||||
"unsafe",
|
||||
"attribute",
|
||||
"trait",
|
||||
"callable",
|
||||
"intraDocLink"
|
||||
]
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
ns = await nvim.call('coc#highlight#create_namespace', ['semanticTokens'])
|
||||
highlighter = helper.plugin.getHandler().semanticHighlighter
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
const defaultResult = {
|
||||
resultId: '1',
|
||||
data: [
|
||||
0, 0, 2, 1, 0,
|
||||
0, 3, 4, 14, 2,
|
||||
0, 4, 1, 41, 0,
|
||||
0, 1, 1, 41, 3,
|
||||
0, 2, 1, 25, 0,
|
||||
1, 4, 8, 17, 0,
|
||||
0, 8, 1, 41, 0,
|
||||
0, 1, 3, 2, 0,
|
||||
0, 3, 1, 41, 0,
|
||||
0, 1, 1, 44, 0,
|
||||
1, 0, 1, 25, 0,
|
||||
]
|
||||
}
|
||||
|
||||
function registerRangeProvider(filetype: string, fn: (range: Range) => number[]): Disposable {
|
||||
return languages.registerDocumentRangeSemanticTokensProvider([{ language: filetype }], {
|
||||
provideDocumentRangeSemanticTokens: (_, range) => {
|
||||
return {
|
||||
data: fn(range)
|
||||
}
|
||||
}
|
||||
}, legend)
|
||||
}
|
||||
|
||||
function registerProvider(): void {
|
||||
disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'rust' }], {
|
||||
provideDocumentSemanticTokens: () => {
|
||||
return defaultResult
|
||||
},
|
||||
provideDocumentSemanticTokensEdits: (_, previousResultId) => {
|
||||
if (previousResultId !== '1') return undefined
|
||||
return {
|
||||
resultId: '2',
|
||||
edits: [{
|
||||
start: 0,
|
||||
deleteCount: 0,
|
||||
data: [0, 0, 3, 1, 0]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}, legend))
|
||||
}
|
||||
|
||||
async function createRustBuffer(): Promise<Buffer> {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['rust'])
|
||||
registerProvider()
|
||||
let code = `fn main() {
|
||||
println!("H");
|
||||
}`
|
||||
let buf = await nvim.buffer
|
||||
await nvim.command('setf rust')
|
||||
await buf.setLines(code.split('\n'), { start: 0, end: -1, strictIndexing: false })
|
||||
let doc = await workspace.document
|
||||
await doc.patchChange()
|
||||
return buf
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', [])
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
})
|
||||
|
||||
describe('semanticTokens', () => {
|
||||
describe('showHighlightInfo()', () => {
|
||||
it('should show error when buffer not attached', async () => {
|
||||
await nvim.command('h')
|
||||
await highlighter.showHighlightInfo()
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toMatch('not attached')
|
||||
await highlighter.inspectSemanticToken()
|
||||
})
|
||||
|
||||
it('should show message when not enabled', async () => {
|
||||
await helper.edit('t.txt')
|
||||
await highlighter.showHighlightInfo()
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines[2]).toMatch('not enabled for current filetype')
|
||||
})
|
||||
|
||||
it('should show semantic tokens info', async () => {
|
||||
await createRustBuffer()
|
||||
await highlighter.highlightCurrent()
|
||||
await commandManager.executeCommand('semanticTokens.checkCurrent')
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
let content = lines.join('\n')
|
||||
expect(content).toMatch('Semantic highlight groups used by current buffer')
|
||||
})
|
||||
|
||||
it('should show highlight info for empty legend', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['*'])
|
||||
disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {
|
||||
provideDocumentRangeSemanticTokens: (_, range) => {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}, { tokenModifiers: [], tokenTypes: [] }))
|
||||
await highlighter.showHighlightInfo()
|
||||
await highlighter.showHighlightInfo()
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
let content = lines.join('\n')
|
||||
expect(content).toMatch('No token')
|
||||
})
|
||||
})
|
||||
|
||||
describe('highlightCurrent()', () => {
|
||||
it('should refresh highlights', async () => {
|
||||
await createRustBuffer()
|
||||
await nvim.command('hi link CocSemDeclarationFunction MoreMsg')
|
||||
await nvim.command('hi link CocSemDocumentation Statement')
|
||||
await window.moveTo({ line: 0, character: 4 })
|
||||
await highlighter.highlightCurrent()
|
||||
await commandManager.executeCommand('semanticTokens.inspect')
|
||||
let win = await helper.getFloat()
|
||||
let buf = await win.buffer
|
||||
let lines = await buf.lines
|
||||
let content = lines.join('\n')
|
||||
expect(content).toMatch('CocSemDeclarationFunction')
|
||||
await window.moveTo({ line: 1, character: 0 })
|
||||
await commandManager.executeCommand('semanticTokens.inspect')
|
||||
win = await helper.getFloat()
|
||||
expect(win).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should refresh highlights by command', async () => {
|
||||
await helper.edit()
|
||||
let err
|
||||
try {
|
||||
await commandManager.executeCommand('semanticTokens.refreshCurrent')
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
|
||||
it('should refresh when buffer visible', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['rust'])
|
||||
let code = `fn main() {
|
||||
println!("H");
|
||||
}`
|
||||
let buf = await nvim.buffer
|
||||
await nvim.command('setf rust')
|
||||
await buf.setLines(code.split('\n'), { start: 0, end: -1, strictIndexing: false })
|
||||
await helper.wait(10)
|
||||
let doc = await workspace.document
|
||||
await doc.synchronize()
|
||||
let item = await highlighter.getCurrentItem()
|
||||
expect(item.enabled).toBe(false)
|
||||
await nvim.command('edit bar')
|
||||
registerProvider()
|
||||
expect(item.enabled).toBe(true)
|
||||
await helper.wait(20)
|
||||
await nvim.command(`b ${buf.id}`)
|
||||
await item.waitRefresh()
|
||||
expect(item.highlights).toBeDefined()
|
||||
})
|
||||
|
||||
it('should reuse exists tokens when version not changed', async () => {
|
||||
let doc = await helper.createDocument('t.vim')
|
||||
await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])
|
||||
let fn = jest.fn()
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {
|
||||
provideDocumentSemanticTokens: () => {
|
||||
fn()
|
||||
return new Promise(resolve => {
|
||||
resolve({
|
||||
resultId: '1',
|
||||
data: [0, 0, 3, 1, 0]
|
||||
})
|
||||
})
|
||||
}
|
||||
}, legend))
|
||||
let item = await highlighter.getCurrentItem()
|
||||
item.cancel()
|
||||
await item.doHighlight()
|
||||
await item.doHighlight()
|
||||
expect(fn).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should only highlight limited range on update', async () => {
|
||||
let doc = await helper.createDocument('t.vim')
|
||||
let fn = jest.fn()
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {
|
||||
provideDocumentSemanticTokens: (doc, token) => {
|
||||
let text = doc.getText()
|
||||
if (!text.trim()) {
|
||||
return Promise.resolve({ resultId: '1', data: [] })
|
||||
}
|
||||
fn()
|
||||
let lines = text.split('\n')
|
||||
let data = [0, 0, 1, 1, 0]
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
data.push(1, 0, 1, 1, 0)
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
resolve(undefined)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
resolve({ resultId: '1', data })
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
}, legend))
|
||||
let item = await highlighter.getCurrentItem()
|
||||
await item.doHighlight()
|
||||
let newLine = 'l\n'
|
||||
await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: `${newLine.repeat(2000)}` }])
|
||||
await item.doHighlight()
|
||||
await item.waitRefresh()
|
||||
expect(fn).toBeCalled()
|
||||
let buf = nvim.createBuffer(doc.bufnr)
|
||||
let markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
let len = markers.length
|
||||
expect(len).toBeLessThan(400)
|
||||
await nvim.command('normal! gg')
|
||||
await helper.wait(50)
|
||||
await nvim.command('normal! 200G')
|
||||
await helper.wait(50)
|
||||
markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBeGreaterThan(len)
|
||||
})
|
||||
|
||||
it('should highlight hidden buffer on shown', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['rust'])
|
||||
registerProvider()
|
||||
let code = 'fn main() {\n println!("H"); \n}'
|
||||
let filepath = path.join(os.tmpdir(), 'a.rs')
|
||||
fs.writeFileSync(filepath, code, 'utf8')
|
||||
let uri = URI.file(filepath).toString()
|
||||
await workspace.loadFile(uri)
|
||||
let doc = workspace.getDocument(uri)
|
||||
let item = highlighter.getItem(doc.bufnr)
|
||||
let fn = jest.fn()
|
||||
item.onDidRefresh(() => {
|
||||
fn()
|
||||
})
|
||||
let buf = doc.buffer
|
||||
await helper.wait(10)
|
||||
expect(doc.filetype).toBe('rust')
|
||||
expect(fn).toBeCalledTimes(0)
|
||||
await nvim.command(`b ${buf.id}`)
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not highlight on shown when document not changed', async () => {
|
||||
let fn = jest.fn()
|
||||
let buf = await createRustBuffer()
|
||||
let item = await highlighter.getCurrentItem()
|
||||
await item.waitRefresh()
|
||||
await nvim.command('enew')
|
||||
item.doHighlight = async () => {
|
||||
fn()
|
||||
}
|
||||
await nvim.command(`b ${buf.id}`)
|
||||
await helper.wait(100)
|
||||
expect(fn).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('clear highlights', () => {
|
||||
it('should clear highlights of current buffer', async () => {
|
||||
await createRustBuffer()
|
||||
await highlighter.highlightCurrent()
|
||||
let buf = await nvim.buffer
|
||||
let markers = await buf.getExtMarks(ns, 0, -1)
|
||||
expect(markers.length).toBeGreaterThan(0)
|
||||
await commandManager.executeCommand('semanticTokens.clearCurrent')
|
||||
markers = await buf.getExtMarks(ns, 0, -1)
|
||||
expect(markers.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should clear all highlights', async () => {
|
||||
await createRustBuffer()
|
||||
await highlighter.highlightCurrent()
|
||||
let buf = await nvim.buffer
|
||||
await commandManager.executeCommand('semanticTokens.clearAll')
|
||||
let markers = await buf.getExtMarks(ns, 0, -1)
|
||||
expect(markers.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rangeProvider', () => {
|
||||
it('should invoke range provider first time when both kinds exist', async () => {
|
||||
let fn = jest.fn()
|
||||
disposables.push(registerRangeProvider('rust', () => {
|
||||
fn()
|
||||
return []
|
||||
}))
|
||||
let buf = await createRustBuffer()
|
||||
let item = highlighter.getItem(buf.id)
|
||||
await item.waitRefresh()
|
||||
await helper.wait(50)
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should do range highlight first time', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
let r: Range
|
||||
disposables.push(registerRangeProvider('vim', range => {
|
||||
r = range
|
||||
return [0, 0, 3, 1, 0]
|
||||
}))
|
||||
let filepath = await createTmpFile('let')
|
||||
fs.renameSync(filepath, filepath + '.vim')
|
||||
let doc = await helper.createDocument(filepath + '.vim')
|
||||
expect(doc.filetype).toBe('vim')
|
||||
await helper.waitValue(() => {
|
||||
return typeof r !== 'undefined'
|
||||
}, true)
|
||||
})
|
||||
|
||||
it('should do range highlight after cursor moved', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
let doc = await helper.createDocument('t.vim')
|
||||
let r: Range
|
||||
expect(doc.filetype).toBe('vim')
|
||||
await nvim.call('setline', [2, (new Array(200).fill(''))])
|
||||
await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])
|
||||
await helper.wait(50)
|
||||
disposables.push(registerRangeProvider('vim', range => {
|
||||
r = range
|
||||
return []
|
||||
}))
|
||||
await nvim.command('normal! G')
|
||||
await helper.wait(100)
|
||||
expect(r).toBeDefined()
|
||||
expect(r.end).toEqual({ line: 201, character: 0 })
|
||||
})
|
||||
|
||||
it('should only cancel range highlight request', async () => {
|
||||
let rangeCancelled = false
|
||||
disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: 'vim' }], {
|
||||
provideDocumentRangeSemanticTokens: (_, range, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timeout)
|
||||
rangeCancelled = true
|
||||
resolve(null)
|
||||
})
|
||||
let timeout = setTimeout(() => {
|
||||
resolve({ data: [] })
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
}, legend))
|
||||
disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {
|
||||
provideDocumentSemanticTokens: (_, token) => {
|
||||
return new Promise(resolve => {
|
||||
resolve({
|
||||
resultId: '1',
|
||||
data: [0, 0, 3, 1, 0]
|
||||
})
|
||||
})
|
||||
}
|
||||
}, legend))
|
||||
let doc = await helper.createDocument('t.vim')
|
||||
await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])
|
||||
let item = await highlighter.getCurrentItem()
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
item.cancel()
|
||||
let p = item.doHighlight()
|
||||
await helper.wait(10)
|
||||
item.cancel(true)
|
||||
await p
|
||||
})
|
||||
})
|
||||
|
||||
describe('triggerSemanticTokens', () => {
|
||||
it('should be disabled by default', async () => {
|
||||
helper.updateConfiguration('semanticTokens.filetypes', [])
|
||||
await workspace.document
|
||||
const curr = await highlighter.getCurrentItem()
|
||||
expect(curr.enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should be enabled', async () => {
|
||||
await createRustBuffer()
|
||||
const curr = await highlighter.getCurrentItem()
|
||||
expect(curr.enabled).toBe(true)
|
||||
})
|
||||
|
||||
it('should get legend by API', async () => {
|
||||
await createRustBuffer()
|
||||
const doc = await workspace.document
|
||||
const l = languages.getLegend(doc.textDocument)
|
||||
expect(l).toEqual(legend)
|
||||
})
|
||||
|
||||
it('should doHighlight', async () => {
|
||||
await createRustBuffer()
|
||||
const doc = await workspace.document
|
||||
await nvim.call('CocAction', 'semanticHighlight')
|
||||
const highlights = await nvim.call("coc#highlight#get_highlights", [doc.bufnr, 'semanticTokens'])
|
||||
expect(highlights.length).toBeGreaterThan(0)
|
||||
expect(highlights[0][0]).toBe('CocSemKeyword')
|
||||
})
|
||||
})
|
||||
|
||||
describe('delta update', () => {
|
||||
it('should perform highlight update', async () => {
|
||||
await createRustBuffer()
|
||||
let buf = await nvim.buffer
|
||||
await highlighter.highlightCurrent()
|
||||
await window.moveTo({ line: 0, character: 0 })
|
||||
let doc = await workspace.document
|
||||
await nvim.input('if')
|
||||
await helper.wait(50)
|
||||
await doc.synchronize()
|
||||
let curr = await highlighter.getCurrentItem()
|
||||
await curr.forceHighlight()
|
||||
let markers = await buf.getExtMarks(ns, 0, -1, { details: true })
|
||||
expect(markers.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkState', () => {
|
||||
it('should throw for invalid state', async () => {
|
||||
let doc = await workspace.document
|
||||
const toThrow = (cb: () => void) => {
|
||||
expect(cb).toThrow(Error)
|
||||
}
|
||||
let item = highlighter.getItem(doc.bufnr)
|
||||
toThrow(() => {
|
||||
item.checkState()
|
||||
})
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['*'])
|
||||
toThrow(() => {
|
||||
item.checkState()
|
||||
})
|
||||
toThrow(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
workspace._env.updateHighlight = false
|
||||
item.checkState()
|
||||
})
|
||||
let enabled = item.enabled
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
workspace._env.updateHighlight = true
|
||||
expect(enabled).toBe(false)
|
||||
doc.detach()
|
||||
toThrow(() => {
|
||||
item.checkState()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('enabled', () => {
|
||||
it('should check if buffer enabled for semanticTokens', async () => {
|
||||
let doc = await workspace.document
|
||||
let item = highlighter.getItem(doc.bufnr)
|
||||
disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {
|
||||
provideDocumentRangeSemanticTokens: (_, range) => {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}, { tokenModifiers: [], tokenTypes: [] }))
|
||||
expect(item.enabled).toBe(false)
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['vim'])
|
||||
expect(item.enabled).toBe(false)
|
||||
helper.updateConfiguration('semanticTokens.filetypes', ['*'])
|
||||
expect(item.enabled).toBe(true)
|
||||
doc.detach()
|
||||
expect(item.enabled).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,369 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, ParameterInformation, SignatureInformation } from 'vscode-languageserver-protocol'
|
||||
import Signature from '../../handler/signature'
|
||||
import languages from '../../languages'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let signature: Signature
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
signature = helper.plugin.getHandler().signature
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await helper.reset()
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
})
|
||||
|
||||
describe('signatureHelp', () => {
|
||||
|
||||
describe('triggerSignatureHelp', () => {
|
||||
it('should show signature by api', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, []))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
await signature.triggerSignatureHelp()
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines[2]).toMatch('my signature')
|
||||
})
|
||||
|
||||
it('should use 0 when activeParameter is undefined', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo(a)', 'my signature', { label: 'a' })],
|
||||
activeParameter: undefined,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, []))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
await signature.triggerSignatureHelp()
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let highlights = await win.getVar('highlights')
|
||||
expect(highlights).toBeDefined()
|
||||
expect(highlights[0].hlGroup).toBe('CocUnderline')
|
||||
})
|
||||
|
||||
it('should trigger by space', async () => {
|
||||
let promise = new Promise(resolve => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
resolve(undefined)
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, [' ']))
|
||||
})
|
||||
await helper.createDocument()
|
||||
await nvim.input('i')
|
||||
await helper.wait(30)
|
||||
await nvim.input(' ')
|
||||
await promise
|
||||
})
|
||||
|
||||
it('should show signature help with param label as string', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [
|
||||
SignatureInformation.create('foo()', 'my signature'),
|
||||
SignatureInformation.create('foo(a, b)', 'my signature', ParameterInformation.create('a', 'description')),
|
||||
],
|
||||
activeParameter: 0,
|
||||
activeSignature: 1
|
||||
}
|
||||
}
|
||||
}, []))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
await signature.triggerSignatureHelp()
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines.join('\n')).toMatch(/description/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('events', () => {
|
||||
it('should trigger signature help', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo(x, y)', 'my signature')],
|
||||
activeParameter: 0,
|
||||
activeSignature: 0
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
await nvim.input('(')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines[2]).toMatch('my signature')
|
||||
})
|
||||
|
||||
it('should cancel trigger on InsertLeave', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: async (_doc, _position, token) => {
|
||||
return new Promise(resolve => {
|
||||
let timer = setTimeout(() => {
|
||||
resolve({
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
})
|
||||
}, 1000)
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
resolve(undefined)
|
||||
})
|
||||
})
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
let p = signature.triggerSignatureHelp()
|
||||
await helper.wait(10)
|
||||
await nvim.command('stopinsert')
|
||||
await nvim.call('feedkeys', [String.fromCharCode(27), 'in'])
|
||||
let res = await p
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should not close signature on type', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo(')
|
||||
await helper.wait(100)
|
||||
await nvim.input('bar')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines[2]).toMatch('my signature')
|
||||
})
|
||||
|
||||
it('should close signature float when empty signatures returned', async () => {
|
||||
let empty = false
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
if (empty) return undefined
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo(')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
empty = true
|
||||
await signature.triggerSignatureHelp()
|
||||
await helper.wait(50)
|
||||
let res = await nvim.call('coc#float#valid', [win.id])
|
||||
expect(res).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('float window', () => {
|
||||
it('should align signature window to top', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
let buf = await nvim.buffer
|
||||
await buf.setLines(['', '', '', '', ''], { start: 0, end: -1, strictIndexing: true })
|
||||
await nvim.call('cursor', [5, 1])
|
||||
await nvim.input('foo(')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines[2]).toMatch('my signature')
|
||||
let res = await nvim.call('GetFloatCursorRelative', [win.id]) as any
|
||||
expect(res.row).toBeLessThan(0)
|
||||
})
|
||||
|
||||
it('should show parameter docs', async () => {
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo(a, b)', 'my signature',
|
||||
ParameterInformation.create('a', 'foo'),
|
||||
ParameterInformation.create([7, 8], 'bar'))],
|
||||
activeParameter: 1,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
let buf = await nvim.buffer
|
||||
await buf.setLines(['', '', '', '', ''], { start: 0, end: -1, strictIndexing: true })
|
||||
await nvim.call('cursor', [5, 1])
|
||||
await nvim.input('foo(a,')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeDefined()
|
||||
let lines = await helper.getWinLines(win.id)
|
||||
expect(lines.join('\n')).toMatch('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('configurations', () => {
|
||||
let { configurations } = workspace
|
||||
afterEach(() => {
|
||||
configurations.updateUserConfig({
|
||||
'signature.target': 'float',
|
||||
'signature.hideOnTextChange': false,
|
||||
'signature.enable': true,
|
||||
'signature.triggerSignatureWait': 500
|
||||
})
|
||||
})
|
||||
|
||||
it('should cancel signature on timeout', async () => {
|
||||
configurations.updateUserConfig({ 'signature.triggerSignatureWait': 50 })
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position, token) => {
|
||||
return new Promise(resolve => {
|
||||
token.onCancellationRequested(() => {
|
||||
clearTimeout(timer)
|
||||
resolve(undefined)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
resolve({
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await signature.triggerSignatureHelp()
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeUndefined()
|
||||
configurations.updateUserConfig({ 'signature.triggerSignatureWait': 100 })
|
||||
})
|
||||
|
||||
it('should hide signature window on text change', async () => {
|
||||
configurations.updateUserConfig({ 'signature.hideOnTextChange': true })
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('ifoo(')
|
||||
let winid = await helper.waitFloat()
|
||||
await nvim.input('x')
|
||||
await helper.wait(100)
|
||||
let res = await nvim.call('coc#float#valid', [winid])
|
||||
expect(res).toBe(0)
|
||||
configurations.updateUserConfig({ 'signature.hideOnTextChange': false })
|
||||
})
|
||||
|
||||
it('should disable signature help trigger', async () => {
|
||||
configurations.updateUserConfig({ 'signature.enable': false })
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo()', 'my signature')],
|
||||
activeParameter: null,
|
||||
activeSignature: null
|
||||
}
|
||||
}
|
||||
}, ['(', ',']))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo')
|
||||
await nvim.input('(')
|
||||
await helper.wait(100)
|
||||
let win = await helper.getFloat()
|
||||
expect(win).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should echo simple signature help', async () => {
|
||||
let idx = 0
|
||||
let activeSignature = null
|
||||
configurations.updateUserConfig({ 'signature.target': 'echo' })
|
||||
disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {
|
||||
provideSignatureHelp: (_doc, _position) => {
|
||||
return {
|
||||
signatures: [SignatureInformation.create('foo(a, b)', 'my signature',
|
||||
ParameterInformation.create('a', 'foo'),
|
||||
ParameterInformation.create([7, 8], 'bar')),
|
||||
SignatureInformation.create('a'.repeat(workspace.env.columns + 10))
|
||||
],
|
||||
activeParameter: idx,
|
||||
activeSignature
|
||||
}
|
||||
}
|
||||
}, []))
|
||||
await helper.createDocument()
|
||||
await nvim.input('foo(')
|
||||
await signature.triggerSignatureHelp()
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toMatch('(a, b)')
|
||||
await nvim.input('a,')
|
||||
idx = 1
|
||||
await signature.triggerSignatureHelp()
|
||||
line = await helper.getCmdline()
|
||||
expect(line).toMatch('foo(a, b)')
|
||||
activeSignature = 1
|
||||
await signature.triggerSignatureHelp()
|
||||
line = await helper.getCmdline()
|
||||
expect(line).toMatch('aaaaaa')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,280 +0,0 @@
|
|||
import { Buffer, Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable, SymbolInformation, SymbolKind, Range } from 'vscode-languageserver-protocol'
|
||||
import Symbols from '../../handler/symbols/index'
|
||||
import languages from '../../languages'
|
||||
import workspace from '../../workspace'
|
||||
import window from '../../window'
|
||||
import events from '../../events'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
import Parser from './parser'
|
||||
|
||||
let nvim: Neovim
|
||||
let symbols: Symbols
|
||||
let disposables: Disposable[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
symbols = helper.plugin.getHandler().symbols
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
disposables.push(languages.registerDocumentSymbolProvider([{ language: 'javascript' }], {
|
||||
provideDocumentSymbols: document => {
|
||||
let text = document.getText()
|
||||
let parser = new Parser(text, text.includes('detail'))
|
||||
let res = parser.parse()
|
||||
return Promise.resolve(res)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
disposables = []
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Parser', () => {
|
||||
it('should parse content', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() { }
|
||||
}`
|
||||
let parser = new Parser(code)
|
||||
let res = parser.parse()
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('symbols handler', () => {
|
||||
|
||||
async function createBuffer(code: string): Promise<Buffer> {
|
||||
let buf = await nvim.buffer
|
||||
await nvim.command('setf javascript')
|
||||
await buf.setLines(code.split('\n'), { start: 0, end: -1, strictIndexing: false })
|
||||
let doc = await workspace.document
|
||||
doc.forceSync()
|
||||
return buf
|
||||
}
|
||||
|
||||
describe('configuration', () => {
|
||||
it('should get configuration', async () => {
|
||||
let functionUpdate = symbols.functionUpdate
|
||||
expect(functionUpdate).toBe(false)
|
||||
helper.updateConfiguration('coc.preferences.currentFunctionSymbolAutoUpdate', true)
|
||||
functionUpdate = symbols.functionUpdate
|
||||
expect(functionUpdate).toBe(true)
|
||||
})
|
||||
|
||||
it('should update symbols automatically', async () => {
|
||||
helper.updateConfiguration('coc.preferences.currentFunctionSymbolAutoUpdate', true)
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
}
|
||||
}`
|
||||
let buf = await createBuffer(code)
|
||||
await nvim.call('cursor', [2, 8])
|
||||
await events.fire('CursorHold', [buf.id])
|
||||
let val = await buf.getVar('coc_current_function')
|
||||
expect(val).toBe('fun1')
|
||||
await nvim.call('cursor', [1, 8])
|
||||
await events.fire('CursorHold', [buf.id])
|
||||
val = await buf.getVar('coc_current_function')
|
||||
expect(val).toBe('myClass')
|
||||
})
|
||||
})
|
||||
|
||||
describe('documentSymbols', () => {
|
||||
it('should get symbols of current buffer', async () => {
|
||||
let code = `class detail {
|
||||
fun1() { }
|
||||
}`
|
||||
await createBuffer(code)
|
||||
let res = await helper.plugin.cocAction('documentSymbols')
|
||||
expect(res.length).toBe(2)
|
||||
expect(res[1].detail).toBeDefined()
|
||||
})
|
||||
|
||||
it('should get current function symbols', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
}
|
||||
fun2() {
|
||||
}
|
||||
}
|
||||
`
|
||||
await createBuffer(code)
|
||||
await nvim.call('cursor', [3, 0])
|
||||
let res = await helper.doAction('getCurrentFunctionSymbol')
|
||||
expect(res).toBe('fun1')
|
||||
await nvim.command('normal! G')
|
||||
res = await helper.doAction('getCurrentFunctionSymbol')
|
||||
expect(res).toBe('')
|
||||
})
|
||||
|
||||
it('should reset coc_current_function when symbols do not exist', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
}
|
||||
}`
|
||||
await createBuffer(code)
|
||||
await nvim.call('cursor', [3, 0])
|
||||
let res = await helper.doAction('getCurrentFunctionSymbol')
|
||||
expect(res).toBe('fun1')
|
||||
await nvim.command('normal! ggdG')
|
||||
res = await symbols.getCurrentFunctionSymbol()
|
||||
expect(res).toBe('')
|
||||
})
|
||||
|
||||
it('should support SymbolInformation', async () => {
|
||||
disposables.push(languages.registerDocumentSymbolProvider(['*'], {
|
||||
provideDocumentSymbols: () => {
|
||||
return [
|
||||
SymbolInformation.create('root', SymbolKind.Function, Range.create(0, 0, 0, 10)),
|
||||
SymbolInformation.create('child', SymbolKind.Function, Range.create(0, 0, 0, 10), '', 'root')
|
||||
]
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
let res = await symbols.getDocumentSymbols()
|
||||
expect(res.length).toBe(2)
|
||||
expect(res[0].text).toBe('root')
|
||||
expect(res[1].text).toBe('child')
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectSymbolRange', () => {
|
||||
it('should show warning when no symbols exist', async () => {
|
||||
disposables.push(languages.registerDocumentSymbolProvider(['*'], {
|
||||
provideDocumentSymbols: () => {
|
||||
return []
|
||||
}
|
||||
}))
|
||||
await helper.createDocument()
|
||||
await nvim.call('cursor', [3, 0])
|
||||
await symbols.selectSymbolRange(false, '', ['Function'])
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch(/No symbols found/)
|
||||
})
|
||||
|
||||
it('should select symbol range at cursor position', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
}
|
||||
}`
|
||||
await createBuffer(code)
|
||||
await nvim.call('cursor', [3, 0])
|
||||
await helper.doAction('selectSymbolRange', false, '', ['Function', 'Method'])
|
||||
let mode = await nvim.mode
|
||||
expect(mode.mode).toBe('v')
|
||||
await nvim.input('<esc>')
|
||||
let res = await window.getSelectedRange('v')
|
||||
expect(res).toEqual({ start: { line: 1, character: 6 }, end: { line: 2, character: 6 } })
|
||||
})
|
||||
|
||||
it('should select inner range', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
let foo;
|
||||
}
|
||||
}`
|
||||
let buf = await createBuffer(code)
|
||||
await nvim.call('cursor', [3, 3])
|
||||
await symbols.selectSymbolRange(true, '', ['Method'])
|
||||
let mode = await nvim.mode
|
||||
expect(mode.mode).toBe('v')
|
||||
await nvim.input('<esc>')
|
||||
let res = await window.getSelectedRange('v')
|
||||
expect(res).toEqual({
|
||||
start: { line: 2, character: 8 }, end: { line: 2, character: 16 }
|
||||
})
|
||||
})
|
||||
|
||||
it('should reset visualmode when selection not found', async () => {
|
||||
let code = `class myClass {}`
|
||||
await createBuffer(code)
|
||||
await nvim.call('cursor', [1, 1])
|
||||
await nvim.command('normal! gg0v$')
|
||||
let mode = await nvim.mode
|
||||
expect(mode.mode).toBe('v')
|
||||
await nvim.input('<esc>')
|
||||
await symbols.selectSymbolRange(true, 'v', ['Method'])
|
||||
mode = await nvim.mode
|
||||
expect(mode.mode).toBe('v')
|
||||
})
|
||||
|
||||
it('should select symbol range from select range', async () => {
|
||||
let code = `class myClass {
|
||||
fun1() {
|
||||
}
|
||||
}`
|
||||
let buf = await createBuffer(code)
|
||||
await nvim.call('cursor', [2, 8])
|
||||
await nvim.command('normal! viw')
|
||||
await nvim.input('<esc>')
|
||||
await helper.doAction('selectSymbolRange', false, 'v', ['Class'])
|
||||
let mode = await nvim.mode
|
||||
expect(mode.mode).toBe('v')
|
||||
let doc = workspace.getDocument(buf.id)
|
||||
await nvim.input('<esc>')
|
||||
let res = await window.getSelectedRange('v')
|
||||
expect(res).toEqual({ start: { line: 0, character: 0 }, end: { line: 3, character: 4 } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('cancel', () => {
|
||||
it('should cancel symbols request on insert', async () => {
|
||||
let cancelled = false
|
||||
disposables.push(languages.registerDocumentSymbolProvider([{ language: 'text' }], {
|
||||
provideDocumentSymbols: (_doc, token) => {
|
||||
return new Promise(s => {
|
||||
token.onCancellationRequested(() => {
|
||||
if (timer) clearTimeout(timer)
|
||||
cancelled = true
|
||||
s(undefined)
|
||||
})
|
||||
let timer = setTimeout(() => {
|
||||
s(undefined)
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
}))
|
||||
let doc = await helper.createDocument('t.txt')
|
||||
let p = symbols.getDocumentSymbols(doc.bufnr)
|
||||
setTimeout(async () => {
|
||||
await nvim.input('i')
|
||||
}, 500)
|
||||
await p
|
||||
expect(cancelled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('workspaceSymbols', () => {
|
||||
it('should get workspace symbols', async () => {
|
||||
disposables.push(languages.registerWorkspaceSymbolProvider({
|
||||
provideWorkspaceSymbols: (_query, _token) => {
|
||||
return [SymbolInformation.create('far', SymbolKind.Class, Range.create(0, 0, 0, 0))]
|
||||
},
|
||||
resolveWorkspaceSymbol: sym => {
|
||||
let res = Object.assign({}, sym)
|
||||
res.location.uri = 'test:///foo'
|
||||
return res
|
||||
}
|
||||
}))
|
||||
disposables.push(languages.registerWorkspaceSymbolProvider({
|
||||
provideWorkspaceSymbols: (_query, _token) => {
|
||||
return [SymbolInformation.create('bar', SymbolKind.Function, Range.create(0, 0, 0, 0))]
|
||||
}
|
||||
}))
|
||||
let res = await symbols.getWorkspaceSymbols('a')
|
||||
expect(res.length).toBe(2)
|
||||
let resolved = await symbols.resolveWorkspaceSymbol(res[0])
|
||||
expect(resolved?.location?.uri).toBe('test:///foo')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,99 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import WorkspaceHandler from '../../handler/workspace'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import extensions from '../../extensions'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let handler: WorkspaceHandler
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
handler = helper.plugin.getHandler().workspace
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Workspace handler', () => {
|
||||
describe('methods', () => {
|
||||
it('should open log', async () => {
|
||||
await handler.openLog()
|
||||
let bufname = await nvim.call('bufname', ['%']) as string
|
||||
expect(bufname.endsWith('coc-nvim.log')).toBe(true)
|
||||
})
|
||||
|
||||
it('should get configuration of current document', async () => {
|
||||
let config = await handler.getConfiguration('suggest')
|
||||
let wait = config.get<number>('triggerCompletionWait')
|
||||
expect(wait).toBe(0)
|
||||
})
|
||||
|
||||
it('should get root patterns', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
let patterns = handler.getRootPatterns(doc.bufnr)
|
||||
expect(patterns).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('doKeymap()', () => {
|
||||
it('should return default value when key mapping does not exist', async () => {
|
||||
let res = await handler.doKeymap('not_exists', '', '<C-a')
|
||||
expect(res).toBe('')
|
||||
})
|
||||
|
||||
it('should support repeat key mapping', async () => {
|
||||
let called = false
|
||||
await nvim.command('nmap do <Plug>(coc-test)')
|
||||
disposables.push(workspace.registerKeymap(['n'], 'test', () => {
|
||||
called = true
|
||||
}, { repeat: true, silent: true, sync: false }))
|
||||
await helper.wait(100)
|
||||
await nvim.call('feedkeys', ['do', 'i'])
|
||||
await helper.wait(30)
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('snippetCheck()', () => {
|
||||
it('should return false when coc-snippets not found', async () => {
|
||||
expect(await handler.snippetCheck(true, false)).toBe(false)
|
||||
})
|
||||
|
||||
it('should check jump', async () => {
|
||||
expect(await handler.snippetCheck(false, true)).toBe(false)
|
||||
})
|
||||
|
||||
it('should check expand by coc-snippets', async () => {
|
||||
let has = extensions.has
|
||||
let getExtensionApi = extensions.getExtensionApi
|
||||
extensions.has = () => {
|
||||
return true
|
||||
}
|
||||
extensions.getExtensionApi = () => {
|
||||
return {
|
||||
expandable: () => {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
extensions.has = has
|
||||
extensions.getExtensionApi = getExtensionApi
|
||||
}
|
||||
})
|
||||
let res = await handler.snippetCheck(true, false)
|
||||
expect(res).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,22 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import Plugin from '../plugin'
|
||||
import helper from './helper'
|
||||
|
||||
let nvim: Neovim
|
||||
let plugin: Plugin
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
plugin = helper.plugin
|
||||
})
|
||||
|
||||
describe('Helper', () => {
|
||||
it('should setup', () => {
|
||||
expect(nvim).toBeTruthy()
|
||||
expect(plugin.isReady).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
|
@ -1,360 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { Buffer, Neovim, Window } from '@chemzqm/neovim'
|
||||
import * as cp from 'child_process'
|
||||
import { EventEmitter } from 'events'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import attach from '../attach'
|
||||
import completion from '../completion'
|
||||
import events from '../events'
|
||||
import Document from '../model/document'
|
||||
import Plugin from '../plugin'
|
||||
import { OutputChannel, VimCompleteItem } from '../types'
|
||||
import { terminate } from '../util/processes'
|
||||
import workspace from '../workspace'
|
||||
|
||||
export interface CursorPosition {
|
||||
bufnum: number
|
||||
lnum: number
|
||||
col: number
|
||||
}
|
||||
|
||||
const nullChannel: OutputChannel = {
|
||||
content: '',
|
||||
show: () => {},
|
||||
dispose: () => {},
|
||||
name: 'null',
|
||||
append: () => {},
|
||||
appendLine: () => {},
|
||||
clear: () => {},
|
||||
hide: () => {}
|
||||
}
|
||||
|
||||
process.on('uncaughtException', err => {
|
||||
let msg = 'Uncaught exception: ' + err.stack
|
||||
console.error(msg)
|
||||
})
|
||||
export class Helper extends EventEmitter {
|
||||
public nvim: Neovim
|
||||
public proc: cp.ChildProcess
|
||||
public plugin: Plugin
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.setMaxListeners(99)
|
||||
}
|
||||
|
||||
public setupNvim(): void {
|
||||
const vimrc = path.resolve(__dirname, 'vimrc')
|
||||
let proc = this.proc = cp.spawn(process.env.COC_TEST_NVIM ?? 'nvim', ['-u', vimrc, '-i', 'NONE', '--embed'], {
|
||||
cwd: __dirname
|
||||
})
|
||||
let plugin = attach({ proc })
|
||||
this.nvim = plugin.nvim
|
||||
}
|
||||
|
||||
public setup(): Promise<void> {
|
||||
const vimrc = path.resolve(__dirname, 'vimrc')
|
||||
let proc = this.proc = cp.spawn('nvim', ['-u', vimrc, '-i', 'NONE', '--embed'], {
|
||||
cwd: __dirname
|
||||
})
|
||||
let plugin = this.plugin = attach({ proc })
|
||||
this.nvim = plugin.nvim
|
||||
this.nvim.uiAttach(160, 80, {}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
this.nvim.on('notification', (method, args) => {
|
||||
if (method == 'redraw') {
|
||||
for (let arg of args) {
|
||||
let event = arg[0]
|
||||
this.emit(event, arg.slice(1))
|
||||
if (event == 'put') {
|
||||
let arr = arg.slice(1).map(o => o[0])
|
||||
let line = arr.join('').trim()
|
||||
if (line.length > 3) {
|
||||
// console.log(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return new Promise(resolve => {
|
||||
plugin.once('ready', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async shutdown(): Promise<void> {
|
||||
if (this.plugin) this.plugin.dispose()
|
||||
this.nvim.removeAllListeners()
|
||||
this.nvim = null
|
||||
if (this.proc) {
|
||||
this.proc.unref()
|
||||
terminate(this.proc)
|
||||
this.proc = null
|
||||
}
|
||||
await this.wait(60)
|
||||
}
|
||||
|
||||
public async waitPopup(): Promise<void> {
|
||||
let visible = await this.nvim.call('pumvisible')
|
||||
if (visible) return
|
||||
let res = await events.race(['MenuPopupChanged'], 5000)
|
||||
if (!res) throw new Error('wait pum timeout after 5s')
|
||||
}
|
||||
|
||||
public async waitPreviewWindow(): Promise<void> {
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await this.wait(50)
|
||||
let has = await this.nvim.call('coc#list#has_preview')
|
||||
if (has > 0) return
|
||||
}
|
||||
throw new Error('timeout after 2s')
|
||||
}
|
||||
|
||||
public async waitPrompt(): Promise<void> {
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await this.wait(50)
|
||||
let prompt = await this.nvim.call('coc#prompt#activated')
|
||||
if (prompt) return
|
||||
}
|
||||
throw new Error('Wait prompt timeout after 2s')
|
||||
}
|
||||
|
||||
public async waitFloat(): Promise<number> {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
await this.wait(20)
|
||||
let winid = await this.nvim.call('GetFloatWin')
|
||||
if (winid) return winid
|
||||
}
|
||||
throw new Error('timeout after 2s')
|
||||
}
|
||||
|
||||
public async selectCompleteItem(idx: number): Promise<void> {
|
||||
await this.nvim.call('nvim_select_popupmenu_item', [idx, true, true, {}])
|
||||
}
|
||||
|
||||
public async doAction(method: string, ...args: any[]): Promise<any> {
|
||||
return await this.plugin.cocAction(method, ...args)
|
||||
}
|
||||
|
||||
public async synchronize(): Promise<void> {
|
||||
let doc = await workspace.document
|
||||
doc.forceSync()
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
let mode = await this.nvim.mode
|
||||
if (mode.blocking && mode.mode == 'r') {
|
||||
await this.nvim.input('<cr>')
|
||||
} else if (mode.mode != 'n' || mode.blocking) {
|
||||
await this.nvim.call('feedkeys', [String.fromCharCode(27), 'in'])
|
||||
}
|
||||
completion.stop()
|
||||
workspace.reset()
|
||||
await this.nvim.command('silent! %bwipeout!')
|
||||
await this.nvim.command('setl nopreviewwindow')
|
||||
await this.wait(30)
|
||||
await workspace.document
|
||||
}
|
||||
|
||||
public async pumvisible(): Promise<boolean> {
|
||||
let res = await this.nvim.call('pumvisible', []) as number
|
||||
return res == 1
|
||||
}
|
||||
|
||||
public wait(ms = 30): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
|
||||
public async visible(word: string, source?: string): Promise<boolean> {
|
||||
await this.waitPopup()
|
||||
let context = await this.nvim.getVar('coc#_context') as any
|
||||
let items = context.candidates
|
||||
if (!items) return false
|
||||
let item = items.find(o => o.word == word)
|
||||
if (!item || !item.user_data) return false
|
||||
try {
|
||||
let arr = item.user_data.split(':', 2)
|
||||
if (source && arr[0] !== source) {
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public async notVisible(word: string): Promise<boolean> {
|
||||
let items = await this.getItems()
|
||||
return items.findIndex(o => o.word == word) == -1
|
||||
}
|
||||
|
||||
public async getItems(): Promise<VimCompleteItem[]> {
|
||||
let visible = await this.pumvisible()
|
||||
if (!visible) return []
|
||||
let context = await this.nvim.getVar('coc#_context') as any
|
||||
let items = context.candidates
|
||||
return items || []
|
||||
}
|
||||
|
||||
public async edit(file?: string): Promise<Buffer> {
|
||||
if (!file || !path.isAbsolute(file)) {
|
||||
file = path.join(__dirname, file ? file : `${uuid()}`)
|
||||
}
|
||||
let escaped = await this.nvim.call('fnameescape', file) as string
|
||||
await this.nvim.command(`edit ${escaped}`)
|
||||
let doc = await workspace.document
|
||||
return doc.buffer
|
||||
}
|
||||
|
||||
public async createDocument(name?: string): Promise<Document> {
|
||||
let buf = await this.edit(name)
|
||||
let doc = workspace.getDocument(buf.id)
|
||||
if (!doc) return await workspace.document
|
||||
return doc
|
||||
}
|
||||
|
||||
public async listInput(input: string): Promise<void> {
|
||||
await events.fire('InputChar', ['list', input, 0])
|
||||
}
|
||||
|
||||
public async getMarkers(bufnr: number, ns: number): Promise<[number, number, number][]> {
|
||||
return await this.nvim.call('nvim_buf_get_extmarks', [bufnr, ns, 0, -1, {}]) as [number, number, number][]
|
||||
}
|
||||
|
||||
public async getCmdline(): Promise<string> {
|
||||
let str = ''
|
||||
for (let i = 1, l = 70; i < l; i++) {
|
||||
let ch = await this.nvim.call('screenchar', [79, i])
|
||||
if (ch == -1) break
|
||||
str += String.fromCharCode(ch)
|
||||
}
|
||||
return str.trim()
|
||||
}
|
||||
|
||||
public updateConfiguration(key: string, value: any): () => void {
|
||||
let { configurations } = workspace
|
||||
let curr = workspace.getConfiguration(key)
|
||||
configurations.updateUserConfig({ [key]: value })
|
||||
return () => {
|
||||
configurations.updateUserConfig({ [key]: curr })
|
||||
}
|
||||
}
|
||||
|
||||
public async mockFunction(name: string, result: string | number | any): Promise<void> {
|
||||
let content = `
|
||||
function! ${name}(...)
|
||||
return ${typeof result == 'number' ? result : JSON.stringify(result)}
|
||||
endfunction`
|
||||
await this.nvim.exec(content)
|
||||
}
|
||||
|
||||
public async items(): Promise<VimCompleteItem[]> {
|
||||
let context = await this.nvim.getVar('coc#_context')
|
||||
return context['candidates'] || []
|
||||
}
|
||||
|
||||
public async screenLine(line: number): Promise<string> {
|
||||
let res = ''
|
||||
for (let i = 1; i <= 80; i++) {
|
||||
let ch = await this.nvim.call('screenchar', [line, i])
|
||||
res = res + String.fromCharCode(ch)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
public async getWinLines(winid: number): Promise<string[]> {
|
||||
return await this.nvim.eval(`getbufline(winbufnr(${winid}), 1, '$')`) as string[]
|
||||
}
|
||||
|
||||
public async getFloat(): Promise<Window> {
|
||||
let wins = await this.nvim.windows
|
||||
let floatWin: Window
|
||||
for (let win of wins) {
|
||||
let f = await win.getVar('float')
|
||||
if (f) floatWin = win
|
||||
}
|
||||
return floatWin
|
||||
}
|
||||
|
||||
public async getFloats(): Promise<Window[]> {
|
||||
let ids = await this.nvim.call('coc#float#get_float_win_list', [])
|
||||
if (!ids) return []
|
||||
return ids.map(id => this.nvim.createWindow(id))
|
||||
}
|
||||
|
||||
public async getExtmarkers(bufnr: number, ns: number): Promise<[number, number, number, number, string][]> {
|
||||
let res = await this.nvim.call('nvim_buf_get_extmarks', [bufnr, ns, 0, -1, { details: true }]) as any
|
||||
return res.map(o => {
|
||||
return [o[1], o[2], o[3].end_row, o[3].end_col, o[3].hl_group]
|
||||
})
|
||||
}
|
||||
|
||||
public async waitFor<T>(method: string, args: any[], value: T): Promise<void> {
|
||||
let find = false
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await this.wait(50)
|
||||
let res = await this.nvim.call(method, args) as T
|
||||
if (res == value || (value instanceof RegExp && value.test(res.toString()))) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!find) {
|
||||
throw new Error(`waitFor ${value} timeout`)
|
||||
}
|
||||
}
|
||||
|
||||
public async waitValue<T>(fn: () => T, value: T): Promise<void> {
|
||||
let find = false
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await this.wait(50)
|
||||
let res = fn()
|
||||
if (res == value) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!find) {
|
||||
throw new Error(`waitValue ${value} timeout`)
|
||||
}
|
||||
}
|
||||
|
||||
public createNullChannel(): OutputChannel {
|
||||
return nullChannel
|
||||
}
|
||||
}
|
||||
|
||||
export function rmdir(dir: string): void {
|
||||
if (typeof fs['rm'] === 'function') {
|
||||
fs['rmSync'](dir, { recursive: true })
|
||||
} else {
|
||||
fs.rmdirSync(dir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTmpFile(content: string, disposables?: Disposable[]): Promise<string> {
|
||||
let tmpFolder = path.join(os.tmpdir(), `coc-${process.pid}`)
|
||||
if (!fs.existsSync(tmpFolder)) {
|
||||
fs.mkdirSync(tmpFolder)
|
||||
}
|
||||
let fsPath = path.join(tmpFolder, uuid())
|
||||
await util.promisify(fs.writeFile)(fsPath, content, 'utf8')
|
||||
if (disposables) {
|
||||
disposables.push(Disposable.create(() => {
|
||||
if (fs.existsSync(fsPath)) fs.unlinkSync(fsPath)
|
||||
}))
|
||||
}
|
||||
return fsPath
|
||||
}
|
||||
|
||||
export default new Helper()
|
|
@ -1,136 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import path from 'path'
|
||||
import { ListContext, ListTask } from '../../types'
|
||||
import manager from '../../list/manager'
|
||||
import helper, { createTmpFile } from '../helper'
|
||||
import BasicList from '../../list/basic'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import { disposeAll } from '../../util'
|
||||
|
||||
class DataList extends BasicList {
|
||||
public name = 'data'
|
||||
public async loadItems(_context: ListContext): Promise<ListTask> {
|
||||
let fsPath = await createTmpFile(`console.log('foo');console.log('');console.log('bar');`)
|
||||
return this.createCommandTask({
|
||||
cmd: 'node',
|
||||
args: [fsPath],
|
||||
cwd: path.dirname(fsPath),
|
||||
onLine: line => {
|
||||
if (!line) return undefined
|
||||
return {
|
||||
label: line
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class SleepList extends BasicList {
|
||||
public name = 'sleep'
|
||||
public loadItems(_context: ListContext): Promise<ListTask> {
|
||||
return Promise.resolve(this.createCommandTask({
|
||||
cmd: 'sleep',
|
||||
args: ['10'],
|
||||
onLine: line => {
|
||||
return {
|
||||
label: line
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
class StderrList extends BasicList {
|
||||
public name = 'stderr'
|
||||
public async loadItems(_context: ListContext): Promise<ListTask> {
|
||||
let fsPath = await createTmpFile(`console.error('stderr');console.log('stdout')`)
|
||||
return Promise.resolve(this.createCommandTask({
|
||||
cmd: 'node',
|
||||
args: [fsPath],
|
||||
cwd: path.dirname(fsPath),
|
||||
onLine: line => {
|
||||
return {
|
||||
label: line
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorTask extends BasicList {
|
||||
public name = 'error'
|
||||
public async loadItems(_context: ListContext): Promise<ListTask> {
|
||||
return Promise.resolve(this.createCommandTask({
|
||||
cmd: 'NOT_EXISTS',
|
||||
args: [],
|
||||
cwd: __dirname,
|
||||
onLine: line => {
|
||||
return {
|
||||
label: line
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('Command task', () => {
|
||||
it('should not show stderr', async () => {
|
||||
disposables.push(manager.registerList(new StderrList(nvim)))
|
||||
await manager.start(['stderr'])
|
||||
await manager.session.ui.ready
|
||||
let lines = await nvim.call('getline', [1, '$']) as string[]
|
||||
expect(lines).toEqual(['stdout'])
|
||||
})
|
||||
|
||||
it('should show error for bad key', async () => {
|
||||
let list = new DataList(nvim)
|
||||
list.config.fixKey('<X-a>')
|
||||
await helper.wait(200)
|
||||
await nvim.command('redraw')
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('not supported')
|
||||
})
|
||||
|
||||
it('should not show error', async () => {
|
||||
disposables.push(manager.registerList(new ErrorTask(nvim)))
|
||||
await manager.start(['error'])
|
||||
await helper.wait(300)
|
||||
await nvim.command('redraw')
|
||||
let len = manager.session.ui.length
|
||||
expect(len).toBe(0)
|
||||
})
|
||||
|
||||
it('should create command task', async () => {
|
||||
let list = new DataList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['data'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
let lines = await nvim.call('getline', [1, '$']) as string[]
|
||||
expect(lines).toEqual(['foo', 'bar'])
|
||||
})
|
||||
|
||||
it('should stop command task', async () => {
|
||||
let list = new SleepList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['sleep'])
|
||||
manager.session.stop()
|
||||
})
|
||||
})
|
|
@ -1,508 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import path from 'path'
|
||||
import manager from '../../list/manager'
|
||||
import events from '../../events'
|
||||
import { QuickfixItem, IList, ListItem } from '../../types'
|
||||
import helper from '../helper'
|
||||
|
||||
let nvim: Neovim
|
||||
const locations: ReadonlyArray<QuickfixItem> = [{
|
||||
filename: __filename,
|
||||
col: 2,
|
||||
lnum: 1,
|
||||
text: 'foo'
|
||||
}, {
|
||||
filename: __filename,
|
||||
col: 1,
|
||||
lnum: 2,
|
||||
text: 'Bar'
|
||||
}, {
|
||||
filename: __filename,
|
||||
col: 1,
|
||||
lnum: 3,
|
||||
text: 'option'
|
||||
}]
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
await nvim.setVar('coc_jump_locations', locations)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
describe('list', () => {
|
||||
describe('events', () => {
|
||||
it('should cancel and enable prompt', async () => {
|
||||
let winid = await nvim.call('win_getid')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await helper.wait(50)
|
||||
let res = await nvim.call('coc#prompt#activated')
|
||||
expect(res).toBe(0)
|
||||
await nvim.command('wincmd p')
|
||||
await helper.wait(50)
|
||||
res = await nvim.call('coc#prompt#activated')
|
||||
expect(res).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('list commands', () => {
|
||||
it('should be activated', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(50)
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let line = await nvim.getLine()
|
||||
expect(line).toMatch(/manager.test.ts/)
|
||||
})
|
||||
|
||||
it('should get list names', () => {
|
||||
let names = manager.names
|
||||
expect(names.length > 0).toBe(true)
|
||||
})
|
||||
|
||||
it('should resume list', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(30)
|
||||
await nvim.eval('feedkeys("j", "in")')
|
||||
await helper.wait(30)
|
||||
let line = await nvim.call('line', '.')
|
||||
expect(line).toBe(2)
|
||||
await manager.cancel()
|
||||
await helper.wait(30)
|
||||
await manager.resume()
|
||||
await helper.wait(30)
|
||||
line = await nvim.call('line', '.')
|
||||
expect(line).toBe(2)
|
||||
})
|
||||
|
||||
it('should not quit list with --no-quit', async () => {
|
||||
await manager.start(['--normal', '--no-quit', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let winnr = await nvim.eval('win_getid()') as number
|
||||
await manager.doAction()
|
||||
await helper.wait(100)
|
||||
let wins = await nvim.windows
|
||||
let ids = wins.map(o => o.id)
|
||||
expect(ids).toContain(winnr)
|
||||
})
|
||||
|
||||
it('should do default action for first item', async () => {
|
||||
await manager.start(['--normal', '--first', 'location'])
|
||||
await helper.wait(300)
|
||||
let name = await nvim.eval('bufname("%")') as string
|
||||
let filename = path.basename(__filename)
|
||||
expect(name.includes(filename)).toBe(true)
|
||||
let pos = await nvim.eval('getcurpos()')
|
||||
expect(pos[1]).toBe(1)
|
||||
expect(pos[2]).toBe(2)
|
||||
})
|
||||
|
||||
it('should goto next & previous', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session?.ui.ready
|
||||
await helper.wait(60)
|
||||
await manager.doAction()
|
||||
await manager.cancel()
|
||||
let bufname = await nvim.eval('expand("%:p")')
|
||||
expect(bufname).toMatch('manager.test.ts')
|
||||
await manager.next()
|
||||
let line = await nvim.call('line', '.')
|
||||
expect(line).toBe(2)
|
||||
await helper.wait(60)
|
||||
await manager.previous()
|
||||
line = await nvim.call('line', '.')
|
||||
expect(line).toBe(1)
|
||||
})
|
||||
|
||||
it('should parse arguments', async () => {
|
||||
await manager.start(['--input=test', '--reverse', '--normal', '--no-sort', '--ignore-case', '--top', '--number-select', '--auto-preview', '--strict', 'location'])
|
||||
await helper.wait(30)
|
||||
let opts = manager.session?.listOptions
|
||||
expect(opts).toEqual({
|
||||
reverse: true,
|
||||
numberSelect: true,
|
||||
autoPreview: true,
|
||||
first: false,
|
||||
input: 'test',
|
||||
interactive: false,
|
||||
matcher: 'strict',
|
||||
ignorecase: true,
|
||||
position: 'top',
|
||||
mode: 'normal',
|
||||
noQuit: false,
|
||||
sort: false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('list configuration', () => {
|
||||
it('should change indicator', async () => {
|
||||
helper.updateConfiguration('list.indicator', '>>')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(200)
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toMatch('>>')
|
||||
})
|
||||
|
||||
it('should split right for preview window', async () => {
|
||||
helper.updateConfiguration('list.previewSplitRight', true)
|
||||
let win = await nvim.window
|
||||
await manager.start(['location'])
|
||||
await helper.wait(100)
|
||||
await manager.doAction('preview')
|
||||
await helper.wait(100)
|
||||
manager.prompt.cancel()
|
||||
await helper.wait(10)
|
||||
await nvim.call('win_gotoid', [win.id])
|
||||
await nvim.command('wincmd l')
|
||||
let curr = await nvim.window
|
||||
let isPreview = await curr.getVar('previewwindow')
|
||||
expect(isPreview).toBe(1)
|
||||
})
|
||||
|
||||
it('should toggle selection mode', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session?.ui.ready
|
||||
await nvim.input('V')
|
||||
await helper.wait(30)
|
||||
await nvim.input('1')
|
||||
await helper.wait(30)
|
||||
await nvim.input('j')
|
||||
await helper.wait(100)
|
||||
await manager.session?.ui.toggleSelection()
|
||||
let items = await manager.session?.ui.getItems()
|
||||
expect(items.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should change next/previous keymap', async () => {
|
||||
helper.updateConfiguration('list.nextKeymap', '<tab>')
|
||||
helper.updateConfiguration('list.previousKeymap', '<s-tab>')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
await nvim.eval('feedkeys("\\<tab>", "in")')
|
||||
await helper.wait(100)
|
||||
let line = await nvim.line
|
||||
expect(line).toMatch('Bar')
|
||||
await nvim.eval('feedkeys("\\<s-tab>", "in")')
|
||||
await helper.wait(100)
|
||||
line = await nvim.line
|
||||
expect(line).toMatch('foo')
|
||||
})
|
||||
|
||||
it('should respect mouse events', async () => {
|
||||
async function setMouseEvent(line: number): Promise<void> {
|
||||
let winid = manager.session?.ui.winid
|
||||
await nvim.command(`let v:mouse_winid = ${winid}`)
|
||||
await nvim.command(`let v:mouse_lnum = ${line}`)
|
||||
await nvim.command(`let v:mouse_col = 1`)
|
||||
}
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
await setMouseEvent(1)
|
||||
await manager.onNormalInput('<LeftMouse>')
|
||||
await setMouseEvent(2)
|
||||
await manager.onNormalInput('<LeftDrag>')
|
||||
await setMouseEvent(3)
|
||||
await manager.onNormalInput('<LeftRelease>')
|
||||
await helper.wait(100)
|
||||
let items = await manager.session?.ui.getItems()
|
||||
expect(items.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should toggle preview', async () => {
|
||||
await manager.start(['--normal', '--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
await manager.togglePreview()
|
||||
await helper.wait(100)
|
||||
await manager.togglePreview()
|
||||
await helper.wait(100)
|
||||
let has = await nvim.call('coc#list#has_preview')
|
||||
expect(has).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should show help of current list', async () => {
|
||||
await manager.start(['--normal', '--auto-preview', 'location'])
|
||||
await helper.wait(200)
|
||||
await manager.session?.showHelp()
|
||||
let bufname = await nvim.call('bufname', '%')
|
||||
expect(bufname).toBe('[LIST HELP]')
|
||||
})
|
||||
|
||||
it('should resolve list item', async () => {
|
||||
let list: IList = {
|
||||
name: 'test',
|
||||
actions: [{
|
||||
name: 'open', execute: _item => {
|
||||
// noop
|
||||
}
|
||||
}],
|
||||
defaultAction: 'open',
|
||||
loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }]),
|
||||
resolveItem: item => {
|
||||
item.label = item.label.slice(0, 1)
|
||||
return Promise.resolve(item)
|
||||
}
|
||||
}
|
||||
let disposable = manager.registerList(list)
|
||||
await manager.start(['--normal', 'test'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(50)
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('f')
|
||||
disposable.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
describe('descriptions', () => {
|
||||
it('should get descriptions', async () => {
|
||||
let res = manager.descriptions
|
||||
expect(res).toBeDefined()
|
||||
expect(res.location).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadItems()', () => {
|
||||
it('should load items for list', async () => {
|
||||
let res = await manager.loadItems('location')
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
; (manager as any).lastSession = undefined
|
||||
manager.toggleMode()
|
||||
manager.stop()
|
||||
})
|
||||
})
|
||||
|
||||
describe('onInsertInput()', () => {
|
||||
it('should handle insert input', async () => {
|
||||
await manager.onInsertInput('k')
|
||||
await manager.onInsertInput('<LeftMouse>')
|
||||
await manager.start(['--number-select', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.onInsertInput('1')
|
||||
await helper.wait(300)
|
||||
let bufname = await nvim.call('expand', ['%:p'])
|
||||
expect(bufname).toMatch('manager.test.ts')
|
||||
})
|
||||
|
||||
it('should ignore invalid input', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.onInsertInput('<X-y>')
|
||||
await manager.onInsertInput(String.fromCharCode(65533))
|
||||
await manager.onInsertInput(String.fromCharCode(30))
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should ignore <plug> insert', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.eval('feedkeys("\\<plug>x", "in")')
|
||||
await helper.wait(50)
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseArgs()', () => {
|
||||
it('should show error for bad option', async () => {
|
||||
manager.parseArgs(['$x', 'location'])
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('Invalid list option')
|
||||
})
|
||||
|
||||
it('should show error for option that does not exist', async () => {
|
||||
manager.parseArgs(['-xyz', 'location'])
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('Invalid option')
|
||||
})
|
||||
|
||||
it('should show error for interactive with list not support interactive', async () => {
|
||||
manager.parseArgs(['--interactive', 'location'])
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('not supported')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resume()', () => {
|
||||
it('should resume by name', async () => {
|
||||
await events.fire('FocusGained', [])
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.session.hide()
|
||||
await helper.wait(100)
|
||||
await manager.resume('location')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('first(), last()', () => {
|
||||
it('should get session by name', async () => {
|
||||
let last: string
|
||||
let list: IList = {
|
||||
name: 'test',
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: (item: ListItem) => {
|
||||
last = item.label
|
||||
}
|
||||
}],
|
||||
defaultAction: 'open',
|
||||
loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }])
|
||||
}
|
||||
manager.registerList(list)
|
||||
await manager.start(['test'])
|
||||
await manager.session.ui.ready
|
||||
await manager.first('a')
|
||||
await manager.last('a')
|
||||
await manager.first('test')
|
||||
expect(last).toBe('foo')
|
||||
await manager.last('test')
|
||||
expect(last).toBe('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerList()', () => {
|
||||
it('should recreat list', async () => {
|
||||
let list: IList = {
|
||||
name: 'test',
|
||||
actions: [{
|
||||
name: 'open', execute: _item => {
|
||||
// noop
|
||||
}
|
||||
}],
|
||||
defaultAction: 'open',
|
||||
loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }])
|
||||
}
|
||||
manager.registerList(list)
|
||||
helper.updateConfiguration('list.source.test.defaultAction', 'open')
|
||||
let disposable = manager.registerList(list)
|
||||
disposable.dispose()
|
||||
await helper.wait(30)
|
||||
let msg = await helper.getCmdline()
|
||||
expect(msg).toMatch('recreated')
|
||||
})
|
||||
})
|
||||
|
||||
describe('start()', () => {
|
||||
it('should show error when loadItems throws', async () => {
|
||||
let list: IList = {
|
||||
name: 'test',
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: (_item: ListItem) => {
|
||||
}
|
||||
}],
|
||||
defaultAction: 'open',
|
||||
loadItems: () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
}
|
||||
manager.registerList(list)
|
||||
await manager.start(['test'])
|
||||
await helper.wait(100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('list options', () => {
|
||||
it('should respect auto preview option', async () => {
|
||||
await manager.start(['--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.waitFor('winnr', ['$'], 3)
|
||||
let previewWinnr = await nvim.call('coc#list#has_preview')
|
||||
expect(previewWinnr).toBe(2)
|
||||
let bufnr = await nvim.call('winbufnr', previewWinnr)
|
||||
let buf = nvim.createBuffer(bufnr)
|
||||
let name = await buf.name
|
||||
expect(name).toMatch('manager.test.ts')
|
||||
await nvim.eval('feedkeys("j", "in")')
|
||||
await helper.wait(100)
|
||||
let winnr = await nvim.call('coc#list#has_preview')
|
||||
expect(winnr).toBe(previewWinnr)
|
||||
})
|
||||
|
||||
it('should respect input option', async () => {
|
||||
await manager.start(['--input=foo', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(30)
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toMatch('foo')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should respect regex filter', async () => {
|
||||
await manager.start(['--input=f.o', '--regex', 'location'])
|
||||
await helper.wait(200)
|
||||
let item = await manager.session?.ui.item
|
||||
expect(item.label).toMatch('foo')
|
||||
})
|
||||
|
||||
it('should respect normal option', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let line = await helper.getCmdline()
|
||||
expect(line).toBe('')
|
||||
})
|
||||
|
||||
it('should respect nosort option', async () => {
|
||||
await manager.start(['--ignore-case', '--no-sort', 'location'])
|
||||
await manager.session.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
await nvim.input('oo')
|
||||
await helper.wait(100)
|
||||
let line = await nvim.call('getline', ['.'])
|
||||
expect(line).toMatch('foo')
|
||||
})
|
||||
|
||||
it('should respect ignorecase option', async () => {
|
||||
await manager.start(['--ignore-case', '--strict', 'location'])
|
||||
await manager.session.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
await nvim.input('bar')
|
||||
await helper.wait(100)
|
||||
let n = manager.session?.ui.length
|
||||
expect(n).toBe(1)
|
||||
let line = await nvim.line
|
||||
expect(line).toMatch('Bar')
|
||||
})
|
||||
|
||||
it('should respect top option', async () => {
|
||||
await manager.start(['--top', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let nr = await nvim.call('winnr')
|
||||
expect(nr).toBe(1)
|
||||
})
|
||||
|
||||
it('should respect number select option', async () => {
|
||||
await manager.start(['--number-select', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
await nvim.eval('feedkeys("2", "in")')
|
||||
await helper.wait(100)
|
||||
let lnum = locations[1].lnum
|
||||
let curr = await nvim.call('line', '.')
|
||||
expect(lnum).toBe(curr)
|
||||
})
|
||||
|
||||
it('should respect tab option', async () => {
|
||||
await manager.start(['--tab', '--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(100)
|
||||
await nvim.command('wincmd l')
|
||||
let previewwindow = await nvim.eval('w:previewwindow')
|
||||
expect(previewwindow).toBe(1)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,764 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import path from 'path'
|
||||
import { CancellationToken, Disposable } from 'vscode-languageserver-protocol'
|
||||
import BasicList from '../../list/basic'
|
||||
import manager from '../../list/manager'
|
||||
import { IList, ListContext, ListItem, QuickfixItem } from '../../types'
|
||||
import { disposeAll } from '../../util/index'
|
||||
import window from '../../window'
|
||||
import helper from '../helper'
|
||||
|
||||
class TestList extends BasicList {
|
||||
public name = 'test'
|
||||
public timeout = 3000
|
||||
public text = 'test'
|
||||
public detail = 'detail'
|
||||
public loadItems(_context: ListContext, token: CancellationToken): Promise<ListItem[]> {
|
||||
return new Promise(resolve => {
|
||||
let timer = setTimeout(() => {
|
||||
resolve([{ label: this.text }])
|
||||
}, this.timeout)
|
||||
token.onCancellationRequested(() => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
const locations: ReadonlyArray<QuickfixItem> = [{
|
||||
filename: __filename,
|
||||
col: 2,
|
||||
lnum: 1,
|
||||
text: 'foo'
|
||||
}, {
|
||||
filename: __filename,
|
||||
col: 1,
|
||||
lnum: 2,
|
||||
text: 'Bar'
|
||||
}, {
|
||||
filename: __filename,
|
||||
col: 1,
|
||||
lnum: 3,
|
||||
text: 'option'
|
||||
}]
|
||||
|
||||
const lineList: IList = {
|
||||
name: 'lines',
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: async item => {
|
||||
await window.moveTo({
|
||||
line: (item as ListItem).data.line,
|
||||
character: 0
|
||||
})
|
||||
// noop
|
||||
}
|
||||
}],
|
||||
defaultAction: 'open',
|
||||
async loadItems(_context, _token): Promise<ListItem[]> {
|
||||
let lines = []
|
||||
for (let i = 0; i < 100; i++) {
|
||||
lines.push(i.toString())
|
||||
}
|
||||
return lines.map((line, idx) => ({
|
||||
label: line,
|
||||
data: { line: idx }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
await nvim.setVar('coc_jump_locations', locations)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
disposeAll(disposables)
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('isValidAction()', () => {
|
||||
it('should check invalid action', async () => {
|
||||
let mappings = manager.mappings
|
||||
expect(mappings.isValidAction('foo')).toBe(false)
|
||||
expect(mappings.isValidAction('do:switch')).toBe(true)
|
||||
expect(mappings.isValidAction('eval:@*')).toBe(true)
|
||||
expect(mappings.isValidAction('undefined:undefined')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('User mappings', () => {
|
||||
it('should show warning for invalid key', async () => {
|
||||
let revert = helper.updateConfiguration('list.insertMappings', {
|
||||
xy: 'action:tabe',
|
||||
})
|
||||
await helper.wait(30)
|
||||
let msg = await helper.getCmdline()
|
||||
revert()
|
||||
await nvim.command('echo ""')
|
||||
expect(msg).toMatch('Invalid configuration')
|
||||
revert = helper.updateConfiguration('list.insertMappings', {
|
||||
'<M-x>': 'action:tabe',
|
||||
})
|
||||
await helper.wait(30)
|
||||
msg = await helper.getCmdline()
|
||||
revert()
|
||||
expect(msg).toMatch('Invalid configuration')
|
||||
revert = helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-a>': 'foo:bar',
|
||||
})
|
||||
await helper.wait(30)
|
||||
msg = await helper.getCmdline()
|
||||
revert()
|
||||
expect(msg).toMatch('Invalid configuration')
|
||||
})
|
||||
|
||||
it('should execute action keymap', async () => {
|
||||
let revert = helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-d>': 'action:quickfix',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-d>')
|
||||
let buftype = await nvim.eval('&buftype')
|
||||
expect(buftype).toBe('quickfix')
|
||||
revert()
|
||||
})
|
||||
|
||||
it('should execute expr keymap', async () => {
|
||||
await helper.mockFunction('TabOpen', 'quickfix')
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-t>': 'expr:TabOpen',
|
||||
})
|
||||
helper.updateConfiguration('list.normalMappings', {
|
||||
t: 'expr:TabOpen',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-t>')
|
||||
let buftype = await nvim.eval('&buftype')
|
||||
expect(buftype).toBe('quickfix')
|
||||
await nvim.command('close')
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('t')
|
||||
buftype = await nvim.eval('&buftype')
|
||||
expect(buftype).toBe('quickfix')
|
||||
})
|
||||
|
||||
it('should execute do mappings', async () => {
|
||||
helper.updateConfiguration('list.previousKeymap', '<C-j>')
|
||||
helper.updateConfiguration('list.nextKeymap', '<C-k>')
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-n>': 'do:next',
|
||||
'<C-p>': 'do:previous',
|
||||
'<C-d>': 'do:exit',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-n>')
|
||||
let item = await manager.session?.ui.item
|
||||
expect(item.label).toMatch(locations[1].text)
|
||||
await helper.listInput('<C-p>')
|
||||
item = await manager.session?.ui.item
|
||||
expect(item.label).toMatch(locations[0].text)
|
||||
await helper.listInput('<C-k>')
|
||||
item = await manager.session?.ui.item
|
||||
expect(item.label).toMatch(locations[1].text)
|
||||
await helper.listInput('<C-j>')
|
||||
item = await manager.session?.ui.item
|
||||
expect(item.label).toMatch(locations[0].text)
|
||||
await helper.listInput('<C-d>')
|
||||
expect(manager.isActivated).toBe(false)
|
||||
})
|
||||
|
||||
it('should execute prompt mappings', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-p>': 'prompt:previous',
|
||||
'<C-n>': 'prompt:next',
|
||||
'<C-a>': 'prompt:start',
|
||||
'<C-e>': 'prompt:end',
|
||||
'<Left>': 'prompt:left',
|
||||
'<Right>': 'prompt:right',
|
||||
'<backspace>': 'prompt:deleteforward',
|
||||
'<C-x>': 'prompt:deletebackward',
|
||||
'<C-k>': 'prompt:removetail',
|
||||
'<C-u>': 'prompt:removeahead',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
for (let key of ['<C-p>', '<C-n>', '<C-a>', '<C-e>', '<Left>', '<Right>', '<backspace>', '<C-x>', '<C-k>', '<C-u>']) {
|
||||
await helper.listInput(key)
|
||||
}
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should execute feedkeys keymap', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-f>': 'feedkeys:\\<C-f>',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-f>')
|
||||
let line = await nvim.call('line', '.')
|
||||
expect(line).toBe(locations.length)
|
||||
})
|
||||
|
||||
it('should execute normal keymap', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-g>': 'normal:G',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-g>')
|
||||
let line = await nvim.call('line', '.')
|
||||
expect(line).toBe(locations.length)
|
||||
})
|
||||
|
||||
it('should execute command keymap', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-w>': 'command:wincmd p',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-w>')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let winnr = await nvim.call('winnr')
|
||||
expect(winnr).toBe(1)
|
||||
})
|
||||
|
||||
it('should execute call keymap', async () => {
|
||||
await helper.mockFunction('Test', 1)
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-t>': 'call:Test',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-t>')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should insert clipboard register to prompt', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-r>': 'prompt:paste',
|
||||
})
|
||||
await nvim.command('let @* = "foobar"')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-r>')
|
||||
let { input } = manager.prompt
|
||||
expect(input).toMatch('foobar')
|
||||
await nvim.command('let @* = ""')
|
||||
await helper.listInput('<C-r>')
|
||||
expect(manager.prompt.input).toMatch('foobar')
|
||||
})
|
||||
|
||||
it('should insert text from default register to prompt', async () => {
|
||||
helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-v>': 'eval:@@',
|
||||
})
|
||||
await nvim.command('let @@ = "bar"')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-v>')
|
||||
let { input } = manager.prompt
|
||||
expect(input).toMatch('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('doAction()', () => {
|
||||
it('should throw when action not found', async () => {
|
||||
let mappings = manager.mappings
|
||||
let fn = async () => {
|
||||
await mappings.doAction('foo:bar')
|
||||
}
|
||||
await expect(fn()).rejects.toThrow(/doesn't exist/)
|
||||
})
|
||||
|
||||
it('should not throw when session does not exist', async () => {
|
||||
let mappings = manager.mappings
|
||||
await mappings.doAction('do:selectall')
|
||||
await mappings.doAction('do:help')
|
||||
await mappings.doAction('do:refresh')
|
||||
await mappings.doAction('do:toggle')
|
||||
await mappings.doAction('do:jumpback')
|
||||
await mappings.doAction('prompt:previous')
|
||||
await mappings.doAction('prompt:next')
|
||||
await mappings.doAction('do:refresh')
|
||||
})
|
||||
|
||||
it('should not throw when action name does not exist', async () => {
|
||||
await helper.mockFunction('MyExpr', '')
|
||||
let mappings = manager.mappings
|
||||
await mappings.doAction('expr', 'MyExpr')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAction()', () => {
|
||||
it('should throw for invalid action', async () => {
|
||||
let mappings = manager.mappings
|
||||
let fn = () => {
|
||||
mappings.getAction('foo')
|
||||
}
|
||||
expect(fn).toThrow(Error)
|
||||
fn = () => {
|
||||
mappings.getAction('do:bar')
|
||||
}
|
||||
expect(fn).toThrow(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Default normal mappings', () => {
|
||||
it('should invoke action', async () => {
|
||||
await manager.start(['--normal', '--no-quit', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let winid = manager.session.ui.winid
|
||||
await helper.listInput('t')
|
||||
let nr = await nvim.call('tabpagenr')
|
||||
expect(nr).toBe(2)
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await helper.listInput('s')
|
||||
let winnr = await nvim.call('winnr', ['$'])
|
||||
expect(winnr).toBe(3)
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await helper.listInput('d')
|
||||
let filename = await nvim.call('expand', ['%'])
|
||||
expect(filename).toMatch(path.basename(__filename))
|
||||
await nvim.call('win_gotoid', [winid])
|
||||
await helper.listInput('<cr>')
|
||||
filename = await nvim.call('expand', ['%'])
|
||||
expect(filename).toMatch(path.basename(__filename))
|
||||
})
|
||||
|
||||
it('should select all items by <C-a>', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-a>')
|
||||
let selected = manager.session?.ui.selectedItems
|
||||
expect(selected.length).toBe(locations.length)
|
||||
})
|
||||
|
||||
it('should stop by <C-c>', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-c>')
|
||||
let loading = manager.session?.worker.isLoading
|
||||
expect(loading).toBe(false)
|
||||
})
|
||||
|
||||
it('should jump back by <C-o>', async () => {
|
||||
let doc = await helper.createDocument()
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-o>')
|
||||
let bufnr = await nvim.call('bufnr', ['%'])
|
||||
expect(bufnr).toBe(doc.bufnr)
|
||||
})
|
||||
|
||||
it('should scroll preview window by <C-e>, <C-y>', async () => {
|
||||
await helper.createDocument()
|
||||
await manager.start(['--auto-preview', '--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.waitPreviewWindow()
|
||||
let winnr = await nvim.call('coc#list#has_preview') as number
|
||||
let winid = await nvim.call('win_getid', [winnr])
|
||||
await helper.listInput('<C-e>')
|
||||
let res = await nvim.call('getwininfo', [winid])
|
||||
expect(res[0].topline).toBeGreaterThan(1)
|
||||
await helper.listInput('<C-y>')
|
||||
res = await nvim.call('getwininfo', [winid])
|
||||
expect(res[0].topline).toBeLessThan(7)
|
||||
})
|
||||
|
||||
it('should insert command by :', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput(':')
|
||||
await nvim.eval('feedkeys("let g:x = 1\\<cr>", "in")')
|
||||
let res = await nvim.getVar('x')
|
||||
expect(res).toBe(1)
|
||||
})
|
||||
|
||||
it('should select action by <tab>', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let p = helper.listInput('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('t')
|
||||
await p
|
||||
let nr = await nvim.call('tabpagenr')
|
||||
expect(nr).toBe(2)
|
||||
})
|
||||
|
||||
it('should preview by p', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('p')
|
||||
let winnr = await nvim.call('coc#list#has_preview')
|
||||
expect(winnr).toBe(2)
|
||||
})
|
||||
|
||||
it('should stop task by <C-c>', async () => {
|
||||
disposables.push(manager.registerList(new TestList(nvim)))
|
||||
let p = manager.start(['--normal', 'test'])
|
||||
await helper.wait(50)
|
||||
await nvim.input('<C-c>')
|
||||
await p
|
||||
let len = manager.session?.ui.length
|
||||
expect(len).toBe(0)
|
||||
})
|
||||
|
||||
it('should cancel list by <esc>', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.eval('feedkeys("\\<esc>", "in")')
|
||||
await helper.waitValue(() => {
|
||||
return manager.isActivated
|
||||
}, false)
|
||||
})
|
||||
|
||||
it('should reload list by <C-l>', async () => {
|
||||
let list = new TestList(nvim)
|
||||
list.timeout = 0
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'test'])
|
||||
await manager.session.ui.ready
|
||||
list.text = 'new'
|
||||
await helper.listInput('<C-l>')
|
||||
await helper.wait(30)
|
||||
let line = await nvim.line
|
||||
expect(line).toMatch('new')
|
||||
})
|
||||
|
||||
it('should toggle selection <space>', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput(' ')
|
||||
let selected = manager.session?.ui.selectedItems
|
||||
expect(selected.length).toBe(1)
|
||||
await helper.listInput('k')
|
||||
await helper.listInput(' ')
|
||||
selected = manager.session?.ui.selectedItems
|
||||
expect(selected.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should change to insert mode by i, o, a', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
let keys = ['i', 'I', 'o', 'O', 'a', 'A']
|
||||
for (let key of keys) {
|
||||
await helper.listInput(key)
|
||||
let mode = manager.prompt.mode
|
||||
expect(mode).toBe('insert')
|
||||
await helper.listInput('<C-o>')
|
||||
mode = manager.prompt.mode
|
||||
expect(mode).toBe('normal')
|
||||
}
|
||||
})
|
||||
|
||||
it('should show help by ?', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('?')
|
||||
let bufname = await nvim.call('bufname', '%')
|
||||
expect(bufname).toBe('[LIST HELP]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('list insert mappings', () => {
|
||||
it('should open by <cr>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<cr>')
|
||||
let bufname = await nvim.call('expand', ['%:p'])
|
||||
expect(bufname).toMatch('mappings.test.ts')
|
||||
})
|
||||
|
||||
it('should paste input by <C-v>', async () => {
|
||||
await nvim.command('let @* = "foo"')
|
||||
await nvim.command('let @@ = "foo"')
|
||||
await nvim.call('setreg', ['*', 'foo'])
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-v>')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('foo')
|
||||
})
|
||||
|
||||
it('should insert register content by <C-r>', async () => {
|
||||
await nvim.command('let @* = "foo"')
|
||||
await nvim.command('let @@ = "foo"')
|
||||
await nvim.call('setreg', ['*', 'foo'])
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-r>')
|
||||
await helper.listInput('*')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('foo')
|
||||
await helper.listInput('<C-r>')
|
||||
await helper.listInput('<')
|
||||
input = manager.prompt.input
|
||||
expect(input).toBe('foo')
|
||||
manager.prompt.reset()
|
||||
})
|
||||
|
||||
it('should cancel by <esc>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<esc>')
|
||||
expect(manager.isActivated).toBe(false)
|
||||
})
|
||||
|
||||
it('should select action by insert <tab>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
let p = helper.listInput('<tab>')
|
||||
await helper.wait(50)
|
||||
await nvim.input('d')
|
||||
await p
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toMatch(path.basename(__filename))
|
||||
})
|
||||
|
||||
it('should select action for visual selected items', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(50)
|
||||
await nvim.input('V')
|
||||
await helper.wait(30)
|
||||
await nvim.input('2')
|
||||
await helper.wait(30)
|
||||
await nvim.input('j')
|
||||
await helper.wait(30)
|
||||
await manager.doAction('quickfix')
|
||||
let buftype = await nvim.eval('&buftype')
|
||||
expect(buftype).toBe('quickfix')
|
||||
})
|
||||
|
||||
it('should stop loading by <C-c>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-c>')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should reload by <C-l>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-l>')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should change to normal mode by <C-o>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-o>')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should select line by <down> and <up>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.eval('feedkeys("\\<down>", "in")')
|
||||
await nvim.eval('feedkeys("\\<up>", "in")')
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let line = await nvim.line
|
||||
expect(line).toMatch('foo')
|
||||
})
|
||||
|
||||
it('should move cursor by <left> and <right>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('f')
|
||||
await helper.listInput('<left>')
|
||||
await helper.listInput('<left>')
|
||||
await helper.listInput('a')
|
||||
await helper.listInput('<right>')
|
||||
await helper.listInput('<right>')
|
||||
await helper.listInput('c')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('afc')
|
||||
})
|
||||
|
||||
it('should move cursor by <end> and <home>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<home>')
|
||||
await helper.listInput('<end>')
|
||||
await helper.listInput('a')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('a')
|
||||
})
|
||||
|
||||
it('should move cursor by <PageUp> <PageDown> <C-d>', async () => {
|
||||
disposables.push(manager.registerList(lineList))
|
||||
await manager.start(['lines'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<PageDown>')
|
||||
await helper.listInput('<PageUp>')
|
||||
await helper.listInput('<C-d>')
|
||||
})
|
||||
|
||||
it('should scroll window by <C-f> and <C-b>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-f>')
|
||||
await helper.listInput('<C-b>')
|
||||
})
|
||||
|
||||
it('should change input by <Backspace>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('f')
|
||||
await helper.listInput('<backspace>')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('')
|
||||
})
|
||||
|
||||
it('should change input by <C-b>', async () => {
|
||||
let revert = helper.updateConfiguration('list.insertMappings', {
|
||||
'<C-b>': 'prompt:removetail',
|
||||
})
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('f')
|
||||
await helper.listInput('o')
|
||||
await helper.listInput('o')
|
||||
await helper.listInput('<C-a>')
|
||||
await helper.listInput('<C-b>')
|
||||
expect(manager.mappings.hasUserMapping('insert', '<C-b>')).toBe(true)
|
||||
let input = manager.prompt.input
|
||||
revert()
|
||||
expect(input).toBe('')
|
||||
})
|
||||
|
||||
it('should change input by <C-h>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('f')
|
||||
await helper.listInput('<C-h>')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('')
|
||||
})
|
||||
|
||||
it('should change input by <C-w>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('f')
|
||||
await helper.listInput('a')
|
||||
await helper.listInput('<C-w>')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('')
|
||||
})
|
||||
|
||||
it('should change input by <C-u>', async () => {
|
||||
await manager.start(['--input=a', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-u>')
|
||||
let input = manager.prompt.input
|
||||
expect(input).toBe('')
|
||||
})
|
||||
|
||||
it('should change input by <C-n> and <C-p>', async () => {
|
||||
async function session(input: string): Promise<void> {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
for (let ch of input) {
|
||||
await helper.listInput(ch)
|
||||
}
|
||||
await manager.cancel()
|
||||
}
|
||||
await session('foo')
|
||||
await session('bar')
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-n>')
|
||||
let input = manager.prompt.input
|
||||
expect(input.length).toBeGreaterThan(0)
|
||||
await helper.listInput('<C-p>')
|
||||
input = manager.prompt.input
|
||||
expect(input.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should change matcher by <C-s>', async () => {
|
||||
await manager.start(['location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('<C-s>')
|
||||
let matcher = manager.session?.listOptions.matcher
|
||||
expect(matcher).toBe('strict')
|
||||
await helper.listInput('<C-s>')
|
||||
matcher = manager.session?.listOptions.matcher
|
||||
expect(matcher).toBe('regex')
|
||||
await helper.listInput('f')
|
||||
let len = manager.session?.ui.length
|
||||
expect(len).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('evalExpression', () => {
|
||||
it('should exit list', async () => {
|
||||
helper.updateConfiguration('list.normalMappings', {
|
||||
t: 'do:exit',
|
||||
})
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
expect(manager.mappings.hasUserMapping('normal', 't')).toBe(true)
|
||||
await helper.listInput('t')
|
||||
expect(manager.isActivated).toBe(false)
|
||||
})
|
||||
|
||||
it('should cancel prompt', async () => {
|
||||
helper.updateConfiguration('list.normalMappings', {
|
||||
t: 'do:cancel',
|
||||
})
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('t')
|
||||
let res = await nvim.call('coc#prompt#activated')
|
||||
expect(res).toBe(0)
|
||||
})
|
||||
|
||||
it('should invoke normal command', async () => {
|
||||
let revert = helper.updateConfiguration('list.normalMappings', {
|
||||
x: 'normal!:G'
|
||||
})
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput('x')
|
||||
revert()
|
||||
let lnum = await nvim.call('line', ['.'])
|
||||
expect(lnum).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should toggle, scroll preview', async () => {
|
||||
let revert = helper.updateConfiguration('list.normalMappings', {
|
||||
'<space>': 'do:toggle',
|
||||
a: 'do:toggle',
|
||||
b: 'do:previewtoggle',
|
||||
c: 'do:previewup',
|
||||
d: 'do:previewdown',
|
||||
e: 'prompt:insertregister',
|
||||
f: 'do:stop',
|
||||
g: 'do:togglemode',
|
||||
})
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.listInput(' ')
|
||||
for (let key of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {
|
||||
await helper.listInput(key)
|
||||
}
|
||||
revert()
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
|
@ -1,304 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import BasicList from '../../list/basic'
|
||||
import manager from '../../list/manager'
|
||||
import ListSession from '../../list/session'
|
||||
import { ListItem, IList } from '../../types'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let labels: string[] = []
|
||||
let lastItem: string
|
||||
let lastItems: ListItem[]
|
||||
|
||||
class SimpleList extends BasicList {
|
||||
public name = 'simple'
|
||||
public detail = 'detail'
|
||||
public options = [{
|
||||
name: 'foo',
|
||||
description: 'foo'
|
||||
}]
|
||||
constructor(nvim: Neovim) {
|
||||
super(nvim)
|
||||
this.addAction('open', item => {
|
||||
lastItem = item.label
|
||||
})
|
||||
this.addMultipleAction('multiple', items => {
|
||||
lastItems = items
|
||||
})
|
||||
this.addAction('parallel', async () => {
|
||||
await helper.wait(100)
|
||||
}, { parallel: true })
|
||||
this.addAction('reload', item => {
|
||||
lastItem = item.label
|
||||
}, { persist: true, reload: true })
|
||||
}
|
||||
public loadItems(): Promise<ListItem[]> {
|
||||
return Promise.resolve(labels.map(s => {
|
||||
return { label: s } as ListItem
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('list session', () => {
|
||||
describe('doDefaultAction()', () => {
|
||||
it('should throw error when default action does not exist', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
list.defaultAction = 'foo'
|
||||
let len = list.actions.length
|
||||
list.actions.splice(0, len)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
let err
|
||||
try {
|
||||
await manager.session.first()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
err = null
|
||||
try {
|
||||
await manager.session.last()
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
expect(err).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('doItemAction()', () => {
|
||||
it('should invoke multiple action', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.selectAll()
|
||||
await manager.doAction('multiple')
|
||||
expect(lastItems.length).toBe(3)
|
||||
lastItems = undefined
|
||||
})
|
||||
|
||||
it('should invoke parallel action', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.selectAll()
|
||||
let d = Date.now()
|
||||
await manager.doAction('parallel')
|
||||
expect(Date.now() - d).toBeLessThan(300)
|
||||
})
|
||||
|
||||
it('should invoke reload action', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
labels = ['d', 'e']
|
||||
await manager.doAction('reload')
|
||||
await helper.wait(50)
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['d', 'e'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('reloadItems()', () => {
|
||||
it('should not reload items when window is hidden', async () => {
|
||||
let fn = jest.fn()
|
||||
let list: IList = {
|
||||
name: 'reload',
|
||||
defaultAction: 'open',
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: () => {}
|
||||
}],
|
||||
loadItems: () => {
|
||||
fn()
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'reload'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await manager.cancel(true)
|
||||
let ses = manager.getSession('reload')
|
||||
await ses.reloadItems()
|
||||
expect(fn).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resume()', () => {
|
||||
it('should do preview on resume', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let lastItem
|
||||
let list = new SimpleList(nvim)
|
||||
list.actions.push({
|
||||
name: 'preview',
|
||||
execute: item => {
|
||||
lastItem = item
|
||||
}
|
||||
})
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', '--auto-preview', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.selectLines(1, 2)
|
||||
await helper.wait(50)
|
||||
await nvim.call('coc#window#close', [ui.winid])
|
||||
await helper.wait(100)
|
||||
await manager.session.resume()
|
||||
await helper.wait(100)
|
||||
expect(lastItem).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('jumpBack()', () => {
|
||||
it('should jump back', async () => {
|
||||
let win = await nvim.window
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
manager.session.jumpBack()
|
||||
await helper.wait(50)
|
||||
let winid = await nvim.call('win_getid')
|
||||
expect(winid).toBe(win.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('doNumberSelect()', () => {
|
||||
async function create(len: number): Promise<ListSession> {
|
||||
labels = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
let code = 'a'.charCodeAt(0) + i
|
||||
labels.push(String.fromCharCode(code))
|
||||
}
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', '--number-select', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
return manager.session
|
||||
}
|
||||
|
||||
it('should return false for invalid number', async () => {
|
||||
let session = await create(5)
|
||||
let res = await session.doNumberSelect('a')
|
||||
expect(res).toBe(false)
|
||||
res = await session.doNumberSelect('8')
|
||||
expect(res).toBe(false)
|
||||
})
|
||||
|
||||
it('should consider 0 as 10', async () => {
|
||||
let session = await create(15)
|
||||
let res = await session.doNumberSelect('0')
|
||||
expect(res).toBe(true)
|
||||
expect(lastItem).toBe('j')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('showHelp()', () => {
|
||||
it('should show description and options in help', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await manager.session.showHelp()
|
||||
let lines = await nvim.call('getline', [1, '$'])
|
||||
expect(lines.indexOf('DESCRIPTION')).toBeGreaterThan(0)
|
||||
expect(lines.indexOf('ARGUMENTS')).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('chooseAction()', () => {
|
||||
it('should filter actions not have shortcuts', async () => {
|
||||
labels = ['a', 'b', 'c']
|
||||
let fn = jest.fn()
|
||||
let list = new SimpleList(nvim)
|
||||
list.actions.push({
|
||||
name: 'a',
|
||||
execute: () => {
|
||||
fn()
|
||||
}
|
||||
})
|
||||
list.actions.push({
|
||||
name: 'b',
|
||||
execute: () => {
|
||||
}
|
||||
})
|
||||
list.actions.push({
|
||||
name: 'ab',
|
||||
execute: () => {
|
||||
}
|
||||
})
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
await manager.session.ui.ready
|
||||
let p = manager.session.chooseAction()
|
||||
await helper.wait(50)
|
||||
await nvim.input('a')
|
||||
await p
|
||||
expect(fn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should choose action by menu picker', async () => {
|
||||
helper.updateConfiguration('list.menuAction', true)
|
||||
labels = ['a', 'b', 'c']
|
||||
let fn = jest.fn()
|
||||
let list = new SimpleList(nvim)
|
||||
let len = list.actions.length
|
||||
list.actions.splice(0, len)
|
||||
list.actions.push({
|
||||
name: 'a',
|
||||
execute: () => {
|
||||
fn()
|
||||
}
|
||||
})
|
||||
list.actions.push({
|
||||
name: 'b',
|
||||
execute: () => {
|
||||
fn()
|
||||
}
|
||||
})
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
await manager.session.ui.ready
|
||||
let p = manager.session.chooseAction()
|
||||
await helper.wait(100)
|
||||
await nvim.input('<cr>')
|
||||
await p
|
||||
})
|
||||
})
|
|
@ -1,666 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { CancellationToken, Diagnostic, DiagnosticSeverity, Disposable, Emitter, Location, Range } from 'vscode-languageserver-protocol'
|
||||
import { URI } from 'vscode-uri'
|
||||
import diagnosticManager from '../../diagnostic/manager'
|
||||
import events from '../../events'
|
||||
import languages from '../../languages'
|
||||
import BasicList, { PreviewOptions, toVimFiletype } from '../../list/basic'
|
||||
import { formatListItems, formatPath, UnformattedListItem } from '../../list/formatting'
|
||||
import manager from '../../list/manager'
|
||||
import Document from '../../model/document'
|
||||
import services, { IServiceProvider } from '../../services'
|
||||
import { ListArgument, ListContext, ListItem, ServiceStat } from '../../types'
|
||||
import { disposeAll } from '../../util'
|
||||
import workspace from '../../workspace'
|
||||
import helper from '../helper'
|
||||
|
||||
let listItems: ListItem[] = []
|
||||
class OptionList extends BasicList {
|
||||
public name = 'option'
|
||||
public options: ListArgument[] = [{
|
||||
name: '-w, -word',
|
||||
description: 'word'
|
||||
}, {
|
||||
name: '-i, -input INPUT',
|
||||
hasValue: true,
|
||||
description: 'input'
|
||||
}]
|
||||
constructor(nvim) {
|
||||
super(nvim)
|
||||
this.addLocationActions()
|
||||
}
|
||||
public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListItem[]> {
|
||||
return Promise.resolve(listItems)
|
||||
}
|
||||
}
|
||||
|
||||
let previewOptions: PreviewOptions
|
||||
class SimpleList extends BasicList {
|
||||
public name = 'simple'
|
||||
public defaultAction: 'preview'
|
||||
constructor(nvim: Neovim) {
|
||||
super(nvim)
|
||||
this.addAction('preview', async (_item, context) => {
|
||||
await this.preview(previewOptions, context)
|
||||
})
|
||||
}
|
||||
public loadItems(): Promise<ListItem[]> {
|
||||
return Promise.resolve(['a', 'b', 'c'].map((s, idx) => {
|
||||
return { label: s, location: Location.create('test:///a', Range.create(idx, 0, idx + 1, 0)) } as ListItem
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let disposables: Disposable[] = []
|
||||
let nvim: Neovim
|
||||
const locations: any[] = [{
|
||||
filename: __filename,
|
||||
range: Range.create(0, 0, 0, 6),
|
||||
text: 'foo'
|
||||
}, {
|
||||
filename: __filename,
|
||||
range: Range.create(2, 0, 2, 6),
|
||||
text: 'Bar'
|
||||
}, {
|
||||
filename: __filename,
|
||||
range: Range.create(3, 0, 4, 6),
|
||||
text: 'multiple'
|
||||
}]
|
||||
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
manager.dispose()
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
listItems = []
|
||||
disposeAll(disposables)
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('formatting', () => {
|
||||
describe('formatPath()', () => {
|
||||
it('should format path', async () => {
|
||||
expect(formatPath('hidden', 'path')).toBe('')
|
||||
expect(formatPath('full', __filename)).toMatch('sources.test.ts')
|
||||
expect(formatPath('short', __filename)).toMatch('sources.test.ts')
|
||||
expect(formatPath('filename', __filename)).toMatch('sources.test.ts')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatListItems', () => {
|
||||
it('should format list items', async () => {
|
||||
expect(formatListItems(false, [])).toEqual([])
|
||||
let items: UnformattedListItem[] = [{
|
||||
label: ['a', 'b', 'c']
|
||||
}]
|
||||
expect(formatListItems(false, items)).toEqual([{
|
||||
label: 'a\tb\tc'
|
||||
}])
|
||||
items = [{
|
||||
label: ['a', 'b', 'c']
|
||||
}, {
|
||||
label: ['foo', 'bar', 'go']
|
||||
}]
|
||||
expect(formatListItems(true, items)).toEqual([{
|
||||
label: 'a \tb \tc '
|
||||
}, {
|
||||
label: 'foo\tbar\tgo'
|
||||
}])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('configuration', () => {
|
||||
beforeEach(() => {
|
||||
let list = new OptionList(nvim)
|
||||
manager.registerList(list)
|
||||
})
|
||||
|
||||
it('should change default options', async () => {
|
||||
helper.updateConfiguration('list.source.option.defaultOptions', ['--normal'])
|
||||
await manager.start(['option'])
|
||||
await manager.session.ui.ready
|
||||
const mode = manager.prompt.mode
|
||||
expect(mode).toBe('normal')
|
||||
})
|
||||
|
||||
it('should change default action', async () => {
|
||||
helper.updateConfiguration('list.source.option.defaultAction', 'split')
|
||||
await manager.start(['option'])
|
||||
await manager.session.ui.ready
|
||||
const action = manager.session.defaultAction
|
||||
expect(action.name).toBe('split')
|
||||
await manager.session.doAction()
|
||||
let tab = await nvim.tabpage
|
||||
let wins = await tab.windows
|
||||
expect(wins.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should change default arguments', async () => {
|
||||
helper.updateConfiguration('list.source.option.defaultArgs', ['-word'])
|
||||
await manager.start(['option'])
|
||||
await manager.session.ui.ready
|
||||
const context = manager.session.context
|
||||
expect(context.args).toEqual(['-word'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('BasicList', () => {
|
||||
describe('getFiletype()', () => {
|
||||
it('should get filetype', async () => {
|
||||
expect(toVimFiletype('latex')).toBe('tex')
|
||||
expect(toVimFiletype('foo')).toBe('foo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('parse arguments', () => {
|
||||
it('should parse args #1', () => {
|
||||
let list = new OptionList(nvim)
|
||||
let res = list.parseArguments(['-w'])
|
||||
expect(res).toEqual({ word: true })
|
||||
})
|
||||
|
||||
it('should parse args #2', () => {
|
||||
let list = new OptionList(nvim)
|
||||
let res = list.parseArguments(['-word'])
|
||||
expect(res).toEqual({ word: true })
|
||||
})
|
||||
|
||||
it('should parse args #3', () => {
|
||||
let list = new OptionList(nvim)
|
||||
let res = list.parseArguments(['-input', 'foo'])
|
||||
expect(res).toEqual({ input: 'foo' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('jumpTo()', () => {
|
||||
let list: OptionList
|
||||
beforeAll(() => {
|
||||
list = new OptionList(nvim)
|
||||
})
|
||||
it('should jump to uri', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
await list.jumpTo(uri, 'edit')
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toMatch('sources.test.ts')
|
||||
})
|
||||
|
||||
it('should jump to location', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let loc = Location.create(uri, Range.create(0, 0, 1, 0))
|
||||
await list.jumpTo(loc, 'edit')
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toMatch('sources.test.ts')
|
||||
})
|
||||
|
||||
it('should jump to location with empty range', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let loc = Location.create(uri, Range.create(0, 0, 0, 0))
|
||||
await list.jumpTo(loc, 'edit')
|
||||
let bufname = await nvim.call('bufname', ['%'])
|
||||
expect(bufname).toMatch('sources.test.ts')
|
||||
})
|
||||
})
|
||||
|
||||
describe('convertLocation()', () => {
|
||||
let list: OptionList
|
||||
beforeAll(() => {
|
||||
list = new OptionList(nvim)
|
||||
})
|
||||
it('should convert uri', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let res = await list.convertLocation(uri)
|
||||
expect(res.uri).toBe(uri)
|
||||
})
|
||||
|
||||
it('should convert location with line', async () => {
|
||||
let uri = URI.file(__filename).toString()
|
||||
let res = await list.convertLocation({ uri, line: 'convertLocation()', text: 'convertLocation' })
|
||||
expect(res.uri).toBe(uri)
|
||||
res = await list.convertLocation({ uri, line: 'convertLocation()' })
|
||||
expect(res.uri).toBe(uri)
|
||||
})
|
||||
|
||||
it('should convert location with custom schema', async () => {
|
||||
let uri = 'test:///foo'
|
||||
let res = await list.convertLocation({ uri, line: 'convertLocation()' })
|
||||
expect(res.uri).toBe(uri)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createAction()', () => {
|
||||
it('should overwrite action', async () => {
|
||||
let idx: number
|
||||
let list = new OptionList(nvim)
|
||||
listItems.push({
|
||||
label: 'foo',
|
||||
location: Location.create('untitled:///1', Range.create(0, 0, 0, 0))
|
||||
})
|
||||
list.createAction({
|
||||
name: 'foo',
|
||||
execute: () => { idx = 0 }
|
||||
})
|
||||
list.createAction({
|
||||
name: 'foo',
|
||||
execute: () => { idx = 1 }
|
||||
})
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['--normal', 'option'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('foo')
|
||||
expect(idx).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('preview()', () => {
|
||||
beforeEach(() => {
|
||||
let list = new SimpleList(nvim)
|
||||
disposables.push(manager.registerList(list))
|
||||
})
|
||||
|
||||
async function doPreview(opts: PreviewOptions): Promise<number> {
|
||||
previewOptions = opts
|
||||
await manager.start(['--normal', 'simple'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('preview')
|
||||
let res = await nvim.call('coc#list#has_preview') as number
|
||||
expect(res).toBeGreaterThan(0)
|
||||
let winid = await nvim.call('win_getid', [res])
|
||||
return winid
|
||||
}
|
||||
|
||||
it('should preview lines', async () => {
|
||||
await doPreview({ filetype: '', lines: ['foo', 'bar'] })
|
||||
})
|
||||
|
||||
it('should preview with bufname', async () => {
|
||||
await doPreview({
|
||||
bufname: 't.js',
|
||||
filetype: 'typescript',
|
||||
lines: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
|
||||
it('should preview with range highlight', async () => {
|
||||
let winid = await doPreview({
|
||||
bufname: 't.js',
|
||||
filetype: 'typescript',
|
||||
lines: ['foo', 'bar'],
|
||||
range: Range.create(0, 0, 0, 3)
|
||||
})
|
||||
let res = await nvim.call('getmatches', [winid])
|
||||
expect(res.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('previewLocation()', () => {
|
||||
it('should preview sketch buffer', async () => {
|
||||
await nvim.command('new')
|
||||
await nvim.setLine('foo')
|
||||
let doc = await workspace.document
|
||||
expect(doc.uri).toMatch('untitled')
|
||||
let list = new OptionList(nvim)
|
||||
listItems.push({
|
||||
label: 'foo',
|
||||
location: Location.create(doc.uri, Range.create(0, 0, 0, 0))
|
||||
})
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['option'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(30)
|
||||
await manager.doAction('preview')
|
||||
await nvim.command('wincmd p')
|
||||
let win = await nvim.window
|
||||
let isPreview = await win.getVar('previewwindow')
|
||||
expect(isPreview).toBe(1)
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('foo')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('list sources', () => {
|
||||
beforeAll(async () => {
|
||||
await nvim.setVar('coc_jump_locations', locations)
|
||||
})
|
||||
|
||||
describe('locations', () => {
|
||||
it('should highlight ranges', async () => {
|
||||
await manager.start(['--normal', '--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(200)
|
||||
manager.prompt.cancel()
|
||||
await nvim.command('wincmd k')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('sources.test.ts')
|
||||
let res = await nvim.call('getmatches')
|
||||
expect(res.length).toBe(1)
|
||||
})
|
||||
|
||||
it('should change highlight on cursor move', async () => {
|
||||
await manager.start(['--normal', '--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.command('exe 2')
|
||||
let bufnr = await nvim.eval('bufnr("%")')
|
||||
await events.fire('CursorMoved', [bufnr, [2, 1]])
|
||||
await helper.waitFor('winnr', ['$'], 3)
|
||||
await nvim.command('wincmd k')
|
||||
let res = await nvim.call('getmatches')
|
||||
expect(res.length).toBe(1)
|
||||
expect(res[0]['pos1']).toEqual([3, 1, 6])
|
||||
})
|
||||
|
||||
it('should highlight multiple line range', async () => {
|
||||
await manager.start(['--normal', '--auto-preview', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.command('exe 3')
|
||||
let bufnr = await nvim.eval('bufnr("%")')
|
||||
await events.fire('CursorMoved', [bufnr, [2, 1]])
|
||||
await helper.waitFor('winnr', ['$'], 3)
|
||||
await nvim.command('wincmd k')
|
||||
let res = await nvim.call('getmatches')
|
||||
expect(res.length).toBe(1)
|
||||
expect(res[0]['pos1']).toBeDefined()
|
||||
expect(res[0]['pos2']).toBeDefined()
|
||||
})
|
||||
|
||||
it('should do open action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('open')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('sources.test.ts')
|
||||
})
|
||||
|
||||
it('should do quickfix action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.session.ui.selectAll()
|
||||
await manager.doAction('quickfix')
|
||||
let buftype = await nvim.eval('&buftype')
|
||||
expect(buftype).toBe('quickfix')
|
||||
})
|
||||
|
||||
it('should do refactor action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.session.ui.selectAll()
|
||||
await manager.doAction('refactor')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('coc_refactor')
|
||||
})
|
||||
|
||||
it('should do tabe action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('tabe')
|
||||
let tabs = await nvim.tabpages
|
||||
expect(tabs.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should do drop action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('drop')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('sources.test.ts')
|
||||
})
|
||||
|
||||
it('should do vsplit action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('vsplit')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('sources.test.ts')
|
||||
})
|
||||
|
||||
it('should do split action', async () => {
|
||||
await manager.start(['--normal', 'location'])
|
||||
await manager.session.ui.ready
|
||||
await manager.doAction('split')
|
||||
let name = await nvim.eval('bufname("%")')
|
||||
expect(name).toMatch('sources.test.ts')
|
||||
})
|
||||
})
|
||||
|
||||
describe('commands', () => {
|
||||
it('should load commands source', async () => {
|
||||
await manager.start(['commands'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should do run action', async () => {
|
||||
await manager.start(['commands'])
|
||||
await manager.session?.ui.ready
|
||||
await manager.doAction()
|
||||
})
|
||||
})
|
||||
|
||||
describe('diagnostics', () => {
|
||||
|
||||
function createDiagnostic(msg: string, range?: Range, severity?: DiagnosticSeverity, code?: number): Diagnostic {
|
||||
range = range ? range : Range.create(0, 0, 0, 1)
|
||||
return Diagnostic.create(range, msg, severity || DiagnosticSeverity.Error, code)
|
||||
}
|
||||
|
||||
async function createDocument(name?: string): Promise<Document> {
|
||||
let doc = await helper.createDocument(name)
|
||||
let collection = diagnosticManager.create('test')
|
||||
disposables.push({
|
||||
dispose: () => {
|
||||
collection.clear()
|
||||
collection.dispose()
|
||||
}
|
||||
})
|
||||
let diagnostics: Diagnostic[] = []
|
||||
await doc.buffer.setLines(['foo bar foo bar', 'foo bar', 'foo', 'bar'], {
|
||||
start: 0,
|
||||
end: -1,
|
||||
strictIndexing: false
|
||||
})
|
||||
diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 4), DiagnosticSeverity.Error, 1001))
|
||||
diagnostics.push(createDiagnostic('warning', Range.create(0, 5, 0, 6), DiagnosticSeverity.Warning, 1002))
|
||||
diagnostics.push(createDiagnostic('information', Range.create(1, 0, 1, 1), DiagnosticSeverity.Information, 1003))
|
||||
diagnostics.push(createDiagnostic('hint', Range.create(1, 2, 1, 3), DiagnosticSeverity.Hint, 1004))
|
||||
diagnostics.push(createDiagnostic('error', Range.create(2, 0, 2, 2), DiagnosticSeverity.Error, 1005))
|
||||
collection.set(doc.uri, diagnostics)
|
||||
await doc.synchronize()
|
||||
return doc
|
||||
}
|
||||
|
||||
it('should load diagnostics source', async () => {
|
||||
await createDocument('a')
|
||||
await manager.start(['diagnostics'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
|
||||
it('should not include code', async () => {
|
||||
let fn = helper.updateConfiguration('list.source.diagnostics.includeCode', false)
|
||||
disposables.push({ dispose: fn })
|
||||
await createDocument('a')
|
||||
await manager.start(['diagnostics'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let line = await nvim.line
|
||||
expect(line.match(/100/)).toBeNull()
|
||||
})
|
||||
|
||||
it('should hide file path', async () => {
|
||||
helper.updateConfiguration('list.source.diagnostics.pathFormat', 'hidden')
|
||||
await createDocument('foo')
|
||||
await manager.start(['diagnostics'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let line = await nvim.line
|
||||
expect(line.match(/foo/)).toBeNull()
|
||||
})
|
||||
|
||||
it('should refresh on diagnostics refresh', async () => {
|
||||
let doc = await createDocument('bar')
|
||||
await manager.start(['diagnostics'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let diagnostics: Diagnostic[] = []
|
||||
let collection = diagnosticManager.create('test')
|
||||
diagnostics.push(createDiagnostic('error', Range.create(2, 0, 2, 2), DiagnosticSeverity.Error, 1009))
|
||||
collection.set(doc.uri, diagnostics)
|
||||
await helper.wait(50)
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('extensions', () => {
|
||||
it('should load extensions source', async () => {
|
||||
await manager.start(['extensions'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('folders', () => {
|
||||
it('should load folders source', async () => {
|
||||
await manager.start(['folders'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('lists', () => {
|
||||
it('should load lists source', async () => {
|
||||
await manager.start(['lists'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
await helper.listInput('<cr>')
|
||||
await helper.wait(50)
|
||||
let s = manager.getSession()
|
||||
expect(s.name != 'lists').toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('outline', () => {
|
||||
it('should load outline source', async () => {
|
||||
await manager.start(['outline'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('services', () => {
|
||||
function createService(name: string): IServiceProvider {
|
||||
let _onServcieReady = new Emitter<void>()
|
||||
// public readonly onServcieReady: Event<void> = this.
|
||||
let service: IServiceProvider = {
|
||||
id: name,
|
||||
name,
|
||||
selector: [{ language: 'vim' }],
|
||||
state: ServiceStat.Initial,
|
||||
start(): Promise<void> {
|
||||
service.state = ServiceStat.Running
|
||||
_onServcieReady.fire()
|
||||
return Promise.resolve()
|
||||
},
|
||||
dispose(): void {
|
||||
service.state = ServiceStat.Stopped
|
||||
},
|
||||
stop(): void {
|
||||
service.state = ServiceStat.Stopped
|
||||
},
|
||||
restart(): void {
|
||||
service.state = ServiceStat.Running
|
||||
_onServcieReady.fire()
|
||||
},
|
||||
onServiceReady: _onServcieReady.event
|
||||
}
|
||||
disposables.push(services.regist(service))
|
||||
return service
|
||||
}
|
||||
|
||||
it('should load services source', async () => {
|
||||
createService('foo')
|
||||
createService('bar')
|
||||
await manager.start(['services'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let lines = await nvim.call('getline', [1, '$']) as string[]
|
||||
expect(lines.length).toBe(2)
|
||||
})
|
||||
|
||||
it('should toggle service state', async () => {
|
||||
let service = createService('foo')
|
||||
await service.start()
|
||||
await manager.start(['services'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let ses = manager.session
|
||||
expect(ses.name).toBe('services')
|
||||
await ses.doAction('toggle')
|
||||
expect(service.state).toBe(ServiceStat.Stopped)
|
||||
await ses.doAction('toggle')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sources', () => {
|
||||
it('should load sources source', async () => {
|
||||
await manager.start(['sources'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let session = manager.getSession()
|
||||
await session.doAction('open')
|
||||
let bufname = await nvim.call('bufname', '%')
|
||||
expect(bufname).toMatch(/native/)
|
||||
})
|
||||
|
||||
it('should toggle source state', async () => {
|
||||
await manager.start(['sources'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let session = manager.getSession()
|
||||
await session.doAction('toggle')
|
||||
await session.doAction('toggle')
|
||||
})
|
||||
|
||||
it('should refresh source', async () => {
|
||||
await manager.start(['sources'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
let session = manager.getSession()
|
||||
await session.doAction('refresh')
|
||||
})
|
||||
})
|
||||
|
||||
describe('symbols', () => {
|
||||
it('should load symbols source', async () => {
|
||||
await helper.createDocument()
|
||||
let disposable = languages.registerWorkspaceSymbolProvider({
|
||||
provideWorkspaceSymbols: () => []
|
||||
})
|
||||
await manager.start(['--interactive', 'symbols'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
disposable.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
describe('links', () => {
|
||||
it('should load links source', async () => {
|
||||
let disposable = languages.registerDocumentLinkProvider([{ scheme: 'file' }, { scheme: 'untitled' }], {
|
||||
provideDocumentLinks: () => []
|
||||
})
|
||||
await manager.start(['links'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
disposable.dispose()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,274 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import { EventEmitter } from 'events'
|
||||
import { Disposable } from 'vscode-languageserver-protocol'
|
||||
import BasicList from '../../list/basic'
|
||||
import events from '../../events'
|
||||
import manager from '../../list/manager'
|
||||
import { ListItem, IList, ListTask } from '../../types'
|
||||
import { disposeAll } from '../../util'
|
||||
import helper from '../helper'
|
||||
|
||||
let labels: string[] = []
|
||||
let lastItem: string
|
||||
|
||||
class SimpleList extends BasicList {
|
||||
public name = 'simple'
|
||||
constructor(nvim: Neovim) {
|
||||
super(nvim)
|
||||
this.addAction('open', item => {
|
||||
lastItem = item.label
|
||||
})
|
||||
}
|
||||
public loadItems(): Promise<ListItem[]> {
|
||||
return Promise.resolve(labels.map(s => {
|
||||
return { label: s, ansiHighlights: [{ span: [0, 1], hlGroup: 'MoreMsg' }] } as ListItem
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
class SlowTask extends EventEmitter implements ListTask {
|
||||
private interval: NodeJS.Timer
|
||||
constructor() {
|
||||
super()
|
||||
let i = 0
|
||||
let interval = this.interval = setInterval(() => {
|
||||
i++
|
||||
this.emit('data', {
|
||||
label: i.toString(), highlights: {
|
||||
spans: [[0, 1]],
|
||||
hlGroup: 'Search'
|
||||
}
|
||||
})
|
||||
if (i == 5) {
|
||||
this.emit('end')
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
clearInterval(this.interval)
|
||||
this.removeAllListeners()
|
||||
}
|
||||
}
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('list ui', () => {
|
||||
describe('selectLines()', () => {
|
||||
it('should select lines', async () => {
|
||||
labels = ['foo', 'bar']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.selectLines(3, 1)
|
||||
let buf = await nvim.buffer
|
||||
let res = await buf.getSigns({ group: 'coc-list' })
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('preselect', () => {
|
||||
it('should select preselect item', async () => {
|
||||
let list: IList = {
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: () => {}
|
||||
}],
|
||||
name: 'preselect',
|
||||
defaultAction: 'open',
|
||||
loadItems: () => {
|
||||
return Promise.resolve([{ label: 'foo' }, { label: 'bar', preselect: true }])
|
||||
}
|
||||
}
|
||||
disposables.push(manager.registerList(list))
|
||||
await manager.start(['preselect'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
let line = await nvim.line
|
||||
expect(line).toBe('bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resume()', () => {
|
||||
it('should resume with selected lines', async () => {
|
||||
labels = ['foo', 'bar']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.selectLines(1, 2)
|
||||
await nvim.call('coc#window#close', [ui.winid])
|
||||
await helper.wait(100)
|
||||
await manager.session.resume()
|
||||
await helper.wait(100)
|
||||
let buf = await nvim.buffer
|
||||
let res = await buf.getSigns({ group: 'coc-list' })
|
||||
expect(res.length).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('events', () => {
|
||||
async function mockMouse(winid: number, lnum: number): Promise<void> {
|
||||
await nvim.command(`let v:mouse_winid = ${winid}`)
|
||||
await nvim.command(`let v:mouse_lnum = ${lnum}`)
|
||||
await nvim.command('let v:mouse_col = 1')
|
||||
}
|
||||
|
||||
it('should fire action on double click', async () => {
|
||||
labels = ['foo', 'bar']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await mockMouse(ui.winid, 1)
|
||||
await manager.session.onMouseEvent('<2-LeftMouse>')
|
||||
await helper.wait(100)
|
||||
expect(lastItem).toBe('foo')
|
||||
})
|
||||
|
||||
it('should select clicked line', async () => {
|
||||
labels = ['foo', 'bar']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await mockMouse(ui.winid, 2)
|
||||
await ui.onMouse('mouseDown')
|
||||
await helper.wait(50)
|
||||
await mockMouse(ui.winid, 2)
|
||||
await ui.onMouse('mouseUp')
|
||||
await helper.wait(50)
|
||||
let item = await ui.item
|
||||
expect(item.label).toBe('bar')
|
||||
})
|
||||
|
||||
it('should jump to original window on click', async () => {
|
||||
labels = ['foo', 'bar']
|
||||
let win = await nvim.window
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await mockMouse(win.id, 1)
|
||||
await ui.onMouse('mouseUp')
|
||||
await helper.wait(50)
|
||||
let curr = await nvim.window
|
||||
expect(curr.id).toBe(win.id)
|
||||
})
|
||||
|
||||
it('should highlights items on CursorMoved', async () => {
|
||||
labels = (new Array(400)).fill('a')
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await nvim.call('cursor', [350, 1])
|
||||
await events.fire('CursorMoved', [ui.bufnr, [350, 1]])
|
||||
await helper.wait(100)
|
||||
let res = await nvim.call('coc#highlight#get_highlights', [ui.bufnr, 'list'])
|
||||
expect(res.length).toBeGreaterThan(300)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('reversed list', () => {
|
||||
it('should render and add highlights', async () => {
|
||||
labels = ['a', 'b', 'c', 'd']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['--reverse', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
let buf = nvim.createBuffer(ui.bufnr)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['d', 'c', 'b', 'a'])
|
||||
await helper.listInput('a')
|
||||
await helper.wait(50)
|
||||
lines = await buf.lines
|
||||
expect(lines).toEqual(['a'])
|
||||
let res = await nvim.call('coc#highlight#get_highlights', [ui.bufnr, 'list'])
|
||||
expect(res.length).toBe(2)
|
||||
let win = nvim.createWindow(ui.winid)
|
||||
let height = await win.height
|
||||
expect(height).toBe(1)
|
||||
})
|
||||
|
||||
it('should moveUp and moveDown', async () => {
|
||||
labels = ['a', 'b', 'c', 'd']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['--reverse', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
ui.moveUp()
|
||||
await helper.waitFor('line', ['.'], 3)
|
||||
ui.moveDown()
|
||||
await helper.waitFor('line', ['.'], 4)
|
||||
})
|
||||
|
||||
it('should toggle selection', async () => {
|
||||
labels = ['a', 'b', 'c', 'd']
|
||||
disposables.push(manager.registerList(new SimpleList(nvim)))
|
||||
await manager.start(['--reverse', '--normal', 'simple'])
|
||||
let ui = manager.session.ui
|
||||
await ui.ready
|
||||
await ui.toggleSelection()
|
||||
let items = ui.selectedItems
|
||||
expect(items.length).toBeGreaterThan(0)
|
||||
expect(items[0].label).toBe('a')
|
||||
let lnum = await nvim.call('line', ['.'])
|
||||
expect(lnum).toBe(3)
|
||||
await helper.listInput('j')
|
||||
await ui.toggleSelection()
|
||||
items = ui.selectedItems
|
||||
expect(items.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should prepend list items', async () => {
|
||||
let o: any
|
||||
let p = new Promise(resolve => {
|
||||
let list: IList = {
|
||||
actions: [{
|
||||
name: 'open',
|
||||
execute: item => {
|
||||
o = item
|
||||
}
|
||||
}],
|
||||
name: 'slow',
|
||||
defaultAction: 'open',
|
||||
loadItems: () => {
|
||||
let task = new SlowTask()
|
||||
task.on('end', () => {
|
||||
resolve(undefined)
|
||||
})
|
||||
return Promise.resolve(task)
|
||||
}
|
||||
}
|
||||
disposables.push(manager.registerList(list))
|
||||
void manager.start(['--reverse', '--normal', 'slow'])
|
||||
})
|
||||
await p
|
||||
await helper.wait(50)
|
||||
let ui = manager.session.ui
|
||||
let buf = nvim.createBuffer(ui.bufnr)
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['5', '4', '3', '2', '1'])
|
||||
let lnum = await nvim.call('line', ['.'])
|
||||
expect(lnum).toBe(5)
|
||||
})
|
||||
})
|
|
@ -1,226 +0,0 @@
|
|||
import { Neovim } from '@chemzqm/neovim'
|
||||
import manager from '../../list/manager'
|
||||
import { parseInput } from '../../list/worker'
|
||||
import helper from '../helper'
|
||||
import { ListContext, ListTask, ListItem } from '../../types'
|
||||
import { CancellationToken, Disposable } from 'vscode-languageserver-protocol'
|
||||
import { EventEmitter } from 'events'
|
||||
import colors from 'colors/safe'
|
||||
import BasicList from '../../list/basic'
|
||||
import { disposeAll } from '../../util'
|
||||
|
||||
let items: ListItem[] = []
|
||||
|
||||
class DataList extends BasicList {
|
||||
public name = 'data'
|
||||
public loadItems(): Promise<ListItem[]> {
|
||||
return Promise.resolve(items)
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyList extends BasicList {
|
||||
public name = 'empty'
|
||||
public loadItems(): Promise<ListItem[]> {
|
||||
let emitter: any = new EventEmitter()
|
||||
setTimeout(() => {
|
||||
emitter.emit('end')
|
||||
}, 20)
|
||||
return emitter
|
||||
}
|
||||
}
|
||||
|
||||
class IntervalTaskList extends BasicList {
|
||||
public name = 'task'
|
||||
public timeout = 3000
|
||||
public loadItems(_context: ListContext, token: CancellationToken): Promise<ListTask> {
|
||||
let emitter: any = new EventEmitter()
|
||||
let i = 0
|
||||
let interval = setInterval(() => {
|
||||
emitter.emit('data', { label: i.toFixed() })
|
||||
i++
|
||||
}, 50)
|
||||
emitter.dispose = () => {
|
||||
clearInterval(interval)
|
||||
emitter.emit('end')
|
||||
}
|
||||
token.onCancellationRequested(() => {
|
||||
emitter.dispose()
|
||||
})
|
||||
return emitter
|
||||
}
|
||||
}
|
||||
|
||||
class DelayTask extends BasicList {
|
||||
public name = 'delay'
|
||||
public interactive = true
|
||||
public loadItems(_context: ListContext, token: CancellationToken): Promise<ListTask> {
|
||||
let emitter: any = new EventEmitter()
|
||||
let disposed = false
|
||||
setTimeout(() => {
|
||||
if (disposed) return
|
||||
emitter.emit('data', { label: 'ahead' })
|
||||
}, 100)
|
||||
setTimeout(() => {
|
||||
if (disposed) return
|
||||
emitter.emit('data', { label: 'abort' })
|
||||
}, 200)
|
||||
emitter.dispose = () => {
|
||||
disposed = true
|
||||
emitter.emit('end')
|
||||
}
|
||||
token.onCancellationRequested(() => {
|
||||
emitter.dispose()
|
||||
})
|
||||
return emitter
|
||||
}
|
||||
}
|
||||
|
||||
class InteractiveList extends BasicList {
|
||||
public name = 'test'
|
||||
public interactive = true
|
||||
public loadItems(context: ListContext, _token: CancellationToken): Promise<ListItem[]> {
|
||||
return Promise.resolve([{
|
||||
label: colors.magenta(context.input || '')
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorList extends BasicList {
|
||||
public name = 'error'
|
||||
public interactive = true
|
||||
public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListItem[]> {
|
||||
return Promise.reject(new Error('test error'))
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorTaskList extends BasicList {
|
||||
public name = 'task'
|
||||
public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListTask> {
|
||||
let emitter: any = new EventEmitter()
|
||||
let timeout = setTimeout(() => {
|
||||
emitter.emit('error', new Error('task error'))
|
||||
}, 100)
|
||||
emitter.dispose = () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
return emitter
|
||||
}
|
||||
}
|
||||
|
||||
let nvim: Neovim
|
||||
let disposables: Disposable[] = []
|
||||
beforeAll(async () => {
|
||||
await helper.setup()
|
||||
nvim = helper.nvim
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await helper.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
disposeAll(disposables)
|
||||
manager.reset()
|
||||
await helper.reset()
|
||||
})
|
||||
|
||||
describe('parseInput', () => {
|
||||
it('should parse input with space', async () => {
|
||||
let res = parseInput('a b')
|
||||
expect(res).toEqual(['a', 'b'])
|
||||
res = parseInput('a b ')
|
||||
expect(res).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
it('should parse input with escaped space', async () => {
|
||||
let res = parseInput('a\\ b')
|
||||
expect(res).toEqual(['a b'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('list worker', () => {
|
||||
|
||||
it('should work with long running task', async () => {
|
||||
disposables.push(manager.registerList(new IntervalTaskList(nvim)))
|
||||
await manager.start(['task'])
|
||||
await manager.session.ui.ready
|
||||
await helper.wait(200)
|
||||
let len = manager.session?.length
|
||||
expect(len > 2).toBe(true)
|
||||
await manager.cancel()
|
||||
})
|
||||
|
||||
it('should sort by sortText', async () => {
|
||||
items = [{
|
||||
label: 'abc',
|
||||
sortText: 'b'
|
||||
}, {
|
||||
label: 'ade',
|
||||
sortText: 'a'
|
||||
}]
|
||||
disposables.push(manager.registerList(new DataList(nvim)))
|
||||
await manager.start(['data'])
|
||||
await manager.session.ui.ready
|
||||
await nvim.input('a')
|
||||
await helper.wait(50)
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['ade', 'abc'])
|
||||
await manager.cancel()
|
||||
})
|
||||
|
||||
it('should show empty line for empty task', async () => {
|
||||
disposables.push(manager.registerList(new EmptyList(nvim)))
|
||||
await manager.start(['empty'])
|
||||
await manager.session.ui.ready
|
||||
let line = await nvim.call('getline', [1])
|
||||
expect(line).toMatch('No results')
|
||||
})
|
||||
|
||||
it('should cancel task by use CancellationToken', async () => {
|
||||
disposables.push(manager.registerList(new IntervalTaskList(nvim)))
|
||||
await manager.start(['task'])
|
||||
expect(manager.session?.worker.isLoading).toBe(true)
|
||||
await helper.wait(100)
|
||||
manager.session?.stop()
|
||||
expect(manager.session?.worker.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('should render slow interactive list', async () => {
|
||||
disposables.push(manager.registerList(new DelayTask(nvim)))
|
||||
await manager.start(['delay'])
|
||||
await nvim.input('a')
|
||||
await helper.wait(600)
|
||||
let buf = await nvim.buffer
|
||||
let lines = await buf.lines
|
||||
expect(lines).toEqual(['ahead', 'abort'])
|
||||
})
|
||||
|
||||
it('should work with interactive list', async () => {
|
||||
disposables.push(manager.registerList(new InteractiveList(nvim)))
|
||||
await manager.start(['-I', 'test'])
|
||||
await manager.session?.ui.ready
|
||||
expect(manager.isActivated).toBe(true)
|
||||
await nvim.eval('feedkeys("f", "in")')
|
||||
await helper.wait(100)
|
||||
await nvim.eval('feedkeys("a", "in")')
|
||||
await helper.wait(100)
|
||||
await nvim.eval('feedkeys("x", "in")')
|
||||
await helper.wait(300)
|
||||
let item = await manager.session?.ui.item
|
||||
expect(item.label).toBe('fax')
|
||||
})
|
||||
|
||||
it('should not activate on load error', async () => {
|
||||
disposables.push(manager.registerList(new ErrorList(nvim)))
|
||||
await manager.start(['test'])
|
||||
expect(manager.isActivated).toBe(false)
|
||||
})
|
||||
|
||||
it('should deactivate on task error', async () => {
|
||||
disposables.push(manager.registerList(new ErrorTaskList(nvim)))
|
||||
await manager.start(['task'])
|
||||
await helper.wait(300)
|
||||
expect(manager.isActivated).toBe(false)
|
||||
})
|
||||
})
|
|
@ -1,265 +0,0 @@
|
|||
import { getHighlightItems, parseMarkdown, parseDocuments } from '../../markdown/index'
|
||||
import { Documentation } from '../../types'
|
||||
|
||||
describe('getHighlightItems', () => {
|
||||
it('should get highlights in single line', async () => {
|
||||
let res = getHighlightItems('this line has highlights', 0, [10, 15])
|
||||
expect(res).toEqual([{
|
||||
colStart: 10,
|
||||
colEnd: 15,
|
||||
lnum: 0,
|
||||
hlGroup: 'CocUnderline'
|
||||
}])
|
||||
})
|
||||
|
||||
it('should get highlights when active end extended', async () => {
|
||||
let res = getHighlightItems('this line', 0, [5, 30])
|
||||
expect(res).toEqual([{
|
||||
colStart: 5,
|
||||
colEnd: 9,
|
||||
lnum: 0,
|
||||
hlGroup: 'CocUnderline'
|
||||
}])
|
||||
})
|
||||
|
||||
it('should get highlights across line', async () => {
|
||||
let res = getHighlightItems('this line\nhas highlights', 0, [5, 15])
|
||||
expect(res).toEqual([{
|
||||
colStart: 5, colEnd: 9, lnum: 0, hlGroup: 'CocUnderline'
|
||||
}, {
|
||||
colStart: 0, colEnd: 5, lnum: 1, hlGroup: 'CocUnderline'
|
||||
}])
|
||||
res = getHighlightItems('a\nb\nc\nd', 0, [2, 5])
|
||||
expect(res).toEqual([
|
||||
{ colStart: 0, colEnd: 1, lnum: 1, hlGroup: 'CocUnderline' },
|
||||
{ colStart: 0, colEnd: 1, lnum: 2, hlGroup: 'CocUnderline' },
|
||||
{ colStart: 0, colEnd: 0, lnum: 3, hlGroup: 'CocUnderline' }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseMarkdown', () => {
|
||||
it('should parse code blocks', async () => {
|
||||
let content = `
|
||||
\`\`\`js
|
||||
var global = globalThis
|
||||
\`\`\`
|
||||
\`\`\`ts
|
||||
let str:string
|
||||
\`\`\`
|
||||
\`\`\`bash
|
||||
if
|
||||
\`\`\`
|
||||
`
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual([
|
||||
'var global = globalThis',
|
||||
'',
|
||||
'let str:string',
|
||||
'',
|
||||
'if'
|
||||
])
|
||||
expect(res.codes).toEqual([
|
||||
{ filetype: 'javascript', startLine: 0, endLine: 1 },
|
||||
{ filetype: 'typescript', startLine: 2, endLine: 3 },
|
||||
{ filetype: 'sh', startLine: 4, endLine: 5 },
|
||||
])
|
||||
})
|
||||
|
||||
it('should merge empty lines', async () => {
|
||||
let content = `
|
||||
![img](http://img.io)
|
||||
![img](http://img.io)
|
||||
[link](http://example.com)
|
||||
[link](javascript:void(0))
|
||||
`
|
||||
let res = parseMarkdown(content, { excludeImages: true })
|
||||
expect(res.lines).toEqual([
|
||||
'link',
|
||||
'',
|
||||
'link: http://example.com'
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse html code block', async () => {
|
||||
let content = `
|
||||
example:
|
||||
\`\`\`html
|
||||
<div>code</div>
|
||||
\`\`\`
|
||||
`
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['example:', '', '<div>code</div>'])
|
||||
expect(res.codes).toEqual([{ filetype: 'html', startLine: 2, endLine: 3 }])
|
||||
})
|
||||
|
||||
it('should compose empty lines', async () => {
|
||||
let content = 'foo\n\n\nbar\n\n\n'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['foo', '', 'bar'])
|
||||
})
|
||||
|
||||
it('should merge lines', async () => {
|
||||
let content = 'first\nsecond'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['first', 'second'])
|
||||
})
|
||||
|
||||
it('should parse ansi highlights', async () => {
|
||||
let content = '__foo__\n[link](link)'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['foo', 'link'])
|
||||
expect(res.highlights).toEqual([
|
||||
{ hlGroup: 'CocBold', lnum: 0, colStart: 0, colEnd: 3 },
|
||||
{ hlGroup: 'CocUnderline', lnum: 1, colStart: 0, colEnd: 4 }
|
||||
])
|
||||
})
|
||||
|
||||
it('should exclude images by option', async () => {
|
||||
let content = 'head\n![img](img)\ncontent ![img](img) ![img](img)'
|
||||
let res = parseMarkdown(content, { excludeImages: false })
|
||||
expect(res.lines).toEqual(['head', '![img](img)', 'content ![img](img) ![img](img)'])
|
||||
content = 'head\n![img](img)\ncontent ![img](img) ![img](img)'
|
||||
res = parseMarkdown(content, { excludeImages: true })
|
||||
expect(res.lines).toEqual(['head', 'content'])
|
||||
})
|
||||
|
||||
it('should render hr', async () => {
|
||||
let content = 'foo\n***\nbar'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['foo', '', '───', 'bar'])
|
||||
})
|
||||
|
||||
it('should render deleted text', async () => {
|
||||
let content = '~foo~'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.highlights).toEqual([
|
||||
{ hlGroup: 'CocStrikeThrough', lnum: 0, colStart: 0, colEnd: 3 }
|
||||
])
|
||||
})
|
||||
|
||||
it('should render br', async () => {
|
||||
let content = 'a \nb'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['a', 'b'])
|
||||
})
|
||||
|
||||
it('should render code span', async () => {
|
||||
let content = '`foo`'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.highlights).toEqual([
|
||||
{ hlGroup: 'CocMarkdownCode', lnum: 0, colStart: 0, colEnd: 3 }
|
||||
])
|
||||
})
|
||||
|
||||
it('should render html', async () => {
|
||||
let content = '<div>foo</div>'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual(['foo'])
|
||||
})
|
||||
|
||||
it('should render checkbox', async () => {
|
||||
let content = '- [x] first\n- [ ] second'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual([
|
||||
' * [X] first', ' * [ ] second'
|
||||
])
|
||||
})
|
||||
|
||||
it('should render numbered list', async () => {
|
||||
let content = '1. one\n2. two\n3. three'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual([
|
||||
' 1. one', ' 2. two', ' 3. three'
|
||||
])
|
||||
})
|
||||
|
||||
it('should render nested list', async () => {
|
||||
let content = '- foo\n- bar\n - one\n - two'
|
||||
let res = parseMarkdown(content, {})
|
||||
expect(res.lines).toEqual([
|
||||
' * foo', ' * bar', ' * one', ' * two'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseDocuments', () => {
|
||||
it('should parse documents with diagnostic filetypes', async () => {
|
||||
let docs = [{
|
||||
filetype: 'Error',
|
||||
content: 'Error text'
|
||||
}, {
|
||||
filetype: 'Warning',
|
||||
content: 'Warning text'
|
||||
}]
|
||||
let res = parseDocuments(docs)
|
||||
expect(res.lines).toEqual([
|
||||
'Error text',
|
||||
'─',
|
||||
'Warning text'
|
||||
])
|
||||
expect(res.codes).toEqual([
|
||||
{ hlGroup: 'CocErrorFloat', startLine: 0, endLine: 1 },
|
||||
{ hlGroup: 'CocWarningFloat', startLine: 2, endLine: 3 }
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse markdown document with filetype document', async () => {
|
||||
let docs = [{
|
||||
filetype: 'typescript',
|
||||
content: 'const workspace'
|
||||
}, {
|
||||
filetype: 'markdown',
|
||||
content: '**header**'
|
||||
}]
|
||||
let res = parseDocuments(docs)
|
||||
expect(res.lines).toEqual([
|
||||
'const workspace',
|
||||
'─',
|
||||
'header'
|
||||
])
|
||||
expect(res.highlights).toEqual([{
|
||||
hlGroup: 'CocBold',
|
||||
lnum: 2,
|
||||
colStart: 0,
|
||||
colEnd: 6
|
||||
}])
|
||||
expect(res.codes).toEqual([
|
||||
{ filetype: 'typescript', startLine: 0, endLine: 1 }
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse document with highlights', async () => {
|
||||
let docs: Documentation[] = [{
|
||||
filetype: 'txt',
|
||||
content: 'foo'
|
||||
}, {
|
||||
filetype: 'txt',
|
||||
content: 'foo bar',
|
||||
highlights: [{
|
||||
lnum: 0,
|
||||
colStart: 4,
|
||||
colEnd: 7,
|
||||
hlGroup: 'String'
|
||||
}]
|
||||
}]
|
||||
let res = parseDocuments(docs)
|
||||
let { highlights } = res
|
||||
expect(highlights).toEqual([{ lnum: 2, colStart: 4, colEnd: 7, hlGroup: 'String' }])
|
||||
})
|
||||
|
||||
it('should parse documents with active highlights', async () => {
|
||||
let docs = [{
|
||||
filetype: 'javascript',
|
||||
content: 'func(foo, bar)',
|
||||
active: [5, 8]
|
||||
}, {
|
||||
filetype: 'javascript',
|
||||
content: 'func()',
|
||||
active: [15, 20]
|
||||
}]
|
||||
let res = parseDocuments(docs as any)
|
||||
expect(res.highlights).toEqual([{ colStart: 5, colEnd: 8, lnum: 0, hlGroup: 'CocUnderline' }
|
||||
])
|
||||
})
|
||||
})
|
|
@ -1,119 +0,0 @@
|
|||
import { marked } from 'marked'
|
||||
import Renderer from '../../markdown/renderer'
|
||||
import * as styles from '../../markdown/styles'
|
||||
import { parseAnsiHighlights, AnsiResult } from '../../util/ansiparse'
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new Renderer()
|
||||
})
|
||||
|
||||
function parse(text: string): AnsiResult {
|
||||
let m = marked(text)
|
||||
let res = parseAnsiHighlights(m.split(/\n/)[0], true)
|
||||
return res
|
||||
}
|
||||
|
||||
describe('styles', () => {
|
||||
it('should add styles', async () => {
|
||||
let keys = ['gray', 'magenta', 'bold', 'underline', 'italic', 'strikethrough', 'yellow', 'green', 'blue']
|
||||
for (let key of keys) {
|
||||
let res = styles[key]('text')
|
||||
expect(res).toContain('text')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Renderer of marked', () => {
|
||||
it('should create bold highlights', async () => {
|
||||
let res = parse('**note**.')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 4],
|
||||
hlGroup: 'CocBold'
|
||||
})
|
||||
})
|
||||
|
||||
it('should create italic highlights', async () => {
|
||||
let res = parse('_note_.')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 4],
|
||||
hlGroup: 'CocItalic'
|
||||
})
|
||||
})
|
||||
|
||||
it('should create underline highlights for link', async () => {
|
||||
let res = parse('[baidu](https://baidu.com)')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 5],
|
||||
hlGroup: 'CocMarkdownLink'
|
||||
})
|
||||
res = parse('https://baidu.com')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 17],
|
||||
hlGroup: 'CocUnderline'
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse link', async () => {
|
||||
// let res = parse('https://doc.rust-lang.org/nightly/core/iter/traits/iterator/Iterator.t.html#map.v')
|
||||
// console.log(JSON.stringify(res, null, 2))
|
||||
let link = 'https://doc.rust-lang.org/nightly/core/iter/traits/iterator/Iterator.t.html#map.v'
|
||||
let parsed = marked(link)
|
||||
let res = parseAnsiHighlights(parsed.split(/\n/)[0], true)
|
||||
expect(res.line).toEqual(link)
|
||||
expect(res.highlights.length).toBeGreaterThan(0)
|
||||
expect(res.highlights[0].hlGroup).toBe('CocUnderline')
|
||||
})
|
||||
|
||||
it('should create highlight for code span', async () => {
|
||||
let res = parse('`let foo = "bar"`')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 15],
|
||||
hlGroup: 'CocMarkdownCode'
|
||||
})
|
||||
})
|
||||
|
||||
it('should create header highlights', async () => {
|
||||
let res = parse('# header')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 8],
|
||||
hlGroup: 'CocMarkdownHeader'
|
||||
})
|
||||
res = parse('## header')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 9],
|
||||
hlGroup: 'CocMarkdownHeader'
|
||||
})
|
||||
res = parse('### header')
|
||||
expect(res.highlights[0]).toEqual({
|
||||
span: [0, 10],
|
||||
hlGroup: 'CocMarkdownHeader'
|
||||
})
|
||||
})
|
||||
|
||||
it('should indent blockquote', async () => {
|
||||
let res = parse('> header')
|
||||
expect(res.line).toBe(' header')
|
||||
})
|
||||
|
||||
it('should preserve code block', async () => {
|
||||
let text = '``` js\nconsole.log("foo")\n```'
|
||||
let m = marked(text)
|
||||
expect(m.split('\n')).toEqual([
|
||||
'``` js',
|
||||
'console.log("foo")',
|
||||
'```',
|
||||
''
|
||||
])
|
||||
})
|
||||
|
||||
it('should renderer table', async () => {
|
||||
let text = `
|
||||
| Syntax | Description |
|
||||
| ----------- | ----------- |
|
||||
| Header | Title |
|
||||
| Paragraph | Text |
|
||||
`
|
||||
let res = marked(text)
|
||||
expect(res).toContain('Syntax')
|
||||
})
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
{}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue