├── .gitignore ├── .woodpecker.yml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ci └── build-release.sh ├── demo ├── cpp │ ├── .kak-dap.json │ ├── build_test.sh │ └── test_c.cpp ├── python │ ├── .kak-dap.json │ └── test.py └── ruby │ ├── .kak-dap.json │ ├── Gemfile │ ├── Gemfile.lock │ └── test.rb ├── rc └── kak-dap.kak └── src ├── breakpoints.rs ├── config.rs ├── context.rs ├── controller.rs ├── debug_adapter_comms.rs ├── general.rs ├── kakoune.rs ├── main.rs ├── stack_trace.rs ├── types.rs └── variables.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Added by cargo 2 | 3 | /target 4 | 5 | demo/cpp/test_c 6 | demo/cpp/.ccls-cache/ 7 | -------------------------------------------------------------------------------- /.woodpecker.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | RELEASE_TARGET: 3 | - x86_64-unknown-linux-musl 4 | - x86_64-apple-darwin 5 | 6 | pipeline: 7 | assembleRelease: 8 | when: 9 | event: tag 10 | image: rust:1-alpine 11 | pull: true 12 | commands: 13 | ci/build_release.sh ${RELEASE_TARGET} 14 | addBinariesToRelease: 15 | when: 16 | event: tag 17 | image: plugins/gitea-release 18 | pull: true 19 | settings: 20 | base_url: https://codeberg.org 21 | api_key: 22 | from_secret: api_token 23 | files: 24 | - kak-dap-x86_64-unknown-linux-musl.tar.gz 25 | - kak-dap-x86_64-apple-darwin.tar.gz 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | Additions: 4 | - Added repeatable -v flag to `kak-dap` binary to customize verbosity of logging 5 | - `dap-continue` now starts debugging session if one isn't running 6 | - Added dap-output command to show output events to the user 7 | - Basic syntax highlighting for variables buffer 8 | - Added `dap` user mode 9 | 10 | Bug Fixes: 11 | - Logging to file would occasionally produce binary (aka unreadable) output 12 | - Expanding/collapsing variables no longer causes cursor to jump to line 1 13 | 14 | ## 1.1.0 - 2022-03-22 15 | 16 | Additions: 17 | - Added --request flag to `kak-dap` binary to send request to currently running `kak-dap` session 18 | - Added --kakoune flag to `kak-dap` binary to allow for getting the `kak-lsp.kak` contents from the binary 19 | - Vastly improved documentation (added `kak-dap` demo asciicast, troubleshooting information, etc) 20 | 21 | Bug Fixes: 22 | - Fixed default `dap-spawn-in-terminal` implementation sending invalid message to `kak-dap` binary 23 | 24 | ## 1.0.0 - 2022-03-05 25 | 26 | First public release of kak-dap 27 | -------------------------------------------------------------------------------- /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.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "ansi_term" 22 | version = "0.12.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 25 | dependencies = [ 26 | "winapi", 27 | ] 28 | 29 | [[package]] 30 | name = "arc-swap" 31 | version = "1.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.1.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 51 | 52 | [[package]] 53 | name = "bitflags" 54 | version = "1.3.2" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 57 | 58 | [[package]] 59 | name = "bumpalo" 60 | version = "3.9.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "chrono" 72 | version = "0.4.19" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 75 | dependencies = [ 76 | "libc", 77 | "num-integer", 78 | "num-traits", 79 | "time 0.1.44", 80 | "winapi", 81 | ] 82 | 83 | [[package]] 84 | name = "clap" 85 | version = "2.34.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 88 | dependencies = [ 89 | "ansi_term", 90 | "atty", 91 | "bitflags", 92 | "strsim", 93 | "textwrap", 94 | "unicode-width", 95 | "vec_map", 96 | ] 97 | 98 | [[package]] 99 | name = "crc32fast" 100 | version = "1.3.2" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 103 | dependencies = [ 104 | "cfg-if", 105 | ] 106 | 107 | [[package]] 108 | name = "crossbeam-channel" 109 | version = "0.5.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" 112 | dependencies = [ 113 | "cfg-if", 114 | "crossbeam-utils", 115 | ] 116 | 117 | [[package]] 118 | name = "crossbeam-utils" 119 | version = "0.8.7" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" 122 | dependencies = [ 123 | "cfg-if", 124 | "lazy_static", 125 | ] 126 | 127 | [[package]] 128 | name = "dirs-next" 129 | version = "2.0.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 132 | dependencies = [ 133 | "cfg-if", 134 | "dirs-sys-next", 135 | ] 136 | 137 | [[package]] 138 | name = "dirs-sys-next" 139 | version = "0.1.2" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 142 | dependencies = [ 143 | "libc", 144 | "redox_users", 145 | "winapi", 146 | ] 147 | 148 | [[package]] 149 | name = "either" 150 | version = "1.6.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 153 | 154 | [[package]] 155 | name = "field-offset" 156 | version = "0.3.4" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" 159 | dependencies = [ 160 | "memoffset", 161 | "rustc_version", 162 | ] 163 | 164 | [[package]] 165 | name = "getrandom" 166 | version = "0.2.6" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 169 | dependencies = [ 170 | "cfg-if", 171 | "libc", 172 | "wasi", 173 | ] 174 | 175 | [[package]] 176 | name = "hermit-abi" 177 | version = "0.1.19" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 180 | dependencies = [ 181 | "libc", 182 | ] 183 | 184 | [[package]] 185 | name = "itertools" 186 | version = "0.10.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 189 | dependencies = [ 190 | "either", 191 | ] 192 | 193 | [[package]] 194 | name = "itoa" 195 | version = "1.0.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 198 | 199 | [[package]] 200 | name = "js-sys" 201 | version = "0.3.56" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 204 | dependencies = [ 205 | "wasm-bindgen", 206 | ] 207 | 208 | [[package]] 209 | name = "json" 210 | version = "0.12.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 213 | 214 | [[package]] 215 | name = "json_comments" 216 | version = "0.2.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "ab25c689752fbb49903458495d3e71853229e262a2e6136325359e0705606483" 219 | 220 | [[package]] 221 | name = "kak-dap" 222 | version = "1.1.0" 223 | dependencies = [ 224 | "clap", 225 | "crossbeam-channel", 226 | "itertools", 227 | "json", 228 | "json_comments", 229 | "libc", 230 | "slog", 231 | "slog-scope", 232 | "sloggers", 233 | "whoami", 234 | ] 235 | 236 | [[package]] 237 | name = "lazy_static" 238 | version = "1.4.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 241 | 242 | [[package]] 243 | name = "libc" 244 | version = "0.2.125" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 247 | 248 | [[package]] 249 | name = "libflate" 250 | version = "1.2.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "05605ab2bce11bcfc0e9c635ff29ef8b2ea83f29be257ee7d730cac3ee373093" 253 | dependencies = [ 254 | "adler32", 255 | "crc32fast", 256 | "libflate_lz77", 257 | ] 258 | 259 | [[package]] 260 | name = "libflate_lz77" 261 | version = "1.1.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "39a734c0493409afcd49deee13c006a04e3586b9761a03543c6272c9c51f2f5a" 264 | dependencies = [ 265 | "rle-decode-fast", 266 | ] 267 | 268 | [[package]] 269 | name = "log" 270 | version = "0.4.14" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 273 | dependencies = [ 274 | "cfg-if", 275 | ] 276 | 277 | [[package]] 278 | name = "memchr" 279 | version = "2.5.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 282 | 283 | [[package]] 284 | name = "memoffset" 285 | version = "0.6.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 288 | dependencies = [ 289 | "autocfg", 290 | ] 291 | 292 | [[package]] 293 | name = "num-integer" 294 | version = "0.1.44" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 297 | dependencies = [ 298 | "autocfg", 299 | "num-traits", 300 | ] 301 | 302 | [[package]] 303 | name = "num-traits" 304 | version = "0.2.14" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 307 | dependencies = [ 308 | "autocfg", 309 | ] 310 | 311 | [[package]] 312 | name = "num_threads" 313 | version = "0.1.6" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 316 | dependencies = [ 317 | "libc", 318 | ] 319 | 320 | [[package]] 321 | name = "once_cell" 322 | version = "1.10.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 325 | 326 | [[package]] 327 | name = "pest" 328 | version = "2.1.3" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 331 | dependencies = [ 332 | "ucd-trie", 333 | ] 334 | 335 | [[package]] 336 | name = "proc-macro2" 337 | version = "1.0.36" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 340 | dependencies = [ 341 | "unicode-xid", 342 | ] 343 | 344 | [[package]] 345 | name = "quote" 346 | version = "1.0.15" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 349 | dependencies = [ 350 | "proc-macro2", 351 | ] 352 | 353 | [[package]] 354 | name = "redox_syscall" 355 | version = "0.2.13" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 358 | dependencies = [ 359 | "bitflags", 360 | ] 361 | 362 | [[package]] 363 | name = "redox_users" 364 | version = "0.4.3" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 367 | dependencies = [ 368 | "getrandom", 369 | "redox_syscall", 370 | "thiserror", 371 | ] 372 | 373 | [[package]] 374 | name = "regex" 375 | version = "1.5.5" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 378 | dependencies = [ 379 | "aho-corasick", 380 | "memchr", 381 | "regex-syntax", 382 | ] 383 | 384 | [[package]] 385 | name = "regex-syntax" 386 | version = "0.6.25" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 389 | 390 | [[package]] 391 | name = "rle-decode-fast" 392 | version = "1.0.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" 395 | 396 | [[package]] 397 | name = "rustc_version" 398 | version = "0.3.3" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" 401 | dependencies = [ 402 | "semver", 403 | ] 404 | 405 | [[package]] 406 | name = "rustversion" 407 | version = "1.0.6" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 410 | 411 | [[package]] 412 | name = "semver" 413 | version = "0.11.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 416 | dependencies = [ 417 | "semver-parser", 418 | ] 419 | 420 | [[package]] 421 | name = "semver-parser" 422 | version = "0.10.2" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 425 | dependencies = [ 426 | "pest", 427 | ] 428 | 429 | [[package]] 430 | name = "serde" 431 | version = "1.0.136" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 434 | dependencies = [ 435 | "serde_derive", 436 | ] 437 | 438 | [[package]] 439 | name = "serde_derive" 440 | version = "1.0.136" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 443 | dependencies = [ 444 | "proc-macro2", 445 | "quote", 446 | "syn", 447 | ] 448 | 449 | [[package]] 450 | name = "slog" 451 | version = "2.7.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" 454 | 455 | [[package]] 456 | name = "slog-async" 457 | version = "2.7.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "766c59b252e62a34651412870ff55d8c4e6d04df19b43eecb2703e417b097ffe" 460 | dependencies = [ 461 | "crossbeam-channel", 462 | "slog", 463 | "take_mut", 464 | "thread_local", 465 | ] 466 | 467 | [[package]] 468 | name = "slog-kvfilter" 469 | version = "0.7.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "ae939ed7d169eed9699f4f5cd440f046f5dc5dfc27c19e3cd311619594c175e0" 472 | dependencies = [ 473 | "regex", 474 | "slog", 475 | ] 476 | 477 | [[package]] 478 | name = "slog-scope" 479 | version = "4.4.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" 482 | dependencies = [ 483 | "arc-swap", 484 | "lazy_static", 485 | "slog", 486 | ] 487 | 488 | [[package]] 489 | name = "slog-stdlog" 490 | version = "4.1.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" 493 | dependencies = [ 494 | "log", 495 | "slog", 496 | "slog-scope", 497 | ] 498 | 499 | [[package]] 500 | name = "slog-term" 501 | version = "2.9.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "87d29185c55b7b258b4f120eab00f48557d4d9bc814f41713f449d35b0f8977c" 504 | dependencies = [ 505 | "atty", 506 | "slog", 507 | "term", 508 | "thread_local", 509 | "time 0.3.9", 510 | ] 511 | 512 | [[package]] 513 | name = "sloggers" 514 | version = "2.1.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "e20d36cb80da75a9c5511872f15247ddad14ead8c1dd97a86b56d1be9f5d4a0e" 517 | dependencies = [ 518 | "chrono", 519 | "libc", 520 | "libflate", 521 | "once_cell", 522 | "regex", 523 | "serde", 524 | "slog", 525 | "slog-async", 526 | "slog-kvfilter", 527 | "slog-scope", 528 | "slog-stdlog", 529 | "slog-term", 530 | "trackable", 531 | "winapi", 532 | "windows-acl", 533 | ] 534 | 535 | [[package]] 536 | name = "strsim" 537 | version = "0.8.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 540 | 541 | [[package]] 542 | name = "syn" 543 | version = "1.0.86" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 546 | dependencies = [ 547 | "proc-macro2", 548 | "quote", 549 | "unicode-xid", 550 | ] 551 | 552 | [[package]] 553 | name = "take_mut" 554 | version = "0.2.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" 557 | 558 | [[package]] 559 | name = "term" 560 | version = "0.7.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 563 | dependencies = [ 564 | "dirs-next", 565 | "rustversion", 566 | "winapi", 567 | ] 568 | 569 | [[package]] 570 | name = "textwrap" 571 | version = "0.11.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 574 | dependencies = [ 575 | "unicode-width", 576 | ] 577 | 578 | [[package]] 579 | name = "thiserror" 580 | version = "1.0.31" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 583 | dependencies = [ 584 | "thiserror-impl", 585 | ] 586 | 587 | [[package]] 588 | name = "thiserror-impl" 589 | version = "1.0.31" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 592 | dependencies = [ 593 | "proc-macro2", 594 | "quote", 595 | "syn", 596 | ] 597 | 598 | [[package]] 599 | name = "thread_local" 600 | version = "1.1.4" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 603 | dependencies = [ 604 | "once_cell", 605 | ] 606 | 607 | [[package]] 608 | name = "time" 609 | version = "0.1.44" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 612 | dependencies = [ 613 | "libc", 614 | "wasi", 615 | "winapi", 616 | ] 617 | 618 | [[package]] 619 | name = "time" 620 | version = "0.3.9" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 623 | dependencies = [ 624 | "itoa", 625 | "libc", 626 | "num_threads", 627 | "time-macros", 628 | ] 629 | 630 | [[package]] 631 | name = "time-macros" 632 | version = "0.2.4" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 635 | 636 | [[package]] 637 | name = "trackable" 638 | version = "1.2.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "017e2a1a93718e4e8386d037cfb8add78f1d690467f4350fb582f55af1203167" 641 | dependencies = [ 642 | "trackable_derive", 643 | ] 644 | 645 | [[package]] 646 | name = "trackable_derive" 647 | version = "1.0.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" 650 | dependencies = [ 651 | "quote", 652 | "syn", 653 | ] 654 | 655 | [[package]] 656 | name = "ucd-trie" 657 | version = "0.1.3" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 660 | 661 | [[package]] 662 | name = "unicode-width" 663 | version = "0.1.9" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 666 | 667 | [[package]] 668 | name = "unicode-xid" 669 | version = "0.2.2" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 672 | 673 | [[package]] 674 | name = "vec_map" 675 | version = "0.8.2" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 678 | 679 | [[package]] 680 | name = "wasi" 681 | version = "0.10.0+wasi-snapshot-preview1" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 684 | 685 | [[package]] 686 | name = "wasm-bindgen" 687 | version = "0.2.79" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 690 | dependencies = [ 691 | "cfg-if", 692 | "wasm-bindgen-macro", 693 | ] 694 | 695 | [[package]] 696 | name = "wasm-bindgen-backend" 697 | version = "0.2.79" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 700 | dependencies = [ 701 | "bumpalo", 702 | "lazy_static", 703 | "log", 704 | "proc-macro2", 705 | "quote", 706 | "syn", 707 | "wasm-bindgen-shared", 708 | ] 709 | 710 | [[package]] 711 | name = "wasm-bindgen-macro" 712 | version = "0.2.79" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 715 | dependencies = [ 716 | "quote", 717 | "wasm-bindgen-macro-support", 718 | ] 719 | 720 | [[package]] 721 | name = "wasm-bindgen-macro-support" 722 | version = "0.2.79" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 725 | dependencies = [ 726 | "proc-macro2", 727 | "quote", 728 | "syn", 729 | "wasm-bindgen-backend", 730 | "wasm-bindgen-shared", 731 | ] 732 | 733 | [[package]] 734 | name = "wasm-bindgen-shared" 735 | version = "0.2.79" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 738 | 739 | [[package]] 740 | name = "web-sys" 741 | version = "0.3.56" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 744 | dependencies = [ 745 | "js-sys", 746 | "wasm-bindgen", 747 | ] 748 | 749 | [[package]] 750 | name = "whoami" 751 | version = "1.2.1" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" 754 | dependencies = [ 755 | "wasm-bindgen", 756 | "web-sys", 757 | ] 758 | 759 | [[package]] 760 | name = "widestring" 761 | version = "0.4.3" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" 764 | 765 | [[package]] 766 | name = "winapi" 767 | version = "0.3.9" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 770 | dependencies = [ 771 | "winapi-i686-pc-windows-gnu", 772 | "winapi-x86_64-pc-windows-gnu", 773 | ] 774 | 775 | [[package]] 776 | name = "winapi-i686-pc-windows-gnu" 777 | version = "0.4.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 780 | 781 | [[package]] 782 | name = "winapi-x86_64-pc-windows-gnu" 783 | version = "0.4.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 786 | 787 | [[package]] 788 | name = "windows-acl" 789 | version = "0.3.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "177b1723986bcb4c606058e77f6e8614b51c7f9ad2face6f6fd63dd5c8b3cec3" 792 | dependencies = [ 793 | "field-offset", 794 | "libc", 795 | "widestring", 796 | "winapi", 797 | ] 798 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kak-dap" 3 | version = "1.1.0" 4 | authors = ["James Dugan "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | clap = "2.33.0" 11 | crossbeam-channel = "0.5" 12 | itertools = "0.10.1" 13 | json = "0.12.4" 14 | json_comments = "0.2.0" 15 | libc = "0.2.71" 16 | # log = "0.4.6" 17 | # simplelog = "^0.10.0" 18 | sloggers = "2.0.2" 19 | slog-scope = "4.3.0" 20 | slog = { version = "2.5.2", features = ["release_max_level_debug"] } 21 | whoami = "1.2.1" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2022 James Dugan, et al. 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NO LONGER MAINTAINED 2 | 3 | `kak-dap` was a fun experiment to test how well a debug UI could be mimicked with Kakoune buffers, but its design is sadly 4 | flawed. A replacement plugin is in development at https://codeberg.org/jdugan6240/kak-debug. 5 | 6 | 7 | # Kakoune Debug Adapter Protocol Client 8 | 9 | kak-dap is a [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) client for [Kakoune](http://kakoune.org) implemented in [Rust](https://www.rust-lang.org). 10 | This allows Kakoune to support debugging in a variety of different languages, provided the language has a debug adapter implementation. 11 | 12 | ## Features 13 | 14 | - Launch debug adapter 15 | - Launch debuggee in external terminal (Kakoune doesn't have an integrated terminal) 16 | - Stop at breakpoints 17 | - Continue/step/next 18 | - Call stack display (current thread only) 19 | - Heirarchical variable display 20 | - Arbitrary expression evaluation 21 | 22 | ## Install 23 | 24 | ### Requirements 25 | 26 | - Rust/Cargo 27 | - Ensure cargo packages are in your path. (eg: `PATH=$HOME/.cargo/bin:$PATH`) 28 | 29 | ### Pre-built Binary 30 | 31 | If using a binary distribution of `kak-dap`, place the following in your kakrc: 32 | 33 | ``` 34 | eval %sh{kak-dap --kakoune -s $kak_session} 35 | ``` 36 | 37 | ### Plug.kak 38 | 39 | If using `plug.kak` as your plugin manager, add the following to your kakrc: 40 | 41 | ``` 42 | plug "https://codeberg.org/jdugan6240/kak-dap" do %{ 43 | cargo install --locked --force --path . 44 | } 45 | ``` 46 | 47 | ### kak-bundle 48 | 49 | If using `kak-bundle` as your plugin manager, add the following to your kakrc: 50 | 51 | ``` 52 | bundle "https://codeberg.org/jdugan6240/kak-dap" %{ 53 | cd ${kak_opt_bundle_path}/kak-dap 54 | cargo install --locked --force --path . 55 | } 56 | ``` 57 | 58 | ### Manual 59 | 60 | If not using a plugin manager, clone the repository anywhere on your system: 61 | 62 | ``` 63 | git clone https://codeberg.org/jdugan6240/kak-dap 64 | cd 65 | cargo install --locked --force --path . 66 | ``` 67 | 68 | where is the directory you cloned the repository to. 69 | 70 | Then, add the following to your kakrc: 71 | 72 | ``` 73 | source /rc/kak-dap.kak 74 | ``` 75 | 76 | where, once again, is the directory you cloned the repository to. 77 | 78 | ## Debug Adapters 79 | 80 | `kak-dap` doesn't manage installation of debug adapters, so you'll need to install 81 | them yourself. Examples of installing adapters that `kak-dap` has been tested with 82 | are in the [Debug Adapter Installation](https://codeberg.org/jdugan6240/kak-dap/wiki/Debug-Adapter-Installation) wiki page. 83 | 84 | ## Usage 85 | 86 | An (old) demo of `kak-dap` can be found here: [![asciicast](https://asciinema.org/a/fjU1GBrXSxplfP6lEo7cqYcj9.svg)](https://asciinema.org/a/fjU1GBrXSxplfP6lEo7cqYcj9) 87 | 88 | ### .kak-dap.json File 89 | 90 | `kak-dap` requires a file to be present in your project's root directory, named 91 | `.kak-dap.json`. This is a standard JSON file, with support for comments for 92 | convenience. In general, it will look like the following: 93 | 94 | ``` 95 | { 96 | // The binary run to start the debug adapter 97 | "adapter": "adapter", 98 | 99 | // The arguments to the debug adapter 100 | "adapter_args": ["args"], 101 | 102 | // The adapter ID. Needed by some debug adapters. 103 | "adapterID": "mydbg", 104 | 105 | // The arguments sent to the "launch" request 106 | "launch_args": { 107 | // This will depend on the debug adapter used. 108 | } 109 | } 110 | ``` 111 | 112 | The "adapter", "adapter_args", "adapterID", and "launch_args" values must be present. 113 | 114 | Since many debug adapters are Visual Studio Code extensions, it's not always obvious 115 | what to put in this file. Let's take this VSCode `launch.json` file as an example: 116 | 117 | ``` 118 | { 119 | "version": "0.2.0", 120 | "configurations": [ 121 | { 122 | "name": "Python: Current File", 123 | "type": "python", 124 | "request": "launch", 125 | "program": "${file}", 126 | "console": "integratedTerminal" 127 | } 128 | ] 129 | } 130 | ``` 131 | 132 | All we need to look at here is the first {} block under "configurations". Taking out 133 | the "name", "type", and "request" values, the remainder of this block can be placed 134 | under "launch_args" in our `.kak-dap.json` file. In other words, the equivalent 135 | `.kak-dap.json` file for the above `launch.json` file is: 136 | 137 | ``` 138 | { 139 | "adapter": "python", 140 | "adapter_args": ["path_to_debugpy_adapter"], 141 | "adapterID": "python", 142 | "launch_args": { 143 | "program": "path_to_file.py", 144 | "console": "integratedTerminal" 145 | } 146 | } 147 | ``` 148 | 149 | You may notice that the "${file}" expansion couldn't be translated over. This is 150 | because `kak-dap` only supports the following expansions: 151 | 152 | ``` 153 | ${CUR_DIR} - The directory containing the `.kak-dap.json` file. 154 | ${USER} - The current username. 155 | ${HOME} - The current user's home directory. 156 | $$ - A literal dollar. 157 | ``` 158 | 159 | If this is still confusing, examples of `.kak-dap.json` file configurations for 160 | various debug adapters can be found in the [Debug Adapter Installation](https://codeberg.org/jdugan6240/kak-dap/wiki/Debug-Adapter-Installation) wiki page. 161 | 162 | ### Setting breakpoints 163 | 164 | In the source file, run the `dap-toggle-breakpoint` command to toggle a breakpoint on 165 | the given line. 166 | 167 | ### Starting and interacting with the debug session 168 | 169 | Once you're ready to begin debugging, run the following command: 170 | 171 | ``` 172 | dap-start 173 | ``` 174 | 175 | If configured correctly, the debug adapter will launch and the debug session will begin. 176 | At every stopping point (usually breakpoints), the "code window" will show the current 177 | line. At this point, the following commands are available: 178 | 179 | ``` 180 | dap-continue - Continue running from the current point, or start debug session if one isn't already running 181 | dap-next - Execute and stop at the next line 182 | dap-step-in - Step into a function/method 183 | dap-step-out - Step out (return from) a function/method 184 | dap-evaluate - Evaluate an arbitrary expression and show the result 185 | ``` 186 | 187 | In addition, the stacktrace and variables buffers will be populated with the current 188 | stack trace and a variable heirarchy, respectively. In the variables buffer, some 189 | variables are expandable, and can be expanded by pressing Enter. 190 | 191 | When you're finished debugging, run the following command to stop debugging: 192 | 193 | ``` 194 | dap-stop 195 | ``` 196 | 197 | ### Custom user mode 198 | 199 | A `kak-dap` user mode is provided with mappings to several commands. You may map this to a key of your liking, below we're using `x`: 200 | 201 | ``` 202 | map global user x -docstring 'dap' ': enter-user-mode dap' 203 | ``` 204 | 205 | ## Troubleshooting 206 | 207 | `kak-dap` isn't perfect, and may fail from time to time. In case this happens, `kak-dap`'s 208 | logging can be enabled by inserting the following command in your kakrc: 209 | 210 | ``` 211 | set global dap_cmd "kak-dap -s %val{session} --log /tmp/kak-dap.log -vvvv" 212 | ``` 213 | 214 | This will cause `kak-dap` to create a debug log in the `/tmp/kak-dap.log` file. If this isn't 215 | enough to diagnose the problem, please don't hesitate to raise an issue. 216 | 217 | ## Not-so-FAQ 218 | 219 | Q: Does it work? 220 | 221 | A: Yes, but it's rather unpolished and limited at the moment. 222 | 223 | Q: What's the point of this? kakoune-gdb and kakoune-dbgp exist. 224 | 225 | A: kakoune-gdb is limited to languages supported by gdb - that is, C languages and rust. 226 | kakoune-dbgp also only supports languages currently supported by the dbgp protocol, which 227 | is mainly PHP at the moment as far as I know. The debug adapter protocol is much more widely 228 | supported, which allows for more languages to be debugged. 229 | 230 | ## License 231 | 232 | kak-dap is licensed under the BSD 0-Clause License. 233 | 234 | ## Contributors 235 | 236 | James Dugan (https://codeberg.org/jdugan6240) 237 | 238 | in0ni (https://github.com/in0ni) 239 | -------------------------------------------------------------------------------- /ci/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | target=$1 4 | 5 | curl -LSfs https://japaric.github.io/trust/install.sh | 6 | sh -s -- --force --git rust-embedded/cross --tag v0.2.1 --target $target 7 | command -v cross || PATH=~/.cargo/bin:$PATH 8 | 9 | cross build --target $target --release 10 | 11 | tar -czf kak-dap-$target.tar.gz target/$target/release/kak-dap 12 | -------------------------------------------------------------------------------- /demo/cpp/.kak-dap.json: -------------------------------------------------------------------------------- 1 | { 2 | // The binary run to start the debug adapter 3 | "adapter": "${HOME}/dap/cpptools/extension/debugAdapters/bin/OpenDebugAD7", 4 | 5 | // The arguments to the debug adapter, if any 6 | "adapter_args": [], 7 | 8 | // The adapter ID. Needed by some debug adapters. 9 | "adapterID": "cppdbg", 10 | 11 | // The arguments sent to the "launch" request 12 | // This will depend on the debug adapter being used 13 | "launch_args": { 14 | "program": "${CUR_DIR}/test_c", 15 | "args": [], 16 | "cwd": "${CUR_DIR}", 17 | "externalConsole": false, 18 | "environment": [], 19 | "MIMode": "gdb", 20 | "logging": { 21 | "programOutput": false 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/cpp/build_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | g++ -g test_c.cpp -o test_c 4 | -------------------------------------------------------------------------------- /demo/cpp/test_c.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | namespace Test { 3 | struct TestStruct { 4 | bool isInt; 5 | 6 | union { 7 | int somethingInt; 8 | char somethingChar; 9 | } something; 10 | }; 11 | 12 | TestStruct _t; 13 | 14 | void bar(TestStruct b) { 15 | std::string s; 16 | s += b.isInt ? "An int" : "A char"; 17 | std::cout << s << '\n'; 18 | } 19 | 20 | void foo(TestStruct m) { 21 | TestStruct t{true, 11}; 22 | bar(t); 23 | } 24 | } // namespace Test 25 | 26 | int main(int argc, char **argv) { 27 | int x{10}; 28 | 29 | Test::TestStruct t{true, 99}; 30 | foo(t); 31 | } 32 | -------------------------------------------------------------------------------- /demo/python/.kak-dap.json: -------------------------------------------------------------------------------- 1 | { 2 | // The binary run to start the debug adapter 3 | "adapter": "python", 4 | 5 | // The arguments to the debug adapter, if any 6 | "adapter_args": ["-m", "debugpy.adapter"], 7 | 8 | // The adapter ID. Needed by some debug adapters. 9 | "adapterID": "pydbg", 10 | 11 | // The arguments sent to the "launch" request 12 | // This will depend on the debug adapter being used 13 | "launch_args": { 14 | "program": "${CUR_DIR}/test.py", 15 | "args": [], 16 | "stopOnEntry": true, 17 | "console": "externalTerminal", 18 | "debugOptions": [], 19 | "cwd": "${CUR_DIR}" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /demo/python/test.py: -------------------------------------------------------------------------------- 1 | class TestClass(object): 2 | def __init__(self, value): 3 | self._var = value 4 | try: 5 | self.DoSomething() 6 | except ValueError: 7 | pass 8 | 9 | def DoSomething(self): 10 | for i in range(0, 100): 11 | if i < self._var: 12 | print('{0} is less than the value'.format(i)) 13 | else: 14 | print('{0} might be more'.format(i)) 15 | 16 | raise ValueError('Done') 17 | 18 | 19 | def Main(): 20 | t = TestClass(18) 21 | 22 | t._var = 99 23 | t.DoSomething() 24 | 25 | 26 | Main() 27 | -------------------------------------------------------------------------------- /demo/ruby/.kak-dap.json: -------------------------------------------------------------------------------- 1 | { 2 | // The binary run to start the debug adapter 3 | "adapter": "bundle", 4 | 5 | // The arguments to the debug adapter, if any 6 | "adapter_args": ["exec", "readapt", "stdio"], 7 | 8 | // The adapter ID. Needed by some debug adapters. 9 | "adapterID": "rubydbg", 10 | 11 | // The arguments sent to the "launch" request 12 | // This will depend on the debug adapter being used 13 | "launch_args": { 14 | "program": "${CUR_DIR}/test.rb", 15 | "args": [] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demo/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 6 | 7 | gem 'readapt' 8 | -------------------------------------------------------------------------------- /demo/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | backport (1.2.0) 5 | readapt (1.4.3) 6 | backport (~> 1.2) 7 | thor (~> 1.0) 8 | thor (1.2.1) 9 | 10 | PLATFORMS 11 | x86_64-linux 12 | 13 | DEPENDENCIES 14 | readapt 15 | 16 | BUNDLED WITH 17 | 2.3.10 18 | -------------------------------------------------------------------------------- /demo/ruby/test.rb: -------------------------------------------------------------------------------- 1 | # Simple for loop using a range. 2 | for i in (1..4) 3 | print i," " 4 | end 5 | print "\n" 6 | 7 | for i in (1...4) 8 | print i," " 9 | end 10 | print "\n" 11 | 12 | # Running through a list (which is what they do). 13 | items = [ 'Mark', 12, 'goobers', 18.45 ] 14 | for it in items 15 | print it, " " 16 | end 17 | print "\n" 18 | 19 | # Go through the legal subscript values of an array. 20 | for i in (0...items.length) 21 | print items[0..i].join(" "), "\n" 22 | end 23 | -------------------------------------------------------------------------------- /rc/kak-dap.kak: -------------------------------------------------------------------------------- 1 | # This option dictates the command run to run the kak-dap binary. 2 | decl -hidden str dap_cmd "kak-dap -s %val{session}" 3 | # This option indicates whether the kak-dap binary for this session is running. 4 | decl -hidden bool dap_running false 5 | 6 | # This option indicates the client in which the "stacktrace" buffer will be shown 7 | decl str stacktraceclient 8 | 9 | # This option indicates the client in which the "variables" buffer will be shown 10 | decl str variablesclient 11 | 12 | set-face global DapBreakpoint red,default 13 | set-face global DapLocation blue,default 14 | 15 | decl str dap_breakpoint_active_symbol "●" 16 | decl str dap_location_symbol "➡" 17 | 18 | # Contains all line breakpoints in this format 19 | # line|file line|file line|file ... 20 | decl -hidden str-list dap_breakpoints_info 21 | # If execution is currently stopped, shows the current location in this format 22 | # line|file 23 | decl -hidden str dap_location_info 24 | 25 | decl -hidden line-specs dap_breakpoints_flags 26 | decl -hidden line-specs dap_location_flags 27 | decl -hidden int dap_variables_cursor_line 28 | # Initial setting to ensure cursor is set to top 29 | set-option global dap_variables_cursor_line 1 30 | 31 | addhl shared/dap group -passes move 32 | addhl shared/dap/ flag-lines DapLocation dap_location_flags 33 | addhl shared/dap/ flag-lines DapBreakpoint dap_breakpoints_flags 34 | 35 | hook global WinDisplay .* %{ 36 | try %{ 37 | addhl window/dap-ref ref -passes move dap 38 | } 39 | dap-refresh-breakpoints-flags %val{buffile} 40 | dap-refresh-location-flag %val{buffile} 41 | } 42 | 43 | hook global BufOpenFile .* %{ 44 | dap-refresh-breakpoints-flags %val{buffile} 45 | dap-refresh-location-flag %val{buffile} 46 | } 47 | 48 | define-command dap-setup-ui %{ 49 | # Setup the jump client 50 | rename-client main 51 | set global jumpclient main 52 | 53 | # Setup the stacktrace client 54 | new rename-client stacktrace 55 | set global stacktraceclient stacktrace 56 | 57 | # Setup the variables client 58 | new rename-client variables 59 | set global variablesclient variables 60 | } 61 | 62 | define-command dap-takedown-ui %{ 63 | # Kill the stacktrace client 64 | evaluate-commands -try-client %opt{stacktraceclient} %{ 65 | quit! 66 | } 67 | # Kill the variables client 68 | evaluate-commands -try-client %opt{variablesclient} %{ 69 | quit! 70 | } 71 | } 72 | 73 | define-command dap-start %{ 74 | eval %sh{ 75 | if [ "$kak_opt_dap_running" = false ]; then 76 | # Setup the UI 77 | printf "%s\n" "dap-setup-ui" 78 | # Start the kak-dap binary 79 | mkdir /tmp/kak-dap 80 | printf '{ 81 | "breakpoints": "%s" 82 | }' "$kak_opt_dap_breakpoints_info" > /tmp/kak-dap/${kak_session}_breakpoints 83 | export CUR_FILE=$kak_buffile 84 | (eval "${kak_opt_dap_cmd}") > /dev/null 2>&1 < /dev/null & 85 | else 86 | printf "echo %s\n" "kak-dap already running" 87 | fi 88 | } 89 | } 90 | 91 | define-command dap-stop %{ 92 | # Stop the kak-dap binary 93 | nop %sh{ 94 | printf '{ 95 | "cmd": "stop" 96 | }' | eval "${kak_opt_dap_cmd} --request" 97 | } 98 | } 99 | 100 | define-command dap-set-breakpoint -params 2 %{ 101 | set-option -add global dap_breakpoints_info "%arg{1}|%arg{2}" 102 | dap-refresh-breakpoints-flags %arg{2} 103 | } 104 | 105 | define-command dap-clear-breakpoint -params 2 %{ 106 | set-option -remove global dap_breakpoints_info "%arg{1}|%arg{2}" 107 | dap-refresh-breakpoints-flags %arg{2} 108 | } 109 | 110 | define-command dap-toggle-breakpoint %{ eval %sh{ 111 | if [ "$kak_opt_dap_running" = false ]; then 112 | # Go through every existing breakpoint 113 | for current in $kak_opt_dap_breakpoints_info; do 114 | buffer=${current#*|*} 115 | line=${current%%|*} 116 | 117 | # If the current file and cursor line match this currently existing breakpoint 118 | if [ "$buffer" = "$kak_buffile" ] && [ "$line" = "$kak_cursor_line" ]; then 119 | printf "set-option -remove global dap_breakpoints_info '%s|%s'\n" "$line" "$buffer" 120 | printf "dap-refresh-breakpoints-flags %s\n" "$buffer" 121 | exit 122 | fi 123 | done 124 | # If we're here, we don't have this breakpoint yet 125 | printf "set-option -add global dap_breakpoints_info '%s|%s'\n" "$kak_cursor_line" "$kak_buffile" 126 | printf "dap-refresh-breakpoints-flags %s\n" "$kak_buffile" 127 | else 128 | printf "echo %s\n" "Can't toggle breakpoints while running" 129 | fi 130 | }} 131 | 132 | # 133 | # Commands sent directly to debug adapter 134 | # 135 | 136 | define-command dap-continue %{ eval %sh{ 137 | if [ "$kak_opt_dap_running" = false ]; then 138 | printf "%s\n" "dap-start" 139 | else 140 | printf '{ 141 | "cmd": "continue" 142 | }' | eval "${kak_opt_dap_cmd} --request" > /dev/null 2>&1 143 | fi 144 | }} 145 | 146 | define-command dap-next %{ nop %sh{ 147 | printf '{ 148 | "cmd": "next" 149 | }' | eval "${kak_opt_dap_cmd} --request" 150 | }} 151 | 152 | define-command dap-step-in %{ nop %sh{ 153 | printf '{ 154 | "cmd": "stepIn" 155 | }' | eval "${kak_opt_dap_cmd} --request" 156 | }} 157 | 158 | define-command dap-step-out %{ nop %sh{ 159 | printf '{ 160 | "cmd": "stepOut" 161 | }' | eval "${kak_opt_dap_cmd} --request" 162 | }} 163 | 164 | define-command dap-evaluate -params 1 %{ nop %sh{ 165 | printf '{ 166 | "cmd": "evaluate", 167 | "args": "%s" 168 | }' "$1" | eval "${kak_opt_dap_cmd} --request" 169 | }} 170 | 171 | define-command dap-set-location -params 2 %{ 172 | set-option global dap_location_info "%arg{1}|%arg{2}" 173 | try %{ eval -client %opt{jumpclient} dap-refresh-location-flag %arg{2} } 174 | } 175 | 176 | define-command dap-reset-location %{ 177 | set-option global dap_location_info "" 178 | try %{ eval -client %opt{jumpclient} dap-refresh-location-flag %val{buffile} } 179 | } 180 | 181 | define-command dap-jump-to-location %{ 182 | try %{ eval %sh{ 183 | # Get the current location info 184 | eval set -- "$kak_quoted_opt_dap_location_info" 185 | [ $# -eq 0 ] && exit 186 | # Extract the line and buffer 187 | line="${1%%|*}" 188 | buffer="${1#*|*}" 189 | # Edit the file at the given line, failing if it doesn't exist (it should be open already, fingers crossed) 190 | printf "edit -existing '%s' %s; exec gi" "$buffer" "$line" 191 | }} 192 | } 193 | 194 | define-command -hidden -params 1 dap-refresh-breakpoints-flags %{ 195 | try %{ 196 | set-option "buffer=%arg{1}" dap_breakpoints_flags %val{timestamp} 197 | eval %sh{ 198 | # Loop through all the current breakpoints 199 | for current in $kak_opt_dap_breakpoints_info; do 200 | buffer=${current#*|*} 201 | # If the current buffer is correct 202 | if [ "$buffer" = "$1" ]; then 203 | line=${current%%|*} 204 | # Set the breakpoint flag 205 | printf "set-option -add \"buffer=%s\" dap_breakpoints_flags %s|$kak_opt_dap_breakpoint_active_symbol\n" "$buffer" "$line" 206 | fi 207 | done 208 | } 209 | } 210 | } 211 | 212 | define-command -hidden -params 1 dap-refresh-location-flag %{ 213 | try %{ 214 | set-option global dap_location_flags %val{timestamp} 215 | set-option "buffer=%arg{1}" dap_location_flags %val{timestamp} 216 | eval %sh{ 217 | current=$kak_opt_dap_location_info 218 | buffer=${current#*|*} 219 | # If the current buffer is correct 220 | if [ "$buffer" = "$1" ]; then 221 | line=${current%%|*} 222 | # Set the location flag 223 | printf "set-option -add \"buffer=%s\" dap_location_flags %s|$kak_opt_dap_location_symbol\n" "$buffer" "$line" 224 | fi 225 | } 226 | } 227 | } 228 | 229 | # 230 | # Handle the variable/stacktrace buffers 231 | # 232 | 233 | define-command -hidden dap-show-stacktrace -params 1 %{ 234 | # Show the stack trace in the stack trace buffer 235 | evaluate-commands -save-regs '"' -try-client %opt[stacktraceclient] %{ 236 | edit! -scratch *stacktrace* 237 | set-register '"' %arg{1} 238 | execute-keys Pgg 239 | } 240 | } 241 | 242 | define-command -hidden dap-show-variables -params 1 %{ 243 | evaluate-commands -save-regs '"' -try-client %opt[variablesclient] %{ 244 | edit! -scratch *variables* 245 | set-register '"' %arg{1} 246 | execute-keys "P%opt{dap_variables_cursor_line}g" 247 | map buffer normal '' ':dap-expand-variable' 248 | # Reset to ensure default value, will be set by expand-variable 249 | set-option global dap_variables_cursor_line 1 250 | 251 | # strings, keep first 252 | add-highlighter buffer/vals regions 253 | add-highlighter buffer/vals/double_string region '"' (?)\s([^\s]+)\s\(([A-Za-z]+)\)" 2:comment 3:variable 4:type 258 | # values 259 | add-highlighter buffer/type_num regex "(-?\d+)$" 1:value 260 | add-highlighter buffer/type_bool regex "((?i)true|false)$" 0:value 261 | add-highlighter buffer/type_null regex "((?i)null|nil|undefined)$" 0:keyword 262 | add-highlighter buffer/type_array regex "(array\(\d+\))$" 0:default+i 263 | } 264 | } 265 | 266 | define-command -hidden dap-expand-variable %{ 267 | evaluate-commands -try-client %opt{variablesclient} %{ 268 | # Get variable we're expanding 269 | execute-keys -save-regs '' "ghwwwW" 270 | set-register t %val{selection} 271 | set-option global dap_variables_cursor_line %val{cursor_line} 272 | nop %sh{ 273 | value="${kak_reg_t}" 274 | printf '{ 275 | "cmd": "expand", 276 | "args": "%s" 277 | }' $value | eval "${kak_opt_dap_cmd} --request" 278 | } 279 | } 280 | } 281 | 282 | # 283 | # Responses to reverseRequests 284 | # 285 | 286 | define-command -hidden dap-run-in-terminal -params 1.. %{ 287 | terminal %arg{@} 288 | nop %sh{ 289 | printf '{ 290 | "cmd": "pid" 291 | }' | eval "${kak_opt_dap_cmd} --request" 292 | } 293 | } 294 | 295 | # 296 | # Responses to debug adapter responses 297 | # 298 | 299 | define-command -hidden dap-output -params 2 %{ 300 | evaluate-commands -client %opt{jumpclient} %{ 301 | echo -debug DAP ADAPTER %arg{1}: %arg{2} 302 | } 303 | } 304 | 305 | define-command -hidden dap-stack-trace -params 3 %{ 306 | dap-set-location %arg{1} %arg{2} 307 | try %{ eval -client %opt{jumpclient} dap-jump-to-location } 308 | dap-show-stacktrace %arg{3} 309 | } 310 | 311 | define-command -hidden dap-evaluate-response -params 2.. %{ 312 | try %{ eval -client %opt{jumpclient} %{ info -title "Result" " %arg{1}:%arg{2} "}} 313 | } 314 | 315 | declare-user-mode dap 316 | map global dap s -docstring 'start' ': dap-start' 317 | map global dap b -docstring 'toggle breakpoints' ': dap-toggle-breakpoint' 318 | map global dap c -docstring 'continue' ': dap-continue' 319 | map global dap n -docstring 'next' ': dap-next' 320 | map global dap o -docstring 'step out' ': dap-step-out' 321 | map global dap i -docstring 'step in' ': dap-step-in' 322 | map global dap e -docstring 'eval' ':dap-evaluate ' 323 | map global dap q -docstring 'stop' ': dap-stop' 324 | map global dap . -docstring 'lock' ': enter-user-mode -lock dap' 325 | -------------------------------------------------------------------------------- /src/breakpoints.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use crate::debug_adapter_comms; 3 | 4 | use json::{JsonValue, object}; 5 | 6 | pub fn process_breakpoints(ctx: &mut Context, breakpoints: JsonValue) { 7 | // If we have no breakpoints, don't do anything 8 | if breakpoints["breakpoints"].is_empty() { 9 | return; 10 | } 11 | let raw_breakpoints = breakpoints["breakpoints"].to_string(); 12 | // Split passed value along spaces 13 | let split = raw_breakpoints.split_whitespace(); 14 | for val in split { 15 | // Get the filepath and line number 16 | let bar_index = val.chars().position(|c| c == '|').unwrap(); 17 | let line_no = &val[0..bar_index].parse::().unwrap(); 18 | let filepath = &val[bar_index + 1..]; 19 | debug!("Filepath: {}, line_no: {}", filepath, line_no); 20 | // If we already have breakpoints in this filename 21 | if ctx.breakpoint_data.contains_key(filepath) { 22 | let mut new_vec = ctx.breakpoint_data.get(filepath).unwrap().clone(); 23 | new_vec.push(*line_no); 24 | ctx.breakpoint_data.insert(filepath.to_string(), new_vec); 25 | } 26 | // Otherwise, create a new entry 27 | else { 28 | let mut new_vec = vec![]; 29 | new_vec.push(*line_no); 30 | ctx.breakpoint_data.insert(filepath.to_string(), new_vec); 31 | } 32 | } 33 | } 34 | 35 | // Handles the "initialized" event. 36 | pub fn handle_initialized_event(_msg: json::JsonValue, ctx: &mut Context) { 37 | // This is where we set the breakpoints 38 | let mut requests : Vec = vec![]; 39 | // Loop over the various source files we were sent 40 | for (source, lines) in &ctx.breakpoint_data { 41 | let mut breakpoints = json::JsonValue::new_array(); 42 | // Ensure we get all the lines in each file 43 | for line in lines { 44 | breakpoints.push(object! { 45 | "line": *line, 46 | }).expect("Couldn't add breakpoint to list"); 47 | } 48 | // Construct the actual request's arguments 49 | let mut break_args = object! { 50 | "source": { 51 | "path": source.to_string(), 52 | }, 53 | }; 54 | break_args["breakpoints"] = breakpoints; 55 | requests.push(break_args); 56 | } 57 | // Send all the breakpoint requests, one after another 58 | for req in requests { 59 | debug_adapter_comms::do_request("setBreakpoints", &req, ctx); 60 | } 61 | 62 | // Now, send the configurationDone request. 63 | // We only do this if the adapter has advertised the "supportsConfigurationDone" capability. 64 | if ctx.capabilities.is_object() && ctx.capabilities["supportsConfigurationDoneRequest"].is_boolean() 65 | && ctx.capabilities["supportsConfigurationDoneRequest"].as_bool().unwrap() == true { 66 | debug_adapter_comms::do_request("configurationDone", &object! {}, ctx); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs}; 2 | use std::io::Read; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use json; 6 | use json_comments::StripComments; 7 | use whoami; 8 | 9 | // Gets the path the current debug configuration is located at 10 | pub fn config_path() -> Option { 11 | // If the CUR_FILE environment variable exists, use that 12 | // Otherwise, use the working directory 13 | let mut cur_file : String = env::current_dir().unwrap().to_string_lossy().to_string(); 14 | if env::var("CUR_FILE").is_ok() { 15 | cur_file = env::var("CUR_FILE").unwrap(); 16 | } 17 | let mut src = PathBuf::from(cur_file); 18 | while !src.is_dir() { 19 | src.pop(); 20 | } 21 | // Look up through the heirarchy to find the config file 22 | loop { 23 | info!("Checking for file {}", src.join(".kak-dap.json").to_str().unwrap()); 24 | let exists = std::path::Path::new(src.join(".kak-dap.json").to_str().unwrap()).exists(); 25 | if exists { 26 | let root_dir = src.join(".kak-dap.json").to_str().unwrap().to_string(); 27 | info!("Found config at {}", root_dir); 28 | return Some(root_dir); 29 | } 30 | if !src.pop() { 31 | break; 32 | } 33 | } 34 | return None 35 | } 36 | 37 | // Gets the JSON configuration from the configuration file. 38 | pub fn get_config(config_path : &String) -> Option { 39 | let data = fs::read_to_string(config_path).expect("Couldn't read configuration file"); 40 | // Remove comments before processing 41 | let mut stripped = String::new(); 42 | StripComments::new(data.as_bytes()).read_to_string(&mut stripped).unwrap(); 43 | // Replace expandables 44 | let config_dir = Path::new(config_path).parent().unwrap().to_string_lossy().to_string(); 45 | stripped = str::replace(&stripped, "${HOME}", env!("HOME")); 46 | stripped = str::replace(&stripped, "${USER}", &whoami::username()); 47 | stripped = str::replace(&stripped, "${CUR_DIR}", &config_dir); 48 | stripped = str::replace(&stripped, "$$", "$"); 49 | // Attempt to retrieve JSON configuration 50 | let parsed = json::parse(&stripped); 51 | if parsed.is_err() { 52 | return None 53 | } 54 | return Some(parsed.unwrap()); 55 | } 56 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::Sender; 2 | use std::collections::HashMap; 3 | 4 | use crate::types::{Scope, Variable}; 5 | 6 | use json::{object, JsonValue}; 7 | 8 | // Struct with which to carry around our "global" variables 9 | pub struct Context { 10 | // Handle to write to the debug adapter 11 | pub debg_apt_tx: Sender, 12 | // The sequence ID of the next request 13 | pub cur_req_id: u64, 14 | // The Kakoune session that spawned us 15 | pub session: String, 16 | // The sequence ID of the last reverseRequest from the adapter 17 | pub last_adapter_seq: u64, 18 | // The thread that last triggered the Stopped event 19 | pub cur_thread: u64, 20 | // The scopes found at the last Stopped event 21 | pub scopes: Vec, 22 | // The variables currently stored in the variable heirarchy 23 | pub variables: Vec, 24 | // The number of Variables requests we still need to service 25 | pub var_reqs: u64, 26 | // The current stack frame. 27 | pub cur_stack: u64, 28 | // The requests we have sent to the adapter 29 | pub cur_requests: Vec, 30 | // The breakpoints passed to the kak-dap session 31 | pub breakpoint_data: HashMap>, 32 | // The debug configuration 33 | pub debug_cfg: JsonValue, 34 | // The capabilities reported by the debug adapter 35 | pub capabilities: JsonValue 36 | } 37 | 38 | impl Context { 39 | pub fn new(debg_apt_tx: Sender, session: String) -> Self { 40 | Context { 41 | debg_apt_tx, 42 | cur_req_id: 0, 43 | session, 44 | last_adapter_seq: 0, 45 | cur_thread: 1, 46 | scopes: vec![], 47 | variables: vec![], 48 | var_reqs: 0, 49 | cur_stack: 0, 50 | cur_requests: vec![], 51 | breakpoint_data: HashMap::new(), 52 | debug_cfg: object!{}, 53 | capabilities: object!{}, 54 | } 55 | } 56 | 57 | pub fn next_req_id(&mut self) -> u64 { 58 | // Increment the current ID and return what it was before 59 | self.cur_req_id += 1; 60 | self.cur_req_id - 1 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/controller.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use std::thread; 3 | 4 | use json::{object, JsonValue}; 5 | 6 | use crate::breakpoints; 7 | use crate::config; 8 | use crate::context::*; 9 | use crate::debug_adapter_comms; 10 | use crate::general; 11 | use crate::kakoune; 12 | use crate::stack_trace; 13 | use crate::variables; 14 | 15 | pub fn start(session: &String, breakpoints: JsonValue) { 16 | let kakoune_rx = kakoune::start_kak_comms(session); 17 | let config : JsonValue; 18 | // Load the debug configuration 19 | let config_path = config::config_path(); 20 | if config_path.is_none() { 21 | // No configuration found; bail 22 | error!("Couldn't find configuration"); 23 | general::goodbye(session); 24 | } 25 | let pot_config = config::get_config(&config_path.unwrap()); 26 | if pot_config.is_none() { 27 | error!("Invalid configuration"); 28 | // Configuration isn't valid JSON; bail 29 | general::goodbye(session); 30 | } 31 | config = pot_config.unwrap(); 32 | debug!("Debug configuration: {}", config.dump()); 33 | // Begin communication with the debug adapter 34 | let mut adapter_args : Vec = vec![]; 35 | for val in config["adapter_args"].members() { 36 | adapter_args.push(val.to_string()); 37 | } 38 | let (adapter_tx, adapter_rx) = debug_adapter_comms::debug_start( 39 | &config["adapter"].to_string(), 40 | &adapter_args, 41 | ); 42 | 43 | let cxt_src = Arc::new(Mutex::new(Context::new(adapter_tx, session.to_string()))); 44 | 45 | // Debug adapter response handling thread 46 | let ctx = Arc::clone(&cxt_src); 47 | thread::spawn(move || { 48 | for msg in adapter_rx { 49 | let mut ctx = ctx.lock().expect("Failed to lock context"); 50 | if msg["type"].to_string() == "response" { 51 | let msg_cln = msg.clone(); 52 | let ctx_cln = &mut ctx; 53 | handle_adapter_response(msg, ctx_cln); 54 | // Find the request that spawned this response and remove it from the pending requests 55 | ctx_cln 56 | .cur_requests 57 | .retain(|x| &x["seq"] != &msg_cln["request_seq"]); 58 | } else if msg["type"].to_string() == "event" { 59 | handle_adapter_event(msg, &mut ctx); 60 | } else if msg["type"].to_string() == "request" { 61 | // Right now, there is only one "reverse request" - runInTerminal. 62 | // Therefore, we simply handle that request. 63 | general::handle_run_in_terminal_request(msg, &mut ctx); 64 | } 65 | } 66 | }); 67 | 68 | // Set dap_running value in Kakoune; process breakpoints; initialize the debug adapter 69 | let ctx = Arc::clone(&cxt_src); 70 | { 71 | let mut ctx = ctx.lock().expect("Failed to lock context"); 72 | ctx.debug_cfg = config; 73 | kakoune::kak_command("set-option global dap_running true", &ctx.session); 74 | breakpoints::process_breakpoints(&mut ctx, breakpoints); 75 | general::initialize(&mut ctx); 76 | } 77 | 78 | // Handle messages from Kakoune 79 | for msg in kakoune_rx { 80 | let mut ctx = ctx.lock().expect("Failed to lock context"); 81 | parse_cmd(msg, &mut ctx); 82 | } 83 | } 84 | 85 | // Handle events from the debug adapter. 86 | pub fn handle_adapter_event(msg: json::JsonValue, ctx: &mut Context) { 87 | match msg["event"].to_string().as_str() { 88 | "exited" => general::goodbye(&ctx.session), 89 | "initialized" => breakpoints::handle_initialized_event(msg, ctx), 90 | "output" => general::output(msg, ctx), 91 | "stopped" => stack_trace::handle_stopped_event(msg, ctx), 92 | "terminated" => general::goodbye(&ctx.session), 93 | _ => (), 94 | }; 95 | } 96 | 97 | // Handle responses from the debug adapter. 98 | pub fn handle_adapter_response(msg: json::JsonValue, ctx: &mut Context) { 99 | match msg["command"].to_string().as_str() { 100 | "initialize" => general::handle_initialize_response(msg, ctx), 101 | "stackTrace" => stack_trace::handle_stack_trace_response(msg, ctx), 102 | "scopes" => variables::handle_scopes_response(msg, ctx), 103 | "variables" => variables::handle_variables_response(msg, ctx), 104 | "evaluate" => general::handle_evaluate_response(msg, ctx), 105 | _ => (), 106 | }; 107 | } 108 | 109 | // Handle commands from Kakoune. 110 | pub fn parse_cmd(cmd: json::JsonValue, ctx: &mut Context) { 111 | // Depending on the command given, act accordingly 112 | if cmd["cmd"] == "stop" { 113 | // We currently rely on the adapter terminating the debuggee once stdio streams are closed 114 | general::goodbye(&ctx.session); 115 | } else if cmd["cmd"] == "continue" { 116 | // Send a continue command to the debugger 117 | let continue_args = object! { 118 | "threadId": ctx.cur_thread 119 | }; 120 | debug_adapter_comms::do_request("continue", &continue_args, ctx); 121 | } else if cmd["cmd"] == "next" { 122 | // Send a next command to the debugger 123 | let next_args = object! { 124 | "threadId": ctx.cur_thread 125 | }; 126 | debug_adapter_comms::do_request("next", &next_args, ctx); 127 | } else if cmd["cmd"] == "pid" { 128 | // Send response to debug adapter 129 | debug_adapter_comms::do_response("runInTerminal", object! {}, ctx); 130 | } else if cmd["cmd"] == "stepIn" { 131 | // Send a stepIn command to the debugger 132 | let step_in_args = object! { 133 | "threadId": ctx.cur_thread 134 | }; 135 | debug_adapter_comms::do_request("stepIn", &step_in_args, ctx); 136 | } else if cmd["cmd"] == "stepOut" { 137 | // Send a stepIn command to the debugger 138 | let step_out_args = object! { 139 | "threadId": ctx.cur_thread 140 | }; 141 | debug_adapter_comms::do_request("stepOut", &step_out_args, ctx); 142 | } else if cmd["cmd"] == "evaluate" { 143 | // Extract the expression and send an "evaluate" command to the debugger 144 | let expr = cmd["args"].to_string(); 145 | let eval_args = object! { 146 | "expression": expr, 147 | "frameId": ctx.cur_stack 148 | }; 149 | debug_adapter_comms::do_request("evaluate", &eval_args, ctx); 150 | } else if cmd["cmd"] == "expand" { 151 | variables::expand_variable(&cmd["args"].to_string(), ctx); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/debug_adapter_comms.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{bounded, Receiver, Sender}; 2 | use json::object; 3 | use std::collections::HashMap; 4 | use std::io::{self, BufRead, BufReader, BufWriter, Error, ErrorKind, Read, Write}; 5 | use std::process::{Command, Stdio}; 6 | use std::thread; 7 | 8 | use crate::context::*; 9 | 10 | // Start the debug adapter process and connect to its stdio. 11 | pub fn debug_start( 12 | cmd: &str, 13 | args: &[String], 14 | ) -> (Sender, Receiver) { 15 | // Spawn debug adapter process and capture stdio 16 | let mut child = Command::new(cmd) 17 | .args(args) 18 | .stdin(Stdio::piped()) 19 | .stdout(Stdio::piped()) 20 | .stderr(Stdio::piped()) 21 | .spawn() 22 | .expect("Failed to start debug adapter"); 23 | 24 | // Obtain reader and writer objects for the child process 25 | let writer = BufWriter::new(child.stdin.take().expect("Failed to open stdin")); 26 | let reader = BufReader::new(child.stdout.take().expect("Failed to open stdout")); 27 | 28 | // Tracing debug adapter errors 29 | let mut stderr = BufReader::new(child.stderr.take().expect("Failed to open stderr")); 30 | thread::spawn(move || loop { 31 | let mut buf = String::new(); 32 | stderr.read_to_string(&mut buf).unwrap(); 33 | if buf.is_empty() { 34 | continue; 35 | } 36 | error!("Debug adapter error: {}", buf); 37 | }); 38 | 39 | let (reader_tx, reader_rx) = bounded(1024); 40 | thread::spawn(move || { 41 | reader_loop(reader, &reader_tx).expect("Failed to read message from debug adapter"); 42 | }); 43 | 44 | let (writer_tx, writer_rx): (Sender, Receiver) = 45 | bounded(1024); 46 | thread::spawn(move || { 47 | writer_loop(writer, &writer_rx).expect("Failed to write message to debug adapter"); 48 | }); 49 | 50 | (writer_tx, reader_rx) 51 | } 52 | 53 | // Thread to read the stdout of the debug adapter process. 54 | fn reader_loop(mut reader: impl BufRead, tx: &Sender) -> io::Result<()> { 55 | // Store headers of message being received 56 | // Used to determine if Content-Length header has been received 57 | let mut headers = HashMap::new(); 58 | loop { 59 | headers.clear(); 60 | loop { 61 | let mut header = String::new(); 62 | if reader.read_line(&mut header)? == 0 { 63 | debug!("Debug adapter closed pipe, stopping reading"); 64 | return Ok(()); 65 | } 66 | let header = header.trim(); 67 | if header.is_empty() { 68 | break; 69 | } 70 | let parts: Vec<&str> = header.split(": ").collect(); 71 | if parts.len() != 2 { 72 | return Err(Error::new(ErrorKind::Other, "Failed to parse header")); 73 | } 74 | headers.insert(parts[0].to_string(), parts[1].to_string()); 75 | } 76 | // Get the length of the message we are receiving 77 | let content_len = headers 78 | .get("Content-Length") 79 | .expect("Failed to find Content-Length header") 80 | .parse() 81 | .expect("Failed to parse Content-Length header"); 82 | // Now read that many characters to obtain the message 83 | let mut content = vec![0; content_len]; 84 | reader.read_exact(&mut content)?; 85 | let msg = String::from_utf8(content).expect("Failed to read content as UTF-8 string"); 86 | let output = json::parse(&msg.to_string()).unwrap(); 87 | debug!("From debug adapter: {}", output); 88 | if output.is_object() { 89 | tx.send(output) 90 | .expect("Failed to send message from debug adapter"); 91 | } 92 | } 93 | } 94 | 95 | // Thread to write to the stdin of the debug adapter process. 96 | fn writer_loop(mut writer: impl Write, rx: &Receiver) -> io::Result<()> { 97 | for request in rx { 98 | let request = request.dump(); 99 | write!( 100 | writer, 101 | "Content-Length: {}\r\n\r\n{}", 102 | request.len(), 103 | request 104 | )?; 105 | writer.flush()?; 106 | } 107 | Ok(()) 108 | } 109 | 110 | // Sends a request to the debug adapter. 111 | pub fn do_request(cmd: &str, args: &json::JsonValue, ctx: &mut Context) { 112 | let args_cln = args.clone(); 113 | let msg = object! { 114 | "type": "request", 115 | "seq": ctx.next_req_id(), 116 | "command": cmd, 117 | "arguments": args_cln 118 | }; 119 | 120 | let msg_cln = msg.clone(); 121 | 122 | debug!("To debug adapter: {}", msg_cln); 123 | 124 | // Send it to the debug adapter 125 | ctx.debg_apt_tx 126 | .send(msg) 127 | .expect("Failed to send message to debug adapter"); 128 | 129 | // Add it to the pending requests list 130 | ctx.cur_requests.push(msg_cln); 131 | } 132 | 133 | // Sends a response to the debug adapter. 134 | // Currently, only one response is sent by the client: the response to the runInTerminal command. 135 | pub fn do_response(cmd: &str, body: json::JsonValue, ctx: &mut Context) { 136 | let msg = object! { 137 | "type": "response", 138 | "seq": ctx.next_req_id(), 139 | "request_seq": ctx.last_adapter_seq, 140 | "command": cmd, 141 | "success": true, 142 | "body": body 143 | }; 144 | debug!("To debug adapter: {}", msg); 145 | // Send it to the debug adapter 146 | ctx.debg_apt_tx 147 | .send(msg) 148 | .expect("Failed to send response to debug adapter"); 149 | } 150 | -------------------------------------------------------------------------------- /src/general.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use crate::debug_adapter_comms; 3 | use crate::kakoune; 4 | 5 | use json::object; 6 | use std::process; 7 | 8 | // Initializes the debug adapter. 9 | pub fn initialize(ctx: &mut Context) { 10 | // Construct the initialize request 11 | let initialize_args = object! { 12 | "adapterID": ctx.debug_cfg["adapterID"].to_string(), 13 | "linesStartAt1": true, 14 | "columnsStartAt1": true, 15 | "pathFormat": "path", 16 | "supportsRunInTerminalRequest": true, 17 | }; 18 | debug_adapter_comms::do_request("initialize", &initialize_args, ctx); 19 | } 20 | 21 | 22 | // Handles the "initialize" response. 23 | pub fn handle_initialize_response(_msg: json::JsonValue, ctx: &mut Context) { 24 | let capabilities = &_msg["body"]; 25 | if capabilities.is_object() { 26 | ctx.capabilities = capabilities.clone(); 27 | } 28 | // We need to send the launch request before the breakpoints. 29 | // For background: https://github.com/microsoft/vscode/issues/4902 30 | let launch_args : &json::JsonValue = &ctx.debug_cfg["launch_args"]; 31 | let launch_args_cln = launch_args.clone(); 32 | debug_adapter_comms::do_request("launch", &launch_args_cln, ctx); 33 | } 34 | 35 | // Handles the "runInTerminal" request. 36 | pub fn handle_run_in_terminal_request(msg: json::JsonValue, ctx: &mut Context) { 37 | // Get the sequence number of this request to send back later 38 | let seq = &msg["seq"]; 39 | ctx.last_adapter_seq = seq.to_string().parse::().unwrap(); 40 | // Extract the program we need to run 41 | let args = &msg["arguments"]["args"]; 42 | let mut cmd = "dap-run-in-terminal ".to_string(); 43 | let args_members = args.members(); 44 | for val in args_members { 45 | cmd.push_str(&val.to_string()); 46 | cmd.push_str(" "); 47 | } 48 | kakoune::kak_command(&cmd, &ctx.session); 49 | } 50 | 51 | //Handles the "evaluate" response. 52 | pub fn handle_evaluate_response(msg: json::JsonValue, ctx: &mut Context) { 53 | //Get the result and type 54 | let result = &msg["body"]["result"]; 55 | let typ = &msg["body"]["type"]; 56 | 57 | //Send it to Kakoune for processing 58 | let mut cmd = "dap-evaluate-response ' ".to_string(); 59 | cmd.push_str(&kakoune::editor_escape(&result.to_string())); 60 | cmd.push_str(" ' ' "); 61 | cmd.push_str(&kakoune::editor_escape(&typ.to_string())); 62 | cmd.push_str(" '"); 63 | kakoune::kak_command(&cmd, &ctx.session); 64 | } 65 | 66 | //Handles the "output" event. 67 | pub fn output(msg: json::JsonValue, ctx: &mut Context) { 68 | // Get the category. If it's not telemetry (who wants telemetry?), pass it to the dap-output command. 69 | let category = &msg["body"]["category"]; 70 | if category.is_string() && category.as_str().unwrap() == "telemetry" { 71 | return; 72 | } 73 | let category_str = &msg["body"]["category"].as_str().unwrap_or("output"); 74 | let output = msg["body"]["output"].as_str().unwrap(); 75 | let mut cmd = "dap-output ".to_string(); 76 | cmd += &category_str; 77 | cmd += " '"; 78 | cmd += &kakoune::editor_escape(output); 79 | cmd += "'"; 80 | kakoune::kak_command(&cmd, &ctx.session); 81 | } 82 | 83 | //Tries to end kak-dap gracefully. 84 | pub fn goodbye(session: &str) { 85 | kakoune::kak_command("try %{ eval -client %opt{jumpclient} %{ dap-reset-location }}", session); 86 | kakoune::kak_command("try %{ eval -client %opt{jumpclient} %{ dap-takedown-ui }}", session); 87 | kakoune::kak_command("set-option global dap_running false", session); 88 | kakoune::clean_socket(&session.to_string()); 89 | process::exit(0); 90 | } 91 | -------------------------------------------------------------------------------- /src/kakoune.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{bounded, Receiver}; 2 | use std::io::{Read, Write}; 3 | use std::os::unix::fs::DirBuilderExt; 4 | use std::os::unix::net::UnixListener; 5 | use std::process::{Command, Stdio}; 6 | use std::{env, fs, path, thread}; 7 | 8 | // This function sends a Kakoune command to the given Kakoune session. 9 | pub fn kak_command(command: &str, session: &str) { 10 | let mut child = Command::new("kak") 11 | .args(&["-p", session]) 12 | .stdin(Stdio::piped()) 13 | .stdout(Stdio::null()) 14 | .stdout(Stdio::null()) 15 | .spawn() 16 | .unwrap(); 17 | let child_stdin = child.stdin.as_mut().unwrap(); 18 | debug!("To editor: {}", command); 19 | child_stdin 20 | .write_all(command.as_bytes()) 21 | .expect("Failed to write to stdin of child process."); 22 | } 23 | 24 | // Escape Kakoune string wrapped into single quote 25 | pub fn editor_escape(s: &str) -> String { 26 | s.replace("'", "''") 27 | } 28 | 29 | // This function creates the kak-dap temp dir. 30 | pub fn temp_dir() -> path::PathBuf { 31 | let mut path = env::temp_dir(); 32 | path.push("kak-dap"); 33 | let old_mask = unsafe { libc::umask(0) }; 34 | // Ignoring possible error during $TMPDIR/kak-dap creation to have a chance to restore umask. 35 | let _ = fs::DirBuilder::new() 36 | .recursive(true) 37 | .mode(0o1777) 38 | .create(&path); 39 | unsafe { 40 | libc::umask(old_mask); 41 | } 42 | path 43 | } 44 | 45 | // This function removes the socket file. 46 | pub fn clean_socket(session: &String) { 47 | let path = temp_dir(); 48 | let sock_path = path.join(session); 49 | if fs::remove_file(sock_path).is_err() { 50 | error!("Failed to remove socket file"); 51 | }; 52 | } 53 | 54 | // This function spawns the thread that listens for commands on a socket 55 | // and issues commands to the Kakoune session that spawned us. 56 | pub fn start_kak_comms(session: &String) -> Receiver { 57 | let (reader_tx, reader_rx) = bounded(1024); 58 | // Create socket 59 | let mut path = temp_dir(); 60 | path.push(session); 61 | if path.exists() { 62 | let sock_path = path.clone(); 63 | // Clean up dead kak-dap session 64 | if fs::remove_file(sock_path).is_err() { 65 | error!("Failed to clean up dead kak-dap session"); 66 | } 67 | } 68 | let listener = match UnixListener::bind(&path) { 69 | Ok(listener) => listener, 70 | Err(e) => { 71 | error!("Failed to bind: {}", e); 72 | return reader_rx; 73 | } 74 | }; 75 | // Begin socket processing 76 | thread::spawn(move || { 77 | for stream in listener.incoming() { 78 | match stream { 79 | Ok(mut stream) => { 80 | let mut request = String::new(); 81 | match stream.read_to_string(&mut request) { 82 | Ok(_) => { 83 | if request.is_empty() { 84 | continue; 85 | } 86 | debug!("From editor: {}", request); 87 | let parsed_request = json::parse(&request).unwrap(); 88 | reader_tx 89 | .send(parsed_request) 90 | .expect("Failed to send request from Kakoune"); 91 | } 92 | Err(e) => { 93 | error!("Failed to read from stream: {}", e); 94 | } 95 | } 96 | } 97 | Err(e) => { 98 | error!("Failed to accept connection: {}", e); 99 | } 100 | } 101 | } 102 | }); 103 | 104 | reader_rx 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate slog; 3 | #[macro_use] 4 | extern crate slog_scope; 5 | 6 | mod breakpoints; 7 | mod config; 8 | mod context; 9 | mod controller; 10 | mod debug_adapter_comms; 11 | mod general; 12 | mod kakoune; 13 | mod stack_trace; 14 | mod types; 15 | mod variables; 16 | 17 | use clap::{crate_version, App, Arg}; 18 | use sloggers::file::FileLoggerBuilder; 19 | use sloggers::terminal::{Destination, TerminalLoggerBuilder}; 20 | use sloggers::types::Severity; 21 | use sloggers::Build; 22 | use std::fs; 23 | use std::panic; 24 | use std::env; 25 | 26 | use std::io::{stdin, Read, Write}; 27 | use std::os::unix::net::UnixStream; 28 | 29 | use itertools::Itertools; 30 | use json::object; 31 | 32 | fn setup_logger(matches: &clap::ArgMatches<'_>) -> slog_scope::GlobalLoggerGuard { 33 | let mut verbosity = matches.occurrences_of("v") as u8; 34 | 35 | if verbosity == 0 { 36 | verbosity = 2 37 | } 38 | 39 | let level = match verbosity { 40 | 0 => Severity::Error, 41 | 1 => Severity::Warning, 42 | 2 => Severity::Info, 43 | 3 => Severity::Debug, 44 | _ => Severity::Trace, 45 | }; 46 | 47 | let logger = if let Some(log_path) = matches.value_of("log") { 48 | // First remove the existing logfile. We don't want multiple logging sessions in a single file. 49 | let _result = fs::remove_file(log_path); 50 | 51 | let mut builder = FileLoggerBuilder::new(log_path); 52 | builder.level(level); 53 | builder.build().unwrap() 54 | } else { 55 | let mut builder = TerminalLoggerBuilder::new(); 56 | builder.level(level); 57 | builder.destination(Destination::Stderr); 58 | builder.build().unwrap() 59 | }; 60 | 61 | panic::set_hook(Box::new(|panic_info| { 62 | error!("panic: {}", panic_info); 63 | })); 64 | 65 | slog_scope::set_global_logger(logger) 66 | } 67 | 68 | fn main() { 69 | // Get command line arguments 70 | let matches = App::new("Kak-DAP") 71 | .version(crate_version!()) 72 | .arg( 73 | Arg::with_name("session") 74 | .short("s") 75 | .long("session") 76 | .value_name("SESSION") 77 | .help("Kakoune session to communicate with") 78 | .required(true), 79 | ) 80 | .arg( 81 | Arg::with_name("log") 82 | .long("log") 83 | .value_name("PATH") 84 | .help("File to write the log into instead of stderr") 85 | .takes_value(true), 86 | ) 87 | .arg( 88 | Arg::with_name("request") 89 | .long("request") 90 | .help("Forward stdin to kak-dap server") 91 | ) 92 | .arg( 93 | Arg::with_name("kakoune") 94 | .long("kakoune") 95 | .help("Generate commands for Kakoune to plug in kak-dap") 96 | ) 97 | .arg( 98 | Arg::with_name("v") 99 | .short("v") 100 | .multiple(true) 101 | .help("Sets the level of verbosity (use up to 4 times)"), 102 | ) 103 | .get_matches(); 104 | 105 | // Enable logging of panics 106 | panic::set_hook(Box::new(|panic_info| { 107 | error!("panic: {}", panic_info); 108 | })); 109 | 110 | // Extract the current session 111 | let session: String = matches.value_of("session").map(str::to_string).unwrap(); 112 | 113 | // Initialize the logger 114 | let _guard = setup_logger(&matches); 115 | 116 | if matches.is_present("kakoune") { 117 | // Grab ../rc/kak-dap.kak and print it out 118 | let script: &str = include_str!("../rc/kak-dap.kak"); 119 | let args = env::args() 120 | .skip(1) 121 | .filter(|arg| arg != "--kakoune") 122 | .join(" "); 123 | let cmd = env::current_exe().unwrap(); 124 | let cmd = cmd.to_str().unwrap(); 125 | let lsp_cmd = format!( 126 | "set global lsp_cmd '{} {}'", 127 | kakoune::editor_escape(cmd), 128 | kakoune::editor_escape(&args) 129 | ); 130 | println!("{}\n{}", script, lsp_cmd); 131 | } else if matches.is_present("request") { 132 | // Forward the stdin to the kak-dap server 133 | let mut input = Vec::new(); 134 | stdin() 135 | .read_to_end(&mut input) 136 | .expect("Failed to read stdin"); 137 | let mut path = kakoune::temp_dir(); 138 | path.push(session); 139 | if let Ok(mut stream) = UnixStream::connect(&path) { 140 | stream 141 | .write_all(&input) 142 | .expect("Failed to send stdin to server"); 143 | } 144 | } else { 145 | // If we are receiving breakpoints from the breakpoints file, get them 146 | let mut breakpoints = object! {}; 147 | let mut path = kakoune::temp_dir(); 148 | path.push(format!("{}_breakpoints", session)); 149 | debug!( 150 | "Searching for breakpoints on path {}", 151 | path.to_string_lossy() 152 | ); 153 | if path.exists() { 154 | let break_path = path.clone(); 155 | let contents = fs::read_to_string(path).expect("Couldn't read from file"); 156 | breakpoints = json::parse(&contents).expect("Couldn't convert contents to JSON"); 157 | debug!("Breakpoint data: {}", breakpoints.to_string()); 158 | if fs::remove_file(break_path).is_err() { 159 | error!("Couldn't clean up breakpoints file"); 160 | } 161 | } 162 | 163 | // Set the dap_running option and kickstart the whole kit and kaboodle 164 | debug!("Starting kak-dap session"); 165 | controller::start(&session, breakpoints); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/stack_trace.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use crate::debug_adapter_comms; 3 | use crate::kakoune; 4 | 5 | use json::object; 6 | 7 | // Handles the "stopped" event. 8 | pub fn handle_stopped_event(msg: json::JsonValue, ctx: &mut Context) { 9 | if msg["body"]["threadId"].is_number() { 10 | ctx.cur_thread = msg["body"]["threadId"].to_string().parse::().unwrap() 11 | } 12 | // Send a stack trace request 13 | let stack_trace_args = object! { 14 | "threadId": ctx.cur_thread 15 | }; 16 | debug_adapter_comms::do_request("stackTrace", &stack_trace_args, ctx); 17 | } 18 | 19 | // Handles the "stackTrace" response. 20 | pub fn handle_stack_trace_response(msg: json::JsonValue, ctx: &mut Context) { 21 | let frames = &msg["body"]["stackFrames"]; 22 | // Get first stack frame to obtain current execution location 23 | let frame = &frames[0]; 24 | let line = &frame["line"]; 25 | let file = &frame["source"]["path"]; 26 | // Construct Kakoune command to jump to location 27 | let mut cmd = "dap-stack-trace ".to_string(); 28 | cmd.push_str(&line.to_string()); 29 | cmd.push_str(" "); 30 | cmd.push_str(&file.to_string()); 31 | cmd.push_str(" "); 32 | cmd.push_str("'"); 33 | // Add contents to push to stacktrace buffer 34 | let frame_members = frames.members(); 35 | for val in frame_members { 36 | let id = &val["id"]; 37 | // Source is not guaranteed to exist 38 | let source : &json::JsonValue; 39 | let dummy_src = object!{"name": ""}; 40 | if val["source"].is_null() { 41 | source = &dummy_src; 42 | } 43 | else { 44 | source = &val["source"]; 45 | } 46 | let frame_name = &val["name"].to_string(); 47 | // Technically, sources from a debug adapter are required to have a name. 48 | // Unfortunately, some adapters (debugpy cough cough) seem to think it's OK 49 | // to ignore the protocol, and omit this value. 50 | // In this case, we get it from the path. 51 | let source_name : String; 52 | let mut path : String = "".to_string(); 53 | let path_var = &val["source"]["path"]; 54 | let source_name_var = &val["source"]["name"]; 55 | if path_var.is_string() { 56 | path = path_var.to_string(); 57 | } 58 | if source["name"].is_null() { 59 | let slash_index = path.rfind("/").unwrap(); 60 | source_name = path.get((slash_index + 1)..).unwrap().to_string(); 61 | } 62 | else { 63 | source_name = source_name_var.to_string(); 64 | } 65 | let line = &val["line"]; 66 | cmd.push_str(&format!("{}: {}@{}:{}", id, frame_name, source_name, line)); 67 | cmd.push_str("\n"); 68 | } 69 | cmd.push_str("'"); 70 | kakoune::kak_command(&cmd, &ctx.session); 71 | // Send a Scopes message to kickstart retrieving the variables 72 | let id = frames[0]["id"].to_string().parse::().unwrap(); 73 | ctx.cur_stack = id; 74 | let scopes_args = object! { 75 | "frameId": id, 76 | }; 77 | debug_adapter_comms::do_request("scopes", &scopes_args, ctx); 78 | } 79 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | pub struct Scope { 2 | // If nonzero, this can be expanded 3 | pub variable_reference: u64, 4 | // This is the contents of the Scope 5 | pub contents: json::JsonValue, 6 | } 7 | 8 | #[derive(Debug)] 9 | pub struct Variable { 10 | // If nonzero, this can be expanded 11 | pub variable_reference: u64, 12 | // This contains the variable reference of this variable's "parent" 13 | pub par_variable_reference: u64, 14 | // This is the contents of the Variable 15 | pub contents: json::JsonValue, 16 | } 17 | -------------------------------------------------------------------------------- /src/variables.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use crate::debug_adapter_comms; 3 | use crate::kakoune; 4 | use crate::types::{Scope, Variable}; 5 | 6 | use json::object; 7 | 8 | // Handles the "scopes" response. 9 | pub fn handle_scopes_response(msg: json::JsonValue, ctx: &mut Context) { 10 | // Update the "scopes" array in the context 11 | ctx.scopes.clear(); 12 | ctx.variables.clear(); 13 | let scopes_members = msg["body"]["scopes"].members(); 14 | for val in scopes_members { 15 | let value = val.clone(); 16 | // Enter this scope in the scopes array 17 | let scope = Scope { 18 | variable_reference: val["variablesReference"] 19 | .to_string() 20 | .parse::() 21 | .unwrap(), 22 | contents: value, 23 | }; 24 | ctx.scopes.push(scope); 25 | // Request variables for this scope 26 | let var_ref = val["variablesReference"] 27 | .to_string() 28 | .parse::() 29 | .unwrap(); 30 | let var_args = object! { 31 | "variablesReference": var_ref 32 | }; 33 | ctx.var_reqs += 1; 34 | debug_adapter_comms::do_request("variables", &var_args, ctx); 35 | } 36 | } 37 | 38 | // Handles the "variables" response. 39 | pub fn handle_variables_response(msg: json::JsonValue, ctx: &mut Context) { 40 | ctx.var_reqs -= 1; 41 | // Find the variables request that spawned this response 42 | let cur_requests = &ctx.cur_requests.clone(); 43 | let val_req = cur_requests 44 | .into_iter() 45 | .find(|x| &x["seq"] == &msg["request_seq"]) 46 | .unwrap(); 47 | 48 | // Loop over every variable in this response 49 | let variables = &msg["body"]["variables"]; 50 | let variables_members = variables.members(); 51 | for val in variables_members { 52 | let val_cln = val.clone(); 53 | // Construct an Expandable instance containing this variable's properties 54 | let variable = Variable { 55 | variable_reference: val["variablesReference"] 56 | .to_string() 57 | .parse::() 58 | .unwrap(), 59 | par_variable_reference: val_req["arguments"]["variablesReference"] 60 | .to_string() 61 | .parse::() 62 | .unwrap(), 63 | contents: val_cln, 64 | }; 65 | ctx.variables.push(variable); 66 | } 67 | // If we've serviced all pending variable requests, render the scopes and variables in the variables buffer 68 | if ctx.var_reqs == 0 { 69 | serialize_variables(ctx); 70 | } 71 | } 72 | 73 | // Constructs the command that renders all the scopes and variables in the variables buffer. 74 | pub fn serialize_variables(ctx: &mut Context) { 75 | let mut cmd = "dap-show-variables '".to_string(); 76 | let mut cmd_val = "".to_string(); 77 | for scope in &ctx.scopes { 78 | let scope_name = &scope.contents["name"]; 79 | cmd_val.push_str(&"Scope: ".to_string()); 80 | cmd_val.push_str(&scope_name.to_string()); 81 | cmd_val.push_str("\n"); 82 | // First confirm that this scope has child variables 83 | let mut has_child = false; 84 | for var in &ctx.variables { 85 | if var.par_variable_reference == scope.variable_reference { 86 | has_child = true; 87 | break; 88 | } 89 | } 90 | if has_child { 91 | let val = serialize_variable(ctx, scope.variable_reference, 2); 92 | cmd_val.push_str(&val); 93 | } 94 | } 95 | cmd.push_str(&kakoune::editor_escape(&cmd_val)); 96 | cmd.push_str("'"); 97 | kakoune::kak_command(&cmd, &ctx.session); 98 | } 99 | 100 | // Constructs the command that renders all the child variables of the given variable reference in the variables buffer. 101 | pub fn serialize_variable(ctx: &Context, par_ref: u64, indent: u64) -> String { 102 | let mut val = "".to_string(); 103 | let mut icon = " "; 104 | for var in &ctx.variables { 105 | if var.par_variable_reference == par_ref { 106 | for _i in 0..indent { 107 | val.push_str(" "); 108 | } 109 | // If this variable is expandable 110 | if var.variable_reference > 0 { 111 | icon = "+"; 112 | // Determine if this variable has any child variables currently 113 | for varr in &ctx.variables { 114 | if varr.par_variable_reference == var.variable_reference { 115 | icon = "-"; 116 | break; 117 | } 118 | } 119 | } 120 | val.push_str(&format!("{} ", icon)); 121 | val.push_str("<"); 122 | val.push_str(&var.variable_reference.to_string()); 123 | val.push_str("> "); 124 | val.push_str(&var.contents["name"].to_string()); 125 | val.push_str(" ("); 126 | val.push_str(&var.contents["type"].to_string()); 127 | val.push_str("): "); 128 | val.push_str(&var.contents["value"].to_string()); 129 | val.push_str("\n"); 130 | if var.variable_reference > 0 { 131 | val.push_str(&serialize_variable(ctx, var.variable_reference, indent + 2)); 132 | } 133 | } 134 | } 135 | val 136 | } 137 | 138 | // Handles the "expand" command from the editor. 139 | pub fn expand_variable(cmd: &String, ctx: &mut Context) { 140 | let mut var = cmd.to_string(); 141 | var = var.trim().to_string(); 142 | // If the string starts with a '<', this is an expandable variable 143 | let first_char = var.chars().next().unwrap(); 144 | if first_char == '<' { 145 | // Extract the variable reference 146 | var = var[1..].to_string(); 147 | let var_ref = var.parse::().unwrap(); 148 | // If the variables list contains any child variables of this variable reference, then it's expanded 149 | let mut is_expanded = false; 150 | for varr in &ctx.variables { 151 | if varr.par_variable_reference == var_ref { 152 | is_expanded = true; 153 | break; 154 | } 155 | } 156 | // If this variable isn't expanded, then expand it 157 | if !is_expanded { 158 | let var_args = object! { 159 | "variablesReference": var_ref 160 | }; 161 | ctx.var_reqs += 1; 162 | debug_adapter_comms::do_request("variables", &var_args, ctx); 163 | } 164 | // Otherwise, collapse it 165 | else { 166 | ctx.variables 167 | .retain(|x| x.par_variable_reference != var_ref); 168 | serialize_variables(ctx); 169 | } 170 | } 171 | } 172 | --------------------------------------------------------------------------------