1use std::{any::Any, ops::Range, path::PathBuf, sync::Arc};
2
3use anyhow::{anyhow, Result};
4use async_trait::async_trait;
5use language::{CodeLabel, LanguageServerName, LspAdapter, LspAdapterDelegate};
6use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
7use rope::Rope;
8
9const OPERATOR_CHAR: [char; 17] = [
10 '~', '!', '?', '%', '<', ':', '.', '$', '&', '*', '+', '-', '/', '=', '>', '@', '^',
11];
12
13pub struct OCamlLspAdapter;
14
15#[async_trait]
16impl LspAdapter for OCamlLspAdapter {
17 fn name(&self) -> LanguageServerName {
18 LanguageServerName("ocamllsp".into())
19 }
20
21 fn short_name(&self) -> &'static str {
22 "ocaml"
23 }
24
25 async fn fetch_latest_server_version(
26 &self,
27 _: &dyn LspAdapterDelegate,
28 ) -> Result<Box<dyn 'static + Send + Any>> {
29 Ok(Box::new(()))
30 }
31
32 async fn fetch_server_binary(
33 &self,
34 _: Box<dyn 'static + Send + Any>,
35 _: PathBuf,
36 _: &dyn LspAdapterDelegate,
37 ) -> Result<LanguageServerBinary> {
38 Err(anyhow!(
39 "ocamllsp (ocaml-language-server) must be installed manually."
40 ))
41 }
42
43 async fn cached_server_binary(
44 &self,
45 _: PathBuf,
46 _: &dyn LspAdapterDelegate,
47 ) -> Option<LanguageServerBinary> {
48 Some(LanguageServerBinary {
49 path: "ocamllsp".into(),
50 env: None,
51 arguments: vec![],
52 })
53 }
54
55 fn can_be_reinstalled(&self) -> bool {
56 false
57 }
58
59 async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
60 None
61 }
62
63 async fn label_for_completion(
64 &self,
65 completion: &lsp::CompletionItem,
66 language: &Arc<language::Language>,
67 ) -> Option<CodeLabel> {
68 let name = &completion.label;
69 let detail = completion.detail.as_ref().map(|s| s.replace("\n", " "));
70
71 match completion.kind.zip(detail) {
72 // Error of 'b : ('a, 'b) result
73 // Stack_overflow : exn
74 Some((CompletionItemKind::CONSTRUCTOR | CompletionItemKind::ENUM_MEMBER, detail)) => {
75 let (argument, return_t) = detail
76 .split_once("->")
77 .map_or((None, detail.as_str()), |(arg, typ)| {
78 (Some(arg.trim()), typ.trim())
79 });
80
81 let constr_decl = argument.map_or(name.to_string(), |argument| {
82 format!("{} of {}", name, argument)
83 });
84
85 let constr_host = if return_t.ends_with("exn") {
86 "exception "
87 } else {
88 "type t = "
89 };
90
91 let source_host = Rope::from([constr_host, &constr_decl].join(" "));
92 let mut source_highlight = {
93 let constr_host_len = constr_host.len() + 1;
94
95 language.highlight_text(
96 &source_host,
97 Range {
98 start: constr_host_len,
99 end: constr_host_len + constr_decl.len(),
100 },
101 )
102 };
103
104 let signature_host: Rope = Rope::from(format!("let _ : {} = ()", return_t));
105
106 // We include the ': ' in the range as we use it later
107 let mut signature_highlight =
108 language.highlight_text(&signature_host, 6..8 + return_t.len());
109
110 if let Some(last) = source_highlight.last() {
111 let offset = last.0.end + 1;
112
113 signature_highlight.iter_mut().for_each(|(r, _)| {
114 r.start += offset;
115 r.end += offset;
116 });
117 };
118
119 Some(CodeLabel {
120 text: format!("{} : {}", constr_decl, return_t),
121 runs: {
122 source_highlight.append(&mut signature_highlight);
123 source_highlight
124 },
125 filter_range: 0..name.len(),
126 })
127 }
128 // version : string
129 // NOTE: (~|?) are omitted as we don't use them in the fuzzy filtering
130 Some((CompletionItemKind::FIELD, detail))
131 if name.starts_with("~") || name.starts_with("?") =>
132 {
133 let label = name.trim_start_matches(&['~', '?']);
134 let text = format!("{} : {}", label, detail);
135
136 let signature_host = Rope::from(format!("let _ : {} = ()", detail));
137 let signature_highlight =
138 &mut language.highlight_text(&signature_host, 6..8 + detail.len());
139
140 let offset = label.len() + 1;
141 for (r, _) in signature_highlight.iter_mut() {
142 r.start += offset;
143 r.end += offset;
144 }
145
146 let mut label_highlight = vec![(
147 0..0 + label.len(),
148 language.grammar()?.highlight_id_for_name("property")?,
149 )];
150
151 Some(CodeLabel {
152 text,
153 runs: {
154 label_highlight.append(signature_highlight);
155 label_highlight
156 },
157 filter_range: 0..label.len(),
158 })
159 }
160 // version: string;
161 Some((CompletionItemKind::FIELD, detail)) => {
162 let (_record_t, field_t) = detail.split_once("->")?;
163
164 let text = format!("{}: {};", name, field_t);
165 let source_host: Rope = Rope::from(format!("type t = {{ {} }}", text));
166
167 let runs: Vec<(Range<usize>, language::HighlightId)> =
168 language.highlight_text(&source_host, 11..11 + text.len());
169
170 Some(CodeLabel {
171 text,
172 runs,
173 filter_range: 0..name.len(),
174 })
175 }
176 // let* : 'a t -> ('a -> 'b t) -> 'b t
177 Some((CompletionItemKind::VALUE, detail))
178 if name.contains(OPERATOR_CHAR)
179 || (name.starts_with("let") && name.contains(OPERATOR_CHAR)) =>
180 {
181 let text = format!("{} : {}", name, detail);
182
183 let source_host = Rope::from(format!("let ({}) : {} = ()", name, detail));
184 let mut runs = language.highlight_text(&source_host, 5..6 + text.len());
185
186 if runs.len() > 1 {
187 // ')'
188 runs.remove(1);
189
190 for run in &mut runs[1..] {
191 run.0.start -= 1;
192 run.0.end -= 1;
193 }
194 }
195
196 Some(CodeLabel {
197 text,
198 runs,
199 filter_range: 0..name.len(),
200 })
201 }
202 // version : Version.t list -> Version.t option Lwt.t
203 Some((CompletionItemKind::VALUE, detail)) => {
204 let text = format!("{} : {}", name, detail);
205
206 let source_host = Rope::from(format!("let {} = ()", text));
207 let runs = language.highlight_text(&source_host, 4..4 + text.len());
208
209 Some(CodeLabel {
210 text,
211 runs,
212 filter_range: 0..name.len(),
213 })
214 }
215 // status : string
216 Some((CompletionItemKind::METHOD, detail)) => {
217 let text = format!("{} : {}", name, detail);
218
219 let method_host = Rope::from(format!("class c : object method {} end", text));
220 let runs = language.highlight_text(&method_host, 24..24 + text.len());
221
222 Some(CodeLabel {
223 text,
224 runs,
225 filter_range: 0..name.len(),
226 })
227 }
228 Some((kind, _)) => {
229 let highlight_name = match kind {
230 CompletionItemKind::MODULE | CompletionItemKind::INTERFACE => "title",
231 CompletionItemKind::KEYWORD => "keyword",
232 CompletionItemKind::TYPE_PARAMETER => "type",
233 _ => return None,
234 };
235
236 Some(CodeLabel {
237 text: name.clone(),
238 runs: vec![(
239 0..name.len(),
240 language.grammar()?.highlight_id_for_name(highlight_name)?,
241 )],
242 filter_range: 0..name.len(),
243 })
244 }
245 _ => None,
246 }
247 }
248
249 async fn label_for_symbol(
250 &self,
251 name: &str,
252 kind: SymbolKind,
253 language: &Arc<language::Language>,
254 ) -> Option<CodeLabel> {
255 let (text, filter_range, display_range) = match kind {
256 SymbolKind::PROPERTY => {
257 let text = format!("type t = {{ {}: (); }}", name);
258 let filter_range: Range<usize> = 0..name.len();
259 let display_range = 11..11 + name.len();
260 (text, filter_range, display_range)
261 }
262 SymbolKind::FUNCTION
263 if name.contains(OPERATOR_CHAR)
264 || (name.starts_with("let") && name.contains(OPERATOR_CHAR)) =>
265 {
266 let text = format!("let ({}) () = ()", name);
267
268 let filter_range = 5..5 + name.len();
269 let display_range = 0..filter_range.end + 1;
270 (text, filter_range, display_range)
271 }
272 SymbolKind::FUNCTION => {
273 let text = format!("let {} () = ()", name);
274
275 let filter_range = 4..4 + name.len();
276 let display_range = 0..filter_range.end;
277 (text, filter_range, display_range)
278 }
279 SymbolKind::CONSTRUCTOR => {
280 let text = format!("type t = {}", name);
281 let filter_range = 0..name.len();
282 let display_range = 9..9 + name.len();
283 (text, filter_range, display_range)
284 }
285 SymbolKind::MODULE => {
286 let text = format!("module {} = struct end", name);
287 let filter_range = 7..7 + name.len();
288 let display_range = 0..filter_range.end;
289 (text, filter_range, display_range)
290 }
291 SymbolKind::CLASS => {
292 let text = format!("class {} = object end", name);
293 let filter_range = 6..6 + name.len();
294 let display_range = 0..filter_range.end;
295 (text, filter_range, display_range)
296 }
297 SymbolKind::METHOD => {
298 let text = format!("class c = object method {} = () end", name);
299 let filter_range = 0..name.len();
300 let display_range = 17..24 + name.len();
301 (text, filter_range, display_range)
302 }
303 SymbolKind::STRING => {
304 let text = format!("type {} = T", name);
305 let filter_range = 5..5 + name.len();
306 let display_range = 0..filter_range.end;
307 (text, filter_range, display_range)
308 }
309 _ => return None,
310 };
311
312 Some(CodeLabel {
313 runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
314 text: text[display_range].to_string(),
315 filter_range,
316 })
317 }
318}