From c543275cbbbafd66a5bdb5d00476263aa5b4909f Mon Sep 17 00:00:00 2001 From: Taylor Chaparro Date: Fri, 1 Nov 2024 08:30:06 -0700 Subject: [PATCH] feat: add sorbet Ruby LSP server option --- extension.toml | 4 + src/language_servers.rs | 2 + src/language_servers/sorbet.rs | 139 +++++++++++++++++++++++++++++++++ src/ruby.rs | 10 ++- 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/language_servers/sorbet.rs diff --git a/extension.toml b/extension.toml index 1f4053f..cfcc0a4 100644 --- a/extension.toml +++ b/extension.toml @@ -18,6 +18,10 @@ languages = ["Ruby", "ERB"] name = "Rubocop" languages = ["Ruby"] +[language_servers.sorbet] +name = "Sorbet" +languages = ["Ruby"] + [grammars.ruby] repository = "https://github.com/tree-sitter/tree-sitter-ruby" commit = "7dbc1e2d0e2d752577655881f73b4573f3fe85d4" diff --git a/src/language_servers.rs b/src/language_servers.rs index 3ad3de5..9be1875 100644 --- a/src/language_servers.rs +++ b/src/language_servers.rs @@ -1,7 +1,9 @@ mod rubocop; mod ruby_lsp; mod solargraph; +mod sorbet; pub use rubocop::*; pub use ruby_lsp::*; pub use solargraph::*; +pub use sorbet::*; diff --git a/src/language_servers/sorbet.rs b/src/language_servers/sorbet.rs new file mode 100644 index 0000000..b964ed3 --- /dev/null +++ b/src/language_servers/sorbet.rs @@ -0,0 +1,139 @@ +use zed_extension_api::{ + self as zed, + lsp::{Completion, CompletionKind, Symbol, SymbolKind}, + settings::LspSettings, + CodeLabel, CodeLabelSpan, LanguageServerId, Result, +}; + +pub struct SorbetBinary { + pub path: String, + pub args: Option>, +} + +pub struct Sorbet {} + +impl Sorbet { + pub const LANGUAGE_SERVER_ID: &'static str = "sorbet"; + + pub fn new() -> Self { + Self {} + } + + pub fn language_server_command( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + let binary = self.language_server_binary(language_server_id, worktree)?; + + Ok(zed::Command { + command: binary.path, + args: binary.args.unwrap_or_default(), + env: worktree.shell_env(), + }) + } + + fn language_server_binary( + &self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + let binary_settings = + LspSettings::for_worktree(language_server_id.to_string().as_str(), worktree) + .ok() + .and_then(|lsp_settings| lsp_settings.binary); + + let default_binary_args = Some( + vec![ + "tc", + "--lsp", + "--enable-experimental-lsp-document-highlight", + ] + .iter() + .map(|s| s.to_string()) + .collect(), + ); + + let binary_args = binary_settings + .as_ref() + .and_then(|binary_settings| binary_settings.arguments.clone()) + .or(default_binary_args); + + if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) { + return Ok(SorbetBinary { + path, + args: binary_args, + }); + } + + if let Some(path) = worktree.which("srb") { + return Ok(SorbetBinary { + path, + args: binary_args, + }); + } + + Err("srb gem must be installed manually. Install it with `gem install sorbet`.".to_string()) + } + + pub fn label_for_completion(&self, completion: Completion) -> Option { + let highlight_name = match completion.kind? { + CompletionKind::Class | CompletionKind::Module => "type", + CompletionKind::Constant => "constant", + CompletionKind::Method => "function.method", + CompletionKind::Reference => "function.method", + CompletionKind::Keyword => "keyword", + _ => return None, + }; + + let len = completion.label.len(); + let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string())); + + Some(CodeLabel { + code: Default::default(), + spans: vec![name_span], + filter_range: (0..len).into(), + }) + } + + pub fn label_for_symbol(&self, symbol: Symbol) -> Option { + let name = &symbol.name; + + match symbol.kind { + SymbolKind::Method => { + let code = format!("def {name}; end"); + let filter_range = 0..name.len(); + let display_range = 4..4 + name.len(); + + Some(CodeLabel { + code, + spans: vec![CodeLabelSpan::code_range(display_range)], + filter_range: filter_range.into(), + }) + } + SymbolKind::Class | SymbolKind::Module => { + let code = format!("class {name}; end"); + let filter_range = 0..name.len(); + let display_range = 6..6 + name.len(); + + Some(CodeLabel { + code, + spans: vec![CodeLabelSpan::code_range(display_range)], + filter_range: filter_range.into(), + }) + } + SymbolKind::Constant => { + let code = name.to_uppercase().to_string(); + let filter_range = 0..name.len(); + let display_range = 0..name.len(); + + Some(CodeLabel { + code, + spans: vec![CodeLabelSpan::code_range(display_range)], + filter_range: filter_range.into(), + }) + } + _ => None, + } + } +} diff --git a/src/ruby.rs b/src/ruby.rs index 1c8476e..0cef6ec 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -5,12 +5,13 @@ use zed::settings::LspSettings; use zed::{serde_json, CodeLabel, LanguageServerId}; use zed_extension_api::{self as zed, Result}; -use crate::language_servers::{Rubocop, RubyLsp, Solargraph}; +use crate::language_servers::{Rubocop, RubyLsp, Solargraph, Sorbet}; struct RubyExtension { solargraph: Option, ruby_lsp: Option, rubocop: Option, + sorbet: Option, } impl zed::Extension for RubyExtension { @@ -19,6 +20,7 @@ impl zed::Extension for RubyExtension { solargraph: None, ruby_lsp: None, rubocop: None, + sorbet: None, } } @@ -40,6 +42,10 @@ impl zed::Extension for RubyExtension { let rubocop = self.rubocop.get_or_insert_with(Rubocop::new); rubocop.language_server_command(language_server_id, worktree) } + Sorbet::LANGUAGE_SERVER_ID => { + let sorbet = self.sorbet.get_or_insert_with(Sorbet::new); + sorbet.language_server_command(language_server_id, worktree) + } language_server_id => Err(format!("unknown language server: {language_server_id}")), } } @@ -52,6 +58,7 @@ impl zed::Extension for RubyExtension { match language_server_id.as_ref() { Solargraph::LANGUAGE_SERVER_ID => self.solargraph.as_ref()?.label_for_symbol(symbol), RubyLsp::LANGUAGE_SERVER_ID => self.ruby_lsp.as_ref()?.label_for_symbol(symbol), + Sorbet::LANGUAGE_SERVER_ID => self.sorbet.as_ref()?.label_for_symbol(symbol), _ => None, } } @@ -66,6 +73,7 @@ impl zed::Extension for RubyExtension { self.solargraph.as_ref()?.label_for_completion(completion) } RubyLsp::LANGUAGE_SERVER_ID => self.ruby_lsp.as_ref()?.label_for_completion(completion), + Sorbet::LANGUAGE_SERVER_ID => self.sorbet.as_ref()?.label_for_completion(completion), _ => None, } }