manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

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:
MCargo.lock | 396++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
MCargo.toml | 5++---
Dqueries/locals.scm | 35-----------------------------------
Msrc/completion.rs | 246++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/editor.rs | 6+++---
Dsrc/highlight.rs | 150-------------------------------------------------------------------------------
Msrc/main.rs | 17+++++++++++++----
Asrc/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 + } +}