Add OCaml support (#6929)

This pull request implements support for the [OCaml
Language](https://ocaml.org/).

### Additions
- [x]
[tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml)
grammar
- [x] Highlight, Indents, Outline queries
- [x] A new file icon for .ml(i) files. Based on
[ocaml/ocaml-logo](https://github.com/ocaml/ocaml-logo/blob/master/Colour/SVG/colour-transparent-icon.svg)
- [x] LSP Integration with
[ocaml-language-server](https://github.com/ocaml/ocaml-lsp)
- [x] Completion Labels
- [x] Symbol Labels

### Bug Fixes
- [x] Improper parsing of LSP headers. 

### Missing [will file a separate issue]
- Documentation on completionItem, requires: `completionItem/resolve`
with support for `documentation` as a provider.

### Screenshots

<details><summary>Zed</summary>
<img width="1800" alt="Screenshot 2024-02-01 at 03 33 20"
src="https://github.com/zed-industries/zed/assets/69181766/e17c184e-203e-40c3-a08f-4de46226b79c">
</details>

Release Notes:
- Added OCaml Support
([#5316](https://github.com/zed-industries/zed/issues/5316)).

> [!NOTE]
> Partially completes #5316 
> To complete #5316:
> 1. addition of a reason tree-sitter grammar.
> 2. opam/esy integration, however it may be better as it's own plugin.
This commit is contained in:
Rashid Almheiri 2024-02-02 18:58:07 +04:00 committed by GitHub
parent 980d4f1003
commit 998f6cf80d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 784 additions and 9 deletions

10
Cargo.lock generated
View File

@ -8960,6 +8960,15 @@ 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-php"
version = "0.21.1"
@ -10418,6 +10427,7 @@ dependencies = [
"tree-sitter-markdown",
"tree-sitter-nix",
"tree-sitter-nu",
"tree-sitter-ocaml",
"tree-sitter-php",
"tree-sitter-proto",
"tree-sitter-purescript",

View File

@ -100,11 +100,14 @@ ctor = "0.2.6"
derive_more = "0.99.17"
env_logger = "0.9"
futures = "0.3"
git2 = { version = "0.15", default-features = false}
git2 = { version = "0.15", default-features = false }
globset = "0.4"
indoc = "1"
# We explicitly disable a http2 support in isahc.
isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
isahc = { version = "1.7.2", default-features = false, features = [
"static-curl",
"text-decoding",
] }
lazy_static = "1.4.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = "2.1.1"
@ -122,7 +125,10 @@ schemars = "0.8"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.1", features = [
"preserve_order",
"raw_value",
] }
serde_repr = "0.1"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
@ -137,7 +143,7 @@ tree-sitter = { version = "0.20", features = ["wasm"] }
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
tree-sitter-c = "0.20.1"
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
@ -157,8 +163,9 @@ tree-sitter-lua = "0.0.14"
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa" }
tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" }
tree-sitter-php = "0.21.1"
tree-sitter-proto = {git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" }
tree-sitter-python = "0.20.2"
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }

View File

@ -69,6 +69,8 @@
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"ml": "ocaml",
"mli": "ocaml",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
@ -179,6 +181,9 @@
"log": {
"icon": "icons/file_icons/info.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},

View File

@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.73843 11.709C6.70584 11.6334 6.60683 11.4367 6.55712 11.3736C6.44917 11.2362 6.42396 11.2258 6.39221 11.0523C6.33703 10.7501 6.19094 10.202 6.01879 9.82381C5.92987 9.62863 5.78201 9.46467 5.64665 9.32312C5.52847 9.19895 5.26214 8.99002 5.2157 9.00037C4.7807 9.09487 4.64576 9.55902 4.44115 9.92673C4.32795 10.1301 4.208 10.3031 4.1188 10.5195C4.03641 10.7184 4.04373 10.9387 3.90268 11.1095C3.75801 11.2849 3.66398 11.4715 3.59311 11.6981C3.57968 11.7412 3.54148 12.1939 3.5 12.3006L4.14649 12.2511C4.74896 12.2958 4.57496 12.547 5.51526 12.4922L7 12.4423C6.95398 12.2942 6.89056 12.1228 6.86613 12.067C6.82472 11.9732 6.77267 11.7897 6.73843 11.709Z" fill="black"/>
<path d="M8.72454 8.42043C8.61775 8.50889 8.40904 8.72165 7.95506 8.8021C7.75133 8.83825 7.56076 8.84122 7.35158 8.82925C7.24918 8.8236 7.15263 8.81758 7.04997 8.81605C6.9895 8.81552 6.78663 8.80812 6.79667 8.83039L6.77408 8.89506C6.7776 8.91633 6.78497 8.96949 6.78703 8.98237C6.79534 9.03461 6.79766 9.07617 6.79939 9.12421C6.80252 9.22297 6.79228 9.32592 6.79667 9.42559C6.80577 9.63232 6.87262 9.82076 6.88106 10.0293C6.89029 10.2615 6.99037 10.5072 7.08718 10.6969C7.12393 10.7691 7.17988 10.7773 7.20426 10.8663C7.23284 10.9681 7.20579 11.0762 7.21968 11.1848C7.27417 11.6058 7.37982 12.0459 7.54501 12.4259C7.54621 12.4291 7.54747 12.4325 7.54893 12.4354C7.75293 12.3961 7.95732 12.3119 8.22239 12.2669C8.70839 12.1841 9.3843 12.2268 9.81848 12.1801C10.9171 12.0616 11.5133 12.6972 12.5 12.4367V3.09052C12.5 2.21217 11.8798 1.5 11.1142 1.5H2.88578C2.1205 1.5 1.5 2.21217 1.5 3.09052V6.5608C1.69828 6.47851 1.98348 5.99435 2.07285 5.87661C2.2292 5.67071 2.25758 5.40808 2.33546 5.24267C2.51281 4.86596 2.54331 4.60691 2.94645 4.60691C3.13436 4.60691 3.20899 4.65663 3.3361 4.85238C3.42454 4.98851 3.57731 5.24 3.64881 5.40815C3.73134 5.60215 3.86583 5.86464 3.92497 5.91763C3.96876 5.95698 4.01221 5.9865 4.05275 6.00396C4.11813 6.0321 4.17222 5.98047 4.21595 5.94051C4.27176 5.8895 4.29582 5.78556 4.34751 5.64692C4.422 5.44689 4.5032 5.20721 4.54938 5.12356C4.62932 4.97897 4.65656 4.80739 4.74288 4.72427C4.8702 4.60172 5.03632 4.59311 5.08203 4.58274C5.33779 4.52478 5.45408 4.72419 5.58006 4.85315C5.66253 4.93764 5.77522 5.10785 5.85523 5.33594C5.91776 5.51408 5.99736 5.67887 6.03065 5.78174C6.06281 5.88103 6.14222 6.04018 6.18926 6.23098C6.23199 6.40424 6.34635 6.537 6.3898 6.61936C6.3898 6.61936 6.45632 6.83319 6.86079 7.02864C6.9485 7.07104 7.12579 7.13998 7.23157 7.18413C7.40733 7.25741 7.57757 7.24788 7.79432 7.21807C7.94888 7.21807 8.03261 6.96123 8.10284 6.75556C8.14437 6.634 8.18418 6.28566 8.21129 6.18675C8.23754 6.09051 8.17614 6.01608 8.22843 5.93174C8.28956 5.83329 8.32591 5.82796 8.3612 5.69961C8.43701 5.42478 8.87524 5.4109 9.12156 5.4109C9.32689 5.4109 9.30078 5.63967 9.6491 5.56143C9.84858 5.51652 10.0408 5.59094 10.2526 5.65531C10.4309 5.7096 10.5986 5.77145 10.699 5.90642C10.764 5.99382 10.9254 6.43169 10.761 6.45037C10.7768 6.47257 10.7884 6.5126 10.8179 6.53456C10.7813 6.69974 10.622 6.58207 10.5335 6.56087C10.4142 6.5325 10.33 6.56514 10.2133 6.6244C10.0138 6.72635 9.72206 6.71446 9.5483 6.88055C9.40085 7.02132 9.40111 7.33558 9.33234 7.51166C9.33234 7.51166 9.14137 8.07551 8.72454 8.42043Z" fill="black"/>
<path d="M3.6514 8.80413C3.57333 8.79137 3.50083 8.77702 3.425 8.75238C3.28339 8.70644 3.12867 8.66165 2.9892 8.60788C2.90451 8.57488 2.62239 8.41409 2.5611 8.36877C2.41737 8.26211 2.32191 7.97231 2.20955 8.00214C2.13782 8.02098 2.06795 8.06058 2.02333 8.17701C1.98692 8.27196 1.97457 8.43521 1.94936 8.54469C1.92011 8.67177 1.86959 8.7904 1.82536 8.91149C1.74401 9.13362 1.59759 9.33453 1.5345 9.55093C1.52181 9.59546 1.51055 9.64527 1.5 9.6972V10.5274V11.9637V12.1713C1.57359 12.1915 1.65057 12.2164 1.73674 12.2535C2.37264 12.5265 2.52781 12.5497 3.15152 12.4348L3.21002 12.4223C3.25775 12.2625 3.2946 11.718 3.32555 11.5494C3.34966 11.4202 3.38279 11.3173 3.39537 11.1853C3.40723 11.06 3.39427 10.9406 3.3876 10.8267C3.37011 10.5414 3.51669 10.4395 3.58667 10.1945C3.64976 9.97283 3.68617 9.7206 3.73839 9.49399C3.78847 9.27653 3.86665 8.96922 4 8.85975C3.98382 8.82938 3.72144 8.81539 3.6514 8.80413Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -332,11 +332,36 @@ impl LanguageServer {
};
let header = std::str::from_utf8(&buffer)?;
let message_len: usize = header
let mut segments = header.lines();
let message_len: usize = segments
.next()
.context("unable to find the first line of the LSP message header")?
.strip_prefix(CONTENT_LEN_HEADER)
.ok_or_else(|| anyhow!("invalid LSP message header {header:?}"))?
.trim_end()
.parse()?;
.context("invalid LSP message header")?
.parse()
.with_context(|| {
format!(
"failed to parse Content-Length of LSP message header: {}",
header
)
})?;
if let Some(second_segment) = segments.next() {
match second_segment {
"" => (), // Header end
header_field if header_field.starts_with("Content-Type:") => {
stdout.read_until(b'\n', &mut buffer).await?;
}
_ => {
anyhow::bail!(
"expected a Content-Type header field or a header ending CRLF, got {second_segment:?}"
);
}
}
} else {
anyhow::bail!("unable to find the second line of the LSP message header");
}
buffer.resize(message_len, 0);
stdout.read_exact(&mut buffer).await?;

View File

@ -130,6 +130,7 @@ tree-sitter-lua.workspace = true
tree-sitter-markdown.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-ocaml.workspace = true
tree-sitter-php.workspace = true
tree-sitter-proto.workspace = true
tree-sitter-purescript.workspace = true

View File

@ -25,6 +25,7 @@ mod json;
mod language_plugin;
mod lua;
mod nu;
mod ocaml;
mod php;
mod purescript;
mod python;
@ -299,6 +300,16 @@ pub fn init(
tree_sitter_nu::language(),
vec![Arc::new(nu::NuLanguageServer {})],
);
language(
"ocaml",
tree_sitter_ocaml::language_ocaml(),
vec![Arc::new(ocaml::OCamlLspAdapter)],
);
language(
"ocaml-interface",
tree_sitter_ocaml::language_ocaml_interface(),
vec![Arc::new(ocaml::OCamlLspAdapter)],
);
language(
"vue",
tree_sitter_vue::language(),

View File

@ -0,0 +1,6 @@
("(" @open ")" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("sig" @open "end" @close)
("object" @open "end" @close)

View File

@ -0,0 +1,13 @@
name = "OCaml Interface"
path_suffixes = ["mli"]
block_comment = ["(* ", "*)"]
autoclose_before = ";,=)}"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "<", end = ">", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "sig", end = " end", close = true, newline = true },
# HACK: For some reason `object` alone does not work
{ start = "object ", end = "end", close = true, newline = true },
]

View File

@ -0,0 +1 @@
../ocaml/highlights.scm

View File

@ -0,0 +1,21 @@
[
(type_binding)
(value_specification)
(method_specification)
(external)
(field_declaration)
] @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
(_ "object" @start "end" @end) @indent
(signature
"sig" @start
"end" @end) @indent
";;" @outdent

View File

@ -0,0 +1,48 @@
(module_type_definition
"module" @context
"type" @context
name: (_) @name) @item
(module_definition
"module" @context
(module_binding name: (_) @name)) @item
(type_definition
"type" @context
(type_binding name: (_) @name)) @item
(class_definition
"class" @context
(class_binding
"virtual"? @context
name: (_) @name)) @item
(class_type_definition
"class" @context
"type" @context
(class_type_binding
"virtual"? @context
name: (_) @name)) @item
(instance_variable_definition
"val" @context
"method"? @context
name: (_) @name) @item
(method_specification
"method" @context
"virtual"? @context
(method_name) @name) @item
(value_specification
"val" @context
(value_name) @name) @item
(external
"external" @context
(value_name) @name) @item
(exception_definition
"exception" @context
(constructor_declaration
(constructor_name) @name)) @item

View File

@ -0,0 +1,317 @@
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]
impl LspAdapter for OCamlLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("ocamllsp".into())
}
fn short_name(&self) -> &'static str {
"ocaml"
}
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(),
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..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,
})
}
}

View File

@ -0,0 +1,12 @@
("(" @open ")" @close)
("[" @open "]" @close)
("[|" @open "|]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
("begin" @open "end" @close)
("struct" @open "end" @close)
("sig" @open "end" @close)
("object" @open "end" @close)
("do" @open "done" @close)

View File

@ -0,0 +1,18 @@
name = "OCaml"
path_suffixes = ["ml"]
block_comment = ["(* ", "*)"]
autoclose_before = ";,=)}]"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "<", end = ">", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "[|", end = "|", close = true, newline = true, not_in = ["string"] },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "begin", end = " end", close = true, newline = true },
{ start = "struct", end = " end", close = true, newline = true },
{ start = "sig", end = " end", close = true, newline = true },
# HACK: For some reason `object` alone does not work
{ start = "object ", end = "end", close = true, newline = true },
{ start = "do", end = " done", close = true, newline = true }
]

View File

@ -0,0 +1,142 @@
; Modules
;--------
[(module_name) (module_type_name)] @title
; Types
;------
[(class_name) (class_type_name) (type_constructor)] @type
[(constructor_name) (tag)] @constructor
; Functions
;----------
(let_binding
pattern: (value_name) @function
(parameter))
(let_binding
pattern: (value_name) @function
body: [(fun_expression) (function_expression)])
(value_specification (value_name) @function)
(external (value_name) @function)
(method_name) @function
(infix_expression
left: (value_path (value_name) @function)
operator: (concat_operator) @operator
(#eq? @operator "@@"))
(infix_expression
operator: (rel_operator) @operator
right: (value_path (value_name) @function)
(#eq? @operator "|>"))
(application_expression
function: (value_path (value_name) @function))
; Variables
;----------
[(type_variable) (value_pattern)] @variable
; Properties
;-----------
[(label_name) (field_name) (instance_variable_name)] @property
; Constants
;----------
(boolean) @boolean
[(number) (signed_number)] @number
[(string) (character)] @string
(quoted_string "{" @string "}" @string) @string
(quoted_string_content) @string
(escape_sequence) @string.escape
[
(conversion_specification)
(pretty_printing_indication)
] @punctuation.special
; Operators
;----------
(match_expression (match_operator) @keyword)
(value_definition [(let_operator) (let_and_operator)] @keyword)
[
(prefix_operator)
(sign_operator)
(pow_operator)
(mult_operator)
(add_operator)
(concat_operator)
(rel_operator)
(and_operator)
(or_operator)
(assign_operator)
(hash_operator)
(indexing_operator)
(let_operator)
(let_and_operator)
(match_operator)
] @operator
["*" "#" "::" "<-"] @operator
; Keywords
;---------
[
"and" "as" "assert" "begin" "class" "constraint" "do" "done" "downto" "else"
"end" "exception" "external" "for" "fun" "function" "functor" "if" "in"
"include" "inherit" "initializer" "lazy" "let" "match" "method" "module"
"mutable" "new" "nonrec" "object" "of" "open" "private" "rec" "sig" "struct"
"then" "to" "try" "type" "val" "virtual" "when" "while" "with"
] @keyword
; Punctuation
;------------
["(" ")" "[" "]" "{" "}" "[|" "|]" "[<" "[>"] @punctuation.bracket
(object_type ["<" ">"] @punctuation.bracket)
[
"," "." ";" ":" "=" "|" "~" "?" "+" "-" "!" ">" "&"
"->" ";;" ":>" "+=" ":=" ".."
] @punctuation.delimiter
; Attributes
;-----------
[
(attribute)
(item_attribute)
(floating_attribute)
(extension)
(item_extension)
(quoted_extension)
(quoted_item_extension)
"%"
] @attribute
(attribute_id) @tag
; Comments
;---------
[(comment) (line_number_directive) (directive) (shebang)] @comment

View File

@ -0,0 +1,43 @@
[
(let_binding)
(type_binding)
(method_definition)
(external)
(value_specification)
(method_specification)
(match_case)
(function_expression)
(field_declaration)
(field_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "[|" "|]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
(_ "object" @start "end" @end) @indent
(structure
"struct" @start
"end" @end) @indent
(signature
"sig" @start
"end" @end) @indent
(parenthesized_expression
"begin" @start
"end") @indent
(do_clause
"do" @start
"done" @end) @indent
";;" @outdent

View File

@ -0,0 +1,59 @@
(_structure_item/value_definition
"let" @context
(let_binding
pattern: (_) @name)) @item
(_structure_item/exception_definition
"exception" @context
(constructor_declaration
(constructor_name) @name)) @item
(_structure_item/module_definition
"module" @context
(module_binding
name: (module_name) @name)) @item
(module_type_definition
"module" @context
"type" @context
name: (_) @name) @item
(type_definition
"type" @context
(type_binding name: (_) @name)) @item
(value_specification
"val" @context
(value_name) @name) @item
(class_definition
"class" @context
(class_binding
"virtual"? @context
name: (_) @name)) @item
(class_type_definition
"class" @context
"type" @context
(class_type_binding
"virtual"? @context
name: (_) @name)) @item
(instance_variable_definition
"val" @context
"method"? @context
name: (_) @name) @item
(method_specification
"method" @context
"virtual"? @context
(method_name) @name) @item
(method_definition
"method" @context
"virtual"? @context
name: (_) @name) @item
(external
"external" @context
(value_name) @name) @item

View File

@ -0,0 +1,31 @@
# OCaml
- Tree Sitter: [tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml)
- Language Server: [ocamllsp](https://github.com/ocaml/ocaml-lsp)
## Setup Instructions
If you have the development environment already setup, you can skip to [Launching Zed](#launching-zed)
### Using OPAM
Opam is the official package manager for OCaml and is highly recommended for getting started with OCaml. To get started using Opam, please follow the instructions provided [here](https://opam.ocaml.org/doc/Install.html).
Once you install opam and setup a switch with your development environment as per the instructions, you can proceed.
### Launching Zed
By now you should have `ocamllsp` installed, you can verify so by running
```sh
$ ocamllsp --help
```
in your terminal. If you get a help message, you're good to go. If not, please revisit the installation instructions for `ocamllsp` and ensure it's properly installed.
With that aside, we can now launch Zed. Given how the OCaml package manager works, we require you to run Zed from the terminal, so please make sure you install the [Zed cli](https://zed.dev/features#cli) if you haven't already.
Once you have the cli, simply from a terminal, navigate to your project and run
```sh
$ zed .
```
Voila! You should have Zed running with OCaml support, no additional setup required.