Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extension.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ languages = ["HTML+ERB"]
name = "Kanayago"
languages = ["Ruby"]

[language_servers.fuzzy-ruby-server]
name = "Fuzzy Ruby Server"
languages = ["Ruby"]

[grammars.ruby]
repository = "https://github.com/tree-sitter/tree-sitter-ruby"
commit = "71bd32fb7607035768799732addba884a37a6210"
Expand Down
150 changes: 150 additions & 0 deletions src/language_servers/fuzzy_ruby_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use super::language_server::LanguageServerBinary;
use super::LanguageServer;
use zed_extension_api::{self as zed};

pub struct FuzzyRubyServer {}

impl LanguageServer for FuzzyRubyServer {
const SERVER_ID: &str = "fuzzy-ruby-server";
const EXECUTABLE_NAME: &str = "fuzzy";
const GEM_NAME: &str = "fuzzy-ruby-server--not-a-gem";

fn language_server_binary(
&self,
language_server_id: &zed::LanguageServerId,
worktree: &zed::Worktree,
) -> zed::Result<LanguageServerBinary> {
self.resolve_binary(language_server_id.as_ref(), worktree)
}
}

impl FuzzyRubyServer {
pub fn new() -> Self {
Self {}
}

fn resolve_binary<T: super::language_server::WorktreeLike>(
&self,
server_id: &str,
worktree: &T,
) -> zed::Result<LanguageServerBinary> {
let lsp_settings = worktree.lsp_binary_settings(server_id)?;

if let Some(binary_settings) = lsp_settings {
if let Some(path) = binary_settings.path {
if !std::path::Path::new(&path).is_file() {
return Err(format!(
"fuzzy-ruby-server: configured binary path '{}' does not exist or is not a file. Update lsp.fuzzy-ruby-server.binary.path in your Zed settings.",
path
)
.into());
}
return Ok(LanguageServerBinary {
path,
args: binary_settings.arguments,
env: Some(worktree.shell_env()),
});
}
}

if let Some(path) = worktree.which(Self::EXECUTABLE_NAME) {
return Ok(LanguageServerBinary {
path,
args: Some(self.get_executable_args(worktree)),
env: Some(worktree.shell_env()),
});
}

Err("fuzzy not found. Install with: cargo install --git https://github.com/doompling/fuzzy_ruby_server".into())
}
}

#[cfg(test)]
mod tests {
use crate::language_servers::{
language_server::{FakeWorktree, LspBinarySettings},
FuzzyRubyServer, LanguageServer,
};

#[test]
fn test_server_id() {
assert_eq!(FuzzyRubyServer::SERVER_ID, "fuzzy-ruby-server");
}

#[test]
fn test_executable_name() {
assert_eq!(FuzzyRubyServer::EXECUTABLE_NAME, "fuzzy");
}

#[test]
fn test_executable_args() {
let server = FuzzyRubyServer::new();
let mock_worktree = FakeWorktree::new("/path/to/project".to_string());
assert_eq!(server.get_executable_args(&mock_worktree), Vec::<String>::new());
}

#[test]
fn test_language_server_binary_custom_path() {
let server = FuzzyRubyServer::new();
let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string());
// Use a path that actually exists on the system for the custom-path test
let real_path = std::env::current_exe()
.expect("could not get test binary path")
.to_string_lossy()
.to_string();
mock_worktree.add_lsp_binary_setting(
FuzzyRubyServer::SERVER_ID.to_string(),
Ok(Some(LspBinarySettings {
path: Some(real_path.clone()),
arguments: None,
})),
);
let result = server.resolve_binary(FuzzyRubyServer::SERVER_ID, &mock_worktree);
assert!(result.is_ok());
assert_eq!(result.unwrap().path, real_path);
}

#[test]
fn test_language_server_binary_custom_path_missing() {
let server = FuzzyRubyServer::new();
let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string());
mock_worktree.add_lsp_binary_setting(
FuzzyRubyServer::SERVER_ID.to_string(),
Ok(Some(LspBinarySettings {
path: Some("/nonexistent/fuzzy".to_string()),
arguments: None,
})),
);
let result = server.resolve_binary(FuzzyRubyServer::SERVER_ID, &mock_worktree);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.contains("does not exist or is not a file"),
"Error was: {err}"
);
}

#[test]
fn test_language_server_binary_path_lookup() {
let server = FuzzyRubyServer::new();
let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string());
mock_worktree.add_lsp_binary_setting(FuzzyRubyServer::SERVER_ID.to_string(), Ok(None));
mock_worktree.set_which("fuzzy".to_string(), Some("/usr/local/bin/fuzzy".to_string()));
let result = server.resolve_binary(FuzzyRubyServer::SERVER_ID, &mock_worktree);
assert!(result.is_ok());
assert_eq!(result.unwrap().path, "/usr/local/bin/fuzzy");
}

#[test]
fn test_language_server_binary_not_found() {
let server = FuzzyRubyServer::new();
let mut mock_worktree = FakeWorktree::new("/path/to/project".to_string());
mock_worktree.add_lsp_binary_setting(FuzzyRubyServer::SERVER_ID.to_string(), Ok(None));
mock_worktree.set_which("fuzzy".to_string(), None);
let result = server.resolve_binary(FuzzyRubyServer::SERVER_ID, &mock_worktree);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("fuzzy not found"), "Error was: {err}");
assert!(err.contains("cargo install"), "Error was: {err}");
}
}
15 changes: 15 additions & 0 deletions src/language_servers/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub trait WorktreeLike {
fn shell_env(&self) -> Vec<(String, String)>;
fn read_text_file(&self, path: &str) -> Result<String, String>;
fn lsp_binary_settings(&self, server_id: &str) -> Result<Option<LspBinarySettings>, String>;
fn which(&self, name: &str) -> Option<String>;
}

impl WorktreeLike for zed::Worktree {
Expand All @@ -54,6 +55,10 @@ impl WorktreeLike for zed::Worktree {
Err(e) => Err(e),
}
}

fn which(&self, name: &str) -> Option<String> {
zed::Worktree::which(self, name)
}
}

#[cfg(test)]
Expand All @@ -62,6 +67,7 @@ pub struct FakeWorktree {
shell_env: Vec<(String, String)>,
files: HashMap<String, Result<String, String>>,
lsp_binary_settings_map: HashMap<String, Result<Option<LspBinarySettings>, String>>,
which_map: HashMap<String, Option<String>>,
}

#[cfg(test)]
Expand All @@ -72,6 +78,7 @@ impl FakeWorktree {
shell_env: Vec::new(),
files: HashMap::new(),
lsp_binary_settings_map: HashMap::new(),
which_map: HashMap::new(),
}
}

Expand All @@ -86,6 +93,10 @@ impl FakeWorktree {
) {
self.lsp_binary_settings_map.insert(server_id, settings);
}

pub fn set_which(&mut self, name: String, result: Option<String>) {
self.which_map.insert(name, result);
}
}

#[cfg(test)]
Expand All @@ -111,6 +122,10 @@ impl WorktreeLike for FakeWorktree {
.cloned()
.unwrap_or(Ok(None))
}

fn which(&self, name: &str) -> Option<String> {
self.which_map.get(name).cloned().flatten()
}
}

pub trait LanguageServer {
Expand Down
2 changes: 2 additions & 0 deletions src/language_servers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod fuzzy_ruby_server;
mod herb;
mod kanayago;
mod language_server;
Expand All @@ -7,6 +8,7 @@ mod solargraph;
mod sorbet;
mod steep;

pub use fuzzy_ruby_server::FuzzyRubyServer;
pub use herb::Herb;
pub use kanayago::Kanayago;
pub use language_server::LanguageServer;
Expand Down
7 changes: 6 additions & 1 deletion src/ruby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bundler::Bundler;
use command_executor::RealCommandExecutor;
use gemset::{versioned_gem_home, Gemset};
use language_servers::{
Herb, Kanayago, LanguageServer, Rubocop, RubyLsp, Solargraph, Sorbet, Steep,
FuzzyRubyServer, Herb, Kanayago, LanguageServer, Rubocop, RubyLsp, Solargraph, Sorbet, Steep,
};
use serde::{Deserialize, Serialize};
use zed_extension_api::{
Expand All @@ -27,6 +27,7 @@ struct RubyExtension {
steep: Option<Steep>,
herb: Option<Herb>,
kanayago: Option<Kanayago>,
fuzzy_ruby_server: Option<FuzzyRubyServer>,
}

#[derive(Serialize, Deserialize)]
Expand Down Expand Up @@ -80,6 +81,10 @@ impl zed::Extension for RubyExtension {
let kanayago = self.kanayago.get_or_insert_with(Kanayago::new);
kanayago.language_server_command(language_server_id, worktree)
}
FuzzyRubyServer::SERVER_ID => {
let server = self.fuzzy_ruby_server.get_or_insert_with(FuzzyRubyServer::new);
server.language_server_command(language_server_id, worktree)
}
language_server_id => Err(format!("unknown language server: {language_server_id}")),
}
}
Expand Down