mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
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
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -5469,7 +5469,6 @@ dependencies = [
|
||||
"tree-sitter-json 0.20.0",
|
||||
"tree-sitter-markdown",
|
||||
"tree-sitter-nu",
|
||||
"tree-sitter-ocaml",
|
||||
"tree-sitter-proto",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-regex",
|
||||
@ -10492,15 +10491,6 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-ocaml"
|
||||
version = "0.20.4"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter-ocaml?rev=4abfdc1c7af2c6c77a370aee974627be1c285b3b#4abfdc1c7af2c6c77a370aee974627be1c285b3b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-proto"
|
||||
version = "0.0.2"
|
||||
@ -12658,6 +12648,13 @@ dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_ocaml"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_php"
|
||||
version = "0.0.1"
|
||||
|
@ -112,6 +112,7 @@ members = [
|
||||
"extensions/haskell",
|
||||
"extensions/html",
|
||||
"extensions/lua",
|
||||
"extensions/ocaml",
|
||||
"extensions/php",
|
||||
"extensions/prisma",
|
||||
"extensions/purescript",
|
||||
@ -329,7 +330,6 @@ tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc",
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "7dd29f9616822e5fc259f5b4ae6c4ded9a71a132" }
|
||||
tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" }
|
||||
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-regex = "0.20.0"
|
||||
|
@ -48,6 +48,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
|
||||
("lua", &["lua"]),
|
||||
("make", &["Makefile"]),
|
||||
("nix", &["nix"]),
|
||||
("ocaml", &["ml", "mli"]),
|
||||
("php", &["php"]),
|
||||
("prisma", &["prisma"]),
|
||||
("purescript", &["purs"]),
|
||||
|
@ -1,11 +1,14 @@
|
||||
use crate::language_settings::{AllLanguageSettingsContent, LanguageSettingsContent};
|
||||
use crate::{
|
||||
language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter,
|
||||
File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
|
||||
LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
||||
language_settings::{
|
||||
all_language_settings, AllLanguageSettingsContent, LanguageSettingsContent,
|
||||
},
|
||||
task_context::ContextProvider,
|
||||
CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
|
||||
LanguageServerName, LspAdapter, LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::{hash_map, HashMap};
|
||||
use futures::TryFutureExt;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
@ -454,11 +457,12 @@ impl LanguageRegistry {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn language_for_file_path(
|
||||
pub fn language_for_file_path<'a>(
|
||||
self: &Arc<Self>,
|
||||
path: &Path,
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
path: &'a Path,
|
||||
) -> impl Future<Output = Result<Arc<Language>>> + 'a {
|
||||
self.language_for_file_internal(path, None, None)
|
||||
.map_err(|error| error.context(format!("language for file path {}", path.display())))
|
||||
}
|
||||
|
||||
fn language_for_file_internal(
|
||||
|
@ -51,7 +51,6 @@ tree-sitter-jsdoc.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-ocaml.workspace = true
|
||||
tree-sitter-proto.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-regex.workspace = true
|
||||
|
@ -19,7 +19,6 @@ mod elixir;
|
||||
mod go;
|
||||
mod json;
|
||||
mod nu;
|
||||
mod ocaml;
|
||||
mod python;
|
||||
mod ruby;
|
||||
mod rust;
|
||||
@ -70,11 +69,6 @@ pub fn init(
|
||||
("json", tree_sitter_json::language()),
|
||||
("markdown", tree_sitter_markdown::language()),
|
||||
("nu", tree_sitter_nu::language()),
|
||||
("ocaml", tree_sitter_ocaml::language_ocaml()),
|
||||
(
|
||||
"ocaml_interface",
|
||||
tree_sitter_ocaml::language_ocaml_interface(),
|
||||
),
|
||||
("proto", tree_sitter_proto::language()),
|
||||
("python", tree_sitter_python::language()),
|
||||
("regex", tree_sitter_regex::language()),
|
||||
@ -278,8 +272,6 @@ pub fn init(
|
||||
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))]
|
||||
);
|
||||
language!("nu", vec![Arc::new(nu::NuLanguageServer {})]);
|
||||
language!("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]);
|
||||
language!("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]);
|
||||
language!(
|
||||
"vue",
|
||||
vec![
|
||||
|
@ -1,314 +0,0 @@
|
||||
use std::{any::Any, ops::Range, path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{CodeLabel, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
|
||||
use rope::Rope;
|
||||
|
||||
const OPERATOR_CHAR: [char; 17] = [
|
||||
'~', '!', '?', '%', '<', ':', '.', '$', '&', '*', '+', '-', '/', '=', '>', '@', '^',
|
||||
];
|
||||
|
||||
pub struct OCamlLspAdapter;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for OCamlLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("ocamllsp".into())
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"ocamllsp (ocaml-language-server) must be installed manually."
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "ocamllsp".into(),
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<language::Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let name = &completion.label;
|
||||
let detail = completion.detail.as_ref().map(|s| s.replace('\n', " "));
|
||||
|
||||
match completion.kind.zip(detail) {
|
||||
// Error of 'b : ('a, 'b) result
|
||||
// Stack_overflow : exn
|
||||
Some((CompletionItemKind::CONSTRUCTOR | CompletionItemKind::ENUM_MEMBER, detail)) => {
|
||||
let (argument, return_t) = detail
|
||||
.split_once("->")
|
||||
.map_or((None, detail.as_str()), |(arg, typ)| {
|
||||
(Some(arg.trim()), typ.trim())
|
||||
});
|
||||
|
||||
let constr_decl = argument.map_or(name.to_string(), |argument| {
|
||||
format!("{} of {}", name, argument)
|
||||
});
|
||||
|
||||
let constr_host = if return_t.ends_with("exn") {
|
||||
"exception "
|
||||
} else {
|
||||
"type t = "
|
||||
};
|
||||
|
||||
let source_host = Rope::from([constr_host, &constr_decl].join(" "));
|
||||
let mut source_highlight = {
|
||||
let constr_host_len = constr_host.len() + 1;
|
||||
|
||||
language.highlight_text(
|
||||
&source_host,
|
||||
Range {
|
||||
start: constr_host_len,
|
||||
end: constr_host_len + constr_decl.len(),
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let signature_host: Rope = Rope::from(format!("let _ : {} = ()", return_t));
|
||||
|
||||
// We include the ': ' in the range as we use it later
|
||||
let mut signature_highlight =
|
||||
language.highlight_text(&signature_host, 6..8 + return_t.len());
|
||||
|
||||
if let Some(last) = source_highlight.last() {
|
||||
let offset = last.0.end + 1;
|
||||
|
||||
signature_highlight.iter_mut().for_each(|(r, _)| {
|
||||
r.start += offset;
|
||||
r.end += offset;
|
||||
});
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
text: format!("{} : {}", constr_decl, return_t),
|
||||
runs: {
|
||||
source_highlight.append(&mut signature_highlight);
|
||||
source_highlight
|
||||
},
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
// version : string
|
||||
// NOTE: (~|?) are omitted as we don't use them in the fuzzy filtering
|
||||
Some((CompletionItemKind::FIELD, detail))
|
||||
if name.starts_with('~') || name.starts_with('?') =>
|
||||
{
|
||||
let label = name.trim_start_matches(&['~', '?']);
|
||||
let text = format!("{} : {}", label, detail);
|
||||
|
||||
let signature_host = Rope::from(format!("let _ : {} = ()", detail));
|
||||
let signature_highlight =
|
||||
&mut language.highlight_text(&signature_host, 6..8 + detail.len());
|
||||
|
||||
let offset = label.len() + 1;
|
||||
for (r, _) in signature_highlight.iter_mut() {
|
||||
r.start += offset;
|
||||
r.end += offset;
|
||||
}
|
||||
|
||||
let mut label_highlight = vec![(
|
||||
0..label.len(),
|
||||
language.grammar()?.highlight_id_for_name("property")?,
|
||||
)];
|
||||
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs: {
|
||||
label_highlight.append(signature_highlight);
|
||||
label_highlight
|
||||
},
|
||||
filter_range: 0..label.len(),
|
||||
})
|
||||
}
|
||||
// version: string;
|
||||
Some((CompletionItemKind::FIELD, detail)) => {
|
||||
let (_record_t, field_t) = detail.split_once("->")?;
|
||||
|
||||
let text = format!("{}: {};", name, field_t);
|
||||
let source_host: Rope = Rope::from(format!("type t = {{ {} }}", text));
|
||||
|
||||
let runs: Vec<(Range<usize>, language::HighlightId)> =
|
||||
language.highlight_text(&source_host, 11..11 + text.len());
|
||||
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
// let* : 'a t -> ('a -> 'b t) -> 'b t
|
||||
Some((CompletionItemKind::VALUE, detail))
|
||||
if name.contains(OPERATOR_CHAR)
|
||||
|| (name.starts_with("let") && name.contains(OPERATOR_CHAR)) =>
|
||||
{
|
||||
let text = format!("{} : {}", name, detail);
|
||||
|
||||
let source_host = Rope::from(format!("let ({}) : {} = ()", name, detail));
|
||||
let mut runs = language.highlight_text(&source_host, 5..6 + text.len());
|
||||
|
||||
if runs.len() > 1 {
|
||||
// ')'
|
||||
runs.remove(1);
|
||||
|
||||
for run in &mut runs[1..] {
|
||||
run.0.start -= 1;
|
||||
run.0.end -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
// version : Version.t list -> Version.t option Lwt.t
|
||||
Some((CompletionItemKind::VALUE, detail)) => {
|
||||
let text = format!("{} : {}", name, detail);
|
||||
|
||||
let source_host = Rope::from(format!("let {} = ()", text));
|
||||
let runs = language.highlight_text(&source_host, 4..4 + text.len());
|
||||
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
// status : string
|
||||
Some((CompletionItemKind::METHOD, detail)) => {
|
||||
let text = format!("{} : {}", name, detail);
|
||||
|
||||
let method_host = Rope::from(format!("class c : object method {} end", text));
|
||||
let runs = language.highlight_text(&method_host, 24..24 + text.len());
|
||||
|
||||
Some(CodeLabel {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
Some((kind, _)) => {
|
||||
let highlight_name = match kind {
|
||||
CompletionItemKind::MODULE | CompletionItemKind::INTERFACE => "title",
|
||||
CompletionItemKind::KEYWORD => "keyword",
|
||||
CompletionItemKind::TYPE_PARAMETER => "type",
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
text: name.clone(),
|
||||
runs: vec![(
|
||||
0..name.len(),
|
||||
language.grammar()?.highlight_id_for_name(highlight_name)?,
|
||||
)],
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
kind: SymbolKind,
|
||||
language: &Arc<language::Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let (text, filter_range, display_range) = match kind {
|
||||
SymbolKind::PROPERTY => {
|
||||
let text = format!("type t = {{ {}: (); }}", name);
|
||||
let filter_range: Range<usize> = 0..name.len();
|
||||
let display_range = 11..11 + name.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::FUNCTION
|
||||
if name.contains(OPERATOR_CHAR)
|
||||
|| (name.starts_with("let") && name.contains(OPERATOR_CHAR)) =>
|
||||
{
|
||||
let text = format!("let ({}) () = ()", name);
|
||||
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end + 1;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::FUNCTION => {
|
||||
let text = format!("let {} () = ()", name);
|
||||
|
||||
let filter_range = 4..4 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::CONSTRUCTOR => {
|
||||
let text = format!("type t = {}", name);
|
||||
let filter_range = 0..name.len();
|
||||
let display_range = 9..9 + name.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::MODULE => {
|
||||
let text = format!("module {} = struct end", name);
|
||||
let filter_range = 7..7 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::CLASS => {
|
||||
let text = format!("class {} = object end", name);
|
||||
let filter_range = 6..6 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::METHOD => {
|
||||
let text = format!("class c = object method {} = () end", name);
|
||||
let filter_range = 0..name.len();
|
||||
let display_range = 17..24 + name.len();
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::STRING => {
|
||||
let text = format!("type {} = T", name);
|
||||
let filter_range = 5..5 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
}
|
||||
}
|
@ -9904,18 +9904,29 @@ async fn populate_labels_for_symbols(
|
||||
) {
|
||||
let mut symbols_by_language = HashMap::<Option<Arc<Language>>, Vec<CoreSymbol>>::default();
|
||||
|
||||
let mut unknown_path = None;
|
||||
for symbol in symbols {
|
||||
let language = language_registry
|
||||
.language_for_file_path(&symbol.path.path)
|
||||
.await
|
||||
.log_err()
|
||||
.or_else(|| default_language.clone());
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
unknown_path.get_or_insert(symbol.path.path.clone());
|
||||
default_language.clone()
|
||||
});
|
||||
symbols_by_language
|
||||
.entry(language)
|
||||
.or_default()
|
||||
.push(symbol);
|
||||
}
|
||||
|
||||
if let Some(unknown_path) = unknown_path {
|
||||
log::info!(
|
||||
"no language found for symbol path {}",
|
||||
unknown_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let mut label_params = Vec::new();
|
||||
for (language, mut symbols) in symbols_by_language {
|
||||
label_params.clear();
|
||||
|
16
extensions/ocaml/Cargo.toml
Normal file
16
extensions/ocaml/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "zed_ocaml"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/ocaml.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.0.6"
|
21
extensions/ocaml/extension.toml
Normal file
21
extensions/ocaml/extension.toml
Normal file
@ -0,0 +1,21 @@
|
||||
id = "ocaml"
|
||||
name = "OCaml"
|
||||
description = "OCaml support."
|
||||
version = "0.0.1"
|
||||
schema_version = 1
|
||||
authors = ["Rashid Almheiri <69181766+huwaireb@users.noreply.github.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
[language_servers.ocamllsp]
|
||||
name = "ocamllsp"
|
||||
languages = ["OCaml", "OCaml Interface"]
|
||||
|
||||
[grammars.ocaml]
|
||||
repository = "https://github.com/tree-sitter/tree-sitter-ocaml"
|
||||
commit = "0b12614ded3ec7ed7ab7933a9ba4f695ba4c342e"
|
||||
path = "grammars/ocaml"
|
||||
|
||||
[grammars.ocaml_interface]
|
||||
repository = "https://github.com/tree-sitter/tree-sitter-ocaml"
|
||||
commit = "0b12614ded3ec7ed7ab7933a9ba4f695ba4c342e"
|
||||
path = "grammars/interface"
|
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…
Reference in New Issue
Block a user