ocaml.rs

  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}