1use zed::lsp::{Completion, CompletionKind, Symbol, SymbolKind};
2use zed::{CodeLabel, CodeLabelSpan};
3use zed_extension_api::settings::LspSettings;
4use zed_extension_api::{self as zed, LanguageServerId, Result};
5
6pub struct SolargraphBinary {
7 pub path: String,
8 pub args: Option<Vec<String>>,
9}
10
11pub struct Solargraph {}
12
13impl Solargraph {
14 pub const LANGUAGE_SERVER_ID: &'static str = "solargraph";
15
16 pub fn new() -> Self {
17 Self {}
18 }
19
20 pub fn language_server_command(
21 &mut self,
22 language_server_id: &LanguageServerId,
23 worktree: &zed::Worktree,
24 ) -> Result<zed::Command> {
25 let binary = self.language_server_binary(language_server_id, worktree)?;
26
27 Ok(zed::Command {
28 command: binary.path,
29 args: binary.args.unwrap_or_else(|| vec!["stdio".to_string()]),
30 env: worktree.shell_env(),
31 })
32 }
33
34 fn language_server_binary(
35 &self,
36 _language_server_id: &LanguageServerId,
37 worktree: &zed::Worktree,
38 ) -> Result<SolargraphBinary> {
39 let binary_settings = LspSettings::for_worktree("solargraph", worktree)
40 .ok()
41 .and_then(|lsp_settings| lsp_settings.binary);
42 let binary_args = binary_settings
43 .as_ref()
44 .and_then(|binary_settings| binary_settings.arguments.clone());
45
46 if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
47 return Ok(SolargraphBinary {
48 path,
49 args: binary_args,
50 });
51 }
52
53 if let Some(path) = worktree.which("solargraph") {
54 return Ok(SolargraphBinary {
55 path,
56 args: binary_args,
57 });
58 }
59
60 Err("solargraph must be installed manually".to_string())
61 }
62
63 pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
64 let highlight_name = match completion.kind? {
65 CompletionKind::Class | CompletionKind::Module => "type",
66 CompletionKind::Constant => "constant",
67 CompletionKind::Method => "function.method",
68 CompletionKind::Keyword => {
69 if completion.label.starts_with(':') {
70 "string.special.symbol"
71 } else {
72 "keyword"
73 }
74 }
75 CompletionKind::Variable => {
76 if completion.label.starts_with('@') {
77 "property"
78 } else {
79 return None;
80 }
81 }
82 _ => return None,
83 };
84
85 let len = completion.label.len();
86 let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
87
88 Some(CodeLabel {
89 code: Default::default(),
90 spans: if let Some(detail) = completion.detail {
91 vec![
92 name_span,
93 CodeLabelSpan::literal(" ", None),
94 CodeLabelSpan::literal(detail, None),
95 ]
96 } else {
97 vec![name_span]
98 },
99 filter_range: (0..len).into(),
100 })
101 }
102
103 pub fn label_for_symbol(&self, symbol: Symbol) -> Option<CodeLabel> {
104 let name = &symbol.name;
105
106 return match symbol.kind {
107 SymbolKind::Method => {
108 let mut parts = name.split('#');
109 let container_name = parts.next()?;
110 let method_name = parts.next()?;
111
112 if parts.next().is_some() {
113 return None;
114 }
115
116 let filter_range = 0..name.len();
117
118 let spans = vec![
119 CodeLabelSpan::literal(container_name, Some("type".to_string())),
120 CodeLabelSpan::literal("#", None),
121 CodeLabelSpan::literal(method_name, Some("function.method".to_string())),
122 ];
123
124 Some(CodeLabel {
125 code: name.to_string(),
126 spans,
127 filter_range: filter_range.into(),
128 })
129 }
130 SymbolKind::Class | SymbolKind::Module => {
131 let class = "class ";
132 let code = format!("{class}{name}");
133 let filter_range = 0..name.len();
134 let display_range = class.len()..class.len() + name.len();
135
136 Some(CodeLabel {
137 code,
138 spans: vec![CodeLabelSpan::code_range(display_range)],
139 filter_range: filter_range.into(),
140 })
141 }
142 SymbolKind::Constant => {
143 let code = name.to_uppercase().to_string();
144 let filter_range = 0..name.len();
145 let display_range = 0..name.len();
146
147 Some(CodeLabel {
148 code,
149 spans: vec![CodeLabelSpan::code_range(display_range)],
150 filter_range: filter_range.into(),
151 })
152 }
153 _ => None,
154 };
155 }
156}