Skip to content

Commit b7512c9

Browse files
Support "binary" settings for Rubocop and Solargraph (zed-industries/zed#15110)
Hello, this pull request adds support for specifying and using the "binary" settings for Rubocop and Solargraph LSPs. AFAIK, Ruby LSP does not require the bundler context but that could be added later easily. In Ruby world, like in Node.js world, almost all projects rely on project specific packages (gems) and their versions. Solargraph and Rubocop gems are usually installed as project dependencies. Attempting to use global installation of them fail in most cases due to incompatible or missing dependencies (gems). To avoid that, Ruby engineers have the `bundler` gem that provides the `exec` command. This command executes the given command in the context of the bundle. This pull request adds support for pulling the `binary` settings to use them in starting both LSPs. For instance, to start the Solargraph gem in the context of the bundler, the end user must configure the binary settings in the folder-specific settings file like so: ```json { "lsp": { "solargraph": { "binary": { "path": "/Users/vslobodin/Development/festivatica/bin/rubocop" } } } } ``` The `path` key must be an absolute path to the `binstub` of the `solargraph` gem. The same applies to the "rubocop" gem. Side note but it would be awesome to use Zed specific environment variables to make this a bit easier. For instance, we could use the `ZED_WORKTREE_ROOT` environment variable: ```json { "lsp": { "solargraph": { "binary": { "path": "${ZED_WORKTREE_ROOT}/bin/rubocop" } } } } ``` But this is out of the scope of this pull request. The code is a bit messy and repeatable in some places, I am happy to improve it here or later. References: - https://bundler.io/v2.4/man/bundle-exec.1.html - https://solargraph.org/guides/troubleshooting - https://bundler.io/v2.5/man/bundle-binstubs.1.html This pull request is based on these two pull requests: - zed-industries/zed#14655 - zed-industries/zed#15001 Closes zed-industries/zed#5109. Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
1 parent 4f0b961 commit b7512c9

File tree

4 files changed

+148
-39
lines changed

4 files changed

+148
-39
lines changed

src/language_servers/rubocop.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use zed_extension_api::{self as zed, Result};
1+
use zed_extension_api::{self as zed, settings::LspSettings, LanguageServerId, Result};
2+
3+
pub struct RubocopBinary {
4+
pub path: String,
5+
pub args: Option<Vec<String>>,
6+
}
27

38
pub struct Rubocop {}
49

@@ -9,11 +14,46 @@ impl Rubocop {
914
Self {}
1015
}
1116

12-
pub fn server_script_path(&mut self, worktree: &zed::Worktree) -> Result<String> {
13-
let path = worktree.which("rubocop").ok_or_else(|| {
14-
"rubocop must be installed manually. Install it with `gem install rubocop` or specify the 'binary' path to it via local settings.".to_string()
15-
})?;
17+
pub fn language_server_command(
18+
&mut self,
19+
language_server_id: &LanguageServerId,
20+
worktree: &zed::Worktree,
21+
) -> Result<zed::Command> {
22+
let binary = self.language_server_binary(language_server_id, worktree)?;
23+
24+
Ok(zed::Command {
25+
command: binary.path,
26+
args: binary.args.unwrap_or_else(|| vec!["--lsp".to_string()]),
27+
env: worktree.shell_env(),
28+
})
29+
}
30+
31+
fn language_server_binary(
32+
&self,
33+
_language_server_id: &LanguageServerId,
34+
worktree: &zed::Worktree,
35+
) -> Result<RubocopBinary> {
36+
let binary_settings = LspSettings::for_worktree("rubocop", worktree)
37+
.ok()
38+
.and_then(|lsp_settings| lsp_settings.binary);
39+
let binary_args = binary_settings
40+
.as_ref()
41+
.and_then(|binary_settings| binary_settings.arguments.clone());
42+
43+
if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
44+
return Ok(RubocopBinary {
45+
path,
46+
args: binary_args,
47+
});
48+
}
49+
50+
if let Some(path) = worktree.which("rubocop") {
51+
return Ok(RubocopBinary {
52+
path,
53+
args: binary_args,
54+
});
55+
}
1656

17-
Ok(path)
57+
Err("rubocop must be installed manually. Install it with `gem install rubocop` or specify the 'binary' path to it via local settings.".to_string())
1858
}
1959
}

src/language_servers/ruby_lsp.rs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
use zed::{
1+
use zed_extension_api::{
2+
self as zed,
23
lsp::{Completion, CompletionKind, Symbol, SymbolKind},
3-
CodeLabel, CodeLabelSpan,
4+
settings::LspSettings,
5+
CodeLabel, CodeLabelSpan, LanguageServerId, Result,
46
};
5-
use zed_extension_api::{self as zed, Result};
7+
8+
pub struct RubyLspBinary {
9+
pub path: String,
10+
pub args: Option<Vec<String>>,
11+
}
612

713
pub struct RubyLsp {}
814

@@ -13,13 +19,50 @@ impl RubyLsp {
1319
Self {}
1420
}
1521

16-
pub fn server_script_path(&mut self, worktree: &zed::Worktree) -> Result<String> {
17-
let path = worktree.which("ruby-lsp").ok_or_else(|| {
18-
"ruby-lsp must be installed manually. Install it with `gem install ruby-lsp`."
19-
.to_string()
20-
})?;
22+
pub fn language_server_command(
23+
&mut self,
24+
language_server_id: &LanguageServerId,
25+
worktree: &zed::Worktree,
26+
) -> Result<zed::Command> {
27+
let binary = self.language_server_binary(language_server_id, worktree)?;
28+
29+
Ok(zed::Command {
30+
command: binary.path,
31+
args: binary.args.unwrap_or_default(),
32+
env: worktree.shell_env(),
33+
})
34+
}
35+
36+
fn language_server_binary(
37+
&self,
38+
_language_server_id: &LanguageServerId,
39+
worktree: &zed::Worktree,
40+
) -> Result<RubyLspBinary> {
41+
let binary_settings = LspSettings::for_worktree("ruby-lsp", worktree)
42+
.ok()
43+
.and_then(|lsp_settings| lsp_settings.binary);
44+
let binary_args = binary_settings
45+
.as_ref()
46+
.and_then(|binary_settings| binary_settings.arguments.clone());
47+
48+
if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
49+
return Ok(RubyLspBinary {
50+
path,
51+
args: binary_args,
52+
});
53+
}
2154

22-
Ok(path)
55+
if let Some(path) = worktree.which("ruby-lsp") {
56+
return Ok(RubyLspBinary {
57+
path,
58+
args: binary_args,
59+
});
60+
}
61+
62+
Err(
63+
"ruby-lsp must be installed manually. Install it with `gem install ruby-lsp`."
64+
.to_string(),
65+
)
2366
}
2467

2568
pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {

src/language_servers/solargraph.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
use zed::lsp::{Completion, CompletionKind, Symbol, SymbolKind};
22
use zed::{CodeLabel, CodeLabelSpan};
3-
use zed_extension_api::{self as zed, Result};
3+
use zed_extension_api::settings::LspSettings;
4+
use zed_extension_api::{self as zed, LanguageServerId, Result};
5+
6+
pub struct SolargraphBinary {
7+
pub path: String,
8+
pub args: Option<Vec<String>>,
9+
}
410

511
pub struct Solargraph {}
612

@@ -11,12 +17,47 @@ impl Solargraph {
1117
Self {}
1218
}
1319

14-
pub fn server_script_path(&mut self, worktree: &zed::Worktree) -> Result<String> {
15-
let path = worktree
16-
.which("solargraph")
17-
.ok_or_else(|| "solargraph must be installed manually".to_string())?;
20+
pub fn language_server_command(
21+
&mut self,
22+
language_server_id: &LanguageServerId,
23+
worktree: &zed::Worktree,
24+
) -> Result<zed::Command> {
25+
let binary = self.language_server_binary(language_server_id, worktree)?;
26+
27+
Ok(zed::Command {
28+
command: binary.path,
29+
args: binary.args.unwrap_or_else(|| vec!["stdio".to_string()]),
30+
env: worktree.shell_env(),
31+
})
32+
}
1833

19-
Ok(path)
34+
fn language_server_binary(
35+
&self,
36+
_language_server_id: &LanguageServerId,
37+
worktree: &zed::Worktree,
38+
) -> Result<SolargraphBinary> {
39+
let binary_settings = LspSettings::for_worktree("solargraph", worktree)
40+
.ok()
41+
.and_then(|lsp_settings| lsp_settings.binary);
42+
let binary_args = binary_settings
43+
.as_ref()
44+
.and_then(|binary_settings| binary_settings.arguments.clone());
45+
46+
if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
47+
return Ok(SolargraphBinary {
48+
path,
49+
args: binary_args,
50+
});
51+
}
52+
53+
if let Some(path) = worktree.which("solargraph") {
54+
return Ok(SolargraphBinary {
55+
path,
56+
args: binary_args,
57+
});
58+
}
59+
60+
Err("solargraph must be installed manually".to_string())
2061
}
2162

2263
pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {

src/ruby.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,15 @@ impl zed::Extension for RubyExtension {
3030
match language_server_id.as_ref() {
3131
Solargraph::LANGUAGE_SERVER_ID => {
3232
let solargraph = self.solargraph.get_or_insert_with(|| Solargraph::new());
33-
34-
Ok(zed::Command {
35-
command: solargraph.server_script_path(worktree)?,
36-
args: vec!["stdio".into()],
37-
env: worktree.shell_env(),
38-
})
33+
solargraph.language_server_command(language_server_id, worktree)
3934
}
4035
RubyLsp::LANGUAGE_SERVER_ID => {
4136
let ruby_lsp = self.ruby_lsp.get_or_insert_with(|| RubyLsp::new());
42-
43-
Ok(zed::Command {
44-
command: ruby_lsp.server_script_path(worktree)?,
45-
args: vec![],
46-
env: worktree.shell_env(),
47-
})
37+
ruby_lsp.language_server_command(language_server_id, worktree)
4838
}
4939
Rubocop::LANGUAGE_SERVER_ID => {
5040
let rubocop = self.rubocop.get_or_insert_with(|| Rubocop::new());
51-
52-
Ok(zed::Command {
53-
command: rubocop.server_script_path(worktree)?,
54-
args: vec!["--lsp".into()],
55-
env: worktree.shell_env(),
56-
})
41+
rubocop.language_server_command(language_server_id, worktree)
5742
}
5843
language_server_id => Err(format!("unknown language server: {language_server_id}")),
5944
}

0 commit comments

Comments
 (0)