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