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:
1 parent
66086111b2
commit
2649194d44
21 files changed
+289
-343
No files matched your search
Generated
+7
-10
@@ -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"
|
||||
|
||||
+1
-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();
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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);
|
||||
Reference in new issue
Block a user