diff --git a/native/Cargo.lock b/native/Cargo.lock index be0931d5b..453057f5b 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -2,6 +2,12 @@ # 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 = "ahash" version = "0.8.12" @@ -30,6 +36,26 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "ast-grep-core" version = "0.39.9" @@ -51,12 +77,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[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" @@ -89,6 +130,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "cc" version = "1.2.56" @@ -105,6 +158,21 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "const-random" version = "0.1.18" @@ -134,6 +202,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -175,6 +252,30 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "either" version = "1.15.0" @@ -205,24 +306,106 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -234,6 +417,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "globset" version = "0.4.18" @@ -338,11 +531,23 @@ dependencies = [ name = "gsd-engine" version = "0.1.0" dependencies = [ + "arboard", + "dashmap", + "globset", + "gsd-ast", "gsd-grep", "html-to-markdown-rs", + "ignore", + "image", + "libc", "napi", "napi-build", "napi-derive", + "regex", + "smallvec", + "syntect", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -356,6 +561,23 @@ dependencies = [ "rayon", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.16.1" @@ -419,6 +641,35 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -426,7 +677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -451,6 +702,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "lock_api" version = "0.4.14" @@ -472,7 +729,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -501,6 +758,26 @@ dependencies = [ "libc", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "napi" version = "2.16.17" @@ -564,6 +841,88 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -593,6 +952,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "phf" version = "0.13.1" @@ -646,6 +1011,19 @@ dependencies = [ "siphasher", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -661,6 +1039,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.45" @@ -728,6 +1118,19 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -798,6 +1201,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "siphasher" version = "1.0.2" @@ -851,6 +1260,24 @@ 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 = "tendril" version = "0.5.0" @@ -881,6 +1308,20 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -1292,6 +1733,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "utf-8" version = "0.7.6" @@ -1338,13 +1785,19 @@ dependencies = [ "string_cache_codegen", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1353,6 +1806,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1362,6 +1824,88 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "zerocopy" version = "0.8.42" @@ -1387,3 +1931,18 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" +dependencies = [ + "zune-core", +] diff --git a/native/crates/engine/Cargo.toml b/native/crates/engine/Cargo.toml index 506628fd6..e1ed956c0 100644 --- a/native/crates/engine/Cargo.toml +++ b/native/crates/engine/Cargo.toml @@ -26,6 +26,7 @@ image = { version = "0.25", default-features = false, features = [ ] } napi = { version = "2", features = ["napi8"] } napi-derive = "2" +regex = "1" smallvec = "1" syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "regex-fancy"] } unicode-segmentation = "1" diff --git a/native/crates/engine/src/lib.rs b/native/crates/engine/src/lib.rs index 0c4d3230e..d85986280 100644 --- a/native/crates/engine/src/lib.rs +++ b/native/crates/engine/src/lib.rs @@ -19,4 +19,5 @@ mod html; mod ps; mod task; mod text; +mod ttsr; mod image; diff --git a/native/crates/engine/src/ttsr.rs b/native/crates/engine/src/ttsr.rs new file mode 100644 index 000000000..571105936 --- /dev/null +++ b/native/crates/engine/src/ttsr.rs @@ -0,0 +1,124 @@ +//! N-API bindings for the TTSR (Time Traveling Stream Rules) regex engine. +//! +//! Pre-compiles all rule condition patterns into a `regex::RegexSet` so that +//! `checkBuffer` can test all patterns against the accumulated stream buffer +//! in a single DFA pass, instead of iterating O(rules x conditions) in JS. + +use napi::bindgen_prelude::*; +use napi_derive::napi; +use std::collections::HashMap; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Mutex; + +/// Maps a compiled regex back to its owning rule. +struct PatternMapping { + /// Index into the RegexSet's pattern list. + _pattern_index: usize, + /// Name of the rule this pattern belongs to. + rule_name: String, +} + +struct CompiledRuleSet { + regex_set: regex::RegexSet, + mappings: Vec, +} + +// Global handle store — handles are u64 keys into this map. +static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1); +static STORE: std::sync::LazyLock>> = + std::sync::LazyLock::new(|| Mutex::new(HashMap::new())); + +#[napi(object)] +pub struct NapiTtsrRuleInput { + pub name: String, + pub conditions: Vec, +} + +/// Compile a set of TTSR rules into an optimized regex engine. +/// +/// Returns an opaque numeric handle. Each rule has one or more regex condition +/// patterns. All patterns are compiled into a single `RegexSet` for O(1)-style +/// matching against the stream buffer. +#[napi(js_name = "ttsrCompileRules")] +pub fn ttsr_compile_rules(rules: Vec) -> Result { + let mut patterns: Vec = Vec::new(); + let mut mappings: Vec = Vec::new(); + + for rule in &rules { + for condition in &rule.conditions { + let idx = patterns.len(); + patterns.push(condition.clone()); + mappings.push(PatternMapping { + _pattern_index: idx, + rule_name: rule.name.clone(), + }); + } + } + + if patterns.is_empty() { + return Err(Error::from_reason("No valid patterns provided")); + } + + let regex_set = regex::RegexSet::new(&patterns) + .map_err(|e| Error::from_reason(format!("Failed to compile regex set: {e}")))?; + + let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed); + + let compiled = CompiledRuleSet { + regex_set, + mappings, + }; + + STORE + .lock() + .map_err(|e| Error::from_reason(format!("Lock poisoned: {e}")))? + .insert(handle, compiled); + + // Return as f64 since napi BigInt interop is awkward; handles won't exceed 2^53. + Ok(handle as f64) +} + +/// Check a buffer against compiled TTSR rules. +/// +/// Returns an array of unique rule names whose conditions matched the buffer. +/// The RegexSet tests all patterns in a single pass over the buffer. +#[napi(js_name = "ttsrCheckBuffer")] +pub fn ttsr_check_buffer(handle: f64, buffer: String) -> Result> { + let handle_key = handle as u64; + + let store = STORE + .lock() + .map_err(|e| Error::from_reason(format!("Lock poisoned: {e}")))?; + + let compiled = store + .get(&handle_key) + .ok_or_else(|| Error::from_reason(format!("Invalid TTSR handle: {handle}")))?; + + let matching_indices: Vec = compiled.regex_set.matches(&buffer).into_iter().collect(); + + // Deduplicate: multiple conditions from the same rule should produce one entry. + let mut seen = std::collections::HashSet::new(); + let mut matched_rules: Vec = Vec::new(); + + for idx in matching_indices { + let rule_name = &compiled.mappings[idx].rule_name; + if seen.insert(rule_name.clone()) { + matched_rules.push(rule_name.clone()); + } + } + + Ok(matched_rules) +} + +/// Free a compiled TTSR rule set, releasing memory. +#[napi(js_name = "ttsrFreeRules")] +pub fn ttsr_free_rules(handle: f64) -> Result<()> { + let handle_key = handle as u64; + + STORE + .lock() + .map_err(|e| Error::from_reason(format!("Lock poisoned: {e}")))? + .remove(&handle_key); + + Ok(()) +} diff --git a/packages/native/src/__tests__/ttsr.test.mjs b/packages/native/src/__tests__/ttsr.test.mjs new file mode 100644 index 000000000..d62a2e8c1 --- /dev/null +++ b/packages/native/src/__tests__/ttsr.test.mjs @@ -0,0 +1,135 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const require = createRequire(import.meta.url); + +// Load the native addon directly +const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon"); +const platformTag = `${process.platform}-${process.arch}`; +const candidates = [ + path.join(addonDir, `gsd_engine.${platformTag}.node`), + path.join(addonDir, "gsd_engine.dev.node"), +]; + +let native; +for (const candidate of candidates) { + try { + native = require(candidate); + break; + } catch { + // try next + } +} + +if (!native) { + console.error("Native addon not found. Run `npm run build:native -w @gsd/native` first."); + process.exit(1); +} + +describe("native ttsr: ttsrCompileRules()", () => { + test("compiles rules and returns a numeric handle", () => { + const handle = native.ttsrCompileRules([ + { name: "rule1", conditions: ["foo", "bar"] }, + ]); + assert.equal(typeof handle, "number"); + assert.ok(handle > 0); + native.ttsrFreeRules(handle); + }); + + test("rejects empty conditions", () => { + assert.throws(() => { + native.ttsrCompileRules([]); + }); + }); + + test("rejects invalid regex patterns", () => { + assert.throws(() => { + native.ttsrCompileRules([ + { name: "bad", conditions: ["(unclosed"] }, + ]); + }); + }); +}); + +describe("native ttsr: ttsrCheckBuffer()", () => { + test("returns matching rule names", () => { + const handle = native.ttsrCompileRules([ + { name: "greet", conditions: ["hello\\s+world"] }, + { name: "farewell", conditions: ["goodbye"] }, + ]); + + const matches = native.ttsrCheckBuffer(handle, "say hello world please"); + assert.deepEqual(matches, ["greet"]); + + native.ttsrFreeRules(handle); + }); + + test("returns multiple matching rules", () => { + const handle = native.ttsrCompileRules([ + { name: "a", conditions: ["alpha"] }, + { name: "b", conditions: ["beta"] }, + { name: "c", conditions: ["gamma"] }, + ]); + + const matches = native.ttsrCheckBuffer(handle, "alpha and beta together"); + assert.ok(matches.includes("a")); + assert.ok(matches.includes("b")); + assert.ok(!matches.includes("c")); + + native.ttsrFreeRules(handle); + }); + + test("returns empty array on no match", () => { + const handle = native.ttsrCompileRules([ + { name: "x", conditions: ["zzz_no_match"] }, + ]); + + const matches = native.ttsrCheckBuffer(handle, "nothing here"); + assert.deepEqual(matches, []); + + native.ttsrFreeRules(handle); + }); + + test("deduplicates when multiple conditions of same rule match", () => { + const handle = native.ttsrCompileRules([ + { name: "multi", conditions: ["foo", "bar"] }, + ]); + + const matches = native.ttsrCheckBuffer(handle, "foo and bar"); + assert.deepEqual(matches, ["multi"]); + + native.ttsrFreeRules(handle); + }); + + test("handles large buffers efficiently", () => { + const handle = native.ttsrCompileRules([ + { name: "needle", conditions: ["NEEDLE_PATTERN_XYZ"] }, + ]); + + // 1MB buffer with the needle near the end + const bigBuffer = "x".repeat(1024 * 1024) + "NEEDLE_PATTERN_XYZ"; + const matches = native.ttsrCheckBuffer(handle, bigBuffer); + assert.deepEqual(matches, ["needle"]); + + native.ttsrFreeRules(handle); + }); +}); + +describe("native ttsr: ttsrFreeRules()", () => { + test("frees handle without error", () => { + const handle = native.ttsrCompileRules([ + { name: "temp", conditions: ["tmp"] }, + ]); + native.ttsrFreeRules(handle); + }); + + test("rejects invalid handle on check", () => { + assert.throws(() => { + native.ttsrCheckBuffer(99999, "test"); + }); + }); +}); diff --git a/packages/native/src/index.ts b/packages/native/src/index.ts index 989b59bac..9971a66a2 100644 --- a/packages/native/src/index.ts +++ b/packages/native/src/index.ts @@ -83,3 +83,6 @@ export type { export { parseImage, ImageFormat, SamplingFilter } from "./image/index.js"; export type { NativeImageHandle } from "./image/index.js"; + +export { ttsrCompileRules, ttsrCheckBuffer, ttsrFreeRules } from "./ttsr/index.js"; +export type { TtsrHandle, TtsrRuleInput } from "./ttsr/index.js"; diff --git a/packages/native/src/native.ts b/packages/native/src/native.ts index 9ef18e863..505fa2a93 100644 --- a/packages/native/src/native.ts +++ b/packages/native/src/native.ts @@ -88,4 +88,7 @@ export const native = loadNative() as { visibleWidth: (text: string, tabWidth?: number) => number; fuzzyFind: (options: unknown) => unknown; NativeImage: unknown; + ttsrCompileRules: (rules: unknown[]) => number; + ttsrCheckBuffer: (handle: number, buffer: string) => string[]; + ttsrFreeRules: (handle: number) => void; }; diff --git a/packages/native/src/ttsr/index.ts b/packages/native/src/ttsr/index.ts new file mode 100644 index 000000000..28f3ff9f3 --- /dev/null +++ b/packages/native/src/ttsr/index.ts @@ -0,0 +1,39 @@ +/** + * Native TTSR regex engine. + * + * Pre-compiles all rule condition patterns into a single Rust RegexSet for + * O(1)-style matching per buffer check, replacing per-rule JS regex iteration. + */ + +import { native } from "../native.js"; +import type { TtsrHandle, TtsrRuleInput } from "./types.js"; + +export type { TtsrHandle, TtsrRuleInput }; + +/** + * Compile TTSR rules into an optimized native regex engine. + * + * Returns an opaque handle for use with `ttsrCheckBuffer` and `ttsrFreeRules`. + */ +export function ttsrCompileRules(rules: TtsrRuleInput[]): TtsrHandle { + return native.ttsrCompileRules(rules) as TtsrHandle; +} + +/** + * Check a buffer against compiled TTSR rules. + * + * Returns an array of unique rule names whose conditions matched. + * All patterns are tested in a single pass via Rust's RegexSet. + */ +export function ttsrCheckBuffer(handle: TtsrHandle, buffer: string): string[] { + return native.ttsrCheckBuffer(handle, buffer) as string[]; +} + +/** + * Free a compiled TTSR rule set, releasing native memory. + * + * Call when rules are no longer needed (e.g., session end). + */ +export function ttsrFreeRules(handle: TtsrHandle): void { + native.ttsrFreeRules(handle); +} diff --git a/packages/native/src/ttsr/types.ts b/packages/native/src/ttsr/types.ts new file mode 100644 index 000000000..6ce3c7e82 --- /dev/null +++ b/packages/native/src/ttsr/types.ts @@ -0,0 +1,10 @@ +/** Input rule for TTSR regex compilation. */ +export interface TtsrRuleInput { + /** Unique rule name. */ + name: string; + /** Regex condition patterns (any match triggers the rule). */ + conditions: string[]; +} + +/** Opaque handle to a compiled TTSR rule set. */ +export type TtsrHandle = number; diff --git a/src/resources/extensions/ttsr/ttsr-manager.ts b/src/resources/extensions/ttsr/ttsr-manager.ts index d24fc1d60..b44eead88 100644 --- a/src/resources/extensions/ttsr/ttsr-manager.ts +++ b/src/resources/extensions/ttsr/ttsr-manager.ts @@ -4,9 +4,34 @@ * Manages rules that get injected mid-stream when their condition pattern matches * the agent's output. When a match occurs, the stream is aborted, the rule is * injected as a system reminder, and the request is retried. + * + * The regex hot-path is delegated to a native Rust RegexSet engine when + * available, testing all patterns in a single DFA pass. Falls back to + * per-rule JS RegExp iteration when the native module is not loaded. */ import picomatch from "picomatch"; +// ── Native TTSR engine (optional) ───────────────────────────────────── +let nativeTtsr: { + ttsrCompileRules: (rules: { name: string; conditions: string[] }[]) => number; + ttsrCheckBuffer: (handle: number, buffer: string) => string[]; + ttsrFreeRules: (handle: number) => void; +} | null = null; + +try { + // Dynamic import to avoid hard dependency — gracefully degrades to JS. + const native = await import("@gsd/native"); + if (native.ttsrCompileRules && native.ttsrCheckBuffer && native.ttsrFreeRules) { + nativeTtsr = { + ttsrCompileRules: native.ttsrCompileRules, + ttsrCheckBuffer: native.ttsrCheckBuffer, + ttsrFreeRules: native.ttsrFreeRules, + }; + } +} catch { + // Native module not available — JS fallback will be used. +} + export type TtsrMatchSource = "text" | "thinking" | "tool"; /** Context about the stream content currently being checked against TTSR rules. */ @@ -86,6 +111,8 @@ export class TtsrManager { readonly #injectionRecords = new Map(); readonly #buffers = new Map(); #messageCount = 0; + #nativeHandle: number | null = null; + #nativeDirty = false; constructor(settings?: TtsrSettings) { this.#settings = { ...DEFAULT_SETTINGS, ...settings }; @@ -245,6 +272,40 @@ export class TtsrManager { return false; } + /** Compile (or recompile) the native RegexSet from all current rules. */ + #compileNative(): void { + if (!nativeTtsr || !this.#nativeDirty) return; + + // Free previous handle if any. + if (this.#nativeHandle !== null) { + try { + nativeTtsr.ttsrFreeRules(this.#nativeHandle); + } catch { /* ignore */ } + this.#nativeHandle = null; + } + + const ruleInputs: { name: string; conditions: string[] }[] = []; + for (const [, entry] of this.#rules) { + ruleInputs.push({ + name: entry.rule.name, + conditions: entry.rule.condition, + }); + } + + if (ruleInputs.length === 0) { + this.#nativeDirty = false; + return; + } + + try { + this.#nativeHandle = nativeTtsr.ttsrCompileRules(ruleInputs); + } catch (err) { + console.warn(`[ttsr] Native compilation failed, using JS fallback: ${(err as Error).message}`); + this.#nativeHandle = null; + } + this.#nativeDirty = false; + } + /** Add a TTSR rule to be monitored. */ addRule(rule: Rule): boolean { if (this.#rules.has(rule.name)) return false; @@ -257,6 +318,7 @@ export class TtsrManager { const globalPathMatchers = this.#compileGlobalPathMatchers(rule.globs); this.#rules.set(rule.name, { rule, conditions, scope, globalPathMatchers }); + this.#nativeDirty = true; return true; } @@ -265,6 +327,10 @@ export class TtsrManager { * * Buffers are isolated by source/tool key so matches don't bleed across * assistant prose, thinking text, and unrelated tool argument streams. + * + * When the native Rust engine is available, all regex conditions are tested + * in a single DFA pass via RegexSet. Scope, glob, and repeat-gate checks + * remain in JS as they are lightweight and context-dependent. */ checkDelta(delta: string, context: TtsrMatchContext): Rule[] { const bufferKey = this.#bufferKey(context); @@ -275,6 +341,26 @@ export class TtsrManager { } this.#buffers.set(bufferKey, nextBuffer); + // Lazily compile native engine if rules changed. + if (this.#nativeDirty) this.#compileNative(); + + // ── Native path: single-pass RegexSet match ─────────────────────── + if (nativeTtsr && this.#nativeHandle !== null) { + const regexMatchedNames = nativeTtsr.ttsrCheckBuffer(this.#nativeHandle, nextBuffer); + const regexMatchedSet = new Set(regexMatchedNames); + + const matches: Rule[] = []; + for (const [name, entry] of this.#rules) { + if (!regexMatchedSet.has(name)) continue; + if (!this.#canTrigger(name)) continue; + if (!this.#matchesScope(entry, context)) continue; + if (!this.#matchesGlobalPaths(entry, context)) continue; + matches.push(entry.rule); + } + return matches; + } + + // ── JS fallback: per-rule regex iteration ───────────────────────── const matches: Rule[] = []; for (const [name, entry] of this.#rules) { if (!this.#canTrigger(name)) continue;