First commit

main
xenia 2023-11-12 00:21:50 +01:00
commit f193be1731
16 changed files with 2092 additions and 0 deletions

1
.envrc 100644
View File

@ -0,0 +1 @@
use flake

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
target/
.direnv

902
Cargo.lock generated 100644
View File

@ -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"

68
Cargo.toml 100644
View File

@ -0,0 +1,68 @@
[package]
name = "kak-tree"
version = "0.1.0"
authors = ["Ruslan Prokopchuk <fer.obbee@gmail.com>"]
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 = []

View File

@ -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 <<Configuration>> 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 [<KIND>]
| Select the closest visible ancestor or ancestor of KIND when provided.
| tree-select-next-node [<KIND>]
| Select the closest visible next sibling or next sibling of KIND when provided.
| tree-select-previous-node [<KIND>]
| Select the closest visible previous sibling or previous sibling of KIND when provided.
| tree-select-children [<KIND>]
| Select all immediate visible children or all descendants matching KIND when provided.
| tree-select-first-child [<KIND>]
| 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.

24
UNLICENSE 100644
View File

@ -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 <http://unlicense.org/>

51
build.rs 100644
View File

@ -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}");
}
}

195
flake.lock 100644
View File

@ -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
}

64
flake.nix 100644
View File

@ -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 ];
};
}
);
}

79
rc/tree.kak 100644
View File

@ -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 <OP_TYPE> [<OP_PARAMS>]
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 <OP_TYPE> [<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 [<KIND>]
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 [<KIND>]
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 [<KIND>]
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 [<KIND>]
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 [<KIND>]
Select the first immediate visible children or the first descendant matching KIND when provided.
} %{
tree-command-with-optional-kind SelectChildren %arg{1}
execute-keys <space>
}

70
src/config.rs 100644
View File

@ -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<String, FiletypeConfig>,
}
#[derive(Clone, Deserialize, Default)]
pub struct FiletypeConfig {
blacklist: Option<Vec<String>>,
whitelist: Option<Vec<String>>,
#[serde(default)]
group: HashMap<String, Vec<String>>,
}
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<P: AsRef<std::path::Path>>(path: P) -> Option<Self> {
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<String> {
self.group
.get(kind)
.cloned()
.unwrap_or_else(|| vec![kind.to_string()])
}
}

99
src/ffi.rs 100644
View File

@ -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<Language> {
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() })
}

89
src/kakoune.rs 100644
View File

@ -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<Range> {
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::<usize>().unwrap() - 1;
let offset = coords.next().unwrap().parse::<usize>().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))
}

19
src/log.rs 100644
View File

@ -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);
}

229
src/main.rs 100644
View File

@ -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<String> },
SelectNextNode { kind: Option<String> },
SelectParentNode { kind: Option<String> },
SelectPreviousNode { kind: Option<String> },
}
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 <fer.obbee@gmail.com>")
.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::<Vec<_>>();
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<Range>) {
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<Vec<String>>,
) -> 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<Vec<String>>) -> bool {
kinds
.as_ref()
.and_then(|kinds| Some(kinds.iter().any(|x| x == node.kind())))
.unwrap_or(true)
}

79
src/tree.rs 100644
View File

@ -0,0 +1,79 @@
use tree_sitter::{Node, Range};
pub fn named_children<'a>(node: &'a Node) -> impl Iterator<Item = Node<'a>> {
(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<Node<'a>> {
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
}