Skip to content

Commit 8010205

Browse files
authored
Add support to specify server name and initialization options in server command (#1116)
* Add support to specify server name to send the correct initializationOptions * Clippy fixes * Remove unnecessary dev dependency * Log instead of error if using `initializationOptions` in workspace settings * Fall back to default if no workspace settings are found * Merge all server's intialization options in under State::initialization_options * Simplify merged_initialization_options
1 parent 7892d85 commit 8010205

10 files changed

+367
-40
lines changed

doc/LanguageClient.txt

+74-10
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,64 @@ To use the language server with Vim's formatting operator |gq|, set 'formatexpr'
7575

7676
2.1 g:LanguageClient_serverCommands *g:LanguageClient_serverCommands*
7777

78-
String to list map. Defines commands to start language server for specific
79-
filetype. For example: >
78+
Dictionary to specify the servers to be used for each filetype. The keys are
79+
strings representing the filetypes and the values are either
80+
1 - a list of strings that form a command
81+
2 - a dictionary with keys name, command and initializationOptions, where:
82+
- name: is the name of the server, which should match the root node of the
83+
configuration options specified in the settings.json file for this server
84+
- command: is the same list of strings in option 1
85+
- initializationOptions: is an optional dictionary with options to be passed
86+
to the server. The values to be set here are highly dependent on each
87+
server so you should see the documentation for each server to find out
88+
what to configure in here (if anything). The options set in
89+
initializationOptions are merged with the default settings for each
90+
language server and with the workspace settings defined by the user in
91+
the files specified in the variable `LanguageClient_settingsPath'. The
92+
order in which these settings are merged is first the defualt settings,
93+
then the server settings configured in this section and lastly the
94+
contents of the files in the `LanguageClient_settingsPath` variable in
95+
the order in which they were listed.
96+
97+
For example: >
8098
8199
let g:LanguageClient_serverCommands = {
82100
\ 'rust': ['rustup', 'run', 'stable', 'rls'],
83-
\ }
84-
85-
In the example above, 'run', 'stable', and 'rls' are arguments to the
86-
'rustup' command line tool.
87-
88-
Or tcp connection string to the server, >
101+
\ 'go': {
102+
\ 'name': 'gopls',
103+
\ 'command': ['gopls'],
104+
\ 'initializationOptions': {
105+
\ 'usePlaceholders': v:true,
106+
\ 'codelens': {
107+
\ 'generate': v:true,
108+
\ 'test': v:true,
109+
\ },
110+
\ },
111+
\ },
112+
\}
113+
114+
In the configuration for the rust filetype above, 'run', 'stable', and 'rls'
115+
are arguments to the 'rustup' command line tool. And in the configuration for
116+
the go filetype the server is configured to run the command `gopls` with no
117+
additional arguments, and with the initialization options set in the
118+
`initializationOptions` key.
119+
120+
You can also use a tcp connection to the server, for example: >
89121
let g:LanguageClient_serverCommands = {
90122
\ 'javascript': ['tcp://127.0.0.1:2089'],
91123
\ }
92124
93125
Note: environmental variables are not supported except home directory alias `~`.
94126

95127
Default: {}
96-
Valid Option: Map<String, List<String> | String>
128+
129+
130+
131+
Valid Option: Map<String, List<String> | {
132+
name: String
133+
command: List<String>
134+
initializationOptions?: Map<String, Any>
135+
}>
97136

98137
2.2 g:LanguageClient_diagnosticsDisplay *g:LanguageClient_diagnosticsDisplay*
99138

@@ -231,9 +270,34 @@ path this is relative to the workspace directory. If several paths are
231270
provided, then the corresponding settings are merged with precedence going to
232271
the last file.
233272

273+
The initialization options found in the files in this config are combined with
274+
the initialization options specified in the server command, if any. The former
275+
taking precedence over the latter.
276+
277+
Note that the key under which the initialization options for each server lives
278+
matches the key under which the server expects it's configuration. This is
279+
important for servers that can request configuration via a
280+
`workspace/configuration` request.
281+
282+
Previously, the initialization options lived under a `initializationOptions`
283+
key, which worked, but made answering that `workspace/configuration` request
284+
hard, since we couldn't really get the correct path to the setting the server
285+
requested. Since version 0.1.161 of this plugin, that key has been deprecated
286+
and you'll see a message saying that you should move off of it if your settings
287+
file includes an `initializationOptions` key.
288+
234289
Example settings file content: >
235290
{
236-
"rust.clippy_preference": "on"
291+
"gopls": {
292+
"usePlaceholders": true,
293+
"local": "github.com/my/pkg",
294+
},
295+
"rust-analyzer": {
296+
"inlayHints": {
297+
"enable": true,
298+
"chainingHints": true
299+
},
300+
},
237301
}
238302
239303
Default: ".vim/settings.json"

src/config.rs src/config/mod.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod server_command;
2+
3+
pub use server_command::*;
4+
15
use crate::{
26
types::{
37
CodeLensDisplay, DiagnosticsDisplay, DiagnosticsList, DocumentHighlightDisplay,
@@ -14,7 +18,7 @@ use std::{path::PathBuf, str::FromStr, time::Duration};
1418
#[derive(Debug)]
1519
pub struct Config {
1620
pub auto_start: bool,
17-
pub server_commands: HashMap<String, Vec<String>>,
21+
pub server_commands: HashMap<String, ServerCommand>,
1822
pub selection_ui: SelectionUI,
1923
pub trace: TraceOption,
2024
pub settings_path: Vec<String>,
@@ -98,7 +102,7 @@ struct DeserializableConfig {
98102
logging_level: log::LevelFilter,
99103
server_stderr: Option<String>,
100104
auto_start: u8,
101-
server_commands: HashMap<String, Vec<String>>,
105+
server_commands: HashMap<String, ServerCommand>,
102106
selection_ui: Option<String>,
103107
trace: Option<String>,
104108
settings_path: Vec<String>,

src/config/server_command.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use crate::utils::expand_json_path;
2+
use jsonrpc_core::Value;
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6+
#[serde(rename_all = "camelCase")]
7+
pub struct ServerDetails {
8+
pub command: Vec<String>,
9+
pub name: String,
10+
pub initialization_options: Option<Value>,
11+
}
12+
13+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14+
#[serde(untagged)]
15+
pub enum ServerCommand {
16+
Simple(Vec<String>),
17+
Detailed(ServerDetails),
18+
}
19+
20+
impl ServerCommand {
21+
pub fn get_command(&self) -> &[String] {
22+
match self {
23+
ServerCommand::Simple(cmd) => &cmd,
24+
ServerCommand::Detailed(cmd) => &cmd.command,
25+
}
26+
}
27+
28+
/// Returns the server name from a ServerCommand. For compatibility purposes, this
29+
/// makes a rather wild assumption when the server name hasn't been explicitly
30+
/// configured. The assumption is that the command for this server is an
31+
/// executable and that the name of the executable is the name of the server.
32+
/// This may not be true for many cases, but it's the best we can do to try and
33+
/// guess the name of the server.
34+
pub fn name(&self) -> String {
35+
match self {
36+
ServerCommand::Simple(cmd) => ServerCommand::name_from_command(&cmd),
37+
ServerCommand::Detailed(cmd) => cmd.name.clone(),
38+
}
39+
}
40+
41+
pub fn initialization_options(&self) -> Value {
42+
match self {
43+
ServerCommand::Simple(_) => Value::Null,
44+
ServerCommand::Detailed(cmd) => {
45+
let options = cmd.initialization_options.clone();
46+
expand_json_path(options.unwrap_or_default())
47+
}
48+
}
49+
}
50+
51+
#[cfg(not(target_os = "windows"))]
52+
fn name_from_command(cmd: &[String]) -> String {
53+
// it's safe to assume there is at least one item in cmd, otherwise
54+
// this would be an empty server command
55+
let first = cmd.first().cloned().unwrap_or_default();
56+
first.split('/').last().unwrap_or_default().to_string()
57+
}
58+
59+
#[cfg(target_os = "windows")]
60+
fn name_from_command(cmd: &[String]) -> String {
61+
// it's safe to assume there is at least one item in cmd, otherwise
62+
// this would be an empty server command
63+
let first = cmd.first().cloned().unwrap_or_default();
64+
first.split('\\').last().unwrap_or_default().to_string()
65+
}
66+
}
67+
68+
#[cfg(test)]
69+
mod test {
70+
use super::*;
71+
72+
#[test]
73+
fn test_name_from_command_handles_binary_name() {
74+
let name = ServerCommand::name_from_command(&vec!["gopls".into()]);
75+
assert_eq!(name.as_str(), "gopls");
76+
}
77+
78+
#[test]
79+
fn test_name_from_command_handles_binary_path() {
80+
let name = ServerCommand::name_from_command(&vec!["/path/to/gopls".into()]);
81+
assert_eq!(name.as_str(), "gopls");
82+
}
83+
}

src/extensions/rust_analyzer.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ pub mod request {
9494
}
9595
}
9696

97-
const FILETYPE: &'static str = "rust";
98-
pub const SERVER_NAME: &'static str = "rust-analyzer";
97+
const FILETYPE: &str = "rust";
98+
pub const SERVER_NAME: &str = "rust-analyzer";
9999

100100
impl LanguageClient {
101101
pub fn rust_analyzer_inlay_hints(&self, filename: &str) -> Result<Vec<types::InlayHint>> {

src/language_client.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ pub struct LanguageClient {
2222
}
2323

2424
impl LanguageClient {
25-
pub fn new(version: String, state: State) -> Self {
25+
pub fn new(version: impl Into<String>, state: State) -> Self {
2626
LanguageClient {
27-
version,
27+
version: version.into(),
2828
state_mutex: Arc::new(Mutex::new(state)),
2929
clients_mutex: Arc::new(Mutex::new(HashMap::new())),
3030
config: Arc::new(RwLock::new(Config::default())),

0 commit comments

Comments
 (0)