commit 17fd3454832f6f48b33a9edf4f7ed79953eab7cf
parent a595dfc0108c294f12b851a19a1355bd06312a67
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 27 Jun 2025 02:19:49 -0700
Use emmylua_parser instead of tree-sitter (#1)
* Rewrite highlighter to use emmylua_parser
* Rewrite completer to use emmylua_parser
Diffstat:
| M | Cargo.lock | | | 396 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
| M | Cargo.toml | | | 5 | ++--- |
| D | queries/locals.scm | | | 35 | ----------------------------------- |
| M | src/completion.rs | | | 246 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
| M | src/editor.rs | | | 6 | +++--- |
| D | src/highlight.rs | | | 150 | ------------------------------------------------------------------------------- |
| M | src/main.rs | | | 17 | +++++++++++++---- |
| A | src/parse.rs | | | 346 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
8 files changed, 831 insertions(+), 370 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -98,6 +98,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -119,6 +125,21 @@ dependencies = [
]
[[package]]
+name = "base62"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10e52a7bcb1d6beebee21fb5053af9e3cbb7a7ed1a4909e534040e676437ab1f"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -268,12 +289,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
+name = "countme"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
name = "crossterm"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
"crossterm_winapi",
"mio",
"parking_lot",
@@ -331,6 +383,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
+name = "emmylua_parser"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303f3ff1299f8fbfcd2cd9381dfb67ff65b9336b8cd705ce033fc3172c00e90"
+dependencies = [
+ "rowan",
+ "rust-i18n",
+ "serde",
+]
+
+[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -416,6 +479,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
+name = "glob"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+
+[[package]]
+name = "globset"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -452,6 +551,22 @@ dependencies = [
]
[[package]]
+name = "ignore"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -464,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.15.4",
]
[[package]]
@@ -475,6 +590,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
@@ -526,7 +650,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
"libc",
]
@@ -596,13 +720,12 @@ dependencies = [
"comfy-table",
"ctrlc",
"directories",
+ "emmylua_parser",
"lazy_static",
"mlua",
"nu-ansi-term",
"reedline",
- "tree-sitter",
- "tree-sitter-highlight",
- "tree-sitter-lua",
+ "rowan",
]
[[package]]
@@ -646,7 +769,7 @@ dependencies = [
"mlua-sys",
"num-traits",
"parking_lot",
- "rustc-hash",
+ "rustc-hash 2.1.1",
"rustversion",
]
@@ -670,13 +793,22 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
+name = "normpath"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -792,7 +924,7 @@ version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
]
[[package]]
@@ -815,7 +947,7 @@ dependencies = [
"chrono",
"crossterm",
"fd-lock",
- "itertools",
+ "itertools 0.13.0",
"nu-ansi-term",
"serde",
"strip-ansi-escapes",
@@ -856,6 +988,72 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
+name = "rowan"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
+dependencies = [
+ "countme",
+ "hashbrown 0.14.5",
+ "rustc-hash 1.1.0",
+ "text-size",
+]
+
+[[package]]
+name = "rust-i18n"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
+dependencies = [
+ "globwalk",
+ "once_cell",
+ "regex",
+ "rust-i18n-macro",
+ "rust-i18n-support",
+ "smallvec",
+]
+
+[[package]]
+name = "rust-i18n-macro"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
+dependencies = [
+ "glob",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "rust-i18n-support",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "syn",
+]
+
+[[package]]
+name = "rust-i18n-support"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
+dependencies = [
+ "arc-swap",
+ "base62",
+ "globwalk",
+ "itertools 0.11.0",
+ "lazy_static",
+ "normpath",
+ "once_cell",
+ "proc-macro2",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "siphasher",
+ "toml",
+ "triomphe",
+]
+
+[[package]]
name = "rustc-demangle"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -863,6 +1061,12 @@ checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
@@ -873,7 +1077,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@@ -886,7 +1090,7 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
- "bitflags",
+ "bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.9.4",
@@ -906,6 +1110,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -937,7 +1150,6 @@ version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
- "indexmap",
"itoa",
"memchr",
"ryu",
@@ -945,6 +1157,28 @@ dependencies = [
]
[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -990,6 +1224,12 @@ dependencies = [
]
[[package]]
+name = "siphasher"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+
+[[package]]
name = "slab"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1002,10 +1242,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
-name = "streaming-iterator"
-version = "0.1.9"
+name = "stable_deref_trait"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strip-ansi-escapes"
@@ -1053,6 +1293,12 @@ dependencies = [
]
[[package]]
+name = "text-size"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
+
+[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1082,6 +1328,47 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1123,45 +1410,14 @@ dependencies = [
]
[[package]]
-name = "tree-sitter"
-version = "0.25.6"
+name = "triomphe"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7cf18d43cbf0bfca51f657132cc616a5097edc4424d538bae6fa60142eaf9f0"
+checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85"
dependencies = [
- "cc",
- "regex",
- "regex-syntax",
- "serde_json",
- "streaming-iterator",
- "tree-sitter-language",
-]
-
-[[package]]
-name = "tree-sitter-highlight"
-version = "0.25.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6eea684ab5dd71e19f6c0add355be96f2b4eb58327cb305337415208681761aa"
-dependencies = [
- "regex",
- "streaming-iterator",
- "thiserror",
- "tree-sitter",
-]
-
-[[package]]
-name = "tree-sitter-language"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8"
-
-[[package]]
-name = "tree-sitter-lua"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb9adf0965fec58e7660cbb3a059dbb12ebeec9459e6dcbae3db004739641e"
-dependencies = [
- "cc",
- "tree-sitter-language",
+ "arc-swap",
+ "serde",
+ "stable_deref_trait",
]
[[package]]
@@ -1183,6 +1439,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1204,6 +1466,16 @@ dependencies = [
]
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1296,6 +1568,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1516,6 +1797,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
+name = "winnow"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -23,10 +23,9 @@ color-eyre = "0.6.5"
comfy-table = "7.1.4"
ctrlc = "3.4.7"
directories = "6.0.0"
+emmylua_parser = "0.10.8"
lazy_static = "1.5.0"
mlua = { version = "0.10.5", features = ["anyhow", "send", "async"] }
nu-ansi-term = "0.50.1"
reedline = "0.40.0"
-tree-sitter = "0.25.6"
-tree-sitter-highlight = "0.25.6"
-tree-sitter-lua = "0.2.0"
+rowan = "0.16.1"
diff --git a/queries/locals.scm b/queries/locals.scm
@@ -1,35 +0,0 @@
-; Scopes
-
-[
- (chunk)
- (do_statement)
- (while_statement)
- (repeat_statement)
- (if_statement)
- (for_statement)
- (function_definition)
- (function_declaration)
-] @local.scope
-
-; Definitions
-
-(assignment_statement
- (variable_list
- (identifier) @local.definition))
-
-; technically a "local function name() ... end" is
-; local name
-; name = function() ... end
-; so we need to ensure that the function name goes into the parent scope
-; to do what we treat it as a special case and hoist it in the code
-(function_declaration
- name: (identifier) @local.fn_name)
-
-(for_generic_clause
- (variable_list
- (identifier) @local.definition))
-
-(for_numeric_clause
- name: (identifier) @local.definition)
-
-(parameters (identifier) @local.definition)
diff --git a/src/completion.rs b/src/completion.rs
@@ -1,65 +1,42 @@
+use emmylua_parser::{
+ LuaAst, LuaAstNode, LuaAstToken, LuaBlock, LuaNameExpr, LuaParser, LuaSyntaxTree, ParserConfig,
+};
use mlua::prelude::*;
use reedline::{Completer, Span, Suggestion};
-use tree_sitter::{Parser, Point, Query, QueryCursor, Range, StreamingIterator, Tree};
+use rowan::TextRange;
#[derive(Debug)]
struct Variable {
- range: Range,
+ range: TextRange,
name: String,
}
#[derive(Debug)]
struct Scope {
- range: Range,
+ range: TextRange,
variables: Vec<Variable>,
}
pub struct LuaCompleter {
lua: Lua,
- parser: Parser,
- tree: Tree,
+ tree: LuaSyntaxTree,
- locals_query: Query,
- identifiers_query: Query,
scopes: Vec<Scope>,
text: String,
}
impl LuaCompleter {
pub fn new(lua: Lua) -> Self {
- let mut parser = Parser::new();
-
- parser
- .set_language(&tree_sitter_lua::LANGUAGE.into())
- .unwrap();
-
- let tree = parser.parse("", None).unwrap();
-
- let locals_query = Query::new(
- &tree_sitter_lua::LANGUAGE.into(),
- include_str!("../queries/locals.scm"),
- )
- .unwrap();
-
- let identifiers_query = Query::new(
- &tree_sitter_lua::LANGUAGE.into(),
- "(identifier) @identifier",
- )
- .unwrap();
-
Self {
lua,
- parser,
- tree,
- locals_query,
- identifiers_query,
+ tree: LuaParser::parse("", ParserConfig::default()),
scopes: Vec::new(),
text: String::new(),
}
}
fn refresh_tree(&mut self, text: &str) {
- self.tree = self.parser.parse(text, None).unwrap();
+ self.tree = LuaParser::parse(text, ParserConfig::default());
self.text = text.to_string();
self.scopes = self.resolve_scopes();
}
@@ -74,90 +51,89 @@ impl LuaCompleter {
}
fn resolve_scopes(&self) -> Vec<Scope> {
- let mut cursor = QueryCursor::new();
-
- let matches = cursor.matches(
- &self.locals_query,
- self.tree.root_node(),
- self.text.as_bytes(),
- );
- let names = self.locals_query.capture_names();
-
- let lines = self.text.split("\n").collect::<Vec<_>>();
-
- let mut scopes: Vec<Scope> = vec![
- // fallback scope
- Scope {
- range: Range {
- start_byte: 0,
- end_byte: self.text.len(),
- start_point: Point::new(0, 0),
- end_point: Point::new(lines.len(), lines.last().map_or("", |v| v).len()),
- },
- variables: Vec::new(),
- },
- ];
- let mut scope_hierarchy: Vec<usize> = vec![0];
-
- matches.for_each(|m| {
- for capture in m.captures {
- let name = names[capture.index as usize];
- let text = capture
- .node
- .utf8_text(self.text.as_bytes())
- .unwrap()
- .to_string();
- let range = capture.node.range();
-
- match name {
- "local.definition" => {
- let last = scope_hierarchy.last().unwrap();
- let last_scope = &mut scopes[*last];
-
- last_scope.variables.push(Variable { range, name: text });
+ let mut scopes = Vec::new();
+
+ let chunk = self.tree.get_chunk_node();
+
+ for scope in chunk.descendants::<LuaBlock>() {
+ let mut variables = Vec::new();
+
+ match scope.get_parent() {
+ Some(LuaAst::LuaClosureExpr(closure)) => {
+ if let Some(params) = closure.get_params_list() {
+ for param in params.get_params() {
+ if let Some(token) = param.get_name_token() {
+ variables.push(Variable {
+ range: param.get_range(),
+ name: token.get_name_text().to_string(),
+ });
+ }
+ }
}
- "local.fn_name" => {
- let len = scope_hierarchy.len();
- let parent = scope_hierarchy
- .get(len - 2)
- .unwrap_or_else(|| scope_hierarchy.last().unwrap());
- let scope = &mut scopes[*parent];
-
- scope.variables.push(Variable { range, name: text });
+ }
+ Some(LuaAst::LuaForRangeStat(range)) => {
+ for token in range.get_var_name_list() {
+ variables.push(Variable {
+ range: token.get_range(),
+ name: token.get_name_text().to_string(),
+ })
}
- "local.scope" => {
- let scope = Scope {
- range: capture.node.range(),
- variables: Vec::new(),
- };
-
- if let Some(last) = scope_hierarchy.last() {
- // outside
- let last_scope = &scopes[*last];
-
- if scope.range.end_byte > last_scope.range.end_byte {
- scope_hierarchy.pop();
+ }
+ Some(LuaAst::LuaForStat(stat)) => {
+ if let Some(token) = stat.get_var_name() {
+ variables.push(Variable {
+ range: token.get_range(),
+ name: token.get_name_text().to_string(),
+ });
+ }
+ }
+ _ => {}
+ }
+
+ // TODO; for loops
+
+ for node in scope.children::<LuaAst>() {
+ match node {
+ LuaAst::LuaLocalFuncStat(stat) => {
+ if let Some(name) = stat.get_local_name() {
+ if let Some(token) = name.get_name_token() {
+ variables.push(Variable {
+ range: token.get_range(),
+ name: token.get_name_text().to_string(),
+ });
+ }
+ }
+ }
+ LuaAst::LuaLocalStat(stat) => {
+ for name in stat.get_local_name_list() {
+ if let Some(token) = name.get_name_token() {
+ variables.push(Variable {
+ range: stat.get_range(),
+ name: token.get_name_text().to_string(),
+ });
}
}
-
- scope_hierarchy.push(scopes.len());
- scopes.push(scope);
}
_ => {}
}
}
- });
+
+ scopes.push(Scope {
+ range: scope.get_range(),
+ variables,
+ });
+ }
scopes
}
- fn locals(&self, position: usize) -> Vec<String> {
+ fn locals(&self, position: u32) -> Vec<String> {
let mut variables = Vec::new();
for scope in self.scopes.iter() {
- if position > scope.range.start_byte && position < scope.range.end_byte {
+ if position >= scope.range.start().into() && position <= scope.range.end().into() {
for var in scope.variables.iter() {
- if position > var.range.end_byte {
+ if position >= var.range.end().into() {
variables.push(var.name.clone());
}
}
@@ -211,7 +187,7 @@ impl LuaCompleter {
// to summarize, this function is not properly named
//
// globals either exist or are an extension of _ENV
- fn autocomplete_upvalue(&self, query: &str, position: usize) -> Vec<String> {
+ fn autocomplete_upvalue(&self, query: &str, position: u32) -> Vec<String> {
let mut upvalues = self.locals(position);
upvalues.extend(self.globals());
upvalues.sort();
@@ -222,26 +198,17 @@ impl LuaCompleter {
.collect()
}
- fn current_identifier(&self, position: usize) -> Option<(Range, String)> {
- let mut cursor = QueryCursor::new();
+ fn current_identifier(&self, position: u32) -> Option<(TextRange, String)> {
+ let chunk = self.tree.get_chunk_node();
- let mut matches = cursor.matches(
- &self.identifiers_query,
- self.tree.root_node(),
- self.text.as_bytes(),
- );
+ for identifier in chunk.descendants::<LuaNameExpr>() {
+ let range = identifier.get_range();
- while let Some(m) = matches.next() {
- for capture in m.captures {
- let text = capture
- .node
- .utf8_text(self.text.as_bytes())
- .unwrap()
- .to_string();
- let range = capture.node.range();
-
- if position > range.start_byte && position < range.end_byte {
- return Some((range, text.to_string()));
+ if position >= range.start().into() && position < range.end().into() {
+ if let Some(name) = identifier.get_name_text() {
+ return Some((range, name));
+ } else {
+ return None;
}
}
}
@@ -252,6 +219,7 @@ impl LuaCompleter {
impl Completer for LuaCompleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
+ let pos = pos as u32;
// TODO; proper autocomplete
self.refresh_tree(line);
@@ -261,7 +229,7 @@ impl Completer for LuaCompleter {
.into_iter()
.map(|s| Suggestion {
value: s,
- span: Span::new(range.start_byte, range.end_byte),
+ span: Span::new(range.start().into(), range.end().into()),
..Default::default()
})
.collect();
@@ -275,9 +243,9 @@ impl Completer for LuaCompleter {
mod tests {
use super::*;
- fn line_to_position(line: usize, text: &str) -> usize {
+ fn line_to_position(line: usize, text: &str) -> u32 {
let split = text.split("\n").collect::<Vec<_>>();
- split[0..line].join("\n").len()
+ split[0..line].join("\n").len() as u32
}
#[test]
@@ -298,6 +266,20 @@ mod tests {
end
-- 13: foo, bar
+
+ for i = 1, 10 do
+ -- 16: foo, bar, i
+ print(i)
+ end
+
+ -- 20: foo, bar
+
+ for i, v in pairs(_G) do
+ -- 23: foo, bar, i, v
+ print(i, v)
+ end
+
+ -- 27: foo, bar
"#;
completer.refresh_tree(text);
@@ -321,6 +303,26 @@ mod tests {
&["foo", "bar"].as_slice(),
&completer.locals(line_to_position(13, text)),
);
+
+ assert_eq!(
+ &["foo", "bar", "i"].as_slice(),
+ &completer.locals(line_to_position(16, text)),
+ );
+
+ assert_eq!(
+ &["foo", "bar"].as_slice(),
+ &completer.locals(line_to_position(20, text)),
+ );
+
+ assert_eq!(
+ &["foo", "bar", "i", "v"].as_slice(),
+ &completer.locals(line_to_position(23, text)),
+ );
+
+ assert_eq!(
+ &["foo", "bar"].as_slice(),
+ &completer.locals(line_to_position(27, text)),
+ );
}
#[test]
diff --git a/src/editor.rs b/src/editor.rs
@@ -15,8 +15,8 @@ use reedline::{
};
use crate::{
- completion::LuaCompleter, format::TableFormat, highlight::LuaHighlighter, hinter::LuaHinter,
- inspect::display_basic, validator::LuaValidator,
+ completion::LuaCompleter, format::TableFormat, hinter::LuaHinter, inspect::display_basic,
+ parse::LuaHighlighter, validator::LuaValidator,
};
pub struct Editor {
@@ -69,9 +69,9 @@ impl Editor {
let ide_menu = IdeMenu::default().with_name("completion_menu");
let mut editor = Reedline::create()
- .with_highlighter(Box::new(LuaHighlighter::new()))
.with_validator(Box::new(LuaValidator::new()))
.with_completer(Box::new(LuaCompleter::new(lua.clone())))
+ .with_highlighter(Box::new(LuaHighlighter))
.with_hinter(Box::new(LuaHinter))
.with_edit_mode(Box::new(Emacs::new(keybindings)))
.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu)));
diff --git a/src/highlight.rs b/src/highlight.rs
@@ -1,150 +0,0 @@
-use std::cell::RefCell;
-
-use nu_ansi_term::{Color, Style};
-use reedline::StyledText;
-use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent};
-
-const LUA_HIGHLIGHT_NAMES: &[&str] = &[
- "keyword",
- "keyword.return",
- "keyword.function",
- "keyword.operator",
- "punctuation.delimiter",
- "punctuation.bracket",
- "variable",
- "variable.builtin",
- "constant",
- "constant.builtin",
- "attribute",
- "function",
- "function.call",
- "function.builtin",
- "method",
- "method.call",
- "parameter",
- "string",
- "string.escape",
- "boolean",
- "number",
- "field",
- "constructor",
- "label",
- "repeat",
- "conditional",
- "operator",
- "comment",
- "preproc",
-];
-
-const fn style_fg(color: Color) -> Style {
- Style {
- foreground: Some(color),
- background: None,
- is_bold: false,
- is_dimmed: false,
- is_italic: false,
- is_underline: false,
- is_blink: false,
- is_reverse: false,
- is_hidden: false,
- is_strikethrough: false,
- prefix_with_reset: true,
- }
-}
-
-const STYLES: &[Style] = &[
- style_fg(Color::Purple), // keyword
- style_fg(Color::Purple), // keyword.return
- style_fg(Color::Purple), // keyword.function
- style_fg(Color::Purple), // keyword.operator
- style_fg(Color::LightGray), // punctuation.delimiter
- style_fg(Color::LightRed), // punctuation.bracket
- style_fg(Color::LightGray), // variable
- style_fg(Color::LightRed), // variable.builtin
- style_fg(Color::LightYellow), // constant
- style_fg(Color::LightYellow), // constant.builtin
- style_fg(Color::Red), // attribute
- style_fg(Color::LightBlue), // function
- style_fg(Color::LightBlue), // function.call
- style_fg(Color::LightBlue), // function.builtin
- style_fg(Color::LightBlue), // method
- style_fg(Color::LightBlue), // method.call
- style_fg(Color::Red), // parameter
- style_fg(Color::Green), // string
- style_fg(Color::Cyan), // string.escape
- style_fg(Color::LightYellow), // boolean
- style_fg(Color::LightYellow), // number
- style_fg(Color::LightGray), // field
- style_fg(Color::LightRed), // constructor
- style_fg(Color::LightGray), // label
- style_fg(Color::Purple), // repeat
- style_fg(Color::Purple), // conditional
- style_fg(Color::LightBlue), // operator
- style_fg(Color::DarkGray), // comment
- style_fg(Color::DarkGray), // preproc
-];
-
-pub struct LuaHighlighter {
- highlighter: RefCell<tree_sitter_highlight::Highlighter>,
- config: HighlightConfiguration,
-}
-
-impl LuaHighlighter {
- pub fn new() -> Self {
- let highlighter = tree_sitter_highlight::Highlighter::new();
- let mut config = HighlightConfiguration::new(
- tree_sitter_lua::LANGUAGE.into(),
- "lua",
- tree_sitter_lua::HIGHLIGHTS_QUERY,
- tree_sitter_lua::INJECTIONS_QUERY,
- tree_sitter_lua::LOCALS_QUERY,
- )
- .unwrap();
-
- config.configure(LUA_HIGHLIGHT_NAMES);
-
- Self {
- highlighter: RefCell::new(highlighter),
- config,
- }
- }
-}
-
-impl reedline::Highlighter for LuaHighlighter {
- fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
- let mut binding = self.highlighter.borrow_mut();
- let highlights = binding.highlight(&self.config, line.as_bytes(), None, |_| None);
-
- let mut text = StyledText::new();
-
- let highlights = if let Ok(highlights) = highlights {
- highlights
- } else {
- text.push((Style::new(), line.to_string()));
-
- return text;
- };
-
- let mut style = Style::new();
- let mut highlight = 0usize;
-
- for event in highlights.flatten() {
- match event {
- HighlightEvent::Source { start, end } => {
- text.push((style, line[start..end].to_string()));
-
- if highlight == 18 {
- style = STYLES[17];
- }
- }
- HighlightEvent::HighlightStart(s) => {
- style = STYLES[s.0];
- highlight = s.0;
- }
- HighlightEvent::HighlightEnd => {}
- }
- }
-
- text
- }
-}
diff --git a/src/main.rs b/src/main.rs
@@ -7,19 +7,20 @@ use std::{
use clap::{Parser, Subcommand};
use editor::Editor;
-use highlight::LuaHighlighter;
+use emmylua_parser::{LuaParser, ParserConfig};
use mlua::prelude::*;
use reedline::Highlighter;
use format::comfy_table;
use inspect::inspect;
+use parse::LuaHighlighter;
mod completion;
mod editor;
mod format;
-mod highlight;
mod hinter;
mod inspect;
+mod parse;
mod validator;
#[derive(Parser)]
@@ -44,6 +45,8 @@ enum Command {
/// Path to Lua file (default: stdin)
path: Option<PathBuf>,
},
+ /// DEBUG: Parse a Lua file with emmylua_parser
+ Parse { path: PathBuf },
}
fn eval_lua(file: String, path: &Path) -> LuaResult<()> {
@@ -107,11 +110,17 @@ fn main() -> color_eyre::Result<()> {
buffer
};
- let highlighter = LuaHighlighter::new();
- let text = highlighter.highlight(&file, 0);
+ let text = LuaHighlighter.highlight(&file, 0);
println!("{}", text.render_simple());
}
+ Some(Command::Parse { path }) => {
+ let code = fs::read_to_string(path)?;
+
+ let tree = LuaParser::parse(&code, ParserConfig::default());
+
+ parse::debug_tree(&tree);
+ }
}
Ok(())
diff --git a/src/parse.rs b/src/parse.rs
@@ -0,0 +1,346 @@
+use emmylua_parser::{
+ LuaAst, LuaAstNode, LuaKind, LuaParser, LuaSyntaxKind, LuaSyntaxNode, LuaSyntaxToken,
+ LuaSyntaxTree, LuaTokenKind, ParserConfig,
+};
+use nu_ansi_term::{Color, Style};
+use reedline::StyledText;
+use rowan::WalkEvent;
+
+fn node_name(node: &LuaAst) -> Option<&'static str> {
+ match node {
+ LuaAst::LuaChunk(_) => Some("chunk"),
+ LuaAst::LuaBlock(_) => Some("block"),
+ LuaAst::LuaAssignStat(_) => Some("assignment"),
+ LuaAst::LuaLocalStat(_) => Some("local"),
+ LuaAst::LuaCallExprStat(_) => Some("call_statement"),
+ LuaAst::LuaLabelStat(_) => Some("label"),
+ LuaAst::LuaBreakStat(_) => Some("break"),
+ LuaAst::LuaGotoStat(_) => Some("goto"),
+ LuaAst::LuaDoStat(_) => Some("do"),
+ LuaAst::LuaWhileStat(_) => Some("while"),
+ LuaAst::LuaRepeatStat(_) => Some("repeat"),
+ LuaAst::LuaIfStat(_) => Some("if"),
+ LuaAst::LuaForStat(_) => Some("for_range"),
+ LuaAst::LuaForRangeStat(_) => Some("for"),
+ LuaAst::LuaFuncStat(_) => Some("function"),
+ LuaAst::LuaLocalFuncStat(_) => Some("local_function"),
+ LuaAst::LuaReturnStat(_) => Some("return"),
+ LuaAst::LuaNameExpr(_) => Some("identifier"),
+ LuaAst::LuaIndexExpr(_) => Some("index"),
+ LuaAst::LuaTableExpr(_) => Some("table"),
+ LuaAst::LuaBinaryExpr(_) => Some("binop"),
+ LuaAst::LuaUnaryExpr(_) => Some("unop"),
+ LuaAst::LuaParenExpr(_) => Some("parenthesis"),
+ LuaAst::LuaCallExpr(_) => Some("call"),
+ LuaAst::LuaLiteralExpr(_) => Some("literal"),
+ LuaAst::LuaClosureExpr(_) => Some("closure"),
+ LuaAst::LuaTableField(_) => Some("table_field"),
+ LuaAst::LuaParamList(_) => Some("parameters"),
+ LuaAst::LuaParamName(_) => Some("parameter"),
+ LuaAst::LuaCallArgList(_) => Some("arguments"),
+ LuaAst::LuaLocalName(_) => Some("identifier"),
+ LuaAst::LuaLocalAttribute(_) => Some("attribute"),
+ LuaAst::LuaElseIfClauseStat(_) => Some("elseif"),
+ LuaAst::LuaElseClauseStat(_) => Some("else"),
+ LuaAst::LuaComment(_) => Some("comment"),
+ _ => None,
+ }
+}
+
+fn should_print_contents(node: &LuaAst) -> bool {
+ matches!(
+ node,
+ LuaAst::LuaLabelStat(_)
+ | LuaAst::LuaGotoStat(_)
+ | LuaAst::LuaNameExpr(_)
+ | LuaAst::LuaIndexExpr(_)
+ | LuaAst::LuaTableExpr(_)
+ | LuaAst::LuaBinaryExpr(_)
+ | LuaAst::LuaUnaryExpr(_)
+ | LuaAst::LuaParenExpr(_)
+ | LuaAst::LuaCallExpr(_)
+ | LuaAst::LuaLiteralExpr(_)
+ | LuaAst::LuaTableField(_)
+ | LuaAst::LuaParamName(_)
+ | LuaAst::LuaLocalName(_)
+ | LuaAst::LuaLocalAttribute(_)
+ | LuaAst::LuaComment(_)
+ )
+}
+
+pub fn debug_tree(tree: &LuaSyntaxTree) {
+ let chunk = tree.get_chunk_node();
+ let mut depth = -1isize;
+
+ for event in chunk.walk_descendants::<LuaAst>() {
+ match event {
+ WalkEvent::Enter(node) => {
+ if let Some(name) = node_name(&node) {
+ depth += 1;
+
+ let syntax = node.syntax();
+ let range = syntax.text_range();
+ let start: u32 = range.start().into();
+ let end: u32 = range.end().into();
+
+ let text = if should_print_contents(&node) {
+ format!("`{}`", syntax.text())
+ } else {
+ String::new()
+ };
+
+ println!(
+ "{}{name} [{start}-{end}] {}",
+ " ".repeat(depth as usize),
+ text
+ )
+ }
+ }
+ WalkEvent::Leave(_) => {
+ depth -= 1;
+ }
+ }
+ }
+}
+
+fn default_token_color(token: &LuaSyntaxToken) -> Color {
+ let kind = match token.kind() {
+ LuaKind::Syntax(_) => unreachable!(),
+ LuaKind::Token(kind) => kind,
+ };
+
+ match kind {
+ LuaTokenKind::TkWhitespace
+ | LuaTokenKind::TkEndOfLine
+ | LuaTokenKind::TkEof
+ | LuaTokenKind::TkUnknown
+ | LuaTokenKind::None => Color::Default,
+
+ LuaTokenKind::TkBreak
+ | LuaTokenKind::TkDo
+ | LuaTokenKind::TkElse
+ | LuaTokenKind::TkElseIf
+ | LuaTokenKind::TkEnd
+ | LuaTokenKind::TkFor
+ | LuaTokenKind::TkFunction
+ | LuaTokenKind::TkGoto
+ | LuaTokenKind::TkIf
+ | LuaTokenKind::TkIn
+ | LuaTokenKind::TkLocal
+ | LuaTokenKind::TkRepeat
+ | LuaTokenKind::TkReturn
+ | LuaTokenKind::TkThen
+ | LuaTokenKind::TkUntil
+ | LuaTokenKind::TkWhile
+ | LuaTokenKind::TkGlobal => Color::Purple,
+
+ LuaTokenKind::TkOr | LuaTokenKind::TkNot | LuaTokenKind::TkAnd => Color::Cyan,
+
+ LuaTokenKind::TkFalse | LuaTokenKind::TkTrue | LuaTokenKind::TkNil => Color::Red,
+
+ LuaTokenKind::TkInt | LuaTokenKind::TkFloat | LuaTokenKind::TkComplex => Color::LightYellow,
+
+ LuaTokenKind::TkPlus
+ | LuaTokenKind::TkMinus
+ | LuaTokenKind::TkMul
+ | LuaTokenKind::TkDiv
+ | LuaTokenKind::TkIDiv
+ | LuaTokenKind::TkDot
+ | LuaTokenKind::TkConcat
+ | LuaTokenKind::TkDots
+ | LuaTokenKind::TkComma
+ | LuaTokenKind::TkAssign
+ | LuaTokenKind::TkEq
+ | LuaTokenKind::TkGe
+ | LuaTokenKind::TkLe
+ | LuaTokenKind::TkNe
+ | LuaTokenKind::TkShl
+ | LuaTokenKind::TkShr
+ | LuaTokenKind::TkLt
+ | LuaTokenKind::TkGt
+ | LuaTokenKind::TkMod
+ | LuaTokenKind::TkPow
+ | LuaTokenKind::TkLen
+ | LuaTokenKind::TkBitAnd
+ | LuaTokenKind::TkBitOr
+ | LuaTokenKind::TkBitXor
+ | LuaTokenKind::TkColon
+ | LuaTokenKind::TkDbColon
+ | LuaTokenKind::TkSemicolon
+ | LuaTokenKind::TkLeftBracket
+ | LuaTokenKind::TkRightBracket
+ | LuaTokenKind::TkLeftParen
+ | LuaTokenKind::TkRightParen
+ | LuaTokenKind::TkLeftBrace
+ | LuaTokenKind::TkRightBrace => Color::LightGray,
+
+ LuaTokenKind::TkName => Color::LightGray,
+
+ LuaTokenKind::TkString | LuaTokenKind::TkLongString => Color::Green,
+
+ LuaTokenKind::TkShortComment | LuaTokenKind::TkLongComment | LuaTokenKind::TkShebang => {
+ Color::DarkGray
+ }
+
+ // EmmyLua
+ LuaTokenKind::TkTagClass
+ | LuaTokenKind::TkTagEnum
+ | LuaTokenKind::TkTagInterface
+ | LuaTokenKind::TkTagAlias
+ | LuaTokenKind::TkTagModule
+ | LuaTokenKind::TkTagField
+ | LuaTokenKind::TkTagType
+ | LuaTokenKind::TkTagParam
+ | LuaTokenKind::TkTagReturn
+ | LuaTokenKind::TkTagOverload
+ | LuaTokenKind::TkTagGeneric
+ | LuaTokenKind::TkTagSee
+ | LuaTokenKind::TkTagDeprecated
+ | LuaTokenKind::TkTagAsync
+ | LuaTokenKind::TkTagCast
+ | LuaTokenKind::TkTagOther
+ | LuaTokenKind::TkTagVisibility
+ | LuaTokenKind::TkTagReadonly
+ | LuaTokenKind::TkTagDiagnostic
+ | LuaTokenKind::TkTagMeta
+ | LuaTokenKind::TkTagVersion
+ | LuaTokenKind::TkTagAs
+ | LuaTokenKind::TkTagNodiscard
+ | LuaTokenKind::TkTagOperator
+ | LuaTokenKind::TkTagMapping
+ | LuaTokenKind::TkTagNamespace
+ | LuaTokenKind::TkTagUsing
+ | LuaTokenKind::TkTagSource
+ | LuaTokenKind::TkTagReturnCast => Color::LightMagenta,
+ LuaTokenKind::TkDocVisibility => Color::Purple,
+ _ => Color::DarkGray,
+ }
+}
+
+fn modify_token_color(token: &LuaSyntaxToken, parent: &LuaSyntaxNode) -> Option<Color> {
+ let tk_kind = match token.kind() {
+ LuaKind::Syntax(_) => unreachable!(),
+ LuaKind::Token(kind) => kind,
+ };
+
+ let node_kind = match parent.kind() {
+ LuaKind::Syntax(kind) => kind,
+ LuaKind::Token(_) => unreachable!(),
+ };
+
+ match (tk_kind, node_kind) {
+ (LuaTokenKind::TkName, LuaSyntaxKind::TypeName) => Some(Color::Yellow),
+ (LuaTokenKind::TkName, LuaSyntaxKind::DocTagParam) => Some(Color::Red),
+ (LuaTokenKind::TkName, LuaSyntaxKind::ParamName) => Some(Color::Red),
+ (LuaTokenKind::TkName, _) => {
+ let parent_kind = if let Some(p) = parent.parent() {
+ match p.kind() {
+ LuaKind::Syntax(kind) => kind,
+ LuaKind::Token(_) => unreachable!(),
+ }
+ } else {
+ return None;
+ };
+
+ match (node_kind, parent_kind) {
+ (_, LuaSyntaxKind::CallExpr) => Some(Color::Blue),
+ (_, LuaSyntaxKind::LocalFuncStat) => Some(Color::Blue),
+ (LuaSyntaxKind::IndexExpr, LuaSyntaxKind::FuncStat) => Some(Color::Blue),
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+}
+
+// this function is rubbish but it works
+fn highlight_string(text: &str) -> StyledText {
+ let mut styled = StyledText::new();
+
+ let mut chars = text.chars();
+ let mut current = String::new();
+
+ while let Some(c) = chars.next() {
+ if c != '\\' {
+ current.push(c);
+ continue;
+ }
+
+ styled.push((Style::new().fg(Color::Green), current.clone()));
+ current.clear();
+
+ let modifier = if let Some(c) = chars.next() {
+ c
+ } else {
+ // incomplete string
+ styled.push((Style::new().fg(Color::Cyan), String::from("\\")));
+ break;
+ };
+
+ if modifier == 'x' || modifier == 'X' {
+ let hex1 = chars.next().map(|c| c.to_string()).unwrap_or_default();
+ let hex2 = chars.next().map(|c| c.to_string()).unwrap_or_default();
+
+ styled.push((
+ Style::new().fg(Color::Cyan),
+ format!("\\{modifier}{hex1}{hex2}"),
+ ));
+ } else if modifier == 'u' || modifier == 'U' {
+ for c in chars.by_ref() {
+ current.push(c);
+ if c == '}' {
+ break;
+ }
+ }
+
+ styled.push((Style::new().fg(Color::Cyan), format!("\\u{current}")));
+ current.clear();
+ } else {
+ styled.push((Style::new().fg(Color::Cyan), format!("\\{modifier}")));
+ }
+ }
+
+ styled.push((Style::new().fg(Color::Green), current.clone()));
+
+ styled
+}
+
+pub struct LuaHighlighter;
+
+impl reedline::Highlighter for LuaHighlighter {
+ fn highlight(&self, line: &str, _cursor: usize) -> StyledText {
+ let tree = LuaParser::parse(line, ParserConfig::default());
+ let root = tree.get_red_root();
+
+ let mut text = StyledText::new();
+
+ for token in root
+ .descendants_with_tokens()
+ .filter_map(|d| d.into_token())
+ {
+ let mut color = default_token_color(&token);
+
+ if let Some(parent) = token.parent() {
+ if let Some(new_color) = modify_token_color(&token, &parent) {
+ color = new_color;
+ }
+ }
+
+ match token.kind() {
+ LuaKind::Syntax(_) => unreachable!(),
+ LuaKind::Token(kind) => {
+ if let LuaTokenKind::TkString = kind {
+ let styled = highlight_string(token.text());
+
+ text.buffer.extend(styled.buffer);
+ continue;
+ }
+ }
+ }
+
+ text.push((Style::new().fg(color), token.text().to_string()));
+ }
+
+ text
+ }
+}