Skip to content

Commit 9fbf632

Browse files
authored
Extract common LSPs stuff into LanguageServer trait (#24)
Extract common LSPs stuff into `LanguageServer` trait. There is a common piece of code that is repeated across all LSPs. It can be extracted into `LanguageServer` trait to make the code a bit more readable, in my opinion. I also added some basic unit tests.
1 parent f0225e8 commit 9fbf632

File tree

6 files changed

+207
-187
lines changed

6 files changed

+207
-187
lines changed

src/language_servers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
mod language_server;
12
mod rubocop;
23
mod ruby_lsp;
34
mod solargraph;
45

6+
pub use language_server::LanguageServer;
57
pub use rubocop::*;
68
pub use ruby_lsp::*;
79
pub use solargraph::*;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use zed_extension_api::{self as zed, settings::LspSettings, LanguageServerId, Result};
2+
3+
#[derive(Clone, Debug)]
4+
pub struct LanguageServerBinary {
5+
pub path: String,
6+
pub args: Option<Vec<String>>,
7+
}
8+
9+
pub trait LanguageServer {
10+
const SERVER_ID: &str;
11+
const EXECUTABLE_NAME: &str;
12+
13+
fn default_use_bundler() -> bool {
14+
true // Default for most LSPs except Ruby LSP
15+
}
16+
17+
fn get_executable_args() -> Vec<String> {
18+
Vec::new()
19+
}
20+
21+
fn language_server_binary(
22+
&self,
23+
language_server_id: &LanguageServerId,
24+
worktree: &zed::Worktree,
25+
) -> Result<LanguageServerBinary> {
26+
let lsp_settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree)?;
27+
28+
if let Some(binary_settings) = lsp_settings.binary {
29+
if let Some(path) = binary_settings.path {
30+
return Ok(LanguageServerBinary {
31+
path,
32+
args: binary_settings.arguments,
33+
});
34+
}
35+
}
36+
37+
let use_bundler = lsp_settings
38+
.settings
39+
.as_ref()
40+
.and_then(|settings| settings["use_bundler"].as_bool())
41+
.unwrap_or(Self::default_use_bundler());
42+
43+
if use_bundler {
44+
worktree
45+
.which("bundle")
46+
.map(|path| LanguageServerBinary {
47+
path,
48+
args: Some(
49+
[
50+
vec!["exec".to_string(), Self::EXECUTABLE_NAME.to_string()],
51+
Self::get_executable_args(),
52+
]
53+
.concat(),
54+
),
55+
})
56+
.ok_or_else(|| "Unable to find the 'bundle' command.".into())
57+
} else {
58+
worktree
59+
.which(Self::EXECUTABLE_NAME)
60+
.map(|path| LanguageServerBinary {
61+
path,
62+
args: Some(Self::get_executable_args()),
63+
})
64+
.ok_or_else(|| format!("Unable to find the '{}' command.", Self::EXECUTABLE_NAME))
65+
}
66+
}
67+
}
68+
69+
#[cfg(test)]
70+
mod tests {
71+
use super::*;
72+
73+
struct TestServer {}
74+
impl LanguageServer for TestServer {
75+
const SERVER_ID: &'static str = "test-server";
76+
const EXECUTABLE_NAME: &'static str = "test-exe";
77+
78+
fn get_executable_args() -> Vec<String> {
79+
vec!["--test-arg".into()]
80+
}
81+
}
82+
83+
#[test]
84+
fn test_default_use_bundler() {
85+
assert!(TestServer::default_use_bundler());
86+
}
87+
88+
#[test]
89+
fn test_default_executable_args() {
90+
assert!(TestServer::get_executable_args() == vec!["--test-arg"]);
91+
}
92+
}

src/language_servers/rubocop.rs

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
use zed_extension_api::{self as zed, settings::LspSettings, LanguageServerId, Result};
1+
use zed_extension_api::{self as zed, LanguageServerId, Result};
22

3-
#[derive(Clone, Debug)]
4-
pub struct RubocopBinary {
5-
pub path: String,
6-
pub args: Option<Vec<String>>,
7-
}
3+
use super::LanguageServer;
84

95
pub struct Rubocop {}
106

11-
impl Rubocop {
12-
pub const LANGUAGE_SERVER_ID: &str = "rubocop";
13-
pub const EXECUTABLE_NAME: &str = "rubocop";
7+
impl LanguageServer for Rubocop {
8+
const SERVER_ID: &str = "rubocop";
9+
const EXECUTABLE_NAME: &str = "rubocop";
10+
11+
fn get_executable_args() -> Vec<String> {
12+
vec!["--lsp".to_string()]
13+
}
14+
}
1415

16+
impl Rubocop {
1517
pub fn new() -> Self {
1618
Self {}
1719
}
@@ -25,58 +27,33 @@ impl Rubocop {
2527

2628
Ok(zed::Command {
2729
command: binary.path,
28-
args: binary.args.unwrap_or(vec!["--lsp".into()]),
30+
args: binary.args.unwrap_or(Self::get_executable_args()),
2931
env: Default::default(),
3032
})
3133
}
34+
}
3235

33-
fn language_server_binary(
34-
&self,
35-
language_server_id: &LanguageServerId,
36-
worktree: &zed::Worktree,
37-
) -> Result<RubocopBinary> {
38-
let lsp_settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree)?;
36+
#[cfg(test)]
37+
mod tests {
38+
use crate::language_servers::{LanguageServer, Rubocop};
3939

40-
let binary_settings = lsp_settings.binary;
41-
let binary_args = binary_settings
42-
.as_ref()
43-
.and_then(|binary_settings| binary_settings.arguments.clone());
40+
#[test]
41+
fn test_server_id() {
42+
assert_eq!(Rubocop::SERVER_ID, "rubocop");
43+
}
4444

45-
if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
46-
return Ok(RubocopBinary {
47-
path,
48-
args: binary_args,
49-
});
50-
}
45+
#[test]
46+
fn test_executable_name() {
47+
assert_eq!(Rubocop::EXECUTABLE_NAME, "rubocop");
48+
}
5149

52-
let use_bundler = lsp_settings
53-
.settings
54-
.as_ref()
55-
.and_then(|settings| settings["use_bundler"].as_bool())
56-
.unwrap_or(true);
50+
#[test]
51+
fn test_executable_args() {
52+
assert_eq!(Rubocop::get_executable_args(), vec!["--lsp"]);
53+
}
5754

58-
if use_bundler {
59-
worktree
60-
.which("bundle")
61-
.map(|path| RubocopBinary {
62-
path,
63-
args: Some(vec![
64-
"exec".into(),
65-
Rubocop::EXECUTABLE_NAME.into(),
66-
"--lsp".into(),
67-
]),
68-
})
69-
.ok_or_else(|| "Unable to find the 'bundle' command.".into())
70-
} else {
71-
worktree
72-
.which(Rubocop::EXECUTABLE_NAME)
73-
.map(|path| RubocopBinary {
74-
path,
75-
args: Some(vec!["--lsp".into()]),
76-
})
77-
.ok_or_else(|| {
78-
format!("Unable to find the '{}' command.", Rubocop::EXECUTABLE_NAME)
79-
})
80-
}
55+
#[test]
56+
fn test_default_use_bundler() {
57+
assert!(Rubocop::default_use_bundler());
8158
}
8259
}

src/language_servers/ruby_lsp.rs

Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
use zed_extension_api::{
22
self as zed,
33
lsp::{Completion, CompletionKind, Symbol, SymbolKind},
4-
settings::LspSettings,
54
CodeLabel, CodeLabelSpan, LanguageServerId, Result,
65
};
76

8-
pub struct RubyLspBinary {
9-
pub path: String,
10-
pub args: Option<Vec<String>>,
11-
}
7+
use super::LanguageServer;
128

139
pub struct RubyLsp {}
1410

15-
impl RubyLsp {
16-
pub const LANGUAGE_SERVER_ID: &str = "ruby-lsp";
17-
pub const EXECUTABLE_NAME: &str = "ruby-lsp";
11+
impl LanguageServer for RubyLsp {
12+
const SERVER_ID: &str = "ruby-lsp";
13+
const EXECUTABLE_NAME: &str = "ruby-lsp";
1814

15+
fn default_use_bundler() -> bool {
16+
false
17+
}
18+
}
19+
20+
impl RubyLsp {
1921
pub fn new() -> Self {
2022
Self {}
2123
}
@@ -29,57 +31,11 @@ impl RubyLsp {
2931

3032
Ok(zed::Command {
3133
command: binary.path,
32-
args: binary.args.unwrap_or(vec![]),
34+
args: binary.args.unwrap_or(Self::get_executable_args()),
3335
env: Default::default(),
3436
})
3537
}
3638

37-
fn language_server_binary(
38-
&self,
39-
language_server_id: &LanguageServerId,
40-
worktree: &zed::Worktree,
41-
) -> Result<RubyLspBinary> {
42-
let lsp_settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree)?;
43-
44-
let binary_settings = lsp_settings.binary;
45-
let binary_args = binary_settings
46-
.as_ref()
47-
.and_then(|binary_settings| binary_settings.arguments.clone());
48-
49-
if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
50-
return Ok(RubyLspBinary {
51-
path,
52-
args: binary_args,
53-
});
54-
}
55-
56-
let use_bundler = lsp_settings
57-
.settings
58-
.as_ref()
59-
.and_then(|settings| settings["use_bundler"].as_bool())
60-
.unwrap_or(false);
61-
62-
if use_bundler {
63-
worktree
64-
.which("bundle")
65-
.map(|path| RubyLspBinary {
66-
path,
67-
args: Some(vec!["exec".into(), RubyLsp::EXECUTABLE_NAME.into()]),
68-
})
69-
.ok_or_else(|| "Unable to find the 'bundle' command.".into())
70-
} else {
71-
worktree
72-
.which(RubyLsp::EXECUTABLE_NAME)
73-
.map(|path| RubyLspBinary {
74-
path,
75-
args: Some(vec![]),
76-
})
77-
.ok_or_else(|| {
78-
format!("Unable to find the '{}' command.", RubyLsp::EXECUTABLE_NAME)
79-
})
80-
}
81-
}
82-
8339
pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
8440
let highlight_name = match completion.kind? {
8541
CompletionKind::Class | CompletionKind::Module => "type",
@@ -152,3 +108,28 @@ impl RubyLsp {
152108
}
153109
}
154110
}
111+
112+
#[cfg(test)]
113+
mod tests {
114+
use crate::language_servers::{LanguageServer, RubyLsp};
115+
116+
#[test]
117+
fn test_server_id() {
118+
assert_eq!(RubyLsp::SERVER_ID, "ruby-lsp");
119+
}
120+
121+
#[test]
122+
fn test_executable_name() {
123+
assert_eq!(RubyLsp::EXECUTABLE_NAME, "ruby-lsp");
124+
}
125+
126+
#[test]
127+
fn test_executable_args() {
128+
assert_eq!(RubyLsp::get_executable_args(), vec![] as Vec<String>);
129+
}
130+
131+
#[test]
132+
fn test_default_use_bundler() {
133+
assert!(!RubyLsp::default_use_bundler());
134+
}
135+
}

0 commit comments

Comments
 (0)