Skip to content

Commit 8c3b74e

Browse files
authored
add enable_extensions config and set up gopls extensions (#1072)
1 parent d441f17 commit 8c3b74e

File tree

5 files changed

+156
-18
lines changed

5 files changed

+156
-18
lines changed

doc/LanguageClient.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,9 @@ Example setting 3. Set the preferred markup kind to `markdown`
569569

570570
```
571571
let g:LanguageClient_preferredMarkupKind: ['markdown']
572+
```
572573

573-
This setting may have no effect of the server decides not to honour it.
574+
This setting may have no effect if the server decides not to honour it.
574575

575576
Default: v:null
576577
Valid options: Array<String>
@@ -603,6 +604,31 @@ Full path to languageclient binary.
603604
Default: 'bin/languageclient' relative to plugin root.
604605
Valid options: String
605606

607+
2.39 g:LanguageClient_enableExtensions *g:LanguageClient_enableExtensions*
608+
609+
LanguageClient-neovim provides extensions for some language servers, such as gopls or rust-analyzer.
610+
These extensions can be turned on or off, and they are configurable per language (not per language
611+
server).
612+
613+
An example of the extensions provided is running Rust or Go tests in a vim terminal. This takes
614+
advantage of the commands provided by rust-analyzer or gopls (respectively) and executes those
615+
commands in a vim terminal.
616+
617+
Example config:
618+
```
619+
let g:LanguageClient_enableExtensions = {
620+
\ 'go': v:false,
621+
\ 'rust': v:true,
622+
\ }
623+
```
624+
In this example we explicitly disable extensions for the filetype `go`, and enable them for `rust`.
625+
Filetypes not included in this config will also have their extensions disabled, unless you
626+
explicitly enable them like we did for `rust`.
627+
628+
The default value for this config, or the absence of this config, enables extensions for all filetypes.
629+
630+
Default: v:null
631+
606632
==============================================================================
607633
3. Commands *LanguageClientCommands*
608634

src/extensions/gopls.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::language_client::LanguageClient;
2+
use anyhow::Result;
3+
use lsp_types::Command;
4+
use serde::Deserialize;
5+
use serde_json::Value;
6+
7+
pub mod command {
8+
pub const TEST: &str = "test";
9+
pub const GENERATE: &str = "generate";
10+
}
11+
12+
impl LanguageClient {
13+
pub fn handle_gopls_command(&self, cmd: &Command) -> Result<bool> {
14+
match cmd.command.as_str() {
15+
command::TEST => {
16+
if let Some(args) = &cmd.arguments {
17+
if let Some(file) = args.get(0) {
18+
let file = String::deserialize(file)?;
19+
let path = parse_package_path(file.as_str()).unwrap_or("./...".into());
20+
let run = <Option<Vec<String>>>::deserialize(
21+
args.get(1).unwrap_or(&Value::Null),
22+
)?;
23+
24+
let bench = <Option<Vec<String>>>::deserialize(
25+
args.get(2).unwrap_or(&Value::Null),
26+
)?;
27+
28+
let run = run.unwrap_or_default();
29+
let bench = bench.unwrap_or_default();
30+
31+
if run.len() > 0 {
32+
let cmd = format!("term go test -run '{}' {}", run.join("|"), path);
33+
self.vim()?.command(cmd)?;
34+
} else if bench.len() > 0 {
35+
let cmd = format!("term go test -bench '{}' {}", bench.join("|"), path);
36+
self.vim()?.command(cmd)?;
37+
} else {
38+
self.vim()?.echoerr("No tests to run")?;
39+
}
40+
}
41+
}
42+
}
43+
command::GENERATE => {
44+
if let Some(arguments) = &cmd.arguments {
45+
if let Some(package) = arguments.get(0) {
46+
let package = String::deserialize(package)?;
47+
let recursive =
48+
bool::deserialize(arguments.get(1).unwrap_or(&Value::Bool(false)))?;
49+
let cmd = match (package, recursive) {
50+
(package, false) => format!("term go generate -x {}", package),
51+
(_, true) => "term go generate -x ./...".into(),
52+
};
53+
self.vim()?.command(cmd)?;
54+
}
55+
}
56+
}
57+
58+
_ => return Ok(false),
59+
}
60+
61+
Ok(true)
62+
}
63+
}
64+
65+
fn parse_package_path(path: &str) -> Option<String> {
66+
let path = if path.starts_with("file://") {
67+
path.strip_prefix("file://")?
68+
} else {
69+
path
70+
};
71+
let path = std::path::PathBuf::from(path);
72+
Some(path.parent()?.to_str()?.to_owned())
73+
}
74+
75+
#[cfg(test)]
76+
mod test {
77+
use super::*;
78+
79+
#[test]
80+
fn test_parse_package_path() {
81+
let folder = parse_package_path("file:///home/dev/someone/project/file.go");
82+
assert!(folder.is_some());
83+
assert_eq!("/home/dev/someone/project", folder.unwrap());
84+
85+
let folder = parse_package_path("/home/dev/someone/project/file.go");
86+
assert!(folder.is_some());
87+
assert_eq!("/home/dev/someone/project", folder.unwrap());
88+
}
89+
}

src/extensions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod gopls;
12
pub mod java;
23
pub mod rust_analyzer;

src/language_server_protocol.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::extensions::{java, rust_analyzer};
1+
use crate::extensions::java;
22
use crate::language_client::LanguageClient;
33
use crate::vim::{try_get, Mode};
44
use crate::{
@@ -177,6 +177,7 @@ impl LanguageClient {
177177
apply_completion_text_edits,
178178
preferred_markup_kind,
179179
hide_virtual_texts_on_insert,
180+
enable_extensions,
180181
): (
181182
Option<usize>,
182183
String,
@@ -190,6 +191,7 @@ impl LanguageClient {
190191
u8,
191192
Option<Vec<MarkupKind>>,
192193
u8,
194+
Option<HashMap<String, bool>>,
193195
) = self.vim()?.eval(
194196
[
195197
"get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)",
@@ -204,6 +206,7 @@ impl LanguageClient {
204206
"get(g:, 'LanguageClient_applyCompletionAdditionalTextEdits', 1)",
205207
"get(g:, 'LanguageClient_preferredMarkupKind', v:null)",
206208
"s:GetVar('LanguageClient_hideVirtualTextsOnInsert', 0)",
209+
"get(g:, 'LanguageClient_enableExtensions', v:null)",
207210
]
208211
.as_ref(),
209212
)?;
@@ -320,6 +323,7 @@ impl LanguageClient {
320323
state.server_stderr = server_stderr;
321324
state.is_nvim = is_nvim;
322325
state.preferred_markup_kind = preferred_markup_kind;
326+
state.enable_extensions = enable_extensions;
323327
Ok(())
324328
})?;
325329

@@ -1017,15 +1021,32 @@ impl LanguageClient {
10171021
}
10181022

10191023
fn try_handle_command_by_client(&self, cmd: &Command) -> Result<bool> {
1020-
match cmd.command.as_str() {
1021-
java::command::APPLY_WORKSPACE_EDIT => self.handle_java_command(cmd),
1022-
rust_analyzer::command::RUN
1023-
| rust_analyzer::command::RUN_SINGLE
1024-
| rust_analyzer::command::SHOW_REFERENCES
1025-
| rust_analyzer::command::SELECT_APPLY_SOURCE_CHANGE
1026-
| rust_analyzer::command::APPLY_SOURCE_CHANGE => self.handle_rust_analyzer_command(cmd),
1027-
1028-
_ => Ok(false),
1024+
let filetype: String = self.vim()?.eval("&filetype")?;
1025+
let enabled_extensions = self.get(|state| state.enable_extensions.clone())?;
1026+
if !enabled_extensions
1027+
.unwrap_or_default()
1028+
.get(&filetype)
1029+
.cloned()
1030+
.unwrap_or(true)
1031+
{
1032+
return Ok(false);
1033+
}
1034+
1035+
let capabilities = self.get(|state| state.capabilities.get(&filetype).cloned())?;
1036+
let server_name = capabilities
1037+
.unwrap_or_default()
1038+
.server_info
1039+
.unwrap_or_default()
1040+
.name;
1041+
1042+
match server_name.as_str() {
1043+
"gopls" => self.handle_gopls_command(cmd),
1044+
"rust-analyzer" => self.handle_rust_analyzer_command(cmd),
1045+
_ => match cmd.command.as_str() {
1046+
// not sure which name java's language server advertises
1047+
java::command::APPLY_WORKSPACE_EDIT => self.handle_java_command(cmd),
1048+
_ => Ok(false),
1049+
},
10291050
}
10301051
}
10311052

@@ -1247,10 +1268,11 @@ impl LanguageClient {
12471268
},
12481269
)?;
12491270

1271+
let initialize_result = InitializeResult::deserialize(&result)?;
12501272
self.update(|state| {
12511273
state
12521274
.capabilities
1253-
.insert(language_id.clone(), result.clone());
1275+
.insert(language_id.clone(), initialize_result);
12541276
Ok(())
12551277
})?;
12561278

@@ -2122,9 +2144,7 @@ impl LanguageClient {
21222144
if let Some(initialize_result) = capabilities.get(&language_id) {
21232145
// XXX: the capabilities state field stores the initialize result, not the capabilities
21242146
// themselves, so we need to deserialize to InitializeResult.
2125-
let initialize_result: InitializeResult =
2126-
serde_json::from_value(initialize_result.clone())?;
2127-
let capabilities = initialize_result.capabilities;
2147+
let capabilities = initialize_result.capabilities.clone();
21282148

21292149
if let Some(code_lens_provider) = capabilities.code_lens_provider {
21302150
info!("Begin {}", lsp_types::request::CodeLensRequest::METHOD);

src/types.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use log::*;
1313
use lsp_types::{
1414
CodeAction, CodeLens, Command, CompletionItem, CompletionTextEdit, Diagnostic,
1515
DiagnosticSeverity, DocumentHighlightKind, FileChangeType, FileEvent, Hover, HoverContents,
16-
InsertTextFormat, Location, MarkedString, MarkupContent, MarkupKind, MessageType,
17-
NumberOrString, Registration, SemanticHighlightingInformation, SymbolInformation,
16+
InitializeResult, InsertTextFormat, Location, MarkedString, MarkupContent, MarkupKind,
17+
MessageType, NumberOrString, Registration, SemanticHighlightingInformation, SymbolInformation,
1818
TextDocumentItem, TextDocumentPositionParams, TraceOption, Url, WorkspaceEdit,
1919
};
2020
use maplit::hashmap;
@@ -146,7 +146,7 @@ pub struct State {
146146
#[serde(skip_serializing)]
147147
pub vim: Vim,
148148

149-
pub capabilities: HashMap<String, Value>,
149+
pub capabilities: HashMap<String, InitializeResult>,
150150
pub registrations: Vec<Registration>,
151151
pub roots: HashMap<String, String>,
152152
pub text_documents: HashMap<String, TextDocumentItem>,
@@ -209,6 +209,7 @@ pub struct State {
209209
pub use_virtual_text: UseVirtualText,
210210
pub hide_virtual_texts_on_insert: bool,
211211
pub echo_project_root: bool,
212+
pub enable_extensions: Option<HashMap<String, bool>>,
212213

213214
pub server_stderr: Option<String>,
214215
pub logger: Logger,
@@ -292,6 +293,7 @@ impl State {
292293
echo_project_root: true,
293294
server_stderr: None,
294295
preferred_markup_kind: None,
296+
enable_extensions: None,
295297

296298
logger,
297299
})

0 commit comments

Comments
 (0)