Repository: autozimu/LanguageClient-neovim (language: rust)

Issue:
`colorProvider: null` does not conform to specification
- Did you upgrade to latest plugin version?

Yes.

- Did you upgrade to/compile latest binary? Run shell command
  `bin/languageclient --version` to get its version number.

Yes.

- (Neovim users only) Did you check output of `:checkhealth LanguageClient`?

```
health#LanguageClient#check
========================================================================
  - OK: binary found: /Users/USER/.local/share/nvim/plugged/LanguageClient-neovim/bin/languageclient
  - OK: languageclient 0.1.155 1ffab7ef19e2acae6f32c6f3a27581429d28e47b
  - OK: Floating window is supported and will be used for hover
```
                                                                        
- Did you check [troubleshooting]?

Yes. I get the same error with a `min-vimrc.vim`. I have shared the configuration below.

[troubleshooting]: https://github.com/autozimu/LanguageClient-neovim/blob/next/INSTALL.md#6-troubleshooting

## Describe the bug
A clear and concise description of what the bug is.

Using Julia and neovim, with the following configuration:

The `LanguageClient-neovim` plugin sends the following message to the Julia LanguageServer:

```
14:06:19 INFO writer-Some("julia") src/rpcclient.rs:215 => Some("julia") {"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{"textDocument":{"colorProvider":null,"completion":{"completionItem":{"snippetSupport":false}},"declaration":{"linkSupport":true},"definition":{"linkSupport":true},"implementation":{"linkSupport":true},"publishDiagnostics":{"relatedInformation":true},"signatureHelp":{"signatureInformation":{"parameterInformation":{"labelOffsetSupport":true}}},"typeDefinition":{"linkSupport":true}},"workspace":{"applyEdit":true,"didChangeWatchedFiles":{"dynamicRegistration":true}}},"processId":77091,"rootPath":"/Users/USER/GitRepos/Test","rootUri":"file:///Users/USER/GitRepos/Test","trace":"off"},"id":1}
```

`colorProvider` is set to `null` in this message. [According to the specification](https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/), `colorProvider` can either be not defined or a boolean value.

This causes the following error: 

```
┌ Warning: no Manifest.toml file found, static paths used
└ @ Revise ~/.julia/packages/Revise/S7mrl/src/Revise.jl:1190
[ Info: Started symbol server
[ Info: Tried to load bac558e1-5e72-5ebc-8fee-abe8a469f55d but failed to find it in the manifest.
[ Info: Tried to load 37e2e46d-f89d-539d-b4ee-838fcccc9c8e but failed to find it in the manifest.
[ Info: Tried to load 2f01184e-e22b-5df5-ae63-d93ebab69eaf but failed to find it in the manifest.
[ Info: Tried to load 8f399da3-3557-5675-b5ff-fb832c97cbdb but failed to find it in the manifest.
[ Info: Tried to load b8f27783-ece8-5eb3-8dc8-9495eed66fee but failed to find it in the manifest.
[ Info: Tried to load fdba3010-5040-5b88-9595-932c9decdf73 but failed to find it in the manifest.
[ Info: store set in 0.014803887 seconds
[ Info: Dict{String,Any}("workspace" => Dict{String,Any}("didChangeWatchedFiles" => Dict{String,Any}("dynamicRegistration" => true),"applyEdit" => true),"textDocument" => Dict{String,Any}("definition" => Dict{String,Any}("linkSupport" => true),"declaration" => Dict{String,Any}("linkSupport" => true),"publishDiagnostics" => Dict{String,Any}("relatedInformation" => true),"completion" => Dict{String,Any}("completionItem" => Dict{String,Any}("snippetSupport" => false)),"implementation" => Dict{String,Any}("linkSupport" => true),"signatureHelp" => Dict{String,Any}("signatureInformation" => Dict{String,Any}("parameterInformation" => Dict{String,Any}("labelOffsetSupport" => true))),"typeDefinition" => Dict{String,Any}("linkSupport" => true),"colorProvider" => nothing))
ERROR: MethodError: Cannot `convert` an object of type Nothing to an object of type Bool
Closest candidates are:
  convert(::Type{T}, !Matched::T) where T<:Number at number.jl:6
  convert(::Type{T}, !Matched::Number) where T<:Number at number.jl:7
  convert(::Type{T}, !Matched::Ptr) where T<:Integer at pointer.jl:23
  ...
Stacktrace:
 [1] convert(::Type{Union{Missing, Bool}}, ::Nothing) at ./missing.jl:69
 [2] LanguageServer.Capabilities(::Nothing) at /Users/USER/.julia/dev/LanguageServer/src/protocol/initialize.jl:4
 [3] LanguageServer.TextDocumentClientCapabilities(::Dict{String,Any}) at /Users/USER/.julia/dev/LanguageServer/src/protocol/protocol.jl:37
 [4] LanguageServer.ClientCapabilities(::Dict{String,Any}) at /Users/USER/.julia/dev/LanguageServer/src/protocol/protocol.jl:37
 [5] LanguageServer.InitializeParams(::Dict{String,Any}) at /Users/USER/.julia/dev/LanguageServer/src/protocol/initialize.jl:183
 [6] parse_params(::Type{Val{:initialize}}, ::Dict{String,Any}) at /Users/USER/.julia/dev/LanguageServer/src/requests/init.jl:73
 [7] parse(::Type{LanguageServer.JSONRPC.Request}, ::Dict{String,Any}) at /Users/USER/.julia/dev/LanguageServer/src/jsonrpc.jl:46
 [8] run(::LanguageServerInstance) at /Users/USER/.julia/dev/LanguageServer/src/languageserverinstance.jl:101
 [9] top-level scope at none:1
/Users/USER/.julia/dev/LanguageServer/contrib/languageserver.sh: line 34: kill: (77142) - No such process
```

[LanguageServer.jl](https://github.com/julia-vscode/LanguageServer.jl) conforms to the specification strictly. See discussion  here for more information: https://github.com/julia-vscode/LanguageServer.jl/pull/454


## Environment
- neovim/vim version (`nvim --version` or `vim --version`):

```
$ nvim --version
NVIM v0.5.0-279-ge922576bd
Build type: Release
LuaJIT 2.1.0-beta3
Compilation: /Applications/Xcode-10.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -O2 -DNDEBUG -Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion -Wmissing-prototypes -Wimplicit-fallthrough -Wvla -fstack-protector-strong -fdiagnostics-color=auto -DINCLUDE_GENERATED_DECLARATIONS -D_GNU_SOURCE -DNVIM_MSGPACK_HAS_FLOAT32 -DNVIM_UNIBI_HAS_VAR_FROM -DMIN_LOG_LEVEL=3 -I/Users/travis/build/neovim/bot-ci/build/neovim/build/config -I/Users/travis/build/neovim/bot-ci/build/neovim/src -I/Users/travis/build/neovim/bot-ci/build/neovim/.deps/usr/include -I/usr/local/opt/gettext/include -I/usr/include -I/Users/travis/build/neovim/bot-ci/build/neovim/build/src/nvim/auto -I/Users/travis/build/neovim/bot-ci/build/neovim/build/include
Compiled by travis@Traviss-Mac-6.local

Features: +acl +iconv +tui
See ":help feature-compile"

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/share/nvim"

Run :checkhealth for more info
```

- This plugin version (`git rev-parse --short HEAD`):

```
$ cd ~/.local/share/nvim/plugged/LanguageClient-neovim
$ git rev-parse --short HEAD
0394fba
```

- This plugin's binary version (`bin/languageclient --version`):

```
$ ~/.local/share/nvim/plugged/LanguageClient-neovim/bin/languageclient --version
languageclient 0.1.155 1ffab7ef19e2acae6f32c6f3a27581429d28e47b
```

- Minimal vimrc content (A minimal vimrc is the smallest vimrc that could
  reproduce the issue. Refer to an example [here][min-vimrc.vim]):

```
call plug#begin('~/.local/share/nvim/plugged')

Plug 'JuliaEditorSupport/julia-vim'
Plug 'autozimu/LanguageClient-neovim', {
    \ 'branch': 'next',
    \ 'do': 'bash install.sh',
    \ }

call plug#end()

" Always draw sign column. Prevent buffer moving when adding/deleting sign.
set signcolumn=yes

let g:LanguageClient_serverCommands = {
    \ 'julia': ['~/Applications/Julia-1.3.app/Contents/Resources/julia/bin/julia', '--startup-file=no', '--history-file=no', '--project', '-e', '
    \       using LanguageServer;
    \       using Pkg;
    \       env_path = dirname(Pkg.Types.Context().env.project_file);
    \       debug = false;
    \       server = LanguageServer.LanguageServerInstance(stdin, stdout, debug, env_path, "");
    \       server.runlinter = true;
    \       run(server);
    \   '],
    \ }

let g:LanguageClient_loggingLevel = 'INFO'
let g:LanguageClient_virtualTextPrefix = ''
let g:LanguageClient_loggingFile =  expand('~/.local/share/nvim/LanguageClient.log')
let g:LanguageClient_serverStderr = expand('~/.local/share/nvim/LanguageServer.log')
```

- Language server link and version:

https://github.com/julia-vscode/LanguageServer.jl/tree/b9ba36fc96801671752ba15242a3d4fc2e8d25be

[min-vimrc.vim]: https://github.com/autozimu/LanguageClient-neovim/blob/next/min-vimrc.vim

## To Reproduce
Steps to reproduce the behavior:
1. Store settings above in min-vimrc.vim
1. Start vim: `nvim -u min-vimrc.vim test.jl`
1. See error in `~/.local/share/nvim/LanguageServer.log`

## Current behavior

The current behaviour sends `colorProvider: null` as part of the JSON, and that does not match the spec.

## Expected behavior

Send `colorProvider: false` instead, or don't send a key value pair for the key `colorProvider` if the value is `null`.




Discussion / hints:
Just wanted to bump this in case anyone is watching.  This issue is crashing our Julia language server as well.
This seems to be an issue with a missing attribute in the crate `lsp-types`. Just opened https://github.com/gluon-lang/lsp-types/pull/160 to fix that, it should be fixed once that gets merged and we upgrade the dependency.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 562854572..62543772e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -67,7 +67,7 @@ dependencies = [
 
 [[package]]
 name = "base64"
-version = "0.11.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -402,7 +402,7 @@ dependencies = [
  "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.70.0 (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.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -475,10 +475,10 @@ dependencies = [
 
 [[package]]
 name = "lsp-types"
-version = "0.70.0"
+version = "0.74.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "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)",
@@ -1050,7 +1050,7 @@ dependencies = [
 "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
 "checksum backtrace 0.3.34 (registry+https://github.com/rust-lang/crates.io-index)" = "b5164d292487f037ece34ec0de2fcede2faa162f085dd96d2385ab81b12765ba"
 "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
-"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 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"
@@ -1096,7 +1096,7 @@ dependencies = [
 "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.3 (registry+https://github.com/rust-lang/crates.io-index)" = "100052474df98158c0738a7d3f4249c99978490178b5f9f68cd835ac57adbd1b"
-"checksum lsp-types 0.70.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef197b24cb3f12fc3984667a505691fec9d683204ddff56f12b2d1940e09a988"
+"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.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
diff --git a/Cargo.toml b/Cargo.toml
index 11840b137..da2a75150 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,7 +29,7 @@ serde_json = "1"
 json-patch = "0.2"
 crossbeam = "0.7.3"
 jsonrpc-core = "12"
-lsp-types = { version = "0.70.0", features = ["proposed"] }
+lsp-types = { version = "0.74.2", features = ["proposed"] }
 url = "2"
 pathdiff = "0"
 diff = "0"
diff --git a/src/language_server_protocol.rs b/src/language_server_protocol.rs
index dad48d8e5..0723ccc39 100644
--- a/src/language_server_protocol.rs
+++ b/src/language_server_protocol.rs
@@ -2,8 +2,8 @@ use super::*;
 
 use crate::language_client::LanguageClient;
 use crate::lsp::notification::Notification;
-use crate::lsp::request::GotoDefinitionResponse;
 use crate::lsp::request::Request;
+use crate::lsp::GotoDefinitionResponse;
 use crate::rpcclient::RpcClient;
 use failure::err_msg;
 use itertools::Itertools;
@@ -1183,10 +1183,18 @@ impl LanguageClient {
                 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()
@@ -1471,6 +1479,8 @@ impl LanguageClient {
                 text_document: TextDocumentIdentifier {
                     uri: filename.to_url()?,
                 },
+                work_done_progress_params: WorkDoneProgressParams::default(),
+                partial_result_params: PartialResultParams::default(),
             },
         )?;
 
@@ -3313,7 +3323,11 @@ impl LanguageClient {
 
         let mut edits = vec![];
         if self.get(|state| state.completionPreferTextEdit)? {
-            if let Some(edit) = lspitem.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.
                 //
@@ -3328,7 +3342,7 @@ impl LanguageClient {
                     return Ok(());
                 }
                 edits.push(edit);
-            };
+            }
         }
 
         if self.get(|state| state.applyCompletionAdditionalTextEdits)? {
diff --git a/src/types.rs b/src/types.rs
index b58ad12ee..d2628ad07 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -591,19 +591,38 @@ impl VimCompleteItem {
         );
         let abbr = lspitem.label.clone();
 
+        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| text_edit.new_text.is_empty())
+                    .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(ref 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.character != complete_position {
@@ -616,7 +635,7 @@ impl VimCompleteItem {
                         text_edit.new_text.clone()
                     }
                 }
-                (Some(ref text_edit), _) => text_edit.new_text.clone(),
+                (Some(CompletionTextEdit::Edit(ref text_edit)), _) => text_edit.new_text.clone(),
                 (_, _) => lspitem.label.clone(),
             }
         });


Test patch: