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(?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}