commit f193be17314709fcc426a96eebc8a9117d671939 Author: xenia Date: Sun Nov 12 00:21:50 2023 +0100 First commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599a890 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.direnv diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6df18a0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,902 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kak-tree" +version = "0.1.0" +dependencies = [ + "clap", + "itertools", + "serde", + "slog", + "slog-scope", + "sloggers", + "toml", + "tree-sitter", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libflate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +dependencies = [ + "adler32", + "crc32fast", + "rle-decode-fast", + "take_mut", +] + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.20", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-async" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-kvfilter" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" +dependencies = [ + "regex", + "slog", +] + +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-stdlog" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c469573d1e3f36f9eee66cd132206caf47b50c94b1f6c6e7b4d8235e9ecf01" +dependencies = [ + "crossbeam", + "log 0.3.9", + "slog", + "slog-scope", +] + +[[package]] +name = "slog-term" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d29185c55b7b258b4f120eab00f48557d4d9bc814f41713f449d35b0f8977c" +dependencies = [ + "atty", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "sloggers" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31bef221d42166d6708aa1e9b0182324b37a0a7517ff590ec201dbfe1cfa46ef" +dependencies = [ + "chrono", + "libflate", + "regex", + "serde", + "serde_derive", + "slog", + "slog-async", + "slog-kvfilter", + "slog-scope", + "slog-stdlog", + "slog-term", + "trackable 0.2.24", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "trackable" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" +dependencies = [ + "trackable 1.3.0", + "trackable_derive", +] + +[[package]] +name = "trackable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" +dependencies = [ + "trackable_derive", +] + +[[package]] +name = "trackable_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tree-sitter" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "376e181cb69da67bad2d69806cf2500656fd68123c526a61e5cbbfc65110c5b0" +dependencies = [ + "cc", + "regex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log 0.4.20", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3c12e58 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "kak-tree" +version = "0.1.0" +authors = ["Ruslan Prokopchuk "] +edition = "2018" + +[dependencies] +clap = "2.33.0" +itertools = "0.8.2" +serde = { version = "1.0.104", features = ["derive"] } +slog = { version = "2.5.2", features = ["release_max_level_debug"] } +slog-scope = "4.3.0" +sloggers = "0.3.5" +toml = "0.5.6" +tree-sitter = "0.6.0" + +# [profile.release] +# lto = true + +[features] +default = ["rust"] +all = [ + "bash", + "c_sharp", + "c", + "clojure", + "cpp", + "css", + "elm", + "go", + "haskell", + "html", + "java", + "javascript", + "json", + "julia", + "ocaml", + "php", + "python", + "racket", + "ruby", + "rust", + "scala", + "typescript" +] +bash = [] +c_sharp = [] +c = [] +clojure = [] +cpp = [] +css = [] +elm = [] +go = [] +haskell = [] +html = [] +java = [] +javascript = [] +json = [] +julia = [] +ocaml = [] +php = [] +python = [] +racket = [] +ruby = [] +rust = [] +scala = [] +typescript = [] + diff --git a/README.original.asciidoc b/README.original.asciidoc new file mode 100644 index 0000000..c700a03 --- /dev/null +++ b/README.original.asciidoc @@ -0,0 +1,121 @@ += Structural selections for Kakoune + +kak-tree is a plugin for Kakoune which enables selection of syntax tree nodes. Parsing is performed with https://github.com/tree-sitter/tree-sitter[tree-sitter]. + +Status: proof of concept, interface and overall development direction could change drastically based on feedback. + +== Installation + +Replace `"rust javascript"` with a list of languages you need. Or use `all` to build all supported +languages. Note that `all` build takes a long time, and resulting binary is quite fat which could +have a negative impact on responsiveness. + +---- +git clone --recurse-submodules +cargo install --path . --force --features "rust javascript" +cp rc/tree.kak ~/.config/kak/autoload/ +---- + +Look at `Cargo.toml` for a full list of supported languages. + +It is possible to check programmaticaly if kak-tree was built with support for a given filetype: + +---- +kak-tree --do-you-understand rust +---- + +If language is supported then exit code is 0 otherwise it's non-zero (1 at the moment, but it is not +guaranteed in future). + +== Usage + +Tree-sitter parsers produce very detailed syntax tree, many elements of which are not interesting +for day-to-day selection purposes. kak-tree introduces the concept of a _visible_ node. Node is +_visible_ when: + +. Node is named in the tree-sitter grammar for the given language (as opposed to anonymous nodes, +http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes[more]). +. Either there is no white/blacklist for the given filetype or node kind is whitelisted or not +blacklisted. See <> for details about white/blacklisting. + +Most of the kak-tree commands operate on _visible_ nodes and skip not _visible_ ones. + +[cols=2*] +|=== + +| tree-select-parent-node [] +| Select the closest visible ancestor or ancestor of KIND when provided. + +| tree-select-next-node [] +| Select the closest visible next sibling or next sibling of KIND when provided. + +| tree-select-previous-node [] +| Select the closest visible previous sibling or previous sibling of KIND when provided. + +| tree-select-children [] +| Select all immediate visible children or all descendants matching KIND when provided. + +| tree-select-first-child [] +| Select the first immediate visible children or the first descendant matching KIND when provided. + +| tree-node-sexp +| Show info box with a syntax tree of the main selection parent. +|=== + +== Configuration + +kak-tree supports configuration via a configuration file. As for now there is no default path to +load the configuration file, and it must be given using CLI option `--config` or `-c` for short: + +---- +set global tree_cmd 'kak-tree -c /path/to/kak-tree.toml' +---- + +=== Filetype configuration + +Configuration for specific filetypes should be provided like this: + +---- +[filetype.rust] +blacklist = ["identifier", "scoped_identifier", "string_literal"] +whitelist = ["function_item"] +group.identifier = ["identifier", "scoped_identifier"] +group.fn = ["function_item"] + +[filetype.javascript] +group.fn = ["function", "arrow_function"] +---- + +Configuration under the `[filetype.default]` key will be used for all filetypes without +configuration. Specific filetype configuration _doesn't_ extend default configuration but rather +overwrites it. + +==== White/blacklisting + +If `whitelist` array is provided then kak-tree selection will skip nodes which kinds are not whitelisted. +If `blacklist` array is provided then kak-tree selection will skip nodes which kinds are blacklisted. + +NOTE: `whitelist` takes precedence over `blacklist`. In the Rust example above kak-tree would expand +selection up to the function definition, ignoring other node kinds. + +NOTE: `tree-node-sexp` command is useful for exploring node kinds which appear in the specific code. + +Whitelisting or blacklisting node kinds could be tedious as tree-sitter parsers define many of them, +but it also could be rewarding as you will be able to quickly modify selection in scopes which +matter for you with fewer keystrokes. + +==== Kind groups + +Groups of node kinds serve a two-fold purpose: + +. Groups allow matching functionally similar node kinds (i.e. `identifier` and `scoped_identifier` +in Rust) by a single query. + +. Groups allow matching functionally similar nodes across filetypes (i.e. `function_item` in Rust +and `function` in JavaScript) as tree-sitter parsers don't use uniform node kind names. + +NOTE: `whitelist` and `blacklist` options doesn't expand groups yet. + +== License + +For kak-tree see UNLICENSE file. For tree-sitter and its parsers look at their repositories. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..fae59e8 --- /dev/null +++ b/build.rs @@ -0,0 +1,51 @@ +fn main() { + println!("cargo:rustc-link-search=native=lib"); + for lang in &[ + #[cfg(feature = "bash")] + "bash", + #[cfg(feature = "c_sharp")] + "c-sharp", + #[cfg(feature = "c")] + "c", + #[cfg(feature = "clojure")] + "clojure", + #[cfg(feature = "cpp")] + "cpp", + #[cfg(feature = "css")] + "css", + #[cfg(feature = "elm")] + "elm", + #[cfg(feature = "go")] + "go", + #[cfg(feature = "haskell")] + "haskell", + #[cfg(feature = "html")] + "html", + #[cfg(feature = "java")] + "java", + #[cfg(feature = "javascript")] + "javascript", + #[cfg(feature = "json")] + "json", + #[cfg(feature = "julia")] + "julia", + #[cfg(feature = "ocaml")] + "ocaml", + #[cfg(feature = "php")] + "php", + #[cfg(feature = "python")] + "python", + #[cfg(feature = "racket")] + "racket", + #[cfg(feature = "ruby")] + "ruby", + #[cfg(feature = "rust")] + "rust", + #[cfg(feature = "scala")] + "scala", + #[cfg(feature = "typescript")] + "typescript", + ] { + println!("cargo:rustc-link-lib=static=tree-sitter-{lang}"); + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a2a0392 --- /dev/null +++ b/flake.lock @@ -0,0 +1,195 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1699155732, + "narHash": "sha256-Wg4RmOGUEO4YCF0fEc/qiQ/3+BGC/f0qywIE8xEkIpY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "15492ddc2974ba426ea7e17116ea7aa44fc96dcd", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1697009197, + "narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "tree-sitters": "tree-sitters" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1699669856, + "narHash": "sha256-OIb0WAoEMUA1EH70AwpWabdEpvYt/kJChBnb7XiXAJs=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "efd15e11c8954051a47679e7718b4c2a9b68ce27", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "tree-sitters": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1699742193, + "narHash": "sha256-1pc59vyEpFwtHYKsEGAwxL7/4wiTmLmzKO6j61wqOeo=", + "ref": "refs/heads/main", + "rev": "bb561d2e33d4d9f9d8171df2484ba6ae8e810b74", + "revCount": 3, + "type": "git", + "url": "https://githug.xyz/xenia/tree-sitters" + }, + "original": { + "type": "git", + "url": "https://githug.xyz/xenia/tree-sitters" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..481b5eb --- /dev/null +++ b/flake.nix @@ -0,0 +1,64 @@ +{ + description = "kak-tree, packaged for nix"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; + tree-sitters.url = "git+https://githug.xyz/xenia/tree-sitters"; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay, tree-sitters }: + flake-utils.lib.eachDefaultSystem (sys: + let pkgs = import nixpkgs { + system = sys; + overlays = [ (import rust-overlay) ]; + }; + rust = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" ]; + }; + platform = pkgs.makeRustPlatform { + rustc = rust; + cargo = rust; + }; + sitters = tree-sitters.packages.${sys}; + sitterlist = [ + sitters.agda sitters.bash sitters.c sitters.nix sitters.python sitters.rust + ]; + + kak-tree-bin = platform.buildRustPackage { + name = "kak-tree-bin"; + src = ./.; + cargoLock = { lockFile = ./Cargo.lock; }; + buildFeatures = ["bash" "c" "python" "rust"]; # TODO: Add agda, nix + inherit sitterlist; + preBuild = '' + mkdir lib + for sitter in $sitterlist ; do + echo "Copying $sitter" + cp $sitter/lib/* lib + done + ''; + doCheck = false; + }; + + in rec { + packages.bin = kak-tree-bin; + packages.kak-tree = pkgs.stdenv.mkDerivation { + name = "kak-tree"; + src = ./rc ; + rtpPath = "share/kak/autoload/plugins"; + + patchPhase = '' + sed -i -e 's|tree_cmd "kak-tree"|tree_cmd "${kak-tree-bin}/bin/kak-tree"|' tree.kak + ''; + buildPhase = '' + mkdir -p "$out/$rtpPath/kak-tree" + cp * "$out/$rtpPath/kak-tree" + ''; + }; + devShells.default = pkgs.mkShell { + packages = [ rust ]; + }; + } + ); +} diff --git a/rc/tree.kak b/rc/tree.kak new file mode 100644 index 0000000..2c11a10 --- /dev/null +++ b/rc/tree.kak @@ -0,0 +1,79 @@ +# Path to the kak-tree executable. +# To load config: set-option global tree_cmd "kak-tree --config /path/to/kak-tree.toml" +# To enable debug logging: set-option global tree_cmd "kak-tree -vvv" +declare-option str tree_cmd "kak-tree" + +# Path to the log file. +declare-option str tree_log "/tmp/kak-tree.log" + +# Option to store draft of the current buffer before passing to shell. +declare-option -hidden str tree_draft + +define-command -hidden tree-command -params 1..2 -docstring %{ + tree-command [] + Send request to kak-tree and evaluate response. +} %{ + evaluate-commands -draft -no-hooks %{exec '%'; set buffer tree_draft %val{selection}} + evaluate-commands %sh{ + +tree_draft=$(printf '%s.' "${kak_opt_tree_draft}" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed "s/$(printf '\t')/\\\\t/g") + +tree_draft=${tree_draft%.} + +printf ' +filetype = "%s" +selections_desc = "%s" +content = """ +%s""" +[op] +type = "%s" +%s +' "${kak_opt_filetype}" "${kak_selections_desc}" "${tree_draft}" $1 "$2" | ${kak_opt_tree_cmd} 2>${kak_opt_tree_log} + } +} + + +define-command -hidden tree-command-with-optional-kind -params 1..2 -docstring %{ + tree-command-with-optional-kind [] + Send request which optionally takes node kind. +} %{ + tree-command %arg{1} %sh{ + if [ -n "$2" ]; then + printf 'kind = "%s"' "$2" + fi + } +} + +define-command tree-select-parent-node -params ..1 -docstring %{ + tree-select-parent-node [] + Select the closest visible ancestor or ancestor of KIND when provided. +} %{ tree-command-with-optional-kind SelectParentNode %arg{1} } + +define-command tree-select-next-node -params ..1 -docstring %{ + tree-select-next-node [] + Select the closest visible next sibling or next sibling of KIND when provided. +} %{ tree-command-with-optional-kind SelectNextNode %arg{1} } + +define-command tree-select-previous-node -params ..1 -docstring %{ + tree-select-previous-node [] + Select the closest visible previous sibling or previous sibling of KIND when provided. +} %{ tree-command-with-optional-kind SelectPreviousNode %arg{1} } + +define-command tree-select-children -params ..1 -docstring %{ + tree-select-children [] + Select all immediate visible children or all descendants matching KIND when provided. +} %{ tree-command-with-optional-kind SelectChildren %arg{1} } + +define-command tree-node-sexp -docstring %{ + tree-node-sexp + Show info box with a syntax tree of the main selection parent. +} %{ tree-command NodeSExp } + +define-command tree-select-first-child -params ..1 -docstring %{ + tree-select-first-child [] + Select the first immediate visible children or the first descendant matching KIND when provided. +} %{ + tree-command-with-optional-kind SelectChildren %arg{1} + execute-keys +} + diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..357a3ae --- /dev/null +++ b/src/config.rs @@ -0,0 +1,70 @@ +use serde::Deserialize; +use std::collections::HashMap; +use toml; +use tree_sitter::Node; + +#[derive(Deserialize)] +pub struct Config { + #[serde(default)] + filetype: HashMap, +} + +#[derive(Clone, Deserialize, Default)] +pub struct FiletypeConfig { + blacklist: Option>, + whitelist: Option>, + #[serde(default)] + group: HashMap>, +} + +impl Default for Config { + fn default() -> Self { + let mut config = Config { + filetype: HashMap::default(), + }; + config + .filetype + .insert("default".to_owned(), FiletypeConfig::default()); + config + } +} + +impl Config { + pub fn load>(path: P) -> Option { + let config = std::fs::read_to_string(path).ok()?; + let mut config: Config = toml::from_str(&config).ok()?; + if config.filetype.get("default").is_none() { + config + .filetype + .insert("default".to_owned(), FiletypeConfig::default()); + } + Some(config) + } + + pub fn get_filetype_config<'a>(&'a self, filetype: &str) -> &'a FiletypeConfig { + self.filetype + .get(filetype) + .or_else(|| self.filetype.get("default")) + .unwrap() + } +} + +impl FiletypeConfig { + pub fn is_node_visible(&self, node: Node) -> bool { + let kind = node.kind(); + match &self.whitelist { + Some(whitelist) => whitelist.iter().any(|x| x == kind), + None => match &self.blacklist { + Some(blacklist) => !blacklist.iter().any(|x| x == kind), + None => true, + }, + } + } + + pub fn resolve_alias<'a>(&'a self, kind: &str) -> Vec { + self.group + .get(kind) + .cloned() + .unwrap_or_else(|| vec![kind.to_string()]) + } +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..c96641c --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,99 @@ +use tree_sitter::Language; + +extern "C" { + #[cfg(feature = "bash")] + fn tree_sitter_bash() -> Language; + #[cfg(feature = "c")] + fn tree_sitter_c() -> Language; + #[cfg(feature = "c_sharp")] + fn tree_sitter_c_sharp() -> Language; + #[cfg(feature = "clojure")] + fn tree_sitter_clojure() -> Language; + #[cfg(feature = "cpp")] + fn tree_sitter_cpp() -> Language; + #[cfg(feature = "css")] + fn tree_sitter_css() -> Language; + #[cfg(feature = "elm")] + fn tree_sitter_elm() -> Language; + #[cfg(feature = "go")] + fn tree_sitter_go() -> Language; + #[cfg(feature = "haskell")] + fn tree_sitter_haskell() -> Language; + #[cfg(feature = "html")] + fn tree_sitter_html() -> Language; + #[cfg(feature = "java")] + fn tree_sitter_java() -> Language; + #[cfg(feature = "javascript")] + fn tree_sitter_javascript() -> Language; + #[cfg(feature = "json")] + fn tree_sitter_json() -> Language; + #[cfg(feature = "julia")] + fn tree_sitter_julia() -> Language; + #[cfg(feature = "ocaml")] + fn tree_sitter_ocaml() -> Language; + #[cfg(feature = "php")] + fn tree_sitter_php() -> Language; + #[cfg(feature = "python")] + fn tree_sitter_python() -> Language; + #[cfg(feature = "racket")] + fn tree_sitter_racket() -> Language; + #[cfg(feature = "ruby")] + fn tree_sitter_ruby() -> Language; + #[cfg(feature = "rust")] + fn tree_sitter_rust() -> Language; + #[cfg(feature = "scala")] + fn tree_sitter_scala() -> Language; + #[cfg(feature = "typescript")] + fn tree_sitter_typescript() -> Language; +} + +pub fn filetype_to_language(filetype: &str) -> Option { + let sitter = match filetype { + #[cfg(feature = "bash")] + "sh" => tree_sitter_bash, + #[cfg(feature = "c")] + "c" => tree_sitter_c, + #[cfg(feature = "c_sharp")] + "c_sharp" => tree_sitter_c_sharp, + #[cfg(feature = "clojure")] + "clojure" => tree_sitter_clojure, + #[cfg(feature = "cpp")] + "cpp" => tree_sitter_cpp, + #[cfg(feature = "css")] + "css" => tree_sitter_css, + #[cfg(feature = "elm")] + "elm" => tree_sitter_elm, + #[cfg(feature = "go")] + "go" => tree_sitter_go, + #[cfg(feature = "haskell")] + "haskell" => tree_sitter_haskell, + #[cfg(feature = "html")] + "html" => tree_sitter_html, + #[cfg(feature = "java")] + "java" => tree_sitter_java, + #[cfg(feature = "javascript")] + "javascript" => tree_sitter_javascript, + #[cfg(feature = "json")] + "json" => tree_sitter_json, + #[cfg(feature = "julia")] + "julia" => tree_sitter_julia, + #[cfg(feature = "ocaml")] + "ocaml" => tree_sitter_ocaml, + #[cfg(feature = "php")] + "php" => tree_sitter_php, + #[cfg(feature = "python")] + "python" => tree_sitter_python, + #[cfg(feature = "racket")] + "racket" => tree_sitter_racket, + #[cfg(feature = "ruby")] + "ruby" => tree_sitter_ruby, + #[cfg(feature = "rust")] + "rust" => tree_sitter_rust, + #[cfg(feature = "scala")] + "scala" => tree_sitter_scala, + #[cfg(feature = "typescript")] + "typescript" => tree_sitter_typescript, + _ => return None, + }; + Some(unsafe { sitter() }) +} diff --git a/src/kakoune.rs b/src/kakoune.rs new file mode 100644 index 0000000..1bc881b --- /dev/null +++ b/src/kakoune.rs @@ -0,0 +1,89 @@ +use itertools::Itertools; +use tree_sitter::{Point, Range}; + +pub fn select_ranges(buffer: &[String], ranges: &[Range]) -> String { + if ranges.is_empty() { + "fail no selections remaining".into() + } else { + format!("select {}", ranges_to_selections_desc(&buffer, &ranges)) + } +} + +pub fn ranges_to_selections_desc(buffer: &[String], ranges: &[Range]) -> String { + ranges + .iter() + .map(|range| { + let mut end_row = range.end_point.row; + let mut end_column = range.end_point.column; + if end_column > 0 { + end_column -= 1; + } else { + end_row -= 1; + end_column = 1_000_000; + } + format!( + "{},{}", + point_to_kak_coords(buffer, range.start_point), + point_to_kak_coords(buffer, Point::new(end_row, end_column)) + ) + }) + .join(" ") +} + +pub fn selections_desc_to_ranges(buffer: &[String], selections_desc: &str) -> Vec { + selections_desc + .split_whitespace() + .map(|selection_desc| selection_desc_to_range(buffer, selection_desc)) + .collect() +} + +fn selection_desc_to_range(buffer: &[String], selection_desc: &str) -> Range { + let mut range = selection_desc.split(','); + let start = range.next().unwrap(); + let end = range.next().unwrap(); + let (start_byte, start_point) = kak_coords_to_byte_and_point(buffer, start); + let (end_byte, end_point) = kak_coords_to_byte_and_point(buffer, end); + let reverse = start_byte > end_byte; + if reverse { + Range { + start_byte: end_byte, + end_byte: start_byte, + start_point: end_point, + end_point: start_point, + } + } else { + Range { + start_byte, + end_byte, + start_point, + end_point, + } + } +} + +fn point_to_kak_coords(buffer: &[String], p: Point) -> String { + let offset = buffer[p.row] + .char_indices() + .enumerate() + .find_map(|(column, (offset, _))| { + if column == p.column { + Some(offset) + } else { + None + } + }) + .unwrap_or_else(|| buffer[p.row].len()); + format!("{}.{}", p.row + 1, offset + 1) +} + +fn kak_coords_to_byte_and_point(buffer: &[String], coords: &str) -> (usize, Point) { + let mut coords = coords.split('.'); + let row = coords.next().unwrap().parse::().unwrap() - 1; + let offset = coords.next().unwrap().parse::().unwrap() - 1; + let byte = buffer[..row].iter().fold(0, |offset, c| offset + c.len()) + offset; + let column = buffer[row] + .char_indices() + .position(|(i, _)| i == offset) + .unwrap(); + (byte, Point::new(row, column)) +} diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..cb87199 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,19 @@ +use sloggers::terminal::{Destination, TerminalLoggerBuilder}; +use sloggers::types::Severity; +use sloggers::Build; + +pub fn init_global_logger(verbosity: u8) { + let level = match verbosity { + 0 => Severity::Error, + 1 => Severity::Warning, + 2 => Severity::Info, + 3 => Severity::Debug, + _ => Severity::Trace, + }; + + let mut builder = TerminalLoggerBuilder::new(); + builder.level(level); + builder.destination(Destination::Stderr); + let logger = builder.build().unwrap(); + let _guard = slog_scope::set_global_logger(logger); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6f61c37 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,229 @@ +use crate::config::{Config, FiletypeConfig}; +use clap::{crate_version, App, Arg}; +use serde::Deserialize; +use std::io::Read; +use toml; +use tree_sitter::{Node, Parser, Range}; + +mod config; +mod ffi; +mod kakoune; +mod log; +mod tree; + +#[derive(Deserialize)] +struct Request { + op: Op, + filetype: String, + selections_desc: String, + content: String, +} + +#[derive(Deserialize)] +#[serde(tag = "type")] +enum Op { + NodeSExp, + SelectChildren { kind: Option }, + SelectNextNode { kind: Option }, + SelectParentNode { kind: Option }, + SelectPreviousNode { kind: Option }, +} + +fn main() { + let matches = cli(); + + let verbosity = matches.occurrences_of("v") as u8; + log::init_global_logger(verbosity); + + if let Some(filetype) = matches.value_of("do-you-understand") { + let language = ffi::filetype_to_language(filetype); + std::process::exit(if language.is_some() { 0 } else { 1 }); + } + + let config = if let Some(config_path) = matches.value_of("config") { + Config::load(config_path).unwrap() + } else { + Config::default() + }; + + let mut request = String::new(); + std::io::stdin().read_to_string(&mut request).unwrap(); + let request: Request = toml::from_str(&request).unwrap(); + let response = handle_request(&config, &request); + println!("{}", response); +} + +fn cli() -> clap::ArgMatches<'static> { + App::new("kak-tree") + .version(crate_version!()) + .author("Ruslan Prokopchuk ") + .about("Structural selections for Kakoune") + .arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Read config from FILE") + .takes_value(true), + ) + .arg( + Arg::with_name("do-you-understand") + .long("do-you-understand") + .value_name("FILETYPE") + .help("Exit with 0 if FILETYPE is supported, non-zero otherwise") + .takes_value(true), + ) + .arg( + Arg::with_name("v") + .short("v") + .multiple(true) + .help("Sets the level of verbosity"), + ) + .get_matches() +} + +fn handle_request(config: &Config, request: &Request) -> String { + let mut parser = Parser::new(); + let language = ffi::filetype_to_language(&request.filetype).unwrap(); + parser.set_language(language).unwrap(); + let tree = parser.parse(&request.content, None).unwrap(); + let buffer = request + .content + .split('\n') + .map(|s| format!("{}\n", s)) + .collect::>(); + let ranges = kakoune::selections_desc_to_ranges(&buffer, &request.selections_desc); + let mut new_ranges = Vec::new(); + let filetype_config = config.get_filetype_config(&request.filetype); + match &request.op { + Op::SelectParentNode { kind } => { + let kinds = kind + .as_ref() + .and_then(|kind| Some(filetype_config.resolve_alias(kind))); + for range in &ranges { + let node = tree::shrink_to_range(tree.root_node(), range); + let node = find_parent_of_interest(filetype_config, node, &kinds); + new_ranges.push(node.range()); + } + kakoune::select_ranges(&buffer, &new_ranges) + } + Op::SelectNextNode { kind } => { + let kinds = kind + .as_ref() + .and_then(|kind| Some(filetype_config.resolve_alias(kind))); + 'outer_next: for range in &ranges { + let node = tree::shrink_to_range(tree.root_node(), range); + let mut cursor = traverse_up_to_node_which_matters(filetype_config, node); + while let Some(node) = cursor.next_named_sibling() { + if filetype_config.is_node_visible(node) && node_of_kinds(node, &kinds) { + new_ranges.push(node.range()); + continue 'outer_next; + } + cursor = node; + } + let node = find_parent_of_interest(filetype_config, node, &kinds); + new_ranges.push(node.range()); + } + kakoune::select_ranges(&buffer, &new_ranges) + } + Op::SelectPreviousNode { kind } => { + let kinds = kind + .as_ref() + .and_then(|kind| Some(filetype_config.resolve_alias(kind))); + 'outer_prev: for range in &ranges { + let node = tree::shrink_to_range(tree.root_node(), range); + let mut cursor = traverse_up_to_node_which_matters(filetype_config, node); + while let Some(node) = cursor.prev_named_sibling() { + if filetype_config.is_node_visible(node) && node_of_kinds(node, &kinds) { + new_ranges.push(node.range()); + continue 'outer_prev; + } + cursor = node; + } + let node = find_parent_of_interest(filetype_config, node, &kinds); + new_ranges.push(node.range()); + } + kakoune::select_ranges(&buffer, &new_ranges) + } + Op::SelectChildren { kind } => { + match kind { + Some(kind) => { + let kinds = filetype_config.resolve_alias(kind); + for range in &ranges { + for node in tree::nodes_in_range(tree.root_node(), range) { + select_nodes(&node, &kinds, &mut new_ranges); + } + } + } + None => { + for range in &ranges { + let node = tree::shrink_to_range(tree.root_node(), range); + for child in tree::named_children(&node) { + if filetype_config.is_node_visible(child) { + new_ranges.push(child.range()); + } + } + } + } + } + kakoune::select_ranges(&buffer, &new_ranges) + } + Op::NodeSExp => { + let node = tree::shrink_to_range(tree.root_node(), &ranges[0]); + format!("info '{}'", node.to_sexp()) + } + } +} + +fn select_nodes(node: &Node, kinds: &[String], new_ranges: &mut Vec) { + if kinds.iter().any(|kind| kind == node.kind()) { + new_ranges.push(node.range()); + } else { + for child in tree::named_children(&node) { + if kinds.iter().any(|kind| kind == child.kind()) { + new_ranges.push(child.range()); + } else { + select_nodes(&child, kinds, new_ranges); + } + } + } +} + +fn traverse_up_to_node_which_matters<'a>( + filetype_config: &FiletypeConfig, + current_node: Node<'a>, +) -> Node<'a> { + let mut opt_node = Some(current_node); + while let Some(node) = opt_node.filter(|&n| !(n.is_named() && filetype_config.is_node_visible(n))) { + opt_node = node.parent(); + } + opt_node.unwrap_or(current_node) +} + +fn find_parent_of_interest<'a>( + filetype_config: &FiletypeConfig, + current_node: Node<'a>, + kinds: &Option>, +) -> Node<'a> { + let parent = current_node.parent(); + match &kinds { + Some(kinds) => { + let mut cursor = parent; + while let Some(node) = cursor { + if kinds.iter().any(|kind| kind == node.kind()) { + return node; + } + cursor = node.parent(); + } + current_node + } + None => traverse_up_to_node_which_matters(filetype_config, parent.unwrap_or(current_node)), + } +} + +fn node_of_kinds(node: Node, kinds: &Option>) -> bool { + kinds + .as_ref() + .and_then(|kinds| Some(kinds.iter().any(|x| x == node.kind()))) + .unwrap_or(true) +} diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..92a3e2d --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,79 @@ +use tree_sitter::{Node, Range}; + +pub fn named_children<'a>(node: &'a Node) -> impl Iterator> { + (0..node.child_count()).map(move |i| node.child(i).unwrap()) +} + +pub fn shrink_to_range<'a>(root_node: Node<'a>, range: &Range) -> Node<'a> { + let mut node = root_node; + 'outer: loop { + let parent = node; + let mut cursor = parent.walk(); + for child in parent.children(&mut cursor) { + if child.range().start_byte <= range.start_byte + && range.end_byte <= child.range().end_byte + { + node = child; + continue 'outer; + } + } + return highest_node_of_same_range(node); + } +} + +pub fn nodes_in_range<'a>(root_node: Node<'a>, range: &Range) -> Vec> { + let mut nodes = Vec::new(); + let node = shrink_to_range(root_node, range); + if node.range().start_byte >= range.start_byte && range.end_byte >= node.range().end_byte { + nodes.push(node); + return nodes; + } + let mut cursor = node.walk(); + for child in node.children(&mut cursor) { + if child.range().start_byte <= range.start_byte && range.end_byte >= child.range().end_byte + { + nodes.extend(nodes_in_range( + child, + &Range { + start_byte: range.start_byte, + start_point: range.start_point, + end_byte: child.range().end_byte, + end_point: child.range().end_point, + }, + )); + } else if child.range().start_byte >= range.start_byte + && range.end_byte <= child.range().end_byte + { + nodes.extend(nodes_in_range( + child, + &Range { + start_byte: child.range().start_byte, + start_point: child.range().start_point, + end_byte: range.end_byte, + end_point: range.end_point, + }, + )); + } else if child.range().start_byte >= range.start_byte + && range.end_byte >= child.range().end_byte + { + nodes.push(child); + } + } + nodes +} + +fn highest_node_of_same_range<'a>(current_node: Node<'a>) -> Node<'a> { + let start_byte = current_node.start_byte(); + let end_byte = current_node.end_byte(); + let mut node = current_node; + while let Some(parent) = node.parent().and_then(|parent| { + if parent.start_byte() < start_byte || end_byte < parent.end_byte() { + None + } else { + Some(parent) + } + }) { + node = parent + } + node +}