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
9 changes: 9 additions & 0 deletions crates/lsp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ pub enum LanguageId {
JavaScriptReact,
C,
Cpp,
Ruby,
}

impl LanguageId {
pub fn from_path(path: &Path) -> Option<Self> {
if let Some("Gemfile" | "Rakefile" | "Guardfile" | "Capfile") =
path.file_name().and_then(|n| n.to_str())
{
return Some(Self::Ruby);
}
let extn = path.extension()?;
match extn.to_str()? {
"rs" => Some(Self::Rust),
Expand All @@ -52,6 +58,7 @@ impl LanguageId {
// compile_commands.json is present, clangd will use the correct language
// regardless of the languageId we send.
"h" | "H" | "hh" | "hpp" | "hxx" => Some(Self::Cpp),
"rb" | "rbw" | "rake" | "gemspec" | "ru" => Some(Self::Ruby),
_ => None,
}
}
Expand All @@ -69,6 +76,7 @@ impl LanguageId {
LanguageId::JavaScriptReact => "javascriptreact",
LanguageId::C => "c",
LanguageId::Cpp => "cpp",
LanguageId::Ruby => "ruby",
}
}

Expand All @@ -83,6 +91,7 @@ impl LanguageId {
| LanguageId::JavaScript
| LanguageId::JavaScriptReact => LSPServerType::TypeScriptLanguageServer,
LanguageId::C | LanguageId::Cpp => LSPServerType::Clangd,
LanguageId::Ruby => LSPServerType::RubyLsp,
}
}
}
Expand Down
43 changes: 42 additions & 1 deletion crates/lsp/src/config_tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use lsp_types::Uri;

use crate::config::{lsp_uri_to_path, path_to_lsp_uri};
use crate::supported_servers::LSPServerType;
use crate::LanguageId;

// Unix-specific tests use Unix paths
#[cfg(not(windows))]
Expand Down Expand Up @@ -216,3 +218,42 @@ fn test_path_to_lsp_uri_rejects_relative_path() {
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("must be absolute"));
}

#[test]
fn test_ruby_language_id_from_extension() {
for ext in ["rb", "rbw", "rake", "gemspec", "ru"] {
let path = PathBuf::from(format!("foo.{ext}"));
assert_eq!(
LanguageId::from_path(&path),
Some(LanguageId::Ruby),
"extension .{ext} should map to Ruby"
);
}
}

#[test]
fn test_ruby_language_id_from_filename() {
for name in ["Gemfile", "Rakefile", "Guardfile", "Capfile"] {
let path = Path::new(name);
assert_eq!(
LanguageId::from_path(path),
Some(LanguageId::Ruby),
"filename {name} should map to Ruby"
);
}
}

#[test]
fn test_ruby_server_type() {
assert_eq!(LanguageId::Ruby.server_type(), LSPServerType::RubyLsp);
}

#[test]
fn test_ruby_lsp_binary_name() {
assert_eq!(LSPServerType::RubyLsp.binary_name(), "ruby-lsp");
}

#[test]
fn test_ruby_lsp_language_name() {
assert_eq!(LSPServerType::RubyLsp.language_name(), "Ruby");
}
1 change: 1 addition & 0 deletions crates/lsp/src/servers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod clangd;
pub mod go;
pub mod pyright;
pub mod ruby_lsp;
pub mod rust;
pub mod typescript_language_server;
94 changes: 94 additions & 0 deletions crates/lsp/src/servers/ruby_lsp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::path::Path;
use std::sync::Arc;

use crate::language_server_candidate::{LanguageServerCandidate, LanguageServerMetadata};
use crate::CommandBuilder;
use async_trait::async_trait;

#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
pub struct RubyLspCandidate {
#[allow(dead_code)]
client: Arc<http_client::Client>,
}

impl RubyLspCandidate {
pub fn new(client: Arc<http_client::Client>) -> Self {
Self { client }
}
}

#[async_trait]
#[cfg(feature = "local_fs")]
impl LanguageServerCandidate for RubyLspCandidate {
async fn should_suggest_for_repo(&self, path: &Path, _executor: &CommandBuilder) -> bool {
if path.join("Gemfile").exists()
|| path.join("Rakefile").exists()
|| path.join(".ruby-version").exists()
|| path.join("config.ru").exists()
{
return true;
}

std::fs::read_dir(path)
.map(|entries| {
entries.flatten().any(|entry| {
entry.path().extension().and_then(|s| s.to_str()) == Some("gemspec")
})
})
.unwrap_or(false)
}

async fn is_installed_in_data_dir(&self, _executor: &CommandBuilder) -> bool {
false
}

async fn is_installed_on_path(&self, executor: &CommandBuilder) -> bool {
executor
.command("ruby-lsp")
.arg("--version")
.output()
.await
.map(|o| o.status.success())
.unwrap_or(false)
}

async fn install(
&self,
_metadata: LanguageServerMetadata,
_executor: &CommandBuilder,
) -> anyhow::Result<()> {
anyhow::bail!("Install ruby-lsp manually: `gem install ruby-lsp`")
}

async fn fetch_latest_server_metadata(&self) -> anyhow::Result<LanguageServerMetadata> {
anyhow::bail!("Auto-install not supported; install via `gem install ruby-lsp`")
}
}

#[async_trait]
#[cfg(not(feature = "local_fs"))]
impl LanguageServerCandidate for RubyLspCandidate {
async fn should_suggest_for_repo(&self, _path: &Path, _executor: &CommandBuilder) -> bool {
false
}

async fn is_installed_in_data_dir(&self, _executor: &CommandBuilder) -> bool {
false
}

async fn is_installed_on_path(&self, _executor: &CommandBuilder) -> bool {
false
}

async fn install(
&self,
_metadata: LanguageServerMetadata,
_executor: &CommandBuilder,
) -> anyhow::Result<()> {
todo!()
}

async fn fetch_latest_server_metadata(&self) -> anyhow::Result<LanguageServerMetadata> {
todo!()
}
}
12 changes: 11 additions & 1 deletion crates/lsp/src/supported_servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::servers::clangd::ClangdCandidate;
use crate::servers::go::GoPlsCandidate;
use crate::servers::pyright::PyrightCandidate;
use crate::servers::ruby_lsp::RubyLspCandidate;
use crate::servers::rust::RustAnalyzerCandidate;
use crate::servers::typescript_language_server::TypeScriptLanguageServerCandidate;
#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -42,6 +43,7 @@ pub enum LSPServerType {
Pyright,
TypeScriptLanguageServer,
Clangd,
RubyLsp,
}

/// Provides server-specific configuration for each LSP server type.
Expand Down Expand Up @@ -109,6 +111,7 @@ impl LSPServerType {
binary_path: path,
prepend_args: vec![],
}),
LSPServerType::RubyLsp => None,
}
}

Expand All @@ -132,14 +135,18 @@ impl LSPServerType {
LSPServerType::Pyright => "pyright-langserver",
LSPServerType::TypeScriptLanguageServer => "typescript-language-server",
LSPServerType::Clangd => "clangd",
LSPServerType::RubyLsp => "ruby-lsp",
}
}

/// Arguments for running via system PATH.
#[cfg(not(target_arch = "wasm32"))]
fn args(&self) -> Vec<&'static str> {
match self {
LSPServerType::RustAnalyzer | LSPServerType::GoPls | LSPServerType::Clangd => vec![],
LSPServerType::RustAnalyzer
| LSPServerType::GoPls
| LSPServerType::Clangd
| LSPServerType::RubyLsp => vec![],
LSPServerType::Pyright | LSPServerType::TypeScriptLanguageServer => vec!["--stdio"],
}
}
Expand All @@ -154,6 +161,7 @@ impl LSPServerType {
LSPServerType::Pyright => vec!["--stdio"],
LSPServerType::TypeScriptLanguageServer => vec!["--stdio"],
LSPServerType::Clangd => vec![],
LSPServerType::RubyLsp => vec![],
}
}

Expand All @@ -172,6 +180,7 @@ impl LSPServerType {
]
}
LSPServerType::Clangd => vec![LanguageId::C, LanguageId::Cpp],
LSPServerType::RubyLsp => vec![LanguageId::Ruby],
}
}

Expand Down Expand Up @@ -205,6 +214,7 @@ impl LSPServerType {
Box::new(TypeScriptLanguageServerCandidate::new(client))
}
LSPServerType::Clangd => Box::new(ClangdCandidate::new(client)),
LSPServerType::RubyLsp => Box::new(RubyLspCandidate::new(client)),
}
}

Expand Down