ruby_lsp.rs

  1use zed_extension_api::{
  2    self as zed,
  3    lsp::{Completion, CompletionKind, Symbol, SymbolKind},
  4    settings::LspSettings,
  5    CodeLabel, CodeLabelSpan, LanguageServerId, Result,
  6};
  7
  8pub struct RubyLspBinary {
  9    pub path: String,
 10    pub args: Option<Vec<String>>,
 11}
 12
 13pub struct RubyLsp {}
 14
 15impl RubyLsp {
 16    pub const LANGUAGE_SERVER_ID: &'static str = "ruby-lsp";
 17
 18    pub fn new() -> Self {
 19        Self {}
 20    }
 21
 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        }
 54
 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        )
 66    }
 67
 68    pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
 69        let highlight_name = match completion.kind? {
 70            CompletionKind::Class | CompletionKind::Module => "type",
 71            CompletionKind::Constant => "constant",
 72            CompletionKind::Method => "function.method",
 73            CompletionKind::Reference => "function.method",
 74            CompletionKind::Keyword => "keyword",
 75            _ => return None,
 76        };
 77
 78        let len = completion.label.len();
 79        let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
 80
 81        Some(CodeLabel {
 82            code: Default::default(),
 83            spans: vec![name_span],
 84            filter_range: (0..len).into(),
 85        })
 86    }
 87
 88    pub fn label_for_symbol(&self, symbol: Symbol) -> Option<CodeLabel> {
 89        let name = &symbol.name;
 90
 91        match symbol.kind {
 92            SymbolKind::Method => {
 93                let code = format!("def {name}; end");
 94                let filter_range = 0..name.len();
 95                let display_range = 4..4 + name.len();
 96
 97                Some(CodeLabel {
 98                    code,
 99                    spans: vec![CodeLabelSpan::code_range(display_range)],
100                    filter_range: filter_range.into(),
101                })
102            }
103            SymbolKind::Class | SymbolKind::Module => {
104                let code = format!("class {name}; end");
105                let filter_range = 0..name.len();
106                let display_range = 6..6 + name.len();
107
108                Some(CodeLabel {
109                    code,
110                    spans: vec![CodeLabelSpan::code_range(display_range)],
111                    filter_range: filter_range.into(),
112                })
113            }
114            SymbolKind::Constant => {
115                let code = name.to_uppercase().to_string();
116                let filter_range = 0..name.len();
117                let display_range = 0..name.len();
118
119                Some(CodeLabel {
120                    code,
121                    spans: vec![CodeLabelSpan::code_range(display_range)],
122                    filter_range: filter_range.into(),
123                })
124            }
125            _ => None,
126        }
127    }
128}