├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.asciidoc ├── UNLICENSE ├── build.rs ├── rc └── tree.kak ├── rustfmt.toml └── src ├── config.rs ├── ffi.rs ├── kakoune.rs ├── log.rs ├── main.rs └── tree.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | README.html 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/tree-sitter-rust"] 2 | path = vendor/tree-sitter-rust 3 | url = https://github.com/tree-sitter/tree-sitter-rust.git 4 | shallow = true 5 | [submodule "https:/github.com/tree-sitter/tree-sitter-javascript.git"] 6 | path = https:/github.com/tree-sitter/tree-sitter-javascript.git 7 | url = https://github.com/tree-sitter/tree-sitter-verilog.git 8 | shallow = true 9 | [submodule "vendor/tree-sitter-javascript"] 10 | path = vendor/tree-sitter-javascript 11 | url = https://github.com/tree-sitter/tree-sitter-javascript.git 12 | shallow = true 13 | [submodule "vendor/tree-sitter-css"] 14 | path = vendor/tree-sitter-css 15 | url = https://github.com/tree-sitter/tree-sitter-css.git 16 | shallow = true 17 | [submodule "vendor/tree-sitter-ruby"] 18 | path = vendor/tree-sitter-ruby 19 | url = https://github.com/tree-sitter/tree-sitter-ruby.git 20 | shallow = true 21 | [submodule "vendor/tree-sitter-html"] 22 | path = vendor/tree-sitter-html 23 | url = https://github.com/tree-sitter/tree-sitter-html.git 24 | shallow = true 25 | [submodule "vendor/tree-sitter-go"] 26 | path = vendor/tree-sitter-go 27 | url = https://github.com/tree-sitter/tree-sitter-go.git 28 | shallow = true 29 | [submodule "vendor/tree-sitter-typescript"] 30 | path = vendor/tree-sitter-typescript 31 | url = https://github.com/tree-sitter/tree-sitter-typescript.git 32 | shallow = true 33 | [submodule "vendor/tree-sitter-python"] 34 | path = vendor/tree-sitter-python 35 | url = https://github.com/tree-sitter/tree-sitter-python.git 36 | shallow = true 37 | [submodule "vendor/tree-sitter-c"] 38 | path = vendor/tree-sitter-c 39 | url = https://github.com/tree-sitter/tree-sitter-c.git 40 | shallow = true 41 | [submodule "vendor/tree-sitter-cpp"] 42 | path = vendor/tree-sitter-cpp 43 | url = https://github.com/tree-sitter/tree-sitter-cpp.git 44 | shallow = true 45 | [submodule "vendor/tree-sitter-php"] 46 | path = vendor/tree-sitter-php 47 | url = https://github.com/tree-sitter/tree-sitter-php.git 48 | shallow = true 49 | [submodule "vendor/tree-sitter-json"] 50 | path = vendor/tree-sitter-json 51 | url = https://github.com/tree-sitter/tree-sitter-json.git 52 | shallow = true 53 | [submodule "vendor/tree-sitter-bash"] 54 | path = vendor/tree-sitter-bash 55 | url = https://github.com/tree-sitter/tree-sitter-bash.git 56 | shallow = true 57 | [submodule "vendor/tree-sitter-haskell"] 58 | path = vendor/tree-sitter-haskell 59 | url = https://github.com/tree-sitter/tree-sitter-haskell.git 60 | shallow = true 61 | [submodule "vendor/tree-sitter-c-sharp"] 62 | path = vendor/tree-sitter-c-sharp 63 | url = https://github.com/tree-sitter/tree-sitter-c-sharp.git 64 | shallow = true 65 | [submodule "vendor/tree-sitter-ocaml"] 66 | path = vendor/tree-sitter-ocaml 67 | url = https://github.com/tree-sitter/tree-sitter-ocaml.git 68 | shallow = true 69 | [submodule "vendor/tree-sitter-java"] 70 | path = vendor/tree-sitter-java 71 | url = https://github.com/tree-sitter/tree-sitter-java.git 72 | shallow = true 73 | [submodule "vendor/tree-sitter-scala"] 74 | path = vendor/tree-sitter-scala 75 | url = https://github.com/tree-sitter/tree-sitter-scala.git 76 | shallow = true 77 | [submodule "vendor/tree-sitter-julia"] 78 | path = vendor/tree-sitter-julia 79 | url = https://github.com/tree-sitter/tree-sitter-julia.git 80 | shallow = true 81 | [submodule "vendor/tree-sitter-elm"] 82 | path = vendor/tree-sitter-elm 83 | url = https://github.com/Razzeee/tree-sitter-elm.git 84 | [submodule "vendor/tree-sitter-clojure"] 85 | path = vendor/tree-sitter-clojure 86 | url = https://github.com/sogaiu/tree-sitter-clojure.git 87 | [submodule "vendor/tree-sitter-racket"] 88 | path = vendor/tree-sitter-racket 89 | url = https://github.com/tautologico/tree-sitter-racket.git 90 | shallow = true 91 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler32" 7 | version = "1.2.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.19" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "android_system_properties" 22 | version = "0.1.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "arc-swap" 40 | version = "1.5.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" 43 | 44 | [[package]] 45 | name = "atty" 46 | version = "0.2.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 49 | dependencies = [ 50 | "hermit-abi", 51 | "libc", 52 | "winapi", 53 | ] 54 | 55 | [[package]] 56 | name = "autocfg" 57 | version = "1.1.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "bumpalo" 69 | version = "3.11.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" 72 | 73 | [[package]] 74 | name = "cc" 75 | version = "1.0.73" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 78 | dependencies = [ 79 | "jobserver", 80 | ] 81 | 82 | [[package]] 83 | name = "cfg-if" 84 | version = "1.0.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 87 | 88 | [[package]] 89 | name = "chrono" 90 | version = "0.4.22" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" 93 | dependencies = [ 94 | "iana-time-zone", 95 | "js-sys", 96 | "num-integer", 97 | "num-traits", 98 | "time 0.1.44", 99 | "wasm-bindgen", 100 | "winapi", 101 | ] 102 | 103 | [[package]] 104 | name = "clap" 105 | version = "2.34.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 108 | dependencies = [ 109 | "ansi_term", 110 | "atty", 111 | "bitflags", 112 | "strsim", 113 | "textwrap", 114 | "unicode-width", 115 | "vec_map", 116 | ] 117 | 118 | [[package]] 119 | name = "core-foundation-sys" 120 | version = "0.8.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 123 | 124 | [[package]] 125 | name = "crc32fast" 126 | version = "1.3.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 129 | dependencies = [ 130 | "cfg-if", 131 | ] 132 | 133 | [[package]] 134 | name = "crossbeam" 135 | version = "0.2.12" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" 138 | 139 | [[package]] 140 | name = "crossbeam-channel" 141 | version = "0.5.6" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 144 | dependencies = [ 145 | "cfg-if", 146 | "crossbeam-utils", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-utils" 151 | version = "0.8.11" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" 154 | dependencies = [ 155 | "cfg-if", 156 | "once_cell", 157 | ] 158 | 159 | [[package]] 160 | name = "dirs-next" 161 | version = "2.0.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 164 | dependencies = [ 165 | "cfg-if", 166 | "dirs-sys-next", 167 | ] 168 | 169 | [[package]] 170 | name = "dirs-sys-next" 171 | version = "0.1.2" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 174 | dependencies = [ 175 | "libc", 176 | "redox_users", 177 | "winapi", 178 | ] 179 | 180 | [[package]] 181 | name = "either" 182 | version = "1.8.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 185 | 186 | [[package]] 187 | name = "getrandom" 188 | version = "0.2.7" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 191 | dependencies = [ 192 | "cfg-if", 193 | "libc", 194 | "wasi 0.11.0+wasi-snapshot-preview1", 195 | ] 196 | 197 | [[package]] 198 | name = "hermit-abi" 199 | version = "0.1.19" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 202 | dependencies = [ 203 | "libc", 204 | ] 205 | 206 | [[package]] 207 | name = "iana-time-zone" 208 | version = "0.1.47" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" 211 | dependencies = [ 212 | "android_system_properties", 213 | "core-foundation-sys", 214 | "js-sys", 215 | "once_cell", 216 | "wasm-bindgen", 217 | "winapi", 218 | ] 219 | 220 | [[package]] 221 | name = "itertools" 222 | version = "0.8.2" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 225 | dependencies = [ 226 | "either", 227 | ] 228 | 229 | [[package]] 230 | name = "itoa" 231 | version = "1.0.3" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 234 | 235 | [[package]] 236 | name = "jobserver" 237 | version = "0.1.24" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 240 | dependencies = [ 241 | "libc", 242 | ] 243 | 244 | [[package]] 245 | name = "js-sys" 246 | version = "0.3.59" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 249 | dependencies = [ 250 | "wasm-bindgen", 251 | ] 252 | 253 | [[package]] 254 | name = "kak-tree" 255 | version = "0.1.0" 256 | dependencies = [ 257 | "cc", 258 | "clap", 259 | "itertools", 260 | "serde", 261 | "slog", 262 | "slog-scope", 263 | "sloggers", 264 | "toml", 265 | "tree-sitter", 266 | ] 267 | 268 | [[package]] 269 | name = "lazy_static" 270 | version = "1.4.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 273 | 274 | [[package]] 275 | name = "libc" 276 | version = "0.2.132" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 279 | 280 | [[package]] 281 | name = "libflate" 282 | version = "0.1.27" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" 285 | dependencies = [ 286 | "adler32", 287 | "crc32fast", 288 | "rle-decode-fast", 289 | "take_mut", 290 | ] 291 | 292 | [[package]] 293 | name = "log" 294 | version = "0.3.9" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 297 | dependencies = [ 298 | "log 0.4.17", 299 | ] 300 | 301 | [[package]] 302 | name = "log" 303 | version = "0.4.17" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 306 | dependencies = [ 307 | "cfg-if", 308 | ] 309 | 310 | [[package]] 311 | name = "memchr" 312 | version = "2.5.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 315 | 316 | [[package]] 317 | name = "num-integer" 318 | version = "0.1.45" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 321 | dependencies = [ 322 | "autocfg", 323 | "num-traits", 324 | ] 325 | 326 | [[package]] 327 | name = "num-traits" 328 | version = "0.2.15" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 331 | dependencies = [ 332 | "autocfg", 333 | ] 334 | 335 | [[package]] 336 | name = "num_threads" 337 | version = "0.1.6" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 340 | dependencies = [ 341 | "libc", 342 | ] 343 | 344 | [[package]] 345 | name = "once_cell" 346 | version = "1.14.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" 349 | 350 | [[package]] 351 | name = "proc-macro2" 352 | version = "1.0.43" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 355 | dependencies = [ 356 | "unicode-ident", 357 | ] 358 | 359 | [[package]] 360 | name = "quote" 361 | version = "1.0.21" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 364 | dependencies = [ 365 | "proc-macro2", 366 | ] 367 | 368 | [[package]] 369 | name = "redox_syscall" 370 | version = "0.2.16" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 373 | dependencies = [ 374 | "bitflags", 375 | ] 376 | 377 | [[package]] 378 | name = "redox_users" 379 | version = "0.4.3" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 382 | dependencies = [ 383 | "getrandom", 384 | "redox_syscall", 385 | "thiserror", 386 | ] 387 | 388 | [[package]] 389 | name = "regex" 390 | version = "1.6.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 393 | dependencies = [ 394 | "aho-corasick", 395 | "memchr", 396 | "regex-syntax", 397 | ] 398 | 399 | [[package]] 400 | name = "regex-syntax" 401 | version = "0.6.27" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 404 | 405 | [[package]] 406 | name = "rle-decode-fast" 407 | version = "1.0.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 410 | 411 | [[package]] 412 | name = "rustversion" 413 | version = "1.0.9" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 416 | 417 | [[package]] 418 | name = "ryu" 419 | version = "1.0.11" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 422 | 423 | [[package]] 424 | name = "serde" 425 | version = "1.0.144" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" 428 | dependencies = [ 429 | "serde_derive", 430 | ] 431 | 432 | [[package]] 433 | name = "serde_derive" 434 | version = "1.0.144" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" 437 | dependencies = [ 438 | "proc-macro2", 439 | "quote", 440 | "syn", 441 | ] 442 | 443 | [[package]] 444 | name = "serde_json" 445 | version = "1.0.85" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 448 | dependencies = [ 449 | "itoa", 450 | "ryu", 451 | "serde", 452 | ] 453 | 454 | [[package]] 455 | name = "slog" 456 | version = "2.7.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" 459 | 460 | [[package]] 461 | name = "slog-async" 462 | version = "2.7.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "766c59b252e62a34651412870ff55d8c4e6d04df19b43eecb2703e417b097ffe" 465 | dependencies = [ 466 | "crossbeam-channel", 467 | "slog", 468 | "take_mut", 469 | "thread_local", 470 | ] 471 | 472 | [[package]] 473 | name = "slog-kvfilter" 474 | version = "0.7.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" 477 | dependencies = [ 478 | "regex", 479 | "slog", 480 | ] 481 | 482 | [[package]] 483 | name = "slog-scope" 484 | version = "4.4.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" 487 | dependencies = [ 488 | "arc-swap", 489 | "lazy_static", 490 | "slog", 491 | ] 492 | 493 | [[package]] 494 | name = "slog-stdlog" 495 | version = "3.0.5" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "f1c469573d1e3f36f9eee66cd132206caf47b50c94b1f6c6e7b4d8235e9ecf01" 498 | dependencies = [ 499 | "crossbeam", 500 | "log 0.3.9", 501 | "slog", 502 | "slog-scope", 503 | ] 504 | 505 | [[package]] 506 | name = "slog-term" 507 | version = "2.9.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "87d29185c55b7b258b4f120eab00f48557d4d9bc814f41713f449d35b0f8977c" 510 | dependencies = [ 511 | "atty", 512 | "slog", 513 | "term", 514 | "thread_local", 515 | "time 0.3.14", 516 | ] 517 | 518 | [[package]] 519 | name = "sloggers" 520 | version = "0.3.6" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "31bef221d42166d6708aa1e9b0182324b37a0a7517ff590ec201dbfe1cfa46ef" 523 | dependencies = [ 524 | "chrono", 525 | "libflate", 526 | "regex", 527 | "serde", 528 | "serde_derive", 529 | "slog", 530 | "slog-async", 531 | "slog-kvfilter", 532 | "slog-scope", 533 | "slog-stdlog", 534 | "slog-term", 535 | "trackable 0.2.24", 536 | ] 537 | 538 | [[package]] 539 | name = "strsim" 540 | version = "0.8.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 543 | 544 | [[package]] 545 | name = "syn" 546 | version = "1.0.99" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 549 | dependencies = [ 550 | "proc-macro2", 551 | "quote", 552 | "unicode-ident", 553 | ] 554 | 555 | [[package]] 556 | name = "take_mut" 557 | version = "0.2.2" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 560 | 561 | [[package]] 562 | name = "term" 563 | version = "0.7.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 566 | dependencies = [ 567 | "dirs-next", 568 | "rustversion", 569 | "winapi", 570 | ] 571 | 572 | [[package]] 573 | name = "textwrap" 574 | version = "0.11.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 577 | dependencies = [ 578 | "unicode-width", 579 | ] 580 | 581 | [[package]] 582 | name = "thiserror" 583 | version = "1.0.34" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" 586 | dependencies = [ 587 | "thiserror-impl", 588 | ] 589 | 590 | [[package]] 591 | name = "thiserror-impl" 592 | version = "1.0.34" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" 595 | dependencies = [ 596 | "proc-macro2", 597 | "quote", 598 | "syn", 599 | ] 600 | 601 | [[package]] 602 | name = "thread_local" 603 | version = "1.1.4" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 606 | dependencies = [ 607 | "once_cell", 608 | ] 609 | 610 | [[package]] 611 | name = "time" 612 | version = "0.1.44" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 615 | dependencies = [ 616 | "libc", 617 | "wasi 0.10.0+wasi-snapshot-preview1", 618 | "winapi", 619 | ] 620 | 621 | [[package]] 622 | name = "time" 623 | version = "0.3.14" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" 626 | dependencies = [ 627 | "itoa", 628 | "libc", 629 | "num_threads", 630 | "time-macros", 631 | ] 632 | 633 | [[package]] 634 | name = "time-macros" 635 | version = "0.2.4" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 638 | 639 | [[package]] 640 | name = "toml" 641 | version = "0.5.9" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 644 | dependencies = [ 645 | "serde", 646 | ] 647 | 648 | [[package]] 649 | name = "trackable" 650 | version = "0.2.24" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" 653 | dependencies = [ 654 | "trackable 1.2.0", 655 | "trackable_derive", 656 | ] 657 | 658 | [[package]] 659 | name = "trackable" 660 | version = "1.2.0" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "017e2a1a93718e4e8386d037cfb8add78f1d690467f4350fb582f55af1203167" 663 | dependencies = [ 664 | "trackable_derive", 665 | ] 666 | 667 | [[package]] 668 | name = "trackable_derive" 669 | version = "1.0.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" 672 | dependencies = [ 673 | "quote", 674 | "syn", 675 | ] 676 | 677 | [[package]] 678 | name = "tree-sitter" 679 | version = "0.6.3" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "376e181cb69da67bad2d69806cf2500656fd68123c526a61e5cbbfc65110c5b0" 682 | dependencies = [ 683 | "cc", 684 | "regex", 685 | "serde", 686 | "serde_derive", 687 | "serde_json", 688 | ] 689 | 690 | [[package]] 691 | name = "unicode-ident" 692 | version = "1.0.3" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 695 | 696 | [[package]] 697 | name = "unicode-width" 698 | version = "0.1.9" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 701 | 702 | [[package]] 703 | name = "vec_map" 704 | version = "0.8.2" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 707 | 708 | [[package]] 709 | name = "wasi" 710 | version = "0.10.0+wasi-snapshot-preview1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 713 | 714 | [[package]] 715 | name = "wasi" 716 | version = "0.11.0+wasi-snapshot-preview1" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 719 | 720 | [[package]] 721 | name = "wasm-bindgen" 722 | version = "0.2.82" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 725 | dependencies = [ 726 | "cfg-if", 727 | "wasm-bindgen-macro", 728 | ] 729 | 730 | [[package]] 731 | name = "wasm-bindgen-backend" 732 | version = "0.2.82" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 735 | dependencies = [ 736 | "bumpalo", 737 | "log 0.4.17", 738 | "once_cell", 739 | "proc-macro2", 740 | "quote", 741 | "syn", 742 | "wasm-bindgen-shared", 743 | ] 744 | 745 | [[package]] 746 | name = "wasm-bindgen-macro" 747 | version = "0.2.82" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 750 | dependencies = [ 751 | "quote", 752 | "wasm-bindgen-macro-support", 753 | ] 754 | 755 | [[package]] 756 | name = "wasm-bindgen-macro-support" 757 | version = "0.2.82" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 760 | dependencies = [ 761 | "proc-macro2", 762 | "quote", 763 | "syn", 764 | "wasm-bindgen-backend", 765 | "wasm-bindgen-shared", 766 | ] 767 | 768 | [[package]] 769 | name = "wasm-bindgen-shared" 770 | version = "0.2.82" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 773 | 774 | [[package]] 775 | name = "winapi" 776 | version = "0.3.9" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 779 | dependencies = [ 780 | "winapi-i686-pc-windows-gnu", 781 | "winapi-x86_64-pc-windows-gnu", 782 | ] 783 | 784 | [[package]] 785 | name = "winapi-i686-pc-windows-gnu" 786 | version = "0.4.0" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 789 | 790 | [[package]] 791 | name = "winapi-x86_64-pc-windows-gnu" 792 | version = "0.4.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 795 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kak-tree" 3 | version = "0.1.0" 4 | authors = ["Ruslan Prokopchuk "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2.33.0" 9 | itertools = "0.8.2" 10 | serde = { version = "1.0.104", features = ["derive"] } 11 | slog = { version = "2.5.2", features = ["release_max_level_debug"] } 12 | slog-scope = "4.3.0" 13 | sloggers = "0.3.5" 14 | toml = "0.5.6" 15 | tree-sitter = "0.6.0" 16 | 17 | [build-dependencies] 18 | cc = { version = "1.0.50", features = ["parallel"] } 19 | 20 | # [profile.release] 21 | # lto = true 22 | 23 | [features] 24 | default = ["rust"] 25 | all = [ 26 | "bash", 27 | "c_sharp", 28 | "c", 29 | "clojure", 30 | "cpp", 31 | "css", 32 | "elm", 33 | "go", 34 | "haskell", 35 | "html", 36 | "java", 37 | "javascript", 38 | "json", 39 | "julia", 40 | "ocaml", 41 | "php", 42 | "python", 43 | "racket", 44 | "ruby", 45 | "rust", 46 | "scala", 47 | "typescript" 48 | ] 49 | bash = [] 50 | c_sharp = [] 51 | c = [] 52 | clojure = [] 53 | cpp = [] 54 | css = [] 55 | elm = [] 56 | go = [] 57 | haskell = [] 58 | html = [] 59 | java = [] 60 | javascript = [] 61 | json = [] 62 | julia = [] 63 | ocaml = [] 64 | php = [] 65 | python = [] 66 | racket = [] 67 | ruby = [] 68 | rust = [] 69 | scala = [] 70 | typescript = [] 71 | 72 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | = Structural selections for Kakoune 2 | 3 | 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]. 4 | 5 | Status: proof of concept, interface and overall development direction could change drastically based on feedback. 6 | 7 | == Installation 8 | 9 | Replace `"rust javascript"` with a list of languages you need. Or use `all` to build all supported 10 | languages. Note that `all` build takes a long time, and resulting binary is quite fat which could 11 | have a negative impact on responsiveness. 12 | 13 | ---- 14 | git clone --recurse-submodules 15 | cargo install --path . --force --features "rust javascript" 16 | cp rc/tree.kak ~/.config/kak/autoload/ 17 | ---- 18 | 19 | Look at `Cargo.toml` for a full list of supported languages. 20 | 21 | It is possible to check programmaticaly if kak-tree was built with support for a given filetype: 22 | 23 | ---- 24 | kak-tree --do-you-understand rust 25 | ---- 26 | 27 | If language is supported then exit code is 0 otherwise it's non-zero (1 at the moment, but it is not 28 | guaranteed in future). 29 | 30 | == Usage 31 | 32 | Tree-sitter parsers produce very detailed syntax tree, many elements of which are not interesting 33 | for day-to-day selection purposes. kak-tree introduces the concept of a _visible_ node. Node is 34 | _visible_ when: 35 | 36 | . Node is named in the tree-sitter grammar for the given language (as opposed to anonymous nodes, 37 | http://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes[more]). 38 | . Either there is no white/blacklist for the given filetype or node kind is whitelisted or not 39 | blacklisted. See <> for details about white/blacklisting. 40 | 41 | Most of the kak-tree commands operate on _visible_ nodes and skip not _visible_ ones. 42 | 43 | [cols=2*] 44 | |=== 45 | 46 | | tree-select-parent-node [] 47 | | Select the closest visible ancestor or ancestor of KIND when provided. 48 | 49 | | tree-select-next-node [] 50 | | Select the closest visible next sibling or next sibling of KIND when provided. 51 | 52 | | tree-select-previous-node [] 53 | | Select the closest visible previous sibling or previous sibling of KIND when provided. 54 | 55 | | tree-select-children [] 56 | | Select all immediate visible children or all descendants matching KIND when provided. 57 | 58 | | tree-select-first-child [] 59 | | Select the first immediate visible children or the first descendant matching KIND when provided. 60 | 61 | | tree-node-sexp 62 | | Show info box with a syntax tree of the main selection parent. 63 | |=== 64 | 65 | == Configuration 66 | 67 | kak-tree supports configuration via a configuration file. As for now there is no default path to 68 | load the configuration file, and it must be given using CLI option `--config` or `-c` for short: 69 | 70 | ---- 71 | set global tree_cmd 'kak-tree -c /path/to/kak-tree.toml' 72 | ---- 73 | 74 | === Filetype configuration 75 | 76 | Configuration for specific filetypes should be provided like this: 77 | 78 | ---- 79 | [filetype.rust] 80 | blacklist = ["identifier", "scoped_identifier", "string_literal"] 81 | whitelist = ["function_item"] 82 | group.identifier = ["identifier", "scoped_identifier"] 83 | group.fn = ["function_item"] 84 | 85 | [filetype.javascript] 86 | group.fn = ["function", "arrow_function"] 87 | ---- 88 | 89 | Configuration under the `[filetype.default]` key will be used for all filetypes without 90 | configuration. Specific filetype configuration _doesn't_ extend default configuration but rather 91 | overwrites it. 92 | 93 | ==== White/blacklisting 94 | 95 | If `whitelist` array is provided then kak-tree selection will skip nodes which kinds are not whitelisted. 96 | If `blacklist` array is provided then kak-tree selection will skip nodes which kinds are blacklisted. 97 | 98 | NOTE: `whitelist` takes precedence over `blacklist`. In the Rust example above kak-tree would expand 99 | selection up to the function definition, ignoring other node kinds. 100 | 101 | NOTE: `tree-node-sexp` command is useful for exploring node kinds which appear in the specific code. 102 | 103 | Whitelisting or blacklisting node kinds could be tedious as tree-sitter parsers define many of them, 104 | but it also could be rewarding as you will be able to quickly modify selection in scopes which 105 | matter for you with fewer keystrokes. 106 | 107 | ==== Kind groups 108 | 109 | Groups of node kinds serve a two-fold purpose: 110 | 111 | . Groups allow matching functionally similar node kinds (i.e. `identifier` and `scoped_identifier` 112 | in Rust) by a single query. 113 | 114 | . Groups allow matching functionally similar nodes across filetypes (i.e. `function_item` in Rust 115 | and `function` in JavaScript) as tree-sitter parsers don't use uniform node kind names. 116 | 117 | NOTE: `whitelist` and `blacklist` options doesn't expand groups yet. 118 | 119 | == License 120 | 121 | For kak-tree see UNLICENSE file. For tree-sitter and its parsers look at their repositories. 122 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | 3 | use std::path::PathBuf; 4 | 5 | fn main() { 6 | for lang in &[ 7 | #[cfg(feature = "bash")] 8 | "bash", 9 | #[cfg(feature = "c_sharp")] 10 | "c-sharp", 11 | #[cfg(feature = "c")] 12 | "c", 13 | #[cfg(feature = "clojure")] 14 | "clojure", 15 | #[cfg(feature = "cpp")] 16 | "cpp", 17 | #[cfg(feature = "css")] 18 | "css", 19 | #[cfg(feature = "elm")] 20 | "elm", 21 | #[cfg(feature = "go")] 22 | "go", 23 | #[cfg(feature = "haskell")] 24 | "haskell", 25 | #[cfg(feature = "html")] 26 | "html", 27 | #[cfg(feature = "java")] 28 | "java", 29 | #[cfg(feature = "javascript")] 30 | "javascript", 31 | #[cfg(feature = "json")] 32 | "json", 33 | #[cfg(feature = "julia")] 34 | "julia", 35 | #[cfg(feature = "ocaml")] 36 | "ocaml", 37 | #[cfg(feature = "php")] 38 | "php", 39 | #[cfg(feature = "python")] 40 | "python", 41 | #[cfg(feature = "racket")] 42 | "racket", 43 | #[cfg(feature = "ruby")] 44 | "ruby", 45 | #[cfg(feature = "rust")] 46 | "rust", 47 | #[cfg(feature = "scala")] 48 | "scala", 49 | #[cfg(feature = "typescript")] 50 | "typescript", 51 | ] { 52 | let mut build = cc::Build::new(); 53 | 54 | let tree_sitter: PathBuf = match *lang { 55 | "typescript" => [ 56 | "vendor", 57 | &format!("tree-sitter-{}", lang), 58 | "typescript", 59 | "src", 60 | ] 61 | .iter() 62 | .collect(), 63 | _ => ["vendor", &format!("tree-sitter-{}", lang), "src"] 64 | .iter() 65 | .collect(), 66 | }; 67 | 68 | build 69 | .include(&tree_sitter) 70 | .file(tree_sitter.join("parser.c")); 71 | 72 | if tree_sitter.join("scanner.c").exists() { 73 | build.file(tree_sitter.join("scanner.c")); 74 | } else if tree_sitter.join("scanner.cc").exists() { 75 | let mut build = cc::Build::new(); 76 | build.include(&tree_sitter); 77 | build.cpp(true); 78 | build.file(tree_sitter.join("scanner.cc")); 79 | build.compile(&format!("tree_sitter_{}_scanner", lang)); 80 | }; 81 | 82 | build.compile(&format!("tree_sitter_{}", lang)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rc/tree.kak: -------------------------------------------------------------------------------- 1 | # Path to the kak-tree executable. 2 | # To load config: set-option global tree_cmd "kak-tree --config /path/to/kak-tree.toml" 3 | # To enable debug logging: set-option global tree_cmd "kak-tree -vvv" 4 | declare-option str tree_cmd "kak-tree" 5 | 6 | # Path to the log file. 7 | declare-option str tree_log "/tmp/kak-tree.log" 8 | 9 | # Option to store draft of the current buffer before passing to shell. 10 | declare-option -hidden str tree_draft 11 | 12 | define-command -hidden tree-command -params 1..2 -docstring %{ 13 | tree-command [] 14 | Send request to kak-tree and evaluate response. 15 | } %{ 16 | evaluate-commands -draft -no-hooks %{exec '%'; set buffer tree_draft %val{selection}} 17 | evaluate-commands %sh{ 18 | 19 | tree_draft=$(printf '%s.' "${kak_opt_tree_draft}" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed "s/$(printf '\t')/\\\\t/g") 20 | 21 | tree_draft=${tree_draft%.} 22 | 23 | printf ' 24 | filetype = "%s" 25 | selections_desc = "%s" 26 | content = """ 27 | %s""" 28 | [op] 29 | type = "%s" 30 | %s 31 | ' "${kak_opt_filetype}" "${kak_selections_desc}" "${tree_draft}" $1 "$2" | ${kak_opt_tree_cmd} 2>${kak_opt_tree_log} 32 | } 33 | } 34 | 35 | 36 | define-command -hidden tree-command-with-optional-kind -params 1..2 -docstring %{ 37 | tree-command-with-optional-kind [] 38 | Send request which optionally takes node kind. 39 | } %{ 40 | tree-command %arg{1} %sh{ 41 | if [ -n "$2" ]; then 42 | printf 'kind = "%s"' "$2" 43 | fi 44 | } 45 | } 46 | 47 | define-command tree-select-parent-node -params ..1 -docstring %{ 48 | tree-select-parent-node [] 49 | Select the closest visible ancestor or ancestor of KIND when provided. 50 | } %{ tree-command-with-optional-kind SelectParentNode %arg{1} } 51 | 52 | define-command tree-select-next-node -params ..1 -docstring %{ 53 | tree-select-next-node [] 54 | Select the closest visible next sibling or next sibling of KIND when provided. 55 | } %{ tree-command-with-optional-kind SelectNextNode %arg{1} } 56 | 57 | define-command tree-select-previous-node -params ..1 -docstring %{ 58 | tree-select-previous-node [] 59 | Select the closest visible previous sibling or previous sibling of KIND when provided. 60 | } %{ tree-command-with-optional-kind SelectPreviousNode %arg{1} } 61 | 62 | define-command tree-select-children -params ..1 -docstring %{ 63 | tree-select-children [] 64 | Select all immediate visible children or all descendants matching KIND when provided. 65 | } %{ tree-command-with-optional-kind SelectChildren %arg{1} } 66 | 67 | define-command tree-node-sexp -docstring %{ 68 | tree-node-sexp 69 | Show info box with a syntax tree of the main selection parent. 70 | } %{ tree-command NodeSExp } 71 | 72 | define-command tree-select-first-child -params ..1 -docstring %{ 73 | tree-select-first-child [] 74 | Select the first immediate visible children or the first descendant matching KIND when provided. 75 | } %{ 76 | tree-command-with-optional-kind SelectChildren %arg{1} 77 | execute-keys 78 | } 79 | 80 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ul/kak-tree/b2b8539ddf6aee43abfd2131f15ed667ea3855f5/rustfmt.toml -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use std::collections::HashMap; 3 | use toml; 4 | use tree_sitter::Node; 5 | 6 | #[derive(Deserialize)] 7 | pub struct Config { 8 | #[serde(default)] 9 | filetype: HashMap, 10 | } 11 | 12 | #[derive(Clone, Deserialize, Default)] 13 | pub struct FiletypeConfig { 14 | blacklist: Option>, 15 | whitelist: Option>, 16 | #[serde(default)] 17 | group: HashMap>, 18 | } 19 | 20 | impl Default for Config { 21 | fn default() -> Self { 22 | let mut config = Config { 23 | filetype: HashMap::default(), 24 | }; 25 | config 26 | .filetype 27 | .insert("default".to_owned(), FiletypeConfig::default()); 28 | config 29 | } 30 | } 31 | 32 | impl Config { 33 | pub fn load>(path: P) -> Option { 34 | let config = std::fs::read_to_string(path).ok()?; 35 | let mut config: Config = toml::from_str(&config).ok()?; 36 | if config.filetype.get("default").is_none() { 37 | config 38 | .filetype 39 | .insert("default".to_owned(), FiletypeConfig::default()); 40 | } 41 | Some(config) 42 | } 43 | 44 | pub fn get_filetype_config<'a>(&'a self, filetype: &str) -> &'a FiletypeConfig { 45 | self.filetype 46 | .get(filetype) 47 | .or_else(|| self.filetype.get("default")) 48 | .unwrap() 49 | } 50 | } 51 | 52 | impl FiletypeConfig { 53 | pub fn is_node_visible(&self, node: Node) -> bool { 54 | let kind = node.kind(); 55 | match &self.whitelist { 56 | Some(whitelist) => whitelist.iter().any(|x| x == kind), 57 | None => match &self.blacklist { 58 | Some(blacklist) => !blacklist.iter().any(|x| x == kind), 59 | None => true, 60 | }, 61 | } 62 | } 63 | 64 | pub fn resolve_alias<'a>(&'a self, kind: &str) -> Vec { 65 | self.group 66 | .get(kind) 67 | .cloned() 68 | .unwrap_or_else(|| vec![kind.to_string()]) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::Language; 2 | 3 | extern "C" { 4 | #[cfg(feature = "bash")] 5 | fn tree_sitter_bash() -> Language; 6 | #[cfg(feature = "c")] 7 | fn tree_sitter_c() -> Language; 8 | #[cfg(feature = "c_sharp")] 9 | fn tree_sitter_c_sharp() -> Language; 10 | #[cfg(feature = "clojure")] 11 | fn tree_sitter_clojure() -> Language; 12 | #[cfg(feature = "cpp")] 13 | fn tree_sitter_cpp() -> Language; 14 | #[cfg(feature = "css")] 15 | fn tree_sitter_css() -> Language; 16 | #[cfg(feature = "elm")] 17 | fn tree_sitter_elm() -> Language; 18 | #[cfg(feature = "go")] 19 | fn tree_sitter_go() -> Language; 20 | #[cfg(feature = "haskell")] 21 | fn tree_sitter_haskell() -> Language; 22 | #[cfg(feature = "html")] 23 | fn tree_sitter_html() -> Language; 24 | #[cfg(feature = "java")] 25 | fn tree_sitter_java() -> Language; 26 | #[cfg(feature = "javascript")] 27 | fn tree_sitter_javascript() -> Language; 28 | #[cfg(feature = "json")] 29 | fn tree_sitter_json() -> Language; 30 | #[cfg(feature = "julia")] 31 | fn tree_sitter_julia() -> Language; 32 | #[cfg(feature = "ocaml")] 33 | fn tree_sitter_ocaml() -> Language; 34 | #[cfg(feature = "php")] 35 | fn tree_sitter_php() -> Language; 36 | #[cfg(feature = "python")] 37 | fn tree_sitter_python() -> Language; 38 | #[cfg(feature = "racket")] 39 | fn tree_sitter_racket() -> Language; 40 | #[cfg(feature = "ruby")] 41 | fn tree_sitter_ruby() -> Language; 42 | #[cfg(feature = "rust")] 43 | fn tree_sitter_rust() -> Language; 44 | #[cfg(feature = "scala")] 45 | fn tree_sitter_scala() -> Language; 46 | #[cfg(feature = "typescript")] 47 | fn tree_sitter_typescript() -> Language; 48 | } 49 | 50 | pub fn filetype_to_language(filetype: &str) -> Option { 51 | let sitter = match filetype { 52 | #[cfg(feature = "bash")] 53 | "sh" => tree_sitter_bash, 54 | #[cfg(feature = "c")] 55 | "c" => tree_sitter_c, 56 | #[cfg(feature = "c_sharp")] 57 | "c_sharp" => tree_sitter_c_sharp, 58 | #[cfg(feature = "clojure")] 59 | "clojure" => tree_sitter_clojure, 60 | #[cfg(feature = "cpp")] 61 | "cpp" => tree_sitter_cpp, 62 | #[cfg(feature = "css")] 63 | "css" => tree_sitter_css, 64 | #[cfg(feature = "elm")] 65 | "elm" => tree_sitter_elm, 66 | #[cfg(feature = "go")] 67 | "go" => tree_sitter_go, 68 | #[cfg(feature = "haskell")] 69 | "haskell" => tree_sitter_haskell, 70 | #[cfg(feature = "html")] 71 | "html" => tree_sitter_html, 72 | #[cfg(feature = "java")] 73 | "java" => tree_sitter_java, 74 | #[cfg(feature = "javascript")] 75 | "javascript" => tree_sitter_javascript, 76 | #[cfg(feature = "json")] 77 | "json" => tree_sitter_json, 78 | #[cfg(feature = "julia")] 79 | "julia" => tree_sitter_julia, 80 | #[cfg(feature = "ocaml")] 81 | "ocaml" => tree_sitter_ocaml, 82 | #[cfg(feature = "php")] 83 | "php" => tree_sitter_php, 84 | #[cfg(feature = "python")] 85 | "python" => tree_sitter_python, 86 | #[cfg(feature = "racket")] 87 | "racket" => tree_sitter_racket, 88 | #[cfg(feature = "ruby")] 89 | "ruby" => tree_sitter_ruby, 90 | #[cfg(feature = "rust")] 91 | "rust" => tree_sitter_rust, 92 | #[cfg(feature = "scala")] 93 | "scala" => tree_sitter_scala, 94 | #[cfg(feature = "typescript")] 95 | "typescript" => tree_sitter_typescript, 96 | _ => return None, 97 | }; 98 | Some(unsafe { sitter() }) 99 | } 100 | -------------------------------------------------------------------------------- /src/kakoune.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use tree_sitter::{Point, Range}; 3 | 4 | pub fn select_ranges(buffer: &[String], ranges: &[Range]) -> String { 5 | if ranges.is_empty() { 6 | "fail no selections remaining".into() 7 | } else { 8 | format!("select {}", ranges_to_selections_desc(&buffer, &ranges)) 9 | } 10 | } 11 | 12 | pub fn ranges_to_selections_desc(buffer: &[String], ranges: &[Range]) -> String { 13 | ranges 14 | .iter() 15 | .map(|range| { 16 | let mut end_row = range.end_point.row; 17 | let mut end_column = range.end_point.column; 18 | if end_column > 0 { 19 | end_column -= 1; 20 | } else { 21 | end_row -= 1; 22 | end_column = 1_000_000; 23 | } 24 | format!( 25 | "{},{}", 26 | point_to_kak_coords(buffer, range.start_point), 27 | point_to_kak_coords(buffer, Point::new(end_row, end_column)) 28 | ) 29 | }) 30 | .join(" ") 31 | } 32 | 33 | pub fn selections_desc_to_ranges(buffer: &[String], selections_desc: &str) -> Vec { 34 | selections_desc 35 | .split_whitespace() 36 | .map(|selection_desc| selection_desc_to_range(buffer, selection_desc)) 37 | .collect() 38 | } 39 | 40 | fn selection_desc_to_range(buffer: &[String], selection_desc: &str) -> Range { 41 | let mut range = selection_desc.split(','); 42 | let start = range.next().unwrap(); 43 | let end = range.next().unwrap(); 44 | let (start_byte, start_point) = kak_coords_to_byte_and_point(buffer, start); 45 | let (end_byte, end_point) = kak_coords_to_byte_and_point(buffer, end); 46 | let reverse = start_byte > end_byte; 47 | if reverse { 48 | Range { 49 | start_byte: end_byte, 50 | end_byte: start_byte, 51 | start_point: end_point, 52 | end_point: start_point, 53 | } 54 | } else { 55 | Range { 56 | start_byte, 57 | end_byte, 58 | start_point, 59 | end_point, 60 | } 61 | } 62 | } 63 | 64 | fn point_to_kak_coords(buffer: &[String], p: Point) -> String { 65 | let offset = buffer[p.row] 66 | .char_indices() 67 | .enumerate() 68 | .find_map(|(column, (offset, _))| { 69 | if column == p.column { 70 | Some(offset) 71 | } else { 72 | None 73 | } 74 | }) 75 | .unwrap_or_else(|| buffer[p.row].len()); 76 | format!("{}.{}", p.row + 1, offset + 1) 77 | } 78 | 79 | fn kak_coords_to_byte_and_point(buffer: &[String], coords: &str) -> (usize, Point) { 80 | let mut coords = coords.split('.'); 81 | let row = coords.next().unwrap().parse::().unwrap() - 1; 82 | let offset = coords.next().unwrap().parse::().unwrap() - 1; 83 | let byte = buffer[..row].iter().fold(0, |offset, c| offset + c.len()) + offset; 84 | let column = buffer[row] 85 | .char_indices() 86 | .position(|(i, _)| i == offset) 87 | .unwrap(); 88 | (byte, Point::new(row, column)) 89 | } 90 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use sloggers::terminal::{Destination, TerminalLoggerBuilder}; 2 | use sloggers::types::Severity; 3 | use sloggers::Build; 4 | 5 | pub fn init_global_logger(verbosity: u8) { 6 | let level = match verbosity { 7 | 0 => Severity::Error, 8 | 1 => Severity::Warning, 9 | 2 => Severity::Info, 10 | 3 => Severity::Debug, 11 | _ => Severity::Trace, 12 | }; 13 | 14 | let mut builder = TerminalLoggerBuilder::new(); 15 | builder.level(level); 16 | builder.destination(Destination::Stderr); 17 | let logger = builder.build().unwrap(); 18 | let _guard = slog_scope::set_global_logger(logger); 19 | } 20 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{Config, FiletypeConfig}; 2 | use clap::{crate_version, App, Arg}; 3 | use serde::Deserialize; 4 | use std::io::Read; 5 | use toml; 6 | use tree_sitter::{Node, Parser, Range}; 7 | 8 | mod config; 9 | mod ffi; 10 | mod kakoune; 11 | mod log; 12 | mod tree; 13 | 14 | #[derive(Deserialize)] 15 | struct Request { 16 | op: Op, 17 | filetype: String, 18 | selections_desc: String, 19 | content: String, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | #[serde(tag = "type")] 24 | enum Op { 25 | NodeSExp, 26 | SelectChildren { kind: Option }, 27 | SelectNextNode { kind: Option }, 28 | SelectParentNode { kind: Option }, 29 | SelectPreviousNode { kind: Option }, 30 | } 31 | 32 | fn main() { 33 | let matches = cli(); 34 | 35 | let verbosity = matches.occurrences_of("v") as u8; 36 | log::init_global_logger(verbosity); 37 | 38 | if let Some(filetype) = matches.value_of("do-you-understand") { 39 | let language = ffi::filetype_to_language(filetype); 40 | std::process::exit(if language.is_some() { 0 } else { 1 }); 41 | } 42 | 43 | let config = if let Some(config_path) = matches.value_of("config") { 44 | Config::load(config_path).unwrap() 45 | } else { 46 | Config::default() 47 | }; 48 | 49 | let mut request = String::new(); 50 | std::io::stdin().read_to_string(&mut request).unwrap(); 51 | let request: Request = toml::from_str(&request).unwrap(); 52 | let response = handle_request(&config, &request); 53 | println!("{}", response); 54 | } 55 | 56 | fn cli() -> clap::ArgMatches<'static> { 57 | App::new("kak-tree") 58 | .version(crate_version!()) 59 | .author("Ruslan Prokopchuk ") 60 | .about("Structural selections for Kakoune") 61 | .arg( 62 | Arg::with_name("config") 63 | .short("c") 64 | .long("config") 65 | .value_name("FILE") 66 | .help("Read config from FILE") 67 | .takes_value(true), 68 | ) 69 | .arg( 70 | Arg::with_name("do-you-understand") 71 | .long("do-you-understand") 72 | .value_name("FILETYPE") 73 | .help("Exit with 0 if FILETYPE is supported, non-zero otherwise") 74 | .takes_value(true), 75 | ) 76 | .arg( 77 | Arg::with_name("v") 78 | .short("v") 79 | .multiple(true) 80 | .help("Sets the level of verbosity"), 81 | ) 82 | .get_matches() 83 | } 84 | 85 | fn handle_request(config: &Config, request: &Request) -> String { 86 | let mut parser = Parser::new(); 87 | let language = ffi::filetype_to_language(&request.filetype).unwrap(); 88 | parser.set_language(language).unwrap(); 89 | let tree = parser.parse(&request.content, None).unwrap(); 90 | let buffer = request 91 | .content 92 | .split('\n') 93 | .map(|s| format!("{}\n", s)) 94 | .collect::>(); 95 | let ranges = kakoune::selections_desc_to_ranges(&buffer, &request.selections_desc); 96 | let mut new_ranges = Vec::new(); 97 | let filetype_config = config.get_filetype_config(&request.filetype); 98 | match &request.op { 99 | Op::SelectParentNode { kind } => { 100 | let kinds = kind 101 | .as_ref() 102 | .and_then(|kind| Some(filetype_config.resolve_alias(kind))); 103 | for range in &ranges { 104 | let node = tree::shrink_to_range(tree.root_node(), range); 105 | let node = find_parent_of_interest(filetype_config, node, &kinds); 106 | new_ranges.push(node.range()); 107 | } 108 | kakoune::select_ranges(&buffer, &new_ranges) 109 | } 110 | Op::SelectNextNode { kind } => { 111 | let kinds = kind 112 | .as_ref() 113 | .and_then(|kind| Some(filetype_config.resolve_alias(kind))); 114 | 'outer_next: for range in &ranges { 115 | let node = tree::shrink_to_range(tree.root_node(), range); 116 | let mut cursor = traverse_up_to_node_which_matters(filetype_config, node); 117 | while let Some(node) = cursor.next_named_sibling() { 118 | if filetype_config.is_node_visible(node) && node_of_kinds(node, &kinds) { 119 | new_ranges.push(node.range()); 120 | continue 'outer_next; 121 | } 122 | cursor = node; 123 | } 124 | let node = find_parent_of_interest(filetype_config, node, &kinds); 125 | new_ranges.push(node.range()); 126 | } 127 | kakoune::select_ranges(&buffer, &new_ranges) 128 | } 129 | Op::SelectPreviousNode { kind } => { 130 | let kinds = kind 131 | .as_ref() 132 | .and_then(|kind| Some(filetype_config.resolve_alias(kind))); 133 | 'outer_prev: for range in &ranges { 134 | let node = tree::shrink_to_range(tree.root_node(), range); 135 | let mut cursor = traverse_up_to_node_which_matters(filetype_config, node); 136 | while let Some(node) = cursor.prev_named_sibling() { 137 | if filetype_config.is_node_visible(node) && node_of_kinds(node, &kinds) { 138 | new_ranges.push(node.range()); 139 | continue 'outer_prev; 140 | } 141 | cursor = node; 142 | } 143 | let node = find_parent_of_interest(filetype_config, node, &kinds); 144 | new_ranges.push(node.range()); 145 | } 146 | kakoune::select_ranges(&buffer, &new_ranges) 147 | } 148 | Op::SelectChildren { kind } => { 149 | match kind { 150 | Some(kind) => { 151 | let kinds = filetype_config.resolve_alias(kind); 152 | for range in &ranges { 153 | for node in tree::nodes_in_range(tree.root_node(), range) { 154 | select_nodes(&node, &kinds, &mut new_ranges); 155 | } 156 | } 157 | } 158 | None => { 159 | for range in &ranges { 160 | let node = tree::shrink_to_range(tree.root_node(), range); 161 | for child in tree::named_children(&node) { 162 | if filetype_config.is_node_visible(child) { 163 | new_ranges.push(child.range()); 164 | } 165 | } 166 | } 167 | } 168 | } 169 | kakoune::select_ranges(&buffer, &new_ranges) 170 | } 171 | Op::NodeSExp => { 172 | let node = tree::shrink_to_range(tree.root_node(), &ranges[0]); 173 | format!("info '{}'", node.to_sexp()) 174 | } 175 | } 176 | } 177 | 178 | fn select_nodes(node: &Node, kinds: &[String], new_ranges: &mut Vec) { 179 | if kinds.iter().any(|kind| kind == node.kind()) { 180 | new_ranges.push(node.range()); 181 | } else { 182 | for child in tree::named_children(&node) { 183 | if kinds.iter().any(|kind| kind == child.kind()) { 184 | new_ranges.push(child.range()); 185 | } else { 186 | select_nodes(&child, kinds, new_ranges); 187 | } 188 | } 189 | } 190 | } 191 | 192 | fn traverse_up_to_node_which_matters<'a>( 193 | filetype_config: &FiletypeConfig, 194 | current_node: Node<'a>, 195 | ) -> Node<'a> { 196 | let mut opt_node = Some(current_node); 197 | while let Some(node) = opt_node.filter(|&n| !(n.is_named() && filetype_config.is_node_visible(n))) { 198 | opt_node = node.parent(); 199 | } 200 | opt_node.unwrap_or(current_node) 201 | } 202 | 203 | fn find_parent_of_interest<'a>( 204 | filetype_config: &FiletypeConfig, 205 | current_node: Node<'a>, 206 | kinds: &Option>, 207 | ) -> Node<'a> { 208 | let parent = current_node.parent(); 209 | match &kinds { 210 | Some(kinds) => { 211 | let mut cursor = parent; 212 | while let Some(node) = cursor { 213 | if kinds.iter().any(|kind| kind == node.kind()) { 214 | return node; 215 | } 216 | cursor = node.parent(); 217 | } 218 | current_node 219 | } 220 | None => traverse_up_to_node_which_matters(filetype_config, parent.unwrap_or(current_node)), 221 | } 222 | } 223 | 224 | fn node_of_kinds(node: Node, kinds: &Option>) -> bool { 225 | kinds 226 | .as_ref() 227 | .and_then(|kinds| Some(kinds.iter().any(|x| x == node.kind()))) 228 | .unwrap_or(true) 229 | } 230 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Node, Range}; 2 | 3 | pub fn named_children<'a>(node: &'a Node) -> impl Iterator> { 4 | (0..node.child_count()).map(move |i| node.child(i).unwrap()) 5 | } 6 | 7 | pub fn shrink_to_range<'a>(root_node: Node<'a>, range: &Range) -> Node<'a> { 8 | let mut node = root_node; 9 | 'outer: loop { 10 | let parent = node; 11 | let mut cursor = parent.walk(); 12 | for child in parent.children(&mut cursor) { 13 | if child.range().start_byte <= range.start_byte 14 | && range.end_byte <= child.range().end_byte 15 | { 16 | node = child; 17 | continue 'outer; 18 | } 19 | } 20 | return highest_node_of_same_range(node); 21 | } 22 | } 23 | 24 | pub fn nodes_in_range<'a>(root_node: Node<'a>, range: &Range) -> Vec> { 25 | let mut nodes = Vec::new(); 26 | let node = shrink_to_range(root_node, range); 27 | if node.range().start_byte >= range.start_byte && range.end_byte >= node.range().end_byte { 28 | nodes.push(node); 29 | return nodes; 30 | } 31 | let mut cursor = node.walk(); 32 | for child in node.children(&mut cursor) { 33 | if child.range().start_byte <= range.start_byte && range.end_byte >= child.range().end_byte 34 | { 35 | nodes.extend(nodes_in_range( 36 | child, 37 | &Range { 38 | start_byte: range.start_byte, 39 | start_point: range.start_point, 40 | end_byte: child.range().end_byte, 41 | end_point: child.range().end_point, 42 | }, 43 | )); 44 | } else if child.range().start_byte >= range.start_byte 45 | && range.end_byte <= child.range().end_byte 46 | { 47 | nodes.extend(nodes_in_range( 48 | child, 49 | &Range { 50 | start_byte: child.range().start_byte, 51 | start_point: child.range().start_point, 52 | end_byte: range.end_byte, 53 | end_point: range.end_point, 54 | }, 55 | )); 56 | } else if child.range().start_byte >= range.start_byte 57 | && range.end_byte >= child.range().end_byte 58 | { 59 | nodes.push(child); 60 | } 61 | } 62 | nodes 63 | } 64 | 65 | fn highest_node_of_same_range<'a>(current_node: Node<'a>) -> Node<'a> { 66 | let start_byte = current_node.start_byte(); 67 | let end_byte = current_node.end_byte(); 68 | let mut node = current_node; 69 | while let Some(parent) = node.parent().and_then(|parent| { 70 | if parent.start_byte() < start_byte || end_byte < parent.end_byte() { 71 | None 72 | } else { 73 | Some(parent) 74 | } 75 | }) { 76 | node = parent 77 | } 78 | node 79 | } 80 | --------------------------------------------------------------------------------