Files
lapce--lapce/lapce-core/src/language.rs
T
2026-07-13 12:08:36 +08:00

2076 lines
61 KiB
Rust

use std::{
collections::{HashMap, HashSet, hash_map::Entry},
fmt::Write,
path::Path,
str::FromStr,
};
use lapce_rpc::style::{LineStyle, Style};
use once_cell::sync::Lazy;
use regex::Regex;
use strum_macros::{AsRefStr, Display, EnumMessage, EnumString, IntoStaticStr};
use tracing::{Level, event};
use tree_sitter::{Point, TreeCursor};
use crate::{
directory::Directory,
syntax::highlight::{HighlightConfiguration, HighlightIssue},
};
#[remain::sorted]
pub enum Indent {
Space(u8),
Tab,
}
impl Indent {
const fn tab() -> &'static str {
Indent::Tab.as_str()
}
const fn space(count: u8) -> &'static str {
Indent::Space(count).as_str()
}
const fn as_str(&self) -> &'static str {
match self {
Indent::Tab => "\u{0009}",
#[allow(clippy::wildcard_in_or_patterns)]
Indent::Space(v) => match v {
2 => "\u{0020}\u{0020}",
4 => "\u{0020}\u{0020}\u{0020}\u{0020}",
8 | _ => {
"\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}"
}
},
}
}
}
const DEFAULT_CODE_GLANCE_LIST: &[&str] = &["source_file"];
const DEFAULT_CODE_GLANCE_IGNORE_LIST: &[&str] = &["source_file"];
#[macro_export]
macro_rules! comment_properties {
() => {
CommentProperties {
single_line_start: None,
single_line_end: None,
multi_line_start: None,
multi_line_end: None,
multi_line_prefix: None,
}
};
($s:expr) => {
CommentProperties {
single_line_start: Some($s),
single_line_end: None,
multi_line_start: None,
multi_line_end: None,
multi_line_prefix: None,
}
};
($s:expr, $e:expr) => {
CommentProperties {
single_line_start: Some($s),
single_line_end: Some($e),
multi_line_start: None,
multi_line_end: None,
multi_line_prefix: None,
}
};
($sl_s:expr, $sl_e:expr, $ml_s:expr, $ml_e:expr) => {
CommentProperties {
single_line_start: Some($sl_s),
single_line_end: Some($sl_e),
multi_line_start: Some($sl_s),
multi_line_end: None,
multi_line_prefix: Some($sl_e),
}
};
}
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug, PartialOrd, Ord, Default)]
pub struct SyntaxProperties {
/// An extra check to make sure that the array elements are in the correct order.
/// If this id does not match the enum value, a panic will happen with a debug assertion message.
id: LapceLanguage,
/// All tokens that can be used for comments in language
comment: CommentProperties,
/// The indent unit.
/// " " for bash, " " for rust, for example.
indent: &'static str,
/// Filenames that belong to this language
/// `["Dockerfile"]` for Dockerfile, `[".editorconfig"]` for EditorConfig
files: &'static [&'static str],
/// File name extensions to determine the language.
/// `["py"]` for python, `["rs"]` for rust, for example.
extensions: &'static [&'static str],
/// Tree-sitter properties
tree_sitter: TreeSitterProperties,
}
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug, PartialOrd, Ord, Default)]
struct TreeSitterProperties {
/// the grammar name that's in the grammars folder
grammar: Option<&'static str>,
/// the grammar fn name
grammar_fn: Option<&'static str>,
/// the query folder name
query: Option<&'static str>,
/// Preface: Originally this feature was called "Code Lens", which is not
/// an LSP "Code Lens". It is renamed to "Code Glance", below doc text is
/// left unchanged.
///
/// Lists of tree-sitter node types that control how code lenses are built.
/// The first is a list of nodes that should be traversed and included in
/// the lens, along with their children. The second is a list of nodes that
/// should be excluded from the lens, though they will still be traversed.
/// See `walk_tree` for more details.
///
/// The tree-sitter playground may be useful when creating these lists:
/// https://tree-sitter.github.io/tree-sitter/playground
///
/// If unsure, use `DEFAULT_CODE_GLANCE_LIST` and
/// `DEFAULT_CODE_GLANCE_IGNORE_LIST`.
code_glance: (&'static [&'static str], &'static [&'static str]),
/// the tree-sitter tag names that can be put in sticky headers
sticky_headers: &'static [&'static str],
}
impl TreeSitterProperties {
const DEFAULT: Self = Self {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &[],
};
}
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug, PartialOrd, Ord, Default)]
struct CommentProperties {
/// Single line comment token used when commenting out one line.
/// "#" for python, "//" for rust for example.
single_line_start: Option<&'static str>,
single_line_end: Option<&'static str>,
/// Multi line comment token used when commenting a selection of lines.
/// "#" for python, "//" for rust for example.
multi_line_start: Option<&'static str>,
multi_line_end: Option<&'static str>,
multi_line_prefix: Option<&'static str>,
}
/// NOTE: Keep the enum variants "fieldless" so they can cast to usize as array
/// indices into the LANGUAGES array. See method `LapceLanguage::properties`.
///
/// Do not assign values to the variants because the number of variants and
/// number of elements in the LANGUAGES array change as different features
/// selected by the cargo build command.
#[derive(
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Clone,
Copy,
Debug,
Display,
AsRefStr,
IntoStaticStr,
EnumString,
EnumMessage,
Default,
)]
#[strum(ascii_case_insensitive)]
#[remain::sorted]
pub enum LapceLanguage {
// Do not move
#[remain::unsorted]
#[default]
#[strum(message = "Plain Text")]
PlainText,
#[strum(message = "Ada")]
Ada,
#[strum(message = "Adl")]
Adl,
#[strum(message = "Agda")]
Agda,
#[strum(message = "Astro")]
Astro,
#[strum(message = "Bash")]
Bash,
#[strum(message = "Bass")]
Bass,
#[strum(message = "Beancount")]
Beancount,
#[strum(message = "Bibtex")]
Bibtex,
#[strum(message = "Bitbake")]
Bitbake,
#[strum(message = "Blade")]
Blade,
#[strum(message = "C")]
C,
#[strum(message = "Clojure")]
Clojure,
#[strum(message = "CMake")]
Cmake,
#[strum(message = "Comment")]
Comment,
#[strum(message = "C++")]
Cpp,
#[strum(message = "C#")]
Csharp,
#[strum(message = "CSS")]
Css,
#[strum(message = "Cue")]
Cue,
#[strum(message = "D")]
D,
#[strum(message = "Dart")]
Dart,
#[strum(message = "Dhall")]
Dhall,
#[strum(message = "Diff")]
Diff,
#[strum(message = "Dockerfile")]
Dockerfile,
#[strum(message = "Dot")]
Dot,
#[strum(message = "Elixir")]
Elixir,
#[strum(message = "Elm")]
Elm,
#[strum(message = "Erlang")]
Erlang,
#[strum(message = "Fish Shell")]
Fish,
#[strum(message = "Fluent")]
Fluent,
#[strum(message = "Forth")]
Forth,
#[strum(message = "Fortran")]
Fortran,
#[strum(message = "F#")]
FSharp,
#[strum(message = "Gitattributes")]
Gitattributes,
#[strum(message = "Git (commit)")]
GitCommit,
#[strum(message = "Git (config)")]
GitConfig,
#[strum(message = "Git (rebase)")]
GitRebase,
#[strum(message = "Gleam")]
Gleam,
#[strum(message = "Glimmer")]
Glimmer,
#[strum(message = "GLSL")]
Glsl,
#[strum(message = "Gn")]
Gn,
#[strum(message = "Go")]
Go,
#[strum(message = "Go (go.mod)")]
GoMod,
#[strum(message = "Go (template)")]
GoTemplate,
#[strum(message = "Go (go.work)")]
GoWork,
#[strum(message = "GraphQL")]
GraphQl,
#[strum(message = "Groovy")]
Groovy,
#[strum(message = "Hare")]
Hare,
#[strum(message = "Haskell")]
Haskell,
#[strum(message = "Haxe")]
Haxe,
#[strum(message = "HCL")]
Hcl,
#[strum(message = "Hosts file (/etc/hosts)")]
Hosts,
#[strum(message = "HTML")]
Html,
#[strum(message = "INI")]
Ini,
#[strum(message = "Java")]
Java,
#[strum(message = "JavaScript")]
Javascript,
#[strum(message = "JSDoc")]
Jsdoc,
#[strum(message = "JSON")]
Json,
#[strum(message = "JSON5")]
Json5,
#[strum(message = "Jsonnet")]
Jsonnet,
#[strum(message = "JavaScript React")]
Jsx,
#[strum(message = "Julia")]
Julia,
#[strum(message = "Just")]
Just,
#[strum(message = "KDL")]
Kdl,
#[strum(message = "Kotlin")]
Kotlin,
#[strum(message = "Kotlin Build Script")]
KotlinBuildScript,
#[strum(message = "LaTeX")]
Latex,
#[strum(message = "Linker Script")]
Ld,
#[strum(message = "LLVM")]
Llvm,
#[strum(message = "LLVM MIR")]
LlvmMir,
#[strum(message = "Log")]
Log,
#[strum(message = "Lua")]
Lua,
#[strum(message = "Makefile")]
Make,
#[strum(message = "Markdown")]
Markdown,
#[strum(serialize = "markdown.inline")]
MarkdownInline,
#[strum(message = "Meson")]
Meson,
#[strum(message = "NASM")]
Nasm,
#[strum(message = "Nix")]
Nix,
#[strum(message = "Nu (nushell)")]
Nushell,
#[strum(message = "Ocaml")]
Ocaml,
#[strum(serialize = "ocaml.interface")]
OcamlInterface,
#[strum(message = "Odin")]
Odin,
#[strum(message = "OpenCL")]
OpenCl,
#[strum(message = "Pascal")]
Pascal,
#[strum(message = "Password file (/etc/passwd)")]
Passwd,
#[strum(message = "PEM (RFC 1422)")]
Pem,
#[strum(message = "PHP")]
Php,
#[strum(message = "PKL")]
Pkl,
#[strum(message = "PowerShell")]
PowerShell,
#[strum(message = "Prisma")]
Prisma,
#[strum(message = "Proto")]
ProtoBuf,
#[strum(message = "Python")]
Python,
#[strum(message = "QL")]
Ql,
#[strum(message = "R")]
R,
#[strum(message = "RCL")]
Rcl,
#[strum(message = "RegEx")]
Regex,
#[strum(message = "REGO")]
Rego,
#[strum(message = "RON (Rust Object Notation)")]
Ron,
#[strum(message = "Rst")]
Rst,
#[strum(message = "Ruby")]
Ruby,
#[strum(message = "Rust")]
Rust,
#[strum(message = "Scala")]
Scala,
#[strum(message = "Scheme")]
Scheme,
#[strum(message = "SCSS")]
Scss,
#[strum(message = "Shell Script (POSIX)")]
ShellScript,
#[strum(message = "Smithy")]
Smithy,
#[strum(message = "SQL")]
Sql,
#[strum(message = "SSH Config")]
SshClientConfig,
#[strum(message = "Strace")]
Strace,
#[strum(message = "Svelte")]
Svelte,
#[strum(message = "Sway")]
Sway,
#[strum(message = "Swift")]
Swift,
#[strum(message = "TCL")]
Tcl,
#[strum(message = "TOML")]
Toml,
#[strum(message = "Tsx")]
Tsx,
#[strum(message = "TypeScript")]
Typescript,
#[strum(message = "Typst")]
Typst,
#[strum(message = "Verilog")]
Verilog,
#[strum(message = "Vue")]
Vue,
#[strum(message = "WASM")]
Wasm,
#[strum(message = "WGSL")]
Wgsl,
#[strum(message = "WIT")]
Wit,
#[strum(message = "XML")]
Xml,
#[strum(message = "YAML")]
Yaml,
#[strum(message = "Zig")]
Zig,
}
/// NOTE: Elements in the array must be in the same order as the enum variants of
/// `LapceLanguage` as they will be accessed using the enum variants as indices.
const LANGUAGES: &[SyntaxProperties] = &[
// Undetected/unmatched fallback or just plain file
SyntaxProperties {
id: LapceLanguage::PlainText,
indent: Indent::tab(),
files: &[],
extensions: &["txt"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
// Languages
SyntaxProperties {
id: LapceLanguage::Ada,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Adl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Agda,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Astro,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Bash,
indent: Indent::space(2),
files: &[],
extensions: &["bash", "sh"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Bass,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Beancount,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Bibtex,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Bitbake,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Blade,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::C,
indent: Indent::space(4),
files: &[],
extensions: &["c", "h"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &["function_definition", "struct_specifier"],
},
},
SyntaxProperties {
id: LapceLanguage::Clojure,
indent: Indent::space(2),
files: &[],
extensions: &[
"clj",
"edn", // spellchecker:disable-line
"cljs",
"cljc",
"cljd",
"bb",
"clj_kondo",
],
comment: comment_properties!(";"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Cmake,
indent: Indent::space(2),
files: &["cmakelists"],
extensions: &["cmake"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &["function_definition"],
},
},
SyntaxProperties {
id: LapceLanguage::Comment,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Cpp,
indent: Indent::space(4),
files: &[],
extensions: &["cpp", "cxx", "cc", "c++", "hpp", "hxx", "hh", "h++"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &[
"function_definition",
"class_specifier",
"struct_specifier",
],
},
},
SyntaxProperties {
id: LapceLanguage::Csharp,
indent: Indent::space(2),
files: &[],
extensions: &["cs", "csx"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &[
"interface_declaration",
"class_declaration",
"enum_declaration",
"struct_declaration",
"record_declaration",
"record_struct_declaration",
"namespace_declaration",
"constructor_declaration",
"destructor_declaration",
"method_declaration",
],
},
},
SyntaxProperties {
id: LapceLanguage::Css,
indent: Indent::space(2),
files: &[],
extensions: &["css"],
comment: comment_properties!("/*", "*/"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Cue,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::D,
indent: Indent::space(4),
files: &[],
extensions: &["d", "di", "dlang"],
comment: CommentProperties {
single_line_start: Some("//"),
single_line_end: None,
multi_line_start: Some("/+"),
multi_line_prefix: None,
multi_line_end: Some("+/"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Dart,
indent: Indent::space(2),
files: &[],
extensions: &["dart"],
comment: CommentProperties {
single_line_start: Some("//"),
single_line_end: None,
multi_line_start: Some("/*"),
multi_line_prefix: None,
multi_line_end: Some("*/"),
},
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (
&["program", "class_definition"],
&[
"program",
"import_or_export",
"comment",
"documentation_comment",
],
),
sticky_headers: &["class_definition"],
},
},
SyntaxProperties {
id: LapceLanguage::Dhall,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Diff,
indent: Indent::tab(),
files: &[],
extensions: &["diff", "patch"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Dockerfile,
indent: Indent::space(2),
files: &["Dockerfile", "Containerfile"],
extensions: &["containerfile", "dockerfile"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Dot,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Elixir,
indent: Indent::space(2),
files: &[],
extensions: &["ex", "exs", "eex", "heex", "sface"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &["do_block"],
},
},
SyntaxProperties {
id: LapceLanguage::Elm,
indent: Indent::space(4),
files: &[],
extensions: &["elm"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Erlang,
indent: Indent::space(4),
files: &[],
extensions: &["erl", "hrl"],
comment: comment_properties!("%"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::FSharp,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Fish,
indent: Indent::tab(),
files: &[],
extensions: &["fish"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Fluent,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Forth,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Fortran,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Gitattributes,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GitCommit,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GitConfig,
indent: Indent::tab(),
files: &[".gitconfig", ".git/config"],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GitRebase,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Gleam,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Glimmer,
indent: Indent::space(2),
files: &[],
extensions: &["hbs"],
comment: comment_properties!("{{!", "!}}"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Glsl,
indent: Indent::space(2),
files: &[],
// spellchecker:off
extensions: &[
"glsl", "cs", "vs", "gs", "fs", "csh", "vsh", "gsh", "fsh", "cshader",
"vshader", "gshader", "fshader", "comp", "vert", "geom", "frag", "tesc",
"tese", "mesh", "task", "rgen", "rint", "rahit", "rchit", "rmiss",
"rcall",
],
// spellchecker:on
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Gn,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Go,
indent: Indent::tab(),
files: &[],
extensions: &["go"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (
&[
"source_file",
"type_declaration",
"type_spec",
"interface_type",
"method_spec_list",
],
&["source_file", "comment", "line_comment"],
),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::GoMod,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GoTemplate,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GoWork,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::GraphQl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Groovy,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Hare,
indent: Indent::space(8),
files: &[],
extensions: &["ha"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Haskell,
indent: Indent::space(2),
files: &[],
extensions: &["hs"],
comment: comment_properties!("--"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Haxe,
indent: Indent::space(2),
files: &[],
extensions: &["hx"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Hcl,
indent: Indent::space(2),
files: &[],
extensions: &["hcl", "tf"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Hosts,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Html,
indent: Indent::space(4),
files: &[],
extensions: &["html", "htm"],
comment: comment_properties!("<!--", "-->"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Ini,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Java,
indent: Indent::space(4),
files: &[],
extensions: &["java"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Javascript,
indent: Indent::space(2),
files: &[],
extensions: &["js", "cjs", "mjs"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (&["source_file", "program"], &["source_file"]),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Jsdoc,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Json,
indent: Indent::space(4),
files: &[],
extensions: &["json", "har"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Json5,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Jsonnet,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Jsx,
indent: Indent::space(2),
files: &[],
extensions: &["jsx"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: Some("javascript"),
grammar_fn: Some("javascript"),
query: Some("jsx"),
code_glance: (&["source_file", "program"], &["source_file"]),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Julia,
indent: Indent::space(4),
files: &[],
extensions: &["julia", "jl"],
comment: CommentProperties {
single_line_start: Some("#"),
single_line_end: None,
multi_line_start: Some("#="),
multi_line_prefix: None,
multi_line_end: Some("=#"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Just,
indent: Indent::tab(),
files: &["justfile", "Justfile", ".justfile", ".Justfile"],
extensions: &["just"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Kdl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Kotlin,
indent: Indent::space(2),
files: &[],
extensions: &["kt"],
comment: CommentProperties {
single_line_start: Some("//"),
single_line_end: None,
multi_line_start: Some("/*"),
multi_line_prefix: None,
multi_line_end: Some("*/"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::KotlinBuildScript,
indent: Indent::space(2),
files: &[],
extensions: &["kts"],
comment: CommentProperties {
single_line_start: Some("//"),
single_line_end: None,
multi_line_start: Some("/*"),
multi_line_prefix: None,
multi_line_end: Some("*/"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Latex,
indent: Indent::space(2),
files: &[],
extensions: &["tex"],
comment: comment_properties!("%"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Ld,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Llvm,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::LlvmMir,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Log,
indent: Indent::tab(),
files: &["log.txt"],
extensions: &["log"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Lua,
indent: Indent::space(2),
files: &[],
extensions: &["lua"],
comment: comment_properties!("--"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Make,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Markdown,
indent: Indent::space(4),
files: &[],
extensions: &["md"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::MarkdownInline,
indent: Indent::space(4),
// markdown inline is only used as an injection by the Markdown language
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties {
grammar: Some("markdown_inline"),
grammar_fn: Some("markdown_inline"),
query: Some("markdown.inline"),
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Meson,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Nasm,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Nix,
indent: Indent::space(2),
files: &[],
extensions: &["nix"],
comment: CommentProperties {
single_line_start: Some("#"),
single_line_end: None,
multi_line_start: Some("/*"),
multi_line_prefix: None,
multi_line_end: Some("*/"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Nushell,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Ocaml,
indent: Indent::space(2),
files: &[],
extensions: &["ml"],
comment: CommentProperties {
single_line_start: Some("(*"),
single_line_end: Some("*)"),
multi_line_start: Some("(*"),
multi_line_prefix: Some("*"),
multi_line_end: Some("*)"),
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::OcamlInterface,
indent: Indent::space(2),
files: &[],
extensions: &["mli"],
comment: comment_properties!("(*"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Odin,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::OpenCl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Pascal,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Passwd,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Pem,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Php,
indent: Indent::space(2),
files: &[],
extensions: &["php"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (
&[
"program",
"class_declaration",
"trait_declaration",
"interface_declaration",
"declaration_list",
"method_declaration",
"function_declaration",
],
&[
"program",
"php_tag",
"comment",
"namespace_definition",
"namespace_use_declaration",
"use_declaration",
"const_declaration",
"property_declaration",
"expression_statement",
],
),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Pkl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::PowerShell,
indent: Indent::space(4),
files: &[],
extensions: &["ps1", "psm1", "psd1", "ps1xml"],
comment: CommentProperties {
single_line_start: Some("#"),
single_line_end: None,
multi_line_start: Some("<#"),
multi_line_end: Some("#>"),
multi_line_prefix: None,
},
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Prisma,
indent: Indent::space(4),
files: &[],
extensions: &["prisma"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::ProtoBuf,
indent: Indent::space(2),
files: &[],
extensions: &["proto"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Python,
indent: Indent::space(4),
files: &[],
extensions: &["py", "pyi", "pyc", "pyd", "pyw"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (
&[
"source_file",
"module",
"class_definition",
"class",
"identifier",
"decorated_definition",
"block",
],
&["source_file", "import_statement", "import_from_statement"],
),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Ql,
indent: Indent::space(2),
files: &[],
extensions: &["ql"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::R,
indent: Indent::space(2),
files: &[],
extensions: &["r"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Rcl,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Regex,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Rego,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Ron,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Rst,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Ruby,
indent: Indent::space(2),
files: &[],
extensions: &["rb"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST),
sticky_headers: &["module", "class", "method", "do_block"],
},
},
SyntaxProperties {
id: LapceLanguage::Rust,
indent: Indent::space(4),
files: &[],
extensions: &["rs"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: None,
grammar_fn: None,
query: None,
code_glance: (
&["source_file", "impl_item", "trait_item", "declaration_list"],
&["source_file", "use_declaration", "line_comment"],
),
sticky_headers: &[
"struct_item",
"enum_item",
"function_item",
"impl_item",
],
},
},
SyntaxProperties {
id: LapceLanguage::Scala,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Scheme,
indent: Indent::space(2),
files: &[],
extensions: &["scm", "ss"],
comment: comment_properties!(";"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Scss,
indent: Indent::space(2),
files: &[],
extensions: &["scss"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::ShellScript,
indent: Indent::space(2),
files: &[],
extensions: &["sh"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Smithy,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Sql,
indent: Indent::space(2),
files: &[],
extensions: &["sql"],
comment: comment_properties!("--"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::SshClientConfig,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Strace,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Svelte,
indent: Indent::space(2),
files: &[],
extensions: &["svelte"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Sway,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Swift,
indent: Indent::space(2),
files: &[],
extensions: &["swift"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Tcl,
indent: Indent::tab(),
files: &[],
extensions: &["tcl"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Toml,
indent: Indent::space(2),
files: &["Cargo.lock"],
extensions: &["toml"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Tsx,
indent: Indent::space(4),
files: &[],
extensions: &["tsx"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: Some("tsx"),
grammar_fn: Some("tsx"),
query: Some("tsx"),
code_glance: (&["source_file", "program"], &["source_file"]),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Typescript,
indent: Indent::space(4),
files: &[],
extensions: &["ts", "cts", "mts"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties {
grammar: Some("typescript"),
grammar_fn: Some("typescript"),
query: Some("typescript"),
code_glance: (&["source_file", "program"], &["source_file"]),
sticky_headers: &[],
},
},
SyntaxProperties {
id: LapceLanguage::Typst,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Verilog,
indent: Indent::tab(),
files: &[],
extensions: &[],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Vue,
indent: Indent::space(2),
files: &[],
extensions: &["vue"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Wasm,
indent: Indent::space(4),
files: &[],
extensions: &["wasm"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Wgsl,
indent: Indent::space(4),
files: &[],
extensions: &["wgsl"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Wit,
indent: Indent::space(4),
files: &[],
extensions: &["wit"],
comment: comment_properties!(),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Xml,
indent: Indent::space(4),
files: &[],
extensions: &["xml", "csproj"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Yaml,
indent: Indent::space(2),
files: &[],
extensions: &["yml", "yaml"],
comment: comment_properties!("#"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
SyntaxProperties {
id: LapceLanguage::Zig,
indent: Indent::space(4),
files: &[],
extensions: &["zig"],
comment: comment_properties!("//"),
tree_sitter: TreeSitterProperties::DEFAULT,
},
];
impl LapceLanguage {
const HIGHLIGHTS_INJECTIONS_FILE_NAME: &'static str = "injections.scm";
const HIGHLIGHTS_QUERIES_FILE_NAME: &'static str = "highlights.scm";
pub fn from_path(path: &Path) -> LapceLanguage {
Self::from_path_raw(path).unwrap_or(LapceLanguage::PlainText)
}
pub fn from_path_raw(path: &Path) -> Option<LapceLanguage> {
let filename = path.file_name().and_then(|s| s.to_str());
let extension = path
.extension()
.and_then(|s| s.to_str().map(|s| s.to_lowercase()));
// NOTE: This is a linear search. It is assumed that this function
// isn't called in any tight loop.
for properties in LANGUAGES {
if properties.files.iter().any(|f| Some(*f) == filename) {
return Some(properties.id);
}
if properties
.extensions
.iter()
.any(|e| Some(*e) == extension.as_deref())
{
return Some(properties.id);
}
}
None
}
pub fn from_name(name: &str) -> Option<LapceLanguage> {
match LapceLanguage::from_str(name.to_lowercase().as_str()) {
Ok(v) => Some(v),
Err(e) => {
event!(Level::DEBUG, "failed parsing `{name}` LapceLanguage: {e}");
None
}
}
}
pub fn languages() -> Vec<&'static str> {
let mut langs = vec![];
for l in LANGUAGES {
// Get only languages with display name to hide inline grammars
if let Some(lang) = strum::EnumMessage::get_message(&l.id) {
langs.push(lang)
}
}
langs
}
// NOTE: Instead of using `&LANGUAGES[*self as usize]` directly, the
// `debug_assertion` gives better feedback should something has gone wrong
// badly.
fn properties(&self) -> &SyntaxProperties {
let i = *self as usize;
let l = &LANGUAGES[i];
debug_assert!(
l.id == *self,
"LANGUAGES[{i}]: Setting::id mismatch: {:?} != {:?}",
l.id,
self
);
l
}
pub fn name(&self) -> &'static str {
strum::EnumMessage::get_message(self).unwrap_or(self.into())
}
pub fn sticky_header_tags(&self) -> &[&'static str] {
self.properties().tree_sitter.sticky_headers
}
pub fn comment_token(&self) -> &'static str {
self.properties()
.comment
.single_line_start
.unwrap_or_default()
}
pub fn indent_unit(&self) -> &str {
self.properties().indent
}
fn get_grammar(&self) -> Option<tree_sitter::Language> {
let grammar_name = self.grammar_name();
let grammar_fn_name = self.grammar_fn_name();
if let Some(grammars_dir) = Directory::grammars_directory() {
match self::load_grammar(&grammar_name, &grammar_fn_name, &grammars_dir)
{
Ok(grammar) => {
return Some(grammar);
}
Err(err) => {
if self != &LapceLanguage::PlainText {
tracing::error!("{:?} {:?}", self, err);
}
}
}
};
None
}
fn query_name(&self) -> String {
self.properties()
.tree_sitter
.query
.unwrap_or(self.properties().id.as_ref())
.to_lowercase()
}
fn grammar_name(&self) -> String {
self.properties()
.tree_sitter
.grammar
.unwrap_or(self.properties().id.as_ref())
.to_lowercase()
}
fn grammar_fn_name(&self) -> String {
self.properties()
.tree_sitter
.grammar_fn
.unwrap_or(self.properties().id.as_ref())
.to_lowercase()
}
fn get_grammar_query(&self) -> (String, String) {
let query_name = self.query_name();
// Try reading highlights from user config dir
if let Some(queries_dir) = Directory::queries_directory() {
return (
read_grammar_query(
&queries_dir,
&query_name,
Self::HIGHLIGHTS_QUERIES_FILE_NAME,
),
read_grammar_query(
&queries_dir,
&query_name,
Self::HIGHLIGHTS_INJECTIONS_FILE_NAME,
),
);
}
("".to_string(), "".to_string())
}
pub(crate) fn new_highlight_config(
&self,
) -> Result<HighlightConfiguration, HighlightIssue> {
let grammar = self.get_grammar().ok_or(HighlightIssue::NotAvailable)?;
let (query, injection) = self.get_grammar_query();
match HighlightConfiguration::new(grammar, &query, &injection, "") {
Ok(x) => Ok(x),
Err(x) => {
let str = format!(
"Encountered {x:?} while trying to construct HighlightConfiguration for {}",
self.name()
);
event!(Level::ERROR, "{str}");
Err(HighlightIssue::Error(str))
}
}
}
pub(crate) fn walk_tree(
&self,
cursor: &mut TreeCursor,
normal_lines: &mut HashSet<usize>,
) {
let (list, ignore_list) = self.properties().tree_sitter.code_glance;
walk_tree(cursor, normal_lines, list, ignore_list);
}
}
fn load_grammar(
grammar_name: &str,
grammar_fn_name: &str,
path: &Path,
) -> Result<tree_sitter::Language, HighlightIssue> {
let mut library_path = path.join(format!("libtree-sitter-{grammar_name}"));
library_path.set_extension(std::env::consts::DLL_EXTENSION);
if !library_path.exists() {
event!(Level::WARN, "Grammar not found at: {library_path:?}");
// Load backward compat libraries
library_path = path.join(format!("tree-sitter-{grammar_name}"));
library_path.set_extension(std::env::consts::DLL_EXTENSION);
if !library_path.exists() {
event!(Level::WARN, "Grammar not found at: {library_path:?}");
return Err(HighlightIssue::Error("grammar not found".to_string()));
}
}
event!(Level::DEBUG, "Loading grammar from user grammar dir");
let library = match unsafe { libloading::Library::new(&library_path) } {
Ok(v) => v,
Err(e) => {
let err = format!("Failed to load '{}': '{e}'", library_path.display());
event!(Level::ERROR, err);
return Err(HighlightIssue::Error(err));
}
};
let language_fn_name =
format!("tree_sitter_{}", grammar_fn_name.replace('-', "_"));
event!(
Level::DEBUG,
"Loading grammar with address: '{language_fn_name}'"
);
let language = unsafe {
let language_fn: libloading::Symbol<
unsafe extern "C" fn() -> tree_sitter::Language,
> = match library.get(language_fn_name.as_bytes()) {
Ok(v) => v,
Err(e) => {
let err = format!("Failed to load '{language_fn_name}': '{e}'");
event!(Level::ERROR, err);
if let Some(e) = library.close().err() {
event!(Level::ERROR, "Failed to drop loaded library: {e}");
};
return Err(HighlightIssue::Error(err));
}
};
language_fn()
};
std::mem::forget(library);
Ok(language)
}
/// Walk an AST and determine which lines to include in the code glance.
///
/// Node types listed in `list` will be walked, along with their children. All
/// nodes encountered will be included, unless they are listed in `ignore_list`.
fn walk_tree(
cursor: &mut TreeCursor,
normal_lines: &mut HashSet<usize>,
list: &[&str],
ignore_list: &[&str],
) {
let node = cursor.node();
let start_pos = node.start_position();
let end_pos = node.end_position();
let kind = node.kind().trim();
if !ignore_list.contains(&kind) && !kind.is_empty() {
normal_lines.insert(start_pos.row);
normal_lines.insert(end_pos.row);
}
if list.contains(&kind) && cursor.goto_first_child() {
loop {
walk_tree(cursor, normal_lines, list, ignore_list);
if !cursor.goto_next_sibling() {
break;
}
}
cursor.goto_parent();
}
}
fn add_bracket_pos(
bracket_pos: &mut HashMap<usize, Vec<LineStyle>>,
start_pos: Point,
color: String,
) {
let line_style = LineStyle {
start: start_pos.column,
end: start_pos.column + 1,
style: Style {
fg_color: Some(color),
},
};
match bracket_pos.entry(start_pos.row) {
Entry::Vacant(v) => _ = v.insert(vec![line_style]),
Entry::Occupied(mut o) => o.get_mut().push(line_style),
}
}
pub(crate) fn walk_tree_bracket_ast(
cursor: &mut TreeCursor,
level: &mut usize,
counter: &mut usize,
bracket_pos: &mut HashMap<usize, Vec<LineStyle>>,
palette: &Vec<String>,
) {
if cursor.node().kind().ends_with('(')
|| cursor.node().kind().ends_with('{')
|| cursor.node().kind().ends_with('[')
{
let row = cursor.node().end_position().row;
let col = cursor.node().end_position().column - 1;
let start_pos = Point::new(row, col);
add_bracket_pos(
bracket_pos,
start_pos,
palette.get(*level % palette.len()).unwrap().clone(),
);
*level += 1;
} else if cursor.node().kind().ends_with(')')
|| cursor.node().kind().ends_with('}')
|| cursor.node().kind().ends_with(']')
{
let (new_level, overflow) = (*level).overflowing_sub(1);
let row = cursor.node().end_position().row;
let col = cursor.node().end_position().column - 1;
let start_pos = Point::new(row, col);
if overflow {
add_bracket_pos(bracket_pos, start_pos, "bracket.unpaired".to_string());
} else {
*level = new_level;
add_bracket_pos(
bracket_pos,
start_pos,
palette.get(*level % palette.len()).unwrap().clone(),
);
}
}
*counter += 1;
if cursor.goto_first_child() {
loop {
walk_tree_bracket_ast(cursor, level, counter, bracket_pos, palette);
if !cursor.goto_next_sibling() {
break;
}
}
cursor.goto_parent();
}
}
fn read_grammar_query(queries_dir: &Path, name: &str, kind: &str) -> String {
static INHERITS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()-]+)\s*").unwrap());
let file = queries_dir.join(name).join(kind);
let query = std::fs::read_to_string(&file).unwrap_or_else(|err| {
tracing::event!(
tracing::Level::WARN,
"Failed to read queries at: {file:?}, {err}"
);
String::new()
});
INHERITS_REGEX
.replace_all(&query, |captures: &regex::Captures| {
captures[1]
.split(',')
.fold(String::new(), |mut output, name| {
write!(
output,
"\n{}\n",
read_grammar_query(queries_dir, name, kind)
)
.unwrap();
output
})
})
.to_string()
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::LapceLanguage;
#[test]
fn test_language_from_path() {
let l = LapceLanguage::from_path(&PathBuf::new().join("test.rs"));
assert_eq!(l, LapceLanguage::Rust);
}
}