solargraph.rs

  1use zed::lsp::{Completion, CompletionKind, Symbol, SymbolKind};
  2use zed::{CodeLabel, CodeLabelSpan};
  3use zed_extension_api::{self as zed, Result};
  4
  5pub struct Solargraph {}
  6
  7impl Solargraph {
  8    pub const LANGUAGE_SERVER_ID: &'static str = "solargraph";
  9
 10    pub fn new() -> Self {
 11        Self {}
 12    }
 13
 14    pub fn server_script_path(&mut self, worktree: &zed::Worktree) -> Result<String> {
 15        let path = worktree
 16            .which("solargraph")
 17            .ok_or_else(|| "solargraph must be installed manually".to_string())?;
 18
 19        Ok(path)
 20    }
 21
 22    pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
 23        let highlight_name = match completion.kind? {
 24            CompletionKind::Class | CompletionKind::Module => "type",
 25            CompletionKind::Constant => "constant",
 26            CompletionKind::Method => "function.method",
 27            CompletionKind::Keyword => {
 28                if completion.label.starts_with(':') {
 29                    "string.special.symbol"
 30                } else {
 31                    "keyword"
 32                }
 33            }
 34            CompletionKind::Variable => {
 35                if completion.label.starts_with('@') {
 36                    "property"
 37                } else {
 38                    return None;
 39                }
 40            }
 41            _ => return None,
 42        };
 43
 44        let len = completion.label.len();
 45        let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
 46
 47        Some(CodeLabel {
 48            code: Default::default(),
 49            spans: if let Some(detail) = completion.detail {
 50                vec![
 51                    name_span,
 52                    CodeLabelSpan::literal(" ", None),
 53                    CodeLabelSpan::literal(detail, None),
 54                ]
 55            } else {
 56                vec![name_span]
 57            },
 58            filter_range: (0..len).into(),
 59        })
 60    }
 61
 62    pub fn label_for_symbol(&self, symbol: Symbol) -> Option<CodeLabel> {
 63        let name = &symbol.name;
 64
 65        return match symbol.kind {
 66            SymbolKind::Method => {
 67                let mut parts = name.split('#');
 68                let container_name = parts.next()?;
 69                let method_name = parts.next()?;
 70
 71                if parts.next().is_some() {
 72                    return None;
 73                }
 74
 75                let filter_range = 0..name.len();
 76
 77                let spans = vec![
 78                    CodeLabelSpan::literal(container_name, Some("type".to_string())),
 79                    CodeLabelSpan::literal("#", None),
 80                    CodeLabelSpan::literal(method_name, Some("function.method".to_string())),
 81                ];
 82
 83                Some(CodeLabel {
 84                    code: name.to_string(),
 85                    spans,
 86                    filter_range: filter_range.into(),
 87                })
 88            }
 89            SymbolKind::Class | SymbolKind::Module => {
 90                let class = "class ";
 91                let code = format!("{class}{name}");
 92                let filter_range = 0..name.len();
 93                let display_range = class.len()..class.len() + name.len();
 94
 95                Some(CodeLabel {
 96                    code,
 97                    spans: vec![CodeLabelSpan::code_range(display_range)],
 98                    filter_range: filter_range.into(),
 99                })
100            }
101            SymbolKind::Constant => {
102                let code = name.to_uppercase().to_string();
103                let filter_range = 0..name.len();
104                let display_range = 0..name.len();
105
106                Some(CodeLabel {
107                    code,
108                    spans: vec![CodeLabelSpan::code_range(display_range)],
109                    filter_range: filter_range.into(),
110                })
111            }
112            _ => None,
113        };
114    }
115}