diff --git a/vim/bundle/LanguageClient-neovim/.circleci/config.yml b/vim/bundle/LanguageClient-neovim/.circleci/config.yml index 7ff3c12..c9101d1 100644 --- a/vim/bundle/LanguageClient-neovim/.circleci/config.yml +++ b/vim/bundle/LanguageClient-neovim/.circleci/config.yml @@ -5,12 +5,26 @@ general: - /binary-.*/ jobs: + lint: + docker: + - image: circleci/rust:latest + steps: + - checkout + - run: | + rustup component add clippy + cargo clippy --all -- --deny warnings + format: + docker: + - image: circleci/rust:latest + steps: + - checkout + - run: cargo fmt -- --check build: docker: - image: autozimu/languageclientneovim steps: - checkout - - run: make test && make integration-test + - run: make ci deploy: docker: - image: autozimu/rust-cross-for-macos @@ -20,13 +34,12 @@ jobs: cargo build --target=x86_64-apple-darwin --release github-release upload --user autozimu --repo LanguageClient-neovim --tag test --name languageclient-x86_64-apple-darwin --file $CARGO_TARGET_DIR/x86_64-apple-darwin/release/languageclient -# workflows: -# version: 2 -# build-and-deploy: -# jobs: -# - build -# - deploy: -# requires: -# - build +workflows: + version: 2 + dev: + jobs: + - format + - lint + - build # vim: tabstop=2 diff --git a/vim/bundle/LanguageClient-neovim/.github/CONTRIBUTING.md b/vim/bundle/LanguageClient-neovim/.github/CONTRIBUTING.md index 83a7b4a..deaae38 100644 --- a/vim/bundle/LanguageClient-neovim/.github/CONTRIBUTING.md +++ b/vim/bundle/LanguageClient-neovim/.github/CONTRIBUTING.md @@ -1,29 +1,47 @@ # Prerequisites + - [rust] toolchain [rust]: https://www.rust-lang.org # Development + Put those settings inside vimrc, + ```vim let g:LanguageClient_devel = 1 " Use rust debug build -let g:LanguageClient_loggingLevel = 'DEBUG' " Use highest logging level +let g:LanguageClient_loggingLevel = 'INFO' " Optional, use higher logging level ``` -1. Make necessary changes. -1. Build. `make` to build, format and run [clippy], or `make build` to run build only. -1. Verify changes. -1. Run tests. (See below section) +- Make necessary changes. +- Execute `make` to build, format and [lint][clippy]. +- Running separate vim/neovim. Verify changes. +- Run tests. (See below section) [clippy]: https://github.com/rust-lang-nursery/rust-clippy # Run tests + (Option 1. Recommended) With docker installed, + ```sh make test && make integration-test-docker ``` (Option 2) Refer [`Dockerfile`](Dockerfile) to install tests dependencies. + ```sh make test && make integration-test ``` + +# Submit PR + +Please submit pull request to `dev` branch. This is to avoid mismatch between +vimscript and rust binary run by end user. + +# Release +1. Update [CHANGELOG](../CHANGELOG.md). +1. Issue command `cargo release patch`. Note you will need [`cargo-release`][cargo-release] installed. This will create a commit with updated version metadata, tag it, push to GitHub remote, which will then trigger Travis workflow to generate binaries. +1. Once Travis workflow is finished successfully, rebase `dev` branch onto `next` branch. + +[cargo-release]: https://github.com/sunng87/cargo-release diff --git a/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/bug_report.md b/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/bug_report.md index 8f9fdd9..28f2bf5 100644 --- a/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/bug_report.md +++ b/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,12 +10,14 @@ about: Create a report to help us improve - (Neovim users only) Did you check output of `:checkhealth LanguageClient`? - Did you check [troubleshooting]? -[troubleshooting]: https://github.com/autozimu/LanguageClient-neovim/blob/next/INSTALL.md#6-troubleshooting +[troubleshooting]: https://github.com/autozimu/LanguageClient-neovim/blob/next/INSTALL.md#troubleshooting ## Describe the bug + A clear and concise description of what the bug is. ## Environment + - neovim/vim version (`nvim --version` or `vim --version`): - This plugin version (`git rev-parse --short HEAD`): - This plugin's binary version (`bin/languageclient --version`): @@ -26,21 +28,27 @@ A clear and concise description of what the bug is. [min-vimrc.vim]: https://github.com/autozimu/LanguageClient-neovim/blob/next/min-vimrc.vim ## To Reproduce + Steps to reproduce the behavior: -1. Create/Fetch example project ... -1. Start vim, `nvim -u min-vimrc.vim` ... -1. Edit ... -1. Execute .... -1. See error + +- Create/Fetch example project ... +- Start vim, `nvim -u min-vimrc.vim` ... +- Edit ... +- Execute .... +- See error ## Current behavior + A clear and concise description of what's the current behavior. ## Expected behavior + A clear and concise description of what you expected to happen. ## Screenshots + If applicable, add screenshots to help explain your problem. ## Additional context + Add any other context about the problem here. diff --git a/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/feature_request.md b/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/feature_request.md index 77eebe1..09eb9fb 100644 --- a/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/feature_request.md +++ b/vim/bundle/LanguageClient-neovim/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,13 +5,17 @@ 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. diff --git a/vim/bundle/LanguageClient-neovim/.travis.yml b/vim/bundle/LanguageClient-neovim/.travis.yml index 03ad474..22b8cc2 100644 --- a/vim/bundle/LanguageClient-neovim/.travis.yml +++ b/vim/bundle/LanguageClient-neovim/.travis.yml @@ -14,7 +14,7 @@ env: matrix: include: # Linux - - env: TARGETS=i686-unknown-linux-musl:x86_64-unknown-linux-musl:aarch64-unknown-linux-gnu:i686-pc-windows-gnu:x86_64-pc-windows-gnu:x86_64-unknown-freebsd + - env: TARGETS=i686-unknown-linux-musl:x86_64-unknown-linux-musl:aarch64-unknown-linux-musl:x86_64-pc-windows-gnu # OSX - env: TARGETS=x86_64-apple-darwin diff --git a/vim/bundle/LanguageClient-neovim/CHANGELOG.md b/vim/bundle/LanguageClient-neovim/CHANGELOG.md new file mode 100644 index 0000000..d6ce502 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [0.1.158] + +### Added +- Add support of multiple configuration files (#1013). +- Add support for code actions using ranges. +- Add support of `$/progress` notification. +- Use a minimal style for neovim's floating window and add support for override (#1033). +- Add `hideVirtualTextsOnInsert` option (#982). +- Add support of populating `source` in diagnostics (#1062). +- Add support of overriding selection UI (#1059, #1060). +- Add plug mappings (#1065). + +### Changed +- Hide error of `ContentModified` (#997). +- Do not show `window/logMessage` messages (#1056). +- Change aarch64 build from `-gnu` to `-musl` (#1044). + +### Fixed +- Fix issue of no signs in sign column and virtual text on Windows (#970). +- Fix error handling of install script (#1011). +- Fix detection of Haskell cabal project root (#1048). +- Fix rust-analyzer runnable actions (#1058). + +### Thanks +Huge thanks to Martin (@martskins) for numerous contributions to this release, +from improving overall code structure, addressing pain points to answering +issues. + +## [0.1.157] + +### Added + +- Update cursor location properly after additional text edits (like automatic import) applied in completion (#961) +- Add [CHANGELOG](https://github.com/autozimu/LanguageClient-neovim/blob/next/CHANGELOG.md). +- Add support for `rust-anaylzer.selectAndApplySourceChange` (#965) +- Add support to customize highlight for floating hover window (#966) +- Add support for the proposed semantic highlighting (#954) +- Add `LanguageClient_applyCompletionAdditionalTextEdits` flag (#978) +- Add default root path for Go project (#976) +- Add doc for installation on ArchLinux through aur (#988) +- Add CodeLens integration with FZF (#996) +- Add option to set preferred MarkupKind (#972) +- New function `LanguageClient#isServerRunning()` for statusline integration (#1003) +- New function `LanguageClient#statusLineDiagnosticsCounts` (#1006) + +### Fixed + +- Fix issue calling `nvim_create_namespace` under vim #967 +- Fix signs not cleanup #1008 diff --git a/vim/bundle/LanguageClient-neovim/Cargo.lock b/vim/bundle/LanguageClient-neovim/Cargo.lock index ca902cf..a815bc8 100644 --- a/vim/bundle/LanguageClient-neovim/Cargo.lock +++ b/vim/bundle/LanguageClient-neovim/Cargo.lock @@ -1,9 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" -version = "0.6.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -11,7 +18,7 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -20,159 +27,236 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "atty" -version = "0.2.11" +name = "anyhow" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "backtrace" -version = "0.3.9" +name = "arc-swap" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "backtrace-sys" -version = "0.1.24" +name = "atty" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "bitflags" -version = "0.7.0" +name = "autocfg" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "bitflags" -version = "1.0.4" +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "base64" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "byteorder" -version = "1.2.6" +name = "bitflags" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "bytes" -version = "0.4.10" +name = "blake2b_simd" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.25" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chrono" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clap" -version = "2.32.0" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crc32fast" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam" -version = "0.3.2" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "crossbeam-utils" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "diff" -version = "0.1.11" +name = "derivative" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "dtoa" -version = "0.4.3" +name = "diff" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "failure" -version = "0.1.2" +name = "dirs" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "failure_derive" -version = "0.1.2" +name = "dirs-sys" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dtoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "filetime" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "flate2" -version = "1.0.3" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -182,20 +266,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "fsevent" -version = "0.2.17" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "fsevent-sys" -version = "0.1.6" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -203,7 +286,7 @@ name = "fuchsia-zircon" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -214,17 +297,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures" -version = "0.1.25" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "getrandom" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "glob" -version = "0.2.11" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "heck" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "humantime" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -232,12 +333,12 @@ dependencies = [ [[package]] name = "idna" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -245,13 +346,9 @@ name = "inotify" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -259,7 +356,7 @@ name = "inotify-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -267,25 +364,43 @@ name = "iovec" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "json-patch" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "treediff 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "jsonrpc-core" -version = "8.0.1" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -299,88 +414,59 @@ dependencies = [ [[package]] name = "languageclient" -version = "0.1.123" +version = "0.1.158" dependencies = [ + "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derivative 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "languageserver-types 0.51.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log4rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "json-patch 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lsp-types 0.74.2 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)", "pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "languageserver-types" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "shellexpand 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lazy_static" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "lazycell" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.43" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "linked-hash-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lock_api" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "log" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -390,25 +476,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "log4rs" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde-value 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_yaml 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lsp-types" +version = "0.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -423,38 +523,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" -version = "2.1.0" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "miniz-sys" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mio" -version = "0.6.16" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -463,10 +573,10 @@ name = "mio-extras" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -485,123 +595,78 @@ name = "net2" version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "notify" -version = "4.0.6" +version = "4.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", - "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "walkdir 2.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-derive" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.9 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-integer" -version = "0.1.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.1.43" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num_cpus" -version = "1.8.0" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ordered-float" -version = "0.5.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "owning_ref" -version = "0.3.3" +name = "pathdiff" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "parking_lot" -version = "0.6.4" +name = "percent-encoding" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "parking_lot_core" -version = "0.3.1" +name = "proc-macro2" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "pathdiff" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "proc-macro2" -version = "0.4.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -611,74 +676,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand" -version = "0.5.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rand_core" -version = "0.2.2" +name = "quote" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rand_core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "redox_termios" -version = "0.1.1" +name = "redox_users" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.0.5" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.2" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "rustc-demangle" -version = "0.1.9" +name = "rust-argon2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rustc_version" @@ -690,20 +742,20 @@ dependencies = [ [[package]] name = "ryu" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "same-file" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "scopeguard" -version = "0.3.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -721,198 +773,192 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.79" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde-value" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", + "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.79" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.32" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "serde_yaml" -version = "0.7.5" +name = "serde_repr" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "slab" -version = "0.4.1" +name = "serde_yaml" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "smallvec" -version = "0.6.5" +name = "shellexpand" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "stable_deref_trait" -version = "1.1.1" +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.12" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.2.12" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.9 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "0.14.9" +version = "0.15.44" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "0.15.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "synstructure" -version = "0.9.0" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "termion" -version = "1.5.1" +name = "thiserror" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "textwrap" -version = "0.10.0" +name = "thiserror-impl" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "thread_local" -version = "0.3.6" +name = "thread-id" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "time" -version = "0.1.40" +name = "thread_local" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "tokio-executor" -version = "0.1.5" +name = "time" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "tokio-io" -version = "0.1.9" +name = "traitobject" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] -name = "tokio-reactor" -version = "0.1.6" +name = "treediff" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "typemap" version = "0.3.3" @@ -921,11 +967,6 @@ dependencies = [ "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ucd-util" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unicode-bidi" version = "0.3.4" @@ -936,7 +977,15 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.7" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -950,12 +999,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "unreachable" -version = "1.0.0" +name = "unicode-xid" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "unsafe-any" @@ -967,53 +1013,35 @@ dependencies = [ [[package]] name = "url" -version = "1.7.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "url_serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf8-ranges" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "walkdir" -version = "2.2.5" +version = "2.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "same-file 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -1021,7 +1049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1040,10 +1068,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1062,140 +1090,145 @@ dependencies = [ [[package]] name = "yaml-rust" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] -"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" -"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" -"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" -"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" -"checksum bytes 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0ce55bd354b095246fc34caf4e9e242f5297a7fd938b090cadfea6eee614aa62" -"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" -"checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" -"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" -"checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" +"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" +"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" +"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +"checksum base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +"checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" +"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +"checksum derivative 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" -"checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" -"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9" -"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426" -"checksum filetime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "da4b9849e77b13195302c174324b5ba73eec9b236b24c221a61000daefb95c5f" -"checksum flate2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4af030962d89d62aa52cd9492083b1cd9b2d1a77764878102a6c0f86b4d5444d" +"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +"checksum dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8c63033fcba1f51ef744505b3cad42510432b904c062afa67ad7ece008429d" +"checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05" -"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" +"checksum fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +"checksum fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" -"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" +"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718" "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf83704f4e79979a424d1082dd2c1e52683058056c9280efa19ac5f6bc9033c" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum json-patch 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +"checksum jsonrpc-core 12.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "288dca7f9713710a29e485076b9340156cb701edb46a881f5d0c31aa4f5b9143" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum languageserver-types 0.51.0 (registry+https://github.com/rust-lang/crates.io-index)" = "caecadd973c43c93f5ce96fa457da310113d867af28808a8ed74023e9887a39e" -"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" -"checksum lazycell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddba4c30a78328befecec92fc94970e53b3ae385827d28620f0f5bb2493081e0" -"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" -"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" -"checksum lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775751a3e69bde4df9b38dd00a1b5d6ac13791e4223d4a0506577f0dd27cfb7a" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" +"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" -"checksum log4rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1f16090a553200fba94e104310b3e53e71f500fd9db7dc2143055aa3cc7ae63" +"checksum log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "100052474df98158c0738a7d3f4249c99978490178b5f9f68cd835ac57adbd1b" +"checksum lsp-types 0.74.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b360754e89e0e13c114245131382ba921d4ff1efabb918e549422938aaa8d392" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b" -"checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4" -"checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +"checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" +"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum notify 4.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "873ecfd8c174964ae30f401329d140142312c8e5590719cf1199d5f1717d8078" -"checksum num-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8af1847c907c2f04d7bfd572fb25bbb4385c637fe5be163cf2f8c5d778fe1e7d" -"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" -"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" -"checksum ordered-float 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7eb5259643245d3f292c7a146b2df53bba24d7eab159410e648eb73dc164669d" -"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -"checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" -"checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" +"checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3bf70094d203e07844da868b634207e71bfab254fe713171fae9a6e751ccf31" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum proc-macro2 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "3d7b7eaaa90b4a90a932a9ea6666c95a389e424eff347f0f793979289429feee" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2069749032ea3ec200ca51e4a31df41759190a88edca0d2d86ee8bedf7073341" -"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +"checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26" +"checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" +"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" -"checksum same-file 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f7794e2fda7f594866840e95f5c5962e886e228e68b6505885811a94dd728c" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "84257ccd054dc351472528c8587b4de2dbf0dc0fe2e634030c1a90bfdacebaa9" -"checksum serde-value 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "52903ade2290cbd61a0937a66a268f26cebf246e3ddd7964a8babb297111fb0d" -"checksum serde_derive 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)" = "31569d901045afbff7a9479f793177fe9259819aff10ab4f89ef69bbc5f567fe" -"checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce" -"checksum serde_yaml 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" -"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d" -"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum structopt 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "d77af7242f18c40fd19cb270985930f239ee1646cfb482050bbae9da1d18743b" -"checksum structopt-derive 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "17ff01fe96de9d16e7372ae5f19dd7ece2c703b51043c3db9ea27f9e393ea311" -"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" -"checksum syn 0.15.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b10ee269228fb723234fce98e9aac0eaed2bd5f1ad2f6930e8d5b93f04445a1a" -"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" +"checksum serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7a663f873dedc4eac1a559d4c6bc0d0b2c34dc5ac4702e105014b8281489e44f" +"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" +"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" +"checksum serde_repr 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "cd02c7587ec314570041b2754829f84d873ced14a96d1fd1823531e11db40573" +"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum shellexpand 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" +"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" +"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" +"checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" -"checksum tokio-executor 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c117b6cf86bb730aab4834f10df96e4dd586eff2c3c27d3781348da49e255bde" -"checksum tokio-io 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "8b8a85fffbec3c5ab1ab62324570230dcd37ee5996a7859da5caf7b9d45e3e8c" -"checksum tokio-reactor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4b26fd37f1125738b2170c80b551f69ff6fecb277e6e5ca885e53eec2b005018" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +"checksum treediff 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" "checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -"checksum url 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a321979c09843d272956e73700d12c4e7d3d92b2ee112b31548aef0d4efc5a6" -"checksum url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74e7d099f1ee52f823d4bdd60c93c3602043c728f5db3b97bdb548467f7bddea" -"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" +"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum walkdir 2.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "af464bc7be7b785c7ac72e266a6b67c4c9070155606f51655a650a6686204e35" +"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" +"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -"checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/vim/bundle/LanguageClient-neovim/Cargo.toml b/vim/bundle/LanguageClient-neovim/Cargo.toml index 6e90938..b649c89 100644 --- a/vim/bundle/LanguageClient-neovim/Cargo.toml +++ b/vim/bundle/LanguageClient-neovim/Cargo.toml @@ -1,34 +1,41 @@ [package] name = "languageclient" -version = "0.1.123" +version = "0.1.158" authors = ["Junfeng Li "] description = "Language Server Protocol (LSP) support for vim and neovim" publish = false +edition = "2018" + +[package.metadata.release] +no-dev-version = true +pre-release-replacements = [ + { file = "install.sh", search = "\nversion=.*", replace = "\nversion={{version}}" }, + { file = "install.ps1", search = "version = '.*'", replace = "version = '{{version}}'" }, + { file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}" }, +] +pre-release-hook = ["cargo", "build"] +tag-name = "{{version}}" [dependencies] -failure = "0" -log = "0" +itertools = "0.8" +log = "0.4" log4rs = "0" structopt = "0" maplit = "1" - serde = "1" serde_derive = "1" serde_json = "1" - -jsonrpc-core = "8" -languageserver-types = "0" -url = "1" +json-patch = "0.2" +crossbeam = "0.7.3" +jsonrpc-core = "12" +lsp-types = { version = "0.74.2", features = ["proposed"] } +url = "2" pathdiff = "0" diff = "0" regex = "1" glob = "0" notify = "4" - -[package.metadata.release] -no-dev-version = true -pre-release-replacements = [ - { file = "install.sh", search = "version=.*", replace = "version={{version}}" }, - { file = "install.ps1", search = "version = '.*'", replace = "version = '{{version}}'" } -] -pre-release-hook = ["cargo", "build"] +shellexpand = "2.0.0" +derivative = "2.1.1" +anyhow = "1.0.31" +thiserror = "1.0.19" diff --git a/vim/bundle/LanguageClient-neovim/INSTALL.md b/vim/bundle/LanguageClient-neovim/INSTALL.md index fc3bc24..70b13d3 100644 --- a/vim/bundle/LanguageClient-neovim/INSTALL.md +++ b/vim/bundle/LanguageClient-neovim/INSTALL.md @@ -1,12 +1,31 @@ -# 1. Install neovim or vim +# Requirements -Obviously you need [neovim](https://github.com/neovim/neovim#install-from-package) or [vim](http://www.vim.org/)! +- neovim or +- vim >= 8.0 -# 2. Install dependencies +# Install dependencies None. -# 3. Install this plugin +# Install this plugin + +## Linux + +Packages are available for the following distributions: + +### Arch Linux + +There are two packages available on the [AUR][archlinux/aur] + +- [`aur/languageclient-neovim`][archlinux/install/aur] +- [`aur/languageclient-neovim-bin`][archlinux/install/aur-bin] + +[archlinux/aur]: https://wiki.archlinux.org/index.php/Arch_User_Repository +[archlinux/install/aur]: https://aur.archlinux.org/packages/languageclient-neovim +[archlinux/install/aur-bin]: https://aur.archlinux.org/packages/languageclient-neovim-bin + + +## With a plugin manager > For Windows users, replace `bash install.sh` with `powershell -executionpolicy bypass -File install.ps1` in following > snippets. @@ -17,10 +36,10 @@ None. > Android is not supported using the `install.sh` script. -Choose steps matching your plugin manager. - ## [vim-plug](https://github.com/junegunn/vim-plug) user + Add following to vimrc + ```vim Plug 'autozimu/LanguageClient-neovim', { \ 'branch': 'next', @@ -31,7 +50,9 @@ Plug 'autozimu/LanguageClient-neovim', { Restart neovim and run `:PlugInstall` to install. ## [dein.vim](https://github.com/Shougo/dein.vim) user + Add following to vimrc + ```vim call dein#add('autozimu/LanguageClient-neovim', { \ 'rev': 'next', @@ -42,7 +63,9 @@ call dein#add('autozimu/LanguageClient-neovim', { Restart neovim and run `:call dein#install()` to install. ## Manual + Clone this repo into some place, e.g., `~/.vim-plugins` + ```sh mkdir -p ~/.vim-plugins cd ~/.vim-plugins @@ -52,18 +75,22 @@ bash install.sh ``` Add this plugin to vim/neovim `runtimepath`, + ```vim set runtimepath+=~/.vim-plugins/LanguageClient-neovim ``` -# 4. Install language servers +# Install language servers + Install language servers if corresponding language servers are not available yet on your system. Please see and/or for list of language servers. -# 5. Configure this plugin +# Configure this plugin + Example configuration + ```vim " Required for operations modifying multiple buffers like rename. set hidden @@ -73,6 +100,7 @@ let g:LanguageClient_serverCommands = { \ 'javascript': ['/usr/local/bin/javascript-typescript-stdio'], \ 'javascript.jsx': ['tcp://127.0.0.1:2089'], \ 'python': ['/usr/local/bin/pyls'], + \ 'ruby': ['~/.rbenv/shims/solargraph', 'stdio'], \ } nnoremap K :call LanguageClient#textDocument_hover() @@ -80,12 +108,13 @@ nnoremap gd :call LanguageClient#textDocument_definition() nnoremap :call LanguageClient#textDocument_rename() ``` -# 6. Troubleshooting +# Troubleshooting -1. Backup your vimrc and use [min-vimrc.vim](min-vimrc.vim) as vimrc. -1. Try on [sample projects](tests/data). -1. Execute `:echo &runtimepath` and make sure the plugin path is in the list. -1. Make sure language server could be started when invoked manually from shell. - Also try use absolute path for server commands, as PATH in vim might be different from shell env, especially on macOS. -1. Check content of log file. Also worth noting language server might have - separate log file. +- Backup your vimrc and use [min-vimrc.vim](min-vimrc.vim) as vimrc. +- Try on [sample projects](tests/data). +- Execute `:echo &runtimepath` and make sure the plugin path is in the list. +- Make sure language server could be started when invoked manually from shell. + Also try use absolute path for server commands, as PATH in vim might be + different from shell env, especially on macOS. +- Check content of log file. Also worth noting language server might have + separate log file. diff --git a/vim/bundle/LanguageClient-neovim/Makefile b/vim/bundle/LanguageClient-neovim/Makefile index 0edc806..08a57c2 100644 --- a/vim/bundle/LanguageClient-neovim/Makefile +++ b/vim/bundle/LanguageClient-neovim/Makefile @@ -1,4 +1,9 @@ -all: check build fmt clippy +all: release + +dev: build fmt clippy vint + +ci: vint python-lint test integration-test + cargo fmt -- --check check: cargo check @@ -17,35 +22,35 @@ vint: release: cargo build --release - cp -f target/release/languageclient bin/ + [ -z "${CARGO_TARGET_DIR}" ] && \ + cp -f target/release/languageclient bin/ || \ + cp -f ${CARGO_TARGET_DIR}/release/languageclient bin/ chmod a+x bin/languageclient bump-version: - [[ `git rev-parse --abbrev-ref HEAD` == "next" ]] || (echo "Not on branch next"; exit 1) + [[ `git rev-parse --abbrev-ref HEAD` == "dev" ]] || (echo "Not on branch dev"; exit 1) cargo release patch test: cargo test -integration-test-lint: +python-lint: mypy --ignore-missing-imports \ tests \ rplugin/python3/denite/source \ rplugin/python3/deoplete/sources flake8 . -integration-test: build integration-test-lint +integration-test: build tests/test.sh integration-test-docker: - docker image pull autozimu/languageclientneovim docker run --volume ${CURDIR}:/root/.config/nvim autozimu/languageclientneovim bash -c "\ export CARGO_TARGET_DIR=/tmp && \ cd /root/.config/nvim && \ make integration-test" integration-test-docker-debug: - docker image pull autozimu/languageclientneovim docker run --interactive --tty --volume ${CURDIR}:/root/.config/nvim autozimu/languageclientneovim cleanup-binary-tags: diff --git a/vim/bundle/LanguageClient-neovim/README.md b/vim/bundle/LanguageClient-neovim/README.md index a5bc9c4..d194045 100644 --- a/vim/bundle/LanguageClient-neovim/README.md +++ b/vim/bundle/LanguageClient-neovim/README.md @@ -1,11 +1,12 @@ > For legacy python implementation, see [branch master](https://github.com/autozimu/LanguageClient-neovim/tree/master). # LanguageClient-neovim + [![CircleCI](https://circleci.com/gh/autozimu/LanguageClient-neovim.svg?style=svg)](https://circleci.com/gh/autozimu/LanguageClient-neovim) -[Language Server Protocol] (LSP) support for [vim] and [neovim]. +[Language Server Protocol](LSP) support for [vim] and [neovim]. -[Language Server Protocol]: https://github.com/Microsoft/language-server-protocol +[language server protocol]: https://github.com/Microsoft/language-server-protocol [neovim]: https://neovim.io/ [vim]: http://www.vim.org/ @@ -17,8 +18,9 @@ More recordings at [Updates, screenshots & GIFs](https://github.com/autozimu/Lan - Non-blocking asynchronous calls. - [Sensible completion](https://github.com/autozimu/LanguageClient-neovim/issues/35#issuecomment-288731936). - Integrated with [deoplete](https://github.com/Shougo/deoplete.nvim) or - [ncm2](https://github.com/ncm2/ncm2). Or with vim built-in `omnifunc`. + Integrated well with [deoplete](https://github.com/Shougo/deoplete.nvim) or + [ncm2](https://github.com/ncm2/ncm2), or [MUcomplete](https://github.com/lifepillar/vim-mucomplete). + Or simply with vim built-in `omnifunc`. - [Realtime diagnostics/compiler/lint message.](https://github.com/autozimu/LanguageClient-neovim/issues/35#issuecomment-288732042) - [Rename.](https://github.com/autozimu/LanguageClient-neovim/issues/35#issuecomment-288731403) - [Hover/Get identifier info.](https://github.com/autozimu/LanguageClient-neovim/issues/35#issuecomment-288731665) @@ -40,8 +42,6 @@ Plug 'autozimu/LanguageClient-neovim', { " (Optional) Multi-entry selection UI. Plug 'junegunn/fzf' - -Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } ``` Example configuration @@ -55,41 +55,41 @@ let g:LanguageClient_serverCommands = { \ 'javascript': ['/usr/local/bin/javascript-typescript-stdio'], \ 'javascript.jsx': ['tcp://127.0.0.1:2089'], \ 'python': ['/usr/local/bin/pyls'], + \ 'ruby': ['~/.rbenv/shims/solargraph', 'stdio'], \ } -nnoremap :call LanguageClient_contextMenu() +" note that if you are using Plug mapping you should not use `noremap` mappings. +nmap (lcn-menu) " Or map each action separately -nnoremap K :call LanguageClient#textDocument_hover() -nnoremap gd :call LanguageClient#textDocument_definition() -nnoremap :call LanguageClient#textDocument_rename() +nmap K (lcn-hover) +nmap gd (lcn-definition) +nmap (lcn-rename) ``` Run command `nvim +PlugInstall +UpdateRemotePlugins +qa` in shell to install this plugin. Install corresponding language servers. Restart neovim/vim and language services will be available right away. Happy hacking! -# Install +# Mappings -[INSTALL](INSTALL.md) +LanguageClient-neovim defines various Plug mappings, see `:help LanguageClientMappings` for a full +list and an example configuration. -# Troubleshooting +# Install -[Troubleshooting](INSTALL.md#6-troubleshooting) +[Full installation steps](INSTALL.md) # Language Servers -Please see and/or . +**Note**, you will also need language server(s) to take advantages of +this plugin. To find list of language server implementations and how +to install them, please see and/or +. # Documentation -[`:help LanguageClient`][LanguageClient.txt] for full list of configurations, commands and functions. - -[LanguageClient.txt]: doc/LanguageClient.txt - -# Contributing - -[CONTRIBUTING](.github/CONTRIBUTING.md) - -# License - -The MIT License. +- [`:help LanguageClient`](doc/LanguageClient.txt) +- [Changelog](CHANGELOG.md) +- [Troubleshooting](INSTALL.md#troubleshooting) +- [Contributing](.github/CONTRIBUTING.md) +- [The MIT License](LICENSE.txt) diff --git a/vim/bundle/LanguageClient-neovim/TODO.md b/vim/bundle/LanguageClient-neovim/TODO.md index 3e76d11..c4b34a1 100644 --- a/vim/bundle/LanguageClient-neovim/TODO.md +++ b/vim/bundle/LanguageClient-neovim/TODO.md @@ -2,3 +2,4 @@ - More integration tests. - Check server capabilities before calling RPC API. - Replace echodoc functionality. +- Async/await rust. diff --git a/vim/bundle/LanguageClient-neovim/autoload/LSP.vim b/vim/bundle/LanguageClient-neovim/autoload/LSP.vim index 1d17aef..9cdadb2 100644 --- a/vim/bundle/LanguageClient-neovim/autoload/LSP.vim +++ b/vim/bundle/LanguageClient-neovim/autoload/LSP.vim @@ -1,10 +1,23 @@ +" TODO: make buffer aware. + function! LSP#filename() abort - return expand('%:p') + " When executing autocommand, `%` might have already changed. + let l:filename = expand(':p') + if !l:filename + let l:filename = expand('%:p') + endif + return l:filename endfunction -function! LSP#text() abort - let l:lines = getline(1, '$') - if l:lines[-1] !=# '' && &fixendofline +" This function will return buffer text as required by LSP. +" +" The main difference with getbufline is that it checks fixendofline settings +" and add extra line at ending if appropriate. +function! LSP#text(...) abort + let l:buf = get(a:000, 0, '') + + let l:lines = getbufline(l:buf, 1, '$') + if len(l:lines) > 0 && l:lines[-1] !=# '' && &fixendofline let l:lines += [''] endif return l:lines @@ -31,10 +44,54 @@ function! LSP#range_end_line() abort return getpos("'>")[1] endfunction -function! LSP#visible_line_start() abort - return line('w0') - 1 +function! LSP#viewport() abort + return { + \ 'start': line('w0') - 1, + \ 'end': line('w$'), + \ } +endfunction + +function! LSP#position() abort + return s:get_position() +endfunction + +function! LSP#range(mode) abort + if a:mode ==# 'v' + return s:get_visual_selection() + endif + + return { + \ 'start': s:get_position(), + \ 'end': s:get_position(), + \ } +endfunction + +function! s:get_position() abort + return { + \ 'line': LSP#line(), + \ 'character': LSP#character(), + \ } endfunction -function! LSP#visible_line_end() abort - return line('w$') - 1 +function! s:get_visual_selection() abort + let [line_start, column_start] = getpos("'<")[1:2] + let [line_end, column_end] = getpos("'>")[1:2] + let lines = getline(line_start, line_end) + if len(lines) == 0 + echohl Error | echom '[LC] No lines found in range.' | echohl None + return '' + endif + let lines[-1] = lines[-1][: column_end - 2] + let lines[0] = lines[0][column_start - 1:] + + return { + \ 'start': { + \ 'line': line_start - 1, + \ 'character': column_start - 1, + \ }, + \ 'end': { + \ 'line': line_end - 1, + \ 'character': column_end - 1, + \ } + \ } endfunction diff --git a/vim/bundle/LanguageClient-neovim/autoload/LanguageClient.vim b/vim/bundle/LanguageClient-neovim/autoload/LanguageClient.vim index a4e103b..7b566e5 100644 --- a/vim/bundle/LanguageClient-neovim/autoload/LanguageClient.vim +++ b/vim/bundle/LanguageClient-neovim/autoload/LanguageClient.vim @@ -8,6 +8,12 @@ let s:TYPE = { \ 'dict': type({}), \ 'funcref': type(function('call')) \ } +let s:FLOAT_WINDOW_AVAILABLE = exists('*nvim_open_win') +let s:POPUP_WINDOW_AVAILABLE = exists('*popup_atcursor') + +function! s:AddPrefix(message) abort + return '[LC] ' . a:message +endfunction function! s:Echo(message) abort echo a:message @@ -37,7 +43,7 @@ endfunction " `echomsg` message without trigger |hit-enter| function! s:EchomsgEllipsis(message) abort " Credit: ALE, snippets from ale#cursor#TruncatedEcho() - let l:message = a:message + let l:message = s:AddPrefix(a:message) " Change tabs to spaces. let l:message = substitute(l:message, "\t", ' ', 'g') " Remove any newlines in the message. @@ -64,15 +70,28 @@ function! s:EchomsgEllipsis(message) abort endfunction function! s:Echomsg(message) abort - echomsg a:message + echomsg s:AddPrefix(a:message) endfunction function! s:Echoerr(message) abort - echohl Error | echomsg a:message | echohl None + echohl Error | echomsg s:AddPrefix(a:message) | echohl None endfunction function! s:Echowarn(message) abort - echohl WarningMsg | echomsg a:message | echohl None + echohl WarningMsg | echomsg s:AddPrefix(a:message) | echohl None +endfunction + +" timeout: skip function call f until this timeout, in seconds. +function! s:Debounce(timeout, f) abort + " Map function to its last execute time. + let s:DebounceMap = {} + let l:lastexectime = get(s:DebounceMap, a:f) + if l:lastexectime == 0 || reltimefloat(reltime(l:lastexectime)) < a:timeout + let s:DebounceMap[a:f] = reltime() + return v:true + else + return v:false + endif endfunction function! s:Debug(message) abort @@ -86,26 +105,39 @@ function! s:Debug(message) abort endfunction function! s:hasSnippetSupport() abort - if get(g:, 'LanguageClient_hasSnippetSupport', 1) !=# 1 - return 0 + if exists('g:LanguageClient_hasSnippetSupport') + return g:LanguageClient_hasSnippetSupport !=# 0 endif - " https://github.com/SirVer/ultisnips - if exists('g:did_plugin_ultisnips') - return 1 - endif " https://github.com/Shougo/neosnippet.vim if exists('g:loaded_neosnippet') return 1 endif - " https://github.com/garbas/vim-snipmate - if exists('g:loaded_snips') - return 1 - endif return 0 endfunction +function! s:getSelectionUI() abort + if type(get(g:, 'LanguageClient_selectionUI', v:null)) is s:TYPE.funcref + return 'funcref' + else + return get(g:, 'LanguageClient_selectionUI', v:null) + endif +endfunction + +function! s:useVirtualText() abort + let l:use = s:GetVar('LanguageClient_useVirtualText') + if l:use isnot v:null + return l:use + endif + + if exists('*nvim_buf_set_virtual_text') + return 'All' + else + return 'No' + endif +endfunction + function! s:IsTrue(v) abort if type(a:v) ==# type(0) return a:v ==# 0 ? v:false : v:true @@ -125,6 +157,44 @@ function! s:Bufnames() abort return map(filter(range(0,bufnr('$')), 'buflisted(v:val)'), 'fnamemodify(bufname(v:val), '':p'')') endfunction +" Clear and set virtual texts between line_start and line_end (exclusive). +function! s:set_virtual_texts(buf_id, ns_id, line_start, line_end, virtual_texts) abort + " VirtualText: map with keys line, text and hl_group. + + let l:prefix = s:GetVar('LanguageClient_virtualTextPrefix') + if l:prefix is v:null + let l:prefix = '' + endif + + if !exists('*nvim_buf_set_virtual_text') + return + endif + + call nvim_buf_clear_namespace(a:buf_id, a:ns_id, a:line_start, a:line_end) + + for vt in a:virtual_texts + call nvim_buf_set_virtual_text(a:buf_id, a:ns_id, vt['line'], [[l:prefix . vt['text'], vt['hl_group']]], {}) + endfor +endfunction + +function! s:set_signs(file, signs_to_add, signs_to_delete) abort + " TODO: Optimize to update sign instead of add + remove sign. + for l:sign in a:signs_to_add + let l:line = l:sign['line'] + 1 + execute ':sign place ' . l:sign['id'] . ' line=' . l:line . ' name=' . l:sign['name'] . ' file=' . a:file + endfor + for l:sign in a:signs_to_delete + execute ':sign unplace ' . l:sign['id'] + endfor +endfunction + +" Execute serious of ex commands. +function! s:command(...) abort + for l:cmd in a:000 + execute l:cmd + endfor +endfunction + function! s:getInput(prompt, default) abort call inputsave() let l:input = input(a:prompt, a:default) @@ -132,25 +202,45 @@ function! s:getInput(prompt, default) abort return l:input endfunction +function! s:inputlist(...) abort + call inputsave() + let l:selection = inputlist(a:000) + call inputrestore() + return l:selection +endfunction + +function! s:selectionUI_funcref(source, sink) abort + if type(get(g:, 'LanguageClient_selectionUI')) is s:TYPE.funcref + call call(g:LanguageClient_selectionUI, [a:source, function(a:sink)]) + elseif get(g:, 'LanguageClient_selectionUI', 'FZF') ==? 'FZF' + \ && get(g:, 'loaded_fzf') + call s:FZF(a:source, a:sink) + else + call s:Echoerr('Unsupported selection UI, use "FZF" or a funcref') + return + endif +endfunction + function! s:FZF(source, sink) abort if !get(g:, 'loaded_fzf') call s:Echoerr('FZF not loaded!') return endif - if exists('LanguageClient_fzfOptions') - let l:options = LanguageClient_fzfOptions - elseif exists('*fzf#vim#with_preview') - let l:options = fzf#vim#with_preview('right:50%:hidden', '?').options - else - let l:options = [] + let l:options = s:GetVar('LanguageClient_fzfOptions') + if l:options is v:null + if exists('*fzf#vim#with_preview') + let l:options = fzf#vim#with_preview('right:50%:hidden', '?').options + else + let l:options = [] + endif endif call fzf#run(fzf#wrap({ \ 'source': a:source, \ 'sink': function(a:sink), \ 'options': l:options, \ })) - if has('nvim') + if has('nvim') && !has('nvim-0.4') call feedkeys('i') endif endfunction @@ -159,7 +249,7 @@ function! s:Edit(action, path) abort " If editing current file, push current location to jump list. let l:bufnr = bufnr(a:path) if l:bufnr == bufnr('%') - execute 'normal m`' + execute 'normal! m`' return endif @@ -167,6 +257,7 @@ function! s:Edit(action, path) abort " Avoid the 'not saved' warning. if l:action ==# 'edit' && l:bufnr != -1 execute 'buffer' l:bufnr + set buflisted return endif @@ -180,6 +271,16 @@ function! s:MatchDelete(ids) abort endfor endfunction +function! s:ApplySemanticHighlights(bufnr, ns_id, clears, highlights) abort + for clear in a:clears + call nvim_buf_clear_namespace(a:bufnr, a:ns_id, clear.line_start, clear.line_end) + endfor + + for hl in a:highlights + call nvim_buf_add_highlight(a:bufnr, a:ns_id, hl.group, hl.line, hl.character_start, hl.character_end) + endfor +endfunction + " Batch version of nvim_buf_add_highlight function! s:AddHighlights(source, highlights) abort for hl in a:highlights @@ -187,6 +288,213 @@ function! s:AddHighlights(source, highlights) abort endfor endfunction +" Get an variable value. +" Get variable from buffer local, or else global, or else default, or else v:null. +function! s:GetVar(...) abort + let name = a:1 + + if exists('b:' . name) + return get(b:, name) + elseif exists('g:' . name) + return get(g:, name) + else + return get(a:000, 1, v:null) + endif +endfunction + +" if the argument is a list, return it unchanged, otherwise return the list +" containing the argument. +function! s:ToList(x) abort + if type(a:x) == v:t_list + return a:x + else + return [ a:x ] + endif +endfunction + +function! s:ShouldUseFloatWindow() abort + let floatingHoverEnabled = s:GetVar('LanguageClient_useFloatingHover', v:true) + return s:FLOAT_WINDOW_AVAILABLE && floatingHoverEnabled +endfunction + +function! s:CloseFloatingHover() abort + if !exists('s:float_win_id') + return + endif + + autocmd! plugin-LC-neovim-close-hover + let winnr = win_id2win(s:float_win_id) + if winnr == 0 + return + endif + execute winnr . 'wincmd c' +endfunction + +function! s:CloseFloatingHoverOnCursorMove(opened) abort + if getpos('.') == a:opened + " Just after opening floating window, CursorMoved event is run. + " To avoid closing floating window immediately, check the cursor + " was really moved + return + endif + autocmd! plugin-LC-neovim-close-hover + let winnr = win_id2win(s:float_win_id) + if winnr == 0 + return + endif + execute winnr . 'wincmd c' +endfunction + +function! s:CloseFloatingHoverOnBufEnter(bufnr) abort + let winnr = win_id2win(s:float_win_id) + if winnr == 0 + " Float window was already closed + autocmd! plugin-LC-neovim-close-hover + return + endif + if winnr == winnr() + " Cursor is moving into floating window. Do not close it + return + endif + if bufnr('%') == a:bufnr + " When current buffer opened hover window, it's not another buffer. Skipped + return + endif + autocmd! plugin-LC-neovim-close-hover + execute winnr . 'wincmd c' +endfunction + +" Open preview window. Window is open in: +" - Floating window on Neovim (0.4.0 or later) +" - popup window on vim (8.2 or later) +" - Preview window on Neovim (0.3.0 or earlier) or Vim +function! s:OpenHoverPreview(bufname, lines, filetype) abort + " Use local variable since parameter is not modifiable + let lines = a:lines + let bufnr = bufnr('%') + + let display_approach = '' + if s:ShouldUseFloatWindow() + let display_approach = 'float_win' + elseif s:POPUP_WINDOW_AVAILABLE && s:GetVar('LanguageClient_usePopupHover', v:true) + let display_approach = 'popup_win' + else + let display_approach = 'preview' + endif + + if display_approach ==# 'float_win' + " When a language server takes a while to initialize and the user + " calls hover multiple times during that time (for example, via an + " automatic hover on cursor move setup), we will get a number of + " successive calls into this function resulting in many hover windows + " opened. This causes a number of issues, and we only really want one, + " so make sure that the previous hover window is closed. + call s:CloseFloatingHover() + + let pos = getpos('.') + + " Calculate width and height and give margin to lines + let width = 0 + for index in range(len(lines)) + let line = lines[index] + if line !=# '' + " Give a left margin + let line = ' ' . line + endif + let lw = strdisplaywidth(line) + if lw > width + let width = lw + endif + let lines[index] = line + endfor + + " Give margin + let width += 1 + let lines = [''] + lines + [''] + let height = len(lines) + + " Calculate anchor + " Prefer North, but if there is no space, fallback into South + let bottom_line = line('w0') + winheight(0) - 1 + if pos[1] + height <= bottom_line + let vert = 'N' + let row = 1 + else + let vert = 'S' + let row = 0 + endif + + " Prefer West, but if there is no space, fallback into East + if pos[2] + width <= &columns + let hor = 'W' + let col = 0 + else + let hor = 'E' + let col = 1 + endif + + let s:float_win_id = nvim_open_win(bufnr, v:true, { + \ 'relative': 'cursor', + \ 'anchor': vert . hor, + \ 'row': row, + \ 'col': col, + \ 'width': width, + \ 'height': height, + \ 'style': s:GetVar('LanguageClient_floatingWindowStyle', 'minimal'), + \ }) + + execute 'noswapfile edit!' a:bufname + + let float_win_highlight = s:GetVar('LanguageClient_floatingHoverHighlight', 'Normal:CursorLine') + execute printf('setlocal winhl=%s', float_win_highlight) + elseif display_approach ==# 'popup_win' + let pop_win_id = popup_atcursor(a:lines, {}) + call setbufvar(winbufnr(pop_win_id), '&filetype', a:filetype) + elseif display_approach ==# 'preview' + execute 'silent! noswapfile pedit!' a:bufname + wincmd P + else + call s:Echoerr('Unknown display approach: ' . display_approach) + endif + + if display_approach !=# 'popup_win' + setlocal buftype=nofile nobuflisted bufhidden=wipe nonumber norelativenumber signcolumn=no modifiable + + if a:filetype isnot v:null + let &filetype = a:filetype + endif + + call setline(1, lines) + setlocal nomodified nomodifiable + + wincmd p + endif + + if display_approach ==# 'float_win' + " Unlike preview window, :pclose does not close window. Instead, close + " hover window automatically when cursor is moved. + let call_after_move = printf('CloseFloatingHoverOnCursorMove(%s)', string(pos)) + let call_on_bufenter = printf('CloseFloatingHoverOnBufEnter(%d)', bufnr) + augroup plugin-LC-neovim-close-hover + execute 'autocmd CursorMoved,CursorMovedI,InsertEnter call ' . call_after_move + execute 'autocmd BufEnter * call ' . call_on_bufenter + augroup END + endif +endfunction + +function! s:MoveIntoHoverPreview() abort + for bufnr in range(1, bufnr('$')) + if bufname(bufnr) ==# '__LanguageClient__' + let winnr = bufwinnr(bufnr) + if winnr != -1 + execute winnr . 'wincmd w' + endif + return v:true + endif + endfor + return v:false +endfunction + let s:id = 1 let s:handlers = {} @@ -234,16 +542,9 @@ function! s:HandleMessage(job, lines, event) abort let l:method = get(l:message, 'method') let l:params = get(l:message, 'params') try - if l:method ==# 'execute' - for l:cmd in l:params - execute l:cmd - endfor - let l:result = 0 - else - let l:params = type(l:params) == s:TYPE.list ? l:params : [l:params] - let l:result = call(l:method, l:params) - endif - if l:id != v:null + let l:params = type(l:params) == s:TYPE.list ? l:params : [l:params] + let l:result = call(l:method, l:params) + if l:id isnot v:null call LanguageClient#Write(json_encode({ \ 'jsonrpc': '2.0', \ 'id': l:id, @@ -252,7 +553,7 @@ function! s:HandleMessage(job, lines, event) abort endif catch let l:exception = v:exception - if l:id != v:null + if l:id isnot v:null try call LanguageClient#Write(json_encode({ \ 'jsonrpc': '2.0', @@ -290,7 +591,7 @@ function! s:HandleMessage(job, lines, event) abort elseif a:event ==# 'stderr' call s:Echoerr('LanguageClient stderr: ' . string(a:lines)) elseif a:event ==# 'exit' - if type(a:lines) == type(0) && a:lines == 0 + if type(a:lines) == type(0) && (a:lines == 0 || a:lines == 143) return endif call s:Debug('LanguageClient exited with: ' . string(a:lines)) @@ -319,7 +620,7 @@ function! s:HandleOutput(output, ...) abort if has_key(a:output, 'result') " let l:result = string(a:result) - " if l:result !=# 'v:null' + " if l:result isnot v:null " echomsg l:result " endif return get(a:output, 'result') @@ -411,7 +712,20 @@ function! LanguageClient#Write(message) abort endif endfunction +function! s:SkipSendingMessage() abort + if expand('%') =~# '^jdt://' + return v:false + endif + + return &buftype !=# '' || &filetype ==# '' || expand('%') ==# '' +endfunction + function! LanguageClient#Call(method, params, callback, ...) abort + if s:SkipSendingMessage() + " call s:Debug('Skip sending message') + return + endif + let l:id = s:id let s:id = s:id + 1 if a:callback is v:null @@ -422,8 +736,9 @@ function! LanguageClient#Call(method, params, callback, ...) abort let l:skipAddParams = get(a:000, 0, v:false) let l:params = a:params if type(a:params) == s:TYPE.dict && !skipAddParams + " TODO: put inside context. let l:params = extend({ - \ 'buftype': &buftype, + \ 'bufnr': bufnr(''), \ 'languageId': &filetype, \ }, l:params) endif @@ -436,10 +751,15 @@ function! LanguageClient#Call(method, params, callback, ...) abort endfunction function! LanguageClient#Notify(method, params) abort + if s:SkipSendingMessage() + " call s:Debug('Skip sending message') + return + endif + let l:params = a:params if type(params) == s:TYPE.dict let l:params = extend({ - \ 'buftype': &buftype, + \ 'bufnr': bufnr(''), \ 'languageId': &filetype, \ }, l:params) endif @@ -451,6 +771,9 @@ function! LanguageClient#Notify(method, params) abort endfunction function! LanguageClient#textDocument_hover(...) abort + if s:ShouldUseFloatWindow() && s:MoveIntoHoverPreview() + return + endif let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), @@ -463,14 +786,16 @@ function! LanguageClient#textDocument_hover(...) abort return LanguageClient#Call('textDocument/hover', l:params, l:Callback) endfunction +function! LanguageClient#closeFloatingHover() abort + call s:CloseFloatingHover() +endfunction + " Meta methods to go to various places. function! LanguageClient#findLocations(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), - \ 'text': LSP#text(), - \ 'line': LSP#line(), - \ 'character': LSP#character(), + \ 'position': LSP#position(), \ 'gotoCmd': v:null, \ 'handle': s:IsFalse(l:Callback), \ } @@ -554,7 +879,7 @@ function! LanguageClient#workspace_symbol(...) abort return LanguageClient#Call('workspace/symbol', l:params, l:Callback) endfunction -function! LanguageClient#textDocument_codeAction(...) abort +function! LanguageClient#textDocument_codeLens(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'filename': LSP#filename(), @@ -564,9 +889,30 @@ function! LanguageClient#textDocument_codeAction(...) abort \ 'handle': s:IsFalse(l:Callback), \ } call extend(l:params, get(a:000, 0, {})) + return LanguageClient#Call('textDocument/codeLens', l:params, l:Callback) +endfunction + +function! s:do_codeAction(mode, ...) abort + let l:Callback = get(a:000, 2, v:null) + let l:params = { + \ 'filename': LSP#filename(), + \ 'line': LSP#line(), + \ 'character': LSP#character(), + \ 'handle': s:IsFalse(l:Callback), + \ 'range': LSP#range(a:mode), + \ } + call extend(l:params, get(a:000, 1, {})) return LanguageClient#Call('textDocument/codeAction', l:params, l:Callback) endfunction +function! LanguageClient#textDocument_visualCodeAction(...) range abort + call s:do_codeAction('v', a:000) +endfunction + +function! LanguageClient#textDocument_codeAction(...) abort + call s:do_codeAction('n', a:000) +endfunction + function! LanguageClient#textDocument_completion(...) abort " Note: do not add 'text' as it might be huge. let l:params = { @@ -593,6 +939,13 @@ function! LanguageClient#textDocument_formatting(...) abort return LanguageClient#Call('textDocument/formatting', l:params, l:Callback) endfunction +function! LanguageClient#textDocument_formatting_sync(...) abort + let l:result = LanguageClient_runSync('LanguageClient#textDocument_formatting', { + \ 'handle': v:true, + \ }) + return l:result isnot v:null +endfunction + function! LanguageClient#textDocument_rangeFormatting(...) abort let l:Callback = get(a:000, 1, v:null) let l:params = { @@ -600,8 +953,8 @@ function! LanguageClient#textDocument_rangeFormatting(...) abort \ 'text': LSP#text(), \ 'line': LSP#line(), \ 'character': LSP#character(), - \ 'LSP#range_start_line()': LSP#range_start_line(), - \ 'LSP#range_end_line()': LSP#range_end_line(), + \ 'range_start_line': LSP#range_start_line(), + \ 'range_end_line': LSP#range_end_line(), \ 'handle': s:IsFalse(l:Callback), \ } call extend(l:params, get(a:000, 0, {})) @@ -625,18 +978,6 @@ function! LanguageClient#textDocument_rangeFormatting_sync(...) abort return l:result isnot v:null endfunction -function! LanguageClient#rustDocument_implementations(...) abort - let l:params = { - \ 'filename': LSP#filename(), - \ 'text': LSP#text(), - \ 'line': LSP#line(), - \ 'character': LSP#character(), - \ } - call extend(l:params, a:0 >= 1 ? a:1 : {}) - let l:Callback = a:0 >= 2 ? a:2 : v:null - return LanguageClient#Call('rustDocument/implementations', l:params, l:Callback) -endfunction - function! LanguageClient#textDocument_didOpen() abort return LanguageClient#Notify('textDocument/didOpen', { \ 'filename': LSP#filename(), @@ -728,21 +1069,17 @@ function! s:ExecuteAutocmd(event) abort endfunction function! LanguageClient_runSync(fn, ...) abort - let s:LanguageClient_runSync_outputs = [] - let l:arguments = add(a:000[:], s:LanguageClient_runSync_outputs) + let l:LanguageClient_runSync_outputs = [] + let l:arguments = add(a:000[:], l:LanguageClient_runSync_outputs) call call(a:fn, l:arguments) - while len(s:LanguageClient_runSync_outputs) == 0 + while len(l:LanguageClient_runSync_outputs) == 0 sleep 100m endwhile - let l:output = remove(s:LanguageClient_runSync_outputs, 0) + let l:output = remove(l:LanguageClient_runSync_outputs, 0) return s:HandleOutput(l:output, v:true) endfunction function! LanguageClient#handleBufNewFile() abort - if &buftype !=# '' || &filetype ==# '' - return - endif - try call LanguageClient#Notify('languageClient/handleBufNewFile', { \ 'filename': LSP#filename(), @@ -752,13 +1089,17 @@ function! LanguageClient#handleBufNewFile() abort endtry endfunction -function! LanguageClient#handleBufReadPost() abort - if &buftype !=# '' || &filetype ==# '' - return +function! LanguageClient#handleBufEnter() abort + if !exists('b:LanguageClient_isServerRunning') + let b:LanguageClient_isServerRunning = 0 + endif + + if !exists('b:LanguageClient_statusLineDiagnosticsCounts') + let b:LanguageClient_statusLineDiagnosticsCounts = {} endif try - call LanguageClient#Notify('languageClient/handleBufReadPost', { + call LanguageClient#Notify('languageClient/handleBufEnter', { \ 'filename': LSP#filename(), \ }) catch @@ -766,8 +1107,22 @@ function! LanguageClient#handleBufReadPost() abort endtry endfunction +function! LanguageClient#handleFileType() abort + try + if s:Debounce(2, 'LanguageClient#handleFileType') + call LanguageClient#Notify('languageClient/handleFileType', { + \ 'filename': LSP#filename(), + \ 'position': LSP#position(), + \ 'viewport': LSP#viewport(), + \ }) + endif + catch + call s:Debug('LanguageClient caught exception: ' . string(v:exception)) + endtry +endfunction + function! LanguageClient#handleTextChanged() abort - if &buftype !=# '' || &filetype ==# '' + if &buftype !=# '' || &filetype ==# '' || expand('%') ==# '' return endif @@ -782,10 +1137,6 @@ function! LanguageClient#handleTextChanged() abort endfunction function! LanguageClient#handleBufWritePost() abort - if &buftype !=# '' || &filetype ==# '' - return - endif - try call LanguageClient#Notify('languageClient/handleBufWritePost', { \ 'filename': LSP#filename(), @@ -796,10 +1147,6 @@ function! LanguageClient#handleBufWritePost() abort endfunction function! LanguageClient#handleBufDelete() abort - if &buftype !=# '' || &filetype ==# '' - return - endif - try call LanguageClient#Notify('languageClient/handleBufDelete', { \ 'filename': LSP#filename(), @@ -809,6 +1156,8 @@ function! LanguageClient#handleBufDelete() abort endtry endfunction +" TODO: Separate CursorMoved and ViewportChanged events. But after separating, +" there will Mutex poison error. let s:last_cursor_line = -1 function! LanguageClient#handleCursorMoved() abort let l:cursor_line = getcurpos()[1] - 1 @@ -817,17 +1166,12 @@ function! LanguageClient#handleCursorMoved() abort endif let s:last_cursor_line = l:cursor_line - if &buftype !=# '' || &filetype ==# '' - return - endif - try call LanguageClient#Notify('languageClient/handleCursorMoved', { \ 'buftype': &buftype, \ 'filename': LSP#filename(), - \ 'line': l:cursor_line, - \ 'LSP#visible_line_start()': LSP#visible_line_start(), - \ 'LSP#visible_line_end()': LSP#visible_line_end(), + \ 'position': LSP#position(), + \ 'viewport': LSP#viewport(), \ }) catch call s:Debug('LanguageClient caught exception: ' . string(v:exception)) @@ -886,10 +1230,6 @@ function! LanguageClient_NCM2OnComplete(context) abort endfunction function! LanguageClient#explainErrorAtPoint(...) abort - if &buftype !=# '' || &filetype ==# '' - return - endif - let l:Callback = get(a:000, 1, v:null) let l:params = { \ 'buftype': &buftype, @@ -960,10 +1300,6 @@ function! LanguageClient#complete(findstart, base) abort endfunction function! LanguageClient#textDocument_signatureHelp(...) abort - if &buftype !=# '' || &filetype ==# '' - return - endif - let l:params = { \ 'filename': LSP#filename(), \ 'line': LSP#line(), @@ -976,10 +1312,6 @@ function! LanguageClient#textDocument_signatureHelp(...) abort endfunction function! LanguageClient#workspace_applyEdit(...) abort - if &buftype !=# '' || &filetype ==# '' - return - endif - let l:params = { \ 'edit': {}, \ } @@ -989,10 +1321,6 @@ function! LanguageClient#workspace_applyEdit(...) abort endfunction function! LanguageClient#workspace_executeCommand(command, ...) abort - if &buftype !=# '' || &filetype ==# '' - return - endif - let l:params = { \ 'command': a:command, \ 'arguments': get(a:000, 0, v:null), @@ -1019,6 +1347,10 @@ function! LanguageClient#serverStatusMessage() abort return g:LanguageClient_serverStatusMessage endfunction +function! LanguageClient#isServerRunning() abort + return b:LanguageClient_isServerRunning +endfunction + " Example function usable for status line. function! LanguageClient#statusLine() abort if g:LanguageClient_serverStatusMessage ==# '' @@ -1028,6 +1360,10 @@ function! LanguageClient#statusLine() abort return '[' . g:LanguageClient_serverStatusMessage . ']' endfunction +function! LanguageClient#statusLineDiagnosticsCounts() abort + return b:LanguageClient_statusLineDiagnosticsCounts +endfunction + function! LanguageClient#cquery_base(...) abort let l:params = { \ 'method': '$cquery/base', @@ -1052,14 +1388,21 @@ function! LanguageClient#cquery_vars(...) abort return call('LanguageClient#findLocations', [l:params] + a:000[1:]) endfunction -function! LanguageClient#java_classFileContent(...) abort - if &buftype !=# '' || &filetype ==# '' - return - endif - +function! LanguageClient#java_classFileContents(...) abort let l:params = get(a:000, 0, {}) let l:Callback = get(a:000, 1, v:null) - return LanguageClient#Call('java/classFileContent', l:params, l:Callback) + return LanguageClient#Call('java/classFileContents', l:params, l:Callback) +endfunction + +function! LanguageClient#handleCodeLensAction(...) abort + let l:Callback = get(a:000, 1, v:null) + let l:params = { + \ 'filename': LSP#filename(), + \ 'line': LSP#line(), + \ 'character': LSP#character(), + \ } + call extend(l:params, get(a:000, 0, {})) + return LanguageClient#Call('LanguageClient/handleCodeLensAction', l:params, l:Callback) endfunction function! LanguageClient_contextMenuItems() abort @@ -1088,12 +1431,15 @@ endfunction function! LanguageClient_contextMenu() abort let l:options = keys(LanguageClient_contextMenuItems()) - - if get(g:, 'loaded_fzf') && get(g:, 'LanguageClient_fzfContextMenu', 1) - return fzf#run(fzf#wrap({ - \ 'source': l:options, - \ 'sink': function('LanguageClient_handleContextMenuItem'), - \ })) + let l:useSelectionUI = get(g:, 'LanguageClient_selectionUIContextMenu', + \ get(g:, 'LanguageClient_fzfContextMenu', 1)) + if l:useSelectionUI + \ && (type(get(g:, 'LanguageClient_selectionUI', v:null)) is s:TYPE.funcref + \ || (get(g:, 'LanguageClient_selectionUI', v:null) ==? 'FZF' + \ && get(g:, 'loaded_fzf') + \ ) + \ ) + return s:selectionUI_funcref(l:options, function('LanguageClient_handleContextMenuItem')) endif let l:selections = map(copy(l:options), { key, val -> printf('%d) %s', key + 1, val ) }) @@ -1109,6 +1455,70 @@ function! LanguageClient_contextMenu() abort return LanguageClient_handleContextMenuItem(l:options[l:selection - 1]) endfunction +function! LanguageClient_showSemanticScopes(...) abort + let l:params = get(a:000, 0, {}) + let l:Callback = get(a:000, 1, function('s:print_semantic_scopes')) + + return LanguageClient#Call('languageClient/semanticScopes', l:params, l:Callback) +endfunction + +function! s:print_semantic_scopes(response) abort + let l:scope_mappings = a:response.result + + let l:msg = '' + for mapping in l:scope_mappings + let l:msg .= "Highlight Group:\n" + let l:msg .= ' ' . l:mapping.hl_group . "\n" + + let l:msg .= "Semantic Scope:\n" + let l:spaces = ' ' + for l:scope_name in l:mapping.scope + let l:msg .= l:spaces . l:scope_name . "\n" + let l:spaces .= ' ' + endfor + let l:msg .= "\n" + endfor + + echo l:msg +endfunction + +function! LanguageClient#showSemanticHighlightSymbols(...) abort + let l:params = get(a:000, 0, {}) + let l:Callback = get(a:000, 1, v:null) + + return LanguageClient#Call('languageClient/showSemanticHighlightSymbols', l:params, l:Callback) +endfunction + +function! LanguageClient_showCursorSemanticHighlightSymbols(...) abort + let l:params = get(a:000, 0, {}) + let l:Callback = get(a:000, 1, function('s:print_cursor_semantic_symbol')) + + return LanguageClient#showSemanticHighlightSymbols(l:params, l:Callback) +endfunction + +function! s:print_cursor_semantic_symbol(response) abort + let l:symbols = a:response.result + let l:lines = [] + + for symbol in l:symbols + if l:symbol.line + 1 == line('.') && + \ symbol.character_start < col('.') && + \ col('.') <= symbol.character_end + let l:spaces = '' + for scope_name in l:symbol.scope + call add(l:lines, l:spaces . l:scope_name) + let l:spaces .= ' ' + endfor + endif + endfor + + if len(l:lines) > 0 + call s:OpenHoverPreview('SemanticScopes', l:lines, 'text') + else + call s:Echowarn('No Symbol Under Cursor or No Semantic Highlighting') + endif +endfunction + function! LanguageClient#debugInfo(...) abort let l:params = get(a:000, 0, {}) let l:Callback = get(a:000, 1, v:null) diff --git a/vim/bundle/LanguageClient-neovim/autoload/health/LanguageClient.vim b/vim/bundle/LanguageClient-neovim/autoload/health/LanguageClient.vim index 598c1f5..4cc5e7e 100644 --- a/vim/bundle/LanguageClient-neovim/autoload/health/LanguageClient.vim +++ b/vim/bundle/LanguageClient-neovim/autoload/health/LanguageClient.vim @@ -15,11 +15,20 @@ function! s:checkBinary() abort \ l:path) endif - let output = system([l:path, '--version']) + let output = substitute(system([l:path, '--version']), '\n$', '', '') call health#report_ok(output) endfunction +function! s:checkFloatingWindow() abort + if !exists('*nvim_open_win') + call health#report_info('Floating window is not supported. Preview window will be used for hover') + return + endif + call health#report_ok('Floating window is supported and will be used for hover') +endfunction + function! health#LanguageClient#check() abort call s:checkJobFeature() call s:checkBinary() + call s:checkFloatingWindow() endfunction diff --git a/vim/bundle/LanguageClient-neovim/ci/Dockerfile b/vim/bundle/LanguageClient-neovim/ci/Dockerfile index 9a5e174..e10f631 100644 --- a/vim/bundle/LanguageClient-neovim/ci/Dockerfile +++ b/vim/bundle/LanguageClient-neovim/ci/Dockerfile @@ -1,17 +1,20 @@ -FROM rust:1.27-slim +FROM rust:1.41-slim RUN apt-get update \ - && apt-get install --yes curl git neovim python3-pip python3-pytest mypy flake8 \ - && pip3 install neovim vim-vint + && apt-get install --yes --no-install-recommends neovim curl git python3-pip python3-pytest mypy flake8 npm make \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - \ - && apt-get install --yes nodejs \ - && npm install -g javascript-typescript-langserver@2.5.5 +RUN python3 -m pip install neovim vim-vint -RUN rustup component add rls-preview rust-analysis rust-src \ +RUN npm install -g javascript-typescript-langserver@2.5.5 + +RUN rustup component add rustfmt rls rust-analysis rust-src \ && rustc --version \ && rls --version +RUN curl -L https://github.com/rust-analyzer/rust-analyzer/releases/latest/download/rust-analyzer-linux -o /usr/local/bin/rust-analyzer \ + && chmod +x /usr/local/bin/rust-analyzer ENV CARGO_TARGET_DIR=/tmp -CMD /bin/bash +CMD ["/bin/bash"] diff --git a/vim/bundle/LanguageClient-neovim/ci/before_deploy.sh b/vim/bundle/LanguageClient-neovim/ci/before_deploy.sh index 1fbd4c4..6f5fa87 100644 --- a/vim/bundle/LanguageClient-neovim/ci/before_deploy.sh +++ b/vim/bundle/LanguageClient-neovim/ci/before_deploy.sh @@ -1,4 +1,8 @@ -set -ex +#!/bin/bash + +set -o nounset +set -o errexit +set -o xtrace package() { BIN_NAME_TAG=$CRATE_NAME-$TRAVIS_TAG-$TARGET @@ -7,6 +11,7 @@ package() { fi cp -f target/$TARGET/release/$BIN_NAME bin/$BIN_NAME_TAG + sha256sum "bin/$BIN_NAME_TAG" | tee "bin/$BIN_NAME_TAG.sha256" } release_tag() { @@ -25,6 +30,10 @@ release_tag() { git reset --hard HEAD^ } +if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH" +fi + TARGETS=(${TARGETS//:/ }) for TARGET in "${TARGETS[@]}"; do BIN_NAME=$CRATE_NAME diff --git a/vim/bundle/LanguageClient-neovim/ci/cleanup-binary-tags.py b/vim/bundle/LanguageClient-neovim/ci/cleanup-binary-tags.py index 72e8d22..0b940e9 100755 --- a/vim/bundle/LanguageClient-neovim/ci/cleanup-binary-tags.py +++ b/vim/bundle/LanguageClient-neovim/ci/cleanup-binary-tags.py @@ -2,17 +2,14 @@ import os import subprocess -import re import semver def tag_to_version(tag): - version = re.sub(r'binary-', '', tag) - version = re.sub(r'-[x86|i686].*', '', version) - return version + return tag.split('-')[1].lstrip('v') -subprocess.check_call('git pull --tags', shell=True) +subprocess.check_call('git fetch --tags', shell=True) tags = subprocess.check_output( 'git tag --list | grep binary', shell=True).decode('UTF-8').splitlines() versions = sorted(list(set([tag_to_version(tag) for tag in tags])), diff --git a/vim/bundle/LanguageClient-neovim/ci/script.sh b/vim/bundle/LanguageClient-neovim/ci/script.sh index 523fcff..03fac0e 100755 --- a/vim/bundle/LanguageClient-neovim/ci/script.sh +++ b/vim/bundle/LanguageClient-neovim/ci/script.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash set -o nounset set -o errexit diff --git a/vim/bundle/LanguageClient-neovim/doc/LanguageClient.txt b/vim/bundle/LanguageClient-neovim/doc/LanguageClient.txt index 4561492..9188920 100644 --- a/vim/bundle/LanguageClient-neovim/doc/LanguageClient.txt +++ b/vim/bundle/LanguageClient-neovim/doc/LanguageClient.txt @@ -1,4 +1,4 @@ -*LanguageClient* Language Server Protocol support for neovim +*LanguageClient.txt* Language Server Protocol support for vim and neovim *LanguageClient* ============================================================================== @@ -8,10 +8,11 @@ CONTENTS *LanguageClientContents* 2. Configuration ............... |LanguageClientConfiguration| 3. Commands .................... |LanguageClientCommands| 4. Functions ................... |LanguageClientFunctions| -5. Events ...................... |LanguageClientEvents| -6. License ..................... |LanguageClientLicense| -7. Bugs ........................ |LanguageClientBugs| -8. Contributing ................ |LanguageClientContributing| +5. Mappings .................... |LanguageClientMappings| +6. Events ...................... |LanguageClientEvents| +7. License ..................... |LanguageClientLicense| +8. Bugs ........................ |LanguageClientBugs| +9. Contributing ................ |LanguageClientContributing| ============================================================================== 1. Usage *LanguageClientUsage* @@ -57,13 +58,12 @@ Alternatively, set 'completefunc': > < If the language server supports, diagnostic/lint information will be displayed via gutter and syntax highlighting with real time editing. At the same time, -those info are populated into quickfix list (or location list), which can be +that info is populated into the quickfix list (or location list), which can be accessed by regular quickfix/location list operations. To use the language server with Vim's formatting operator |gq|, set 'formatexpr': > set formatexpr=LanguageClient#textDocument_rangeFormatting_sync() < - ============================================================================== 2. Configuration *LanguageClientConfiguration* @@ -80,7 +80,7 @@ In the example above, 'run', 'stable', and 'rls' are arguments to the 'rustup' command line tool. Or tcp connection string to the server, > - let g:LanguageCLient_servercommands = { + let g:LanguageClient_serverCommands = { \ 'javascript': ['tcp://127.0.0.1:2089'], \ } @@ -100,24 +100,28 @@ Default: > "texthl": "ALEError", "signText": "✖", "signTexthl": "ALEErrorSign", + "virtualTexthl": "Error", }, 2: { "name": "Warning", "texthl": "ALEWarning", "signText": "⚠", "signTexthl": "ALEWarningSign", + "virtualTexthl": "Todo", }, 3: { "name": "Information", "texthl": "ALEInfo", "signText": "ℹ", "signTexthl": "ALEInfoSign", + "virtualTexthl": "Todo", }, 4: { "name": "Hint", "texthl": "ALEInfo", "signText": "➤", "signTexthl": "ALEInfoSign", + "virtualTexthl": "Todo", }, } @@ -160,7 +164,30 @@ Default: 1. Selection UI used when there are multiple entries. Default: If fzf is loaded, use "fzf", otherwise use "location-list". -Valid options: "fzf" | "quickfix" | "location-list" +Valid options: "fzf" | "quickfix" | "location-list" | |Funcref| + +If you use a |Funcref|, the referenced function should have two arguments +(source, sink). The "source" argument can be either a command string or a +list. The sink argument can be a command string or a |Funcref| that takes the +selected line as its first argument. + +An example implementation is "s:FZF()" in autoload/LanguageClient.vim. For +details, see the source and sink arguments of fzf#run(): +https://github.com/junegunn/fzf/blob/master/README-VIM.md#fzfrun + +Another example, this time using vim-clap: > + + function! MySelectionUI(source, sink) abort + return clap#run({'id': 'LCN', 'source': a:source, 'sink': a:sink}) + endfunction + + let g:LanguageClient_selectionUI = function('MySelectionUI') + +2.7 g:LanguageClient_selectionUI_autoOpen *LanguageClient_selectionUI_autoOpen* +Selection UI auto open when selectionUI is not "fzf" + +Default: 1 +Valid options: 1 | 0 2.8 g:LanguageClient_trace *g:LanguageClient_trace* @@ -193,7 +220,10 @@ Valid options: "Error" | "Warning" | "Info" | "Log" 2.12 g:LanguageClient_settingsPath *g:LanguageClient_settingsPath* -Path for language server settings. +Path for language server settings, or list of such paths. If not an absolute +path this is relative to the workspace directory. If several paths are +provided, then the corresponding settings are merged with precedence going to +the last file. Example settings file content: > { @@ -214,23 +244,29 @@ Valid options: 1 | 0 Log file location. Default: null -Valid options: any valid path. +Valid options: any valid path. Please note that `~` is not a valid path and you +need to `expand` it. -2.14 g:LanguageClient_loggingLevel *g:LanguageClient_loggingLevel* +Example: + + `let g:LanguageClient_loggingFile = expand('~/.vim/LanguageClient.log')`` + + +2.15 g:LanguageClient_loggingLevel *g:LanguageClient_loggingLevel* Logging level. Default: 'WARN' Valid options: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' -2.14 g:LanguageClient_serverStderr *g:LanguageClient_serverStderr* +2.16 g:LanguageClient_serverStderr *g:LanguageClient_serverStderr* Path for language server stderr. Default: None Valid options: any valid path. -2.15 g:LanguageClient_rootMarkers *g:LanguageClient_rootMarkers* +2.17 g:LanguageClient_rootMarkers *g:LanguageClient_rootMarkers* Customized project root markers. Generally a heuristic algorithm within this plugin should be able to detect project root automatically. This option is @@ -248,21 +284,26 @@ Example setting 2. Map filetype to string array. > Default: v:null Valid option: Array | Map> -2.16 g:LanguageClient_fzfOptions *g:LanguageClient_fzfOptions* +2.18 g:LanguageClient_fzfOptions *g:LanguageClient_fzfOptions* Customize fzf. Check fzf documentation for available options. Default: v:null Valid option: Array | String -2.17 g:LanguageClient_hasSnippetSupport *g:LanguageClient_hasSnippetSupport* +2.19 g:LanguageClient_hasSnippetSupport *g:LanguageClient_hasSnippetSupport* Override detection of snippet support. -Default: 1 +If this is not set, the language client will determine whether snippets are +supported by looking for installed plugins that are known to support them. + +Set to 1 (or 0) to enable (or disable) snippet support, skipping the automatic +detection. + Valid options: 1 | 0 -2.18 g:LanguageClient_waitOutputTimeout *g:LanguageClient_waitOutputTimeout* +2.20 g:LanguageClient_waitOutputTimeout *g:LanguageClient_waitOutputTimeout* Duration of time (in seconds) to wait for language server to return output before timing out. @@ -270,7 +311,7 @@ before timing out. Default: 10 Valid options: number -2.19 g:LanguageClient_hoverPreview *g:LanguageClient_hoverPreview* +2.21 g:LanguageClient_hoverPreview *g:LanguageClient_hoverPreview* Controls how hover output is displayed. Must be one of the following: Never - Never use preview window, always echo hover output @@ -280,14 +321,16 @@ Controls how hover output is displayed. Must be one of the following: Default: "Auto" Valid options: "Never", "Auto", "Always" -2.20 g:LanguageClient_fzfContextMenu *g:LanguageClient_fzfContextMenu* +2.22 g:LanguageClient_selectionUIContextMenu *g:LanguageClient_selectionUIContextMenu* *g:LanguageClient_fzfContextMenu* -Should FZF be used for `LanguageClient_contextMenu()`. +Should the selection UI be used for `LanguageClient_contextMenu()`. + +(Alias for the deprecated `LanguageClient_fzfContextMenu`) Default: 1 Valid options: 1 | 0 -2.21 g:LanguageClient_completionPreferTextEdit +2.23 g:LanguageClient_completionPreferTextEdit *g:LanguageClient_completionPreferTextEdit* (Experimental) Should prefer use textEdit it is present in @@ -299,7 +342,7 @@ neovim >= 0.3.0. Default: 0 Valid options: 1 | 0 -2.22 g:LanguageClient_documentHighlightDisplay *g:LanguageClient_documentHighlightDisplay* +2.24 g:LanguageClient_documentHighlightDisplay *g:LanguageClient_documentHighlightDisplay* Control how document highlights are displayed. @@ -319,6 +362,220 @@ Default: > }, } +2.25 g:LanguageClient_useVirtualText *g:LanguageClient_useVirtualText* + +Specify whether to use virtual text to display diagnostics. + +Default: "All" whenever virtual text is supported. +Valid Options: "All" | "No" | "CodeLens" | "Diagnostics" + +2.26 g:LanguageClient_useFloatingHover *g:LanguageClient_useFloatingHover* + +When the value is set to 1, |LanguageClient#textDocument_hover()| opens +documentation in a floating window instead of preview. +This variable is effective only when the floating window feature is +supported. Check by running > + :echomsg exists('nvim_open_win') + +Default: 1 when a floating window is supported, otherwise 0 +Valid Options: 1 | 0 + +2.27 g:LanguageClient_floatingHoverHighlight *g:LanguageClient_floatingHoverHighlight* + +Control how floating window hover highlights are displayed. + +Default: Normal:CursorLine + +2.28 g:LanguageClient_usePopupHover *g:LanguageClient_usePopupHover* + +When the value is set to 1, |LanguageClient#textDocument_hover()| opens +documentation in a popup window instead of preview. +This variable is effective only when the popup window feature is +supported, which is supported only in vim 8.2+. Check by running > + :echomsg has('popupwin') + +Default: 1 when a popup window is supported, otherwise 0 +Valid Options: 1 | 0 + +2.29 g:LanguageClient_virtualTextPrefix *g:LanguageClient_virtualTextPrefix* + +When the value is set to a valid string and |g:LanguageClient_useVirtualText| is +enabled, all virtual text lines are prefixed with the defined string. +This variable is effective only when the virtual text feature is +supported. + +Default: Empty string ('') +Valid Options: string + +2.30 g:LanguageClient_diagnosticsMaxSeverity *g:LanguageClient_diagnosticsMaxSeverity* + +Maximum severity to show diagnostic messages. + +Default: "Hint" +Valid options: "Error" | "Warning" | "Information" | "Hint" + +2.31 g:LanguageClient_echoProjectRoot *g:LanguageClient_echoProjectRoot* + +Whether to echo messages in vim of the form: "Project root: /home/user/myproject" when +the root of a project is detected using g:LanguageClient_rootMarkers. + +Default: 1 to display the messages +Valid options: 1 | 0 + +2.32 g:LanguageClient_semanticHighlightMaps *g:LanguageClient_semanticHighlightMaps* + +String to list/map map. Defines the mapping of semantic highlighting "scopes" to +highlight groups. This depends on the LSP server supporting the proposed +semantic highlighting protocol, see: + +https://github.com/microsoft/language-server-protocol/issues/18 +https://github.com/microsoft/vscode-languageserver-node/issues/368 + + +Like |g:LanguageClient_serverCommands| this is a map where the keys are +filetypes. However each submap has |regexp| keys and highlight group names +as values (see |highlight-groups|). +> + let g:LanguageClient_semanticHighlightMaps = { + \ 'java': { + \ '^entity.name.function.java': 'Function', + \ '^entity.name.type.class.java': 'Type', + \ '^[^:]*entity.name.function.java': 'Function', + \ '^[^:]entity.name.type.class.java': 'Type' + \ } + \ } + +The |regexp| in the keys will be used to match semantic scopes. Then any symbols +that have a semantic scope that matches the key will be highlighted with the +associated highlight group value. Currently there is no defined order if a +semantic scope can match multiple keys, so it is recommended to make the keys +more specific to only match the desired scope(s). + +There are a fixed set of semantic scopes defined by the LSP server on startup. +These can be viewed by calling |LanguageClient_showSemanticScopes| which will +show all the semantic scopes and their currently mapped highlight group for +the currently open buffer's filetype. +> + call LanguageClient_showSemanticScopes() + + == Output from eclipse.jdt.ls == + + Highlight Group: + None + Semantic Scope: + invalid.deprecated.java + meta.class.java + source.java + + Highlight Group: + None + Semantic Scope: + variable.other.autoboxing.java + meta.method.body.java + meta.method.java + meta.class.body.java + meta.class.java + source.java + + ... + +Each semantic scope is a list of strings. They are printed with increasing +indent to make it easier to read. For example the first scope is: +> + ['invalid.deprecated.java', 'meta.class.java', 'source.java'] + +It is currently isn't mapped to any highlight group as indicated by the None. + +Often its more useful to find what semantic scope corresponds to a piece of +text. This can be done by calling |LanguageClient_showCursorSemanticHighlightSymbols| +while hovering over the text of interest. +> + call LanguageClient_showCursorSemanticHighlightSymbols() + +When matching the semantic scopes to keys in |LanguageClient_semanticHighlightMaps|, +the scopes are concatentated using |LanguageClient_semanticScopeSeparator| +which is set to the string |':'| by default. For the previous example the +semantic scope would have this string form using the default separator: +> + invalid.deprecated.java:meta.class.java:source.java + +Here are a couple of example |regexp| keys that can/cannot match this scope: +> + 'meta.class.java' =~ 'invalid.deprecated.java:meta.class.java:source.java' + '^meta.class.java' !~ 'invalid.deprecated.java:meta.class.java:source.java' + '^invalid.deprecated.java' =~ 'invalid.deprecated.java:meta.class.java:source.java' + 'source.java$' =~ 'invalid.deprecated.java:meta.class.java:source.java' + 'meta.class.java:source.java' =~ 'invalid.deprecated.java:meta.class.java:source.java' + 'invalid.deprecated.java:.*:source.java' =~ 'invalid.deprecated.java:meta.class.java:source.java' + +Example configuration for eclipse.jdt.ls: +> + let g:LanguageClient_semanticHighlightMaps = {} + let g:LanguageClient_semanticHighlightMaps['java'] = { + \ '^storage.modifier.static.java:entity.name.function.java': 'JavaStaticMemberFunction', + \ '^meta.definition.variable.java:meta.class.body.java:meta.class.java': 'JavaMemberVariable', + \ '^entity.name.function.java': 'Function', + \ '^[^:]*entity.name.function.java': 'Function', + \ '^[^:]*entity.name.type.class.java': 'Type', + \ } + + highlight! JavaStaticMemberFunction ctermfg=Green cterm=none guifg=Green gui=none + highlight! JavaMemberVariable ctermfg=White cterm=italic guifg=White gui=italic + + +2.33 g:LanguageClient_applyCompletionAdditionalTextEdits *g:LanguageClient_applyCompletionAdditionalTextEdits* + +Indicates whether completionItem additional text edits should be applied. + +Default: 1 +Valid options: 1 | 0 + +2.34 g:LanguageClient_preferredMarkupKind *g:LanguageClient_preferredMarkupKind* + +Sets the preferred markup kind. This value is sent to the server and is +normally used to decide whether to send plaintext or markdown in things like +hover or completion items docunmentation. + +This should be set to an array of values with the preferred markup kinds in +order of preferrence. Leaving this config unset, will send `null` to the +server, effectively letting it decide which markup kind to use. + +Example setting 1. Set the preferred markup kind to `plaintext` + ``` + let g:LanguageClient_preferredMarkupKind: ['plaintext'] + ``` + +Example setting 2. Set the preferred markup kind to `plaintext` and `markdown` +as a fallback. + + ``` + let g:LanguageClient_preferredMarkupKind: ['plaintext', 'markdown'] + ``` + +Example setting 3. Set the preferred markup kind to `markdown` + + ``` + let g:LanguageClient_preferredMarkupKind: ['markdown'] + +This setting may have no effect of the server decides not to honour it. + +Default: v:null +Valid options: Array + +2.35 g:LanguageClient_floatingWindowStyle *g:LanguageClient_floatingWindowStyle* + +Style of opened Neovim floating window. + +Default: 'minimal' +Valid options: 'minimal' + +2.36 g:LanguageClient_hideVirtualTextsOnInsert *g:LanguageClient_hideVirtualTextsOnInsert* + +Hides all virtual texts for the current buffer while editing in insert mode. + +Default: 0 +Valid options: 1 | 0 + ============================================================================== 3. Commands *LanguageClientCommands* @@ -368,6 +625,12 @@ Signature: LanguageClient#textDocument_hover(...) Show type info (and short doc) of identifier under cursor. +If you're using Neovim 0.4.0 or later, this function opens documentation in a +floating window. The window is automatically closed when you move the cursor. +Or calling this function again just after opening the floating window moves +the cursor into the window. It is useful when documentation is longer and you +need to scroll down or you want to yank some text in the documentation. + *LanguageClient#textDocument_definition()* *LanguageClient_textDocument_definition()* Signature: LanguageClient#textDocument_definition(...) @@ -416,6 +679,19 @@ Example bindings combining with tpope/vim-abolish: \ {'newName': Abolish.uppercase(expand(''))}) < +*LanguageClient#textDocument_codeLens()* +*LanguageClient_textDocument_codeLens()* +Signature: LanguageClient#textDocument_codeLens(...) + +Computes and displays the codeLens for the currently open file. + +*LanguageClient#handleCodeLensAction()* +*LanguageClient_handleCodeLensAction()* +Signature: LanguageClient#handleCodeLensAction(...) + +Runs the action associated with the codeLens at the current line. It does +nothing if the codeLens is not actionable. + *LanguageClient#textDocument_documentSymbol()* *LanguageClient_textDocument_documentSymbol()* Signature: LanguageClient#textDocument_documentSymbol(...) @@ -438,6 +714,12 @@ prompt, selecting one of the entry will then goto the reference location. For Denite users, a source with name 'references' is provided. +*LanguageClient#textDocument_visualCodeAction()* +*LanguageClient_textDocument_visualCodeAction()* +Signature: LanguageClient#textDocument_visualCodeAction(...) + +Show code actions at current visual selection. + *LanguageClient#textDocument_codeAction()* *LanguageClient_textDocument_codeAction()* Signature: LanguageClient#textDocument_codeAction(...) @@ -448,7 +730,7 @@ Show code actions at current location. *LanguageClient_textDocument_completion()* Signature: LanguageClient#textDocument_completion(...) -Get a list of completion items at current editing location. Note, this is an +Get a list of completion items at current editing location. Note, this is a synchronous call. When using a supported completion manager (deoplete and @@ -549,12 +831,35 @@ Signature: LanguageClient#serverStatusMessage() Get a detail message of server status. +*LanguageClient#isServerRunning()* +*LanguageClient_isServerRunning()* +Signature: LanguageClient#isServerRunning() + +Get wheter if language server is running for current buffer. 0 for server not working for current buffer file +type. 1 for working. + *LanguageClient#statusLine()* *LanguageClient_statusLine()* Signature: LanguageClient#statusLine() Example status line making use of |LanguageClient_serverStatusMessage|. +*LanguageClient#statusLineDiagnosticsCounts()* +*LanguageClient_statusLineDiagnosticsCounts()* +Signature: LanguageClient#statusLineDiagnosticsCounts() + +Get diagnostics of current buffer in dictionary. + +Example > + function! YourStatusLineMessage() abort + let l:diagnosticsDict = LanguageClient#statusLineDiagnosticsCounts() + let l:errors = get(l:diagnosticsDict,'E',0) + let l:warnings = get(l:diagnosticsDict,'W',0) + let l:informations = get(l:diagnosticsDict,'I',0) + let l:hints = get(l:diagnosticsDict,'H',0) + return l:errors + l:warnings + l:informations + l:hints == 0 ? "✔ " : "E:" . l:errors . " " . "W :" . l:warnings . "I:" . l:informations . " " . "H:" . l:hints + endfunction + *LanguageClient#cquery_base* *LanguageClient_cquery_base* Signature: LanguageClient#cquery_base(...) @@ -573,10 +878,22 @@ Signature: LanguageClient#cquery_vars(...) Call $cquery/vars. -*LanguageClient#java_classFileContent* -Signature: LanguageClient#java_classFileContent(...) +*LanguageClient#java_classFileContents* +Signature: LanguageClient#java_classFileContents(...) + +Call java/classFileContents. -Call java/classFileContent. +*LanguageClient_showSemanticScopes* +Signature: LanguageClient_showSemanticScopes(...) + +Get all Semantic Scopes and their associated highlight groups for the current +filetype (filetype of the currently open buffer) and print them. + +*LanguageClient_showCursorSemanticHighlightSymbols* +Signature: LanguageClient_showCursorSemanticHighlightSymbols(...) + +Get the Semantic Scope of the symbol currently under the cursor. +The result gets displayed in a popup. *LanguageClient#explainErrorAtPoint* Signature: LanguageClient#explainErrorAtPoint(...) @@ -589,16 +906,71 @@ Signature: LanguageClient#debugInfo(...) Print out debug info. ============================================================================== -5. Events *LanguageClientEvents* +5. Mappings *LanguageClientMappings* + +LanguageClient provides various Plug mappings which can be used to set up +custom mappings. For example, to create a mapping to call the rename function +you would create a custom mapping like this: + + nmap (lcn-rename) + +The full list of Plug mappings is: + +*(lcn-menu)* +Calls LanguageClient_contextMenu. + +*(lcn-hover)* +Calls LanguageClient_textDocument_hover. + +*(lcn-rename)* +Calls LanguageClient_textDocument_rename. + +*(lcn-definition)* +Calls LanguageClient_textDocument_definition. + +*(lcn-type-definition)* +Calls LanguageClient_textDocument_typeDefinition. + +*(lcn-references)* +Calls LanguageClient_textDocument_references. + +*(lcn-implementation)* +Calls LanguageClient_textDocument_implementation. + +*(lcn-code-action)* +Calls LanguageClient_textDocument_codeAction if called in normal model or +LanguageClient_textDocument_visualCodeAction if called in visual mode. + +*(lcn-code-lens-action)* +Calls LanguageClient_handleCodeLensAction. + +*(lcn-symbols)* +Calls LanguageClient_textDocument_documentSymbol. + +*(lcn-highlight)* +Calls LanguageClient_textDocument_documentHighlight. + +*(lcn-explain-error)* +Calls LanguageClient_textDocument_explainErrorAtPoint. + +*(lcn-format)* +Calls LanguageClient_textDocument_formatting. + +*(lcn-format-sync)* +Calls LanguageClient_textDocument_formatting_sync. + + +============================================================================== +6. Events *LanguageClientEvents* LanguageClient provides two events for use with |User| |autocmd|s. -5.1 LanguageClientStarted +6.1 LanguageClientStarted *LanguageClientStarted* This event is triggered after LanguageClient has successfully started. -5.2 LanguageClientStopped +6.2 LanguageClientStopped *LanguageClientStopped* This event is triggered after LanguageClient has stopped. @@ -610,27 +982,27 @@ Example: > autocmd User LanguageClientStopped setlocal signcolumn=auto augroup END -5.3 LanguageClientDiagnosticsChanged +6.3 LanguageClientDiagnosticsChanged *LanguageClientDiagnosticsChanged* This event is triggered when diagnostics changed. -5.4 LanguageClientBufReadPost -*LanguageClientBufReadPost* +6.4 LanguageClientTextDocumentDidOpenPost +*LanguageClientTextDocumentDidOpenPost* -Triggered after BufReadPost is successfully handled by language client. +Triggered after textDocument/didOpen notification is sent to language server. ============================================================================== -6. License *LanguageClientLicense* +7. License *LanguageClientLicense* The MIT License. ============================================================================== -7. Bugs *LanguageClientBugs* +8. Bugs *LanguageClientBugs* Please report all bugs at https://github.com/autozimu/LanguageClient-neovim/issues -If you believe you've find a bug in this plugin, please first try to help +If you believe you've found a bug in this plugin, please first try to help narrow down where the error happens, which will reduce bug fix time/effort significantly. @@ -639,7 +1011,7 @@ Experiment with VS Code plugin if the language server has one. Try increasing logging level to 'INFO' or 'DEBUG' using the |LanguageClient_setLoggingLevel| function, and check the log file. -There is also an utility script in: > +There is also a utility script in: > $RUNTIME/tests/wrapper-server.sh @@ -647,7 +1019,7 @@ which can work like a proxy and logs all stdin and stdout of the language server into a log file. ============================================================================== -8. Contributing *LanguageClientContributing* +9. Contributing *LanguageClientContributing* https://github.com/autozimu/LanguageClient-neovim diff --git a/vim/bundle/LanguageClient-neovim/install.ps1 b/vim/bundle/LanguageClient-neovim/install.ps1 old mode 100644 new mode 100755 index edcfbda..f3a8cd9 --- a/vim/bundle/LanguageClient-neovim/install.ps1 +++ b/vim/bundle/LanguageClient-neovim/install.ps1 @@ -1,19 +1,46 @@ -$version = '0.1.123' +#!/usr/bin/env pwsh + +$version = '0.1.158' $name = 'languageclient' $url = "https://github.com/autozimu/LanguageClient-neovim/releases/download/$version/$name-$version-" -if ($ENV:PROCESSOR_ARCHITECTURE -eq 'AMD64') { - $url += 'x86_64' -} else { - $url += 'i686' -} +$path = "$PSScriptRoot\bin\$name" -$url += '-pc-windows-gnu.exe' +switch ($true) { + $IsMacOS { + # MacOS is always x86_64 + $url += 'x86_64-apple-darwin' + } + $IsLinux { + # Detecting architecture is more involved on Linux + $arch = uname -sm + $url += switch ($arch) { + 'Linux x86_64' { 'x86_64' } + 'Linux i686' { 'i686' } + 'Linux aarch64' { 'aarch64' } + Default { throw 'architecture not supported' } + } + $url += '-unknown-linux-musl' + } + Default { + # Windows + $url += if ([Environment]::Is64BitOperatingSystem) { 'x86_64' } else { 'i686' } + $url += '-pc-windows-gnu.exe' + + # We need to tack on the .exe to the end of the download path + $path += '.exe' + } +} -$path = "bin\$name.exe" -if (Test-Path $path) { - Remove-Item -Force $path +if (Test-Path -LiteralPath $path) { + Remove-Item -Force -LiteralPath $path } + echo "Downloading $url ..." -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +if (!$IsCoreCLR) { + # We only need to do this for Windows PowerShell + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +} + Invoke-WebRequest -Uri $url -OutFile $path diff --git a/vim/bundle/LanguageClient-neovim/install.sh b/vim/bundle/LanguageClient-neovim/install.sh index fce601e..e201373 100755 --- a/vim/bundle/LanguageClient-neovim/install.sh +++ b/vim/bundle/LanguageClient-neovim/install.sh @@ -6,9 +6,11 @@ set -o nounset # error when referencing undefined variable set -o errexit # exit when command fails -version=0.1.123 +version=0.1.158 name=languageclient +arch=$(uname -sm) + try_curl() { command -v curl > /dev/null && \ curl --fail --location "$1" --output bin/$name @@ -20,32 +22,40 @@ try_wget() { } download() { - echo "Downloading bin/${name}..." + echo "Tring download bin/${name} ${version}..." url=https://github.com/autozimu/LanguageClient-neovim/releases/download/$version/${1} if (try_curl "$url" || try_wget "$url"); then chmod a+x bin/$name return else - try_build || echo "Prebuilt binary might not be ready yet. Please check minutes later." + echo "Prebuilt binary is not available for:" "${arch}" + try_build fi } try_build() { if command -v cargo > /dev/null; then - echo "Trying build locally ..." + echo "Trying build locally ${version} ..." make release else + echo "cargo is not available. Abort." return 1 fi } -rm -f bin/languageclient +bin=bin/languageclient +if [ -f "$bin" ]; then + installed_version=$($bin -V) + case "${installed_version}" in + *${version}*) echo "Version is equal to ${version}, skip install." ; exit 0 ;; + *) rm -f "$bin" ;; + esac +fi -arch=$(uname -sm) case "${arch}" in "Linux x86_64") download $name-$version-x86_64-unknown-linux-musl ;; "Linux i686") download $name-$version-i686-unknown-linux-musl ;; - "Linux aarch64") download $name-$version-aarch64-unknown-linux-gnu ;; + "Linux aarch64") download $name-$version-aarch64-unknown-linux-musl ;; "Darwin x86_64") download $name-$version-x86_64-apple-darwin ;; "FreeBSD amd64") download $name-$version-x86_64-unknown-freebsd ;; *) echo "No pre-built binary available for ${arch}."; try_build ;; diff --git a/vim/bundle/LanguageClient-neovim/min-vimrc.vim b/vim/bundle/LanguageClient-neovim/min-vimrc.vim index 62631d2..0113fa4 100644 --- a/vim/bundle/LanguageClient-neovim/min-vimrc.vim +++ b/vim/bundle/LanguageClient-neovim/min-vimrc.vim @@ -20,5 +20,6 @@ let g:LanguageClient_serverCommands = { \ } let $RUST_BACKTRACE = 1 let g:LanguageClient_loggingLevel = 'INFO' -let g:LanguageClient_loggingFile = stdpath('data') . '/LanguageClient.log' -let g:LanguageClient_serverStderr = stdpath('data') . '/LanguageServer.log' +let g:LanguageClient_virtualTextPrefix = '' +let g:LanguageClient_loggingFile = expand('~/.local/share/nvim/LanguageClient.log') +let g:LanguageClient_serverStderr = expand('~/.local/share/nvim/LanguageServer.log') diff --git a/vim/bundle/LanguageClient-neovim/nix/sources.json b/vim/bundle/LanguageClient-neovim/nix/sources.json new file mode 100644 index 0000000..9d39c90 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/nix/sources.json @@ -0,0 +1,26 @@ +{ + "niv": { + "branch": "master", + "description": "Easy dependency management for Nix projects", + "homepage": "https://github.com/nmattia/niv", + "owner": "nmattia", + "repo": "niv", + "rev": "f73bf8d584148677b01859677a63191c31911eae", + "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs", + "type": "tarball", + "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixpkgs-unstable", + "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to", + "homepage": "https://github.com/NixOS/nixpkgs", + "owner": "NixOS", + "repo": "nixpkgs-channels", + "rev": "c925ad564c3138b54f87eb02d70f49b3881e43a1", + "sha256": "04g9zbg1c99jrrdf2pfwpskccff7dd1s3qs6v3gkrk00naxf9pws", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs-channels/archive/c925ad564c3138b54f87eb02d70f49b3881e43a1.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/vim/bundle/LanguageClient-neovim/nix/sources.nix b/vim/bundle/LanguageClient-neovim/nix/sources.nix new file mode 100644 index 0000000..8a725cb --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/nix/sources.nix @@ -0,0 +1,134 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: spec: + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; } + else + pkgs.fetchurl { inherit (spec) url sha256; }; + + fetch_tarball = pkgs: spec: + if spec.builtin or true then + builtins_fetchTarball { inherit (spec) url sha256; } + else + pkgs.fetchzip { inherit (spec) url sha256; }; + + fetch_git = spec: + builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; + + fetch_builtin-tarball = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-tarball" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=tarball -a builtin=true + '' + builtins_fetchTarball { inherit (spec) url sha256; }; + + fetch_builtin-url = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-url" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=file -a builtin=true + '' + (builtins_fetchurl { inherit (spec) url sha256; }); + + # + # Various helpers + # + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs spec + else if spec.type == "tarball" then fetch_tarball pkgs spec + else if spec.type == "git" then fetch_git spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec + else if spec.type == "builtin-url" then fetch_builtin-url spec + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball { inherit url; } + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl { inherit url; } + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = fetch config.pkgs name spec; } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? ./sources.json + , sources ? builtins.fromJSON (builtins.readFile sourcesFile) + , pkgs ? mkPkgs sources + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/vim/bundle/LanguageClient-neovim/plugin/LanguageClient.vim b/vim/bundle/LanguageClient-neovim/plugin/LanguageClient.vim index 7b2fe10..ef31a25 100644 --- a/vim/bundle/LanguageClient-neovim/plugin/LanguageClient.vim +++ b/vim/bundle/LanguageClient-neovim/plugin/LanguageClient.vim @@ -1,3 +1,11 @@ +if !exists('g:LanguageClient_serverCommands') + let g:LanguageClient_serverCommands = {} +endif + +if !exists('g:LanguageClient_semanticHighlightMaps') + let g:LanguageClient_semanticHighlightMaps = {} +endif + function! LanguageClient_textDocument_hover(...) return call('LanguageClient#textDocument_hover', a:000) endfunction @@ -30,6 +38,10 @@ function! LanguageClient_textDocument_codeAction(...) return call('LanguageClient#textDocument_codeAction', a:000) endfunction +function! LanguageClient_textDocument_codeLens(...) + return call('LanguageClient#textDocument_codeLens', a:000) +endfunction + function! LanguageClient_textDocument_completion(...) return call('LanguageClient#textDocument_completion', a:000) endfunction @@ -86,10 +98,18 @@ function! LanguageClient_serverStatusMessage(...) return call('LanguageClient#serverStatusMessage', a:000) endfunction +function! LanguageClient_isServerRunning(...) + return call('LanguageClient#isServerRunning', a:000) +endfunction + function! LanguageClient_statusLine(...) return call('LanguageClient#statusLine', a:000) endfunction +function! LanguageClient_statusLineDiagnosticsCounts(...) + return call('LanguageClient#statusLineDiagnosticsCounts', a:000) +endfunction + function! LanguageClient_clearDocumentHighlight(...) return call('LanguageClient#clearDocumentHighlight', a:000) endfunction @@ -102,13 +122,26 @@ function! LanguageClient_cquery_vars(...) return call('LanguageClient#cquery_vars', a:000) endfunction +function! LanguageClient_closeFloatingHover(...) + return call('LanguageClient#closeFloatingHover', a:000) +endfunction + +function! LanguageClient_handleCodeLensAction(...) + return call('LanguageClient#handleCodeLensAction', a:000) +endfunction + +function! LanguageClient_explainErrorAtPoint(...) + return call('LanguageClient#explainErrorAtPoint', a:000) +endfunction + command! -nargs=* LanguageClientStart :call LanguageClient#startServer() command! LanguageClientStop :call LanguageClient#exit() augroup languageClient autocmd! + autocmd FileType * call LanguageClient#handleFileType() autocmd BufNewFile * call LanguageClient#handleBufNewFile() - autocmd BufReadPost * call LanguageClient#handleBufReadPost() + autocmd BufEnter * call LanguageClient#handleBufEnter() autocmd BufWritePost * call LanguageClient#handleBufWritePost() autocmd BufDelete * call LanguageClient#handleBufDelete() autocmd TextChanged * call LanguageClient#handleTextChanged() @@ -125,4 +158,20 @@ augroup languageClient autocmd CompleteDone * \ call LanguageClient#textDocument_signatureHelp({}, 's:HandleOutputNothing') endif + + nnoremap (lcn-menu) :call LanguageClient_contextMenu() + nnoremap (lcn-hover) :call LanguageClient_textDocument_hover() + nnoremap (lcn-rename) :call LanguageClient_textDocument_rename() + nnoremap (lcn-definition) :call LanguageClient_textDocument_definition() + nnoremap (lcn-type-definition) :call LanguageClient_textDocument_typeDefinition() + nnoremap (lcn-references) :call LanguageClient_textDocument_references() + nnoremap (lcn-implementation) :call LanguageClient_textDocument_implementation() + nnoremap (lcn-code-action) :call LanguageClient_textDocument_codeAction() + vnoremap (lcn-code-action) :call LanguageClient#textDocument_visualCodeAction() + nnoremap (lcn-code-lens-action) :call LanguageClient_handleCodeLensAction() + nnoremap (lcn-symbols) :call LanguageClient_textDocument_documentSymbol() + nnoremap (lcn-highlight) :call LanguageClient_textDocument_documentHighlight() + nnoremap (lcn-explain-error) :call LanguageClient_explainErrorAtPoint() + nnoremap (lcn-format) :call LanguageClient_textDocument_formatting() + nnoremap (lcn-format-sync) :call LanguageClient_textDocument_formatting_sync() augroup END diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/common.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/common.py new file mode 100644 index 0000000..88661ca --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/common.py @@ -0,0 +1,159 @@ +from typing import List, Dict +from os.path import dirname, relpath +from urllib import request, parse +import sys +from collections import namedtuple + +from denite.source.base import Base + +sys.path.insert(0, dirname(__file__)) + +from lsp.protocol import SymbolKind # isort:skip # noqa: I100 E402 + + +MAX_FNAME_LEN = 30 + +_HighlightDefinition = namedtuple("HighlightDefinition", ( + "name", + 're', + "contained", + "contains", + "nextgroup", + 'link', +)) + + +def HighlightDefinition(name, + re, + contained=False, + contains=None, + nextgroup=None, + link=None): + + return _HighlightDefinition(name, + re, + contained, + contains, + nextgroup, + link) + + +SYMBOL_CANDIDATE_HIGHLIGHT_SYNTAX = [ + HighlightDefinition( + name='location', + contains=('colon', 'number', 'path'), + nextgroup='kind', + re=r'\([^:]\+:\)\?\d\+:\d\+', + ), + HighlightDefinition( + name='path', + contained=True, + re=r'[^:]\+', + link='String', + ), + HighlightDefinition( + name='colon', + contained=True, + re=r':', + link='Comment', + ), + HighlightDefinition( + name='number', + contained=True, + re=r'\d\+', + link='Number', + ), + HighlightDefinition( + name='kind', + contained=True, + nextgroup='name', + re=r'\s\+\[\(\w\|\s\)*\]', + link='Type', + ), +] + + +def uri_to_path(uri: str) -> str: + return request.url2pathname(parse.urlparse(uri).path) + + +def highlight_setup(source: Base, syntax: List[HighlightDefinition]) -> None: + def mangle_name(name: str) -> str: + if name in ("TOP", "NONE"): + return name + if name.startswith("@"): + return name + + return "{}_{}".format(source.syntax_name, name) + + for hl_def in syntax: + match = [ + mangle_name(hl_def.name), + '/{}/'.format(hl_def.re), + 'contained'] + if hl_def.contains: + match.append("contains=" + ','.join( + mangle_name(i) for i in hl_def.contains + )) + elif hl_def.contains is None: + match.append("contains=NONE") + + if hl_def.nextgroup is not None: + match.append("nextgroup=" + mangle_name(hl_def.nextgroup)) + + if not hl_def.contained: + match.append("containedin=" + source.syntax_name) + + source.vim.command('syntax match ' + ' '.join(match)) + if hl_def.link is not None: + source.vim.command( + 'highlight default link {0}_{1} {2}'.format( + source.syntax_name, hl_def.name, hl_def.link)) + + +def convert_symbols_to_candidates(symbols: List[Dict], + bufname: str = None, + pwd: str = None) -> List[Dict]: + candidates = [] + paths = [] + kinds = [] + max_path_len = 0 + max_kind_len = 0 + for symbol in symbols: + name = symbol["name"] + start = symbol["location"]["range"]["start"] + line = start["line"] + 1 + character = start["character"] + 1 + kinds.append(SymbolKind(symbol.get("kind", 0)).describe()) + if not bufname: + filepath = uri_to_path(symbol["location"]["uri"]) + if pwd: + rpath = relpath(filepath, pwd) + if len(rpath) < len(filepath): + filepath = rpath + disp_path = filepath + if len(disp_path) > MAX_FNAME_LEN: + disp_path = "..." + disp_path[-MAX_FNAME_LEN - 3:] + paths.append("{}:{}:{}".format(disp_path, line, character)) + else: + filepath = bufname + paths.append("{}:{}".format(line, character)) + max_path_len = max(max_path_len, len(paths[-1])) + max_kind_len = max(max_kind_len, len(kinds[-1])) + candidates.append({ + "word": name, + "action__path": filepath, + "action__line": line, + "action__col": character, + }) + + for candidate, path, kind in zip(candidates, paths, kinds): + candidate["abbr"] = "{:<{}} [{:^{}}] {}".format( + path, + max_path_len, + kind, + max_kind_len, + candidate["word"], + ) + + return candidates diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/lsp/protocol.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/lsp/protocol.py new file mode 100644 index 0000000..ac8564b --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/lsp/protocol.py @@ -0,0 +1,55 @@ +from enum import IntEnum +import re + + +class SymbolKind(IntEnum): + Unknown = 0 + File = 1 + Module = 2 + Namespace = 3 + Package = 4 + Class = 5 + Method = 6 + Property = 7 + Field = 8 + Constructor = 9 + Enum = 10 + Interface = 11 + Function = 12 + Variable = 13 + Constant = 14 + String = 15 + Number = 16 + Boolean = 17 + Array = 18 + Object = 19 + Key = 20 + Null = 21 + EnumMember = 22 + Struct = 23 + Event = 24 + Operator = 25 + TypeParameter = 26 + # ccls extensions + # See also https://github.com/Microsoft/language-server-protocol/issues/344 + TypeAlias = 252 + Parameter = 253 + StaticMethod = 254 + Macro = 255 + + @staticmethod + def _missing_(value): + return SymbolKind.Unknown + + def describe(self): + return self._pprint_map[self] + + +SymbolKind._pprint_map = {} +for e in SymbolKind: + if e == SymbolKind.Unknown: + s = "" + else: + s = re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", str(e).split(".", 1)[1]) + + SymbolKind._pprint_map[int(e)] = s diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/contextMenu.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/contextMenu.py index f8d4cda..873f5f1 100644 --- a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/contextMenu.py +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/contextMenu.py @@ -2,6 +2,14 @@ from typing import List, Dict from .base import Base +DeniteOverrides = { + 'Code Action': 'codeAction', + 'Document Symbol': 'documentSymbol', + 'Workspace Symbol': 'workspaceSymbol', + 'References': 'references', +} + + class Source(Base): def __init__(self, vim): super().__init__(vim) @@ -10,10 +18,15 @@ class Source(Base): self.kind = 'command' def convert_to_candidate(self, item): + if item in DeniteOverrides: + cmd = 'call denite#start([{{"name": "{}", "args": []}}])'.format( + DeniteOverrides[item]) + else: + cmd = ('call ' + 'LanguageClient_handleContextMenuItem("{}")'.format(item)) return { "word": item, - 'action__command': 'call ' - 'LanguageClient_handleContextMenuItem("{}")'.format(item), + 'action__command': cmd, } def gather_candidates(self, context: Dict) -> List[Dict]: diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/documentSymbol.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/documentSymbol.py index 77a5837..06544c3 100644 --- a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/documentSymbol.py +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/documentSymbol.py @@ -1,19 +1,16 @@ from typing import List, Dict +from os.path import dirname +import sys from .base import Base +sys.path.insert(0, dirname(dirname(__file__))) -def convert_to_candidate(symbol: Dict, bufname: str) -> Dict: - name = symbol["name"] - start = symbol["location"]["range"]["start"] - line = start["line"] + 1 - character = start["character"] + 1 - return { - "word": "{}:{}:\t{}".format(line, character, name), - "action__path": bufname, - "action__line": line, - "action__col": character, - } +from common import ( # isort:skip # noqa: I100 E402 + convert_symbols_to_candidates, + SYMBOL_CANDIDATE_HIGHLIGHT_SYNTAX, + highlight_setup, +) class Source(Base): @@ -23,9 +20,11 @@ class Source(Base): self.name = 'documentSymbol' self.kind = 'file' + def highlight(self): + highlight_setup(self, SYMBOL_CANDIDATE_HIGHLIGHT_SYNTAX) + def gather_candidates(self, context: Dict) -> List[Dict]: bufname = self.vim.current.buffer.name result = self.vim.funcs.LanguageClient_runSync( 'LanguageClient_textDocument_documentSymbol', {}) or [] - return [convert_to_candidate(symbol, bufname) for - symbol in result] + return convert_symbols_to_candidates(result, bufname) diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/workspaceSymbol.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/workspaceSymbol.py index e7dcc02..bb6e54a 100644 --- a/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/workspaceSymbol.py +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/denite/source/workspaceSymbol.py @@ -1,12 +1,15 @@ -from urllib import request, parse from os import path -from typing import List, Dict +import sys from .base import Base +sys.path.insert(0, path.dirname(path.dirname(__file__))) -def uri_to_path(uri: str) -> str: - return request.url2pathname(parse.urlparse(uri).path) +from common import ( # isort:skip # noqa: I100 E402 + convert_symbols_to_candidates, + SYMBOL_CANDIDATE_HIGHLIGHT_SYNTAX, + highlight_setup, +) class Source(Base): @@ -17,28 +20,34 @@ class Source(Base): self.name = 'workspaceSymbol' self.kind = 'file' - def convert_to_candidates(self, symbols: List[Dict]) -> List[Dict]: - candidates = [] - pwd = self.vim.funcs.getcwd() - for sb in symbols: - name = sb["name"] - uri = sb["location"]["uri"] - filepath = uri_to_path(uri) - relpath = path.relpath(filepath, pwd) - start = sb["location"]["range"]["start"] - line = start["line"] + 1 - character = start["character"] + 1 - candidates.append({ - "word": "{}:{}:{}:\t{}".format( - relpath, line, character, name), - "action__path": filepath, - "action__line": line, - "action__col": character, - }) - - return candidates + def highlight(self): + highlight_setup(self, SYMBOL_CANDIDATE_HIGHLIGHT_SYNTAX) def gather_candidates(self, context): + context['is_interactive'] = True + prefix = context['input'] + bufnr = context['bufnr'] + + # This a hack to get around the fact that LanguageClient APIs + # work in the context of the active buffer, when filtering results + # interactively, the denite buffer is the active buffer and it doesn't + # have a language server asscosiated with it. + # We just switch to the buffer that initiated the denite transaction + # and execute the command from it. This should be changed when we + # have a better way to run requests out of the buffer. + # See issue#674 + current_buffer = self.vim.current.buffer.number + if current_buffer != bufnr: + self.vim.command("tabedit %") + self.vim.command( + "execute 'noautocmd keepalt buffer' {}".format(bufnr)) result = self.vim.funcs.LanguageClient_runSync( - 'LanguageClient#workspace_symbol', '', {}) or [] - return self.convert_to_candidates(result) + 'LanguageClient#workspace_symbol', prefix, {}) or [] + if current_buffer != bufnr: + self.vim.command("tabclose") + + candidates = convert_symbols_to_candidates( + result, + pwd=self.vim.funcs.getcwd()) + + return candidates diff --git a/vim/bundle/LanguageClient-neovim/rplugin/python3/deoplete/sources/LanguageClientSource.py b/vim/bundle/LanguageClient-neovim/rplugin/python3/deoplete/sources/LanguageClientSource.py index d9a984a..4ddb5ff 100644 --- a/vim/bundle/LanguageClient-neovim/rplugin/python3/deoplete/sources/LanguageClientSource.py +++ b/vim/bundle/LanguageClient-neovim/rplugin/python3/deoplete/sources/LanguageClientSource.py @@ -1,7 +1,7 @@ from .base import Base -CompleteOutputs = "g:LanguageClient_omniCompleteResults" +COMPLETE_OUTPUTS = "g:LanguageClient_omniCompleteResults" class Source(Base): @@ -14,16 +14,12 @@ class Source(Base): self.min_pattern_length = 1 self.filetypes = vim.eval( "get(g:, 'LanguageClient_serverCommands', {})").keys() - self.input_pattern += r'(\.|::|->)\w*$' - - def get_complete_position(self, context): - return self.vim.call( - 'LanguageClient#get_complete_start', context['input']) + self.input_pattern = r'(\.|::|->)\w*$' def gather_candidates(self, context): if context["is_async"]: - outputs = self.vim.eval(CompleteOutputs) - if len(outputs) != 0: + outputs = self.vim.eval(COMPLETE_OUTPUTS) + if outputs: context["is_async"] = False # TODO: error handling. candidates = outputs[0].get("result", []) @@ -31,7 +27,7 @@ class Source(Base): return candidates else: context["is_async"] = True - self.vim.command("let {} = []".format(CompleteOutputs)) + self.vim.command("let {} = []".format(COMPLETE_OUTPUTS)) character = (context["complete_position"] + len(context["complete_str"])) self.vim.funcs.LanguageClient_omniComplete({ @@ -39,11 +35,3 @@ class Source(Base): "complete_position": context["complete_position"], }) return [] - - -# f = open("/tmp/deoplete.log", "w") - - -# def log(message): -# f.writelines([message]) -# f.flush() diff --git a/vim/bundle/LanguageClient-neovim/rustfmt.toml b/vim/bundle/LanguageClient-neovim/rustfmt.toml new file mode 100644 index 0000000..1f0f526 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/rustfmt.toml @@ -0,0 +1 @@ +# defaults are great :) diff --git a/vim/bundle/LanguageClient-neovim/shell.nix b/vim/bundle/LanguageClient-neovim/shell.nix new file mode 100644 index 0000000..bbda153 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/shell.nix @@ -0,0 +1,30 @@ +let + sources = import ./nix/sources.nix; + pkgs = import sources.nixpkgs { }; + inherit (pkgs) stdenv; +in +pkgs.mkShell { + buildInputs = (with pkgs; [ + curl + git + mypy + neovim + cargo + rust-analyzer + tmux + vim-vint + (with python37Packages; [ + flake8 + pynvim + pytest + ]) + ]) + ++ stdenv.lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ + CoreServices + ]) + ++ stdenv.lib.optionals stdenv.isLinux (with pkgs; [ + cargo-release # build error on macOS: "iconv.h" not found. + ]); + + NIX_LDFLAGS = stdenv.lib.optionalString stdenv.isDarwin "-framework CoreFoundation"; +} diff --git a/vim/bundle/LanguageClient-neovim/src/extensions/java.rs b/vim/bundle/LanguageClient-neovim/src/extensions/java.rs new file mode 100644 index 0000000..aaeaaa6 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/extensions/java.rs @@ -0,0 +1,26 @@ +use crate::language_client::LanguageClient; +use anyhow::Result; +use lsp_types::{Command, WorkspaceEdit}; + +pub mod command { + pub const APPLY_WORKSPACE_EDIT: &str = "java.apply.workspaceEdit"; +} + +impl LanguageClient { + pub fn handle_java_command(&self, cmd: &Command) -> Result { + match cmd.command.as_str() { + command::APPLY_WORKSPACE_EDIT => { + if let Some(ref edits) = cmd.arguments { + for edit in edits { + let edit: WorkspaceEdit = serde_json::from_value(edit.clone())?; + self.apply_workspace_edit(&edit)?; + } + } + } + + _ => return Ok(false), + } + + Ok(true) + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/extensions/mod.rs b/vim/bundle/LanguageClient-neovim/src/extensions/mod.rs new file mode 100644 index 0000000..0310ed1 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/extensions/mod.rs @@ -0,0 +1,2 @@ +pub mod java; +pub mod rust_analyzer; diff --git a/vim/bundle/LanguageClient-neovim/src/extensions/rust_analyzer.rs b/vim/bundle/LanguageClient-neovim/src/extensions/rust_analyzer.rs new file mode 100644 index 0000000..4062940 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/extensions/rust_analyzer.rs @@ -0,0 +1,202 @@ +use crate::{language_client::LanguageClient, types::WorkspaceEditWithCursor}; +use anyhow::{anyhow, Result}; +use jsonrpc_core::Value; +use lsp_types::{Command, Location}; +use serde::Deserialize; +use std::path::PathBuf; + +// Runnable wraps the two possible shapes of a runnable action from rust-analyzer. Old-ish versions +// of it will use BinRunnable, whereas the newer ones use CargoRunnable. +#[derive(Debug, Deserialize, PartialEq)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +enum Runnable { + Bin(BinRunnable), + Generic(GenericRunnable), +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +struct BinRunnable { + pub label: String, + pub bin: String, + pub args: Vec, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +struct GenericRunnable { + pub label: String, + pub kind: GenericRunnableKind, + pub location: Option, + pub args: GenericRunnableArgs, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +struct GenericRunnableArgs { + pub workspace_root: Option, + pub cargo_args: Vec, + pub executable_args: Vec, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +enum GenericRunnableKind { + Cargo, +} + +pub mod command { + pub const SHOW_REFERENCES: &str = "rust-analyzer.showReferences"; + pub const SELECT_APPLY_SOURCE_CHANGE: &str = "rust-analyzer.selectAndApplySourceChange"; + pub const APPLY_SOURCE_CHANGE: &str = "rust-analyzer.applySourceChange"; + pub const RUN_SINGLE: &str = "rust-analyzer.runSingle"; + pub const RUN: &str = "rust-analyzer.run"; +} + +impl LanguageClient { + pub fn handle_rust_analyzer_command(&self, cmd: &Command) -> Result { + match cmd.command.as_str() { + command::SHOW_REFERENCES => { + let locations = cmd + .arguments + .clone() + .unwrap_or_default() + .get(2) + .cloned() + .unwrap_or_else(|| Value::Array(vec![])); + let locations: Vec = serde_json::from_value(locations)?; + + self.present_list("References", &locations)?; + } + command::SELECT_APPLY_SOURCE_CHANGE => { + if let Some(ref edits) = cmd.arguments { + for edit in edits { + let workspace_edits = >::deserialize(edit)?; + for edit in workspace_edits { + self.apply_workspace_edit(&edit.workspace_edit)?; + if let Some(cursor_position) = edit.cursor_position { + self.vim()?.cursor( + cursor_position.position.line + 1, + cursor_position.position.character + 1, + )?; + } + } + } + } + } + command::APPLY_SOURCE_CHANGE => { + if let Some(ref edits) = cmd.arguments { + for edit in edits { + let edit = WorkspaceEditWithCursor::deserialize(edit)?; + self.apply_workspace_edit(&edit.workspace_edit)?; + if let Some(cursor_position) = edit.cursor_position { + self.vim()?.cursor( + cursor_position.position.line + 1, + cursor_position.position.character + 1, + )?; + } + } + } + } + command::RUN_SINGLE | command::RUN => { + let has_term: i32 = self.vim()?.eval("exists(':terminal')")?; + if has_term == 0 { + return Err(anyhow!("Terminal support is required for this action")); + } + + if let Some(ref args) = cmd.arguments { + if let Some(args) = args.first() { + let runnable = Runnable::deserialize(args)?; + let cmd = match runnable { + Runnable::Bin(runnable) => { + format!("term {} {}", runnable.bin, runnable.args.join(" ")) + } + Runnable::Generic(runnable) => { + format!("term cargo {}", runnable.args.cargo_args.join(" ")) + } + }; + + self.vim()?.command(cmd.replace('"', ""))?; + } + } + } + _ => return Ok(false), + } + + Ok(true) + } +} + +#[cfg(test)] +mod test { + use super::Runnable; + use super::*; + use lsp_types::Command; + use serde::Deserialize; + + #[test] + fn test_deserialize_bin_runnable() { + let cmd = r#"{ + "title":"Run", + "command":"rust-analyzer.runSingle", + "arguments": [ + { + "args":["run","--package","somepkg","--bin","somebin"], + "bin":"cargo", + "cwd":"/home/dev/somebin", + "extraArgs":[], + "label":"run binary" + } + ] + }"#; + + let cmd: Command = serde_json::from_str(cmd).unwrap(); + let actual = Runnable::deserialize(cmd.arguments.unwrap().first().unwrap()) + .expect("failed deserializing bin runnable"); + let expected = Runnable::Bin(BinRunnable { + label: "run binary".into(), + bin: "cargo".into(), + args: vec!["run", "--package", "somepkg", "--bin", "somebin"] + .into_iter() + .map(|it| it.into()) + .collect(), + }); + assert_eq!(actual, expected); + } + + #[test] + fn test_deserialize_generic_runnable() { + let cmd = r#"{ + "title":"▶︎ Run", + "command":"rust-analyzer.runSingle", + "arguments":[{ + "args":{ + "cargoArgs":["run","--package","somepkg","--bin","somebin"], + "executableArgs":[], + "workspaceRoot":"/home/dev/test" + }, + "kind":"cargo", + "label":"run binary" + }] + }"#; + + let cmd: Command = serde_json::from_str(cmd).unwrap(); + let actual = Runnable::deserialize(cmd.arguments.unwrap().first().unwrap()) + .expect("failed deserializing cargo runnable"); + let expected = Runnable::Generic(GenericRunnable { + label: "run binary".into(), + kind: GenericRunnableKind::Cargo, + location: None, + args: GenericRunnableArgs { + workspace_root: Some("/home/dev/test".into()), + cargo_args: vec!["run", "--package", "somepkg", "--bin", "somebin"] + .into_iter() + .map(|it| it.into()) + .collect(), + executable_args: vec![], + }, + }); + assert_eq!(actual, expected); + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/language_client.rs b/vim/bundle/LanguageClient-neovim/src/language_client.rs new file mode 100644 index 0000000..4146ff3 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/language_client.rs @@ -0,0 +1,86 @@ +use crate::{ + types::{LanguageId, State}, + utils::diff_value, + vim::Vim, +}; +use anyhow::{anyhow, Result}; +use log::*; +use serde_json::Value; + +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + sync::{Arc, Mutex, MutexGuard}, +}; + +#[derive(Clone)] +pub struct LanguageClient { + pub version: String, + pub state_mutex: Arc>, + pub clients_mutex: Arc>>>>, +} + +impl LanguageClient { + // NOTE: Don't expose this as public. + // MutexGuard could easily halt the program when one guard is not released immediately after use. + fn lock(&self) -> Result> { + self.state_mutex + .lock() + .map_err(|err| anyhow!("Failed to lock state: {:?}", err)) + } + + // This fetches a mutex that is unique to the provided languageId. + // + // Here, we return a mutex instead of the mutex guard because we need to satisfy the borrow + // checker. Otherwise, there is no way to guarantee that the mutex in the hash map wouldn't be + // garbage collected as a result of another modification updating the hash map, while something was holding the lock + pub fn get_client_update_mutex(&self, language_id: LanguageId) -> Result>> { + let map_guard = self.clients_mutex.lock(); + let mut map = map_guard.or_else(|err| { + Err(anyhow!( + "Failed to lock client creation for languageId {:?}: {:?}", + language_id, + err, + )) + })?; + if !map.contains_key(&language_id) { + map.insert(language_id.clone(), Arc::new(Mutex::new(()))); + } + let mutex: Arc> = map.get(&language_id).unwrap().clone(); + Ok(mutex) + } + + pub fn get(&self, f: impl FnOnce(&State) -> T) -> Result { + Ok(f(self.lock()?.deref())) + } + + pub fn update(&self, f: impl FnOnce(&mut State) -> Result) -> Result { + let mut state = self.lock()?; + let mut state = state.deref_mut(); + + let v = if log_enabled!(log::Level::Debug) { + let s = serde_json::to_string(&state)?; + serde_json::from_str(&s)? + } else { + Value::default() + }; + + let result = f(&mut state); + + let next_v = if log_enabled!(log::Level::Debug) { + let s = serde_json::to_string(&state)?; + serde_json::from_str(&s)? + } else { + Value::default() + }; + + for (k, (v1, v2)) in diff_value(&v, &next_v, "state") { + debug!("{}: {} ==> {}", k, v1, v2); + } + result + } + + pub fn vim(&self) -> Result { + self.get(|state| state.vim.clone()) + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/language_server_protocol.rs b/vim/bundle/LanguageClient-neovim/src/language_server_protocol.rs new file mode 100644 index 0000000..210f5e3 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/language_server_protocol.rs @@ -0,0 +1,4068 @@ +use crate::extensions::{java, rust_analyzer}; +use crate::language_client::LanguageClient; +use crate::vim::{try_get, Mode}; +use crate::{ + rpcclient::RpcClient, + types::*, + utils::{ + apply_text_edits, code_action_kind_as_str, convert_to_vim_str, decode_parameter_label, + escape_single_quote, expand_json_path, get_default_initialization_options, get_root_path, + vim_cmd_args_to_value, Canonicalize, Combine, ToUrl, + }, + viewport, +}; +use anyhow::{anyhow, Context, Error, Result}; +use itertools::Itertools; +use jsonrpc_core::Value; +use log::{debug, error, info, warn}; +use lsp_types::notification::Notification; +use lsp_types::request::Request; +use lsp_types::{ + code_action_kind, ApplyWorkspaceEditParams, ApplyWorkspaceEditResponse, ClientCapabilities, + ClientInfo, CodeAction, CodeActionCapability, CodeActionContext, CodeActionKindLiteralSupport, + CodeActionLiteralSupport, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CodeLens, + Command, CompletionCapability, CompletionItem, CompletionItemCapability, CompletionResponse, + CompletionTextEdit, Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, + DidChangeTextDocumentParams, DidChangeWatchedFilesParams, + DidChangeWatchedFilesRegistrationOptions, DidCloseTextDocumentParams, + DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentChangeOperation, DocumentChanges, + DocumentFormattingParams, DocumentHighlight, DocumentHighlightKind, + DocumentRangeFormattingParams, DocumentSymbolParams, ExecuteCommandParams, FormattingOptions, + GenericCapability, GotoCapability, GotoDefinitionResponse, Hover, HoverCapability, + InitializeParams, InitializeResult, InitializedParams, Location, LogMessageParams, MarkupKind, + MessageType, NumberOrString, ParameterInformation, ParameterInformationSettings, + PartialResultParams, Position, ProgressParams, ProgressParamsValue, + PublishDiagnosticsCapability, PublishDiagnosticsParams, Range, ReferenceContext, + RegistrationParams, RenameParams, ResourceOp, SemanticHighlightingClientCapability, + SemanticHighlightingParams, ShowMessageParams, ShowMessageRequestParams, SignatureHelp, + SignatureHelpCapability, SignatureInformationSettings, SymbolInformation, + TextDocumentClientCapabilities, TextDocumentContentChangeEvent, TextDocumentIdentifier, + TextDocumentItem, TextDocumentPositionParams, TextEdit, TraceOption, UnregistrationParams, + VersionedTextDocumentIdentifier, WorkDoneProgress, WorkDoneProgressParams, + WorkspaceClientCapabilities, WorkspaceEdit, WorkspaceSymbolParams, +}; +use maplit::hashmap; +use notify::Watcher; +use serde::de::Deserialize; +use serde_json::json; +use std::{ + collections::HashMap, + fs::{read_to_string, File}, + io::{BufRead, BufReader, BufWriter}, + net::TcpStream, + path::{Path, PathBuf}, + process::Stdio, + str::FromStr, + sync::{mpsc, Arc, MutexGuard}, + thread, + time::{Duration, Instant}, +}; + +impl LanguageClient { + pub fn get_client(&self, language_id: &LanguageId) -> Result> { + self.get(|state| state.clients.get(language_id).cloned())? + .ok_or_else(|| { + LCError::ServerNotRunning { + language_id: language_id.clone().unwrap_or_default(), + } + .into() + }) + } + + pub fn loop_call(&self, rx: &crossbeam::channel::Receiver) -> Result<()> { + for call in rx.iter() { + let language_client = self.clone(); + thread::spawn(move || { + if let Err(err) = language_client.handle_call(call) { + error!("Error handling request:\n{:?}", err); + } + }); + } + + Ok(()) + } + + /////// Utils /////// + fn sync_settings(&self) -> Result<()> { + info!("Begin sync settings"); + let (logging_file, logging_level, server_stderr): ( + Option, + log::LevelFilter, + Option, + ) = self.vim()?.eval( + [ + "get(g:, 'LanguageClient_loggingFile', v:null)", + "get(g:, 'LanguageClient_loggingLevel', 'WARN')", + "get(g:, 'LanguageClient_serverStderr', v:null)", + ] + .as_ref(), + )?; + self.update(|state| state.logger.update_settings(logging_level, logging_file))?; + + #[allow(clippy::type_complexity)] + let ( + auto_start, + server_commands, + selection_ui, + trace, + settings_path, + load_settings, + root_markers, + change_throttle, + wait_output_timeout, + diagnostics_enable, + diagnostics_list, + diagnostics_display, + window_log_message_level, + hover_preview, + completion_prefer_text_edit, + is_nvim, + ): ( + u64, + HashMap>, + Option, + Option, + Vec, + u64, + Option, + Option, + Option, + u64, + Option, + Value, + String, + Option, + u64, + u64, + ) = self.vim()?.eval( + [ + "!!get(g:, 'LanguageClient_autoStart', 1)", + "s:GetVar('LanguageClient_serverCommands', {})", + "s:getSelectionUI()", + "get(g:, 'LanguageClient_trace', v:null)", + "map(s:ToList(get(g:, 'LanguageClient_settingsPath', '.vim/settings.json')), 'expand(v:val)')", + "!!get(g:, 'LanguageClient_loadSettings', 1)", + "get(g:, 'LanguageClient_rootMarkers', v:null)", + "get(g:, 'LanguageClient_changeThrottle', v:null)", + "get(g:, 'LanguageClient_waitOutputTimeout', v:null)", + "!!get(g:, 'LanguageClient_diagnosticsEnable', 1)", + "get(g:, 'LanguageClient_diagnosticsList', 'Quickfix')", + "get(g:, 'LanguageClient_diagnosticsDisplay', {})", + "get(g:, 'LanguageClient_windowLogMessageLevel', 'Warning')", + "get(g:, 'LanguageClient_hoverPreview', 'Auto')", + "get(g:, 'LanguageClient_completionPreferTextEdit', 0)", + "has('nvim')", + ] + .as_ref(), + )?; + + #[allow(clippy::type_complexity)] + let ( + diagnostics_signs_max, + diagnostics_max_severity, + document_highlight_display, + selection_ui_auto_open, + use_virtual_text, + echo_project_root, + semantic_highlight_maps, + semantic_scope_separator, + apply_completion_text_edits, + preferred_markup_kind, + hide_virtual_texts_on_insert, + ): ( + Option, + String, + Value, + u8, + UseVirtualText, + u8, + HashMap>, + String, + u8, + Option>, + u8, + ) = self.vim()?.eval( + [ + "get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)", + "get(g:, 'LanguageClient_diagnosticsMaxSeverity', 'Hint')", + "get(g:, 'LanguageClient_documentHighlightDisplay', {})", + "!!s:GetVar('LanguageClient_selectionUI_autoOpen', 1)", + "s:useVirtualText()", + "!!s:GetVar('LanguageClient_echoProjectRoot', 1)", + "s:GetVar('LanguageClient_semanticHighlightMaps', {})", + "s:GetVar('LanguageClient_semanticScopeSeparator', ':')", + "get(g:, 'LanguageClient_applyCompletionAdditionalTextEdits', 1)", + "get(g:, 'LanguageClient_preferredMarkupKind', v:null)", + "s:GetVar('LanguageClient_hideVirtualTextsOnInsert', 0)", + ] + .as_ref(), + )?; + + // vimscript use 1 for true, 0 for false. + let auto_start = auto_start == 1; + let selection_ui_auto_open = selection_ui_auto_open == 1; + let load_settings = load_settings == 1; + + let trace = match trace { + Some(t) => match t.to_ascii_uppercase().as_str() { + "OFF" => Some(TraceOption::Off), + "MESSAGES" => Some(TraceOption::Messages), + "VERBOSE" => Some(TraceOption::Verbose), + _ => return Err(anyhow!("Invalid option for LanguageClient_trace: {}", t)), + }, + None => Some(TraceOption::default()), + }; + + let selection_ui = if let Some(s) = selection_ui { + SelectionUI::from_str(&s)? + } else if self.vim()?.eval::<_, i64>("get(g:, 'loaded_fzf')")? == 1 { + SelectionUI::Funcref + } else { + SelectionUI::default() + }; + + let change_throttle = change_throttle.map(|t| Duration::from_millis((t * 1000.0) as u64)); + let wait_output_timeout = + Duration::from_millis((wait_output_timeout.unwrap_or(10.0) * 1000.0) as u64); + + let diagnostics_enable = diagnostics_enable == 1; + + let diagnostics_list = if let Some(s) = diagnostics_list { + DiagnosticsList::from_str(&s)? + } else { + DiagnosticsList::Disabled + }; + + let window_log_level = match window_log_message_level.to_ascii_uppercase().as_str() { + "ERROR" => MessageType::Error, + "WARNING" => MessageType::Warning, + "INFO" => MessageType::Info, + "LOG" => MessageType::Log, + _ => { + return Err(anyhow!( + "Invalid option for LanguageClient_windowLogMessageLevel: {}", + window_log_message_level + )) + } + }; + + let hover_preview = if let Some(s) = hover_preview { + HoverPreviewOption::from_str(&s)? + } else { + HoverPreviewOption::Auto + }; + + let prefer_text_edit = completion_prefer_text_edit == 1; + let apply_completion_edits = apply_completion_text_edits == 1; + + let is_nvim = is_nvim == 1; + + let diagnostics_max_severity = match diagnostics_max_severity.to_ascii_uppercase().as_str() + { + "ERROR" => DiagnosticSeverity::Error, + "WARNING" => DiagnosticSeverity::Warning, + "INFORMATION" => DiagnosticSeverity::Information, + "HINT" => DiagnosticSeverity::Hint, + _ => { + return Err(anyhow!( + "Invalid option for LanguageClient_diagnosticsMaxSeverity: {}", + diagnostics_max_severity + )) + } + }; + + let semantic_highlight_language_ids: Vec = + semantic_highlight_maps.keys().cloned().collect(); + + self.update(|state| { + state.auto_start = auto_start; + state.semantic_highlight_maps = semantic_highlight_maps; + state.semantic_scope_separator = semantic_scope_separator; + state.semantic_scope_to_hl_group_table.clear(); + state.server_commands.extend(server_commands); + state.selection_ui = selection_ui; + state.selection_ui_auto_open = selection_ui_auto_open; + state.trace = trace; + state.diagnostics_enable = diagnostics_enable; + state.diagnostics_list = diagnostics_list; + state.diagnostics_display = serde_json::from_value( + serde_json::to_value(&state.diagnostics_display)?.combine(&diagnostics_display), + )?; + state.diagnostics_signs_max = diagnostics_signs_max; + state.diagnostics_max_severity = diagnostics_max_severity; + state.document_highlight_display = serde_json::from_value( + serde_json::to_value(&state.document_highlight_display)? + .combine(&document_highlight_display), + )?; + state.window_log_message_level = window_log_level; + state.settings_path = settings_path; + state.load_settings = load_settings; + state.root_markers = root_markers; + state.change_throttle = change_throttle; + state.wait_output_timeout = wait_output_timeout; + state.hover_preview = hover_preview; + state.completion_prefer_text_edit = prefer_text_edit; + state.apply_completion_additional_text_edits = apply_completion_edits; + state.use_virtual_text = use_virtual_text; + state.hide_virtual_texts_on_insert = hide_virtual_texts_on_insert == 1; + state.echo_project_root = echo_project_root == 1; + state.server_stderr = server_stderr; + state.is_nvim = is_nvim; + state.preferred_markup_kind = preferred_markup_kind; + Ok(()) + })?; + + for language_id in semantic_highlight_language_ids { + self.update_semantic_highlight_tables(&language_id)?; + } + + info!("End sync settings"); + Ok(()) + } + + fn get_workspace_settings(&self, root: &str) -> Result { + if !self.get(|state| state.load_settings)? { + return Ok(Value::Null); + } + + let mut res = Value::Null; + let mut last_err = None; + let mut at_least_one_success = false; + for orig_path in self.get(|state| state.settings_path.clone())? { + let path = Path::new(root).join(orig_path); + let buffer = read_to_string(&path) + .with_context(|| format!("Failed to read file ({})", path.to_string_lossy())); + let buffer = match buffer { + Err(e) => { + last_err = Some(e); + continue; + } + Ok(x) => x, + }; + let value = serde_json::from_str(&buffer); + let value = match value { + Err(e) => { + last_err = Some(e.into()); + continue; + } + Ok(x) => x, + }; + let value = expand_json_path(value); + json_patch::merge(&mut res, &value); + at_least_one_success = true; + } + + match last_err { + // no file was read and an error happened + Some(e) if !at_least_one_success => Err(e), + _ => Ok(res), + } + } + + fn define_signs(&self) -> Result<()> { + info!("Defining signs"); + + let mut cmds = vec![]; + for entry in self + .get(|state| state.diagnostics_display.clone())? + .values() + { + cmds.push(format!( + "sign define LanguageClient{} text={} texthl={}", + entry.name, entry.sign_text, entry.sign_texthl, + )); + } + + self.vim()?.command(cmds)?; + Ok(()) + } + + pub fn apply_workspace_edit(&self, edit: &WorkspaceEdit) -> Result<()> { + debug!("Begin apply WorkspaceEdit: {:?}", edit); + let mut filename = self.vim()?.get_filename(&Value::Null)?; + let mut position = self.vim()?.get_position(&Value::Null)?; + + if let Some(ref changes) = edit.document_changes { + match changes { + DocumentChanges::Edits(ref changes) => { + for e in changes { + position = self.apply_text_edits( + &e.text_document.uri.filepath()?, + &e.edits, + position, + )?; + } + } + DocumentChanges::Operations(ref ops) => { + for op in ops { + match op { + DocumentChangeOperation::Edit(ref e) => { + position = self.apply_text_edits( + &e.text_document.uri.filepath()?, + &e.edits, + position, + )? + } + DocumentChangeOperation::Op(ref rop) => match rop { + ResourceOp::Create(file) => { + filename = file.uri.filepath()?.to_string_lossy().into_owned(); + position = Position::default(); + } + ResourceOp::Rename(_file) => { + return Err(anyhow!("file renaming not yet supported.")); + } + ResourceOp::Delete(_file) => { + return Err(anyhow!("file deletion not yet supported.")); + } + }, + } + } + } + } + } else if let Some(ref changes) = edit.changes { + for (uri, edits) in changes { + position = self.apply_text_edits(&uri.filepath()?, edits, position)?; + } + } + self.edit(&None, &filename)?; + self.vim()? + .cursor(position.line + 1, position.character + 1)?; + debug!("End apply WorkspaceEdit"); + Ok(()) + } + + pub fn text_document_document_highlight(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!( + "Begin {}", + lsp_types::request::DocumentHighlightRequest::METHOD + ); + let filename = self.vim()?.get_filename(&Value::Null)?; + let language_id = self.vim()?.get_language_id(&filename, &Value::Null)?; + let position = self.vim()?.get_position(&Value::Null)?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::DocumentHighlightRequest::METHOD, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let document_highlight: Option> = + serde_json::from_value(result.clone())?; + if let Some(document_highlight) = document_highlight { + let document_highlight_display = + self.get(|state| state.document_highlight_display.clone())?; + let highlights = document_highlight + .into_iter() + .map(|DocumentHighlight { range, kind }| { + Ok(Highlight { + line: range.start.line, + character_start: range.start.character, + character_end: range.end.character, + group: document_highlight_display + .get( + &kind + .unwrap_or(DocumentHighlightKind::Text) + .to_int() + .unwrap(), + ) + .ok_or_else(|| anyhow!("Failed to get display"))? + .texthl + .clone(), + text: String::new(), + }) + }) + .collect::>>()?; + + let buffer = self.vim()?.get_bufnr(&filename, params)?; + + // The following code needs to be inside the critical section as a whole to update + // everything correctly and not leave hanging highlights. + self.update(|state| { + let source = if let Some(hs) = state.document_highlight_source { + if hs.buffer == buffer { + // If we want to highlight in the same buffer as last time, we can reuse + // the previous source. + Some(hs.source) + } else { + // Clear the highlight in the previous buffer. + state.vim.rpcclient.notify( + "nvim_buf_clear_highlight", + json!([hs.buffer, hs.source, 0, -1]), + )?; + + None + } + } else { + None + }; + + let source = match source { + Some(source) => source, + None => { + // Create a new source. + let source = state.vim.rpcclient.call( + "nvim_buf_add_highlight", + json!([buffer, 0, "Error", 1, 1, 1]), + )?; + state.document_highlight_source = Some(HighlightSource { buffer, source }); + source + } + }; + + state + .vim + .rpcclient + .notify("nvim_buf_clear_highlight", json!([buffer, source, 0, -1]))?; + state + .vim + .rpcclient + .notify("s:AddHighlights", json!([source, highlights]))?; + + Ok(()) + })?; + } + + info!( + "End {}", + lsp_types::request::DocumentHighlightRequest::METHOD + ); + Ok(result) + } + + pub fn clear_document_highlight(&self, _params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_CLEAR_DOCUMENT_HL); + + // The following code needs to be inside the critical section as a whole to update + // everything correctly and not leave hanging highlights. + self.update(|state| { + if let Some(HighlightSource { buffer, source }) = state.document_highlight_source.take() + { + state + .vim + .rpcclient + .notify("nvim_buf_clear_highlight", json!([buffer, source, 0, -1]))?; + } + Ok(()) + })?; + + info!("End {}", NOTIFICATION_CLEAR_DOCUMENT_HL); + Ok(()) + } + + fn apply_text_edits>( + &self, + path: P, + edits: &[TextEdit], + position: Position, + ) -> Result { + debug!("Begin apply TextEdits: {:?}", edits); + if edits.is_empty() { + return Ok(position); + } + + let mut edits = edits.to_vec(); + + // Edits must be applied from bottom to top, so that earlier edits will not interfere + // with the positioning of later edits. Edits that start with the same position must be + // applied in reverse order, so that multiple inserts will have their text appear in the + // same order the server sent it, and so that a delete/replace (according to the LSP spec, + // there can only be one per start position and it must be after the inserts) will work on + // the original document, not on the just-inserted text. + edits.sort_by_key(|edit| (edit.range.start.line, edit.range.start.character)); + edits.reverse(); + + self.edit(&None, path)?; + + let mut lines: Vec = self.vim()?.rpcclient.call("getline", json!([1, '$']))?; + let lines_len_prev = lines.len(); + let fixendofline = self.vim()?.eval::<_, u8>("&fixendofline")? == 1; + if lines.last().map(String::is_empty) == Some(false) && fixendofline { + lines.push("".to_owned()); + } + + let (mut lines, position) = apply_text_edits(&lines, &edits, &position)?; + + if lines.last().map(String::is_empty) == Some(true) && fixendofline { + lines.pop(); + } + if lines.len() < lines_len_prev { + self.vim()? + .command(format!("{},{}d", lines.len() + 1, lines_len_prev))?; + } + self.vim()?.rpcclient.notify("setline", json!([1, lines]))?; + debug!("End apply TextEdits"); + Ok(position) + } + + fn update_quickfixlist(&self) -> Result<()> { + let diagnostics = self.get(|state| state.diagnostics.clone())?; + let qflist: Vec<_> = diagnostics + .iter() + .flat_map(|(filename, diagnostics)| { + diagnostics + .iter() + .map(|dn| QuickfixEntry { + filename: filename.to_owned(), + lnum: dn.range.start.line + 1, + col: Some(dn.range.start.character + 1), + nr: dn.code.clone().map(|ns| ns.to_string()), + text: Some(dn.message.to_owned()), + typ: dn.severity.map(|sev| sev.to_quickfix_entry_type()), + }) + .collect::>() + }) + .collect(); + + let title = "[LC]: diagnostics"; + let diagnostics_list = self.get(|state| state.diagnostics_list)?; + match diagnostics_list { + DiagnosticsList::Quickfix => { + self.vim()?.setqflist(&qflist, "r", title)?; + } + DiagnosticsList::Location => { + self.vim()?.setloclist(&qflist, "r", title)?; + } + DiagnosticsList::Disabled => {} + } + + Ok(()) + } + + fn process_diagnostics(&self, filename: &str, diagnostics: &[Diagnostic]) -> Result<()> { + if !self.get(|state| state.text_documents.contains_key(filename))? { + return Ok(()); + } + + let text = self.get(|state| { + state + .text_documents + .get(filename) + .map(|d| d.text.clone()) + .unwrap_or_default() + })?; + let lines: Vec<_> = text.lines().map(ToOwned::to_owned).collect(); + + // Line diagnostics. + let mut line_diagnostics = HashMap::new(); + for entry in diagnostics { + let line = entry.range.start.line; + let mut msg = String::new(); + if let Some(severity) = entry.severity { + msg += &format!("[{:?}] ", severity); + } + if let Some(ref code) = entry.code { + let s = code.to_string(); + if !s.is_empty() { + msg += &format!("[{}] ", s); + } + } + msg += &entry.message; + line_diagnostics.insert((filename.to_owned(), line), msg); + } + self.update(|state| { + state + .line_diagnostics + .retain(|&(ref f, _), _| f != filename); + state.line_diagnostics.extend(line_diagnostics); + Ok(()) + })?; + + // Highlight. + let diagnostics_display = self.get(|state| state.diagnostics_display.clone())?; + + let mut highlights = vec![]; + for dn in diagnostics { + let line = dn.range.start.line; + let character_start = dn.range.start.character; + let character_end = dn.range.end.character; + + let severity = dn.severity.unwrap_or(DiagnosticSeverity::Hint); + let group = diagnostics_display + .get(&severity.to_int()?) + .ok_or_else(|| anyhow!("Failed to get display"))? + .texthl + .clone(); + // TODO: handle multi-line range. + let text = lines + .get(line as usize) + .and_then(|l| l.get((character_start as usize)..(character_end as usize))) + .map(ToOwned::to_owned) + .unwrap_or_default(); + + highlights.push(Highlight { + line, + character_start, + character_end, + group, + text, + }); + } + // dedup? + self.update(|state| { + state.highlights.insert(filename.to_owned(), highlights); + Ok(()) + })?; + + if !self.get(|state| state.is_nvim)? { + // Clear old highlights. + let ids = self.get(|state| state.highlight_match_ids.clone())?; + self.vim()? + .rpcclient + .notify("s:MatchDelete", json!([ids]))?; + + // Group diagnostics by severity so we can highlight them + // in a single call. + let mut match_groups: HashMap<_, Vec<_>> = HashMap::new(); + + for dn in diagnostics { + let severity = dn.severity.unwrap_or(DiagnosticSeverity::Hint).to_int()?; + match_groups + .entry(severity) + .or_insert_with(Vec::new) + .push(dn); + } + + let mut new_match_ids = Vec::new(); + + for (severity, dns) in match_groups { + let hl_group = diagnostics_display + .get(&severity) + .ok_or_else(|| anyhow!("Failed to get display"))? + .texthl + .clone(); + let ranges: Vec> = dns + .iter() + .flat_map(|dn| { + if dn.range.start.line == dn.range.end.line { + let length = dn.range.end.character - dn.range.start.character; + // Vim line numbers are 1 off + // `matchaddpos` expects an array of [line, col, length] + // for each match. + vec![vec![ + dn.range.start.line + 1, + dn.range.start.character + 1, + length, + ]] + } else { + let mut middle_lines: Vec<_> = (dn.range.start.line + 1 + ..dn.range.end.line) + .map(|l| vec![l + 1]) + .collect(); + let start_line = vec![ + dn.range.start.line + 1, + dn.range.start.character + 1, + 999_999, //Clear to the end of the line + ]; + let end_line = + vec![dn.range.end.line + 1, 1, dn.range.end.character + 1]; + middle_lines.push(start_line); + // For a multi-ringe range ending at the exact start of the last line, + // don't highlight the first character of the last line. + if dn.range.end.character > 0 { + middle_lines.push(end_line); + } + middle_lines + } + }) + .collect(); + + let match_id = self + .vim()? + .rpcclient + .call("matchaddpos", json!([hl_group, ranges]))?; + new_match_ids.push(match_id); + } + self.update(|state| { + state.highlight_match_ids = new_match_ids; + Ok(()) + })?; + } + + Ok(()) + } + + fn register_cm_source(&self, language_id: &str, result: &Value) -> Result<()> { + info!("Begin register NCM source"); + let exists_cm_register: u64 = self.vim()?.eval("exists('g:cm_matcher')")?; + if exists_cm_register == 0 { + return Ok(()); + } + + let result: InitializeResult = serde_json::from_value(result.clone())?; + if result.capabilities.completion_provider.is_none() { + return Ok(()); + } + + let trigger_patterns = result + .capabilities + .completion_provider + .map(|opt| { + let strings: Vec<_> = opt + .trigger_characters + .unwrap_or_default() + .iter() + .map(|c| regex::escape(c)) + .collect(); + strings + }) + .unwrap_or_default(); + + self.vim()?.rpcclient.notify( + "cm#register_source", + json!([{ + "name": format!("LanguageClient_{}", language_id), + "priority": 9, + "scopes": [language_id], + "cm_refresh_patterns": trigger_patterns, + "abbreviation": "LC", + "cm_refresh": REQUEST_NCM_REFRESH, + }]), + )?; + info!("End register NCM source"); + Ok(()) + } + + fn register_ncm2_source(&self, language_id: &str, result: &Value) -> Result<()> { + info!("Begin register NCM2 source"); + let exists_ncm2: u64 = self.vim()?.eval("exists('g:ncm2_loaded')")?; + if exists_ncm2 == 0 { + return Ok(()); + } + + let result: InitializeResult = serde_json::from_value(result.clone())?; + if result.capabilities.completion_provider.is_none() { + return Ok(()); + } + + let trigger_patterns = result + .capabilities + .completion_provider + .map(|opt| { + let strings: Vec<_> = opt + .trigger_characters + .unwrap_or_default() + .iter() + .map(|c| regex::escape(c)) + .collect(); + strings + }) + .unwrap_or_default(); + + self.vim()?.rpcclient.notify( + "ncm2#register_source", + json!([{ + "name": format!("LanguageClient_{}", language_id), + "priority": 9, + "scope": [language_id], + "complete_pattern": trigger_patterns, + "mark": "LC", + "on_complete": REQUEST_NCM2_ON_COMPLETE, + }]), + )?; + info!("End register NCM2 source"); + Ok(()) + } + + fn parse_semantic_scopes(&self, language_id: &str, result: &Value) -> Result<()> { + info!("Begin parse Semantic Scopes"); + let result: InitializeResult = serde_json::from_value(result.clone())?; + + if let Some(capability) = result.capabilities.semantic_highlighting { + self.update(|state| { + state + .semantic_scopes + .insert(language_id.into(), capability.scopes.unwrap_or_default()); + Ok(()) + })?; + } + + info!("End parse Semantic Scopes"); + Ok(()) + } + + /// Build the Semantic Highlight Lookup Table of + /// + /// ScopeIndex -> Option + fn update_semantic_highlight_tables(&self, language_id: &str) -> Result<()> { + info!("Begin updateSemanticHighlightTables"); + let (opt_scopes, opt_hl_map, scope_separator) = self.get(|state| { + ( + state.semantic_scopes.get(language_id).cloned(), + state.semantic_highlight_maps.get(language_id).cloned(), + state.semantic_scope_separator.clone(), + ) + })?; + + if let (Some(semantic_scopes), Some(shm)) = (opt_scopes, opt_hl_map) { + let mut table: Vec> = Vec::new(); + + for scope_list in semantic_scopes { + // Combine all scopes ["scopeA", "scopeB", ...] -> "scopeA:scopeB:..." + let scope_str = scope_list.iter().join(&scope_separator); + + let mut matched = false; + for (scope_regex, hl_group) in &shm { + let match_expr = format!( + "({} =~ {})", + convert_to_vim_str(&scope_str), + convert_to_vim_str(scope_regex) + ); + + let matches: i32 = self.vim()?.eval(match_expr)?; + + if matches == 1 { + table.push(Some(hl_group.clone())); + matched = true; + break; + } + } + + if !matched { + table.push(None); + } + } + + self.update(|state| { + state + .semantic_scope_to_hl_group_table + .insert(language_id.into(), table); + Ok(()) + })?; + } else { + self.update(|state| { + state.semantic_scope_to_hl_group_table.remove(language_id); + Ok(()) + })?; + } + info!("End updateSemanticHighlightTables"); + Ok(()) + } + + pub fn get_line(&self, path: impl AsRef, line: u64) -> Result { + let value = self.vim()?.rpcclient.call( + "getbufline", + json!([path.as_ref().to_string_lossy(), line + 1]), + )?; + let mut texts: Vec = serde_json::from_value(value)?; + let mut text = texts.pop().unwrap_or_default(); + + if text.is_empty() { + let reader = BufReader::new(File::open(path)?); + text = reader + .lines() + .nth(line.to_usize()?) + .ok_or_else(|| anyhow!("Failed to get line! line: {}", line))??; + } + + Ok(text.trim().into()) + } + + fn try_handle_command_by_client(&self, cmd: &Command) -> Result { + match cmd.command.as_str() { + java::command::APPLY_WORKSPACE_EDIT => self.handle_java_command(cmd), + rust_analyzer::command::RUN + | rust_analyzer::command::RUN_SINGLE + | rust_analyzer::command::SHOW_REFERENCES + | rust_analyzer::command::SELECT_APPLY_SOURCE_CHANGE + | rust_analyzer::command::APPLY_SOURCE_CHANGE => self.handle_rust_analyzer_command(cmd), + + _ => Ok(false), + } + } + + fn cleanup(&self, language_id: &str) -> Result<()> { + info!("Begin cleanup"); + + let root = self.get(|state| { + state + .roots + .get(language_id) + .cloned() + .ok_or_else(|| anyhow!("No project root found! languageId: {}", language_id)) + })??; + + let mut filenames = vec![]; + self.update(|state| { + for (f, diag_list) in state.diagnostics.iter_mut() { + if f.starts_with(&root) { + filenames.push(f.clone()); + diag_list.clear(); + } + } + Ok(()) + })?; + + for f in filenames { + if let Ok(bufnr) = self.vim()?.eval::<_, Bufnr>(format!("bufnr('{}')", f)) { + // Some Language Server diagnoses non-opened buffer, so we must check if buffer exists. + if bufnr > 0 { + self.vim()?.rpcclient.notify( + "setbufvar", + json!([f, VIM_STATUS_LINE_DIAGNOSTICS_COUNTS, {}]), + )?; + } + } + self.process_diagnostics(&f, &[])?; + } + self.handle_cursor_moved(&Value::Null)?; + + self.update(|state| { + state.clients.remove(&Some(language_id.into())); + state.last_cursor_line = 0; + state.text_documents.retain(|f, _| !f.starts_with(&root)); + state.roots.remove(language_id); + Ok(()) + })?; + self.update_quickfixlist()?; + + self.vim()?.command(vec![ + format!("let {}=0", VIM_SERVER_STATUS), + format!("let {}=''", VIM_SERVER_STATUS_MESSAGE), + ])?; + self.vim()? + .rpcclient + .notify("s:ExecuteAutocmd", "LanguageClientStopped")?; + + info!("End cleanup"); + Ok(()) + } + + fn preview(&self, to_display: &D) -> Result<()> + where + D: ToDisplay + ?Sized, + { + let bufname = "__LanguageClient__"; + let filetype = &to_display.vim_filetype(); + let lines = to_display.to_display(); + + self.vim()? + .rpcclient + .notify("s:OpenHoverPreview", json!([bufname, lines, filetype]))?; + + Ok(()) + } + + fn edit(&self, goto_cmd: &Option, path: impl AsRef) -> Result<()> { + let path = path.as_ref().to_string_lossy(); + if path.starts_with("jdt://") { + self.java_class_file_contents(&json!({ "gotoCmd": goto_cmd, "uri": path }))?; + Ok(()) + } else { + self.vim()?.edit(&goto_cmd, path.into_owned()) + } + } + + /////// LSP /////// + + fn initialize(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::Initialize::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let has_snippet_support: i8 = try_get("hasSnippetSupport", params)? + .map_or_else(|| self.vim()?.eval("s:hasSnippetSupport()"), Ok)?; + let has_snippet_support = has_snippet_support > 0; + let root = self.get(|state| state.roots.get(&language_id).cloned().unwrap_or_default())?; + + let initialization_options = self + .get_workspace_settings(&root) + .map(|s| s["initializationOptions"].clone()) + .unwrap_or_else(|err| { + warn!("Failed to get initializationOptions: {}", err); + json!(Value::Null) + }); + let initialization_options = + get_default_initialization_options(&language_id).combine(&initialization_options); + let initialization_options = if initialization_options.is_null() { + None + } else { + Some(initialization_options) + }; + + let trace = self.get(|state| state.trace)?; + let preferred_markup_kind = self.get(|state| state.preferred_markup_kind.clone())?; + + let result: Value = self.get_client(&Some(language_id.clone()))?.call( + lsp_types::request::Initialize::METHOD, + #[allow(deprecated)] + InitializeParams { + client_info: Some(ClientInfo { + name: "LanguageClient-neovim".into(), + version: Some(self.version.clone()), + }), + process_id: Some(u64::from(std::process::id())), + /* deprecated in lsp types, but can't initialize without it */ + root_path: Some(root.clone()), + root_uri: Some(root.to_url()?), + initialization_options, + capabilities: ClientCapabilities { + text_document: Some(TextDocumentClientCapabilities { + color_provider: Some(GenericCapability { + dynamic_registration: Some(false), + }), + completion: Some(CompletionCapability { + completion_item: Some(CompletionItemCapability { + snippet_support: Some(has_snippet_support), + documentation_format: preferred_markup_kind.clone(), + // note that if this value was to be changed to true, then + // additional changes around edits should be made, as it currently + // just panics if it encounters a completion item of type + // InsertAndReplace. + insert_replace_support: Some(false), + ..CompletionItemCapability::default() + }), + ..CompletionCapability::default() + }), + code_action: Some(CodeActionCapability { + code_action_literal_support: Some(CodeActionLiteralSupport { + code_action_kind: CodeActionKindLiteralSupport { + value_set: [ + code_action_kind::QUICKFIX, + code_action_kind::REFACTOR, + code_action_kind::REFACTOR_EXTRACT, + code_action_kind::REFACTOR_INLINE, + code_action_kind::REFACTOR_REWRITE, + code_action_kind::SOURCE, + code_action_kind::SOURCE_ORGANIZE_IMPORTS, + ] + .iter() + .map(|x| x.to_string()) + .collect(), + }, + }), + ..CodeActionCapability::default() + }), + signature_help: Some(SignatureHelpCapability { + signature_information: Some(SignatureInformationSettings { + documentation_format: preferred_markup_kind.clone(), + parameter_information: Some(ParameterInformationSettings { + label_offset_support: Some(true), + }), + }), + ..SignatureHelpCapability::default() + }), + declaration: Some(GotoCapability { + link_support: Some(true), + ..GotoCapability::default() + }), + definition: Some(GotoCapability { + link_support: Some(true), + ..GotoCapability::default() + }), + type_definition: Some(GotoCapability { + link_support: Some(true), + ..GotoCapability::default() + }), + implementation: Some(GotoCapability { + link_support: Some(true), + ..GotoCapability::default() + }), + publish_diagnostics: Some(PublishDiagnosticsCapability { + related_information: Some(true), + ..PublishDiagnosticsCapability::default() + }), + code_lens: Some(GenericCapability { + dynamic_registration: Some(true), + }), + semantic_highlighting_capabilities: Some( + SemanticHighlightingClientCapability { + semantic_highlighting: true, + }, + ), + hover: Some(HoverCapability { + content_format: preferred_markup_kind, + ..HoverCapability::default() + }), + ..TextDocumentClientCapabilities::default() + }), + workspace: Some(WorkspaceClientCapabilities { + apply_edit: Some(true), + did_change_watched_files: Some(GenericCapability { + dynamic_registration: Some(true), + }), + ..WorkspaceClientCapabilities::default() + }), + ..ClientCapabilities::default() + }, + trace, + workspace_folders: None, + }, + )?; + + self.update(|state| { + state + .capabilities + .insert(language_id.clone(), result.clone()); + Ok(()) + })?; + + info!("End {}", lsp_types::request::Initialize::METHOD); + + if let Err(e) = self.register_cm_source(&language_id, &result) { + let message = format!("LanguageClient: failed to register as NCM source: {}", e); + error!("{}\n{:?}", message, e); + self.vim()?.echoerr(&message)?; + } + if let Err(e) = self.register_ncm2_source(&language_id, &result) { + let message = format!("LanguageClient: failed to register as NCM source: {}", e); + error!("{}\n{:?}", message, e); + self.vim()?.echoerr(&message)?; + } + if let Err(e) = self.parse_semantic_scopes(&language_id, &result) { + let message = format!("LanguageClient: failed to parse semantic scopes: {}", e); + error!("{}\n{:?}", message, e); + self.vim()?.echoerr(&message)?; + } + + Ok(result) + } + + fn initialized(&self, params: &Value) -> Result<()> { + info!("Begin {}", lsp_types::notification::Initialized::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + self.update_semantic_highlight_tables(&language_id)?; + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::Initialized::METHOD, + InitializedParams {}, + )?; + info!("End {}", lsp_types::notification::Initialized::METHOD); + Ok(()) + } + + pub fn text_document_hover(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::HoverRequest::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let position = self.vim()?.get_position(params)?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::HoverRequest::METHOD, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let hover = Option::::deserialize(&result)?; + if let Some(hover) = hover { + let hover_preview = self.get(|state| state.hover_preview)?; + let use_preview = match hover_preview { + HoverPreviewOption::Always => true, + HoverPreviewOption::Never => false, + HoverPreviewOption::Auto => hover.lines_len() > 1, + }; + if use_preview { + self.preview(&hover)? + } else { + self.vim()?.echo_ellipsis(hover.to_string())? + } + } + + info!("End {}", lsp_types::request::HoverRequest::METHOD); + Ok(result) + } + + /// Generic find locations, e.g, definitions, references. + pub fn find_locations(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + let method: String = + try_get("method", params)?.ok_or_else(|| anyhow!("method not found in request!"))?; + info!("Begin {}", method); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let position = self.vim()?.get_position(params)?; + let current_word = self.vim()?.get_current_word(params)?; + let goto_cmd = self.vim()?.get_goto_cmd(params)?; + + let params = serde_json::to_value(TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + })? + .combine(params); + + let result = self + .get_client(&Some(language_id))? + .call(&method, ¶ms)?; + + if !self.vim()?.get_handle(¶ms)? { + return Ok(result); + } + + let response = Option::::deserialize(&result)?; + + let locations = match response { + None => vec![], + Some(GotoDefinitionResponse::Scalar(loc)) => vec![loc], + Some(GotoDefinitionResponse::Array(arr)) => arr, + Some(GotoDefinitionResponse::Link(links)) => links + .into_iter() + .map(|link| Location::new(link.target_uri, link.target_selection_range)) + .collect(), + }; + + match locations.len() { + 0 => self.vim()?.echowarn("Not found!")?, + 1 => { + let loc = locations.get(0).ok_or_else(|| anyhow!("Not found!"))?; + let path = loc.uri.filepath()?.to_string_lossy().into_owned(); + self.edit(&goto_cmd, path)?; + self.vim()? + .cursor(loc.range.start.line + 1, loc.range.start.character + 1)?; + let cur_file: String = self.vim()?.eval("expand('%')")?; + self.vim()?.echomsg_ellipsis(format!( + "{} {}:{}", + cur_file, + loc.range.start.line + 1, + loc.range.start.character + 1 + ))?; + } + _ => { + let title = format!("[LC]: search for {}", current_word); + self.present_list(&title, &locations)? + } + } + + info!("End {}", method); + Ok(result) + } + + pub fn text_document_rename(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::Rename::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let position = self.vim()?.get_position(params)?; + let current_word = self.vim()?.get_current_word(params)?; + let new_name: Option = try_get("newName", params)?; + + let mut new_name = new_name.unwrap_or_default(); + if new_name.is_empty() { + new_name = self + .vim()? + .rpcclient + .call("s:getInput", ["Rename to: ", ¤t_word])?; + } + if new_name.is_empty() { + return Ok(Value::Null); + } + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::Rename::METHOD, + RenameParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + }, + new_name, + work_done_progress_params: WorkDoneProgressParams::default(), + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + if result == Value::Null { + return Ok(result); + } + + let edit: WorkspaceEdit = serde_json::from_value(result.clone())?; + self.apply_workspace_edit(&edit)?; + + info!("End {}", lsp_types::request::Rename::METHOD); + Ok(result) + } + + pub fn text_document_document_symbol(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!( + "Begin {}", + lsp_types::request::DocumentSymbolRequest::METHOD + ); + + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::DocumentSymbolRequest::METHOD, + DocumentSymbolParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let syms = ::Result::deserialize(&result)?; + let title = format!("[LC]: symbols for {}", filename); + + match syms { + Some(lsp_types::DocumentSymbolResponse::Flat(flat)) => { + self.present_list(&title, &flat)?; + } + Some(lsp_types::DocumentSymbolResponse::Nested(nested)) => { + let mut symbols = Vec::new(); + + fn walk_document_symbol( + buffer: &mut Vec, + parent: Option<&lsp_types::DocumentSymbol>, + ds: &lsp_types::DocumentSymbol, + ) { + let name = if let Some(parent) = parent { + format!("{}::{}", parent.name, ds.name) + } else { + ds.name.clone() + }; + + buffer.push(lsp_types::DocumentSymbol { name, ..ds.clone() }); + + if let Some(children) = &ds.children { + for child in children { + walk_document_symbol(buffer, Some(&ds), child); + } + } + } + + for ds in &nested { + walk_document_symbol(&mut symbols, None, ds); + } + + self.present_list(&title, &symbols)?; + } + _ => (), + }; + + info!("End {}", lsp_types::request::DocumentSymbolRequest::METHOD); + Ok(result) + } + + pub fn text_document_code_action(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::CodeActionRequest::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let range: Range = serde_json::from_value(params["range"].clone())?; + + // Unify filename. + let filename = filename.canonicalize(); + + let diagnostics: Vec<_> = self.get(|state| { + state + .diagnostics + .get(&filename) + .unwrap_or(&vec![]) + .iter() + .filter(|dn| range.start >= dn.range.start && range.start < dn.range.end) + .cloned() + .collect() + })?; + + let result: Value = self.get_client(&Some(language_id))?.call( + lsp_types::request::CodeActionRequest::METHOD, + CodeActionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + range, + context: CodeActionContext { + diagnostics, + only: None, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }, + )?; + + let response = >::deserialize(&result)?; + let response = response.unwrap_or_default(); + + // Convert any Commands into CodeActions, so that the remainder of the handling can be + // shared. + let actions: Vec<_> = response + .into_iter() + .map(|action_or_command| match action_or_command { + CodeActionOrCommand::Command(command) => CodeAction { + title: command.title.clone(), + kind: Some(command.command.clone()), + diagnostics: None, + edit: None, + command: Some(command), + ..CodeAction::default() + }, + CodeActionOrCommand::CodeAction(action) => action, + }) + .collect(); + + self.update(|state| { + state.stashed_code_action_actions = actions.clone(); + Ok(()) + })?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + self.present_actions("Code Actions", &actions, |idx| -> Result<()> { + self.handle_code_action_selection(&actions, idx) + })?; + + info!("End {}", lsp_types::request::CodeActionRequest::METHOD); + Ok(result) + } + + fn handle_code_action_selection(&self, actions: &[CodeAction], idx: usize) -> Result<()> { + match actions.get(idx) { + Some(action) => { + // Apply edit before command. + if let Some(edit) = &action.edit { + self.apply_workspace_edit(edit)?; + } + + if let Some(command) = &action.command { + if !self.try_handle_command_by_client(&command)? { + let params = json!({ + "command": command.command, + "arguments": command.arguments, + }); + self.workspace_execute_command(¶ms)?; + } + } + + self.update(|state| { + state.stashed_code_action_actions = vec![]; + Ok(()) + })?; + } + None => return Err(anyhow!("Code action not stashed, please try again")), + }; + + Ok(()) + } + + pub fn text_document_completion(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::Completion::METHOD); + + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let position = self.vim()?.get_position(params)?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::Completion::METHOD, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + info!("End {}", lsp_types::request::Completion::METHOD); + Ok(result) + } + + pub fn text_document_signature_help(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::SignatureHelpRequest::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let position = self.vim()?.get_position(params)?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::SignatureHelpRequest::METHOD, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + position, + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + if result == Value::Null { + return Ok(result); + } + + let help: SignatureHelp = serde_json::from_value(result)?; + if help.signatures.is_empty() { + return Ok(Value::Null); + } + + // active_signature may be negative value. + // So if it is negative value, we convert it into zero. + let active_signature_index = help.active_signature.unwrap_or(0).max(0) as usize; + + let active_signature = help + .signatures + .get(active_signature_index) + .ok_or_else(|| anyhow!("Failed to get active signature"))?; + + // active_signature may be negative value. + // So if it is negative value, we convert it into zero. + let active_parameter_index = help.active_parameter.unwrap_or(0).max(0) as usize; + + let active_parameter: Option<&ParameterInformation>; + if let Some(ref parameters) = active_signature.parameters { + active_parameter = parameters.get(active_parameter_index); + } else { + active_parameter = None; + } + + if let Some((begin, label, end)) = active_parameter.and_then(|active_parameter| { + decode_parameter_label(&active_parameter.label, &active_signature.label).ok() + }) { + let cmd = format!( + "echo | echon '{}' | echohl WarningMsg | echon '{}' | echohl None | echon '{}'", + begin, label, end + ); + self.vim()?.command(&cmd)?; + } else { + self.vim()?.echo(&active_signature.label)?; + } + + info!("End {}", lsp_types::request::SignatureHelpRequest::METHOD); + Ok(Value::Null) + } + + pub fn text_document_references(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::References::METHOD); + + let include_declaration: bool = try_get("includeDeclaration", params)?.unwrap_or(true); + // TODO: cleanup. + let params = json!({ + "method": lsp_types::request::References::METHOD, + "context": ReferenceContext { + include_declaration, + } + }) + .combine(params); + let result = self.find_locations(¶ms)?; + + info!("End {}", lsp_types::request::References::METHOD); + Ok(result) + } + + pub fn text_document_formatting(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::Formatting::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let tab_size = self.vim()?.get_tab_size()?; + let insert_spaces = self.vim()?.get_insert_spaces(&filename)?; + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::Formatting::METHOD, + DocumentFormattingParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + options: FormattingOptions { + tab_size, + insert_spaces, + properties: HashMap::new(), + ..FormattingOptions::default() + }, + work_done_progress_params: WorkDoneProgressParams::default(), + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let text_edits: Option> = serde_json::from_value(result.clone())?; + let text_edits = text_edits.unwrap_or_default(); + let edit = lsp_types::WorkspaceEdit { + changes: Some(hashmap! {filename.to_url()? => text_edits}), + document_changes: None, + }; + self.apply_workspace_edit(&edit)?; + info!("End {}", lsp_types::request::Formatting::METHOD); + Ok(result) + } + + pub fn text_document_range_formatting(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::RangeFormatting::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let start_line = try_get("range_start_line", params)? + .map_or_else(|| self.vim()?.eval("LSP#range_start_line()"), Ok)?; + let end_line = try_get("range_end_line", params)? + .map_or_else(|| self.vim()?.eval("LSP#range_end_line()"), Ok)?; + + let tab_size = self.vim()?.get_tab_size()?; + let insert_spaces = self.vim()?.get_insert_spaces(&filename)?; + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::RangeFormatting::METHOD, + DocumentRangeFormattingParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + options: FormattingOptions { + tab_size, + insert_spaces, + properties: HashMap::new(), + ..FormattingOptions::default() + }, + range: Range { + start: Position { + line: start_line, + character: 0, + }, + end: Position { + line: end_line, + character: 0, + }, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let text_edits: Option> = serde_json::from_value(result.clone())?; + let text_edits = text_edits.unwrap_or_default(); + let edit = lsp_types::WorkspaceEdit { + changes: Some(hashmap! {filename.to_url()? => text_edits}), + document_changes: None, + }; + self.apply_workspace_edit(&edit)?; + info!("End {}", lsp_types::request::RangeFormatting::METHOD); + Ok(result) + } + + pub fn completion_item_resolve(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!( + "Begin {}", + lsp_types::request::ResolveCompletionItem::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let completion_item: CompletionItem = try_get("completionItem", params)? + .ok_or_else(|| anyhow!("completionItem not found in request!"))?; + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::ResolveCompletionItem::METHOD, + completion_item, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + // TODO: proper integration. + let msg = format!("completionItem/resolve result not handled: {:?}", result); + warn!("{}", msg); + self.vim()?.echowarn(&msg)?; + + info!("End {}", lsp_types::request::ResolveCompletionItem::METHOD); + Ok(Value::Null) + } + + // shows a list of actions for the user to choose one. + fn present_actions(&self, title: &str, actions: &[T], callback: F) -> Result<()> + where + T: ListItem, + F: Fn(usize) -> Result<()>, + { + if actions.is_empty() { + return Err(anyhow!("No code actions found at point")); + } + + let cwd: String = self.vim()?.eval("getcwd()")?; + let actions: Result> = actions + .iter() + .map(|it| ListItem::string_item(it, self, &cwd)) + .collect(); + + match self.get(|state| state.selection_ui)? { + SelectionUI::Funcref => { + self.vim()?.rpcclient.notify( + "s:selectionUI_funcref", + json!([actions?, NOTIFICATION_FZF_SINK_COMMAND]), + )?; + } + SelectionUI::Quickfix | SelectionUI::LocationList => { + let mut actions: Vec = actions? + .iter_mut() + .enumerate() + .map(|(idx, it)| format!("{}) {}", idx + 1, it)) + .collect(); + let mut options = vec![title.to_string()]; + options.append(&mut actions); + + let index: Option = self.vim()?.rpcclient.call("s:inputlist", options)?; + if let Some(index) = index { + return callback(index - 1); + } + } + } + + Ok(()) + } + + // shows a list of items, used for things like diagnostics or things that do not need a user + // selection. + pub fn present_list(&self, title: &str, items: &[T]) -> Result<()> + where + T: ListItem, + { + let selection_ui = self.get(|state| state.selection_ui)?; + let selection_ui_auto_open = self.get(|state| state.selection_ui_auto_open)?; + + match selection_ui { + SelectionUI::Funcref => { + let cwd: String = self.vim()?.eval("getcwd()")?; + let source: Result> = items + .iter() + .map(|it| ListItem::string_item(it, self, &cwd)) + .collect(); + let source = source?; + + self.vim()?.rpcclient.notify( + "s:selectionUI_funcref", + json!([source, format!("s:{}", NOTIFICATION_FZF_SINK_LOCATION)]), + )?; + } + SelectionUI::Quickfix => { + let list: Result> = items + .iter() + .map(|it| ListItem::quickfix_item(it, self)) + .collect(); + let list = list?; + self.vim()?.setqflist(&list, " ", title)?; + if selection_ui_auto_open { + self.vim()?.command("botright copen")?; + } + self.vim()?.echo("Populated quickfix list.")?; + } + SelectionUI::LocationList => { + let list: Result> = items + .iter() + .map(|it| ListItem::quickfix_item(it, self)) + .collect(); + let list = list?; + self.vim()?.setloclist(&list, " ", title)?; + if selection_ui_auto_open { + self.vim()?.command("lopen")?; + } + self.vim()?.echo("Populated location list.")?; + } + } + + Ok(()) + } + + pub fn workspace_symbol(&self, params: &Value) -> Result { + self.text_document_did_change(params)?; + info!("Begin {}", lsp_types::request::WorkspaceSymbol::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let query = try_get("query", params)?.unwrap_or_default(); + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::WorkspaceSymbol::METHOD, + WorkspaceSymbolParams { + query, + partial_result_params: PartialResultParams::default(), + work_done_progress_params: WorkDoneProgressParams::default(), + }, + )?; + + if !self.vim()?.get_handle(params)? { + return Ok(result); + } + + let symbols = >::deserialize(&result)?; + let title = "[LC]: workspace symbols"; + + self.present_list(title, &symbols)?; + + info!("End {}", lsp_types::request::WorkspaceSymbol::METHOD); + Ok(result) + } + + pub fn workspace_execute_command(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::ExecuteCommand::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let command: String = + try_get("command", params)?.ok_or_else(|| anyhow!("command not found in request!"))?; + let arguments: Vec = try_get("arguments", params)?.unwrap_or_default(); + + let result = self.get_client(&Some(language_id))?.call( + lsp_types::request::ExecuteCommand::METHOD, + ExecuteCommandParams { + command, + arguments, + work_done_progress_params: WorkDoneProgressParams::default(), + }, + )?; + info!("End {}", lsp_types::request::ExecuteCommand::METHOD); + Ok(result) + } + + pub fn workspace_apply_edit(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::ApplyWorkspaceEdit::METHOD); + + let params = ApplyWorkspaceEditParams::deserialize(params)?; + self.apply_workspace_edit(¶ms.edit)?; + + info!("End {}", lsp_types::request::ApplyWorkspaceEdit::METHOD); + + Ok(serde_json::to_value(ApplyWorkspaceEditResponse { + applied: true, + })?) + } + + pub fn workspace_did_change_configuration(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidChangeConfiguration::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let settings: Value = try_get("settings", params)?.unwrap_or_default(); + + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::DidChangeConfiguration::METHOD, + DidChangeConfigurationParams { settings }, + )?; + info!( + "End {}", + lsp_types::notification::DidChangeConfiguration::METHOD + ); + Ok(()) + } + + pub fn handle_code_lens_action(&self, params: &Value) -> Result { + let filename = self.vim()?.get_filename(params)?; + let line = self.vim()?.get_position(params)?.line; + + let code_lens: Vec = self.get(|state| { + state + .code_lens + .get(&filename) + .cloned() + .unwrap_or_else(Vec::new) + .into_iter() + .filter(|action| action.range.start.line == line) + .collect() + })?; + if code_lens.is_empty() { + warn!("No actions associated with this codeLens"); + return Ok(Value::Null); + } + + let actions: Result> = code_lens + .iter() + .map(|cl| match &cl.command { + None => Err(anyhow!("no command, skipping")), + Some(cmd) => Ok(CodeAction { + kind: Some(cmd.command.clone()), + title: cmd.title.clone(), + command: cl.clone().command, + diagnostics: None, + edit: None, + is_preferred: None, + }), + }) + .filter(Result::is_ok) + .collect(); + let actions = actions?; + + self.update(|state| { + state.stashed_code_action_actions = actions.clone(); + Ok(()) + })?; + + let source: Result> = actions + .iter() + .map(|it| match &it.command { + None => Err(anyhow!("expected a command, found none")), + Some(cmd) => Ok(cmd.clone()), + }) + .collect(); + // every item in `actions` should have a command, as we filtered the ones that didn't have + // one before. If we happen to encounter one that does not have a command, we just error, + // as this is unexpected behaviour and could potentially lead to triggering the incorrect + // code action, as the index may be incorrect. + let source = source?; + + self.present_actions("Code Lens Actions", &source, |idx| -> Result<()> { + self.handle_code_action_selection(&actions, idx) + })?; + + Ok(Value::Null) + } + + pub fn progress(&self, params: &Value) -> Result<()> { + let params = ProgressParams::deserialize(params)?; + let message = match params.value { + ProgressParamsValue::WorkDone(wd) => match wd { + WorkDoneProgress::Begin(r) => { + Some(format!("{} {}", r.title, r.message.unwrap_or_default())) + } + WorkDoneProgress::Report(r) => r.message, + // WorkDoneProgress::End has no value, so we return Done, otherwise the previous + // message would be left in screen and it would appear as if it didn't ever finish. + WorkDoneProgress::End(_) => Some("Done".into()), + }, + }; + + if message.is_none() { + return Ok(()); + } + + let token = match params.token { + // number is a not a particularly useful token to report to the user, so we just use + // INFO instead. + NumberOrString::Number(_) => "INFO".to_string(), + NumberOrString::String(s) => s, + }; + + let message = format!("{}: {}", token, message.unwrap_or_default()); + self.vim()?.echomsg(&message)?; + Ok(()) + } + + pub fn text_document_code_lens(&self, params: &Value) -> Result { + let use_virtual_text = self.get(|state| state.use_virtual_text.clone())?; + if UseVirtualText::No == use_virtual_text || UseVirtualText::Diagnostics == use_virtual_text + { + return Ok(Value::Null); + } + + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let capabilities = self.get(|state| state.capabilities.clone())?; + if let Some(initialize_result) = capabilities.get(&language_id) { + // XXX: the capabilities state field stores the initialize result, not the capabilities + // themselves, so we need to deserialize to InitializeResult. + let initialize_result: InitializeResult = + serde_json::from_value(initialize_result.clone())?; + let capabilities = initialize_result.capabilities; + + if let Some(code_lens_provider) = capabilities.code_lens_provider { + info!("Begin {}", lsp_types::request::CodeLensRequest::METHOD); + let client = self.get_client(&Some(language_id))?; + let input = lsp_types::CodeLensParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + + let results: Value = + client.call(lsp_types::request::CodeLensRequest::METHOD, &input)?; + let code_lens: Option> = serde_json::from_value(results)?; + let mut code_lens: Vec = code_lens.unwrap_or_default(); + + if code_lens_provider.resolve_provider.unwrap_or_default() { + code_lens = code_lens + .into_iter() + .map(|cl| { + if cl.data.is_none() { + return cl; + } + + client + .call(lsp_types::request::CodeLensResolve::METHOD, &cl) + .unwrap_or(cl) + }) + .collect(); + } + + self.update(|state| { + state.code_lens.insert(filename.to_owned(), code_lens); + Ok(Value::Null) + })?; + + info!("End {}", lsp_types::request::CodeLensRequest::METHOD); + } else { + info!( + "CodeLens not supported. Skipping {}", + lsp_types::request::CodeLensRequest::METHOD + ); + } + } + + self.draw_virtual_texts(¶ms)?; + + Ok(Value::Null) + } + + pub fn text_document_did_open(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidOpenTextDocument::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let text = self.vim()?.get_text(&filename)?; + + let text_document = TextDocumentItem { + uri: filename.to_url()?, + language_id: language_id.clone(), + version: 0, + text: text.join("\n"), + }; + + self.update(|state| { + Ok(state + .text_documents + .insert(filename.clone(), text_document.clone())) + })?; + + self.get_client(&Some(language_id.clone()))?.notify( + lsp_types::notification::DidOpenTextDocument::METHOD, + DidOpenTextDocumentParams { text_document }, + )?; + + self.vim()? + .command("setlocal omnifunc=LanguageClient#complete")?; + let root = self.get(|state| state.roots.get(&language_id).cloned().unwrap_or_default())?; + self.vim()?.rpcclient.notify( + "setbufvar", + json!([filename, "LanguageClient_projectRoot", root]), + )?; + self.vim()? + .rpcclient + .notify("s:ExecuteAutocmd", "LanguageClientTextDocumentDidOpenPost")?; + + self.text_document_code_lens(params)?; + + info!( + "End {}", + lsp_types::notification::DidOpenTextDocument::METHOD + ); + Ok(()) + } + + pub fn text_document_did_change(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidChangeTextDocument::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + if !self.get(|state| state.text_documents.contains_key(&filename))? { + info!("Not opened yet. Switching to didOpen."); + return self.text_document_did_open(params); + } + + let text = self.vim()?.get_text(&filename)?.join("\n"); + let text_state = self.get(|state| { + state + .text_documents + .get(&filename) + .map(|d| d.text.clone()) + .unwrap_or_default() + })?; + if text == text_state { + info!("Texts equal. Skipping didChange."); + return Ok(()); + } + + let version = self.update(|state| { + let document = state + .text_documents + .get_mut(&filename) + .ok_or_else(|| anyhow!("Failed to get TextDocumentItem! filename: {}", filename))?; + + let version = document.version + 1; + document.version = version; + document.text = text.clone(); + + if state.change_throttle.is_some() { + let metadata = state + .text_documents_metadata + .entry(filename.clone()) + .or_insert_with(TextDocumentItemMetadata::default); + metadata.last_change = Instant::now(); + } + Ok(version) + })?; + + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::DidChangeTextDocument::METHOD, + DidChangeTextDocumentParams { + text_document: VersionedTextDocumentIdentifier { + uri: filename.to_url()?, + version: Some(version), + }, + content_changes: vec![TextDocumentContentChangeEvent { + range: None, + range_length: None, + text, + }], + }, + )?; + + self.text_document_code_lens(params)?; + + info!( + "End {}", + lsp_types::notification::DidChangeTextDocument::METHOD + ); + Ok(()) + } + + pub fn text_document_did_save(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidSaveTextDocument::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + if !self.get(|state| state.server_commands.contains_key(&language_id))? { + return Ok(()); + } + + let uri = filename.to_url()?; + + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::DidSaveTextDocument::METHOD, + DidSaveTextDocumentParams { + text_document: TextDocumentIdentifier { uri }, + }, + )?; + + self.draw_virtual_texts(params)?; + + info!( + "End {}", + lsp_types::notification::DidSaveTextDocument::METHOD + ); + Ok(()) + } + + pub fn text_document_did_close(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidCloseTextDocument::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::DidCloseTextDocument::METHOD, + DidCloseTextDocumentParams { + text_document: TextDocumentIdentifier { + uri: filename.to_url()?, + }, + }, + )?; + info!( + "End {}", + lsp_types::notification::DidCloseTextDocument::METHOD + ); + Ok(()) + } + + pub fn text_document_publish_diagnostics(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::PublishDiagnostics::METHOD + ); + let params = PublishDiagnosticsParams::deserialize(params)?; + if !self.get(|state| state.diagnostics_enable)? { + return Ok(()); + } + + let mut filename = params.uri.filepath()?.to_string_lossy().into_owned(); + // Workaround bug: remove first '/' in case of '/C:/blabla'. + if filename.starts_with('/') && filename.chars().nth(2) == Some(':') { + filename.remove(0); + } + // Unify name to avoid mismatch due to case insensitivity. + let filename = filename.canonicalize(); + + let diagnostics_max_severity = self.get(|state| state.diagnostics_max_severity)?; + let mut diagnostics = params + .diagnostics + .iter() + .filter(|&diagnostic| { + diagnostic.severity.unwrap_or(DiagnosticSeverity::Hint) <= diagnostics_max_severity + }) + .map(Clone::clone) + .collect::>(); + + self.update(|state| { + state + .diagnostics + .insert(filename.clone(), diagnostics.clone()); + Ok(()) + })?; + self.update_quickfixlist()?; + + let mut severity_count: HashMap = [ + ( + DiagnosticSeverity::Error + .to_quickfix_entry_type() + .to_string(), + 0, + ), + ( + DiagnosticSeverity::Warning + .to_quickfix_entry_type() + .to_string(), + 0, + ), + ( + DiagnosticSeverity::Information + .to_quickfix_entry_type() + .to_string(), + 0, + ), + ( + DiagnosticSeverity::Hint + .to_quickfix_entry_type() + .to_string(), + 0, + ), + ] + .iter() + .cloned() + .collect(); + + for diagnostic in diagnostics.iter() { + let severity = diagnostic + .severity + .unwrap_or(DiagnosticSeverity::Hint) + .to_quickfix_entry_type() + .to_string(); + let count = severity_count.entry(severity).or_insert(0); + *count += 1; + } + + if let Ok(bufnr) = self + .vim()? + .eval::<_, Bufnr>(format!("bufnr('{}')", filename)) + { + // Some Language Server diagnoses non-opened buffer, so we must check if buffer exists. + if bufnr > 0 { + self.vim()?.rpcclient.notify( + "setbufvar", + json!([filename, VIM_STATUS_LINE_DIAGNOSTICS_COUNTS, severity_count]), + )?; + } + } + + let current_filename: String = self.vim()?.get_filename(&Value::Null)?; + if filename != current_filename.canonicalize() { + return Ok(()); + } + + // Sort diagnostics as pre-process for display. + // First sort by line. + // Then severity descending. Error should come last since when processing item comes + // later will override its precedence. + // Then by character descending. + diagnostics.sort_by_key(|diagnostic| { + ( + diagnostic.range.start.line, + -(diagnostic.severity.unwrap_or(DiagnosticSeverity::Hint) as i8), + -(diagnostic.range.start.line as i64), + ) + }); + + self.process_diagnostics(¤t_filename, &diagnostics)?; + self.handle_cursor_moved(&Value::Null)?; + self.vim()? + .rpcclient + .notify("s:ExecuteAutocmd", "LanguageClientDiagnosticsChanged")?; + + info!( + "End {}", + lsp_types::notification::PublishDiagnostics::METHOD + ); + Ok(()) + } + + pub fn text_document_semantic_highlight(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::SemanticHighlighting::METHOD + ); + let mut params = SemanticHighlightingParams::deserialize(params)?; + + // TODO: Do we need to handle the versioning of the file? + let mut filename = params + .text_document + .uri + .filepath()? + .to_string_lossy() + .into_owned(); + // Workaround bug: remove first '/' in case of '/C:/blabla'. + if filename.starts_with('/') && filename.chars().nth(2) == Some(':') { + filename.remove(0); + } + // Unify name to avoid mismatch due to case insensitivity. + let filename = filename.canonicalize(); + let language_id = self.vim()?.get_language_id(&filename, &Value::Null)?; + + let opt_hl_table = self.get(|state| { + state + .semantic_scope_to_hl_group_table + .get(&language_id) + .cloned() + })?; + + // Sort lines in ascending order + params.lines.sort_by(|a, b| a.line.cmp(&b.line)); + + // Remove obviously invalid values + while let Some(line_info) = params.lines.first() { + if line_info.line >= 0 { + break; + } else { + warn!( + "Invalid Semantic Highlight Line: {}", + params.lines.remove(0).line + ); + } + } + + let semantic_hl_state = TextDocumentSemanticHighlightState { + last_version: params.text_document.version, + symbols: params.lines, + highlights: None, + }; + + if let Some(hl_table) = opt_hl_table { + let ns_id = self.get_or_create_namespace(&LCNamespace::SemanticHighlight)?; + + let buffer = self.vim()?.get_bufnr(&filename, &Value::Null)?; + + if buffer == -1 { + error!( + "Received Semantic Highlighting for non-open buffer: {}", + filename + ); + return Ok(()); + } + + /* + * Currently servers update entire regions of text at a time or a + * single line so simply clear between the first and last line to + * ensure no highlights are left dangling + */ + let mut clear_region: Option<(u64, u64)> = None; + let mut highlights = Vec::with_capacity(semantic_hl_state.symbols.len()); + + for line in &semantic_hl_state.symbols { + if let Some(tokens) = &line.tokens { + for token in tokens { + if token.length == 0 { + continue; + } + + if let Some(Some(group)) = hl_table.get(token.scope as usize) { + highlights.push(Highlight { + line: line.line as u64, + character_start: token.character as u64, + character_end: token.character as u64 + token.length as u64, + group: group.clone(), + text: String::new(), + }); + } + } + + match clear_region { + Some((begin, _)) => { + clear_region = Some((begin, line.line as u64 + 1)); + } + None => { + clear_region = Some((line.line as u64, line.line as u64 + 1)); + } + } + } + } + + info!( + "Semantic Highlighting Region [{}, {}]:", + semantic_hl_state + .symbols + .first() + .map_or(-1, |h| h.line as i64), + semantic_hl_state + .symbols + .last() + .map_or(-1, |h| h.line as i64) + ); + + info!( + "Semantic Highlighting Region (Parsed) [{}, {}]:", + highlights.first().map_or(-1, |h| h.line as i64), + highlights.last().map_or(-1, |h| h.line as i64) + ); + + let mut clears = Vec::new(); + if let Some((begin, end)) = clear_region { + clears.push(ClearNamespace { + line_start: begin, + line_end: end, + }); + } + + let mut num_semantic_hls = 0; + let num_new_semantic_hls = highlights.len(); + + self.update(|state| { + state.vim.rpcclient.notify( + "s:ApplySemanticHighlights", + json!([buffer, ns_id, clears, highlights]), + )?; + + let old_semantic_hl_state = state + .semantic_highlights + .insert(language_id.clone(), semantic_hl_state); + + let semantic_hl_state = state.semantic_highlights.get_mut(&language_id).unwrap(); + + let mut combined_hls = Vec::with_capacity(highlights.len()); + + let mut existing_hls = old_semantic_hl_state + .map_or(Vec::new(), |hl_state| { + hl_state.highlights.unwrap_or_default() + }) + .into_iter() + .peekable(); + + let mut new_hls = highlights.into_iter().peekable(); + + // Incrementally update the highlighting + loop { + match (existing_hls.peek(), new_hls.peek()) { + (Some(existing_hl), Some(new_hl)) => { + use std::cmp::Ordering; + + match existing_hl.line.cmp(&new_hl.line) { + Ordering::Less => { + if clear_region.unwrap_or((0, 0)).0 <= existing_hl.line + && existing_hl.line < clear_region.unwrap_or((0, 0)).1 + { + // within clear region, this highlight gets cleared + existing_hls.next(); + } else { + combined_hls + .push(existing_hls.next().expect("unreachable")); + } + } + Ordering::Greater => { + combined_hls.push(new_hls.next().expect("unreachable")); + } + Ordering::Equal => { + // existing highlight on same line as new, it gets cleared + existing_hls.next(); + } + } + } + (Some(_), None) => { + combined_hls.push(existing_hls.next().expect("unreachable")); + } + (None, Some(_)) => { + combined_hls.push(new_hls.next().expect("unreachable")); + } + (None, None) => { + break; + } + } + } + + num_semantic_hls = combined_hls.len(); + + semantic_hl_state.highlights = Some(combined_hls); + + Ok(()) + })?; + + info!( + "Applied Semantic Highlighting for {} Symbols ({} new)", + num_semantic_hls, num_new_semantic_hls + ) + } else { + self.update(|state| { + state + .semantic_highlights + .insert(language_id.clone(), semantic_hl_state); + Ok(()) + })?; + } + + info!( + "End {}", + lsp_types::notification::SemanticHighlighting::METHOD + ); + Ok(()) + } + + // logs a message to with the specified level to the log file if the threshold is below the + // message's level. + pub fn window_log_message(&self, params: &Value) -> Result<()> { + info!("Begin {}", lsp_types::notification::LogMessage::METHOD); + let params = LogMessageParams::deserialize(params)?; + let threshold = self.get(|state| state.window_log_message_level)?; + if params.typ.to_int()? > threshold.to_int()? { + return Ok(()); + } + + match threshold { + MessageType::Error => error!("{}", params.message), + MessageType::Warning => warn!("{}", params.message), + MessageType::Info => info!("{}", params.message), + MessageType::Log => debug!("{}", params.message), + }; + + info!("End {}", lsp_types::notification::LogMessage::METHOD); + Ok(()) + } + + // shows the given message in vim. + pub fn window_show_message(&self, params: &Value) -> Result<()> { + info!("Begin {}", lsp_types::notification::ShowMessage::METHOD); + let params = ShowMessageParams::deserialize(params)?; + let msg = format!("[{:?}] {}", params.typ, params.message); + self.vim()?.echomsg(&msg)?; + info!("End {}", lsp_types::notification::ShowMessage::METHOD); + Ok(()) + } + + // TODO: change this to use the show_acions method + pub fn window_show_message_request(&self, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::ShowMessageRequest::METHOD); + let mut v = Value::Null; + let msg_params = ShowMessageRequestParams::deserialize(params)?; + let msg = format!("[{:?}] {}", msg_params.typ, msg_params.message); + let msg_actions = msg_params.actions.unwrap_or_default(); + if msg_actions.is_empty() { + self.vim()?.echomsg(&msg)?; + } else { + let mut options = Vec::with_capacity(msg_actions.len() + 1); + options.push(msg); + options.extend( + msg_actions + .iter() + .enumerate() + .map(|(i, item)| format!("{}) {}", i + 1, item.title)), + ); + + let index: Option = self.vim()?.rpcclient.call("s:inputlist", options)?; + if let Some(index) = index { + v = serde_json::to_value(msg_actions.get(index - 1))?; + } + } + + info!("End {}", lsp_types::request::ShowMessageRequest::METHOD); + Ok(v) + } + + pub fn client_register_capability(&self, language_id: &str, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::RegisterCapability::METHOD); + let params = RegistrationParams::deserialize(params)?; + for r in ¶ms.registrations { + match r.method.as_str() { + lsp_types::notification::DidChangeWatchedFiles::METHOD => { + let opt: DidChangeWatchedFilesRegistrationOptions = + serde_json::from_value(r.register_options.clone().unwrap_or_default())?; + if !self.get(|state| state.watchers.contains_key(language_id))? { + let (watcher_tx, watcher_rx) = mpsc::channel(); + // TODO: configurable duration. + let watcher = notify::watcher(watcher_tx, Duration::from_secs(2))?; + self.update(|state| { + state.watchers.insert(language_id.to_owned(), watcher); + state.watcher_rxs.insert(language_id.to_owned(), watcher_rx); + Ok(()) + })?; + } + + self.update(|state| { + if let Some(ref mut watcher) = state.watchers.get_mut(language_id) { + for w in &opt.watchers { + let recursive_mode = if w.glob_pattern.ends_with("**") { + notify::RecursiveMode::Recursive + } else { + notify::RecursiveMode::NonRecursive + }; + watcher + .watch(w.glob_pattern.trim_end_matches("**"), recursive_mode)?; + } + } + Ok(()) + })?; + } + _ => { + warn!("Unknown registration: {:?}", r); + } + } + } + + self.update(|state| { + state.registrations.extend(params.registrations); + Ok(()) + })?; + info!("End {}", lsp_types::request::RegisterCapability::METHOD); + Ok(Value::Null) + } + + pub fn client_unregister_capability(&self, language_id: &str, params: &Value) -> Result { + info!("Begin {}", lsp_types::request::UnregisterCapability::METHOD); + let params = UnregistrationParams::deserialize(params)?; + let mut regs_removed = vec![]; + for r in ¶ms.unregisterations { + if let Some(idx) = self.get(|state| { + state + .registrations + .iter() + .position(|i| i.id == r.id && i.method == r.method) + })? { + regs_removed.push(self.update(|state| Ok(state.registrations.swap_remove(idx)))?); + } + } + + for r in ®s_removed { + match r.method.as_str() { + lsp_types::notification::DidChangeWatchedFiles::METHOD => { + let opt: DidChangeWatchedFilesRegistrationOptions = + serde_json::from_value(r.register_options.clone().unwrap_or_default())?; + self.update(|state| { + if let Some(ref mut watcher) = state.watchers.get_mut(language_id) { + for w in opt.watchers { + watcher.unwatch(w.glob_pattern)?; + } + } + Ok(()) + })?; + } + _ => { + warn!("Unknown registration: {:?}", r); + } + } + } + + info!("End {}", lsp_types::request::UnregisterCapability::METHOD); + Ok(Value::Null) + } + + pub fn exit(&self, params: &Value) -> Result<()> { + info!("Begin {}", lsp_types::notification::Exit::METHOD); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let result = self + .get_client(&Some(language_id.clone()))? + .notify(lsp_types::notification::Exit::METHOD, Value::Null); + if let Err(err) = result { + error!("Error: {:?}", err); + } + + self.vim()? + .rpcclient + .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 0]))?; + + if let Err(err) = self.cleanup(&language_id) { + error!("Error: {:?}", err); + } + info!("End {}", lsp_types::notification::Exit::METHOD); + Ok(()) + } + + /////// Extensions by this plugin /////// + + pub fn get_state(&self, _params: &Value) -> Result { + info!("Begin {}", REQUEST_GET_STATE); + let s = self.get(|state| serde_json::to_string(state))??; + info!("End {}", REQUEST_GET_STATE); + Ok(Value::String(s)) + } + + pub fn is_alive(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_IS_ALIVE); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let is_alive = self.get(|state| state.clients.contains_key(&Some(language_id.clone())))?; + info!("End {}", REQUEST_IS_ALIVE); + Ok(Value::Bool(is_alive)) + } + + pub fn register_server_commands(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_REGISTER_SERVER_COMMANDS); + let commands = HashMap::>::deserialize(params)?; + self.update(|state| { + state.server_commands.extend(commands); + Ok(()) + })?; + let exp = format!( + "let g:LanguageClient_serverCommands={}", + serde_json::to_string(&self.get(|state| state.server_commands.clone())?)? + ); + self.vim()?.command(&exp)?; + info!("End {}", REQUEST_REGISTER_SERVER_COMMANDS); + Ok(Value::Null) + } + + pub fn set_logging_level(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_SET_LOGGING_LEVEL); + let logging_level = + try_get("loggingLevel", params)?.ok_or_else(|| anyhow!("loggingLevel not found!"))?; + self.update(|state| { + state.logger.set_level(logging_level)?; + Ok(()) + })?; + info!("End {}", REQUEST_SET_LOGGING_LEVEL); + Ok(Value::Null) + } + + pub fn set_diagnostics_list(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_SET_DIAGNOSTICS_LIST); + let diagnostics_list = try_get("diagnosticsList", params)? + .ok_or_else(|| anyhow!("diagnosticsList not found!"))?; + self.update(|state| { + state.diagnostics_list = diagnostics_list; + Ok(()) + })?; + info!("End {}", REQUEST_SET_DIAGNOSTICS_LIST); + Ok(Value::Null) + } + + pub fn register_handlers(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_REGISTER_HANDLERS); + let handlers: Result> = params + .as_object() + .ok_or_else(|| anyhow!("Invalid arguments!"))? + .iter() + .filter_map(|(k, v)| { + if *k == "bufnr" || *k == "languageId" { + return None; + } + + if let serde_json::Value::String(v) = v { + Some(Ok((k.clone(), v.clone()))) + } else { + None + } + }) + .collect(); + let handlers = handlers?; + self.update(|state| { + state.user_handlers.extend(handlers); + Ok(()) + })?; + info!("End {}", REQUEST_REGISTER_HANDLERS); + Ok(Value::Null) + } + + pub fn omnicomplete(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_OMNI_COMPLETE); + let result = self.text_document_completion(params)?; + let result: Option = serde_json::from_value(result)?; + let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); + let matches = match result { + CompletionResponse::Array(arr) => arr, + CompletionResponse::List(list) => list.items, + }; + + let complete_position: Option = try_get("complete_position", params)?; + + let matches: Result> = matches + .iter() + .map(|item| VimCompleteItem::from_lsp(item, complete_position)) + .collect(); + let matches = matches?; + info!("End {}", REQUEST_OMNI_COMPLETE); + Ok(serde_json::to_value(matches)?) + } + + pub fn handle_buf_new_file(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_BUF_NEW_FILE); + if self.vim()?.get_filename(params)?.is_empty() { + return Ok(()); + } + + let auto_start: u8 = self + .vim()? + .eval("!!get(g:, 'LanguageClient_autoStart', 1)")?; + if auto_start == 1 { + let ret = self.start_server(params); + // This is triggered from autocmd, silent all errors. + if let Err(err) = ret { + warn!("Failed to start language server automatically. {}", err); + } + } + + info!("End {}", NOTIFICATION_HANDLE_BUF_NEW_FILE); + Ok(()) + } + + pub fn handle_buf_enter(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_BUF_ENTER); + if self.vim()?.get_filename(params)?.is_empty() { + return Ok(()); + } + + let filename = self.vim()?.get_filename(params)?.canonicalize(); + let language_id = self.vim()?.get_language_id(&filename, params)?; + + if self.get(|state| state.clients.contains_key(&Some(language_id.clone())))? { + self.vim()? + .rpcclient + .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 1]))?; + } else { + self.vim()? + .rpcclient + .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 0]))?; + } + info!("End {}", NOTIFICATION_HANDLE_BUF_ENTER); + Ok(()) + } + + pub fn handle_file_type(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_FILE_TYPE); + if self.vim()?.get_filename(params)?.is_empty() { + return Ok(()); + } + + let filename = self.vim()?.get_filename(params)?.canonicalize(); + let language_id = self.vim()?.get_language_id(&filename, params)?; + + if self.get(|state| state.clients.contains_key(&Some(language_id.clone())))? { + self.text_document_did_open(params)?; + + if let Some(diagnostics) = + self.get(|state| state.diagnostics.get(&filename).cloned())? + { + self.process_diagnostics(&filename, &diagnostics)?; + self.handle_cursor_moved(params)?; + } + } else { + let auto_start: u8 = self + .vim()? + .eval("!!get(g:, 'LanguageClient_autoStart', 1)")?; + if auto_start == 1 { + let ret = self.start_server(params); + // This is triggered from autocmd, silent all errors. + if let Err(err) = ret { + warn!("Failed to start language server automatically. {}", err); + } + } + } + + info!("End {}", NOTIFICATION_HANDLE_FILE_TYPE); + Ok(()) + } + + pub fn handle_text_changed(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_TEXT_CHANGED); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + if !self.get(|state| state.server_commands.contains_key(&language_id))? { + return Ok(()); + } + + let skip_notification = self.get(|state| { + if let Some(metadata) = state.text_documents_metadata.get(&filename) { + if let Some(throttle) = state.change_throttle { + if metadata.last_change.elapsed() < throttle { + return true; + } + } + } + false + })?; + if skip_notification { + info!("Skip handleTextChanged due to throttling"); + return Ok(()); + } + + self.text_document_did_change(params)?; + info!("End {}", NOTIFICATION_HANDLE_TEXT_CHANGED); + Ok(()) + } + + pub fn handle_buf_write_post(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); + self.text_document_did_save(params)?; + info!("End {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); + Ok(()) + } + + pub fn handle_buf_delete(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + if !self.get(|state| state.server_commands.contains_key(&language_id))? { + return Ok(()); + } + + self.update(|state| { + state.text_documents.retain(|f, _| f != &filename); + state.diagnostics.retain(|f, _| f != &filename); + state.line_diagnostics.retain(|fl, _| fl.0 != *filename); + state.signs.retain(|f, _| f != &filename); + Ok(()) + })?; + self.text_document_did_close(params)?; + info!("End {}", NOTIFICATION_HANDLE_BUF_WRITE_POST); + Ok(()) + } + + pub fn handle_cursor_moved(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_HANDLE_CURSOR_MOVED); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let line = self.vim()?.get_position(params)?.line; + if !self.get(|state| state.server_commands.contains_key(&language_id))? { + return Ok(()); + } + if !self.get(|state| state.diagnostics.contains_key(&filename))? + && !self.get(|state| state.code_lens.contains_key(&filename))? + { + return Ok(()); + } + + if line != self.get(|state| state.last_cursor_line)? { + let message = self.get(|state| { + state + .line_diagnostics + .get(&(filename.clone(), line)) + .cloned() + .unwrap_or_default() + })?; + + if message != self.get(|state| state.last_line_diagnostic.clone())? { + self.vim()?.echo_ellipsis(&message)?; + self.update(|state| { + state.last_line_diagnostic = message; + Ok(()) + })?; + } + + self.update(|state| { + state.last_cursor_line = line; + Ok(()) + })?; + } + + let viewport = self.vim()?.get_viewport(params)?; + + // use the most severe diagnostic of each line as the sign + let signs_next: Vec<_> = self.update(|state| { + let mut diagnostics = state + .diagnostics + .entry(filename.clone()) + .or_default() + .iter() + .filter(|diag| viewport.overlaps(diag.range)) + .sorted_by_key(|diag| { + ( + diag.range.start.line, + diag.severity.unwrap_or(DiagnosticSeverity::Hint), + ) + }) + .collect_vec(); + diagnostics.dedup_by_key(|diag| diag.range.start.line); + Ok(diagnostics + .into_iter() + .take(state.diagnostics_signs_max.unwrap_or(std::usize::MAX)) + .map(Into::into) + .collect()) + })?; + self.update(|state| { + let signs_prev: Vec<_> = state + .signs + .entry(filename.clone()) + .or_default() + .iter() + .map(|(_, sign)| sign.clone()) + .collect(); + let mut signs_to_add = vec![]; + let mut signs_to_delete = vec![]; + let diffs = diff::slice(&signs_next, &signs_prev); + for diff in diffs { + match diff { + diff::Result::Left(s) => { + signs_to_add.push(s.clone()); + } + diff::Result::Right(s) => { + signs_to_delete.push(s.clone()); + } + _ => {} + } + } + + let signs = state.signs.entry(filename.clone()).or_default(); + // signs might be deleted AND added in the same line to change severity, + // so deletions must be before additions + for sign in &signs_to_delete { + signs.remove(&sign.line); + } + for sign in &signs_to_add { + signs.insert(sign.line, sign.clone()); + } + state + .vim + .set_signs(&filename, &signs_to_add, &signs_to_delete)?; + Ok(()) + })?; + + let highlights: Vec<_> = self.update(|state| { + Ok(state + .highlights + .entry(filename.clone()) + .or_insert_with(Vec::new) + .iter() + .filter_map(|h| { + if h.line < viewport.start || h.line > viewport.end { + return None; + } + + Some(h.clone()) + }) + .collect()) + })?; + + if Some(highlights.clone()) + != self.get(|state| state.highlights_placed.get(&filename).cloned())? + && self.get(|state| state.is_nvim)? + { + let source = if let Some(source) = self.get(|state| state.highlight_source)? { + source + } else { + let source = self + .vim()? + .rpcclient + .call("nvim_buf_add_highlight", json!([0, 0, "Error", 1, 1, 1]))?; + self.update(|state| { + state.highlight_source = Some(source); + Ok(()) + })?; + source + }; + + self.update(|state| { + state + .highlights_placed + .insert(filename.clone(), highlights.clone()); + Ok(()) + })?; + + self.vim()?.rpcclient.notify( + "nvim_buf_clear_highlight", + json!([0, source, viewport.start, viewport.end]), + )?; + + self.vim()? + .rpcclient + .notify("s:AddHighlights", json!([source, highlights]))?; + } + + self.draw_virtual_texts(¶ms)?; + + info!("End {}", NOTIFICATION_HANDLE_CURSOR_MOVED); + Ok(()) + } + + fn draw_virtual_texts(&self, params: &Value) -> Result<()> { + if !self.get(|state| state.is_nvim)? { + return Ok(()); + } + + let filename = self.vim()?.get_filename(params)?; + let filename = filename.as_str(); + let viewport = self.vim()?.get_viewport(params)?; + let bufnr = self.vim()?.get_bufnr(&filename, params)?; + let namespace_id = self.get_or_create_namespace(&LCNamespace::VirtualText)?; + let is_insert_mode = self.vim()?.get_mode()? == Mode::Insert; + if self.get(|state| state.hide_virtual_texts_on_insert)? && is_insert_mode { + self.vim()?.set_virtual_texts( + bufnr, + namespace_id, + viewport.start, + viewport.end, + &[], + )?; + return Ok(()); + } + + let mut virtual_texts = vec![]; + let use_virtual_text = self.get(|state| state.use_virtual_text.clone())?; + + // diagnostics + if UseVirtualText::All == use_virtual_text + || UseVirtualText::Diagnostics == use_virtual_text + { + let vt_diagnostics = self + .virtual_texts_from_diagnostics(filename, viewport)? + .into_iter(); + virtual_texts.extend(vt_diagnostics); + } + + // code lens + if UseVirtualText::All == use_virtual_text || UseVirtualText::CodeLens == use_virtual_text { + virtual_texts.extend(self.virtual_texts_from_code_lenses(filename)?.into_iter()); + } + + self.vim()?.set_virtual_texts( + bufnr, + namespace_id, + viewport.start, + viewport.end, + &virtual_texts, + )?; + + Ok(()) + } + + fn virtual_texts_from_diagnostics( + &self, + filename: &str, + viewport: viewport::Viewport, + ) -> Result> { + let mut virtual_texts = vec![]; + let diagnostics = self.get(|state| state.diagnostics.clone())?; + let diagnostics_display = self.get(|state| state.diagnostics_display.clone())?; + let diag_list = diagnostics.get(filename); + if let Some(diag_list) = diag_list { + for diag in diag_list { + if viewport.overlaps(diag.range) { + let mut explanation = diag.message.clone(); + if let Some(source) = &diag.source { + explanation = format!("{}: {}\n", source, explanation); + } + virtual_texts.push(VirtualText { + line: diag.range.start.line, + text: explanation.replace("\n", " "), + hl_group: diagnostics_display + .get(&(diag.severity.unwrap_or(DiagnosticSeverity::Hint) as u64)) + .ok_or_else(|| anyhow!("Failed to get display"))? + .virtual_texthl + .clone(), + }); + } + } + } + + Ok(virtual_texts) + } + + fn virtual_texts_from_code_lenses(&self, filename: &str) -> Result> { + let mut virtual_texts = vec![]; + let code_lenses = + self.get(|state| state.code_lens.get(filename).cloned().unwrap_or_default())?; + + for cl in code_lenses { + if let Some(command) = cl.command { + let line = cl.range.start.line; + let text = command.title; + + match virtual_texts + .iter() + .position(|v: &VirtualText| v.line == line) + { + Some(idx) => virtual_texts[idx] + .text + .push_str(format!(" | {}", text).as_str()), + None => virtual_texts.push(VirtualText { + line, + text, + hl_group: "Comment".into(), + }), + } + } + } + + Ok(virtual_texts) + } + + pub fn handle_complete_done(&self, params: &Value) -> Result<()> { + let filename = self.vim()?.get_filename(params)?; + let position = self.vim()?.get_position(params)?; + let completed_item: VimCompleteItem = try_get("completed_item", params)? + .ok_or_else(|| anyhow!("completed_item not found!"))?; + + let user_data = match completed_item.user_data { + Some(user_data) => user_data, + _ => return Ok(()), + }; + let user_data: VimCompleteItemUserData = serde_json::from_str(&user_data)?; + let lspitem = match user_data.lspitem { + Some(lspitem) => lspitem, + _ => return Ok(()), + }; + + let mut edits = vec![]; + if self.get(|state| state.completion_prefer_text_edit)? { + if let Some(CompletionTextEdit::InsertAndReplace(_)) = lspitem.text_edit { + error!("insert and replace is not supported"); + } + + if let Some(CompletionTextEdit::Edit(edit)) = lspitem.text_edit { + // The text edit should be at the completion point, and deleting the partial text + // that the user had typed when the language server provided the completion. + // + // We want to tweak the edit so that it instead deletes the completion that we've + // already inserted. + // + // Check that we're not doing anything stupid before going ahead with this. + let mut edit = edit; + edit.range.end.character = + edit.range.start.character + completed_item.word.len() as u64; + if edit.range.end != position || edit.range.start.line != edit.range.end.line { + return Ok(()); + } + edits.push(edit); + } + } + + if self.get(|state| state.apply_completion_additional_text_edits)? { + if let Some(aedits) = lspitem.additional_text_edits { + edits.extend(aedits); + }; + } + + if edits.is_empty() { + return Ok(()); + } + + let position = self.apply_text_edits(filename, &edits, position)?; + self.vim()? + .cursor(position.line + 1, position.character + 1) + } + + pub fn fzf_sink_location(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_FZF_SINK_LOCATION); + let params = match params { + Value::Array(ref arr) => Value::Array(arr.clone()), + _ => { + return Err(anyhow!("Expecting array params!")); + } + }; + + let lines: Vec = serde_json::from_value(params.clone())?; + if lines.is_empty() { + anyhow!("No selection!"); + } + + let location = lines + .get(0) + .ok_or_else(|| anyhow!("Failed to get line! lines: {:?}", lines))? + .split('\t') + .next() + .ok_or_else(|| anyhow!("Failed to parse: {:?}", lines))?; + let tokens: Vec<_> = location.split_terminator(':').collect(); + + let (filename, mut tokens_iter): (String, _) = if tokens.len() > 2 { + let end_index = tokens.len() - 2; + let path = tokens[..end_index].join(":"); + let rest_tokens_iter = tokens[end_index..].iter(); + (path, rest_tokens_iter) + } else { + (self.vim()?.get_filename(¶ms)?, tokens.iter()) + }; + + let line = tokens_iter + .next() + .ok_or_else(|| anyhow!("Failed to get line! tokens: {:?}", tokens))? + .to_int()? + - 1; + let character = tokens_iter + .next() + .ok_or_else(|| anyhow!("Failed to get character! tokens: {:?}", tokens))? + .to_int()? + - 1; + + self.edit(&None, &filename)?; + self.vim()?.cursor(line + 1, character + 1)?; + + info!("End {}", NOTIFICATION_FZF_SINK_LOCATION); + Ok(()) + } + + pub fn fzf_sink_command(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_FZF_SINK_COMMAND); + let selection: String = + try_get("selection", params)?.ok_or_else(|| anyhow!("selection not found!"))?; + let tokens: Vec<&str> = selection.splitn(2, ": ").collect(); + let kind = tokens + .get(0) + .cloned() + .ok_or_else(|| anyhow!("Failed to get title! tokens: {:?}", tokens))?; + let title = tokens + .get(1) + .cloned() + .ok_or_else(|| anyhow!("Failed to get kind! tokens: {:?}", tokens))?; + let actions = self.get(|state| state.stashed_code_action_actions.clone())?; + let idx = actions + .iter() + .position(|it| code_action_kind_as_str(&it) == kind && it.title == title); + + match idx { + Some(idx) => self.handle_code_action_selection(&actions, idx)?, + None => return Err(anyhow!("Action not stashed, please try again")), + }; + + info!("End {}", NOTIFICATION_FZF_SINK_COMMAND); + Ok(()) + } + + pub fn semantic_scopes(&self, params: &Value) -> Result { + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let (scopes, mut scope_mapping) = self.get(|state| { + ( + state + .semantic_scopes + .get(&language_id) + .cloned() + .unwrap_or_default(), + state + .semantic_scope_to_hl_group_table + .get(&language_id) + .cloned() + .unwrap_or_default(), + ) + })?; + + let mut semantic_scopes = Vec::new(); + + // If the user has not set up highlighting yet the table does not exist + if scopes.len() > scope_mapping.len() { + scope_mapping.resize(scopes.len(), None); + } + + for (scope, opt_hl_group) in scopes.iter().zip(scope_mapping.iter()) { + if let Some(hl_group) = opt_hl_group { + semantic_scopes.push(json!({ + "scope": scope, + "hl_group": hl_group, + })); + } else { + semantic_scopes.push(json!({ + "scope": scope, + "hl_group": "None", + })); + } + } + + Ok(json!(semantic_scopes)) + } + + pub fn semantic_highlight_symbols(&self, params: &Value) -> Result { + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let (opt_scopes, opt_hl_state) = self.get(|state| { + ( + state.semantic_scopes.get(&language_id).cloned(), + state.semantic_highlights.get(&language_id).cloned(), + ) + })?; + + if let (Some(scopes), Some(hl_state)) = (opt_scopes, opt_hl_state) { + let mut symbols = Vec::new(); + + for sym in hl_state.symbols { + for token in sym.tokens.unwrap_or_default() { + symbols.push(json!({ + "line": sym.line as u64, + "character_start": token.character as u64, + "character_end": token.character as u64 + token.length as u64, + "scope": scopes.get(token.scope as usize).cloned().unwrap_or_default() + })); + } + } + + Ok(json!(symbols)) + } else { + Ok(json!([])) + } + } + + pub fn ncm_refresh(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_NCM_REFRESH); + let params: NCMRefreshParams = + serde_json::from_value(jsonrpc_core::to_value(params.clone())?)?; + let NCMRefreshParams { info, ctx } = params; + if ctx.typed.is_empty() { + return Ok(Value::Null); + } + + let filename = ctx.filepath.clone(); + let line = ctx.lnum - 1; + let character = ctx.col - 1; + + let result = self.text_document_completion(&json!({ + "languageId": ctx.filetype, + "filename": filename, + "line": line, + "character": character, + "handle": false, + }))?; + let result: Option = serde_json::from_value(result)?; + let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); + let is_incomplete = match result { + CompletionResponse::Array(_) => false, + CompletionResponse::List(ref list) => list.is_incomplete, + }; + let matches: Result> = match result { + CompletionResponse::Array(arr) => arr, + CompletionResponse::List(list) => list.items, + } + .iter() + .map(|item| VimCompleteItem::from_lsp(item, None)) + .collect(); + let matches = matches?; + self.vim()?.rpcclient.notify( + "cm#complete", + json!([info.name, ctx, ctx.startcol, matches, is_incomplete]), + )?; + info!("End {}", REQUEST_NCM_REFRESH); + Ok(Value::Null) + } + + pub fn ncm2_on_complete(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_NCM2_ON_COMPLETE); + + let orig_ctx: Value = serde_json::from_value(jsonrpc_core::to_value(params.clone())?)?; + let orig_ctx = &orig_ctx["ctx"]; + + let ctx: NCM2Context = serde_json::from_value(orig_ctx.clone())?; + if ctx.typed.is_empty() { + return Ok(Value::Null); + } + + let filename = ctx.filepath.clone(); + let line = ctx.lnum - 1; + let character = ctx.ccol - 1; + + let result = self.text_document_completion(&json!({ + "languageId": ctx.filetype, + "filename": filename, + "line": line, + "character": character, + "handle": false})); + let is_incomplete; + let matches; + if let Ok(ref value) = result { + let completion = serde_json::from_value(value.clone())?; + is_incomplete = match completion { + CompletionResponse::List(ref list) => list.is_incomplete, + _ => false, + }; + let matches_result: Result> = match completion { + CompletionResponse::Array(arr) => arr, + CompletionResponse::List(list) => list.items, + } + .iter() + .map(|item| VimCompleteItem::from_lsp(item, None)) + .collect(); + matches = matches_result?; + } else { + is_incomplete = true; + matches = vec![]; + } + self.vim()?.rpcclient.notify( + "ncm2#complete", + json!([orig_ctx, ctx.startccol, matches, is_incomplete]), + )?; + info!("End {}", REQUEST_NCM2_ON_COMPLETE); + result + } + + pub fn explain_error_at_point(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_EXPLAIN_ERROR_AT_POINT); + let filename = self.vim()?.get_filename(params)?; + let position = self.vim()?.get_position(params)?; + let diag = self.get(|state| { + state + .diagnostics + .get(&filename) + .ok_or_else(|| anyhow!("No diagnostics found: filename: {}", filename,))? + .iter() + .find(|dn| position >= dn.range.start && position < dn.range.end) + .cloned() + .ok_or_else(|| { + anyhow!( + "No diagnostics found: filename: {}, line: {}, character: {}", + filename, + position.line, + position.character + ) + }) + })??; + + let language_id = self.vim()?.get_language_id(&filename, params)?; + let root = self.get(|state| state.roots.get(&language_id).cloned().unwrap_or_default())?; + let root_uri = root.to_url()?; + + let mut explanation = diag.message; + if let Some(source) = diag.source { + explanation = format!("{}: {}\n", source, explanation); + } + if let Some(related_information) = diag.related_information { + explanation = format!("{}\n", explanation); + for ri in related_information { + let prefix = format!("{}/", root_uri); + let uri = if ri.location.uri.as_str().starts_with(prefix.as_str()) { + // Heuristic: if start of stringified URI matches rootUri, abbreviate it away + &ri.location.uri.as_str()[root_uri.as_str().len() + 1..] + } else { + ri.location.uri.as_str() + }; + if ri.location.uri.scheme() == "file" { + explanation = format!( + "{}\n{}:{}: {}", + explanation, + uri, + &ri.location.range.start.line + 1, + &ri.message + ); + } else { + // Heuristic: if scheme is not file, don't show line numbers + explanation = format!("{}\n{}: {}", explanation, uri, &ri.message); + } + } + } + + self.preview(explanation.as_str())?; + + info!("End {}", REQUEST_EXPLAIN_ERROR_AT_POINT); + Ok(Value::Null) + } + + // Extensions by language servers. + pub fn language_status(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_LANGUAGE_STATUS); + let params = LanguageStatusParams::deserialize(params)?; + let msg = format!("{} {}", params.typee, params.message); + self.vim()?.echomsg(&msg)?; + info!("End {}", NOTIFICATION_LANGUAGE_STATUS); + Ok(()) + } + + pub fn rust_handle_begin_build(&self, _params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_RUST_BEGIN_BUILD); + self.vim()?.command(vec![ + format!("let {}=1", VIM_SERVER_STATUS), + format!("let {}='Rust: build begin'", VIM_SERVER_STATUS_MESSAGE), + ])?; + info!("End {}", NOTIFICATION_RUST_BEGIN_BUILD); + Ok(()) + } + + pub fn rust_handle_diagnostics_begin(&self, _params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_RUST_DIAGNOSTICS_BEGIN); + self.vim()?.command(vec![ + format!("let {}=1", VIM_SERVER_STATUS), + format!( + "let {}='Rust: diagnostics begin'", + VIM_SERVER_STATUS_MESSAGE + ), + ])?; + info!("End {}", NOTIFICATION_RUST_DIAGNOSTICS_BEGIN); + Ok(()) + } + + pub fn rust_handle_diagnostics_end(&self, _params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_RUST_DIAGNOSTICS_END); + self.vim()?.command(vec![ + format!("let {}=0", VIM_SERVER_STATUS), + format!("let {}='Rust: diagnostics end'", VIM_SERVER_STATUS_MESSAGE), + ])?; + info!("End {}", NOTIFICATION_RUST_DIAGNOSTICS_END); + Ok(()) + } + + pub fn window_progress(&self, params: &Value) -> Result<()> { + info!("Begin {}", NOTIFICATION_WINDOW_PROGRESS); + let params = WindowProgressParams::deserialize(params)?; + + let done = params.done.unwrap_or(false); + + let mut buf = "LS: ".to_owned(); + + if done { + buf += "Idle"; + } else { + // For RLS this can be "Build" or "Diagnostics" or "Indexing". + buf += params.title.as_ref().map(AsRef::as_ref).unwrap_or("Busy"); + + // For RLS this is the crate name, present only if the progress isn't known. + if let Some(message) = params.message { + buf += &format!(" ({})", &message); + } + // For RLS this is the progress percentage, present only if the it's known. + if let Some(percentage) = params.percentage { + buf += &format!(" ({:.1}% done)", percentage); + } + } + + self.vim()?.command(vec![ + format!("let {}={}", VIM_SERVER_STATUS, if done { 0 } else { 1 }), + format!( + "let {}='{}'", + VIM_SERVER_STATUS_MESSAGE, + &escape_single_quote(buf) + ), + ])?; + info!("End {}", NOTIFICATION_WINDOW_PROGRESS); + Ok(()) + } + + pub fn start_server(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_START_SERVER); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let cmdargs: Vec = try_get("cmdargs", params)?.unwrap_or_default(); + let cmdparams = vim_cmd_args_to_value(&cmdargs)?; + let params = params.combine(&cmdparams); + + // When multiple buffers get opened up concurrently, + // startServer gets called concurrently. + // This lock ensures that at most one language server is starting up at a time per + // languageId. + // We keep the mutex in scope to satisfy the borrow checker. + // This ensures that the mutex isn't garbage collected while the MutexGuard is held. + // + // - e.g. prevents starting multiple servers with `vim -p`. + // - This continues to allow distinct language servers to start up concurrently + // by languageId (e.g. java and rust) + // - Revisit this when more than one server is allowed per languageId. + // (ensure that the mutex is acquired by what starts the group of servers) + // + // TODO: May want to lock other methods that update the list of clients. + let mutex_for_language_id = self.get_client_update_mutex(Some(language_id.clone()))?; + let _raii_lock: MutexGuard<()> = mutex_for_language_id.lock().map_err(|err| { + anyhow!( + "Failed to lock client creation for languageId {:?}: {:?}", + language_id, + err + ) + })?; + + if self.get(|state| state.clients.contains_key(&Some(language_id.clone())))? { + return Ok(json!({})); + } + + self.sync_settings()?; + info!("settings synced"); + + let command = self + .get(|state| state.server_commands.get(&language_id).cloned())? + .ok_or_else(|| { + Error::from(LCError::NoServerCommands { + language_id: language_id.clone(), + }) + })?; + + let root_path: Option = try_get("rootPath", ¶ms)?; + let root = if let Some(r) = root_path { + r + } else { + get_root_path( + Path::new(&filename), + &language_id, + &self.get(|state| state.root_markers.clone())?, + )? + .to_string_lossy() + .into() + }; + let message = format!("Project root: {}", root); + if self.get(|state| state.echo_project_root)? { + self.vim()?.echomsg_ellipsis(&message)?; + } + info!("{}", message); + self.update(|state| { + state.roots.insert(language_id.clone(), root.clone()); + Ok(()) + })?; + + let (child_id, reader, writer): (_, Box, Box) = + if command.get(0).map(|c| c.starts_with("tcp://")) == Some(true) { + let addr = command + .get(0) + .map(|s| s.replace("tcp://", "")) + .ok_or_else(|| anyhow!("Server command can't be empty!"))?; + let stream = TcpStream::connect(addr)?; + let reader = Box::new(BufReader::new(stream.try_clone()?)); + let writer = Box::new(BufWriter::new(stream)); + (None, reader, writer) + } else { + let command: Vec<_> = command + .into_iter() + .map(|cmd| match shellexpand::full(&cmd) { + Ok(cmd) => cmd.as_ref().into(), + Err(err) => { + warn!("Error expanding ({}): {}", cmd, err); + cmd.clone() + } + }) + .collect(); + + let stderr = match self.get(|state| state.server_stderr.clone())? { + Some(ref path) => std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .with_context(|| format!("Failed to open file ({})", path))? + .into(), + None => Stdio::null(), + }; + + let process = std::process::Command::new( + command.get(0).ok_or_else(|| anyhow!("Empty command!"))?, + ) + .args(&command[1..]) + .current_dir(&root) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(stderr) + .spawn() + .with_context(|| format!("Failed to start language server ({:?})", command))?; + + let child_id = Some(process.id()); + let reader = Box::new(BufReader::new( + process + .stdout + .ok_or_else(|| anyhow!("Failed to get subprocess stdout"))?, + )); + let writer = Box::new(BufWriter::new( + process + .stdin + .ok_or_else(|| anyhow!("Failed to get subprocess stdin"))?, + )); + (child_id, reader, writer) + }; + + let client = RpcClient::new( + Some(language_id.clone()), + reader, + writer, + child_id, + self.get(|state| state.tx.clone())?, + )?; + self.update(|state| { + state + .clients + .insert(Some(language_id.clone()), Arc::new(client)); + Ok(()) + })?; + + info!("End {}", REQUEST_START_SERVER); + + if self.get(|state| state.clients.len())? == 2 { + self.define_signs()?; + } + + self.initialize(¶ms)?; + self.initialized(¶ms)?; + + let root = self.get(|state| state.roots.get(&language_id).cloned().unwrap_or_default())?; + match self.get_workspace_settings(&root) { + Ok(Value::Null) => (), + Ok(settings) => self.workspace_did_change_configuration(&json!({ + "languageId": language_id, + "settings": settings, + }))?, + Err(err) => warn!("Failed to get workspace settings: {}", err), + } + + self.vim()? + .rpcclient + .notify("setbufvar", json!([filename, VIM_IS_SERVER_RUNNING, 1]))?; + + self.text_document_did_open(¶ms)?; + self.text_document_did_change(¶ms)?; + + self.vim()? + .rpcclient + .notify("s:ExecuteAutocmd", "LanguageClientStarted")?; + Ok(Value::Null) + } + + pub fn handle_server_exited(&self, params: &Value) -> Result<()> { + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let message: String = try_get("message", params)?.unwrap_or_default(); + + if self.get(|state| state.clients.contains_key(&Some(language_id.clone())))? { + if let Err(err) = self.cleanup(&language_id) { + error!("Error in cleanup: {:?}", err); + } + if let Err(err) = self.vim()?.echoerr(format!( + "Language server {} exited unexpectedly: {}", + language_id, message + )) { + error!("Error in echoerr: {:?}", err); + } + } + + Ok(()) + } + + pub fn handle_fs_events(&self) -> Result<()> { + let mut pending_changes = HashMap::new(); + self.update(|state| { + for (language_id, watcher_rx) in &mut state.watcher_rxs { + let mut events = vec![]; + loop { + let result = watcher_rx.try_recv(); + let event = match result { + Ok(event) => event, + Err(mpsc::TryRecvError::Empty) => { + break; + } + Err(mpsc::TryRecvError::Disconnected) => { + return Err(anyhow!("File system notification channel disconnected!")); + } + }; + events.push(event); + } + + let mut changes = vec![]; + for e in events { + if let Ok(c) = e.to_lsp() { + changes.extend(c); + } + } + + if changes.is_empty() { + continue; + } + + pending_changes.insert(language_id.to_owned(), changes); + } + Ok(()) + })?; + + for (language_id, changes) in pending_changes { + self.workspace_did_change_watched_files(&json!({ + "languageId": language_id, + "changes": changes + }))?; + } + + Ok(()) + } + + pub fn workspace_did_change_watched_files(&self, params: &Value) -> Result<()> { + info!( + "Begin {}", + lsp_types::notification::DidChangeWatchedFiles::METHOD + ); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let params = DidChangeWatchedFilesParams::deserialize(params)?; + self.get_client(&Some(language_id))?.notify( + lsp_types::notification::DidChangeWatchedFiles::METHOD, + params, + )?; + + info!( + "End {}", + lsp_types::notification::DidChangeWatchedFiles::METHOD + ); + Ok(()) + } + + pub fn java_class_file_contents(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_CLASS_FILE_CONTENTS); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + + let content: String = self + .get_client(&Some(language_id))? + .call(REQUEST_CLASS_FILE_CONTENTS, params)?; + + let lines: Vec = content + .lines() + .map(std::string::ToString::to_string) + .collect(); + + let goto_cmd = self + .vim()? + .get_goto_cmd(params)? + .unwrap_or_else(|| "edit".to_string()); + + let uri: String = + try_get("uri", params)?.ok_or_else(|| anyhow!("uri not found in request!"))?; + + self.vim()? + .rpcclient + .notify("s:Edit", json!([goto_cmd, uri]))?; + + self.vim()?.setline(1, &lines)?; + self.vim()? + .command("setlocal buftype=nofile filetype=java noswapfile")?; + + info!("End {}", REQUEST_CLASS_FILE_CONTENTS); + Ok(Value::String(content)) + } + + pub fn debug_info(&self, params: &Value) -> Result { + info!("Begin {}", REQUEST_DEBUG_INFO); + let filename = self.vim()?.get_filename(params)?; + let language_id = self.vim()?.get_language_id(&filename, params)?; + let mut msg = String::new(); + self.get(|state| { + msg += &format!( + "Project root: {}\n", + state.roots.get(&language_id).cloned().unwrap_or_default() + ); + msg += &format!( + "Language server process id: {:?}\n", + state + .clients + .get(&Some(language_id.clone())) + .map(|c| c.process_id) + .unwrap_or_default(), + ); + msg += &format!( + "Language server stderr: {}\n", + state.server_stderr.clone().unwrap_or_default() + ); + msg += &format!("Log level: {}\n", state.logger.level); + msg += &format!("Log file: {:?}\n", state.logger.path); + })?; + self.vim()?.echo(&msg)?; + info!("End {}", REQUEST_DEBUG_INFO); + Ok(json!(msg)) + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/languageclient.rs b/vim/bundle/LanguageClient-neovim/src/languageclient.rs deleted file mode 100644 index d7a71b0..0000000 --- a/vim/bundle/LanguageClient-neovim/src/languageclient.rs +++ /dev/null @@ -1,3023 +0,0 @@ -use super::*; -use lsp::notification::Notification; -use lsp::request::Request; - -impl State { - /////// Utils /////// - - pub fn gather_args( - &mut self, - exps: &[E], - map: &Value, - ) -> Result { - let mut map = map - .as_object() - .cloned() - .unwrap_or_else(serde_json::Map::new); - let mut keys_request = vec![]; - let mut exps_request = vec![]; - for e in exps { - let k = e.to_key(); - if !map.contains_key(&k) { - keys_request.push(k); - exps_request.push(e.to_exp()); - } - } - let values_request: Vec = if keys_request.is_empty() { - vec![] - } else { - info!( - "Some arguments are not available. Requesting from vim. Keys: {:?}. Exps: {:?}", - keys_request, exps_request, - ); - self.eval::<&[_], _>(exps_request.as_ref())? - }; - for (k, v) in keys_request.into_iter().zip(values_request.into_iter()) { - map.insert(k, v); - } - - let mut result = vec![]; - for e in exps { - let k = e.to_key(); - result.push( - map.remove(&k) - .ok_or_else(|| format_err!("Failed to get value! k: {}", k))?, - ); - } - - info!("gather_args: {:?} = {:?}", exps, result); - Ok(serde_json::from_value(Value::Array(result))?) - } - - fn sync_settings(&mut self) -> Result<()> { - let (loggingFile, loggingLevel, serverStderr): ( - Option, - log::LevelFilter, - Option, - ) = self.eval( - [ - "get(g:, 'LanguageClient_loggingFile', v:null)", - "get(g:, 'LanguageClient_loggingLevel', 'WARN')", - "get(g:, 'LanguageClient_serverStderr', v:null)", - ] - .as_ref(), - )?; - logger::update_settings(&self.logger, &loggingFile, loggingLevel)?; - - #[allow(unknown_lints)] - #[allow(type_complexity)] - let ( - autoStart, - serverCommands, - selectionUI, - trace, - settingsPath, - loadSettings, - rootMarkers, - change_throttle, - wait_output_timeout, - diagnosticsEnable, - diagnosticsList, - diagnosticsDisplay, - windowLogMessageLevel, - hoverPreview, - completionPreferTextEdit, - is_nvim, - ): ( - u64, - HashMap>, - Option, - Option, - String, - u64, - Option, - Option, - Option, - u64, - Option, - Value, - String, - Option, - u64, - u64, - ) = self.eval( - [ - "!!get(g:, 'LanguageClient_autoStart', 1)", - "get(g:, 'LanguageClient_serverCommands', {})", - "get(g:, 'LanguageClient_selectionUI', v:null)", - "get(g:, 'LanguageClient_trace', v:null)", - "expand(get(g:, 'LanguageClient_settingsPath', '.vim/settings.json'))", - "!!get(g:, 'LanguageClient_loadSettings', 1)", - "get(g:, 'LanguageClient_rootMarkers', v:null)", - "get(g:, 'LanguageClient_changeThrottle', v:null)", - "get(g:, 'LanguageClient_waitOutputTimeout', v:null)", - "!!get(g:, 'LanguageClient_diagnosticsEnable', 1)", - "get(g:, 'LanguageClient_diagnosticsList', 'Quickfix')", - "get(g:, 'LanguageClient_diagnosticsDisplay', {})", - "get(g:, 'LanguageClient_windowLogMessageLevel', 'Warning')", - "get(g:, 'LanguageClient_hoverPreview', 'Auto')", - "get(g:, 'LanguageClient_completionPreferTextEdit', 0)", - "has('nvim')", - ] - .as_ref(), - )?; - - let (diagnosticsSignsMax, documentHighlightDisplay): (Option, Value) = self.eval( - [ - "get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)", - "get(g:, 'LanguageClient_documentHighlightDisplay', {})", - ] - .as_ref(), - )?; - - // vimscript use 1 for true, 0 for false. - let autoStart = autoStart == 1; - let loadSettings = loadSettings == 1; - - let trace = if let Some(t) = trace { - match t.to_ascii_uppercase().as_str() { - "OFF" => Some(TraceOption::Off), - "MESSAGES" => Some(TraceOption::Messages), - "VERBOSE" => Some(TraceOption::Verbose), - _ => bail!("Invalid option for LanguageClient_trace: {}", t), - } - } else { - Some(TraceOption::default()) - }; - - let selectionUI = if let Some(s) = selectionUI { - SelectionUI::from_str(&s)? - } else if self.eval::<_, i64>("get(g:, 'loaded_fzf')")? == 1 { - SelectionUI::FZF - } else { - SelectionUI::default() - }; - - let change_throttle = change_throttle.map(|t| Duration::from_millis((t * 1000.0) as u64)); - let wait_output_timeout = - Duration::from_millis((wait_output_timeout.unwrap_or(10.0) * 1000.0) as u64); - - let diagnosticsEnable = diagnosticsEnable == 1; - - let diagnosticsList = if let Some(s) = diagnosticsList { - DiagnosticsList::from_str(&s)? - } else { - DiagnosticsList::Disabled - }; - - let windowLogMessageLevel = match windowLogMessageLevel.to_ascii_uppercase().as_str() { - "ERROR" => MessageType::Error, - "WARNING" => MessageType::Warning, - "INFO" => MessageType::Info, - "LOG" => MessageType::Log, - _ => bail!( - "Invalid option for LanguageClient_windowLogMessageLevel: {}", - windowLogMessageLevel - ), - }; - - let hoverPreview = if let Some(s) = hoverPreview { - HoverPreviewOption::from_str(&s)? - } else { - HoverPreviewOption::Auto - }; - - let completionPreferTextEdit = completionPreferTextEdit == 1; - - let is_nvim = is_nvim == 1; - - self.update(|state| { - state.autoStart = autoStart; - state.serverCommands.extend(serverCommands); - state.selectionUI = selectionUI; - state.trace = trace; - state.diagnosticsEnable = diagnosticsEnable; - state.diagnosticsList = diagnosticsList; - state.diagnosticsDisplay = serde_json::from_value( - serde_json::to_value(&state.diagnosticsDisplay)?.combine(&diagnosticsDisplay), - )?; - state.diagnosticsSignsMax = diagnosticsSignsMax; - state.documentHighlightDisplay = serde_json::from_value( - serde_json::to_value(&state.documentHighlightDisplay)? - .combine(&documentHighlightDisplay), - )?; - state.windowLogMessageLevel = windowLogMessageLevel; - state.settingsPath = settingsPath; - state.loadSettings = loadSettings; - state.rootMarkers = rootMarkers; - state.change_throttle = change_throttle; - state.wait_output_timeout = wait_output_timeout; - state.hoverPreview = hoverPreview; - state.completionPreferTextEdit = completionPreferTextEdit; - state.loggingFile = loggingFile; - state.loggingLevel = loggingLevel; - state.serverStderr = serverStderr; - state.is_nvim = is_nvim; - Ok(()) - })?; - - Ok(()) - } - - fn get_workspace_settings(&self, root: &str) -> Result { - if !self.loadSettings { - return Ok(Value::Null); - } - - let path = Path::new(root).join(self.settingsPath.clone()); - let buffer = read_to_string(&path).with_context(|err| { - format!("Failed to read file ({}): {}", path.to_string_lossy(), err) - })?; - let value = serde_json::from_str(&buffer)?; - let value = expand_json_path(value); - Ok(value) - } - - fn define_signs(&mut self) -> Result<()> { - info!("Defining signs"); - - let mut cmds = vec![]; - for entry in self.diagnosticsDisplay.values() { - cmds.push(format!( - "sign define LanguageClient{} text={} texthl={}", - entry.name, entry.signText, entry.signTexthl, - )); - } - - self.command(cmds)?; - Ok(()) - } - - fn apply_WorkspaceEdit(&mut self, edit: &WorkspaceEdit, params: &Value) -> Result<()> { - debug!( - "Begin apply WorkspaceEdit: {:?}. Params: {:?}", - edit, params - ); - let (filename, line, character): (String, u64, u64) = - self.gather_args(&[VimVar::Filename, VimVar::Line, VimVar::Character], params)?; - - if let Some(ref changes) = edit.document_changes { - match changes { - DocumentChanges::Edits(ref changes) => { - for e in changes { - self.apply_TextEdits(&e.text_document.uri.filepath()?, &e.edits)?; - } - } - DocumentChanges::Operations(ref ops) => { - for op in ops { - if let DocumentChangeOperation::Edit(ref e) = op { - self.apply_TextEdits(&e.text_document.uri.filepath()?, &e.edits)?; - } - // TODO: handle ResourceOp. - } - } - } - } - if let Some(ref changes) = edit.changes { - for (uri, edits) in changes { - self.apply_TextEdits(&uri.filepath()?, edits)?; - } - } - self.edit(&None, &filename)?; - self.cursor(line + 1, character + 1)?; - debug!("End apply WorkspaceEdit"); - Ok(()) - } - - pub fn textDocument_documentHighlight(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::DocumentHighlightRequest::METHOD); - let (languageId, filename, line, character, handle): ( - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - - let result = self.call( - Some(&languageId), - lsp::request::DocumentHighlightRequest::METHOD, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - }, - )?; - - if !handle { - return Ok(result); - } - - let document_highlight: Option> = - serde_json::from_value(result.clone())?; - if let Some(document_highlight) = document_highlight { - let highlights = document_highlight - .into_iter() - .map(|DocumentHighlight { range, kind }| { - Ok(Highlight { - line: range.start.line, - character_start: range.start.character, - character_end: range.end.character, - group: self - .documentHighlightDisplay - .get( - &kind - .unwrap_or(DocumentHighlightKind::Text) - .to_int() - .unwrap(), - ).ok_or_else(|| err_msg("Failed to get display"))? - .texthl - .clone(), - text: String::new(), - }) - }).collect::>>()?; - - let buffer = self.call(None, "nvim_win_get_buf", json!([0]))?; - - let source = if let Some(hs) = self.document_highlight_source { - if hs.buffer == buffer { - // If we want to highlight in the same buffer as last time, we can reuse - // the previous source. - Some(hs.source) - } else { - // Clear the highlight in the previous buffer. - self.notify( - None, - "nvim_buf_clear_highlight", - json!([hs.buffer, hs.source, 0, -1]), - )?; - - None - } - } else { - None - }; - - let source = match source { - Some(source) => source, - None => { - // Create a new source. - let source = self.call( - None, - "nvim_buf_add_highlight", - json!([buffer, 0, "Error", 1, 1, 1]), - )?; - self.document_highlight_source = Some(HighlightSource { buffer, source }); - source - } - }; - - self.notify( - None, - "nvim_buf_clear_highlight", - json!([buffer, source, 0, -1]), - )?; - self.notify(None, "s:AddHighlights", json!([source, highlights]))?; - } - - info!("End {}", lsp::request::DocumentHighlightRequest::METHOD); - Ok(result) - } - - pub fn languageClient_clearDocumentHighlight(&mut self, _: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__ClearDocumentHighlight); - - if let Some(HighlightSource { buffer, source }) = self.document_highlight_source.take() { - self.notify( - None, - "nvim_buf_clear_highlight", - json!([buffer, source, 0, -1]), - )?; - } - - info!("End {}", NOTIFICATION__ClearDocumentHighlight); - Ok(()) - } - - fn apply_TextEdits>(&mut self, path: P, edits: &[TextEdit]) -> Result<()> { - debug!("Begin apply TextEdits: {:?}", edits); - if edits.is_empty() { - return Ok(()); - } - - let mut edits = edits.to_vec(); - - // Edits must be applied from bottom to top, so that earlier edits will not interfere - // with the positioning of later edits. Edits that start with the same position must be - // applied in reverse order, so that multiple inserts will have their text appear in the - // same order the server sent it, and so that a delete/replace (according to the LSP spec, - // there can only be one per start position and it must be after the inserts) will work on - // the original document, not on the just-inserted text. - edits.sort_by_key(|edit| (edit.range.start.line, edit.range.start.character)); - edits.reverse(); - - self.edit(&None, &path)?; - - let mut lines: Vec = self.call(None, "getline", json!([1, '$']))?; - let lines_len_prev = lines.len(); - let fixendofline = self.eval::<_, u8>("&fixendofline")? == 1; - if lines.last().map(String::is_empty) == Some(false) && fixendofline { - lines.push("".to_owned()); - } - - let mut lines = apply_TextEdits(&lines, &edits)?; - - if lines.last().map(String::is_empty) == Some(true) && fixendofline { - lines.pop(); - } - if lines.len() < lines_len_prev { - self.command(format!("{},{}d", lines.len() + 1, lines_len_prev))?; - } - if self.call::<_, i64>(None, "setline", json!([1, lines]))? != 0 { - bail!("Failed to set buffer content!"); - } - debug!("End apply TextEdits"); - Ok(()) - } - - fn update_quickfixlist(&mut self) -> Result<()> { - let qflist: Vec<_> = self - .diagnostics - .iter() - .flat_map(|(filename, diagnostics)| { - diagnostics - .iter() - .map(|dn| QuickfixEntry { - filename: filename.to_owned(), - lnum: dn.range.start.line + 1, - col: Some(dn.range.start.character + 1), - nr: dn.code.clone().map(|ns| ns.to_string()), - text: Some(dn.message.to_owned()), - typ: dn.severity.map(|sev| sev.to_quickfix_entry_type()), - }).collect::>() - }).collect(); - - match self.diagnosticsList { - DiagnosticsList::Quickfix => { - self.setqflist(&qflist)?; - } - DiagnosticsList::Location => { - self.setloclist(&qflist)?; - } - DiagnosticsList::Disabled => {} - } - - Ok(()) - } - - fn process_diagnostics(&mut self, filename: &str, diagnostics: &[Diagnostic]) -> Result<()> { - if !self.text_documents.contains_key(filename) { - return Ok(()); - } - - let lines: Vec<_> = self - .text_documents - .get(filename) - .map(|d| d.text.lines().map(ToOwned::to_owned).collect()) - .unwrap_or_default(); - - // Line diagnostics. - let mut line_diagnostics = HashMap::new(); - for entry in diagnostics { - let line = entry.range.start.line; - let mut msg = String::new(); - if let Some(severity) = entry.severity { - msg += &format!("[{:?}]", severity); - } - if let Some(ref code) = entry.code { - let s = code.to_string(); - if !s.is_empty() { - msg += &format!("[{}]", s); - } - } - msg += &entry.message; - line_diagnostics.insert((filename.to_owned(), line), msg); - } - self.line_diagnostics.retain(|&(ref f, _), _| f != filename); - self.line_diagnostics.extend(line_diagnostics); - - // Signs. - let mut signs: Vec<_> = diagnostics - .iter() - .map(|dn| { - let line = dn.range.start.line; - let text = lines - .get(line as usize) - .map(|l| l.to_string()) - .unwrap_or_default(); - - Sign::new(line + 1, text, dn.severity) - }).collect(); - - // There might be multiple diagnostics for one line. Show only highest severity. - signs.sort_unstable(); - signs.dedup(); - if let Some(diagnosticSignsMax) = self.diagnosticsSignsMax { - signs.truncate(diagnosticSignsMax as usize); - } - - self.signs.insert(filename.to_owned(), signs); - - // Highlight. - let diagnosticsDisplay = self.diagnosticsDisplay.clone(); - - let mut highlights = vec![]; - for dn in diagnostics { - let line = dn.range.start.line; - let character_start = dn.range.start.character; - let character_end = dn.range.end.character; - - let severity = dn.severity.unwrap_or(DiagnosticSeverity::Hint); - let group = diagnosticsDisplay - .get(&severity.to_int()?) - .ok_or_else(|| err_msg("Failed to get display"))? - .texthl - .clone(); - // TODO: handle multi-line range. - let text = lines - .get(line as usize) - .and_then(|l| l.get((character_start as usize)..(character_end as usize))) - .map(ToOwned::to_owned) - .unwrap_or_default(); - - highlights.push(Highlight { - line, - character_start, - character_end, - group, - text, - }); - } - // dedup? - self.highlights.insert(filename.to_owned(), highlights); - - if !self.is_nvim { - // Clear old highlights. - let ids = self.highlight_match_ids.clone(); - self.notify(None, "s:MatchDelete", json!([ids]))?; - - // Group diagnostics by severity so we can highlight them - // in a single call. - let mut match_groups: HashMap<_, Vec<_>> = HashMap::new(); - - for dn in diagnostics { - let severity = dn - .severity - .unwrap_or(DiagnosticSeverity::Information) - .to_int()?; - match_groups - .entry(severity) - .or_insert_with(Vec::new) - .push(dn); - } - - let mut new_match_ids = Vec::new(); - - for (severity, dns) in match_groups { - let hl_group = diagnosticsDisplay - .get(&severity) - .ok_or_else(|| err_msg("Failed to get display"))? - .texthl - .clone(); - let ranges: Vec> = dns - .iter() - .flat_map(|dn| { - if dn.range.start.line == dn.range.end.line { - let length = dn.range.end.character - dn.range.start.character; - // Vim line numbers are 1 off - // `matchaddpos` expects an array of [line, col, length] - // for each match. - vec![vec![ - dn.range.start.line + 1, - dn.range.start.character + 1, - length, - ]] - } else { - let mut middleLines: Vec<_> = (dn.range.start.line + 1 - ..dn.range.end.line) - .map(|l| vec![l + 1]) - .collect(); - let startLine = vec![ - dn.range.start.line + 1, - dn.range.start.character + 1, - 999_999, //Clear to the end of the line - ]; - let endLine = - vec![dn.range.end.line + 1, 1, dn.range.end.character + 1]; - middleLines.push(startLine); - middleLines.push(endLine); - middleLines - } - }).collect(); - - let match_id = self.call(None, "matchaddpos", json!([hl_group, ranges]))?; - new_match_ids.push(match_id); - } - self.highlight_match_ids = new_match_ids; - } - - Ok(()) - } - - fn display_locations(&mut self, locations: &[Location]) -> Result<()> { - let location_to_quickfix_entry = - |state: &mut Self, loc: &Location| -> Result { - let filename = loc.uri.filepath()?.to_string_lossy().into_owned(); - let start = loc.range.start; - let text = state.get_line(&filename, start.line).unwrap_or_default(); - - Ok(QuickfixEntry { - filename, - lnum: start.line + 1, - col: Some(start.character + 1), - text: Some(text), - nr: None, - typ: None, - }) - }; - - match self.get(|state| Ok(state.selectionUI.clone()))? { - SelectionUI::FZF => { - let cwd: String = self.eval("getcwd()")?; - let source: Result> = locations - .iter() - .map(|loc| { - let filename = loc.uri.filepath()?; - let start = loc.range.start; - let text = self.get_line(&filename, start.line).unwrap_or_default(); - let relpath = diff_paths(&filename, Path::new(&cwd)).unwrap_or(filename); - Ok(format!( - "{}:{}:{}:\t{}", - relpath.to_string_lossy(), - start.line + 1, - start.character + 1, - text - )) - }).collect(); - let source = source?; - - self.call::<_, u8>( - None, - "s:FZF", - json!([source, format!("s:{}", NOTIFICATION__FZFSinkLocation)]), - )?; - } - SelectionUI::Quickfix => { - let list: Result> = locations - .iter() - .map(|loc| location_to_quickfix_entry(self, loc)) - .collect(); - let list = list?; - self.setqflist(&list)?; - self.echo("Quickfix list updated.")?; - } - SelectionUI::LocationList => { - let list: Result> = locations - .iter() - .map(|loc| location_to_quickfix_entry(self, loc)) - .collect(); - let list = list?; - self.setloclist(&list)?; - self.echo("Location list updated.")?; - } - } - Ok(()) - } - - fn registerCMSource(&mut self, languageId: &str, result: &Value) -> Result<()> { - info!("Begin register NCM source"); - let exists_CMRegister: u64 = self.eval("exists('g:cm_matcher')")?; - if exists_CMRegister == 0 { - return Ok(()); - } - - let result: InitializeResult = serde_json::from_value(result.clone())?; - if result.capabilities.completion_provider.is_none() { - return Ok(()); - } - - let trigger_patterns = result - .capabilities - .completion_provider - .map(|opt| { - let strings: Vec<_> = opt - .trigger_characters - .unwrap_or_default() - .iter() - .map(|c| regex::escape(c)) - .collect(); - strings - }).unwrap_or_default(); - - self.call::<_, u8>( - None, - "cm#register_source", - json!([{ - "name": format!("LanguageClient_{}", languageId), - "priority": 9, - "scopes": [languageId], - "cm_refresh_patterns": trigger_patterns, - "abbreviation": "LC", - "cm_refresh": REQUEST__NCMRefresh, - }]), - )?; - info!("End register NCM source"); - Ok(()) - } - - fn registerNCM2Source(&mut self, languageId: &str, result: &Value) -> Result<()> { - info!("Begin register NCM2 source"); - let exists_ncm2: u64 = self.eval("exists('g:ncm2_loaded')")?; - if exists_ncm2 == 0 { - return Ok(()); - } - - let result: InitializeResult = serde_json::from_value(result.clone())?; - if result.capabilities.completion_provider.is_none() { - return Ok(()); - } - - let trigger_patterns = result - .capabilities - .completion_provider - .map(|opt| { - let strings: Vec<_> = opt - .trigger_characters - .unwrap_or_default() - .iter() - .map(|c| regex::escape(c)) - .collect(); - strings - }).unwrap_or_default(); - - self.call::<_, u8>( - None, - "ncm2#register_source", - json!([{ - "name": format!("LanguageClient_{}", languageId), - "priority": 9, - "scope": [languageId], - "complete_pattern": trigger_patterns, - "mark": "LC", - "on_complete": REQUEST__NCM2OnComplete, - }]), - )?; - info!("End register NCM2 source"); - Ok(()) - } - - fn get_line>(&mut self, path: P, line: u64) -> Result { - let value = self.call( - None, - "getbufline", - json!([path.as_ref().to_string_lossy(), line + 1]), - )?; - let mut texts: Vec = serde_json::from_value(value)?; - let mut text = texts.pop().unwrap_or_default(); - - if text.is_empty() { - let reader = BufReader::new(File::open(path)?); - text = reader - .lines() - .nth(line.to_usize()?) - .ok_or_else(|| format_err!("Failed to get line! line: {}", line))??; - } - - Ok(text.trim().into()) - } - - fn try_handle_command_by_client(&mut self, cmd: &Command) -> Result { - if !CommandsClient.contains(&cmd.command.as_str()) { - return Ok(false); - } - - if cmd.command == "java.apply.workspaceEdit" { - if let Some(ref edits) = cmd.arguments { - for edit in edits { - let edit: WorkspaceEdit = serde_json::from_value(edit.clone())?; - self.apply_WorkspaceEdit(&edit, &Value::Null)?; - } - } - } else { - bail!("Not implemented: {}", cmd.command); - } - - Ok(true) - } - - fn cleanup(&mut self, languageId: &str) -> Result<()> { - info!("Begin cleanup"); - - let root = self - .roots - .get(languageId) - .cloned() - .ok_or_else(|| format_err!("No project root found! languageId: {}", languageId))?; - - let mut filenames = vec![]; - for f in self.diagnostics.keys() { - if f.starts_with(&root) { - filenames.push(f.clone()); - } - } - for f in filenames { - self.process_diagnostics(&f, &[])?; - } - self.languageClient_handleCursorMoved(&Value::Null)?; - - self.diagnostics.retain(|f, _| !f.starts_with(&root)); - self.update_quickfixlist()?; - - self.writers.remove(languageId); - self.child_ids.remove(languageId); - self.last_cursor_line = 0; - self.text_documents.retain(|f, _| !f.starts_with(&root)); - self.roots.remove(languageId); - - self.command(vec![ - format!("let {}=0", VIM__ServerStatus), - format!("let {}=''", VIM__ServerStatusMessage), - ])?; - self.notify(None, "s:ExecuteAutocmd", "LanguageClientStopped")?; - - info!("End cleanup"); - Ok(()) - } - - fn preview(&mut self, lines: &[S]) -> Result<()> - where - S: AsRef + Serialize, - { - let bufname = "__LanguageClient__"; - - let mut cmd = String::new(); - cmd += "silent! pedit! +setlocal\\ buftype=nofile\\ filetype=markdown\\ nobuflisted\\ noswapfile\\ nonumber "; - cmd += bufname; - self.command(cmd)?; - - if self.get(|state| Ok(state.is_nvim))? { - let bufnr: u64 = serde_json::from_value(self.call(None, "bufnr", bufname)?)?; - self.call::<_, Option>( - None, - "nvim_buf_set_lines", - json!([bufnr, 0, -1, 0, lines]), - )?; - } else if self.call::<_, i64>(None, "setbufline", json!([bufname, 1, lines]))? != 0 { - bail!("Failed to set preview buffer content!"); - // TODO: removing existing bottom lines. - } - - Ok(()) - } - - /////// LSP /////// - - fn initialize(&mut self, params: &Value) -> Result { - info!("Begin {}", lsp::request::Initialize::METHOD); - let (languageId, filename): (String, String) = - self.gather_args(&[VimVar::LanguageId, VimVar::Filename], params)?; - let (rootPath, has_snippet_support): (Option, u64) = self.gather_args( - &[ - ("rootPath", "v:null"), - ("hasSnippetSupport", "s:hasSnippetSupport()"), - ], - params, - )?; - let root = if let Some(r) = rootPath { - r - } else { - let rootMarkers = self.get(|state| Ok(state.rootMarkers.clone()))?; - let root = get_rootPath(Path::new(&filename), &languageId, &rootMarkers)? - .to_string_lossy() - .into_owned(); - self.echomsg_ellipsis(format!("LanguageClient project root: {}", root))?; - root - }; - info!("Project root: {}", root); - let has_snippet_support = has_snippet_support > 0; - self.update(|state| Ok(state.roots.insert(languageId.clone(), root.clone())))?; - - let initialization_options = self - .get_workspace_settings(&root) - .map(|s| s["initializationOptions"].clone()) - .unwrap_or_else(|err| { - warn!("Failed to get initializationOptions: {}", err); - json!(Value::Null) - }); - let initialization_options = - get_default_initializationOptions(&languageId).combine(&initialization_options); - let initialization_options = if initialization_options.is_null() { - None - } else { - Some(initialization_options) - }; - - let trace = self.trace.clone(); - - let result: Value = self.call( - Some(&languageId), - lsp::request::Initialize::METHOD, - InitializeParams { - process_id: Some(u64::from(std::process::id())), - root_path: Some(root.clone()), - root_uri: Some(root.to_url()?), - initialization_options, - capabilities: ClientCapabilities { - text_document: Some(TextDocumentClientCapabilities { - completion: Some(CompletionCapability { - completion_item: Some(CompletionItemCapability { - snippet_support: Some(has_snippet_support), - ..CompletionItemCapability::default() - }), - ..CompletionCapability::default() - }), - ..TextDocumentClientCapabilities::default() - }), - workspace: Some(WorkspaceClientCapabilities { - apply_edit: Some(true), - did_change_watched_files: Some(GenericCapability { - dynamic_registration: Some(true), - }), - ..WorkspaceClientCapabilities::default() - }), - ..ClientCapabilities::default() - }, - trace, - workspace_folders: None, - }, - )?; - - self.update(|state| { - state - .capabilities - .insert(languageId.clone(), result.clone()); - Ok(()) - })?; - - info!("End {}", lsp::request::Initialize::METHOD); - - if let Err(e) = self.registerCMSource(&languageId, &result) { - let message = format!("LanguageClient: failed to register as NCM source: {}", e); - error!("{}\n{:?}", message, e); - self.echoerr(message)?; - } - if let Err(e) = self.registerNCM2Source(&languageId, &result) { - let message = format!("LanguageClient: failed to register as NCM source: {}", e); - error!("{}\n{:?}", message, e); - self.echoerr(message)?; - } - - Ok(result) - } - - fn initialized(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::Initialized::METHOD); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - self.notify( - Some(&languageId), - lsp::notification::Initialized::METHOD, - InitializedParams {}, - )?; - info!("End {}", lsp::notification::Initialized::METHOD); - Ok(()) - } - - pub fn textDocument_hover(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::HoverRequest::METHOD); - let (languageId, filename, line, character, handle): ( - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - - let result = self.call( - Some(&languageId), - lsp::request::HoverRequest::METHOD, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - }, - )?; - - if !handle { - return Ok(result); - } - - let hover: Option = serde_json::from_value(result.clone())?; - if let Some(hover) = hover { - let use_preview = match &self.hoverPreview { - HoverPreviewOption::Always => true, - HoverPreviewOption::Never => false, - HoverPreviewOption::Auto => hover.lines_len() > 1, - }; - if use_preview { - self.preview(&hover.to_display())? - } else { - self.echo_ellipsis(hover.to_string())? - } - } - - info!("End {}", lsp::request::HoverRequest::METHOD); - Ok(result) - } - - /// Generic find locations, e.g, definitions, references. - pub fn find_locations(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - let (method,): (String,) = self.gather_args(&["method"], params)?; - info!("Begin {}", method); - let (languageId, filename, line, character, handle, goto_cmd): ( - String, - String, - u64, - u64, - bool, - Option, - ) = self.gather_args( - &[ - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - VimVar::GotoCmd, - ], - params, - )?; - - let params = serde_json::to_value(TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - })?.combine(params); - - let result = self.call(Some(&languageId), &method, ¶ms)?; - - if !handle { - return Ok(result); - } - - let response: Option = result.clone().to_lsp()?; - - match response { - None => { - self.echowarn("Not found!")?; - return Ok(Value::Null); - } - Some(GotoDefinitionResponse::Scalar(loc)) => { - self.edit(&goto_cmd, loc.uri.filepath()?)?; - self.cursor(loc.range.start.line + 1, loc.range.start.character + 1)?; - } - Some(GotoDefinitionResponse::Array(arr)) => match arr.len() { - 0 => self.echowarn("Not found!")?, - 1 => { - let loc = arr.get(0).ok_or_else(|| err_msg("Not found!"))?; - self.edit(&goto_cmd, loc.uri.filepath()?)?; - self.cursor(loc.range.start.line + 1, loc.range.start.character + 1)?; - let cur_file: String = self.eval("expand('%')")?; - self.echomsg_ellipsis(format!( - "[LC]: {} {}:{}", - cur_file, - loc.range.start.line + 1, - loc.range.start.character + 1 - ))?; - } - _ => self.display_locations(&arr)?, - }, - }; - - info!("End {}", method); - Ok(result) - } - - pub fn textDocument_rename(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::Rename::METHOD); - let (buftype, languageId, filename, line, character, cword, new_name, handle): ( - String, - String, - String, - u64, - u64, - String, - Option, - bool, - ) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Cword, - VimVar::NewName, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let mut new_name = new_name.unwrap_or_default(); - if new_name.is_empty() { - let value = self.call(None, "s:getInput", ["Rename to: ".to_owned(), cword])?; - new_name = serde_json::from_value(value)?; - } - if new_name.is_empty() { - return Ok(Value::Null); - } - - let result = self.call( - Some(&languageId), - lsp::request::Rename::METHOD, - RenameParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - new_name, - }, - )?; - - if !handle || result == Value::Null { - return Ok(result); - } - - let edit: WorkspaceEdit = serde_json::from_value(result.clone())?; - self.apply_WorkspaceEdit(&edit, params)?; - - info!("End {}", lsp::request::Rename::METHOD); - Ok(result) - } - - pub fn textDocument_documentSymbol(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::DocumentSymbolRequest::METHOD); - - let (buftype, languageId, filename, handle): (String, String, String, bool) = self - .gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Handle, - ], - params, - )?; - - if !buftype.is_empty() { - return Ok(Value::Null); - } - - let result = self.call( - Some(&languageId), - lsp::request::DocumentSymbolRequest::METHOD, - DocumentSymbolParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - }, - )?; - - if !handle { - return Ok(result); - } - - let symbols: Vec = serde_json::from_value(result.clone())?; - - match self.get(|state| Ok(state.selectionUI.clone()))? { - SelectionUI::FZF => { - let source: Vec<_> = symbols - .iter() - .map(|sym| { - let start = sym.location.range.start; - format!( - "{}:{}:\t{}\t\t{:?}", - start.line + 1, - start.character + 1, - sym.name, - sym.kind - ) - }).collect(); - - self.call::<_, u8>( - None, - "s:FZF", - json!([source, format!("s:{}", NOTIFICATION__FZFSinkLocation)]), - )?; - } - SelectionUI::Quickfix => { - let list: Result> = symbols.iter().map(QuickfixEntry::from_lsp).collect(); - let list = list?; - self.setqflist(&list)?; - self.echo("Document symbols populated to quickfix list.")?; - } - SelectionUI::LocationList => { - let list: Result> = symbols.iter().map(QuickfixEntry::from_lsp).collect(); - let list = list?; - self.setloclist(&list)?; - self.echo("Document symbols populated to location list.")?; - } - } - - info!("End {}", lsp::request::DocumentSymbolRequest::METHOD); - Ok(result) - } - - pub fn textDocument_codeAction(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::CodeActionRequest::METHOD); - let (buftype, languageId, filename, line, character, handle): ( - String, - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - // Unify filename. - let filename = filename.canonicalize(); - - let diagnostics: Vec<_> = self - .diagnostics - .get(&filename) - .unwrap_or(&vec![]) - .iter() - .filter(|dn| { - let start = dn.range.start; - let end = dn.range.end; - (line, character) >= (start.line, start.character) - && (line, character) < (end.line, end.character) - }).cloned() - .collect(); - let result: Value = self.call( - Some(&languageId), - lsp::request::CodeActionRequest::METHOD, - CodeActionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - range: Range { - start: Position { line, character }, - end: Position { line, character }, - }, - context: CodeActionContext { - diagnostics, - only: None, - }, - }, - )?; - - let commands: Vec = serde_json::from_value(result.clone())?; - - let source: Vec<_> = commands - .iter() - .map(|cmd| format!("{}: {}", cmd.command, cmd.title)) - .collect(); - - self.update(|state| { - state.stashed_codeAction_commands = commands; - Ok(()) - })?; - - if !handle { - return Ok(result); - } - - self.call::<_, u8>(None, "s:FZF", json!([source, NOTIFICATION__FZFSinkCommand]))?; - - info!("End {}", lsp::request::CodeActionRequest::METHOD); - Ok(result) - } - - pub fn textDocument_completion(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::Completion::METHOD); - - let (buftype, languageId, filename, line, character, handle): ( - String, - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let result = self.call( - Some(&languageId), - lsp::request::Completion::METHOD, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - }, - )?; - - if !handle { - return Ok(result); - } - - info!("End {}", lsp::request::Completion::METHOD); - Ok(result) - } - - pub fn textDocument_signatureHelp(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::SignatureHelpRequest::METHOD); - let (buftype, languageId, filename, line, character, handle): ( - String, - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let result = self.call( - Some(&languageId), - lsp::request::SignatureHelpRequest::METHOD, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - }, - )?; - - if !handle || result == Value::Null { - return Ok(result); - } - - let help: SignatureHelp = serde_json::from_value(result)?; - if help.signatures.is_empty() { - return Ok(Value::Null); - } - let active_signature = help - .signatures - .get(help.active_signature.unwrap_or(0).to_usize()?) - .ok_or_else(|| err_msg("Failed to get active signature"))?; - let active_parameter: Option<&ParameterInformation>; - if let Some(ref parameters) = active_signature.parameters { - active_parameter = parameters.get(help.active_parameter.unwrap_or(0).to_usize()?); - } else { - active_parameter = None; - } - - if let Some(active_parameter) = active_parameter { - let mut cmd = "echo".to_owned(); - let chunks: Vec<&str> = active_signature - .label - .split(&active_parameter.label) - .collect(); - if chunks.len() == 2 { - let begin = chunks.get(0).cloned().unwrap_or_default(); - let end = chunks.get(1).cloned().unwrap_or_default(); - cmd += &format!( - " | echon '{}' | echohl WarningMsg | echon '{}' | echohl None | echon '{}'", - begin, active_parameter.label, end - ); - } else { - // Active parameter is not part of signature. - cmd += &format!(" | echo '{}'", active_signature.label); - } - self.command(&cmd)?; - } else { - self.echo(&active_signature.label)?; - } - - info!("End {}", lsp::request::SignatureHelpRequest::METHOD); - Ok(Value::Null) - } - - pub fn textDocument_references(&mut self, params: &Value) -> Result { - info!("Begin {}", lsp::request::References::METHOD); - - let (buftype, include_declaration): (String, bool) = - self.gather_args(&[VimVar::Buftype, VimVar::IncludeDeclaration], params)?; - if !buftype.is_empty() { - return Ok(Value::Null); - } - - let params = json!({ - "method": lsp::request::References::METHOD, - "context": ReferenceContext { - include_declaration, - } - }).combine(params); - - let result = self.find_locations(¶ms)?; - - info!("End {}", lsp::request::References::METHOD); - Ok(result) - } - - pub fn textDocument_formatting(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::Formatting::METHOD); - let (buftype, languageId, filename, handle): (String, String, String, bool) = self - .gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let (tab_size, insert_spaces): (u64, u64) = - self.eval(["shiftwidth()", "&expandtab"].as_ref())?; - let insert_spaces = insert_spaces == 1; - let result = self.call( - Some(&languageId), - lsp::request::Formatting::METHOD, - DocumentFormattingParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - options: FormattingOptions { - tab_size, - insert_spaces, - properties: HashMap::new(), - }, - }, - )?; - - if !handle { - return Ok(result); - } - - let text_edits: Option> = serde_json::from_value(result.clone())?; - let text_edits = text_edits.unwrap_or_default(); - let edit = lsp::WorkspaceEdit { - changes: Some(hashmap!{filename.to_url()? => text_edits}), - document_changes: None, - }; - self.apply_WorkspaceEdit(&edit, params)?; - info!("End {}", lsp::request::Formatting::METHOD); - Ok(result) - } - - pub fn textDocument_rangeFormatting(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::RangeFormatting::METHOD); - let (buftype, languageId, filename, handle, start_line, end_line): ( - String, - String, - String, - bool, - u64, - u64, - ) = self.gather_args( - &[ - VimVar::Buftype.to_key().as_str(), - VimVar::LanguageId.to_key().as_str(), - VimVar::Filename.to_key().as_str(), - VimVar::Handle.to_key().as_str(), - "LSP#range_start_line()", - "LSP#range_end_line()", - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let (tab_size, insert_spaces): (u64, u64) = - self.eval(["shiftwidth()", "&expandtab"].as_ref())?; - let insert_spaces = insert_spaces == 1; - let result = self.call( - Some(&languageId), - lsp::request::RangeFormatting::METHOD, - DocumentRangeFormattingParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - options: FormattingOptions { - tab_size, - insert_spaces, - properties: HashMap::new(), - }, - range: Range { - start: Position { - line: start_line, - character: 0, - }, - end: Position { - line: end_line, - character: 0, - }, - }, - }, - )?; - - if !handle { - return Ok(result); - } - - let text_edits: Option> = serde_json::from_value(result.clone())?; - let text_edits = text_edits.unwrap_or_default(); - let edit = lsp::WorkspaceEdit { - changes: Some(hashmap!{filename.to_url()? => text_edits}), - document_changes: None, - }; - self.apply_WorkspaceEdit(&edit, params)?; - info!("End {}", lsp::request::RangeFormatting::METHOD); - Ok(result) - } - - pub fn completionItem_resolve(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::ResolveCompletionItem::METHOD); - let (buftype, languageId, handle): (String, String, bool) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Handle], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - let (completion_item,): (CompletionItem,) = - self.gather_args(&["completionItem"], params)?; - - let result = self.call( - Some(&languageId), - lsp::request::ResolveCompletionItem::METHOD, - completion_item, - )?; - - if !handle { - return Ok(result); - } - - // TODO: proper integration. - let msg = format!("comletionItem/resolve result not handled: {:?}", result); - warn!("{}", msg); - self.echowarn(&msg)?; - - info!("End {}", lsp::request::ResolveCompletionItem::METHOD); - Ok(Value::Null) - } - - pub fn workspace_symbol(&mut self, params: &Value) -> Result { - self.textDocument_didChange(params)?; - info!("Begin {}", lsp::request::WorkspaceSymbol::METHOD); - let (buftype, languageId, handle): (String, String, bool) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Handle], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let (query,): (String,) = self.gather_args(&[("query", "")], params)?; - let result = self.call( - Some(&languageId), - lsp::request::WorkspaceSymbol::METHOD, - WorkspaceSymbolParams { query }, - )?; - - if !handle { - return Ok(result); - } - - let symbols: Vec = serde_json::from_value(result.clone())?; - - match self.get(|state| Ok(state.selectionUI.clone()))? { - SelectionUI::FZF => { - let cwd: String = self.eval("getcwd()")?; - let source: Result> = symbols - .iter() - .map(|sym| { - let filename = sym.location.uri.filepath()?; - let relpath = diff_paths(&filename, Path::new(&cwd)).unwrap_or(filename); - let start = sym.location.range.start; - Ok(format!( - "{}:{}:{}:\t{}\t\t{:?}", - relpath.to_string_lossy(), - start.line + 1, - start.character + 1, - sym.name, - sym.kind - )) - }).collect(); - let source = source?; - - self.call::<_, u8>( - None, - "s:FZF", - json!([source, format!("s:{}", NOTIFICATION__FZFSinkLocation)]), - )?; - } - SelectionUI::Quickfix => { - let list: Result> = symbols.iter().map(QuickfixEntry::from_lsp).collect(); - let list = list?; - self.setqflist(&list)?; - self.echo("Workspace symbols populated to quickfix list.")?; - } - SelectionUI::LocationList => { - let list: Result> = symbols.iter().map(QuickfixEntry::from_lsp).collect(); - let list = list?; - self.setloclist(&list)?; - self.echo("Workspace symbols populated to location list.")?; - } - } - - info!("End {}", lsp::request::WorkspaceSymbol::METHOD); - Ok(result) - } - - pub fn workspace_executeCommand(&mut self, params: &Value) -> Result { - info!("Begin {}", lsp::request::ExecuteCommand::METHOD); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - let (command, arguments): (String, Vec) = - self.gather_args(&["command", "arguments"], params)?; - - let result = self.call( - Some(&languageId), - lsp::request::ExecuteCommand::METHOD, - ExecuteCommandParams { command, arguments }, - )?; - info!("End {}", lsp::request::ExecuteCommand::METHOD); - Ok(result) - } - - pub fn workspace_applyEdit(&mut self, params: &Value) -> Result { - info!("Begin {}", lsp::request::ApplyWorkspaceEdit::METHOD); - - let params: ApplyWorkspaceEditParams = params.clone().to_lsp()?; - self.apply_WorkspaceEdit(¶ms.edit, &Value::Null)?; - - info!("End {}", lsp::request::ApplyWorkspaceEdit::METHOD); - - Ok(serde_json::to_value(ApplyWorkspaceEditResponse { - applied: true, - })?) - } - - pub fn workspace_didChangeConfiguration(&mut self, params: &Value) -> Result<()> { - info!( - "Begin {}", - lsp::notification::DidChangeConfiguration::METHOD - ); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - let (settings,): (Value,) = self.gather_args(&["settings"], params)?; - - self.notify( - Some(languageId.as_str()), - lsp::notification::DidChangeConfiguration::METHOD, - DidChangeConfigurationParams { settings }, - )?; - info!("End {}", lsp::notification::DidChangeConfiguration::METHOD); - Ok(()) - } - - pub fn textDocument_didOpen(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::DidOpenTextDocument::METHOD); - let (buftype, languageId, filename, text): (String, String, String, Vec) = self - .gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Text, - ], - params, - )?; - - if !buftype.is_empty() || languageId.is_empty() { - return Ok(()); - } - - let text_document = TextDocumentItem { - uri: filename.to_url()?, - language_id: languageId.clone(), - version: 0, - text: text.join("\n"), - }; - - self.update(|state| { - Ok(state - .text_documents - .insert(filename.clone(), text_document.clone())) - })?; - - self.notify( - Some(&languageId), - lsp::notification::DidOpenTextDocument::METHOD, - DidOpenTextDocumentParams { text_document }, - )?; - - self.command("setlocal omnifunc=LanguageClient#complete")?; - let root = self.roots.get(&languageId).cloned().unwrap_or_default(); - self.notify( - None, - "setbufvar", - json!([filename, "LanguageClient_projectRoot", root]), - )?; - self.notify(None, "s:ExecuteAutocmd", "LanguageClientBufReadPost")?; - - info!("End {}", lsp::notification::DidOpenTextDocument::METHOD); - Ok(()) - } - - pub fn textDocument_didChange(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::DidChangeTextDocument::METHOD); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(()); - } - if !self.get(|state| Ok(state.text_documents.contains_key(&filename)))? { - info!("Not opened yet. Switching to didOpen."); - return self.textDocument_didOpen(params); - } - - let (text,): (Vec,) = self.gather_args(&[VimVar::Text], params)?; - - let text = text.join("\n"); - let text_state = self - .get(|state| { - state - .text_documents - .get(&filename) - .ok_or_else(|| { - format_err!("TextDocumentItem not found! filename: {}", filename) - }).map(|doc| doc.text.clone()) - }).unwrap_or_default(); - if text == text_state { - info!("Texts equal. Skipping didChange."); - return Ok(()); - } - - let version = self.update(|state| { - let document = state.text_documents.get_mut(&filename).ok_or_else(|| { - format_err!("Failed to get TextDocumentItem! filename: {}", filename) - })?; - - let version = document.version + 1; - document.version = version; - document.text = text.clone(); - - if state.change_throttle.is_some() { - let metadata = state - .text_documents_metadata - .entry(filename.clone()) - .or_insert_with(TextDocumentItemMetadata::default); - metadata.last_change = Instant::now(); - } - Ok(version) - })?; - - self.notify( - Some(&languageId), - lsp::notification::DidChangeTextDocument::METHOD, - DidChangeTextDocumentParams { - text_document: VersionedTextDocumentIdentifier { - uri: filename.to_url()?, - version: Some(version), - }, - content_changes: vec![TextDocumentContentChangeEvent { - range: None, - range_length: None, - text, - }], - }, - )?; - - info!("End {}", lsp::notification::DidChangeTextDocument::METHOD); - Ok(()) - } - - pub fn textDocument_didSave(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::DidSaveTextDocument::METHOD); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(()); - } - let uri = filename.to_url()?; - - self.notify( - Some(&languageId), - lsp::notification::DidSaveTextDocument::METHOD, - DidSaveTextDocumentParams { - text_document: TextDocumentIdentifier { uri: uri.clone() }, - }, - )?; - - info!("End {}", lsp::notification::DidSaveTextDocument::METHOD); - Ok(()) - } - - pub fn textDocument_didClose(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::DidCloseTextDocument::METHOD); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(()); - } - - self.notify( - Some(&languageId), - lsp::notification::DidCloseTextDocument::METHOD, - DidCloseTextDocumentParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - }, - )?; - info!("End {}", lsp::notification::DidCloseTextDocument::METHOD); - Ok(()) - } - - pub fn textDocument_publishDiagnostics(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::PublishDiagnostics::METHOD); - let params: PublishDiagnosticsParams = params.clone().to_lsp()?; - if !self.get(|state| Ok(state.diagnosticsEnable))? { - return Ok(()); - } - - let mut filename = params.uri.filepath()?.to_string_lossy().into_owned(); - // Workaround bug: remove first '/' in case of '/C:/blabla'. - if filename.chars().nth(0) == Some('/') && filename.chars().nth(2) == Some(':') { - filename.remove(0); - } - // Unify name to avoid mismatch due to case insensitivity. - let filename = filename.canonicalize(); - - let diagnostics = params.diagnostics; - - self.diagnostics - .insert(filename.clone(), diagnostics.clone()); - self.update_quickfixlist()?; - - let current_filename: String = self.eval(VimVar::Filename)?; - if filename != current_filename.canonicalize() { - return Ok(()); - } - self.process_diagnostics(¤t_filename, &diagnostics)?; - self.languageClient_handleCursorMoved(&Value::Null)?; - self.notify(None, "s:ExecuteAutocmd", "LanguageClientDiagnosticsChanged")?; - - info!("End {}", lsp::notification::PublishDiagnostics::METHOD); - Ok(()) - } - - pub fn window_logMessage(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::LogMessage::METHOD); - let params: LogMessageParams = params.clone().to_lsp()?; - let threshold = self.get(|state| state.windowLogMessageLevel.to_int())?; - if params.typ.to_int()? > threshold { - return Ok(()); - } - - let msg = format!("[{:?}] {}", params.typ, params.message); - self.echomsg(&msg)?; - info!("End {}", lsp::notification::LogMessage::METHOD); - Ok(()) - } - - pub fn window_showMessage(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::ShowMessage::METHOD); - let params: ShowMessageParams = params.clone().to_lsp()?; - let msg = format!("[{:?}] {}", params.typ, params.message); - self.echomsg(&msg)?; - info!("End {}", lsp::notification::ShowMessage::METHOD); - Ok(()) - } - - pub fn client_registerCapability(&mut self, languageId: &str, params: &Value) -> Result { - info!("Begin {}", lsp::request::RegisterCapability::METHOD); - let params: RegistrationParams = params.clone().to_lsp()?; - for r in ¶ms.registrations { - match r.method.as_str() { - lsp::notification::DidChangeWatchedFiles::METHOD => { - let opt: DidChangeWatchedFilesRegistrationOptions = - serde_json::from_value(r.register_options.clone().unwrap_or_default())?; - if !self.watchers.contains_key(languageId) { - let (watcher_tx, watcher_rx) = channel(); - // TODO: configurable duration. - let watcher = notify::watcher(watcher_tx, Duration::from_secs(2))?; - self.watchers.insert(languageId.to_owned(), watcher); - self.watcher_rxs.insert(languageId.to_owned(), watcher_rx); - } - - if let Some(ref mut watcher) = self.watchers.get_mut(languageId) { - for w in &opt.watchers { - let recursive_mode = if w.glob_pattern.ends_with("**") { - notify::RecursiveMode::Recursive - } else { - notify::RecursiveMode::NonRecursive - }; - watcher - .watch(w.glob_pattern.trim_right_matches("**"), recursive_mode)?; - } - } - } - _ => { - warn!("Unknown registration: {:?}", r); - } - } - } - - self.registrations.extend(params.registrations); - info!("End {}", lsp::request::RegisterCapability::METHOD); - Ok(Value::Null) - } - - pub fn client_unregisterCapability( - &mut self, - languageId: &str, - params: &Value, - ) -> Result { - info!("Begin {}", lsp::request::UnregisterCapability::METHOD); - let params: UnregistrationParams = params.clone().to_lsp()?; - let mut regs_removed = vec![]; - for r in ¶ms.unregisterations { - if let Some(idx) = self - .registrations - .iter() - .position(|i| i.id == r.id && i.method == r.method) - { - regs_removed.push(self.registrations.swap_remove(idx)); - } - } - - for r in ®s_removed { - match r.method.as_str() { - lsp::notification::DidChangeWatchedFiles::METHOD => { - let opt: DidChangeWatchedFilesRegistrationOptions = - serde_json::from_value(r.register_options.clone().unwrap_or_default())?; - if let Some(ref mut watcher) = self.watchers.get_mut(languageId) { - for w in opt.watchers { - watcher.unwatch(w.glob_pattern)?; - } - } - } - _ => { - warn!("Unknown registration: {:?}", r); - } - } - } - - info!("End {}", lsp::request::UnregisterCapability::METHOD); - Ok(Value::Null) - } - - pub fn exit(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::Exit::METHOD); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - - let result = self.notify( - Some(&languageId), - lsp::notification::Exit::METHOD, - Value::Null, - ); - if let Err(err) = result { - error!("Error: {:?}", err); - } - if let Err(err) = self.cleanup(&languageId) { - error!("Error: {:?}", err); - } - info!("End {}", lsp::notification::Exit::METHOD); - Ok(()) - } - - /////// Extensions by this plugin /////// - - pub fn languageClient_getState(&mut self, _params: &Value) -> Result { - info!("Begin {}", REQUEST__GetState); - let s = self.get(|state| Ok(serde_json::to_string(state)?))?; - info!("End {}", REQUEST__GetState); - Ok(Value::String(s)) - } - - pub fn languageClient_isAlive(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__IsAlive); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - let is_alive = self.get(|state| Ok(state.writers.contains_key(&languageId)))?; - info!("End {}", REQUEST__IsAlive); - Ok(Value::Bool(is_alive)) - } - - pub fn languageClient_registerServerCommands(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__RegisterServerCommands); - let commands: HashMap> = params.clone().to_lsp()?; - self.update(|state| { - state.serverCommands.extend(commands); - Ok(()) - })?; - let exp = format!( - "let g:LanguageClient_serverCommands={}", - serde_json::to_string(&self.get(|state| Ok(state.serverCommands.clone()))?)? - ); - self.command(&exp)?; - info!("End {}", REQUEST__RegisterServerCommands); - Ok(Value::Null) - } - - pub fn languageClient_setLoggingLevel(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__SetLoggingLevel); - let (loggingLevel,): (log::LevelFilter,) = self.gather_args(&["loggingLevel"], params)?; - logger::update_settings(&self.logger, &self.loggingFile, loggingLevel)?; - self.loggingLevel = loggingLevel; - info!("End {}", REQUEST__SetLoggingLevel); - Ok(Value::Null) - } - - pub fn languageClient_setDiagnosticsList(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__SetDiagnosticsList); - let (diagnosticsList,): (DiagnosticsList,) = - self.gather_args(&["diagnosticsList"], params)?; - self.diagnosticsList = diagnosticsList; - info!("End {}", REQUEST__SetDiagnosticsList); - Ok(Value::Null) - } - - pub fn languageClient_registerHandlers(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__RegisterHandlers); - let handlers: HashMap = params.clone().to_lsp()?; - self.update(|state| { - state.user_handlers.extend(handlers); - Ok(()) - })?; - info!("End {}", REQUEST__RegisterHandlers); - Ok(Value::Null) - } - - pub fn languageClient_omniComplete(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__OmniComplete); - let result = self.textDocument_completion(params)?; - let result: Option = serde_json::from_value(result)?; - let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); - let mut matches = match result { - CompletionResponse::Array(arr) => arr, - CompletionResponse::List(list) => list.items, - }; - if !matches.iter().any(|m| m.sort_text.is_none()) { - matches.sort_by(|m1, m2| { - m1.sort_text - .as_ref() - .unwrap() - .cmp(m2.sort_text.as_ref().unwrap()) - }); - } - - let (complete_position,): (Option,) = - self.gather_args(&[("complete_position", "v:null")], params)?; - - let matches: Result> = matches - .iter() - .map(|item| VimCompleteItem::from_lsp(item, complete_position)) - .collect(); - let matches = matches?; - info!("End {}", REQUEST__OmniComplete); - Ok(serde_json::to_value(matches)?) - } - - pub fn languageClient_handleBufNewFile(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleBufNewFile); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() || filename.is_empty() { - return Ok(()); - } - let autoStart: u8 = self.eval("!!get(g:, 'LanguageClient_autoStart', 1)")?; - if autoStart == 1 { - let ret = self.languageClient_startServer(params); - // This is triggered from autocmd, silent all errors. - if let Err(err) = ret { - warn!("Failed to start language server automatically. {}", err); - } - } - - info!("End {}", NOTIFICATION__HandleBufNewFile); - Ok(()) - } - - pub fn languageClient_handleBufReadPost(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleBufReadPost); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() || filename.is_empty() { - return Ok(()); - } - - // File opened before. - if self.get(|state| Ok(state.text_documents.contains_key(&filename)))? { - info!("File is opened before."); - return Ok(()); - } - - let filename = filename.canonicalize(); - - if self.get(|state| Ok(state.writers.contains_key(&languageId)))? { - self.textDocument_didOpen(params)?; - - if let Some(diagnostics) = self.diagnostics.get(&filename).cloned() { - self.process_diagnostics(&filename, &diagnostics)?; - self.languageClient_handleCursorMoved(params)?; - } - } else { - let autoStart: u8 = self.eval("!!get(g:, 'LanguageClient_autoStart', 1)")?; - if autoStart == 1 { - let ret = self.languageClient_startServer(params); - // This is triggered from autocmd, silent all errors. - if let Err(err) = ret { - warn!("Failed to start language server automatically. {}", err); - } - } - } - - info!("End {}", NOTIFICATION__HandleBufReadPost); - Ok(()) - } - - pub fn languageClient_handleTextChanged(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleTextChanged); - let (buftype, filename): (String, String) = - self.gather_args(&[VimVar::Buftype, VimVar::Filename], params)?; - if !buftype.is_empty() { - info!( - "Skip handleTextChanged as buftype is non-empty: {}", - buftype - ); - } - let skip_notification = self.get(|state| { - if let Some(metadata) = state.text_documents_metadata.get(&filename) { - if let Some(throttle) = state.change_throttle { - if metadata.last_change.elapsed() < throttle { - return Ok(true); - } - } - } - Ok(false) - })?; - if skip_notification { - info!("Skip handleTextChanged due to throttling"); - return Ok(()); - } - - self.textDocument_didChange(params)?; - info!("End {}", NOTIFICATION__HandleTextChanged); - Ok(()) - } - - pub fn languageClient_handleBufWritePost(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleBufWritePost); - self.textDocument_didSave(params)?; - info!("End {}", NOTIFICATION__HandleBufWritePost); - Ok(()) - } - - pub fn languageClient_handleBufDelete(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleBufWritePost); - let (filename,): (String,) = self.gather_args(&[VimVar::Filename], params)?; - self.update(|state| { - state.text_documents.retain(|f, _| f != &filename); - state.diagnostics.retain(|f, _| f != &filename); - state.line_diagnostics.retain(|fl, _| fl.0 != filename); - state.signs.retain(|f, _| f != &filename); - Ok(()) - })?; - self.textDocument_didClose(params)?; - info!("End {}", NOTIFICATION__HandleBufWritePost); - Ok(()) - } - - pub fn languageClient_handleCursorMoved(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__HandleCursorMoved); - let (buftype, filename, line): (String, String, u64) = - self.gather_args(&[VimVar::Buftype, VimVar::Filename, VimVar::Line], params)?; - let (visible_line_start, visible_line_end): (u64, u64) = self.gather_args( - &["LSP#visible_line_start()", "LSP#visible_line_end()"], - params, - )?; - if !buftype.is_empty() && !self.diagnostics.contains_key(&filename) { - return Ok(()); - } - - if line != self.last_cursor_line { - self.last_cursor_line = line; - - let message = self - .line_diagnostics - .get(&(filename.clone(), line)) - .cloned() - .unwrap_or_default(); - - if message != self.last_line_diagnostic { - self.echo_ellipsis(&message)?; - self.last_line_diagnostic = message; - } - } - - let signs: Vec<_> = self - .signs - .entry(filename.clone()) - .or_insert_with(|| vec![]) - .iter() - .filter_map(|s| { - if s.line < visible_line_start + 1 || s.line > visible_line_end + 1 { - return None; - } - - Some(s.clone()) - }).collect(); - if Some(&signs) != self.signs_placed.get(&filename) { - let empty = vec![]; - - let (signs, cmds) = get_command_update_signs( - self.signs_placed.get(&filename).unwrap_or(&empty), - &signs, - &filename, - ); - self.signs_placed.insert(filename.clone(), signs); - - info!("Updating signs: {:?}", cmds); - self.command(&cmds)?; - } - - let highlights: Vec<_> = self - .highlights - .entry(filename.clone()) - .or_insert_with(|| vec![]) - .iter() - .filter_map(|h| { - if h.line < visible_line_start || h.line > visible_line_end { - return None; - } - - Some(h.clone()) - }).collect(); - - if Some(&highlights) != self.highlights_placed.get(&filename) && self.is_nvim { - let source = if let Some(source) = self.highlight_source { - source - } else { - let source = self.call( - None, - "nvim_buf_add_highlight", - json!([0, 0, "Error", 1, 1, 1]), - )?; - self.highlight_source = Some(source); - source - }; - - self.highlights_placed - .insert(filename.clone(), highlights.clone()); - - self.notify( - None, - "nvim_buf_clear_highlight", - json!([0, source, visible_line_start, visible_line_end]), - )?; - - self.notify(None, "s:AddHighlights", json!([source, highlights]))?; - } - - info!("End {}", NOTIFICATION__HandleCursorMoved); - Ok(()) - } - - pub fn languageClient_handleCompleteDone(&mut self, params: &Value) -> Result<()> { - let (filename, completed_item, line, character): ( - String, - VimCompleteItem, - u64, - u64, - ) = self.gather_args( - &[ - VimVar::Filename.to_key().as_str(), - "completed_item", - VimVar::Line.to_key().as_str(), - VimVar::Character.to_key().as_str(), - ], - params, - )?; - - let user_data = match completed_item.user_data { - Some(user_data) => user_data, - _ => return Ok(()), - }; - let user_data: VimCompleteItemUserData = serde_json::from_str(&user_data)?; - let lspitem = match user_data.lspitem { - Some(lspitem) => lspitem, - _ => return Ok(()), - }; - - let mut edits = vec![]; - if self.completionPreferTextEdit { - if let Some(edit) = lspitem.text_edit { - self.command("undo")?; - edits.push(edit.clone()); - }; - } - if let Some(aedits) = lspitem.additional_text_edits { - edits.extend(aedits.clone()); - }; - - if edits.is_empty() { - return Ok(()); - } - - self.apply_TextEdits(filename, &edits)?; - self.cursor(line + 1, character + 1) - } - - pub fn languageClient_FZFSinkLocation(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__FZFSinkLocation); - let params = match params { - Value::Array(ref arr) => Value::Array(arr.clone()), - _ => { - bail!("Expecting array params!"); - } - }; - - let lines: Vec = serde_json::from_value(params)?; - if lines.is_empty() { - err_msg("No selection!"); - } - - let location = lines - .get(0) - .ok_or_else(|| format_err!("Failed to get line! lines: {:?}", lines))? - .split('\t') - .next() - .ok_or_else(|| format_err!("Failed to parse: {:?}", lines))?; - let mut tokens: Vec<_> = location.split_terminator(':').collect(); - tokens.reverse(); - let filename: String = if tokens.len() > 2 { - let relpath = tokens - .pop() - .ok_or_else(|| format_err!("Failed to get file path! tokens: {:?}", tokens))? - .to_owned(); - let cwd: String = self.eval("getcwd()")?; - Path::new(&cwd).join(relpath).to_string_lossy().into_owned() - } else { - self.eval(VimVar::Filename)? - }; - let line = tokens - .pop() - .ok_or_else(|| format_err!("Failed to get line! tokens: {:?}", tokens))? - .to_int()? - - 1; - let character = tokens - .pop() - .ok_or_else(|| format_err!("Failed to get character! tokens: {:?}", tokens))? - .to_int()? - - 1; - - self.edit(&None, &filename)?; - self.cursor(line + 1, character + 1)?; - - info!("End {}", NOTIFICATION__FZFSinkLocation); - Ok(()) - } - - pub fn languageClient_FZFSinkCommand(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__FZFSinkCommand); - let (selection,): (String,) = self.gather_args(&["selection"], params)?; - let tokens: Vec<&str> = selection.splitn(2, ": ").collect(); - let command = tokens - .get(0) - .cloned() - .ok_or_else(|| format_err!("Failed to get command! tokens: {:?}", tokens))?; - let title = tokens - .get(1) - .cloned() - .ok_or_else(|| format_err!("Failed to get title! tokens: {:?}", tokens))?; - let entry = self.get(|state| { - let commands = &state.stashed_codeAction_commands; - - commands - .iter() - .find(|e| e.command == command && e.title == title) - .cloned() - .ok_or_else(|| { - format_err!("No stashed command found! stashed commands: {:?}", commands) - }) - })?; - - if self.try_handle_command_by_client(&entry)? { - return Ok(()); - } - - self.workspace_executeCommand(&json!({ - "command": entry.command, - "arguments": entry.arguments, - }))?; - - self.update(|state| { - state.stashed_codeAction_commands = vec![]; - Ok(()) - })?; - - info!("End {}", NOTIFICATION__FZFSinkCommand); - Ok(()) - } - - pub fn NCM_refresh(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__NCMRefresh); - let params: NCMRefreshParams = serde_json::from_value(rpc::to_value(params.clone())?)?; - let NCMRefreshParams { info, ctx } = params; - if ctx.typed.is_empty() { - return Ok(Value::Null); - } - - let filename = ctx.filepath.clone(); - let line = ctx.lnum - 1; - let character = ctx.col - 1; - - let result = self.textDocument_completion(&json!({ - "buftype": "", - "languageId": ctx.filetype, - "filename": filename, - "line": line, - "character": character, - "handle": false, - }))?; - let result: Option = serde_json::from_value(result)?; - let result = result.unwrap_or_else(|| CompletionResponse::Array(vec![])); - let is_incomplete = match result { - CompletionResponse::Array(_) => false, - CompletionResponse::List(ref list) => list.is_incomplete, - }; - let matches: Result> = match result { - CompletionResponse::Array(arr) => arr, - CompletionResponse::List(list) => list.items, - }.iter() - .map(|item| VimCompleteItem::from_lsp(item, None)) - .collect(); - let matches = matches?; - self.call::<_, u8>( - None, - "cm#complete", - json!([info.name, ctx, ctx.startcol, matches, is_incomplete]), - )?; - info!("End {}", REQUEST__NCMRefresh); - Ok(Value::Null) - } - - pub fn NCM2_on_complete(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__NCM2OnComplete); - - let orig_ctx: Value = serde_json::from_value(rpc::to_value(params.clone())?)?; - let orig_ctx = &orig_ctx["ctx"]; - - let ctx: NCM2Context = serde_json::from_value(orig_ctx.clone())?; - if ctx.typed.is_empty() { - return Ok(Value::Null); - } - - let filename = ctx.filepath.clone(); - let line = ctx.lnum - 1; - let character = ctx.ccol - 1; - - let result = self.textDocument_completion(&json!({ - "buftype": "", - "languageId": ctx.filetype, - "filename": filename, - "line": line, - "character": character, - "handle": false})); - let is_incomplete; - let matches; - if let Ok(ref value) = result { - let completion = serde_json::from_value(value.clone())?; - is_incomplete = match completion { - CompletionResponse::List(ref list) => list.is_incomplete, - _ => false, - }; - let matches_result: Result> = match completion { - CompletionResponse::Array(arr) => arr, - CompletionResponse::List(list) => list.items, - }.iter() - .map(|item| VimCompleteItem::from_lsp(item, None)) - .collect(); - matches = matches_result?; - } else { - is_incomplete = true; - matches = vec![]; - } - self.call::<_, u8>( - None, - "ncm2#complete", - json!([orig_ctx, ctx.startccol, matches, is_incomplete]), - )?; - info!("End {}", REQUEST__NCM2OnComplete); - result - } - - pub fn languageClient_explainErrorAtPoint(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__ExplainErrorAtPoint); - let (buftype, filename, line, character): (String, String, u64, u64) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - ], - params, - )?; - if !buftype.is_empty() { - return Ok(Value::Null); - } - let diag = self.get(|state| { - state - .diagnostics - .get(&filename) - .ok_or_else(|| format_err!("No diagnostics found: filename: {}", filename,))? - .iter() - .find(|d| { - (line, character) >= (d.range.start.line, d.range.start.character) - && (line, character) < (d.range.end.line, d.range.end.character) - }).cloned() - .ok_or_else(|| { - format_err!( - "No diagnostics found: filename: {}, line: {}, character: {}", - filename, - line, - character - ) - }) - })?; - let message: Vec<_> = diag.message.lines().collect(); - self.preview(&message)?; - - info!("End {}", REQUEST__ExplainErrorAtPoint); - Ok(Value::Null) - } - - // Extensions by language servers. - pub fn language_status(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__LanguageStatus); - let params: LanguageStatusParams = params.clone().to_lsp()?; - let msg = format!("{} {}", params.typee, params.message); - self.echomsg(&msg)?; - info!("End {}", NOTIFICATION__LanguageStatus); - Ok(()) - } - - pub fn rustDocument_implementations(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__RustImplementations); - let (buftype, languageId, filename, line, character, handle): ( - String, - String, - String, - u64, - u64, - bool, - ) = self.gather_args( - &[ - VimVar::Buftype, - VimVar::LanguageId, - VimVar::Filename, - VimVar::Line, - VimVar::Character, - VimVar::Handle, - ], - params, - )?; - if !buftype.is_empty() || languageId.is_empty() { - return Ok(Value::Null); - } - - let result = self.call( - Some(&languageId), - REQUEST__RustImplementations, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier { - uri: filename.to_url()?, - }, - position: Position { line, character }, - }, - )?; - - if !handle { - return Ok(result); - } - - let locations: Vec = serde_json::from_value(result.clone())?; - self.display_locations(&locations)?; - - info!("End {}", REQUEST__RustImplementations); - Ok(result) - } - - pub fn rust_handleBeginBuild(&mut self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__RustBeginBuild); - self.command(vec![ - format!("let {}=1", VIM__ServerStatus), - format!("let {}='Rust: build begin'", VIM__ServerStatusMessage), - ])?; - info!("End {}", NOTIFICATION__RustBeginBuild); - Ok(()) - } - - pub fn rust_handleDiagnosticsBegin(&mut self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__RustDiagnosticsBegin); - self.command(vec![ - format!("let {}=1", VIM__ServerStatus), - format!("let {}='Rust: diagnostics begin'", VIM__ServerStatusMessage), - ])?; - info!("End {}", NOTIFICATION__RustDiagnosticsBegin); - Ok(()) - } - - pub fn rust_handleDiagnosticsEnd(&mut self, _params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__RustDiagnosticsEnd); - self.command(vec![ - format!("let {}=0", VIM__ServerStatus), - format!("let {}='Rust: diagnostics end'", VIM__ServerStatusMessage), - ])?; - info!("End {}", NOTIFICATION__RustDiagnosticsEnd); - Ok(()) - } - - pub fn window_progress(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", NOTIFICATION__WindowProgress); - let params: WindowProgressParams = params.clone().to_lsp()?; - - let done = params.done.unwrap_or(false); - - let mut buf = "LS: ".to_owned(); - - if done { - buf += "Idle"; - } else { - // For RLS this can be "Build" or "Diagnostics" or "Indexing". - buf += params - .title - .as_ref() - .map(|title| title.as_ref()) - .unwrap_or("Busy"); - - // For RLS this is the crate name, present only if the progress isn't known. - if let Some(message) = params.message { - buf += &format!(" ({})", &message); - } - // For RLS this is the progress percentage, present only if the it's known. - if let Some(percentage) = params.percentage { - buf += &format!(" ({:.1}% done)", percentage); - } - } - - self.command(vec![ - format!("let {}={}", VIM__ServerStatus, if done { 0 } else { 1 }), - format!( - "let {}='{}'", - VIM__ServerStatusMessage, - &escape_single_quote(buf) - ), - ])?; - info!("End {}", NOTIFICATION__WindowProgress); - Ok(()) - } - - pub fn languageClient_startServer(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__StartServer); - let (cmdargs,): (Vec,) = self.gather_args(&[("cmdargs", "[]")], params)?; - let cmdparams = vim_cmd_args_to_value(&cmdargs)?; - let params = params.combine(&cmdparams); - let (buftype, languageId, filename): (String, String, String) = self.gather_args( - &[VimVar::Buftype, VimVar::LanguageId, VimVar::Filename], - ¶ms, - )?; - - if !buftype.is_empty() || filename.is_empty() { - return Ok(Value::Null); - } - - if self.get(|state| Ok(state.writers.contains_key(&languageId)))? { - bail!( - "Language client has already started for language {}.", - &languageId - ); - } - - self.sync_settings()?; - - let command = self.get(|state| { - state - .serverCommands - .get(&languageId) - .cloned() - .ok_or_else(|| { - format_err!( - "No language server command found for file type: {}.", - &languageId - ) - }) - })?; - - let (child_id, reader, writer): (_, Box, Box) = - if command.get(0).map(|c| c.starts_with("tcp://")) == Some(true) { - let addr = command - .get(0) - .map(|s| s.replace("tcp://", "")) - .ok_or_else(|| err_msg("Server command can't be empty!"))?; - let stream = TcpStream::connect(addr)?; - let reader = Box::new(BufReader::new(stream.try_clone()?)); - let writer = Box::new(BufWriter::new(stream)); - (None, reader, writer) - } else { - let home = env::home_dir().ok_or_else(|| err_msg("Failed to get home dir"))?; - let command: Vec<_> = command - .into_iter() - .map(|cmd| { - if cmd.starts_with('~') { - cmd.replacen('~', &home.to_string_lossy(), 1) - } else { - cmd - } - }).collect(); - - let stderr = match self.serverStderr { - Some(ref path) => std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(path) - .with_context(|err| format!("Failed to open file ({}): {}", path, err))? - .into(), - None => Stdio::null(), - }; - - let process = std::process::Command::new( - command.get(0).ok_or_else(|| err_msg("Empty command!"))?, - ).args(&command[1..]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(stderr) - .spawn() - .with_context(|err| { - format!("Failed to start language server ({:?}): {}", command, err) - })?; - - let child_id = Some(process.id()); - let reader = Box::new(BufReader::new( - process - .stdout - .ok_or_else(|| err_msg("Failed to get subprocess stdout"))?, - )); - let writer = Box::new(BufWriter::new( - process - .stdin - .ok_or_else(|| err_msg("Failed to get subprocess stdin"))?, - )); - (child_id, reader, writer) - }; - - self.update(|state| { - child_id.map(|id| state.child_ids.insert(languageId.clone(), id)); - state.writers.insert(languageId.clone(), writer); - Ok(()) - })?; - - let thread_name = format!("reader-{}", languageId); - let languageId_clone = languageId.clone(); - let tx = self.tx.clone(); - std::thread::Builder::new() - .name(thread_name.clone()) - .spawn(move || { - if let Err(err) = vim::loop_reader(reader, &Some(languageId_clone.clone()), &tx) { - let _ = tx.send(Message::Notification( - Some(languageId_clone.clone()), - rpc::Notification { - jsonrpc: None, - method: NOTIFICATION__ServerExited.into(), - params: json!({ - "languageId": languageId_clone, - "message": format!("{}", err), - }).to_params() - .unwrap_or_default(), - }, - )); - } - })?; - - info!("End {}", REQUEST__StartServer); - - if self.get(|state| Ok(state.writers.len()))? == 1 { - self.define_signs()?; - } - - self.initialize(¶ms)?; - self.initialized(¶ms)?; - - let root = self.roots.get(&languageId).cloned().unwrap_or_default(); - match self.get_workspace_settings(&root) { - Ok(Value::Null) => (), - Ok(settings) => self.workspace_didChangeConfiguration(&json!({ - VimVar::LanguageId.to_key(): languageId, - "settings": settings, - }))?, - Err(err) => warn!("Failed to get workspace settings: {}", err), - } - - self.textDocument_didOpen(¶ms)?; - self.textDocument_didChange(¶ms)?; - - self.notify(None, "s:ExecuteAutocmd", "LanguageClientStarted")?; - Ok(Value::Null) - } - - pub fn languageClient_serverExited(&mut self, params: &Value) -> Result<()> { - let (languageId, message): (String, String) = self.gather_args( - [VimVar::LanguageId.to_key().as_str(), "message"].as_ref(), - params, - )?; - - if self.writers.contains_key(&languageId) { - if let Err(err) = self.cleanup(&languageId) { - error!("Error in cleanup: {:?}", err); - } - if let Err(err) = self.echoerr(format!( - "Language server {} exited unexpectedly: {}", - languageId, message - )) { - error!("Error in echoerr: {:?}", err); - } - } - - Ok(()) - } - - pub fn handle_fs_events(&mut self) -> Result<()> { - let mut pending_changes = HashMap::new(); - for (languageId, watcher_rx) in &mut self.watcher_rxs { - let mut events = vec![]; - loop { - let result = watcher_rx.try_recv(); - let event = match result { - Ok(event) => event, - Err(TryRecvError::Empty) => { - break; - } - Err(TryRecvError::Disconnected) => { - bail!("File system notification channel disconnected!"); - } - }; - events.push(event); - } - - let mut changes = vec![]; - for e in events { - if let Ok(c) = e.to_lsp() { - changes.extend(c); - } - } - - if changes.is_empty() { - continue; - } - - pending_changes.insert(languageId.to_owned(), changes); - } - - for (languageId, changes) in pending_changes { - self.workspace_didChangeWatchedFiles(&json!({ - "languageId": languageId, - "changes": changes - }))?; - } - - Ok(()) - } - - pub fn workspace_didChangeWatchedFiles(&mut self, params: &Value) -> Result<()> { - info!("Begin {}", lsp::notification::DidChangeWatchedFiles::METHOD); - let (languageId,): (String,) = self.gather_args([VimVar::LanguageId].as_ref(), params)?; - - let params: DidChangeWatchedFilesParams = params.clone().to_lsp()?; - self.notify( - Some(&languageId), - lsp::notification::DidChangeWatchedFiles::METHOD, - params, - )?; - - info!("End {}", lsp::notification::DidChangeWatchedFiles::METHOD); - Ok(()) - } - - pub fn java_classFileContents(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__ClassFileContents); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - - let content: String = self.call( - Some(languageId.as_str()), - REQUEST__ClassFileContents, - params, - )?; - - info!("End {}", REQUEST__ClassFileContents); - Ok(Value::String(content)) - } - - pub fn debug_info(&mut self, params: &Value) -> Result { - info!("Begin {}", REQUEST__DebugInfo); - let (languageId,): (String,) = self.gather_args(&[VimVar::LanguageId], params)?; - let mut msg = String::new(); - msg += &format!( - "Project root: {}\n", - self.roots.get(&languageId).cloned().unwrap_or_default() - ); - msg += &format!( - "Language server process id: {}\n", - self.child_ids.get(&languageId).cloned().unwrap_or_default(), - ); - msg += &format!( - "Language server stderr: {}\n", - self.serverStderr.clone().unwrap_or_default() - ); - msg += &format!("Log level: {}\n", self.loggingLevel); - msg += &format!( - "Log file: {}\n", - self.loggingFile.clone().unwrap_or_default() - ); - self.echo(&msg)?; - info!("End {}", REQUEST__DebugInfo); - Ok(json!(msg)) - } -} diff --git a/vim/bundle/LanguageClient-neovim/src/logger.rs b/vim/bundle/LanguageClient-neovim/src/logger.rs index 5b0106b..9821f3f 100644 --- a/vim/bundle/LanguageClient-neovim/src/logger.rs +++ b/vim/bundle/LanguageClient-neovim/src/logger.rs @@ -1,28 +1,83 @@ -use super::*; +use anyhow::{Context, Result}; +use derivative::Derivative; use log::LevelFilter; use log4rs::append::file::FileAppender; -use log4rs::config::{Appender, Config, Logger, Root}; +use log4rs::config::{Appender, Config, Root}; use log4rs::encode::pattern::PatternEncoder; -use log4rs::Handle; +use serde::Serialize; +use std::io::Write; +use std::path::PathBuf; -fn create_config(path: &Option, level: LevelFilter) -> Result { +#[derive(Derivative)] +#[derivative(Debug)] +#[derive(Serialize)] +pub struct Logger { + pub level: LevelFilter, + pub path: Option, + + #[derivative(Debug = "ignore")] + #[serde(skip_serializing)] + handle: log4rs::Handle, +} + +impl Logger { + pub fn new() -> Result { + let level = LevelFilter::Warn; + let path = None; + + let config = create_config(&path, level)?; + let handle = log4rs::init_config(config)?; + Ok(Logger { + path, + level, + handle, + }) + } + + pub fn update_settings(&mut self, level: LevelFilter, path: Option) -> Result<()> { + let config = create_config(&path, level)?; + self.handle.set_config(config); + self.level = level; + self.path = path; + Ok(()) + } + + pub fn set_level(&mut self, level: LevelFilter) -> Result<()> { + let config = create_config(&self.path, level)?; + self.handle.set_config(config); + self.level = level; + Ok(()) + } + + #[allow(dead_code)] + pub fn set_path(&mut self, path: Option) -> Result<()> { + let config = create_config(&path, self.level)?; + self.handle.set_config(config); + self.path = path; + Ok(()) + } +} + +fn create_config(path: &Option, level: LevelFilter) -> Result { let encoder = PatternEncoder::new("{date(%H:%M:%S)} {level} {thread} {file}:{line} {message}{n}"); let mut config_builder = - Config::builder().logger(Logger::builder().build("languageclient", level)); + Config::builder().logger(log4rs::config::Logger::builder().build("languageclient", level)); let mut root_builder = Root::builder(); if let Some(path) = path { + let path = shellexpand::tilde(&path.to_string_lossy()).to_string(); + // Ensure log file writable. { let mut f = std::fs::OpenOptions::new() .create(true) .write(true) .truncate(true) - .open(path) - .with_context(|err| format!("Failed to open file ({}): {}", path, err))?; - #[allow(write_literal)] + .open(&path) + .with_context(|| format!("Failed to open file ({})", path))?; + #[allow(clippy::write_literal)] writeln!( f, "#######\nLanguageClient {} {}\n#######", @@ -41,15 +96,3 @@ fn create_config(path: &Option, level: LevelFilter) -> Result { let config = config_builder.build(root_builder.build(level))?; Ok(config) } - -pub fn init() -> Result { - let handle = log4rs::init_config(create_config(&None, LevelFilter::Warn)?)?; - - Ok(handle) -} - -pub fn update_settings(handle: &Handle, path: &Option, level: LevelFilter) -> Result<()> { - let config = create_config(path, level)?; - handle.set_config(config); - Ok(()) -} diff --git a/vim/bundle/LanguageClient-neovim/src/main.rs b/vim/bundle/LanguageClient-neovim/src/main.rs index 9af0e01..826be15 100644 --- a/vim/bundle/LanguageClient-neovim/src/main.rs +++ b/vim/bundle/LanguageClient-neovim/src/main.rs @@ -1,75 +1,21 @@ -#![allow(non_snake_case, non_upper_case_globals, unknown_lints)] - -use std::collections::{HashMap, HashSet, VecDeque}; -use std::convert::Into; -use std::env; -use std::fmt::Debug; -use std::fs::{read_to_string, File}; -use std::io::prelude::*; -use std::io::{BufRead, BufReader, BufWriter}; -use std::net::TcpStream; -use std::ops::Deref; -use std::path::{Path, PathBuf}; -use std::process::{ChildStdin, ChildStdout, Stdio}; -use std::str::FromStr; -use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; -use std::thread; -use std::time::{Duration, Instant}; - -#[macro_use] -extern crate log; -extern crate log4rs; - -#[macro_use] -extern crate failure; -#[allow(unused_imports)] -use failure::{err_msg, Error, ResultExt}; - -#[macro_use] -extern crate maplit; - -extern crate serde; -use serde::de::DeserializeOwned; -use serde::Serialize; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; - -extern crate jsonrpc_core as rpc; -use rpc::{Params, Value}; - -extern crate languageserver_types as lsp; -use lsp::request::GotoDefinitionResponse; -use lsp::*; - -extern crate url; -use url::Url; - -extern crate pathdiff; -use pathdiff::diff_paths; - -extern crate diff; - -extern crate glob; -extern crate regex; - -extern crate notify; -#[allow(unused_imports)] -use notify::Watcher; - -#[macro_use] -extern crate structopt; -use structopt::StructOpt; - -mod types; -use types::*; -mod utils; -use utils::*; -mod languageclient; +mod extensions; +mod language_client; +mod language_server_protocol; mod logger; +mod rpcclient; mod rpchandler; +mod sign; +mod types; +mod utils; +mod viewport; mod vim; +mod vimext; + +use anyhow::Result; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use structopt::StructOpt; +use types::State; #[derive(Debug, StructOpt)] struct Arguments {} @@ -79,19 +25,12 @@ fn main() -> Result<()> { let args = Arguments::clap().version(version.as_str()); let _ = args.get_matches(); - let mut state = State::new()?; - - let tx = state.tx.clone(); - let reader_thread_name: String = "reader-main".into(); - thread::Builder::new() - .name(reader_thread_name.clone()) - .spawn(move || { - let stdin = std::io::stdin(); - let stdin = stdin.lock(); - if let Err(err) = vim::loop_reader(stdin, &None, &tx) { - error!("{} exited: {:?}", reader_thread_name, err); - } - })?; + let (tx, rx) = crossbeam::channel::unbounded(); + let language_client = language_client::LanguageClient { + version, + state_mutex: Arc::new(Mutex::new(State::new(tx)?)), + clients_mutex: Arc::new(Mutex::new(HashMap::new())), + }; - state.loop_message() + language_client.loop_call(&rx) } diff --git a/vim/bundle/LanguageClient-neovim/src/rpcclient.rs b/vim/bundle/LanguageClient-neovim/src/rpcclient.rs new file mode 100644 index 0000000..2703768 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/rpcclient.rs @@ -0,0 +1,242 @@ +use crate::types::{Call, Id, LSError, LanguageId, RawMessage, ToInt, ToParams, ToRpcError}; +use anyhow::{anyhow, Result}; +use crossbeam::channel::{bounded, unbounded, Receiver, Sender}; +use log::*; +use serde::{de::DeserializeOwned, Serialize}; +use std::io::Write; +use std::str::FromStr; +use std::{ + collections::HashMap, + io::BufRead, + sync::atomic::{AtomicU64, Ordering}, + thread, + time::Duration, +}; + +const CONTENT_MODIFIED_ERROR_CODE: i64 = -32801; + +#[derive(Serialize)] +pub struct RpcClient { + language_id: LanguageId, + #[serde(skip_serializing)] + id: AtomicU64, + #[serde(skip_serializing)] + writer_tx: Sender, + #[serde(skip_serializing)] + reader_tx: Sender<(Id, Sender)>, + pub process_id: Option, +} + +impl RpcClient { + #[allow(clippy::new_ret_no_self)] + pub fn new( + language_id: LanguageId, + reader: impl BufRead + Send + 'static, + writer: impl Write + Send + 'static, + process_id: Option, + sink: Sender, + ) -> Result { + let (reader_tx, reader_rx): (Sender<(Id, Sender)>, _) = unbounded(); + + let language_id_clone = language_id.clone(); + let reader_thread_name = format!("reader-{:?}", language_id); + thread::Builder::new() + .name(reader_thread_name.clone()) + .spawn(move || { + if let Err(err) = loop_read(reader, reader_rx, &sink, &language_id_clone) { + error!("Thread {} exited with error: {:?}", reader_thread_name, err); + } + })?; + + let (writer_tx, writer_rx) = unbounded(); + let writer_thread_name = format!("writer-{:?}", language_id); + let language_id_clone = language_id.clone(); + thread::Builder::new() + .name(writer_thread_name.clone()) + .spawn(move || { + if let Err(err) = loop_write(writer, &writer_rx, &language_id_clone) { + error!("Thread {} exited with error: {:?}", writer_thread_name, err); + } + })?; + + Ok(Self { + language_id, + id: AtomicU64::default(), + process_id, + reader_tx, + writer_tx, + }) + } + + pub fn call( + &self, + method: impl AsRef, + params: impl Serialize, + ) -> Result { + let method = method.as_ref(); + let id = self.id.fetch_add(1, Ordering::SeqCst); + let msg = jsonrpc_core::MethodCall { + jsonrpc: Some(jsonrpc_core::Version::V2), + id: jsonrpc_core::Id::Num(id), + method: method.to_owned(), + params: params.to_params()?, + }; + let (tx, rx) = bounded(1); + self.reader_tx.send((id, tx))?; + self.writer_tx.send(RawMessage::MethodCall(msg))?; + // TODO: duration from config. + match rx.recv_timeout(Duration::from_secs(60))? { + jsonrpc_core::Output::Success(ok) => Ok(serde_json::from_value(ok.result)?), + // NOTE: Errors with code -32801 correspond to the protocol's ContentModified error, + // which we don't want to show to the user and should ignore, as the result of the + // request that triggered this error has been invalidated by changes to the state + // of the server, so we must handle this error specifically. + jsonrpc_core::Output::Failure(err) + if err.error.code.code() == CONTENT_MODIFIED_ERROR_CODE => + { + Err(anyhow::Error::from(LSError::ContentModified)) + } + jsonrpc_core::Output::Failure(err) => Err(anyhow!("Error: {:?}", err)), + } + } + + pub fn notify(&self, method: impl AsRef, params: impl Serialize) -> Result<()> { + let method = method.as_ref(); + + let msg = jsonrpc_core::Notification { + jsonrpc: Some(jsonrpc_core::Version::V2), + method: method.to_owned(), + params: params.to_params()?, + }; + self.writer_tx.send(RawMessage::Notification(msg))?; + Ok(()) + } + + pub fn output(&self, id: Id, result: Result) -> Result<()> { + let output = match result { + Ok(ok) => jsonrpc_core::Output::Success(jsonrpc_core::Success { + jsonrpc: Some(jsonrpc_core::Version::V2), + id: jsonrpc_core::Id::Num(id), + result: serde_json::to_value(ok)?, + }), + Err(err) => jsonrpc_core::Output::Failure(jsonrpc_core::Failure { + jsonrpc: Some(jsonrpc_core::Version::V2), + id: jsonrpc_core::Id::Num(id), + error: err.to_rpc_error(), + }), + }; + + self.writer_tx.send(RawMessage::Output(output))?; + Ok(()) + } +} + +fn loop_read( + reader: impl BufRead, + reader_rx: Receiver<(Id, Sender)>, + sink: &Sender, + language_id: &LanguageId, +) -> Result<()> { + let mut pending_outputs = HashMap::new(); + + // Count how many consequent empty lines. + let mut count_empty_lines = 0; + + let mut reader = reader; + let mut content_length = 0; + loop { + let mut message = String::new(); + let mut line = String::new(); + if language_id.is_some() { + reader.read_line(&mut line)?; + let line = line.trim(); + if line.is_empty() { + count_empty_lines += 1; + if count_empty_lines > 5 { + return Err(anyhow!("Unable to read from language server")); + } + + let mut buf = vec![0; content_length]; + reader.read_exact(buf.as_mut_slice())?; + message = String::from_utf8(buf)?; + } else { + count_empty_lines = 0; + if !line.starts_with("Content-Length") { + continue; + } + + let tokens: Vec<&str> = line.splitn(2, ':').collect(); + let len = tokens + .get(1) + .ok_or_else(|| anyhow!("Failed to get length! tokens: {:?}", tokens))? + .trim(); + content_length = usize::from_str(len)?; + } + } else if reader.read_line(&mut message)? == 0 { + break; + } + + let message = message.trim(); + if message.is_empty() { + continue; + } + info!("<= {:?} {}", language_id, message); + // FIXME: Remove extra `meta` property from javascript-typescript-langserver. + let s = message.replace(r#","meta":{}"#, ""); + let message = serde_json::from_str(&s); + if let Err(ref err) = message { + error!( + "Failed to deserialize output: {}\n\n Message: {}\n\nError: {:?}", + err, s, err + ); + continue; + } + // TODO: cleanup. + let message = message.unwrap(); + match message { + RawMessage::MethodCall(method_call) => { + sink.send(Call::MethodCall(language_id.clone(), method_call))?; + } + RawMessage::Notification(notification) => { + sink.send(Call::Notification(language_id.clone(), notification))?; + } + RawMessage::Output(output) => { + while let Ok((id, tx)) = reader_rx.try_recv() { + pending_outputs.insert(id, tx); + } + + if let Some(tx) = pending_outputs.remove(&output.id().to_int()?) { + tx.send(output) + .map_err(|output| anyhow!("Failed to send output: {:?}", output))?; + } + } + }; + } + + info!("reader-{:?} terminated", language_id); + Ok(()) +} + +fn loop_write( + writer: impl Write, + rx: &Receiver, + language_id: &LanguageId, +) -> Result<()> { + let mut writer = writer; + + for msg in rx.iter() { + let s = serde_json::to_string(&msg)?; + info!("=> {:?} {}", language_id, s); + if language_id.is_none() { + // Use different convention for two reasons, + // 1. If using '\r\ncontent', nvim will receive output as `\r` + `content`, while vim + // receives `content`. + // 2. Without last line ending, vim output handler won't be triggered. + write!(writer, "Content-Length: {}\n\n{}\n", s.len(), s)?; + } else { + write!(writer, "Content-Length: {}\r\n\r\n{}", s.len(), s)?; + }; + writer.flush()?; + } + Ok(()) +} diff --git a/vim/bundle/LanguageClient-neovim/src/rpchandler.rs b/vim/bundle/LanguageClient-neovim/src/rpchandler.rs index c0e4639..91117f1 100644 --- a/vim/bundle/LanguageClient-neovim/src/rpchandler.rs +++ b/vim/bundle/LanguageClient-neovim/src/rpchandler.rs @@ -1,174 +1,225 @@ -use super::*; -use lsp::notification::Notification; -use lsp::request::Request; +use crate::{language_client::LanguageClient, types::*}; +use anyhow::{anyhow, Result}; +use log::*; +use lsp_types::notification::{self, Notification}; +use lsp_types::request::{self, Request}; +use serde_json::Value; + +fn is_content_modified_error(err: &anyhow::Error) -> bool { + match err.downcast_ref::() { + Some(err) if err == &LSError::ContentModified => true, + _ => false, + } +} + +impl LanguageClient { + pub fn handle_call(&self, msg: Call) -> Result<()> { + match msg { + Call::MethodCall(lang_id, method_call) => { + let result = self.handle_method_call(lang_id.as_deref(), &method_call); + if let Err(ref err) = result { + if is_content_modified_error(err) { + return Ok(()); + } + + if err.downcast_ref::().is_none() { + error!( + "Error handling message: {}\n\nMessage: {}\n\nError: {:?}", + err, + serde_json::to_string(&method_call).unwrap_or_default(), + err + ); + } + } + self.get_client(&lang_id)? + .output(method_call.id.to_int()?, result)?; + } + Call::Notification(lang_id, notification) => { + let result = self.handle_notification(lang_id.as_deref(), ¬ification); + if let Err(ref err) = result { + if is_content_modified_error(err) { + return Ok(()); + } + + if err.downcast_ref::().is_none() { + error!( + "Error handling message: {}\n\nMessage: {}\n\nError: {:?}", + err, + serde_json::to_string(¬ification).unwrap_or_default(), + err + ); + } + } + } + } + + // FIXME + if let Err(err) = self.handle_fs_events() { + warn!("{:?}", err); + } + + Ok(()) + } -impl State { pub fn handle_method_call( - &mut self, - languageId: Option<&str>, - method_call: &rpc::MethodCall, + &self, + language_id: Option<&str>, + method_call: &jsonrpc_core::MethodCall, ) -> Result { let params = serde_json::to_value(method_call.params.clone())?; - let user_handler = self.get(|state| { - state - .user_handlers - .get(&method_call.method) - .cloned() - .ok_or_else(|| err_msg("No user handler")) - }); - if let Ok(user_handler) = user_handler { - return self.call(None, &user_handler, params); + let user_handler = + self.get(|state| state.user_handlers.get(&method_call.method).cloned())?; + if let Some(user_handler) = user_handler { + return self.vim()?.rpcclient.call(&user_handler, params); } match method_call.method.as_str() { - lsp::request::RegisterCapability::METHOD => { - self.client_registerCapability(languageId.unwrap_or_default(), ¶ms) + request::RegisterCapability::METHOD => { + self.client_register_capability(language_id.unwrap_or_default(), ¶ms) } - lsp::request::UnregisterCapability::METHOD => { - self.client_unregisterCapability(languageId.unwrap_or_default(), ¶ms) + request::UnregisterCapability::METHOD => { + self.client_unregister_capability(language_id.unwrap_or_default(), ¶ms) } - lsp::request::HoverRequest::METHOD => self.textDocument_hover(¶ms), - REQUEST__FindLocations => self.find_locations(¶ms), - lsp::request::Rename::METHOD => self.textDocument_rename(¶ms), - lsp::request::DocumentSymbolRequest::METHOD => { - self.textDocument_documentSymbol(¶ms) + request::HoverRequest::METHOD => self.text_document_hover(¶ms), + request::Rename::METHOD => self.text_document_rename(¶ms), + request::DocumentSymbolRequest::METHOD => self.text_document_document_symbol(¶ms), + request::ShowMessageRequest::METHOD => self.window_show_message_request(¶ms), + request::WorkspaceSymbol::METHOD => self.workspace_symbol(¶ms), + request::CodeActionRequest::METHOD => self.text_document_code_action(¶ms), + request::Completion::METHOD => self.text_document_completion(¶ms), + request::SignatureHelpRequest::METHOD => self.text_document_signature_help(¶ms), + request::References::METHOD => self.text_document_references(¶ms), + request::Formatting::METHOD => self.text_document_formatting(¶ms), + request::RangeFormatting::METHOD => self.text_document_range_formatting(¶ms), + request::CodeLensRequest::METHOD => self.text_document_code_lens(¶ms), + request::ResolveCompletionItem::METHOD => self.completion_item_resolve(¶ms), + request::ExecuteCommand::METHOD => self.workspace_execute_command(¶ms), + request::ApplyWorkspaceEdit::METHOD => self.workspace_apply_edit(¶ms), + request::DocumentHighlightRequest::METHOD => { + self.text_document_document_highlight(¶ms) } - lsp::request::WorkspaceSymbol::METHOD => self.workspace_symbol(¶ms), - lsp::request::CodeActionRequest::METHOD => self.textDocument_codeAction(¶ms), - lsp::request::Completion::METHOD => self.textDocument_completion(¶ms), - lsp::request::SignatureHelpRequest::METHOD => self.textDocument_signatureHelp(¶ms), - lsp::request::References::METHOD => self.textDocument_references(¶ms), - lsp::request::Formatting::METHOD => self.textDocument_formatting(¶ms), - lsp::request::RangeFormatting::METHOD => self.textDocument_rangeFormatting(¶ms), - lsp::request::ResolveCompletionItem::METHOD => self.completionItem_resolve(¶ms), - lsp::request::ExecuteCommand::METHOD => self.workspace_executeCommand(¶ms), - lsp::request::ApplyWorkspaceEdit::METHOD => self.workspace_applyEdit(¶ms), - lsp::request::DocumentHighlightRequest::METHOD => { - self.textDocument_documentHighlight(¶ms) - } - REQUEST__RustImplementations => self.rustDocument_implementations(¶ms), // Extensions. - REQUEST__GetState => self.languageClient_getState(¶ms), - REQUEST__IsAlive => self.languageClient_isAlive(¶ms), - REQUEST__StartServer => self.languageClient_startServer(¶ms), - REQUEST__RegisterServerCommands => self.languageClient_registerServerCommands(¶ms), - REQUEST__SetLoggingLevel => self.languageClient_setLoggingLevel(¶ms), - REQUEST__SetDiagnosticsList => self.languageClient_setDiagnosticsList(¶ms), - REQUEST__RegisterHandlers => self.languageClient_registerHandlers(¶ms), - REQUEST__NCMRefresh => self.NCM_refresh(¶ms), - REQUEST__NCM2OnComplete => self.NCM2_on_complete(¶ms), - REQUEST__ExplainErrorAtPoint => self.languageClient_explainErrorAtPoint(¶ms), - REQUEST__OmniComplete => self.languageClient_omniComplete(¶ms), - REQUEST__ClassFileContents => self.java_classFileContents(¶ms), - REQUEST__DebugInfo => self.debug_info(¶ms), + REQUEST_FIND_LOCATIONS => self.find_locations(¶ms), + REQUEST_GET_STATE => self.get_state(¶ms), + REQUEST_IS_ALIVE => self.is_alive(¶ms), + REQUEST_START_SERVER => self.start_server(¶ms), + REQUEST_REGISTER_SERVER_COMMANDS => self.register_server_commands(¶ms), + REQUEST_SET_LOGGING_LEVEL => self.set_logging_level(¶ms), + REQUEST_SET_DIAGNOSTICS_LIST => self.set_diagnostics_list(¶ms), + REQUEST_REGISTER_HANDLERS => self.register_handlers(¶ms), + REQUEST_NCM_REFRESH => self.ncm_refresh(¶ms), + REQUEST_NCM2_ON_COMPLETE => self.ncm2_on_complete(¶ms), + REQUEST_EXPLAIN_ERROR_AT_POINT => self.explain_error_at_point(¶ms), + REQUEST_OMNI_COMPLETE => self.omnicomplete(¶ms), + REQUEST_CLASS_FILE_CONTENTS => self.java_class_file_contents(¶ms), + REQUEST_DEBUG_INFO => self.debug_info(¶ms), + REQUEST_CODE_LENS_ACTION => self.handle_code_lens_action(¶ms), + REQUEST_SEMANTIC_SCOPES => self.semantic_scopes(¶ms), + REQUEST_SHOW_SEMANTIC_HL_SYMBOLS => self.semantic_highlight_symbols(¶ms), _ => { - let languageId_target = if languageId.is_some() { + let language_id_target = if language_id.is_some() { // Message from language server. No handler found. let msg = format!("Message not handled: {:?}", method_call); if method_call.method.starts_with('$') { warn!("{}", msg); return Ok(Value::default()); } else { - return Err(err_msg(msg)); + return Err(anyhow!(msg)); } } else { // Message from vim. Proxy to language server. - let (languageId_target,): (String,) = - self.gather_args(&[VimVar::LanguageId], ¶ms)?; + let filename = self.vim()?.get_filename(¶ms)?; + let language_id_target = self.vim()?.get_language_id(&filename, ¶ms)?; info!( "Proxy message directly to language server: {:?}", method_call ); - Some(languageId_target) + Some(language_id_target) }; - self.call(languageId_target.as_deref(), &method_call.method, ¶ms) + self.get_client(&language_id_target)? + .call(&method_call.method, ¶ms) } } } pub fn handle_notification( - &mut self, - languageId: Option<&str>, - notification: &rpc::Notification, + &self, + language_id: Option<&str>, + notification: &jsonrpc_core::Notification, ) -> Result<()> { let params = serde_json::to_value(notification.params.clone())?; - let user_handler = self.get(|state| { - state - .user_handlers - .get(¬ification.method) - .cloned() - .ok_or_else(|| err_msg("No user handler")) - }); - if let Ok(user_handler) = user_handler { - self.call::<_, u8>(None, &user_handler, params.clone())?; - return Ok(()); + let user_handler = + self.get(|state| state.user_handlers.get(¬ification.method).cloned())?; + if let Some(user_handler) = user_handler { + return self.vim()?.rpcclient.notify(&user_handler, params); } match notification.method.as_str() { - lsp::notification::DidChangeConfiguration::METHOD => { - self.workspace_didChangeConfiguration(¶ms)? + notification::DidChangeConfiguration::METHOD => { + self.workspace_did_change_configuration(¶ms)? } - lsp::notification::DidOpenTextDocument::METHOD => self.textDocument_didOpen(¶ms)?, - lsp::notification::DidChangeTextDocument::METHOD => { - self.textDocument_didChange(¶ms)? + notification::DidOpenTextDocument::METHOD => self.text_document_did_open(¶ms)?, + notification::DidChangeTextDocument::METHOD => { + self.text_document_did_change(¶ms)? } - lsp::notification::DidSaveTextDocument::METHOD => self.textDocument_didSave(¶ms)?, - lsp::notification::DidCloseTextDocument::METHOD => { - self.textDocument_didClose(¶ms)? + notification::DidSaveTextDocument::METHOD => self.text_document_did_save(¶ms)?, + notification::DidCloseTextDocument::METHOD => self.text_document_did_close(¶ms)?, + notification::PublishDiagnostics::METHOD => { + self.text_document_publish_diagnostics(¶ms)? } - lsp::notification::PublishDiagnostics::METHOD => { - self.textDocument_publishDiagnostics(¶ms)? + notification::SemanticHighlighting::METHOD => { + self.text_document_semantic_highlight(¶ms)? } - lsp::notification::LogMessage::METHOD => self.window_logMessage(¶ms)?, - lsp::notification::ShowMessage::METHOD => self.window_showMessage(¶ms)?, - lsp::notification::Exit::METHOD => self.exit(¶ms)?, + notification::Progress::METHOD => self.progress(¶ms)?, + notification::LogMessage::METHOD => self.window_log_message(¶ms)?, + notification::ShowMessage::METHOD => self.window_show_message(¶ms)?, + notification::Exit::METHOD => self.exit(¶ms)?, // Extensions. - NOTIFICATION__HandleBufNewFile => self.languageClient_handleBufNewFile(¶ms)?, - NOTIFICATION__HandleBufReadPost => self.languageClient_handleBufReadPost(¶ms)?, - NOTIFICATION__HandleTextChanged => self.languageClient_handleTextChanged(¶ms)?, - NOTIFICATION__HandleBufWritePost => self.languageClient_handleBufWritePost(¶ms)?, - NOTIFICATION__HandleBufDelete => self.languageClient_handleBufDelete(¶ms)?, - NOTIFICATION__HandleCursorMoved => self.languageClient_handleCursorMoved(¶ms)?, - NOTIFICATION__HandleCompleteDone => self.languageClient_handleCompleteDone(¶ms)?, - NOTIFICATION__FZFSinkLocation => self.languageClient_FZFSinkLocation(¶ms)?, - NOTIFICATION__FZFSinkCommand => self.languageClient_FZFSinkCommand(¶ms)?, - NOTIFICATION__ClearDocumentHighlight => { - self.languageClient_clearDocumentHighlight(¶ms)? - } - // Extensions by language servers. - NOTIFICATION__LanguageStatus => self.language_status(¶ms)?, - NOTIFICATION__RustBeginBuild => self.rust_handleBeginBuild(¶ms)?, - NOTIFICATION__RustDiagnosticsBegin => self.rust_handleDiagnosticsBegin(¶ms)?, - NOTIFICATION__RustDiagnosticsEnd => self.rust_handleDiagnosticsEnd(¶ms)?, - NOTIFICATION__WindowProgress => self.window_progress(¶ms)?, - NOTIFICATION__ServerExited => self.languageClient_serverExited(¶ms)?, + NOTIFICATION_HANDLE_FILE_TYPE => self.handle_file_type(¶ms)?, + NOTIFICATION_HANDLE_BUF_NEW_FILE => self.handle_buf_new_file(¶ms)?, + NOTIFICATION_HANDLE_BUF_ENTER => self.handle_buf_enter(¶ms)?, + NOTIFICATION_HANDLE_TEXT_CHANGED => self.handle_text_changed(¶ms)?, + NOTIFICATION_HANDLE_BUF_WRITE_POST => self.handle_buf_write_post(¶ms)?, + NOTIFICATION_HANDLE_BUF_DELETE => self.handle_buf_delete(¶ms)?, + NOTIFICATION_HANDLE_CURSOR_MOVED => self.handle_cursor_moved(¶ms)?, + NOTIFICATION_HANDLE_COMPLETE_DONE => self.handle_complete_done(¶ms)?, + NOTIFICATION_FZF_SINK_LOCATION => self.fzf_sink_location(¶ms)?, + NOTIFICATION_FZF_SINK_COMMAND => self.fzf_sink_command(¶ms)?, + NOTIFICATION_CLEAR_DOCUMENT_HL => self.clear_document_highlight(¶ms)?, + NOTIFICATION_LANGUAGE_STATUS => self.language_status(¶ms)?, + NOTIFICATION_WINDOW_PROGRESS => self.window_progress(¶ms)?, + NOTIFICATION_SERVER_EXITED => self.handle_server_exited(¶ms)?, + NOTIFICATION_RUST_BEGIN_BUILD => self.rust_handle_begin_build(¶ms)?, + NOTIFICATION_RUST_DIAGNOSTICS_BEGIN => self.rust_handle_diagnostics_begin(¶ms)?, + NOTIFICATION_RUST_DIAGNOSTICS_END => self.rust_handle_diagnostics_end(¶ms)?, _ => { - let languageId_target = if languageId.is_some() { + let language_id_target = if language_id.is_some() { // Message from language server. No handler found. let msg = format!("Message not handled: {:?}", notification); if notification.method.starts_with('$') { warn!("{}", msg); return Ok(()); } else { - return Err(err_msg(msg)); + return Err(anyhow!(msg)); } } else { // Message from vim. Proxy to language server. - let (languageId_target,): (String,) = - self.gather_args(&[VimVar::LanguageId], ¶ms)?; + let filename = self.vim()?.get_filename(¶ms)?; + let language_id_target = self.vim()?.get_language_id(&filename, ¶ms)?; info!( "Proxy message directly to language server: {:?}", notification ); - Some(languageId_target) + Some(language_id_target) }; - self.notify(languageId_target.as_deref(), ¬ification.method, ¶ms)?; + self.get_client(&language_id_target)? + .notify(¬ification.method, ¶ms)?; } }; diff --git a/vim/bundle/LanguageClient-neovim/src/sign.rs b/vim/bundle/LanguageClient-neovim/src/sign.rs new file mode 100644 index 0000000..44ac509 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/sign.rs @@ -0,0 +1,21 @@ +use lsp_types::{Diagnostic, DiagnosticSeverity}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Sign { + pub id: u64, + /// line number. 0-based. + pub line: u64, + pub name: String, +} + +impl From<&Diagnostic> for Sign { + fn from(diagnostic: &Diagnostic) -> Self { + let line = diagnostic.range.start.line; + let severity = diagnostic.severity.unwrap_or(DiagnosticSeverity::Hint); + let name = format!("LanguageClient{:?}", severity); + let id = 75_000 + line * DiagnosticSeverity::Hint as u64 + severity as u64; + + Sign { id, line, name } + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/types.rs b/vim/bundle/LanguageClient-neovim/src/types.rs index bdaced4..aaa6baa 100644 --- a/vim/bundle/LanguageClient-neovim/src/types.rs +++ b/vim/bundle/LanguageClient-neovim/src/types.rs @@ -1,115 +1,166 @@ -use super::*; - -pub type Result = std::result::Result; - -#[derive(Debug, Fail)] +use crate::logger::Logger; +use crate::rpcclient::RpcClient; +use crate::sign::Sign; +use crate::{ + language_client::LanguageClient, + utils::{code_action_kind_as_str, ToUrl}, + vim::Vim, +}; +use anyhow::{anyhow, Result}; +use jsonrpc_core::Params; +use log::*; +use lsp_types::{ + CodeAction, CodeLens, Command, CompletionItem, CompletionTextEdit, Diagnostic, + DiagnosticSeverity, DocumentHighlightKind, FileChangeType, FileEvent, Hover, HoverContents, + InsertTextFormat, Location, MarkedString, MarkupContent, MarkupKind, MessageType, + NumberOrString, Registration, SemanticHighlightingInformation, SymbolInformation, + TextDocumentItem, TextDocumentPositionParams, TraceOption, Url, WorkspaceEdit, +}; +use maplit::hashmap; +use pathdiff::diff_paths; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::{ + collections::{BTreeMap, HashMap}, + io::{BufRead, BufReader, BufWriter, Write}, + net::TcpStream, + path::{Path, PathBuf}, + process::{ChildStdin, ChildStdout}, + str::FromStr, + sync::{mpsc, Arc}, + time::{Duration, Instant}, +}; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum LSError { + #[error("Content Modified")] + ContentModified, +} + +#[derive(Debug, Error)] pub enum LCError { - #[fail( - display = "Language server is not running for: {}", - languageId - )] - NoLanguageServer { languageId: String }, -} - -// Extensions. -pub const REQUEST__GetState: &str = "languageClient/getState"; -pub const REQUEST__IsAlive: &str = "languageClient/isAlive"; -pub const REQUEST__StartServer: &str = "languageClient/startServer"; -pub const REQUEST__RegisterServerCommands: &str = "languageClient/registerServerCommands"; -pub const REQUEST__OmniComplete: &str = "languageClient/omniComplete"; -pub const REQUEST__SetLoggingLevel: &str = "languageClient/setLoggingLevel"; -pub const REQUEST__SetDiagnosticsList: &str = "languageClient/setDiagnosticsList"; -pub const REQUEST__RegisterHandlers: &str = "languageClient/registerHandlers"; -pub const REQUEST__NCMRefresh: &str = "LanguageClient_NCMRefresh"; -pub const REQUEST__NCM2OnComplete: &str = "LanguageClient_NCM2OnComplete"; -pub const REQUEST__ExplainErrorAtPoint: &str = "languageClient/explainErrorAtPoint"; -pub const REQUEST__FindLocations: &str = "languageClient/findLocations"; -pub const REQUEST__DebugInfo: &str = "languageClient/debugInfo"; -pub const NOTIFICATION__HandleBufNewFile: &str = "languageClient/handleBufNewFile"; -pub const NOTIFICATION__HandleBufReadPost: &str = "languageClient/handleBufReadPost"; -pub const NOTIFICATION__HandleTextChanged: &str = "languageClient/handleTextChanged"; -pub const NOTIFICATION__HandleBufWritePost: &str = "languageClient/handleBufWritePost"; -pub const NOTIFICATION__HandleBufDelete: &str = "languageClient/handleBufDelete"; -pub const NOTIFICATION__HandleCursorMoved: &str = "languageClient/handleCursorMoved"; -pub const NOTIFICATION__HandleCompleteDone: &str = "languageClient/handleCompleteDone"; -pub const NOTIFICATION__FZFSinkLocation: &str = "LanguageClient_FZFSinkLocation"; -pub const NOTIFICATION__FZFSinkCommand: &str = "LanguageClient_FZFSinkCommand"; -pub const NOTIFICATION__ServerExited: &str = "$languageClient/serverExited"; -pub const NOTIFICATION__ClearDocumentHighlight: &str = "languageClient/clearDocumentHighlight"; - -// Extensions by language servers. -pub const REQUEST__RustImplementations: &str = "rustDocument/implementations"; -pub const NOTIFICATION__RustBeginBuild: &str = "rustDocument/beginBuild"; -pub const NOTIFICATION__RustDiagnosticsBegin: &str = "rustDocument/diagnosticsBegin"; -pub const NOTIFICATION__RustDiagnosticsEnd: &str = "rustDocument/diagnosticsEnd"; -// This is an RLS extension but the name is general enough to assume it might be implemented by -// other language servers or planned for inclusion in the base protocol. -pub const NOTIFICATION__WindowProgress: &str = "window/progress"; -pub const NOTIFICATION__LanguageStatus: &str = "language/status"; -pub const REQUEST__ClassFileContents: &str = "java/classFileContents"; - -pub const CommandsClient: &[&str] = &["java.apply.workspaceEdit"]; - -// Vim variable names -pub const VIM__ServerStatus: &str = "g:LanguageClient_serverStatus"; -pub const VIM__ServerStatusMessage: &str = "g:LanguageClient_serverStatusMessage"; + #[error("No language server commands found for filetype: {}", language_id)] + NoServerCommands { language_id: String }, + #[error("Language server is not running for: {}", language_id)] + ServerNotRunning { language_id: String }, +} + +pub const REQUEST_GET_STATE: &str = "languageClient/getState"; +pub const REQUEST_IS_ALIVE: &str = "languageClient/isAlive"; +pub const REQUEST_START_SERVER: &str = "languageClient/startServer"; +pub const REQUEST_REGISTER_SERVER_COMMANDS: &str = "languageClient/registerServerCommands"; +pub const REQUEST_OMNI_COMPLETE: &str = "languageClient/omniComplete"; +pub const REQUEST_SET_LOGGING_LEVEL: &str = "languageClient/setLoggingLevel"; +pub const REQUEST_SET_DIAGNOSTICS_LIST: &str = "languageClient/setDiagnosticsList"; +pub const REQUEST_REGISTER_HANDLERS: &str = "languageClient/registerHandlers"; +pub const REQUEST_NCM_REFRESH: &str = "LanguageClient_NCMRefresh"; +pub const REQUEST_NCM2_ON_COMPLETE: &str = "LanguageClient_NCM2OnComplete"; +pub const REQUEST_EXPLAIN_ERROR_AT_POINT: &str = "languageClient/explainErrorAtPoint"; +pub const REQUEST_FIND_LOCATIONS: &str = "languageClient/findLocations"; +pub const REQUEST_DEBUG_INFO: &str = "languageClient/debugInfo"; +pub const REQUEST_CODE_LENS_ACTION: &str = "LanguageClient/handleCodeLensAction"; +pub const REQUEST_SEMANTIC_SCOPES: &str = "languageClient/semanticScopes"; +pub const REQUEST_SHOW_SEMANTIC_HL_SYMBOLS: &str = "languageClient/showSemanticHighlightSymbols"; +pub const REQUEST_CLASS_FILE_CONTENTS: &str = "java/classFileContents"; + +pub const NOTIFICATION_HANDLE_BUF_NEW_FILE: &str = "languageClient/handleBufNewFile"; +pub const NOTIFICATION_HANDLE_BUF_ENTER: &str = "languageClient/handleBufEnter"; +pub const NOTIFICATION_HANDLE_FILE_TYPE: &str = "languageClient/handleFileType"; +pub const NOTIFICATION_HANDLE_TEXT_CHANGED: &str = "languageClient/handleTextChanged"; +pub const NOTIFICATION_HANDLE_BUF_WRITE_POST: &str = "languageClient/handleBufWritePost"; +pub const NOTIFICATION_HANDLE_BUF_DELETE: &str = "languageClient/handleBufDelete"; +pub const NOTIFICATION_HANDLE_CURSOR_MOVED: &str = "languageClient/handleCursorMoved"; +pub const NOTIFICATION_HANDLE_COMPLETE_DONE: &str = "languageClient/handleCompleteDone"; +pub const NOTIFICATION_FZF_SINK_LOCATION: &str = "LanguageClient_FZFSinkLocation"; +pub const NOTIFICATION_FZF_SINK_COMMAND: &str = "LanguageClient_FZFSinkCommand"; +pub const NOTIFICATION_SERVER_EXITED: &str = "$languageClient/serverExited"; +pub const NOTIFICATION_CLEAR_DOCUMENT_HL: &str = "languageClient/clearDocumentHighlight"; +pub const NOTIFICATION_RUST_BEGIN_BUILD: &str = "rustDocument/beginBuild"; +pub const NOTIFICATION_RUST_DIAGNOSTICS_BEGIN: &str = "rustDocument/diagnosticsBegin"; +pub const NOTIFICATION_RUST_DIAGNOSTICS_END: &str = "rustDocument/diagnosticsEnd"; +pub const NOTIFICATION_WINDOW_PROGRESS: &str = "window/progress"; +pub const NOTIFICATION_LANGUAGE_STATUS: &str = "language/status"; + +pub const VIM_SERVER_STATUS: &str = "g:LanguageClient_serverStatus"; +pub const VIM_SERVER_STATUS_MESSAGE: &str = "g:LanguageClient_serverStatusMessage"; +pub const VIM_IS_SERVER_RUNNING: &str = "LanguageClient_isServerRunning"; +pub const VIM_STATUS_LINE_DIAGNOSTICS_COUNTS: &str = "LanguageClient_statusLineDiagnosticsCounts"; /// Thread safe read. -pub trait SyncRead: BufRead + Sync + Send + Debug {} +pub trait SyncRead: BufRead + Sync + Send + std::fmt::Debug {} impl SyncRead for BufReader {} impl SyncRead for BufReader {} /// Thread safe write. -pub trait SyncWrite: Write + Sync + Send + Debug {} +pub trait SyncWrite: Write + Sync + Send + std::fmt::Debug {} impl SyncWrite for BufWriter {} impl SyncWrite for BufWriter {} +/// Rpc message id. pub type Id = u64; +/// Language server id. +pub type LanguageId = Option; +/// Buffer id/handle. +pub type Bufnr = i64; #[derive(Debug, Serialize, Deserialize)] pub enum Message { - MethodCall(Option, rpc::MethodCall), - Notification(Option, rpc::Notification), - Output(rpc::Output), + MethodCall(LanguageId, jsonrpc_core::MethodCall), + Notification(LanguageId, jsonrpc_core::Notification), + Output(jsonrpc_core::Output), } #[derive(Debug, Serialize, Deserialize)] pub enum Call { - MethodCall(Option, rpc::MethodCall), - Notification(Option, rpc::Notification), + MethodCall(LanguageId, jsonrpc_core::MethodCall), + Notification(LanguageId, jsonrpc_core::Notification), } #[derive(Clone, Copy, Serialize)] pub struct HighlightSource { - pub buffer: u64, + pub buffer: Bufnr, pub source: u64, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum UseVirtualText { + Diagnostics, + CodeLens, + All, + No, +} + #[derive(Serialize)] pub struct State { // Program state. - pub id: Id, #[serde(skip_serializing)] - pub tx: Sender, + pub tx: crossbeam::channel::Sender, + #[serde(skip_serializing)] - pub rx: Receiver, - pub pending_calls: VecDeque, - pub pending_outputs: HashMap, + pub clients: HashMap>, - pub child_ids: HashMap, #[serde(skip_serializing)] - pub writers: HashMap>, + pub vim: Vim, + pub capabilities: HashMap, pub registrations: Vec, pub roots: HashMap, pub text_documents: HashMap, pub text_documents_metadata: HashMap, + pub semantic_scopes: HashMap>>, + pub semantic_scope_to_hl_group_table: HashMap>>, + // filename => semantic highlight state + pub semantic_highlights: HashMap, // filename => diagnostics. pub diagnostics: HashMap>, + // filename => codeLens. + pub code_lens: HashMap>, #[serde(skip_serializing)] pub line_diagnostics: HashMap<(String, u64), String>, - pub signs: HashMap>, - pub signs_placed: HashMap>, + /// Active signs. + pub signs: HashMap>, + pub namespace_ids: HashMap, pub highlight_source: Option, pub highlights: HashMap>, pub highlights_placed: HashMap>, @@ -120,63 +171,81 @@ pub struct State { #[serde(skip_serializing)] pub watchers: HashMap, #[serde(skip_serializing)] - pub watcher_rxs: HashMap>, + pub watcher_rxs: HashMap>, pub is_nvim: bool, pub last_cursor_line: u64, pub last_line_diagnostic: String, - pub stashed_codeAction_commands: Vec, + pub stashed_code_action_actions: Vec, // User settings. - pub serverCommands: HashMap>, - pub autoStart: bool, - pub selectionUI: SelectionUI, + pub server_commands: HashMap>, + // languageId => (scope_regex => highlight group) + pub semantic_highlight_maps: HashMap>, + pub semantic_scope_separator: String, + pub auto_start: bool, + pub selection_ui: SelectionUI, + pub selection_ui_auto_open: bool, pub trace: Option, - pub diagnosticsEnable: bool, - pub diagnosticsList: DiagnosticsList, - pub diagnosticsDisplay: HashMap, - pub diagnosticsSignsMax: Option, - pub documentHighlightDisplay: HashMap, - pub windowLogMessageLevel: MessageType, - pub settingsPath: String, - pub loadSettings: bool, - pub rootMarkers: Option, + pub diagnostics_enable: bool, + pub diagnostics_list: DiagnosticsList, + pub diagnostics_display: HashMap, + pub diagnostics_signs_max: Option, + pub diagnostics_max_severity: DiagnosticSeverity, + pub document_highlight_display: HashMap, + pub window_log_message_level: MessageType, + pub settings_path: Vec, + pub load_settings: bool, + pub root_markers: Option, pub change_throttle: Option, pub wait_output_timeout: Duration, - pub hoverPreview: HoverPreviewOption, - pub completionPreferTextEdit: bool, + pub hover_preview: HoverPreviewOption, + pub completion_prefer_text_edit: bool, + pub apply_completion_additional_text_edits: bool, + pub use_virtual_text: UseVirtualText, + pub hide_virtual_texts_on_insert: bool, + pub echo_project_root: bool, - pub loggingFile: Option, - pub loggingLevel: log::LevelFilter, - pub serverStderr: Option, - #[serde(skip_serializing)] - pub logger: log4rs::Handle, + pub server_stderr: Option, + pub logger: Logger, + pub preferred_markup_kind: Option>, } impl State { - pub fn new() -> Result { - let logger = logger::init()?; + #[allow(clippy::new_ret_no_self)] + pub fn new(tx: crossbeam::channel::Sender) -> Result { + let logger = Logger::new()?; + + let client = Arc::new(RpcClient::new( + None, + BufReader::new(std::io::stdin()), + BufWriter::new(std::io::stdout()), + None, + tx.clone(), + )?); + + Ok(Self { + tx, - let (tx, rx) = channel(); + clients: hashmap! { + None => client.clone(), + }, - Ok(State { - id: 0, - tx, - rx, - pending_calls: VecDeque::new(), - pending_outputs: HashMap::new(), + vim: Vim::new(client), - child_ids: HashMap::new(), - writers: HashMap::new(), capabilities: HashMap::new(), registrations: vec![], roots: HashMap::new(), text_documents: HashMap::new(), text_documents_metadata: HashMap::new(), + semantic_scopes: HashMap::new(), + semantic_scope_to_hl_group_table: HashMap::new(), + semantic_highlights: HashMap::new(), + code_lens: HashMap::new(), diagnostics: HashMap::new(), line_diagnostics: HashMap::new(), signs: HashMap::new(), - signs_placed: HashMap::new(), + namespace_ids: HashMap::new(), highlight_source: None, highlights: HashMap::new(), highlights_placed: HashMap::new(), @@ -189,37 +258,44 @@ impl State { is_nvim: false, last_cursor_line: 0, last_line_diagnostic: " ".into(), - stashed_codeAction_commands: vec![], - - serverCommands: HashMap::new(), - autoStart: true, - selectionUI: SelectionUI::LocationList, + stashed_code_action_actions: vec![], + + server_commands: HashMap::new(), + semantic_highlight_maps: HashMap::new(), + semantic_scope_separator: ":".into(), + auto_start: true, + selection_ui: SelectionUI::LocationList, + selection_ui_auto_open: true, trace: None, - diagnosticsEnable: true, - diagnosticsList: DiagnosticsList::Quickfix, - diagnosticsDisplay: DiagnosticsDisplay::default(), - diagnosticsSignsMax: None, - documentHighlightDisplay: DocumentHighlightDisplay::default(), - windowLogMessageLevel: MessageType::Warning, - settingsPath: format!(".vim{}settings.json", std::path::MAIN_SEPARATOR), - loadSettings: false, - rootMarkers: None, + diagnostics_enable: true, + diagnostics_list: DiagnosticsList::Quickfix, + diagnostics_display: DiagnosticsDisplay::default(), + diagnostics_signs_max: None, + diagnostics_max_severity: DiagnosticSeverity::Hint, + document_highlight_display: DocumentHighlightDisplay::default(), + window_log_message_level: MessageType::Warning, + settings_path: vec![format!(".vim{}settings.json", std::path::MAIN_SEPARATOR)], + load_settings: false, + root_markers: None, change_throttle: None, wait_output_timeout: Duration::from_secs(10), - hoverPreview: HoverPreviewOption::default(), - completionPreferTextEdit: false, - loggingFile: None, - loggingLevel: log::LevelFilter::Warn, - serverStderr: None, + hover_preview: HoverPreviewOption::default(), + completion_prefer_text_edit: false, + apply_completion_additional_text_edits: true, + use_virtual_text: UseVirtualText::All, + hide_virtual_texts_on_insert: true, + echo_project_root: true, + server_stderr: None, + preferred_markup_kind: None, logger, }) } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum SelectionUI { - FZF, + Funcref, Quickfix, LocationList, } @@ -231,19 +307,36 @@ impl Default for SelectionUI { } impl FromStr for SelectionUI { - type Err = Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s.to_ascii_uppercase().as_str() { - "FZF" => Ok(SelectionUI::FZF), + "FUNCREF" | "FZF" => Ok(SelectionUI::Funcref), "QUICKFIX" => Ok(SelectionUI::Quickfix), "LOCATIONLIST" | "LOCATION-LIST" => Ok(SelectionUI::LocationList), - _ => bail!("Invalid option for LanguageClient_selectionUI: {}", s), + _ => Err(anyhow!( + "Invalid option for LanguageClient_selectionUI: {}", + s + )), } } } -#[derive(Debug, Serialize, Deserialize)] +pub enum LCNamespace { + VirtualText, + SemanticHighlight, +} + +impl LCNamespace { + pub fn name(&self) -> String { + match self { + LCNamespace::VirtualText => "LanguageClient_VirtualText".into(), + LCNamespace::SemanticHighlight => "LanguageClient_SemanticHighlight".into(), + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum HoverPreviewOption { Always, Auto, @@ -257,19 +350,22 @@ impl Default for HoverPreviewOption { } impl FromStr for HoverPreviewOption { - type Err = Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s.to_ascii_uppercase().as_str() { "ALWAYS" => Ok(HoverPreviewOption::Always), "AUTO" => Ok(HoverPreviewOption::Auto), "NEVER" => Ok(HoverPreviewOption::Never), - _ => bail!("Invalid option for LanguageClient_hoverPreview: {}", s), + _ => Err(anyhow!( + "Invalid option for LanguageClient_hoverPreview: {}", + s + )), } } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum DiagnosticsList { Quickfix, Location, @@ -283,97 +379,78 @@ impl Default for DiagnosticsList { } impl FromStr for DiagnosticsList { - type Err = Error; + type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s.to_ascii_uppercase().as_str() { "QUICKFIX" => Ok(DiagnosticsList::Quickfix), "LOCATION" => Ok(DiagnosticsList::Location), "DISABLED" => Ok(DiagnosticsList::Disabled), - _ => bail!("Invalid option for LanguageClient_diagnosticsList: {}", s), + _ => Err(anyhow!( + "Invalid option for LanguageClient_diagnosticsList: {}", + s + )), } } } #[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct DiagnosticsDisplay { pub name: String, pub texthl: String, - pub signText: String, - pub signTexthl: String, + pub sign_text: String, + pub sign_texthl: String, + pub virtual_texthl: String, } impl DiagnosticsDisplay { - pub fn default() -> HashMap { + pub fn default() -> HashMap { let mut map = HashMap::new(); map.insert( 1, - DiagnosticsDisplay { + Self { name: "Error".to_owned(), texthl: "ALEError".to_owned(), - signText: "✖".to_owned(), - signTexthl: "ALEErrorSign".to_owned(), + sign_text: "✖".to_owned(), + sign_texthl: "ALEErrorSign".to_owned(), + virtual_texthl: "Error".to_owned(), }, ); map.insert( 2, - DiagnosticsDisplay { + Self { name: "Warning".to_owned(), texthl: "ALEWarning".to_owned(), - signText: "⚠".to_owned(), - signTexthl: "ALEWarningSign".to_owned(), + sign_text: "⚠".to_owned(), + sign_texthl: "ALEWarningSign".to_owned(), + virtual_texthl: "Todo".to_owned(), }, ); map.insert( 3, - DiagnosticsDisplay { + Self { name: "Information".to_owned(), texthl: "ALEInfo".to_owned(), - signText: "ℹ".to_owned(), - signTexthl: "ALEInfoSign".to_owned(), + sign_text: "ℹ".to_owned(), + sign_texthl: "ALEInfoSign".to_owned(), + virtual_texthl: "Todo".to_owned(), }, ); map.insert( 4, - DiagnosticsDisplay { + Self { name: "Hint".to_owned(), texthl: "ALEInfo".to_owned(), - signText: "➤".to_owned(), - signTexthl: "ALEInfoSign".to_owned(), + sign_text: "➤".to_owned(), + sign_texthl: "ALEInfoSign".to_owned(), + virtual_texthl: "Todo".to_owned(), }, ); map } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Sign { - pub id: u64, - pub line: u64, - pub text: String, - pub severity: Option, -} - -impl Sign { - pub fn new(line: u64, text: String, severity: Option) -> Sign { - Sign { - id: Self::get_id(line, severity), - line, - text, - severity, - } - } - - fn get_id(line: u64, severity: Option) -> u64 { - let base_id = 75_000; - base_id + (line - 1) * 4 + severity - .unwrap_or(DiagnosticSeverity::Hint) - .to_int() - .unwrap_or(4) - - 1 - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DocumentHighlightDisplay { pub name: String, @@ -381,25 +458,25 @@ pub struct DocumentHighlightDisplay { } impl DocumentHighlightDisplay { - pub fn default() -> HashMap { + pub fn default() -> HashMap { let mut map = HashMap::new(); map.insert( 1, - DocumentHighlightDisplay { + Self { name: "Text".to_owned(), texthl: "SpellCap".to_owned(), }, ); map.insert( 2, - DocumentHighlightDisplay { + Self { name: "Read".to_owned(), texthl: "SpellLocal".to_owned(), }, ); map.insert( 3, - DocumentHighlightDisplay { + Self { name: "Write".to_owned(), texthl: "SpellRare".to_owned(), }, @@ -408,32 +485,11 @@ impl DocumentHighlightDisplay { } } -impl std::cmp::Ord for Sign { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.id.cmp(&other.id) - } -} - -impl std::cmp::PartialOrd for Sign { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl std::cmp::PartialEq for Sign { - fn eq(&self, other: &Self) -> bool { - // Quick check whether sign should be updated. - self.text == other.text && self.severity == other.severity - } -} - -impl std::cmp::Eq for Sign {} - -use std::hash::{Hash, Hasher}; -impl Hash for Sign { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TextDocumentSemanticHighlightState { + pub last_version: Option, + pub symbols: Vec, + pub highlights: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -452,6 +508,12 @@ impl PartialEq for Highlight { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClearNamespace { + pub line_start: u64, + pub line_end: u64, +} + #[derive(Debug, Serialize, Deserialize)] pub struct QuickfixEntry { pub filename: String, @@ -530,8 +592,10 @@ pub struct VimCompleteItem { #[serde(skip_serializing_if = "Option::is_none")] pub dup: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[deprecated(note = "use `user_data` instead")] pub snippet: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[deprecated(note = "use `user_data` instead")] pub is_snippet: Option, // NOTE: `user_data` can only be string in vim. So cannot specify concrete type here. #[serde(skip_serializing_if = "Option::is_none")] @@ -542,46 +606,71 @@ pub struct VimCompleteItem { pub struct VimCompleteItemUserData { #[serde(skip_serializing_if = "Option::is_none")] pub lspitem: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub snippet: Option, } impl VimCompleteItem { - pub fn from_lsp( - lspitem: &CompletionItem, - complete_position: Option, - ) -> Result { + pub fn from_lsp(lspitem: &CompletionItem, complete_position: Option) -> Result { + debug!( + "LSP CompletionItem to VimCompleteItem: {:?}, {:?}", + lspitem, complete_position + ); let abbr = lspitem.label.clone(); - let mut word = lspitem.insert_text.clone().unwrap_or_default(); - if word.is_empty() { + + if let Some(CompletionTextEdit::InsertAndReplace(_)) = lspitem.text_edit { + error!("insert replace is not supported"); + } + + let word = lspitem.insert_text.clone().unwrap_or_else(|| { + if lspitem.text_edit.iter().any(|te| match te { + CompletionTextEdit::Edit(_) => false, + CompletionTextEdit::InsertAndReplace(_) => true, + }) { + error!("insert replace is not supported"); + } + + if lspitem.insert_text_format == Some(InsertTextFormat::Snippet) + || lspitem + .text_edit + .as_ref() + .map(|text_edit| match text_edit { + CompletionTextEdit::Edit(edit) => edit.new_text.is_empty(), + // InsertAndReplace is not supported, so if we encounter a completion item + // with a text edit of this variant, then we just default to using the label + // as the format instead of the new text. + CompletionTextEdit::InsertAndReplace(_) => true, + }) + .unwrap_or(true) + { + return lspitem.label.clone(); + } + match (lspitem.text_edit.clone(), complete_position) { - (Some(text_edit), Some(complete_position)) => { + // see comment above about InsertAndReplace + (Some(CompletionTextEdit::InsertAndReplace(_)), _) => lspitem.label.clone(), + (Some(CompletionTextEdit::Edit(ref text_edit)), Some(complete_position)) => { // TextEdit range start might be different from vim expected completion start. // From spec, TextEdit can only span one line, i.e., the current line. - if text_edit.range.start.line != complete_position { - word = text_edit + if text_edit.range.start.character != complete_position { + text_edit .new_text .get((complete_position as usize)..) .and_then(|line| line.split_whitespace().next()) - .map_or_else(String::new, ToOwned::to_owned); + .map_or_else(String::new, ToOwned::to_owned) } else { - word = text_edit.new_text.clone(); + text_edit.new_text.clone() } } - (Some(text_edit), _) => { - word = text_edit.new_text.clone(); - } - (_, _) => { - word = lspitem.label.clone(); - } + (Some(CompletionTextEdit::Edit(ref text_edit)), _) => text_edit.new_text.clone(), + (_, _) => lspitem.label.clone(), } - } + }); - let is_snippet; let snippet; if lspitem.insert_text_format == Some(InsertTextFormat::Snippet) { - is_snippet = Some(true); snippet = Some(word.clone()); } else { - is_snippet = None; snippet = None; }; @@ -592,31 +681,37 @@ impl VimCompleteItem { let user_data = VimCompleteItemUserData { lspitem: Some(lspitem.clone()), + snippet: snippet.clone(), }; - Ok(VimCompleteItem { + #[allow(deprecated)] + Ok(Self { word, abbr, icase: Some(1), dup: Some(1), - menu: lspitem.detail.clone().unwrap_or_default(), + menu: lspitem + .detail + .clone() + .unwrap_or_default() + .replace("\n", " "), info, kind: lspitem.kind.map(|k| format!("{:?}", k)).unwrap_or_default(), + is_snippet: Some(snippet.is_some()), snippet, - is_snippet, user_data: Some(serde_json::to_string(&user_data)?), }) } } pub trait ToRpcError { - fn to_rpc_error(&self) -> rpc::Error; + fn to_rpc_error(&self) -> jsonrpc_core::Error; } -impl ToRpcError for Error { - fn to_rpc_error(&self) -> rpc::Error { - rpc::Error { - code: rpc::ErrorCode::InternalError, +impl ToRpcError for anyhow::Error { + fn to_rpc_error(&self) -> jsonrpc_core::Error { + jsonrpc_core::Error { + code: jsonrpc_core::ErrorCode::InternalError, message: self.to_string(), data: None, } @@ -624,23 +719,21 @@ impl ToRpcError for Error { } pub trait ToParams { - fn to_params(self) -> Result>; + fn to_params(self) -> Result; } impl ToParams for T where T: Serialize, { - fn to_params(self) -> Result> { + fn to_params(self) -> Result { let json_value = serde_json::to_value(self)?; let params = match json_value { - Value::Null => None, - Value::Bool(_) | Value::Number(_) | Value::String(_) => { - Some(Params::Array(vec![json_value])) - } - Value::Array(vec) => Some(Params::Array(vec)), - Value::Object(map) => Some(Params::Map(map)), + Value::Null => Params::None, + Value::Bool(_) | Value::Number(_) | Value::String(_) => Params::Array(vec![json_value]), + Value::Array(vec) => Params::Array(vec), + Value::Object(map) => Params::Map(map), }; Ok(params) @@ -657,12 +750,12 @@ impl<'a> ToInt for &'a str { } } -impl ToInt for rpc::Id { +impl ToInt for jsonrpc_core::Id { fn to_int(&self) -> Result { match *self { - rpc::Id::Num(id) => Ok(id), - rpc::Id::Str(ref s) => s.as_str().to_int(), - rpc::Id::Null => Err(err_msg("Null id")), + jsonrpc_core::Id::Num(id) => Ok(id), + jsonrpc_core::Id::Str(ref s) => s.as_str().to_int(), + jsonrpc_core::Id::Null => Err(anyhow!("Null id")), } } } @@ -671,7 +764,7 @@ pub trait ToString { fn to_string(&self) -> String; } -impl ToString for lsp::MarkedString { +impl ToString for lsp_types::MarkedString { fn to_string(&self) -> String { match *self { MarkedString::String(ref s) => s.clone(), @@ -680,7 +773,7 @@ impl ToString for lsp::MarkedString { } } -impl ToString for lsp::MarkupContent { +impl ToString for lsp_types::MarkupContent { fn to_string(&self) -> String { self.value.clone() } @@ -692,7 +785,7 @@ impl ToString for Hover { HoverContents::Scalar(ref ms) => ms.to_string(), HoverContents::Array(ref vec) => vec .iter() - .map(|i| i.to_string()) + .map(ToString::to_string) .collect::>() .join("\n"), HoverContents::Markup(ref mc) => mc.to_string(), @@ -700,11 +793,11 @@ impl ToString for Hover { } } -impl ToString for lsp::Documentation { +impl ToString for lsp_types::Documentation { fn to_string(&self) -> String { match *self { - lsp::Documentation::String(ref s) => s.to_owned(), - lsp::Documentation::MarkupContent(ref mc) => mc.to_string(), + lsp_types::Documentation::String(ref s) => s.to_owned(), + lsp_types::Documentation::MarkupContent(ref mc) => mc.to_string(), } } } @@ -720,21 +813,24 @@ impl ToString for NumberOrString { pub trait ToDisplay { fn to_display(&self) -> Vec; + fn vim_filetype(&self) -> Option { + None + } } -impl ToDisplay for lsp::MarkedString { +impl ToDisplay for lsp_types::MarkedString { fn to_display(&self) -> Vec { - match *self { - MarkedString::String(ref s) => s.lines().map(|i| i.to_string()).collect(), - MarkedString::LanguageString(ref ls) => { - let mut buf = Vec::new(); - - buf.push(format!("```{}", ls.language)); - buf.extend(ls.value.lines().map(|i| i.to_string())); - buf.push("```".to_string()); + let s = match self { + MarkedString::String(ref s) => s, + MarkedString::LanguageString(ref ls) => &ls.value, + }; + s.lines().map(String::from).collect() + } - buf - } + fn vim_filetype(&self) -> Option { + match self { + MarkedString::String(_) => Some("markdown".to_string()), + MarkedString::LanguageString(ref ls) => Some(ls.language.clone()), } } } @@ -743,23 +839,59 @@ impl ToDisplay for MarkupContent { fn to_display(&self) -> Vec { self.value.lines().map(str::to_string).collect() } + + fn vim_filetype(&self) -> Option { + match self.kind { + MarkupKind::Markdown => Some("markdown".to_string()), + MarkupKind::PlainText => Some("text".to_string()), + } + } } impl ToDisplay for Hover { fn to_display(&self) -> Vec { match self.contents { HoverContents::Scalar(ref ms) => ms.to_display(), - HoverContents::Array(ref arr) => arr.iter().flat_map(ToDisplay::to_display).collect(), + HoverContents::Array(ref arr) => arr + .iter() + .flat_map(|ms| { + if let MarkedString::LanguageString(ref ls) = ms { + let mut buf = Vec::new(); + + buf.push(format!("```{}", ls.language)); + buf.extend(ls.value.lines().map(String::from)); + buf.push("```".to_string()); + + buf + } else { + ms.to_display() + } + }) + .collect(), HoverContents::Markup(ref mc) => mc.to_display(), } } + + fn vim_filetype(&self) -> Option { + match self.contents { + HoverContents::Scalar(ref ms) => ms.vim_filetype(), + HoverContents::Array(_) => Some("markdown".to_string()), + HoverContents::Markup(ref mc) => mc.vim_filetype(), + } + } +} + +impl ToDisplay for str { + fn to_display(&self) -> Vec { + self.lines().map(String::from).collect() + } } pub trait LinesLen { fn lines_len(&self) -> usize; } -impl LinesLen for lsp::MarkedString { +impl LinesLen for lsp_types::MarkedString { fn lines_len(&self) -> usize { match *self { MarkedString::String(ref s) => s.lines().count(), @@ -778,7 +910,7 @@ impl LinesLen for Hover { fn lines_len(&self) -> usize { match self.contents { HoverContents::Scalar(ref c) => c.lines_len(), - HoverContents::Array(ref arr) => arr.iter().map(|i| i.lines_len()).sum(), + HoverContents::Array(ref arr) => arr.iter().map(LinesLen::lines_len).sum(), HoverContents::Markup(ref c) => c.lines_len(), } } @@ -827,65 +959,18 @@ impl ToUsize for u64 { } } -#[derive(Debug, PartialEq)] -pub enum VimVar { - Buftype, - LanguageId, - Filename, - Line, - Character, - Text, - Cword, - NewName, - GotoCmd, - Handle, - IncludeDeclaration, -} - pub trait VimExp { fn to_key(&self) -> String; fn to_exp(&self) -> String; } -impl VimExp for VimVar { - fn to_key(&self) -> String { - match *self { - VimVar::Buftype => "buftype", - VimVar::LanguageId => "languageId", - VimVar::Filename => "filename", - VimVar::Line => "line", - VimVar::Character => "character", - VimVar::Text => "text", - VimVar::Cword => "cword", - VimVar::NewName => "newName", - VimVar::GotoCmd => "gotoCmd", - VimVar::Handle => "handle", - VimVar::IncludeDeclaration => "includeDeclaration", - }.to_owned() - } - - fn to_exp(&self) -> String { - match *self { - VimVar::Buftype => "&buftype", - VimVar::LanguageId => "&filetype", - VimVar::Filename => "LSP#filename()", - VimVar::Line => "LSP#line()", - VimVar::Character => "LSP#character()", - VimVar::Text => "LSP#text()", - VimVar::Cword => "expand('')", - VimVar::NewName | VimVar::GotoCmd => "v:null", - VimVar::Handle | VimVar::IncludeDeclaration => "v:true", - }.to_owned() - } -} - impl<'a> VimExp for &'a str { fn to_key(&self) -> String { - self.to_string() + (*self).to_string() } fn to_exp(&self) -> String { - self.to_string() + (*self).to_string() } } @@ -976,16 +1061,6 @@ impl Default for TextDocumentItemMetadata { } } -pub trait OptionDeref { - fn as_deref(&self) -> Option<&T::Target>; -} - -impl OptionDeref for Option { - fn as_deref(&self) -> Option<&T::Target> { - self.as_ref().map(Deref::deref) - } -} - pub trait ToLSP { fn to_lsp(self) -> Result; } @@ -1020,47 +1095,165 @@ impl ToLSP> for notify::DebouncedEvent { }, ]), notify::DebouncedEvent::Chmod(_) | notify::DebouncedEvent::Rescan => Ok(vec![]), - e @ notify::DebouncedEvent::Error(_, _) => Err(format_err!("{:?}", e)), + e @ notify::DebouncedEvent::Error(_, _) => Err(anyhow!("{:?}", e)), } } } -impl ToLSP for Value -where - T: DeserializeOwned, -{ - fn to_lsp(self) -> Result { - Ok(serde_json::from_value(self)?) +pub trait ListItem { + fn quickfix_item(&self, lc: &LanguageClient) -> Result; + fn string_item(&self, lc: &LanguageClient, cwd: &str) -> Result; +} + +impl ListItem for Location { + fn quickfix_item(&self, lc: &LanguageClient) -> Result { + let filename = self.uri.filepath()?.to_string_lossy().into_owned(); + let start = self.range.start; + let text = lc.get_line(&filename, start.line).unwrap_or_default(); + + Ok(QuickfixEntry { + filename, + lnum: start.line + 1, + col: Some(start.character + 1), + text: Some(text), + nr: None, + typ: None, + }) + } + + fn string_item(&self, lc: &LanguageClient, cwd: &str) -> Result { + let filename = self.uri.filepath()?; + let start = self.range.start; + let text = lc.get_line(&filename, start.line).unwrap_or_default(); + let relpath = diff_paths(&filename, Path::new(&cwd)).unwrap_or(filename); + Ok(format!( + "{}:{}:{}:\t{}", + relpath.to_string_lossy(), + start.line + 1, + start.character + 1, + text, + )) } } -impl ToLSP for Option -where - T: DeserializeOwned, -{ - fn to_lsp(self) -> Result { - serde_json::to_value(self)?.to_lsp() +impl ListItem for CodeAction { + fn quickfix_item(&self, _: &LanguageClient) -> Result { + let text = Some(format!( + "{}: {}", + code_action_kind_as_str(&self), + self.title + )); + + Ok(QuickfixEntry { + filename: "".into(), + lnum: 0, + col: None, + text, + nr: None, + typ: None, + }) + } + + fn string_item(&self, _: &LanguageClient, _: &str) -> Result { + Ok(format!( + "{}: {}", + code_action_kind_as_str(&self), + self.title + )) } } -pub trait FromLSP -where - Self: Sized, -{ - fn from_lsp(f: &F) -> Result; +impl ListItem for Command { + fn quickfix_item(&self, _: &LanguageClient) -> Result { + Ok(QuickfixEntry { + filename: "".into(), + lnum: 0, + col: None, + text: Some(format!("{}: {}", self.command, self.title)), + nr: None, + typ: None, + }) + } + + fn string_item(&self, _: &LanguageClient, _: &str) -> Result { + Ok(format!("{}: {}", self.command, self.title)) + } } -impl FromLSP for QuickfixEntry { - fn from_lsp(sym: &SymbolInformation) -> Result { - let start = sym.location.range.start; +impl ListItem for lsp_types::DocumentSymbol { + fn quickfix_item(&self, _: &LanguageClient) -> Result { + let start = self.selection_range.start; + let result = QuickfixEntry { + filename: "".to_string(), + lnum: start.line + 1, + col: Some(start.character + 1), + text: Some(self.name.clone()), + nr: None, + typ: None, + }; + Ok(result) + } + + fn string_item(&self, _: &LanguageClient, _: &str) -> Result { + let start = self.selection_range.start; + let result = format!( + "{}:{}:\t{}\t\t{:?}", + start.line + 1, + start.character + 1, + self.name.clone(), + self.kind + ); + Ok(result) + } +} + +impl ListItem for SymbolInformation { + fn quickfix_item(&self, _: &LanguageClient) -> Result { + let start = self.location.range.start; Ok(QuickfixEntry { - filename: sym.location.uri.filepath()?.to_string_lossy().into_owned(), + filename: self.location.uri.filepath()?.to_string_lossy().into_owned(), lnum: start.line + 1, col: Some(start.character + 1), - text: Some(sym.name.clone()), + text: Some(self.name.clone()), nr: None, typ: None, }) } + + fn string_item(&self, _: &LanguageClient, cwd: &str) -> Result { + let filename = self.location.uri.filepath()?; + let relpath = diff_paths(&filename, Path::new(cwd)).unwrap_or(filename); + let start = self.location.range.start; + Ok(format!( + "{}:{}:{}:\t{}\t\t{:?}", + relpath.to_string_lossy(), + start.line + 1, + start.character + 1, + self.name, + self.kind + )) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RawMessage { + Notification(jsonrpc_core::Notification), + MethodCall(jsonrpc_core::MethodCall), + Output(jsonrpc_core::Output), +} + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct VirtualText { + pub line: u64, + pub text: String, + pub hl_group: String, +} + +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkspaceEditWithCursor { + pub workspace_edit: WorkspaceEdit, + pub cursor_position: Option, } diff --git a/vim/bundle/LanguageClient-neovim/src/utils.rs b/vim/bundle/LanguageClient-neovim/src/utils.rs index b60a80a..264339c 100644 --- a/vim/bundle/LanguageClient-neovim/src/utils.rs +++ b/vim/bundle/LanguageClient-neovim/src/utils.rs @@ -1,27 +1,32 @@ -use super::*; +use crate::types::{RootMarkers, ToUsize}; +use anyhow::{anyhow, Result}; +use log::*; +use lsp_types::{CodeAction, Position, TextEdit, Url}; +use serde_json::json; +use serde_json::Value; +use std::{ + collections::{HashMap, HashSet}, + path::Path, + str::FromStr, +}; pub fn escape_single_quote>(s: S) -> String { s.as_ref().replace("'", "''") } -#[test] -fn test_escape_single_quote() { - assert_eq!(escape_single_quote("my' precious"), "my'' precious"); -} - -pub fn get_rootPath<'a>( +pub fn get_root_path<'a>( path: &'a Path, - languageId: &str, - rootMarkers: &Option, + language_id: &str, + root_markers: &Option, ) -> Result<&'a Path> { - if let Some(ref rootMarkers) = *rootMarkers { - let empty = vec![]; - let rootMarkers = match *rootMarkers { + if let Some(ref root_markers) = *root_markers { + let empty = &vec![]; + let root_markers = match *root_markers { RootMarkers::Array(ref arr) => arr, - RootMarkers::Map(ref map) => map.get(languageId).unwrap_or(&empty), + RootMarkers::Map(ref map) => map.get(language_id).unwrap_or(empty), }; - for marker in rootMarkers { + for marker in root_markers { let ret = traverse_up(path, |dir| { let p = dir.join(marker); let p = p.to_str(); @@ -41,14 +46,17 @@ pub fn get_rootPath<'a>( } } - match languageId { + match language_id { "rust" => traverse_up(path, |dir| dir.join("Cargo.toml").exists()), "php" => traverse_up(path, |dir| dir.join("composer.json").exists()), - "javascript" | "typescript" => traverse_up(path, |dir| dir.join("package.json").exists()), + "javascript" | "typescript" | "javascript.jsx" | "typescript.tsx" => { + traverse_up(path, |dir| dir.join("package.json").exists()) + } "python" => traverse_up(path, |dir| { dir.join("setup.py").exists() || dir.join("Pipfile").exists() || dir.join("requirements.txt").exists() + || dir.join("pyproject.toml").exists() }), "c" | "cpp" => traverse_up(path, |dir| dir.join("compile_commands.json").exists()), "cs" => traverse_up(path, is_dotnet_root), @@ -58,17 +66,23 @@ pub fn get_rootPath<'a>( || dir.join("build.gradle").exists() }), "scala" => traverse_up(path, |dir| dir.join("build.sbt").exists()), - "haskell" => traverse_up(path, |dir| dir.join("stack.yaml").exists()) - .or_else(|_| traverse_up(path, |dir| dir.join(".cabal").exists())), - _ => Err(format_err!("Unknown languageId: {}", languageId)), - }.or_else(|_| { + "haskell" => traverse_up(path, |dir| dir.join("stack.yaml").exists()).or_else(|_| { + traverse_up(path, |dir| { + dir_contains_file(dir, |f| has_extension(f, "cabal")) + }) + }), + "go" => traverse_up(path, |dir| dir.join("go.mod").exists()), + _ => Err(anyhow!("Unknown languageId: {}", language_id)), + } + .or_else(|_| { traverse_up(path, |dir| { dir.join(".git").exists() || dir.join(".hg").exists() || dir.join(".svn").exists() }) - }).or_else(|_| { + }) + .or_else(|_| { let parent = path .parent() - .ok_or_else(|| format_err!("Failed to get parent dir! path: {:?}", path)); + .ok_or_else(|| anyhow!("Failed to get parent dir! path: {:?}", path)); warn!( "Unknown project type. Fallback to use dir as project root: {:?}", parent @@ -77,6 +91,32 @@ pub fn get_rootPath<'a>( }) } +/// If iterating the directory fails (e.g. because it is not a directory), returns false; if a +/// file cannot be inspected, that file is considered nonexistent. +fn dir_contains_file(path: &Path, predicate: F) -> bool +where + F: Fn(&Path) -> bool, +{ + if let Ok(diriter) = path.read_dir() { + for entry in diriter { + if let Ok(entry) = entry { + if predicate(&entry.path()) { + return true; + } + } + } + } + + false +} + +fn has_extension(path: &Path, ext: &str) -> bool { + match path.extension().and_then(|e| e.to_str()) { + Some(path_ext) if path_ext == ext => true, + _ => false, + } +} + fn traverse_up(path: &Path, predicate: F) -> Result<&Path> where F: Fn(&Path) -> bool, @@ -85,7 +125,7 @@ where return Ok(path); } - let next_path = path.parent().ok_or_else(|| err_msg("Hit root"))?; + let next_path = path.parent().ok_or_else(|| anyhow!("Hit root"))?; traverse_up(next_path, predicate) } @@ -121,11 +161,46 @@ impl + std::fmt::Debug> ToUrl for P { fn to_url(&self) -> Result { Url::from_file_path(self) .or_else(|_| Url::from_str(&self.as_ref().to_string_lossy())) - .or_else(|_| Err(format_err!("Failed to convert ({:?}) to Url", self))) + .or_else(|_| Err(anyhow!("Failed to convert ({:?}) to Url", self))) } } -pub fn apply_TextEdits(lines: &[String], edits: &[TextEdit]) -> Result> { +fn position_to_offset(lines: &[String], position: &Position) -> usize { + if lines.is_empty() { + return 0; + } + + let line = std::cmp::min(position.line as usize, lines.len() - 1); + let character = std::cmp::min(position.character as usize, lines[line].len()); + + let chars_above: usize = lines[..line].iter().map(|text| text.len() + 1).sum(); + chars_above + character +} + +fn offset_to_position(lines: &[String], offset: usize) -> Position { + if lines.is_empty() { + return Position::new(0, 0); + } + + let mut offset = offset; + for (line, text) in lines.iter().enumerate() { + if offset <= text.len() { + return Position::new(line as u64, offset as u64); + } + + offset -= text.len() + 1; + } + + let last_line = lines.len() - 1; + let last_character = lines[last_line].len(); + Position::new(last_line as u64, last_character as u64) +} + +pub fn apply_text_edits( + lines: &[String], + edits: &[TextEdit], + position: &Position, +) -> Result<(Vec, Position)> { // Edits are ordered from bottom to top, from right to left. let mut edits_by_index = vec![]; for edit in edits { @@ -136,172 +211,44 @@ pub fn apply_TextEdits(lines: &[String], edits: &[TextEdit]) -> Result = r#"fn main() { -0; -} -"#.lines() - .map(|l| l.to_owned()) - .collect(); - - let expect: Vec = r#"fn main() { - 0; -} -"#.lines() - .map(|l| l.to_owned()) - .collect(); - - let edit = TextEdit { - range: Range { - start: Position { - line: 0, - character: 0, - }, - end: Position { - line: 3, - character: 0, - }, - }, - new_text: r#"fn main() { - 0; -} -"#.to_owned(), - }; - - assert_eq!(apply_TextEdits(&lines, &[edit]).unwrap(), expect); -} - -#[test] -fn test_apply_TextEdit_overlong_end() { - let lines: Vec = r#"abc = 123"#.lines().map(|l| l.to_owned()).collect(); - - let expect: Vec = r#"nb = 123"#.lines().map(|l| l.to_owned()).collect(); - - let edit = TextEdit { - range: Range { - start: Position { - line: 0, - character: 0, - }, - end: Position { - line: 99999999, - character: 0, - }, - }, - new_text: r#"nb = 123"#.to_owned(), - }; - - assert_eq!(apply_TextEdits(&lines, &[edit]).unwrap(), expect); -} -fn get_command_add_sign(sign: &Sign, filename: &str) -> String { - format!( - "sign place {} line={} name=LanguageClient{:?} file={}", - sign.id, - sign.line, - sign.severity.unwrap_or(DiagnosticSeverity::Hint), - filename - ) -} - -#[test] -fn test_get_command_add_sign() { - let sign = Sign::new(1, "".to_owned(), Some(DiagnosticSeverity::Error)); - assert_eq!( - get_command_add_sign(&sign, ""), - "sign place 75000 line=1 name=LanguageClientError file=" - ); - - let sign = Sign::new(7, "".to_owned(), Some(DiagnosticSeverity::Error)); - assert_eq!( - get_command_add_sign(&sign, ""), - "sign place 75024 line=7 name=LanguageClientError file=" - ); - - let sign = Sign::new(7, "".to_owned(), Some(DiagnosticSeverity::Hint)); - assert_eq!( - get_command_add_sign(&sign, ""), - "sign place 75027 line=7 name=LanguageClientHint file=" - ); -} - -fn get_command_delete_sign(sign: &Sign, filename: &str) -> String { - format!("sign unplace {} file={}", sign.id, filename) -} - -#[test] -fn test_get_command_delete_sign() {} - -use diff; - -pub fn get_command_update_signs( - signs_prev: &[Sign], - signs: &[Sign], - filename: &str, -) -> (Vec, Vec) { - // Sign id might become different due to lines shifting. Use sign's existing sign id to - // track same sign. - let mut signs_next = vec![]; - - let mut cmds = vec![]; - for comp in diff::slice(signs_prev, signs) { - match comp { - diff::Result::Left(sign) => { - cmds.push(get_command_delete_sign(sign, filename)); - } - diff::Result::Right(sign) => { - cmds.push(get_command_add_sign(sign, filename)); - signs_next.push(sign.clone()); - } - diff::Result::Both(sign, _) => { - signs_next.push(sign.clone()); - } + // Update offset only if the edit's entire range is before it. + // Edits after the offset do not affect it. + // Edits covering the offset cause unpredictable effect. + if end <= offset { + offset += new_text.len(); + offset -= new_text.matches("\r\n").count(); // line ending is counted as one offset + offset -= std::cmp::min(offset, end - start); } } - (signs_next, cmds) -} + offset = std::cmp::min(offset, text.len()); -#[test] -fn test_get_command_update_signs() { - let signs_prev = vec![Sign::new( - 1, - "abcde".to_string(), - Some(DiagnosticSeverity::Error), - )]; - let signs = vec![Sign::new( - 3, - "abcde".to_string(), - Some(DiagnosticSeverity::Error), - )]; - let (signs_next, cmds) = get_command_update_signs(&signs_prev, &signs, "f"); - assert_eq!( - serde_json::to_string(&signs_next).unwrap(), - "[{\"id\":75000,\"line\":1,\"text\":\"abcde\",\"severity\":1}]" + let new_lines: Vec = text.lines().map(ToOwned::to_owned).collect(); + let new_position = offset_to_position(&new_lines, offset); + debug!( + "Position change after applying text edits: {:?} -> {:?}", + position, new_position ); - assert!(cmds.is_empty()); + + Ok((new_lines, new_position)) } pub trait Combine { @@ -369,39 +316,17 @@ pub fn expand_json_path(value: Value) -> Value { } } -#[test] -fn test_expand_json_path() { - assert_eq!( - expand_json_path(json!({ - "k": "v" - })), - json!({ - "k": "v" - }) - ); - assert_eq!( - expand_json_path(json!({ - "rust.rls": true - })), - json!({ - "rust": { - "rls": true - } - }) - ); -} - pub fn vim_cmd_args_to_value(args: &[String]) -> Result { let mut map = serde_json::map::Map::new(); for arg in args { let mut tokens: Vec<_> = arg.splitn(2, '=').collect(); tokens.reverse(); - let key = tokens.pop().ok_or_else(|| { - format_err!("Failed to parse command arguments! tokens: {:?}", tokens) - })?; - let value = tokens.pop().ok_or_else(|| { - format_err!("Failed to parse command arguments! tokens: {:?}", tokens) - })?; + let key = tokens + .pop() + .ok_or_else(|| anyhow!("Failed to parse command arguments! tokens: {:?}", tokens))?; + let value = tokens + .pop() + .ok_or_else(|| anyhow!("Failed to parse command arguments! tokens: {:?}", tokens))?; let value = Value::String(value.to_owned()); map.insert(key.to_owned(), value); } @@ -409,17 +334,6 @@ pub fn vim_cmd_args_to_value(args: &[String]) -> Result { Ok(Value::Object(map)) } -#[test] -fn test_vim_cmd_args_to_value() { - let cmdargs = ["rootPath=/tmp".to_owned()]; - assert_eq!( - vim_cmd_args_to_value(&cmdargs).unwrap(), - json!({ - "rootPath": "/tmp" - }) - ); -} - pub fn diff_value<'a>(v1: &'a Value, v2: &'a Value, path: &str) -> HashMap { let mut diffs = HashMap::new(); match (v1, v2) { @@ -427,9 +341,11 @@ pub fn diff_value<'a>(v1: &'a Value, v2: &'a Value, path: &str) -> HashMap if v1 != v2 { - diffs.insert(path.to_owned(), (v1.clone(), v2.clone())); - }, + | (&Value::Array(_), &Value::Array(_)) => { + if v1 != v2 { + diffs.insert(path.to_owned(), (v1.clone(), v2.clone())); + } + } (&Value::Object(ref map1), &Value::Object(ref map2)) => { let keys1: HashSet<&String> = map1.keys().collect(); let keys2: HashSet<&String> = map2.keys().collect(); @@ -453,25 +369,6 @@ pub fn diff_value<'a>(v1: &'a Value, v2: &'a Value, path: &str) -> HashMap (json!(1), json!(3)), - } - ); -} - pub trait Canonicalize { fn canonicalize(&self) -> String; } @@ -488,12 +385,12 @@ where // Trim UNC prefixes. // See https://github.com/rust-lang/rust/issues/42869 - path.trim_left_matches("\\\\?\\").into() + path.trim_start_matches("\\\\?\\").into() } } -pub fn get_default_initializationOptions(languageId: &str) -> Value { - match languageId { +pub fn get_default_initialization_options(language_id: &str) -> Value { + match language_id { "java" => json!({ "extendedClientCapabilities": { "classFileContentsSupport": true @@ -502,3 +399,350 @@ pub fn get_default_initializationOptions(languageId: &str) -> Value { _ => json!(Value::Null), } } + +/// Given a parameter label and its containing signature, return the part before the label, the +/// label itself, and the part after the label. +pub fn decode_parameter_label( + parameter_label: &lsp_types::ParameterLabel, + signature: &str, +) -> Result<(String, String, String)> { + match *parameter_label { + lsp_types::ParameterLabel::Simple(ref label) => { + let chunks: Vec<&str> = signature.split(label).collect(); + if chunks.len() != 2 { + return Err(anyhow!("Parameter is not part of signature")); + } + let begin = chunks[0].to_string(); + let label = label.to_string(); + let end = chunks[1].to_string(); + Ok((begin, label, end)) + } + lsp_types::ParameterLabel::LabelOffsets([start, finish]) => { + // Offsets are based on a UTF-16 string representation, inclusive start, + // exclusive finish. + let start = start.to_usize()?; + let finish = finish.to_usize()?; + let utf16: Vec = signature.encode_utf16().collect(); + let begin = utf16 + .get(..start) + .ok_or_else(|| anyhow!("Offset out of range"))?; + let begin = String::from_utf16(begin)?; + let label = utf16 + .get(start..finish) + .ok_or_else(|| anyhow!("Offset out of range"))?; + let label = String::from_utf16(label)?; + let end = utf16 + .get(finish..) + .ok_or_else(|| anyhow!("Offset out of range"))?; + let end = String::from_utf16(end)?; + Ok((begin, label, end)) + } + } +} + +/// Given a string, convert it into a string for vimscript +/// The string gets surrounded by single quotes. +/// +/// Existing single quotes will get escaped by inserting +/// another single quote in place. +/// +/// E.g. +/// abcdefg -> 'abcdefg' +/// abdcef'g -> 'abcdef''g' +pub fn convert_to_vim_str(s: &str) -> String { + let mut vs = String::with_capacity(s.len()); + + vs.push('\''); + + for i in s.chars() { + if i == '\'' { + vs.push(i); + } + + vs.push(i); + } + + vs.push('\''); + + vs +} + +/// Converts the kind of a `CodeAction` to a `&str`. +pub fn code_action_kind_as_str(action: &CodeAction) -> &str { + match action.kind.as_ref().map(String::as_ref) { + None | Some("") => "action", + Some(kind) => kind, + } +} + +#[cfg(test)] +mod test { + use super::*; + use lsp_types::Range; + + #[test] + fn test_escape_single_quote() { + assert_eq!(escape_single_quote("my' precious"), "my'' precious"); + } + + #[test] + fn test_position_to_offset() { + assert_eq!(position_to_offset(&[], &Position::new(0, 0)), 0); + + let lines: Vec = "\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(position_to_offset(&lines, &Position::new(0, 0)), 0); + assert_eq!(position_to_offset(&lines, &Position::new(0, 1)), 0); + assert_eq!(position_to_offset(&lines, &Position::new(1, 0)), 0); + assert_eq!(position_to_offset(&lines, &Position::new(1, 1)), 0); + + let lines: Vec = "a\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(position_to_offset(&lines, &Position::new(0, 0)), 0); + assert_eq!(position_to_offset(&lines, &Position::new(0, 1)), 1); + assert_eq!(position_to_offset(&lines, &Position::new(0, 2)), 1); + assert_eq!(position_to_offset(&lines, &Position::new(1, 0)), 0); + assert_eq!(position_to_offset(&lines, &Position::new(1, 1)), 1); + assert_eq!(position_to_offset(&lines, &Position::new(1, 2)), 1); + + let lines: Vec = "a\nbc\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(position_to_offset(&lines, &Position::new(1, 0)), 2); + assert_eq!(position_to_offset(&lines, &Position::new(1, 1)), 3); + assert_eq!(position_to_offset(&lines, &Position::new(1, 2)), 4); + assert_eq!(position_to_offset(&lines, &Position::new(1, 3)), 4); + } + + #[test] + fn test_offset_to_position() { + assert_eq!(offset_to_position(&[], 0), Position::new(0, 0)); + + let lines: Vec = "\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(offset_to_position(&lines, 0), Position::new(0, 0)); + assert_eq!(offset_to_position(&lines, 1), Position::new(0, 0)); + + let lines: Vec = "a\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(offset_to_position(&lines, 0), Position::new(0, 0)); + assert_eq!(offset_to_position(&lines, 1), Position::new(0, 1)); + assert_eq!(offset_to_position(&lines, 2), Position::new(0, 1)); + + let lines: Vec = "a\nbc\n".lines().map(ToOwned::to_owned).collect(); + assert_eq!(offset_to_position(&lines, 0), Position::new(0, 0)); + assert_eq!(offset_to_position(&lines, 1), Position::new(0, 1)); + assert_eq!(offset_to_position(&lines, 2), Position::new(1, 0)); + assert_eq!(offset_to_position(&lines, 3), Position::new(1, 1)); + assert_eq!(offset_to_position(&lines, 4), Position::new(1, 2)); + assert_eq!(offset_to_position(&lines, 5), Position::new(1, 2)); + } + #[test] + fn test_apply_text_edit() { + let lines: Vec = r#"fn main() { +0; +} +"# + .lines() + .map(|l| l.to_owned()) + .collect(); + + let expect: Vec = r#"fn main() { + 0; +} +"# + .lines() + .map(|l| l.to_owned()) + .collect(); + + let edit = TextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 3, + character: 0, + }, + }, + new_text: r#"fn main() { + 0; +} +"# + .to_owned(), + }; + + let position = Position::new(0, 0); + + // Ignore returned position since the edit's range covers current position and the new position + // is undefined in this case + let (result, _) = apply_text_edits(&lines, &[edit], &position).unwrap(); + assert_eq!(result, expect); + } + + #[test] + fn test_apply_text_edit_overlong_end() { + use lsp_types::*; + + let lines: Vec = r#"abc = 123"#.lines().map(|l| l.to_owned()).collect(); + + let expect: Vec = r#"nb = 123"#.lines().map(|l| l.to_owned()).collect(); + + let edit = TextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 99999999, + character: 0, + }, + }, + new_text: r#"nb = 123"#.to_owned(), + }; + + let position = Position::new(0, 1); + + let (result, _) = apply_text_edits(&lines, &[edit], &position).unwrap(); + assert_eq!(result, expect); + } + + #[test] + fn test_apply_text_edit_position() { + use lsp_types::*; + + let lines: Vec = "abc = 123".lines().map(|l| l.to_owned()).collect(); + + let expected_lines: Vec = "newline\nabcde = 123" + .lines() + .map(|l| l.to_owned()) + .collect(); + + let edits = [ + TextEdit { + range: Range { + start: Position { + line: 0, + character: 1, + }, + end: Position { + line: 0, + character: 3, + }, + }, + new_text: "bcde".to_owned(), + }, + TextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, + }, + new_text: "newline\n".to_owned(), + }, + ]; + + let position = Position::new(0, 4); + let expected_position = Position::new(1, 6); + + assert_eq!( + apply_text_edits(&lines, &edits, &position).unwrap(), + (expected_lines, expected_position) + ); + } + + #[test] + fn test_apply_text_edit_crlf() { + use lsp_types::*; + + let lines: Vec = "abc = 123".lines().map(|l| l.to_owned()).collect(); + + let expected_lines: Vec = "a\r\nbc = 123".lines().map(|l| l.to_owned()).collect(); + + let edit = TextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 1, + }, + }, + new_text: "a\r\n".to_owned(), + }; + + let position = Position::new(0, 2); + let expected_position = Position::new(1, 1); + + assert_eq!( + apply_text_edits(&lines, &[edit], &position).unwrap(), + (expected_lines, expected_position) + ); + } + + #[test] + fn test_expand_json_path() { + assert_eq!( + expand_json_path(json!({ + "k": "v" + })), + json!({ + "k": "v" + }) + ); + assert_eq!( + expand_json_path(json!({ + "rust.rls": true + })), + json!({ + "rust": { + "rls": true + } + }) + ); + } + + #[test] + fn test_vim_cmd_args_to_value() { + let cmdargs = ["rootPath=/tmp".to_owned()]; + assert_eq!( + vim_cmd_args_to_value(&cmdargs).unwrap(), + json!({ + "rootPath": "/tmp" + }) + ); + } + + #[test] + fn test_diff_value() { + use maplit::hashmap; + + assert_eq!(diff_value(&json!({}), &json!({}), "state",), hashmap!()); + assert_eq!( + diff_value( + &json!({ + "line": 1, + }), + &json!({ + "line": 3, + }), + "state" + ), + hashmap! { + "state.line".to_owned() => (json!(1), json!(3)), + } + ); + } + + #[test] + fn test_convert_to_vim_str() { + assert_eq!(convert_to_vim_str("abcdefg"), "'abcdefg'"); + assert_eq!(convert_to_vim_str("'abcdefg"), "'''abcdefg'"); + assert_eq!(convert_to_vim_str("'x'x'x'x'"), "'''x''x''x''x'''"); + assert_eq!(convert_to_vim_str("xyz'''ffff"), "'xyz''''''ffff'"); + assert_eq!(convert_to_vim_str("'''"), "''''''''"); + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/viewport.rs b/vim/bundle/LanguageClient-neovim/src/viewport.rs new file mode 100644 index 0000000..5470057 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/viewport.rs @@ -0,0 +1,53 @@ +use lsp_types::Range; +use serde::{Deserialize, Serialize}; + +/// Visible lines of editor. +/// +/// Inclusive at start, exclusive at end. Both start and end are 0-based. +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)] +pub struct Viewport { + pub start: u64, + pub end: u64, +} + +impl Viewport { + #[allow(dead_code)] + pub fn new(start: u64, end: u64) -> Self { + Self { start, end } + } + + fn contains(&self, line: u64) -> bool { + line >= self.start && line < self.end + } + + pub fn overlaps(&self, range: Range) -> bool { + self.contains(range.start.line) || self.contains(range.end.line) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_new() { + let viewport = Viewport::new(0, 7); + assert_eq!(viewport.start, 0); + assert_eq!(viewport.end, 7); + } + + #[test] + fn test_overlaps() { + use lsp_types::*; + + let viewport = Viewport::new(2, 7); + assert_eq!( + viewport.overlaps(Range::new(Position::new(0, 0), Position::new(1, 10))), + false + ); + assert_eq!( + viewport.overlaps(Range::new(Position::new(0, 0), Position::new(2, 0))), + true + ); + } +} diff --git a/vim/bundle/LanguageClient-neovim/src/vim.rs b/vim/bundle/LanguageClient-neovim/src/vim.rs index f47ee00..1c054a1 100644 --- a/vim/bundle/LanguageClient-neovim/src/vim.rs +++ b/vim/bundle/LanguageClient-neovim/src/vim.rs @@ -1,406 +1,255 @@ -use super::*; +use crate::rpcclient::RpcClient; +use crate::{ + sign::Sign, + types::{Bufnr, QuickfixEntry, VimExp, VirtualText}, + utils::Canonicalize, + viewport::Viewport, +}; +use anyhow::Result; +use jsonrpc_core::Value; +use log::*; +use lsp_types::Position; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::json; +use std::{path::Path, sync::Arc}; + +/// Try get value of an variable from RPC params. +pub fn try_get(key: &str, params: &Value) -> Result> { + let value = ¶ms[key]; + if value == &Value::Null { + Ok(None) + } else { + Ok(serde_json::from_value(value.clone())?) + } +} -impl State { - fn poll_call(&mut self) -> Result { - if let Some(msg) = self.pending_calls.pop_front() { - return Ok(msg); - } +#[derive(PartialEq)] +pub enum Mode { + Normal, + Insert, + Replace, + Visual, + VisualLine, + VisualBlock, + Command, + Select, + SelectLine, + SelectBlock, + Terminal, +} - loop { - let msg = self.rx.recv()?; - match msg { - Message::MethodCall(lang_id, method_call) => { - return Ok(Call::MethodCall(lang_id, method_call)); - } - Message::Notification(lang_id, notification) => { - return Ok(Call::Notification(lang_id, notification)); - } - Message::Output(output) => { - let mid = output.id().to_int()?; - self.pending_outputs.insert(mid, output); - } +impl From<&str> for Mode { + fn from(mode: &str) -> Self { + match mode { + "n" => Mode::Normal, + "i" => Mode::Insert, + "R" => Mode::Replace, + "v" => Mode::Visual, + "V" => Mode::VisualLine, + "" => Mode::VisualBlock, + "c" => Mode::Command, + "s" => Mode::Select, + "S" => Mode::SelectLine, + "" => Mode::SelectBlock, + "t" => Mode::Terminal, + m => { + error!("unknown mode {}, falling back to Mode::Normal", m); + Mode::Normal } } } +} - fn poll_output(&mut self, id: Id) -> Result { - if let Some(output) = self.pending_outputs.remove(&id) { - return Ok(output); - } +#[derive(Clone)] +pub struct Vim { + pub rpcclient: Arc, +} - loop { - let msg = self.rx.recv_timeout(self.wait_output_timeout)?; - match msg { - Message::MethodCall(lang_id, method_call) => self - .pending_calls - .push_back(Call::MethodCall(lang_id, method_call)), - Message::Notification(lang_id, notification) => self - .pending_calls - .push_back(Call::Notification(lang_id, notification)), - Message::Output(output) => { - let mid = output.id().to_int()?; - if mid == id { - return Ok(output); - } else { - self.pending_outputs.insert(mid, output); - } - } - } - } +impl Vim { + pub fn new(rpcclient: Arc) -> Self { + Self { rpcclient } } - pub fn loop_message(&mut self) -> Result<()> { - loop { - match self.poll_call()? { - Call::MethodCall(lang_id, method_call) => { - let result = self.handle_method_call(lang_id.as_deref(), &method_call); - if let Err(ref err) = result { - if err.downcast_ref::().is_none() { - error!( - "Error handling message: {}\n\nMessage: {}\n\nError: {:?}", - err, - serde_json::to_string(&method_call).unwrap_or_default(), - err - ); - } - } - let _ = self.output(lang_id.as_deref(), method_call.id, result); - } - Call::Notification(lang_id, notification) => { - let result = self.handle_notification(lang_id.as_deref(), ¬ification); - if let Err(ref err) = result { - if err.downcast_ref::().is_none() { - error!( - "Error handling message: {}\n\nMessage: {}\n\nError: {:?}", - err, - serde_json::to_string(¬ification).unwrap_or_default(), - err - ); - } - } - } - } + /// Fundamental functions. - if let Err(err) = self.handle_fs_events() { - warn!("{:?}", err); - } - } + pub fn get_mode(&self) -> Result { + let mode: String = self.rpcclient.call("mode", json!([]))?; + Ok(Mode::from(mode.as_str())) } - /// Send message to RPC server. - fn write(&mut self, languageId: Option<&str>, message: &str) -> Result<()> { - info!("=> {:?} {}", languageId, message); - if let Some(languageId) = languageId { - let writer = self - .writers - .get_mut(languageId) - .ok_or(LCError::NoLanguageServer { - languageId: languageId.to_owned(), - })?; - write!( - writer, - "Content-Length: {}\r\n\r\n{}", - message.len(), - message - )?; - writer.flush()?; - } else { - println!("Content-Length: {}\n\n{}", message.len(), message); - } - - Ok(()) + pub fn command(&self, cmds: impl Serialize) -> Result<()> { + self.rpcclient.notify("s:command", &cmds) } - /// RPC method call. - pub fn call(&mut self, languageId: Option<&str>, method: &str, params: P) -> Result + pub fn eval(&self, exp: E) -> Result where - P: Serialize, - V: DeserializeOwned, + E: VimExp, + T: DeserializeOwned, { - self.id += 1; - let id = self.id; - - let method_call = rpc::MethodCall { - jsonrpc: Some(rpc::Version::V2), - id: rpc::Id::Num(id), - method: method.into(), - params: params.to_params()?, - }; - - let message = serde_json::to_string(&method_call)?; - self.write(languageId, &message)?; - - match self.poll_output(id)? { - rpc::Output::Success(success) => Ok(serde_json::from_value(success.result)?), - rpc::Output::Failure(failure) => Err(format_err!("{}", failure.error.message)), - } + self.rpcclient.call("eval", &exp.to_exp()) } - /// RPC notification. - pub fn notify

(&mut self, languageId: Option<&str>, method: &str, params: P) -> Result<()> - where - P: Serialize, - { - let notification = rpc::Notification { - jsonrpc: Some(rpc::Version::V2), - method: method.to_owned(), - params: params.to_params()?, - }; + /// Function wrappers. - let message = serde_json::to_string(¬ification)?; - self.write(languageId, &message)?; + pub fn getbufvar(&self, bufname: &str, var: &str) -> Result { + self.rpcclient.call("getbufvar", json!([bufname, var])) + } - Ok(()) + pub fn get_filename(&self, params: &Value) -> Result { + let key = "filename"; + let expr = "LSP#filename()"; + + let filename: String = try_get(key, params)?.map_or_else(|| self.eval(expr), Ok)?; + Ok(filename.canonicalize()) } - /// Write an RPC call output. - fn output( - &mut self, - languageId: Option<&str>, - id: rpc::Id, - result: Result, - ) -> Result<()> { - let response = match result { - Ok(ok) => rpc::Output::Success(rpc::Success { - jsonrpc: Some(rpc::Version::V2), - id, - result: ok, - }), - Err(err) => rpc::Output::Failure(rpc::Failure { - jsonrpc: Some(rpc::Version::V2), - id, - error: err.to_rpc_error(), - }), - }; - - let message = serde_json::to_string(&response)?; - self.write(languageId, &message)?; - Ok(()) + pub fn get_language_id(&self, filename: &str, params: &Value) -> Result { + let key = "languageId"; + let expr = "&filetype"; + + try_get(key, params)?.map_or_else(|| self.getbufvar(filename, expr), Ok) } - /////// Vim wrappers /////// + pub fn get_bufnr(&self, filename: &str, params: &Value) -> Result { + let key = "bufnr"; - #[allow(needless_pass_by_value)] - pub fn eval(&mut self, exp: E) -> Result - where - E: VimExp, - T: DeserializeOwned, - { - let result = self.call(None, "eval", exp.to_exp())?; - Ok(serde_json::from_value(result)?) + try_get(key, params)?.map_or_else(|| self.eval(format!("bufnr('{}')", filename)), Ok) } - pub fn command(&mut self, cmds: P) -> Result<()> { - if self.call::<_, u8>(None, "execute", &cmds)? != 0 { - bail!("Failed to execute command: {:?}", cmds); - } - Ok(()) + pub fn get_viewport(&self, params: &Value) -> Result { + let key = "viewport"; + let expr = "LSP#viewport()"; + + try_get(key, params)?.map_or_else(|| self.eval(expr), Ok) } - ////// Vim builtin function wrappers /////// + pub fn get_position(&self, params: &Value) -> Result { + let key = "position"; + let expr = "LSP#position()"; - pub fn echo(&mut self, message: S) -> Result<()> - where - S: AsRef + Serialize, - { - self.notify(None, "s:Echo", message) + try_get(key, params)?.map_or_else(|| self.eval(expr), Ok) + } + + pub fn get_current_word(&self, params: &Value) -> Result { + let key = "cword"; + let expr = "expand('')"; + + try_get(key, params)?.map_or_else(|| self.eval(expr), Ok) + } + + pub fn get_goto_cmd(&self, params: &Value) -> Result> { + let key = "gotoCmd"; + + try_get(key, params) + } + + pub fn get_tab_size(&self) -> Result { + let expr = "shiftwidth()"; + + self.eval(expr) + } + + pub fn get_insert_spaces(&self, filename: &str) -> Result { + let insert_spaces: i8 = self.getbufvar(filename, "&expandtab")?; + Ok(insert_spaces == 1) + } + + pub fn get_text(&self, bufname: &str) -> Result> { + self.rpcclient.call("LSP#text", json!([bufname])) + } + + pub fn get_handle(&self, params: &Value) -> Result { + let key = "handle"; + + try_get(key, params)?.map_or_else(|| Ok(true), Ok) + } + + pub fn echo(&self, message: impl AsRef) -> Result<()> { + self.rpcclient.notify("s:Echo", message.as_ref()) } - pub fn echo_ellipsis>(&mut self, message: S) -> Result<()> { + pub fn echo_ellipsis(&self, message: impl AsRef) -> Result<()> { let message = message.as_ref().lines().collect::>().join(" "); - self.notify(None, "s:EchoEllipsis", message) + self.rpcclient.notify("s:EchoEllipsis", message) } - pub fn echomsg_ellipsis>(&mut self, message: S) -> Result<()> { + pub fn echomsg_ellipsis(&self, message: impl AsRef) -> Result<()> { let message = message.as_ref().lines().collect::>().join(" "); - self.notify(None, "s:EchomsgEllipsis", message) + self.rpcclient.notify("s:EchomsgEllipsis", message) } - pub fn echomsg(&mut self, message: S) -> Result<()> - where - S: AsRef + Serialize, - { - self.notify(None, "s:Echomsg", message) + pub fn echomsg(&self, message: impl AsRef) -> Result<()> { + self.rpcclient.notify("s:Echomsg", message.as_ref()) } - pub fn echoerr(&mut self, message: S) -> Result<()> - where - S: AsRef + Serialize, - { - self.notify(None, "s:Echoerr", message) + pub fn echoerr(&self, message: impl AsRef) -> Result<()> { + self.rpcclient.notify("s:Echoerr", message.as_ref()) } - pub fn echowarn(&mut self, message: S) -> Result<()> - where - S: AsRef + Serialize, - { - self.notify(None, "s:Echowarn", message) + pub fn echowarn(&self, message: impl AsRef) -> Result<()> { + self.rpcclient.notify("s:Echowarn", message.as_ref()) } - pub fn cursor(&mut self, lnum: u64, col: u64) -> Result<()> { - self.notify(None, "cursor", json!([lnum, col])) + pub fn cursor(&self, lnum: u64, col: u64) -> Result<()> { + self.rpcclient.notify("cursor", json!([lnum, col])) } - pub fn setline(&mut self, lnum: u64, text: &[String]) -> Result<()> { - if self.call::<_, u8>(None, "setline", json!([lnum, text]))? != 0 { - bail!("Failed to set buffer content!"); - } - Ok(()) + #[allow(dead_code)] + pub fn setline(&self, lnum: u64, text: &[String]) -> Result<()> { + self.rpcclient.notify("setline", json!([lnum, text])) } - pub fn edit>(&mut self, goto_cmd: &Option, path: P) -> Result<()> { + pub fn edit(&self, goto_cmd: &Option, path: impl AsRef) -> Result<()> { let path = path.as_ref().to_string_lossy(); - let goto = goto_cmd.as_deref().unwrap_or("edit"); - if self.call::<_, u8>(None, "s:Edit", json!([goto, path]))? != 0 { - bail!("Failed to edit file: {}", path); - } - - if path.starts_with("jdt://") { - self.command("setlocal buftype=nofile filetype=java noswapfile")?; - - let result = self.java_classFileContents(&json!({ - VimVar::LanguageId.to_key(): "java", - "uri": path, - }))?; - let content = match result { - Value::String(s) => s, - _ => bail!("Unexpected type: {:?}", result), - }; - let lines: Vec = content - .lines() - .map(std::string::ToString::to_string) - .collect(); - self.setline(1, &lines)?; - } + self.rpcclient.notify("s:Edit", json!([goto, path]))?; Ok(()) } - pub fn setqflist(&mut self, list: &[QuickfixEntry]) -> Result<()> { - if self.call::<_, u8>(None, "setqflist", json!([list, "r"]))? != 0 { - bail!("Failed to set quickfix list!"); - } + pub fn setqflist(&self, list: &[QuickfixEntry], action: &str, title: &str) -> Result<()> { + info!("Begin setqflist"); + let parms = json!([list, action]); + self.rpcclient.notify("setqflist", parms)?; + let parms = json!([[], "a", { "title": title }]); + self.rpcclient.notify("setqflist", parms)?; Ok(()) } - pub fn setloclist(&mut self, list: &[QuickfixEntry]) -> Result<()> { - if self.call::<_, u8>(None, "setloclist", json!([0, list, "r"]))? != 0 { - bail!("Failed to set location list!"); - } + pub fn setloclist(&self, list: &[QuickfixEntry], action: &str, title: &str) -> Result<()> { + let parms = json!([0, list, action]); + self.rpcclient.notify("setloclist", parms)?; + let parms = json!([0, [], "a", { "title": title }]); + self.rpcclient.notify("setloclist", parms)?; Ok(()) } - pub fn get(&self, f: F) -> Result - where - F: FnOnce(&State) -> Result, - { - f(self) + pub fn create_namespace(&self, name: &str) -> Result { + self.rpcclient.call("nvim_create_namespace", [name]) } - pub fn update(&mut self, f: F) -> Result - where - F: FnOnce(&mut State) -> Result, - { - use log::Level; - - let before = if log_enabled!(Level::Debug) { - let s = serde_json::to_string(self)?; - serde_json::from_str(&s)? - } else { - Value::default() - }; - let result = f(self); - let after = if log_enabled!(Level::Debug) { - let s = serde_json::to_string(self)?; - serde_json::from_str(&s)? - } else { - Value::default() - }; - for (k, (v1, v2)) in diff_value(&before, &after, "state") { - debug!("{}: {} ==> {}", k, v1, v2); - } - result + pub fn set_virtual_texts( + &self, + buf_id: i64, + ns_id: i64, + line_start: u64, + line_end: u64, + virtual_texts: &[VirtualText], + ) -> Result { + self.rpcclient.call( + "s:set_virtual_texts", + json!([buf_id, ns_id, line_start, line_end, virtual_texts]), + ) } -} -#[derive(Debug, Serialize, Deserialize)] -#[serde(untagged)] -enum RawMessage { - Notification(rpc::Notification), - MethodCall(rpc::MethodCall), - Output(rpc::Output), -} - -pub fn loop_reader( - input: T, - languageId: &Option, - tx: &Sender, -) -> Result<()> { - // Count how many consequent empty lines. - let mut count_empty_lines = 0; - - let mut input = input; - let mut content_length = 0; - loop { - let mut message = String::new(); - let mut line = String::new(); - if languageId.is_some() { - input.read_line(&mut line)?; - let line = line.trim(); - if line.is_empty() { - count_empty_lines += 1; - if count_empty_lines > 5 { - bail!("Unable to read from language server"); - } - - let mut buf = vec![0; content_length]; - input.read_exact(buf.as_mut_slice())?; - message = String::from_utf8(buf)?; - } else { - count_empty_lines = 0; - if !line.starts_with("Content-Length") { - continue; - } - - let tokens: Vec<&str> = line.splitn(2, ':').collect(); - let len = tokens - .get(1) - .ok_or_else(|| format_err!("Failed to get length! tokens: {:?}", tokens))? - .trim(); - content_length = usize::from_str(len)?; - } - } else if input.read_line(&mut message)? == 0 { - break; - } - - let message = message.trim(); - if message.is_empty() { - continue; - } - info!("<= {:?} {}", languageId, message); - // FIXME: Remove extra `meta` property from javascript-typescript-langserver. - let s = message.replace(r#","meta":{}"#, ""); - let message = serde_json::from_str(&s); - if let Err(ref err) = message { - error!( - "Failed to deserialize output: {}\n\n Message: {}\n\nError: {:?}", - err, s, err - ); - continue; - } - // TODO: cleanup. - let message = message.unwrap(); - let message = match message { - RawMessage::MethodCall(method_call) => { - Message::MethodCall(languageId.clone(), method_call) - } - RawMessage::Notification(notification) => { - Message::Notification(languageId.clone(), notification) - } - RawMessage::Output(output) => Message::Output(output), - }; - tx.send(message)?; + pub fn set_signs( + &self, + filename: &str, + signs_to_add: &[Sign], + signs_to_delete: &[Sign], + ) -> Result { + self.rpcclient.call( + "s:set_signs", + json!([filename, signs_to_add, signs_to_delete]), + ) } - - Ok(()) } diff --git a/vim/bundle/LanguageClient-neovim/src/vimext.rs b/vim/bundle/LanguageClient-neovim/src/vimext.rs new file mode 100644 index 0000000..1525b7b --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/src/vimext.rs @@ -0,0 +1,20 @@ +use crate::language_client::LanguageClient; +use crate::types::LCNamespace; +use anyhow::Result; + +impl LanguageClient { + pub fn get_or_create_namespace(&self, ns: &LCNamespace) -> Result { + let ns_name = ns.name(); + + if let Some(namespace_id) = self.get(|state| state.namespace_ids.get(&ns_name).cloned())? { + Ok(namespace_id) + } else { + let namespace_id = self.vim()?.create_namespace(&ns_name)?; + self.update(|state| { + state.namespace_ids.insert(ns_name, namespace_id); + Ok(()) + })?; + Ok(namespace_id) + } + } +} diff --git a/vim/bundle/LanguageClient-neovim/tests/LanguageClient_test.py b/vim/bundle/LanguageClient-neovim/tests/LanguageClient_test.py index fb2b58c..cba0c50 100644 --- a/vim/bundle/LanguageClient-neovim/tests/LanguageClient_test.py +++ b/vim/bundle/LanguageClient-neovim/tests/LanguageClient_test.py @@ -19,10 +19,10 @@ def join_path(path: str) -> str: return os.path.join(project_root, path) -PATH_INDEXJS = join_path("data/sample-js/src/index.js") -PATH_LIBSJS = join_path("data/sample-js/src/libs.js") +PATH_MAIN_RS = join_path("data/sample-rs/src/main.rs") +PATH_LIBS_RS = join_path("data/sample-rs/src/libs.rs") PATH_CODEACTION = join_path("data/sample-ts/src/codeAction.ts") -print(PATH_INDEXJS) +print(PATH_MAIN_RS) def assertRetry(predicate, retry_max=100): @@ -38,72 +38,76 @@ def assertRetry(predicate, retry_max=100): assert predicate() +def getLanguageClientBuffers(nvim): + return [b for b in nvim.buffers if b.name.endswith("__LanguageClient__")] + + @pytest.fixture(scope="module") def nvim() -> neovim.Nvim: nvim = neovim.attach("socket", path=NVIM_LISTEN_ADDRESS) time.sleep(1) - nvim.command("edit! {}".format(PATH_INDEXJS)) - time.sleep(3) return nvim -def test_fixture(nvim): - pass +@pytest.fixture(autouse=True) +def setup(nvim): + nvim.command("%bdelete!") -def test_textDocument_hover(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) - time.sleep(1) - nvim.funcs.cursor(13, 19) - nvim.funcs.LanguageClient_textDocument_hover() - time.sleep(1) - b = next(b for b in nvim.buffers if b.name.endswith('__LanguageClient__')) - expect = "function greet(): number" +def test_textDocument_definition(nvim): + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(3) + nvim.funcs.cursor(3, 22) + nvim.funcs.LanguageClient_textDocument_definition() + time.sleep(3) - assert expect in b + assert nvim.current.window.cursor == [8, 3] -def test_textDocument_definition(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) +def test_textDocument_hover(nvim): + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) - nvim.funcs.cursor(13, 19) - nvim.funcs.LanguageClient_textDocument_definition() + nvim.funcs.cursor(3, 22) + nvim.funcs.LanguageClient_textDocument_hover() time.sleep(1) + buf = getLanguageClientBuffers(nvim)[0] + expect = "fn greet() -> i32" - assert nvim.current.window.cursor == [7, 9] + assert expect in "\n".join(buf) def test_textDocument_rename(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) expect = [line.replace("greet", "hello") for line in nvim.current.buffer] - nvim.funcs.cursor(13, 19) + nvim.funcs.cursor(3, 22) nvim.funcs.LanguageClient_textDocument_rename({"newName": "hello"}) time.sleep(1) assert nvim.current.buffer[:] == expect - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) def test_textDocument_rename_multiple_oneline(nvim): - nvim.command("edit! {}".format(PATH_LIBSJS)) + nvim.command("edit! {}".format(PATH_LIBS_RS)) time.sleep(1) - nvim.funcs.cursor(7, 11) - expect = [line.replace("a", "abc") for line in nvim.current.buffer] - nvim.funcs.LanguageClient_textDocument_rename({"newName": "abc"}) + expect = [line.replace("a", "x") for line in nvim.current.buffer] + nvim.funcs.cursor(4, 13) + # TODO: Test case where new variable length is different. + nvim.funcs.LanguageClient_textDocument_rename({"newName": "x"}) time.sleep(1) assert nvim.current.buffer[:] == expect nvim.command("bd!") - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) def test_textDocument_rename_multiple_files(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) - nvim.funcs.cursor(20, 5) + nvim.funcs.cursor(17, 5) expect = [line.replace("yo", "hello") for line in nvim.current.buffer] nvim.funcs.LanguageClient_textDocument_rename({"newName": "hello"}) time.sleep(1) @@ -112,11 +116,11 @@ def test_textDocument_rename_multiple_files(nvim): nvim.command("bd!") nvim.command("bd!") - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) def test_textDocument_documentSymbol(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) nvim.funcs.cursor(1, 1) nvim.funcs.LanguageClient_textDocument_documentSymbol() @@ -124,13 +128,13 @@ def test_textDocument_documentSymbol(nvim): assert nvim.funcs.getloclist(0) - nvim.command("4lnext") + nvim.command("3lnext") - assert nvim.current.window.cursor == [19, 0] + assert nvim.current.window.cursor == [8, 0] def test_workspace_symbol(nvim): - nvim.command("edit! {}".format(PATH_LIBSJS)) + nvim.command("edit! {}".format(PATH_LIBS_RS)) time.sleep(1) nvim.funcs.cursor(1, 1) nvim.funcs.LanguageClient_workspace_symbol() @@ -138,43 +142,44 @@ def test_workspace_symbol(nvim): assert nvim.funcs.getloclist(0) - nvim.command("5lnext") + nvim.command("1lnext") - assert nvim.current.window.cursor == [7, 0] + assert nvim.current.window.cursor == [8, 0] def test_textDocument_references(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) - nvim.funcs.cursor(7, 12) + nvim.funcs.cursor(8, 6) nvim.funcs.LanguageClient_textDocument_references() time.sleep(1) - expect = ["function greet() {", - """console.log(greet());"""] + expect = ["fn greet() -> i32 {", """println!("{}", greet());"""] - assert [location["text"] for location in - nvim.funcs.getloclist(0)] == expect + assert [location["text"] + for location in nvim.funcs.getloclist(0)] == expect nvim.command("lnext") - assert nvim.current.window.cursor == [13, 16] + assert nvim.current.window.cursor == [3, 19] def test_textDocument_references_modified_buffer(nvim): - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) time.sleep(1) - nvim.funcs.cursor(7, 10) + nvim.funcs.cursor(8, 6) nvim.input("iabc") time.sleep(1) nvim.funcs.LanguageClient_textDocument_references() time.sleep(1) - assert nvim.current.window.cursor == [7, 9] + assert nvim.current.window.cursor == [8, 3] - nvim.command("edit! {}".format(PATH_INDEXJS)) + nvim.command("edit! {}".format(PATH_MAIN_RS)) def test_languageClient_registerServerCommands(nvim): + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) nvim.command('let g:responses = []') nvim.command("call LanguageClient_registerServerCommands(" "{'bash': ['bash']}, g:responses)") @@ -183,6 +188,8 @@ def test_languageClient_registerServerCommands(nvim): def test_languageClient_registerHandlers(nvim): + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) nvim.command('let g:responses = []') nvim.command("call LanguageClient_registerHandlers(" "{'window/progress': 'HandleWindowProgress'}, g:responses)") @@ -208,3 +215,120 @@ def test_languageClient_registerHandlers(nvim): # if b.name.startswith('term://')), None) is None) # assertRetry(lambda: len(nvim.funcs.getqflist()) == 0) + + +def _open_float_window(nvim): + nvim.funcs.cursor(3, 22) + pos = nvim.funcs.getpos('.') + nvim.funcs.LanguageClient_textDocument_hover() + time.sleep(1) + return pos + + +def test_textDocument_hover_float_window_closed_on_cursor_moved(nvim): + if not nvim.funcs.exists("*nvim_open_win"): + pytest.skip("Neovim 0.3.0 or earlier does not support floating window") + + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) + + buf = nvim.current.buffer + + pos = _open_float_window(nvim) + + float_buf = getLanguageClientBuffers(nvim)[0] + + # Check if float window is open + float_winnr = nvim.funcs.bufwinnr(float_buf.number) + assert float_winnr > 0 + + # Check if cursor is not moved + assert buf.number == nvim.current.buffer.number + assert pos == nvim.funcs.getpos(".") + + # Move cursor to left + nvim.funcs.cursor(13, 17) + + # Check float window buffer was closed by CursorMoved + assert len(getLanguageClientBuffers(nvim)) == 0 + + +def test_textDocument_hover_float_window_closed_on_entering_window(nvim): + if not nvim.funcs.exists("*nvim_open_win"): + pytest.skip("Neovim 0.3.0 or earlier does not support floating window") + + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) + + win_id = nvim.funcs.win_getid() + nvim.command("split") + try: + assert win_id != nvim.funcs.win_getid() + + _open_float_window(nvim) + assert win_id != nvim.funcs.win_getid() + + # Move to another window + nvim.funcs.win_gotoid(win_id) + assert win_id == nvim.funcs.win_getid() + + # Check float window buffer was closed by BufEnter + assert len(getLanguageClientBuffers(nvim)) == 0 + finally: + nvim.command("close!") + + +def test_textDocument_hover_float_window_closed_on_switching_to_buffer(nvim): + if not nvim.funcs.exists("*nvim_open_win"): + pytest.skip("Neovim 0.3.0 or earlier does not support floating window") + + # Create a new buffer + nvim.command("enew!") + + another_bufnr = nvim.current.buffer.number + + try: + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) + + source_bufnr = nvim.current.buffer.number + + _open_float_window(nvim) + + float_buf = getLanguageClientBuffers(nvim)[0] + float_winnr = nvim.funcs.bufwinnr(float_buf.number) + assert float_winnr > 0 + + assert nvim.current.buffer.number == source_bufnr + + # Move to another buffer within the same window + nvim.command("buffer {}".format(another_bufnr)) + assert nvim.current.buffer.number == another_bufnr + + # Check float window buffer was closed by BufEnter + assert len(getLanguageClientBuffers(nvim)) == 0 + finally: + nvim.command("bdelete! {}".format(another_bufnr)) + + +def test_textDocument_hover_float_window_move_cursor_into_window(nvim): + if not nvim.funcs.exists("*nvim_open_win"): + pytest.skip("Neovim 0.3.0 or earlier does not support floating window") + + nvim.command("edit! {}".format(PATH_MAIN_RS)) + time.sleep(1) + + prev_bufnr = nvim.current.buffer.number + + _open_float_window(nvim) + + # Moves cursor into floating window + nvim.funcs.LanguageClient_textDocument_hover() + assert nvim.current.buffer.name.endswith("__LanguageClient__") + + # Close the window + nvim.command('close') + assert nvim.current.buffer.number == prev_bufnr + + # Check float window buffer was closed by :close in the window + assert len(getLanguageClientBuffers(nvim)) == 0 diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/.gitignore b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/.gitignore index 9bf3746..16fcdc4 100644 --- a/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/.gitignore +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/.gitignore @@ -1 +1,2 @@ compile_commands.json +.ccls-cache diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/914-completion-header-cursor.cpp b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/914-completion-header-cursor.cpp new file mode 100644 index 0000000..f428bad --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/914-completion-header-cursor.cpp @@ -0,0 +1,5 @@ +#include +auto main() -> int +{ + auto foo = std::make +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/completion.c b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/completion.c new file mode 100644 index 0000000..f01e0c2 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/completion.c @@ -0,0 +1,8 @@ +struct Point { + int x; +}; + +int main() { + struct Point point; + point. +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/simple.c b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/simple.c new file mode 100644 index 0000000..c414c80 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-cpp/simple.c @@ -0,0 +1,8 @@ +int main() { + int rc = 0; + + if (rc == 0) { + } + + return rc; +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-css/sample.css b/vim/bundle/LanguageClient-neovim/tests/data/sample-css/sample.css new file mode 100644 index 0000000..de5bb41 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-css/sample.css @@ -0,0 +1,3 @@ +.highlight { + color: brown, +} \ No newline at end of file diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-go/simple.go b/vim/bundle/LanguageClient-neovim/tests/data/sample-go/simple.go index 103022d..078ddff 100644 --- a/vim/bundle/LanguageClient-neovim/tests/data/sample-go/simple.go +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-go/simple.go @@ -1 +1,7 @@ -package main; var a int +package main + +import "fmt" + +func main() { + fmt.Println("Hello, 世界") +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.gitignore b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.gitignore new file mode 100644 index 0000000..20b266f --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.gitignore @@ -0,0 +1,33 @@ +# Created by http://www.gitignore.io + +### Scala ### +*.class +*.log + +# sbt specific +.cache/ +.history/ +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.scala_dependencies +.worksheet + +# osx +.DS_Store + +# tag +tags + +# log +logs/ + +# Metals +.metals +.bloop diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.sbtopts b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.sbtopts new file mode 100644 index 0000000..5965b18 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.sbtopts @@ -0,0 +1,7 @@ +-J-Dfile.encoding=UTF-8 +-J-XX:+CMSClassUnloadingEnabled +-J-XX:ReservedCodeCacheSize=128M +-J-XX:MetaspaceSize=256M +-J-XX:MaxMetaspaceSize=512M +-J-Xms2G +-J-Xmx2G diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.scalafmt.conf b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.scalafmt.conf new file mode 100644 index 0000000..e464070 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/.scalafmt.conf @@ -0,0 +1,13 @@ +version=2.0.0-RC5 +docstrings = "ScalaDoc" +maxColumn = 60 +continuationIndent.callSite = 2 +continuationIndent.defnSite = 4 +align = none +align.openParenCallSite = true +align.openParenDefnSite = true +newlines.alwaysBeforeTopLevelStatements = true +newlines.alwaysBeforeElseAfterCurlyIf = false +binPack.literalArgumentLists = true +includeCurlyBraceInSelectChains = true +optIn.breakChainOnFirstMethodDot = false diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/build.sbt b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/build.sbt new file mode 100644 index 0000000..b118cc2 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/build.sbt @@ -0,0 +1,28 @@ +name := "sample-scala" + +version := "0.0.1" + +organization := "com.github" + +libraryDependencies ++= { + + val scalaTestVersion = "3.0.5" + + Seq( + // Logger + "ch.qos.logback" % "logback-classic" % "1.2.3", + "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2", + // Test + "org.scalactic" %% "scalactic" % scalaTestVersion, + "org.scalatest" %% "scalatest" % scalaTestVersion % "test", + "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" + ) +} + +scalafmtConfig in ThisBuild := file(".scalafmt.conf") +scalafmtConfig := file(".scalafmt.conf") +scalafmtConfig in Compile := file(".scalafmt.conf") + +scalafmtOnCompile in ThisBuild := true +scalafmtOnCompile := true +scalafmtOnCompile in Compile := true diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/build.properties b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/build.properties new file mode 100644 index 0000000..c0bab04 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/plugins.sbt b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/plugins.sbt new file mode 100644 index 0000000..bd38a91 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/project/plugins.sbt @@ -0,0 +1,14 @@ +/* + * neo-sbt-scalafmt + */ +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") + +/* + * sbt-scoverage + */ +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") + +/* + * coursier + */ +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.1.0-M13-4") diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/scala.sbt b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/scala.sbt new file mode 100644 index 0000000..f01f995 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/scala.sbt @@ -0,0 +1,11 @@ +scalaVersion := "2.12.8" + +scalacOptions ++= Seq( + "-deprecation", + "-feature", + "-unchecked", + "-Xlint", + "-Ywarn-unused", + "-Ywarn-unused-import", + "-Ywarn-value-discard" +) diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/resources/logback.xml b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/resources/logback.xml new file mode 100644 index 0000000..6cb5fa2 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/resources/logback.xml @@ -0,0 +1,41 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] [%-5level] %logger{36} - %msg%n + + + + + + + ./logs/app.log + + + ./logs/app.%i.log + 1 + 3 + + + + 100KB + + + + %d{HH:mm:ss.SSS} [%thread] [%-5level] %logger{36} - %msg%n + + + + + + + + + + + + + + + diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/scala/com/github/autozimu/Main.scala b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/scala/com/github/autozimu/Main.scala new file mode 100644 index 0000000..de1a987 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/main/scala/com/github/autozimu/Main.scala @@ -0,0 +1,12 @@ +package com.github.autozimu + +import com.typesafe.scalalogging.LazyLogging + +object Main extends App with LazyLogging { + + def helloWorld(name: String): String = { + "Hello " + name + "!" + } + + logger.info(helloWorld("nabezokodaikokn")) +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/test/scala/com/github/autozimu/MainSpec.scala b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/test/scala/com/github/autozimu/MainSpec.scala new file mode 100644 index 0000000..6122745 --- /dev/null +++ b/vim/bundle/LanguageClient-neovim/tests/data/sample-scala/src/test/scala/com/github/autozimu/MainSpec.scala @@ -0,0 +1,9 @@ +package com.github.autozimu + +import org.scalatest.FunSuite + +class MainSpec extends FunSuite { + test("helloWorld") { + assert(Main.helloWorld("nabezokodaikokn") == "Hello nabezokodaikokn!") + } +} diff --git a/vim/bundle/LanguageClient-neovim/tests/data/vimrc b/vim/bundle/LanguageClient-neovim/tests/data/vimrc index 79a1f1c..9be167e 100644 --- a/vim/bundle/LanguageClient-neovim/tests/data/vimrc +++ b/vim/bundle/LanguageClient-neovim/tests/data/vimrc @@ -18,12 +18,13 @@ autocmd BufRead *.rs setlocal filetype=rust let g:LanguageClient_devel = 1 let g:LanguageClient_loggingLevel = 'INFO' +let g:LanguageClient_useVirtualText = 'CodeLens' let g:LanguageClient_loggingFile = expand('~/.local/share/nvim/LanguageClient.log') let g:LanguageClient_serverStderr = expand('~/.local/share/nvim/LanguageServer.log') let g:LanguageClient_serverCommands = { \ 'javascript': ['javascript-typescript-stdio'], \ 'typescript': ['javascript-typescript-stdio'], - \ 'rust': ['rustup', 'run', 'stable', 'rls'], + \ 'rust': ['rust-analyzer'], \ } let g:LanguageClient_selectionUI = 'location-list' set formatexpr=LanguageClient#textDocument_rangeFormatting_sync() diff --git a/vim/bundle/LanguageClient-neovim/tests/test.sh b/vim/bundle/LanguageClient-neovim/tests/test.sh index d790355..82d4117 100755 --- a/vim/bundle/LanguageClient-neovim/tests/test.sh +++ b/vim/bundle/LanguageClient-neovim/tests/test.sh @@ -15,12 +15,12 @@ rm -f /tmp/nvim-LanguageClient-IntegrationTest if [[ "$TMUX" ]]; then tmux split-window 'NVIM_LISTEN_ADDRESS=/tmp/nvim-LanguageClient-IntegrationTest nvim -n -u tests/data/vimrc' else - NVIM_LISTEN_ADDRESS=/tmp/nvim-LanguageClient-IntegrationTest nvim -n -u tests/data/vimrc --headless & + NVIM_LISTEN_ADDRESS=/tmp/nvim-LanguageClient-IntegrationTest nvim -n -u tests/data/vimrc --headless 2>/dev/null & fi PID=$! sleep 1s -py.test-3 --capture=no --exitfirst -v $@ +$(command -v pytest-3 || echo pytest) --capture=no --exitfirst -v $@ ret=$? if [[ $ret != 0 ]]; then diff --git a/vim/bundle/LanguageClient-neovim/tests/wrapper-server.sh b/vim/bundle/LanguageClient-neovim/tests/wrapper-server.sh index 0ce86e4..bfe9fd8 100755 --- a/vim/bundle/LanguageClient-neovim/tests/wrapper-server.sh +++ b/vim/bundle/LanguageClient-neovim/tests/wrapper-server.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/bash # Tee server stdio into log file. if test -z "$TMP"; then