refactor: migrate syntax highlighting to Tree-sitter
This commit is contained in:
Generated
+244
-111
@@ -2,12 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
@@ -38,30 +32,6 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
@@ -93,6 +63,16 @@ dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
@@ -122,15 +102,6 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
@@ -277,32 +248,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.16.2"
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
@@ -373,6 +322,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
@@ -484,16 +443,6 @@ version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.4"
|
||||
@@ -530,12 +479,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.7.7"
|
||||
@@ -697,6 +640,18 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
@@ -805,6 +760,7 @@ version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
@@ -812,6 +768,12 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
@@ -843,12 +805,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.2"
|
||||
@@ -861,6 +817,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -910,24 +872,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"fancy-regex",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"once_cell",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.21"
|
||||
@@ -969,6 +913,185 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5129de2ccc97808c4583d48f1fb44576d015cf241e432800572bda11ef30c3b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"streaming-iterator",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-bash"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-c"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b2eb57a55fed6b00812912e730b7a275cf4fe98bfd6a5d76263d4438371728"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-cpp"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-css"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5cbc5e18f29a2c6d6435891f42569525cf95435a3e01c2f1947abcde178686f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-go"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8560a4d2f835cc0d4d2c2e03cbd0dde2f6114b43bc491164238d333e28b16ea"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-html"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261b708e5d92061ede329babaaa427b819329a9d427a1d710abb0f67bbef63ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-javascript"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68204f2abc0627a90bdf06e605f5c470aa26fdcb2081ea553a04bdad756693f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-json"
|
||||
version = "0.24.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-language"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0af592be68c579aa78a16846bd19422978c3c52e438523d45ff5d1bff1f9d4a"
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-make"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5998dc7cbcbdab19fae8aefef982bf2d6544513d8d2e69cc44aec4c63810104"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-md"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2efd398be546456c814598ee56c0f51769a77241511b4a58077815d120afa882"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bf85fd39652e740bf60f46f4cda9492c3a9ad75880575bf14960f775cb74a1c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-ruby"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-rust"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439e577dbe07423ec2582ac62c7531120dbfccfa6e5f92406f93dd271a120e45"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-toml-ng"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9adc2c898ae49730e857d75be403da3f92bb81d8e37a2f918a08dd10de5ebb1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-typescript"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-yaml"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c223db85f05e34794f065454843b0668ebc15d240ada63e2b5939f43ce7c97"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
@@ -1007,7 +1130,23 @@ dependencies = [
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syntect",
|
||||
"tree-sitter",
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-c",
|
||||
"tree-sitter-cpp",
|
||||
"tree-sitter-css",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-javascript",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-make",
|
||||
"tree-sitter-md",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-ruby",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-toml-ng",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
@@ -1074,12 +1213,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
|
||||
+17
-1
@@ -9,6 +9,22 @@ ignore = "0.4"
|
||||
ratatui = { version = "0.30", default-features = false, features = ["crossterm_0_29"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
syntect = { version = "5.3", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||
tree-sitter = "0.27.1"
|
||||
tree-sitter-bash = "0.25.1"
|
||||
tree-sitter-c = "0.24.2"
|
||||
tree-sitter-cpp = "0.23.4"
|
||||
tree-sitter-css = "0.25.0"
|
||||
tree-sitter-go = "0.25.0"
|
||||
tree-sitter-html = "0.23.2"
|
||||
tree-sitter-javascript = "0.25.0"
|
||||
tree-sitter-json = "0.24.8"
|
||||
tree-sitter-make = "1.1.1"
|
||||
tree-sitter-md = { version = "0.5.3", default-features = false }
|
||||
tree-sitter-python = "0.25.0"
|
||||
tree-sitter-ruby = "0.23.1"
|
||||
tree-sitter-rust = "0.24.2"
|
||||
tree-sitter-toml-ng = "0.7.0"
|
||||
tree-sitter-typescript = "0.23.2"
|
||||
tree-sitter-yaml = "0.7.2"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-width = "0.2"
|
||||
|
||||
@@ -205,7 +205,7 @@ One crate, seven modules, no workspace or terminal-coordinate types in the editi
|
||||
- `editor`: owns buffer lifecycle and per-buffer text, saved state, primary/selection set, modes, search, and undo/redo; session-wide clipboard policy, independent registers and owned-system fragment metadata, semantic native repeat, action metadata, command-palette and file-picker focus/dispatch, key notation, leader/count prefixes and binding resolution, modal paste policy, saving, command ownership, capabilities, and validated edit effects. Its low-level interface returns an `Invocation` when an external command is selected.
|
||||
- `files`: owns the gitignore-aware directory/file catalog, authoritative OS paths, compact tree projection, query filtering, browse/search focus, expansion/selection restoration, navigation, and scan/error state shared by floating and docked presentations.
|
||||
- `clipboard`: owns native plain-text provider selection and bounded subprocess I/O; it never computes selections or editing offsets.
|
||||
- `syntax`: owns bundled grammar detection, semantic byte spans, multiline parser checkpoints, and changed-suffix highlighting; it has no terminal color/geometry or editing dependency.
|
||||
- `syntax`: owns Tree-sitter grammar detection, per-buffer incremental trees, bundled highlight/local/injection queries, and semantic byte spans; it has no terminal color/geometry or editing dependency.
|
||||
- `session`: `EditorSession` owns the editor and its Ruby hosts together. It loads configuration once, registers eligible plugins before installing shared bindings, executes selected commands against the active buffer, and retires both host and command ownership on invocation or effect-validation failure.
|
||||
- `tui`: translates terminal events to session input, renders read-only active-buffer state, keeps frontend-owned viewports and syntax caches by stable buffer ID, and advertises `text` and `styled_text` capabilities. It maps semantic tokens to terminal colors and has no plugin lifecycle callback, host storage, or paste acceptance policy.
|
||||
- `ruby`: evaluates configuration and runs isolated plugin hosts. Protocol, deadlines, and process cleanup stay inside this module.
|
||||
@@ -228,11 +228,11 @@ The buffer viewport includes a fixed left gutter with right-aligned hybrid line
|
||||
|
||||
### Syntax highlighting
|
||||
|
||||
Buffers highlight automatically using [Syntect's bundled Sublime grammars](https://docs.rs/syntect/5.3.0/syntect/). Detection uses the supplied filename/extension and the buffer's own first line/shebang, without rereading the file from disk. Tested languages include Rust, Ruby, Python, JSON, shell, Makefile, and Markdown; other bundled grammars are available. Unknown and Plain Text buffers keep the terminal's normal foreground. The detected language appears in status, and saving under a different extension redetects it.
|
||||
Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) and compiled-in grammar/query packages. The selected language set is Rust, Ruby, Python, JSON, Bash, Makefile, Markdown, JavaScript/JSX, TypeScript/TSX, HTML, CSS, YAML, TOML, C, C++, and Go. Detection uses filename/extension or the in-memory first-line shebang, including common special filenames such as Makefile, Gemfile, and Cargo.lock; no file reread occurs. Unsupported formats use the normal foreground. This migration intentionally replaces the broader former grammar bundle: languages outside this explicit set, such as Lua/Haskell/Scala/Perl, are plain text until added. The detected language appears in status, and save-as redetects it.
|
||||
|
||||
Colors use the terminal palette rather than a fixed background/theme: comments are dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Multiline/nested comments and strings retain parser scope across lines, including when their opening is above the viewport. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide characters keep the existing blank-placeholder behavior. Search, Visual selections, and secondary carets paint afterward and retain their foreground/background precedence.
|
||||
Colors retain the terminal palette: comments dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Grammar queries handle multiline/nested constructs, even when their opening is above the viewport. Markdown uses its inline grammar and named fenced-code injections; HTML script/style content uses JavaScript/CSS. Known tagged-template and raw-string injections use the same selected grammar registry; unknown injected languages retain host highlighting or plain text. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide placeholders remain unchanged. Search, Visual selections, and secondary carets retain foreground/background precedence.
|
||||
|
||||
Each buffer has a frontend-owned cache. Text revisions distinguish edits/plugin effects/undo/redo from cursor and explorer changes; unchanged revision/path skips comparison and parsing. Changed text resumes at the first changed line's cached parser/scope state and reparses the suffix, preserving prefix work. Buffer switching restores its cache, and closing retires it. Real parser/scope errors clear syntax spans, retain readable/editable plain text, and report a status error. No custom lexer, runtime grammar downloads, or editor-offset changes are involved.
|
||||
Each buffer has a frontend-owned parser/tree cache. Unchanged revision/path skips comparison and parsing; unchanged text at a later revision also avoids parsing. Actual text changes produce a minimal UTF-8-safe byte edit with newline-aware Tree-sitter Points, edit the prior tree, then parse using that tree for incremental reuse. Grammar changes reset the tree, including TypeScript versus TSX despite their shared display name. Native highlight/local/injection queries resolve capture precedence and scoped definitions into nonoverlapping original-line spans; unchanged injected trees are reused. Buffer switching restores its cache, and closing retires it. Incomplete text and grammar ERROR nodes remain highlightable; genuine language/query/parser failures expose a status error and readable plain text. Full-tree highlight queries still run after incremental parsing—a deliberate ceiling to revisit if measured costly. Syntect, its scope checkpoints, and its dependencies are removed; there is no second baseline engine, runtime grammar download, or LSP scaffold.
|
||||
|
||||
### Core feature registry
|
||||
|
||||
|
||||
+1392
-556
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,422 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub(super) struct LanguageSpec {
|
||||
pub name: &'static str,
|
||||
pub language: tree_sitter::Language,
|
||||
pub highlights: &'static str,
|
||||
pub injections: &'static str,
|
||||
pub locals: &'static str,
|
||||
}
|
||||
|
||||
// These grammars extend the upstream base queries, rather than replacing them.
|
||||
static JS_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
|
||||
[
|
||||
tree_sitter_javascript::HIGHLIGHT_QUERY,
|
||||
tree_sitter_javascript::JSX_HIGHLIGHT_QUERY,
|
||||
]
|
||||
.join("\n")
|
||||
});
|
||||
static TS_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
|
||||
[
|
||||
tree_sitter_javascript::HIGHLIGHT_QUERY,
|
||||
tree_sitter_typescript::HIGHLIGHTS_QUERY,
|
||||
]
|
||||
.join("\n")
|
||||
});
|
||||
static TSX_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
|
||||
[
|
||||
JS_HIGHLIGHTS.as_str(),
|
||||
tree_sitter_typescript::HIGHLIGHTS_QUERY,
|
||||
]
|
||||
.join("\n")
|
||||
});
|
||||
static TS_LOCALS: LazyLock<String> = LazyLock::new(|| {
|
||||
[
|
||||
tree_sitter_javascript::LOCALS_QUERY,
|
||||
tree_sitter_typescript::LOCALS_QUERY,
|
||||
]
|
||||
.join("\n")
|
||||
});
|
||||
static CPP_HIGHLIGHTS: LazyLock<String> = LazyLock::new(|| {
|
||||
[
|
||||
tree_sitter_c::HIGHLIGHT_QUERY,
|
||||
tree_sitter_cpp::HIGHLIGHT_QUERY,
|
||||
]
|
||||
.join("\n")
|
||||
});
|
||||
|
||||
// The MIT-licensed upstream C++ crate bundles, but does not export, injections.scm.
|
||||
// This minimal AST query uses its raw-string delimiter/content nodes:
|
||||
// https://github.com/tree-sitter/tree-sitter-cpp/blob/v0.23.4/queries/injections.scm
|
||||
const CPP_INJECTIONS: &str = "
|
||||
(raw_string_literal
|
||||
delimiter: (raw_string_delimiter) @injection.language
|
||||
(raw_string_content) @injection.content)
|
||||
";
|
||||
|
||||
fn javascript_injections() -> &'static str {
|
||||
// The trailing upstream hbs/@glimmer rule uses a nonstandard #offset! directive,
|
||||
// not injection.* captures. Glimmer is outside the selected language set;
|
||||
// retain all standard tagged-template, regex and JSDoc injection queries.
|
||||
let query = tree_sitter_javascript::INJECTIONS_QUERY;
|
||||
query
|
||||
.split_once("\n; Parse Ember/")
|
||||
.map_or(query, |(query, _)| query)
|
||||
}
|
||||
|
||||
fn spec(
|
||||
name: &'static str,
|
||||
language: tree_sitter::Language,
|
||||
highlights: &'static str,
|
||||
injections: &'static str,
|
||||
locals: &'static str,
|
||||
) -> LanguageSpec {
|
||||
LanguageSpec {
|
||||
name,
|
||||
language,
|
||||
highlights,
|
||||
injections,
|
||||
locals,
|
||||
}
|
||||
}
|
||||
|
||||
static LANGUAGES: LazyLock<[LanguageSpec; 18]> = LazyLock::new(|| {
|
||||
[
|
||||
spec(
|
||||
"Rust",
|
||||
tree_sitter_rust::LANGUAGE.into(),
|
||||
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||
tree_sitter_rust::INJECTIONS_QUERY,
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"Ruby",
|
||||
tree_sitter_ruby::LANGUAGE.into(),
|
||||
tree_sitter_ruby::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
tree_sitter_ruby::LOCALS_QUERY,
|
||||
),
|
||||
spec(
|
||||
"Python",
|
||||
tree_sitter_python::LANGUAGE.into(),
|
||||
tree_sitter_python::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"JSON",
|
||||
tree_sitter_json::LANGUAGE.into(),
|
||||
tree_sitter_json::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"Bash",
|
||||
tree_sitter_bash::LANGUAGE.into(),
|
||||
tree_sitter_bash::HIGHLIGHT_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"Makefile",
|
||||
tree_sitter_make::LANGUAGE.into(),
|
||||
tree_sitter_make::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"Markdown",
|
||||
tree_sitter_md::LANGUAGE.into(),
|
||||
tree_sitter_md::HIGHLIGHT_QUERY_BLOCK,
|
||||
tree_sitter_md::INJECTION_QUERY_BLOCK,
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"JavaScript",
|
||||
tree_sitter_javascript::LANGUAGE.into(),
|
||||
JS_HIGHLIGHTS.as_str(),
|
||||
javascript_injections(),
|
||||
tree_sitter_javascript::LOCALS_QUERY,
|
||||
),
|
||||
spec(
|
||||
"TypeScript",
|
||||
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
|
||||
TS_HIGHLIGHTS.as_str(),
|
||||
javascript_injections(),
|
||||
TS_LOCALS.as_str(),
|
||||
),
|
||||
spec(
|
||||
"HTML",
|
||||
tree_sitter_html::LANGUAGE.into(),
|
||||
tree_sitter_html::HIGHLIGHTS_QUERY,
|
||||
tree_sitter_html::INJECTIONS_QUERY,
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"CSS",
|
||||
tree_sitter_css::LANGUAGE.into(),
|
||||
tree_sitter_css::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"YAML",
|
||||
tree_sitter_yaml::LANGUAGE.into(),
|
||||
tree_sitter_yaml::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"TOML",
|
||||
tree_sitter_toml_ng::LANGUAGE.into(),
|
||||
tree_sitter_toml_ng::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"C",
|
||||
tree_sitter_c::LANGUAGE.into(),
|
||||
tree_sitter_c::HIGHLIGHT_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"C++",
|
||||
tree_sitter_cpp::LANGUAGE.into(),
|
||||
CPP_HIGHLIGHTS.as_str(),
|
||||
CPP_INJECTIONS,
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"Go",
|
||||
tree_sitter_go::LANGUAGE.into(),
|
||||
tree_sitter_go::HIGHLIGHTS_QUERY,
|
||||
"",
|
||||
"",
|
||||
),
|
||||
spec(
|
||||
"TypeScript",
|
||||
tree_sitter_typescript::LANGUAGE_TSX.into(),
|
||||
TSX_HIGHLIGHTS.as_str(),
|
||||
javascript_injections(),
|
||||
TS_LOCALS.as_str(),
|
||||
),
|
||||
spec(
|
||||
"Markdown inline",
|
||||
tree_sitter_md::INLINE_LANGUAGE.into(),
|
||||
tree_sitter_md::HIGHLIGHT_QUERY_INLINE,
|
||||
tree_sitter_md::INJECTION_QUERY_INLINE,
|
||||
"",
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
pub(super) fn all() -> impl Iterator<Item = &'static LanguageSpec> {
|
||||
LANGUAGES.iter()
|
||||
}
|
||||
|
||||
pub(super) fn by_name(name: &str) -> Option<&'static LanguageSpec> {
|
||||
let name = name.trim();
|
||||
if name.eq_ignore_ascii_case("tsx") {
|
||||
return Some(&LANGUAGES[16]);
|
||||
}
|
||||
if name.eq_ignore_ascii_case("markdown_inline") || name.eq_ignore_ascii_case("markdown-inline")
|
||||
{
|
||||
return Some(&LANGUAGES[17]);
|
||||
}
|
||||
const ALIASES: &[(&str, &str)] = &[
|
||||
("rs", "Rust"),
|
||||
("rb", "Ruby"),
|
||||
("py", "Python"),
|
||||
("sh", "Bash"),
|
||||
("shell", "Bash"),
|
||||
("shellscript", "Bash"),
|
||||
("make", "Makefile"),
|
||||
("mk", "Makefile"),
|
||||
("md", "Markdown"),
|
||||
("js", "JavaScript"),
|
||||
("jsx", "JavaScript"),
|
||||
("node", "JavaScript"),
|
||||
("ecmascript", "JavaScript"),
|
||||
("ts", "TypeScript"),
|
||||
("htm", "HTML"),
|
||||
("xhtml", "HTML"),
|
||||
("yml", "YAML"),
|
||||
("cc", "C++"),
|
||||
("cpp", "C++"),
|
||||
("cxx", "C++"),
|
||||
("golang", "Go"),
|
||||
];
|
||||
let name = ALIASES
|
||||
.iter()
|
||||
.find(|(alias, _)| alias.eq_ignore_ascii_case(name))
|
||||
.map_or(name, |(_, name)| *name);
|
||||
all().find(|spec| spec.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
pub(super) fn detect(
|
||||
path: Option<&Path>,
|
||||
first_line: Option<&str>,
|
||||
) -> Option<&'static LanguageSpec> {
|
||||
path.and_then(detect_path)
|
||||
.or_else(|| first_line.and_then(detect_shebang))
|
||||
}
|
||||
|
||||
fn detect_path(path: &Path) -> Option<&'static LanguageSpec> {
|
||||
let filename = path.file_name()?.to_str()?;
|
||||
let language = match filename {
|
||||
"Makefile" | "makefile" | "GNUmakefile" | "BSDmakefile" => "Makefile",
|
||||
"Gemfile" | "Rakefile" | "Guardfile" | "Vagrantfile" | "Brewfile" | "Podfile"
|
||||
| "Fastfile" | "Appfile" | "Berksfile" | ".irbrc" => "Ruby",
|
||||
"SConstruct" | "SConscript" => "Python",
|
||||
".bashrc" | ".bash_profile" | ".bash_login" | ".bash_logout" | ".profile" => "Bash",
|
||||
"Cargo.lock" | "Pipfile" | "poetry.lock" | "uv.lock" => "TOML",
|
||||
_ => {
|
||||
let extension = path.extension()?.to_str()?;
|
||||
if matches!(extension, "C" | "H") {
|
||||
return by_name("C++");
|
||||
}
|
||||
let extension = if extension.bytes().any(|byte| byte.is_ascii_uppercase()) {
|
||||
Cow::Owned(extension.to_ascii_lowercase())
|
||||
} else {
|
||||
Cow::Borrowed(extension)
|
||||
};
|
||||
match extension.as_ref() {
|
||||
"rs" => "Rust",
|
||||
"rb" | "rake" | "gemspec" => "Ruby",
|
||||
"py" | "pyw" | "pyi" => "Python",
|
||||
"json" => "JSON",
|
||||
"sh" | "bash" => "Bash",
|
||||
"mk" | "mak" | "make" => "Makefile",
|
||||
"md" | "markdown" | "mdown" | "mkd" | "mdwn" => "Markdown",
|
||||
"js" | "jsx" | "mjs" | "cjs" => "JavaScript",
|
||||
"ts" | "mts" | "cts" => "TypeScript",
|
||||
"tsx" => "tsx",
|
||||
"html" | "htm" | "xhtml" => "HTML",
|
||||
"css" => "CSS",
|
||||
"yaml" | "yml" => "YAML",
|
||||
"toml" => "TOML",
|
||||
"c" | "h" => "C",
|
||||
"cc" | "cpp" | "cxx" | "c++" | "hh" | "hpp" | "hxx" | "h++" | "ipp" | "inl"
|
||||
| "tpp" => "C++",
|
||||
"go" => "Go",
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
};
|
||||
by_name(language)
|
||||
}
|
||||
|
||||
fn detect_shebang(line: &str) -> Option<&'static LanguageSpec> {
|
||||
let line = line
|
||||
.strip_prefix('\u{feff}')
|
||||
.unwrap_or(line)
|
||||
.strip_prefix("#!")?;
|
||||
let mut words = line.split_whitespace();
|
||||
let mut program = words.next()?;
|
||||
if Path::new(program).file_name()?.to_str()? == "env" {
|
||||
program = loop {
|
||||
let word = words.next()?;
|
||||
if matches!(word, "-u" | "--unset" | "-C" | "--chdir" | "--argv0") {
|
||||
words.next()?;
|
||||
} else if !word.starts_with('-') && !word.contains('=') {
|
||||
break word;
|
||||
}
|
||||
};
|
||||
}
|
||||
let program = Path::new(program).file_name()?.to_str()?;
|
||||
for (prefix, language) in [("python", "Python"), ("ruby", "Ruby")] {
|
||||
if let Some(suffix) = program.strip_prefix(prefix)
|
||||
&& (suffix.is_empty()
|
||||
|| (suffix.starts_with(|c: char| c.is_ascii_digit())
|
||||
&& suffix.chars().all(|c| c.is_ascii_digit() || c == '.')))
|
||||
{
|
||||
return by_name(language);
|
||||
}
|
||||
}
|
||||
match program {
|
||||
"sh" | "bash" => by_name("Bash"),
|
||||
"node" | "nodejs" => by_name("JavaScript"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn selected_files_and_auxiliary_grammars_are_registered() {
|
||||
assert_eq!(all().count(), 18);
|
||||
assert!(all().all(|spec| !spec.highlights.is_empty()));
|
||||
for (filename, language) in [
|
||||
("main.rs", "Rust"),
|
||||
("Gemfile", "Ruby"),
|
||||
("main.py", "Python"),
|
||||
("package.json", "JSON"),
|
||||
(".bashrc", "Bash"),
|
||||
("GNUmakefile", "Makefile"),
|
||||
("README.md", "Markdown"),
|
||||
("main.jsx", "JavaScript"),
|
||||
("main.ts", "TypeScript"),
|
||||
("index.html", "HTML"),
|
||||
("style.css", "CSS"),
|
||||
("config.yml", "YAML"),
|
||||
("Cargo.toml", "TOML"),
|
||||
("Cargo.lock", "TOML"),
|
||||
("main.c", "C"),
|
||||
("main.cpp", "C++"),
|
||||
("main.C", "C++"),
|
||||
("main.go", "Go"),
|
||||
] {
|
||||
assert_eq!(
|
||||
detect(Some(Path::new(filename)), None).map(|spec| spec.name),
|
||||
Some(language)
|
||||
);
|
||||
}
|
||||
let ts = detect(Some(Path::new("main.ts")), None).unwrap();
|
||||
let tsx = detect(Some(Path::new("main.tsx")), None).unwrap();
|
||||
assert_eq!(ts.name, tsx.name);
|
||||
assert!(!std::ptr::eq(ts, tsx));
|
||||
assert_ne!(ts.language, tsx.language);
|
||||
assert!(std::ptr::eq(tsx, by_name("TSX").unwrap()));
|
||||
assert!(by_name("markdown_inline").is_some());
|
||||
for filename in [
|
||||
"notes.txt",
|
||||
"main.hs",
|
||||
"main.scala",
|
||||
"main.lua",
|
||||
"Dockerfile",
|
||||
] {
|
||||
assert!(detect(Some(Path::new(filename)), None).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shebangs_support_bom_env_options_and_filename_precedence() {
|
||||
for (line, language) in [
|
||||
("#!/usr/bin/python3", "Python"),
|
||||
("\u{feff}#!/usr/bin/env -S python3.12 -u", "Python"),
|
||||
("#!/usr/bin/env -u PYTHONHOME FOO=bar python3", "Python"),
|
||||
("#!/usr/bin/ruby3.3", "Ruby"),
|
||||
("#!/bin/bash", "Bash"),
|
||||
("#! /usr/bin/env node", "JavaScript"),
|
||||
] {
|
||||
assert_eq!(
|
||||
detect(None, Some(line)).map(|spec| spec.name),
|
||||
Some(language)
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
detect(Some(Path::new("main.rs")), Some("#!/bin/bash"))
|
||||
.unwrap()
|
||||
.name,
|
||||
"Rust"
|
||||
);
|
||||
assert!(detect(None, Some("#!/usr/bin/python-not-real")).is_none());
|
||||
assert!(detect(None, Some("#!/usr/bin/perl")).is_none());
|
||||
assert!(detect(None, Some("plain text")).is_none());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user