Extract Ocaml language support into an extension (#10450)
Release Notes: - Extracted Ocaml language support into an extension --------- Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
f6c85b28d5
commit
8d7f5eab79
21 changed files with 289 additions and 343 deletions
219
extensions/ocaml/src/ocaml.rs
Normal file
219
extensions/ocaml/src/ocaml.rs
Normal file
|
@ -0,0 +1,219 @@
|
|||
use std::ops::Range;
|
||||
use zed::lsp::{Completion, CompletionKind, Symbol, SymbolKind};
|
||||
use zed::{CodeLabel, CodeLabelSpan};
|
||||
use zed_extension_api::{self as zed, Result};
|
||||
|
||||
const OPERATOR_CHAR: [char; 17] = [
|
||||
'~', '!', '?', '%', '<', ':', '.', '$', '&', '*', '+', '-', '/', '=', '>', '@', '^',
|
||||
];
|
||||
|
||||
struct OcamlExtension;
|
||||
|
||||
impl zed::Extension for OcamlExtension {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn language_server_command(
|
||||
&mut self,
|
||||
_language_server_id: &zed::LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<zed::Command> {
|
||||
let path = worktree.which("ocamllsp").ok_or_else(|| {
|
||||
"ocamllsp (ocaml-language-server) must be installed manually.".to_string()
|
||||
})?;
|
||||
|
||||
Ok(zed::Command {
|
||||
command: path,
|
||||
args: Vec::new(),
|
||||
env: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn label_for_completion(
|
||||
&self,
|
||||
_language_server_id: &zed::LanguageServerId,
|
||||
completion: Completion,
|
||||
) -> Option<CodeLabel> {
|
||||
let name = &completion.label;
|
||||
let detail = completion.detail.as_ref().map(|s| s.replace('\n', " "));
|
||||
|
||||
match completion.kind.zip(detail) {
|
||||
Some((CompletionKind::Constructor | CompletionKind::EnumMember, detail)) => {
|
||||
let (argument, return_t) = detail
|
||||
.split_once("->")
|
||||
.map_or((None, detail.as_str()), |(arg, typ)| {
|
||||
(Some(arg.trim()), typ.trim())
|
||||
});
|
||||
|
||||
let type_decl = "type t = ";
|
||||
let type_of = argument.map(|_| " of ").unwrap_or_default();
|
||||
let argument = argument.unwrap_or_default();
|
||||
let terminator = "\n";
|
||||
let let_decl = "let _ ";
|
||||
let let_colon = ": ";
|
||||
let let_suffix = " = ()";
|
||||
let code = format!(
|
||||
"{type_decl}{name}{type_of}{argument}{terminator}{let_decl}{let_colon}{return_t}{let_suffix}"
|
||||
);
|
||||
|
||||
let name_start = type_decl.len();
|
||||
let argument_end = name_start + name.len() + type_of.len() + argument.len();
|
||||
let colon_start = argument_end + terminator.len() + let_decl.len();
|
||||
let return_type_end = code.len() - let_suffix.len();
|
||||
Some(CodeLabel {
|
||||
code,
|
||||
spans: vec![
|
||||
CodeLabelSpan::code_range(name_start..argument_end),
|
||||
CodeLabelSpan::code_range(colon_start..return_type_end),
|
||||
],
|
||||
filter_range: (0..name.len()).into(),
|
||||
})
|
||||
}
|
||||
|
||||
Some((CompletionKind::Field, detail)) => {
|
||||
let filter_range_start = if name.starts_with(&['~', '?']) { 1 } else { 0 };
|
||||
|
||||
let record_prefix = "type t = { ";
|
||||
let record_suffix = "; }";
|
||||
let code = format!("{record_prefix}{name} : {detail}{record_suffix}");
|
||||
|
||||
Some(CodeLabel {
|
||||
spans: vec![CodeLabelSpan::code_range(
|
||||
record_prefix.len()..code.len() - record_suffix.len(),
|
||||
)],
|
||||
code,
|
||||
filter_range: (filter_range_start..name.len()).into(),
|
||||
})
|
||||
}
|
||||
|
||||
Some((CompletionKind::Value, detail)) => {
|
||||
let let_prefix = "let ";
|
||||
let suffix = " = ()";
|
||||
let (l_paren, r_paren) = if name.contains(OPERATOR_CHAR) {
|
||||
("( ", " )")
|
||||
} else {
|
||||
("", "")
|
||||
};
|
||||
let code = format!("{let_prefix}{l_paren}{name}{r_paren} : {detail}{suffix}");
|
||||
|
||||
let name_start = let_prefix.len() + l_paren.len();
|
||||
let name_end = name_start + name.len();
|
||||
let type_annotation_start = name_end + r_paren.len();
|
||||
let type_annotation_end = code.len() - suffix.len();
|
||||
|
||||
Some(CodeLabel {
|
||||
spans: vec![
|
||||
CodeLabelSpan::code_range(name_start..name_end),
|
||||
CodeLabelSpan::code_range(type_annotation_start..type_annotation_end),
|
||||
],
|
||||
filter_range: (0..name.len()).into(),
|
||||
code,
|
||||
})
|
||||
}
|
||||
|
||||
Some((CompletionKind::Method, detail)) => {
|
||||
let method_decl = "class c : object method ";
|
||||
let end = " end";
|
||||
let code = format!("{method_decl}{name} : {detail}{end}");
|
||||
|
||||
Some(CodeLabel {
|
||||
spans: vec![CodeLabelSpan::code_range(
|
||||
method_decl.len()..code.len() - end.len(),
|
||||
)],
|
||||
code,
|
||||
filter_range: (0..name.len()).into(),
|
||||
})
|
||||
}
|
||||
|
||||
Some((kind, _)) => {
|
||||
let highlight_name = match kind {
|
||||
CompletionKind::Module | CompletionKind::Interface => "title",
|
||||
CompletionKind::Keyword => "keyword",
|
||||
CompletionKind::TypeParameter => "type",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
spans: vec![(CodeLabelSpan::literal(name, Some(highlight_name.to_string())))],
|
||||
filter_range: (0..name.len()).into(),
|
||||
code: String::new(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn label_for_symbol(
|
||||
&self,
|
||||
_language_server_id: &zed::LanguageServerId,
|
||||
symbol: Symbol,
|
||||
) -> Option<CodeLabel> {
|
||||
let name = &symbol.name;
|
||||
|
||||
let (code, filter_range, display_range) = match symbol.kind {
|
||||
SymbolKind::Property => {
|
||||
let code = format!("type t = {{ {}: (); }}", name);
|
||||
let filter_range: Range<usize> = 0..name.len();
|
||||
let display_range = 11..11 + name.len();
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Function
|
||||
if name.contains(OPERATOR_CHAR)
|
||||
|| (name.starts_with("let") && name.contains(OPERATOR_CHAR)) =>
|
||||
{
|
||||
let code = format!("let ( {name} ) () = ()");
|
||||
|
||||
let filter_range = 6..6 + name.len();
|
||||
let display_range = 0..filter_range.end + 1;
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Function => {
|
||||
let code = format!("let {name} () = ()");
|
||||
|
||||
let filter_range = 4..4 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Constructor => {
|
||||
let code = format!("type t = {name}");
|
||||
let filter_range = 0..name.len();
|
||||
let display_range = 9..9 + name.len();
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Module => {
|
||||
let code = format!("module {name} = struct end");
|
||||
let filter_range = 7..7 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Class => {
|
||||
let code = format!("class {name} = object end");
|
||||
let filter_range = 6..6 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::Method => {
|
||||
let code = format!("class c = object method {name} = () end");
|
||||
let filter_range = 0..name.len();
|
||||
let display_range = 17..24 + name.len();
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::String => {
|
||||
let code = format!("type {name} = T");
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(code, filter_range, display_range)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
code,
|
||||
spans: vec![CodeLabelSpan::code_range(display_range)],
|
||||
filter_range: filter_range.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
zed::register_extension!(OcamlExtension);
|
Loading…
Add table
Add a link
Reference in a new issue