From d202ae34439edb6ad573d3d7537d59088344384b Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Mon, 24 Jun 2019 10:36:27 +0200 Subject: [PATCH 01/36] Update Cargo.lock --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0f3fa8..3e5a529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1233,7 +1233,7 @@ dependencies = [ "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "xrl 0.0.7 (git+https://github.com/little-dude/xrl?branch=unbox)", + "xrl 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1244,7 +1244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xrl" version = "0.0.7" -source = "git+https://github.com/little-dude/xrl?branch=unbox#0d5b90296d66469b9a28995f0e9e29a3e3d313f5" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1410,5 +1410,5 @@ dependencies = [ "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" "checksum xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "541b12c998c5b56aa2b4e6f18f03664eef9a4fd0a246a55594efae6cc2d964b5" -"checksum xrl 0.0.7 (git+https://github.com/little-dude/xrl?branch=unbox)" = "" +"checksum xrl 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "742f0321a57a06e1200a6f8686020f6f14353dc9ebc7499ecf906c4b64f9573a" "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" From 83692fd50adfea3fb1f498d31650030f478c07e8 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Mon, 24 Jun 2019 14:32:45 +0200 Subject: [PATCH 02/36] First step towards configurable keybindings: TUI can use bindings provided by keymap --- Cargo.lock | 177 +++++- Cargo.toml | 3 + configs/Default (Linux).sublime-keymap | 742 +++++++++++++++++++++++++ src/core/cmd.rs | 15 +- src/core/config.rs | 135 +++++ src/core/mod.rs | 3 + src/core/tui.rs | 75 +-- src/main.rs | 11 +- 8 files changed, 1103 insertions(+), 58 deletions(-) create mode 100644 configs/Default (Linux).sublime-keymap create mode 100644 src/core/config.rs diff --git a/Cargo.lock b/Cargo.lock index 3e5a529..c5465f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,11 +80,35 @@ name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "build_const" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.2" @@ -196,6 +220,14 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "digest" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dtoa" version = "0.4.4" @@ -221,6 +253,11 @@ dependencies = [ "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "flate2" version = "1.0.7" @@ -261,6 +298,14 @@ name = "futures" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "generic-array" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "humantime" version = "1.2.0" @@ -288,6 +333,16 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "json5" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -340,7 +395,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -362,9 +417,9 @@ dependencies = [ "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -372,6 +427,11 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "maplit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memoffset" version = "0.2.1" @@ -508,6 +568,11 @@ name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "opaque-debug" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ordered-float" version = "1.0.2" @@ -545,6 +610,45 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pest" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_generator" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_meta" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "plist" version = "0.4.2" @@ -554,7 +658,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -747,8 +851,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde-value" @@ -756,12 +863,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", @@ -776,7 +883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -786,10 +893,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha-1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "signal-hook" version = "0.1.9" @@ -869,8 +987,8 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "plist 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1136,6 +1254,16 @@ dependencies = [ "unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ucd-trie" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-width" version = "0.1.5" @@ -1228,8 +1356,11 @@ dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "json5 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1249,8 +1380,8 @@ dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1278,7 +1409,10 @@ dependencies = [ "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" @@ -1292,19 +1426,23 @@ dependencies = [ "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum json5 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "038d7116122ecb8ad983bf4643b324bdaf5b87d05e8ba2df872e705a287c67c6" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -1315,6 +1453,7 @@ dependencies = [ "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" "checksum log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "100052474df98158c0738a7d3f4249c99978490178b5f9f68cd835ac57adbd1b" +"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" "checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e" @@ -1330,10 +1469,15 @@ dependencies = [ "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum pest 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "933085deae3f32071f135d799d75667b63c8dc1f4537159756e3d4ceab41868c" +"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +"checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" +"checksum pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f249ea6de7c7b7aba92b4ff4376a994c6dbd98fd2166c89d5c4947397ecb574d" "checksum plist 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a9f075f6394100e7c105ed1af73fb1859d6fd14e49d4290d578120beb167f" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" @@ -1359,11 +1503,12 @@ dependencies = [ "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" +"checksum serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)" = "960e29cf7004b3b6e65fc5002981400eb3ccc017a08a2406940823e58e7179a9" "checksum serde-value 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7a663f873dedc4eac1a559d4c6bc0d0b2c34dc5ac4702e105014b8281489e44f" -"checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" +"checksum serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)" = "c4cce6663696bd38272e90bf34a0267e1226156c33f52d3f3915a2dd5d802085" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "72ab58f1fda436857e6337dcb6a5aaa34f16c5ddc87b3a8b6ef7a212f90b9c5a" "checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" @@ -1396,6 +1541,8 @@ dependencies = [ "checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "71a9c5b1fe77426cf144cc30e49e955270f5086e31a6441dfa8b32efc09b9d77" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" diff --git a/Cargo.toml b/Cargo.toml index b350d8f..e08e3d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ tokio = "0.1.21" xdg = "2.2.0" indexmap = "1.0.2" xrl = "0.0.7" +serde = { version = "1.0.93", features = ["derive"] } +serde_json = "1.0.39" +json5 = "0.2.4" \ No newline at end of file diff --git a/configs/Default (Linux).sublime-keymap b/configs/Default (Linux).sublime-keymap new file mode 100644 index 0000000..2820361 --- /dev/null +++ b/configs/Default (Linux).sublime-keymap @@ -0,0 +1,742 @@ +[ + { "keys": ["ctrl+q"], "command": "exit" }, + + { "keys": ["ctrl+shift+n"], "command": "new_window" }, + { "keys": ["ctrl+shift+w"], "command": "close_window" }, + { "keys": ["ctrl+o"], "command": "prompt_open_file" }, + { "keys": ["ctrl+shift+t"], "command": "reopen_last_file" }, + { "keys": ["alt+o"], "command": "switch_file", "args": {"extensions": ["cpp", "cxx", "cc", "c", "hpp", "hxx", "hh", "h", "ipp", "inl", "m", "mm"]} }, + { "keys": ["ctrl+n"], "command": "new_file" }, + { "keys": ["ctrl+s"], "command": "save" }, + { "keys": ["ctrl+shift+s"], "command": "prompt_save_as" }, + { "keys": ["ctrl+f4"], "command": "close_file" }, + { "keys": ["ctrl+w"], "command": "close" }, + + { "keys": ["ctrl+k", "ctrl+b"], "command": "toggle_side_bar" }, + { "keys": ["f11"], "command": "toggle_full_screen" }, + { "keys": ["shift+f11"], "command": "toggle_distraction_free" }, + + { "keys": ["backspace"], "command": "left_delete" }, + { "keys": ["shift+backspace"], "command": "left_delete" }, + { "keys": ["ctrl+shift+backspace"], "command": "left_delete" }, + { "keys": ["delete"], "command": "right_delete" }, + { "keys": ["enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["shift+enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["keypad_enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["shift+keypad_enter"], "command": "insert", "args": {"characters": "\n"} }, + + { "keys": ["ctrl+z"], "command": "undo" }, + { "keys": ["ctrl+shift+z"], "command": "redo" }, + { "keys": ["ctrl+y"], "command": "redo_or_repeat" }, + { "keys": ["ctrl+u"], "command": "soft_undo" }, + { "keys": ["ctrl+shift+u"], "command": "soft_redo" }, + + { "keys": ["shift+delete"], "command": "cut" }, + { "keys": ["ctrl+insert"], "command": "copy" }, + { "keys": ["shift+insert"], "command": "paste" }, + + // These two key bindings should replace the above three if you'd prefer + // the traditional X11 behavior of shift+insert pasting from the primary + // selection. The above CUA keys are the default, to match most GTK + // applications. + //{ "keys": ["shift+insert"], "command": "paste", "args": {"clipboard": "selection"} }, + //{ "keys": ["shift+delete"], "command": "right_delete" }, + + { "keys": ["ctrl+x"], "command": "cut" }, + { "keys": ["ctrl+c"], "command": "copy" }, + { "keys": ["ctrl+v"], "command": "paste" }, + { "keys": ["ctrl+shift+v"], "command": "paste_and_indent" }, + { "keys": ["ctrl+k", "ctrl+v"], "command": "paste_from_history" }, + + { "keys": ["left"], "command": "move", "args": {"by": "characters", "forward": false} }, + { "keys": ["right"], "command": "move", "args": {"by": "characters", "forward": true} }, + { "keys": ["up"], "command": "move", "args": {"by": "lines", "forward": false} }, + { "keys": ["down"], "command": "move", "args": {"by": "lines", "forward": true} }, + { "keys": ["shift+left"], "command": "move", "args": {"by": "characters", "forward": false, "extend": true} }, + { "keys": ["shift+right"], "command": "move", "args": {"by": "characters", "forward": true, "extend": true} }, + { "keys": ["shift+up"], "command": "move", "args": {"by": "lines", "forward": false, "extend": true} }, + { "keys": ["shift+down"], "command": "move", "args": {"by": "lines", "forward": true, "extend": true} }, + + { "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} }, + { "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} }, + { "keys": ["ctrl+shift+left"], "command": "move", "args": {"by": "words", "forward": false, "extend": true} }, + { "keys": ["ctrl+shift+right"], "command": "move", "args": {"by": "word_ends", "forward": true, "extend": true} }, + + { "keys": ["alt+left"], "command": "move", "args": {"by": "subwords", "forward": false} }, + { "keys": ["alt+right"], "command": "move", "args": {"by": "subword_ends", "forward": true} }, + { "keys": ["alt+shift+left"], "command": "move", "args": {"by": "subwords", "forward": false, "extend": true} }, + { "keys": ["alt+shift+right"], "command": "move", "args": {"by": "subword_ends", "forward": true, "extend": true} }, + + { "keys": ["alt+shift+up"], "command": "select_lines", "args": {"forward": false} }, + { "keys": ["alt+shift+down"], "command": "select_lines", "args": {"forward": true} }, + + { "keys": ["pageup"], "command": "move", "args": {"by": "pages", "forward": false} }, + { "keys": ["pagedown"], "command": "move", "args": {"by": "pages", "forward": true} }, + { "keys": ["shift+pageup"], "command": "move", "args": {"by": "pages", "forward": false, "extend": true} }, + { "keys": ["shift+pagedown"], "command": "move", "args": {"by": "pages", "forward": true, "extend": true} }, + + { "keys": ["home"], "command": "move_to", "args": {"to": "bol", "extend": false} }, + { "keys": ["end"], "command": "move_to", "args": {"to": "eol", "extend": false} }, + { "keys": ["shift+home"], "command": "move_to", "args": {"to": "bol", "extend": true} }, + { "keys": ["shift+end"], "command": "move_to", "args": {"to": "eol", "extend": true} }, + { "keys": ["ctrl+home"], "command": "move_to", "args": {"to": "bof", "extend": false} }, + { "keys": ["ctrl+end"], "command": "move_to", "args": {"to": "eof", "extend": false} }, + { "keys": ["ctrl+shift+home"], "command": "move_to", "args": {"to": "bof", "extend": true} }, + { "keys": ["ctrl+shift+end"], "command": "move_to", "args": {"to": "eof", "extend": true} }, + + { "keys": ["ctrl+up"], "command": "scroll_lines", "args": {"amount": 1.0 } }, + { "keys": ["ctrl+down"], "command": "scroll_lines", "args": {"amount": -1.0 } }, + + { "keys": ["pagedown"], "command": "next_view" }, + { "keys": ["pageup"], "command": "prev_view" }, + // { "keys": ["ctrl+pagedown"], "command": "next_view" }, + // { "keys": ["ctrl+pageup"], "command": "prev_view" }, + + { "keys": ["ctrl+tab"], "command": "next_view_in_stack" }, + { "keys": ["ctrl+shift+tab"], "command": "prev_view_in_stack" }, + + { "keys": ["ctrl+a"], "command": "select_all" }, + { "keys": ["ctrl+shift+l"], "command": "split_selection_into_lines" }, + { "keys": ["escape"], "command": "single_selection", "context": + [ + { "key": "num_selections", "operator": "not_equal", "operand": 1 } + ] + }, + { "keys": ["escape"], "command": "clear_fields", "context": + [ + { "key": "has_next_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "clear_fields", "context": + [ + { "key": "has_prev_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_panel", "args": {"cancel": true}, + "context": + [ + { "key": "panel_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_overlay", "context": + [ + { "key": "overlay_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_popup", "context": + [ + { "key": "popup_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_auto_complete", "context": + [ + { "key": "auto_complete_visible", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["tab"], "command": "insert_best_completion", "args": {"default": "\t", "exact": true} }, + { "keys": ["tab"], "command": "insert_best_completion", "args": {"default": "\t", "exact": false}, + "context": + [ + { "key": "setting.tab_completion", "operator": "equal", "operand": true }, + { "key": "preceding_text", "operator": "not_regex_match", "operand": ".*\\b[0-9]+$", "match_all": true }, + ] + }, + { "keys": ["tab"], "command": "replace_completion_with_next_completion", "context": + [ + { "key": "last_command", "operator": "equal", "operand": "insert_best_completion" }, + { "key": "setting.tab_completion", "operator": "equal", "operand": true } + ] + }, + { "keys": ["tab"], "command": "reindent", "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_match", "operand": "^$", "match_all": true }, + { "key": "following_text", "operator": "regex_match", "operand": "^$", "match_all": true } + ] + }, + { "keys": ["tab"], "command": "indent", "context": + [ + { "key": "text", "operator": "regex_contains", "operand": "\n" } + ] + }, + { "keys": ["tab"], "command": "next_field", "context": + [ + { "key": "has_next_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["tab"], "command": "commit_completion", "context": + [ + { "key": "auto_complete_visible" }, + { "key": "setting.auto_complete_commit_on_tab" } + ] + }, + + { "keys": ["shift+tab"], "command": "insert", "args": {"characters": "\t"} }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "setting.shift_tab_unindent", "operator": "equal", "operand": true } + ] + }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "preceding_text", "operator": "regex_match", "operand": "^[\t ]*" } + ] + }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "text", "operator": "regex_contains", "operand": "\n" } + ] + }, + { "keys": ["shift+tab"], "command": "prev_field", "context": + [ + { "key": "has_prev_field", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["ctrl+]"], "command": "indent" }, + { "keys": ["ctrl+["], "command": "unindent" }, + + { "keys": ["insert"], "command": "toggle_overwrite" }, + + { "keys": ["ctrl+l"], "command": "expand_selection", "args": {"to": "line"} }, + { "keys": ["ctrl+d"], "command": "find_under_expand" }, + { "keys": ["ctrl+k", "ctrl+d"], "command": "find_under_expand_skip" }, + { "keys": ["ctrl+shift+space"], "command": "expand_selection", "args": {"to": "scope"} }, + { "keys": ["ctrl+shift+m"], "command": "expand_selection", "args": {"to": "brackets"} }, + { "keys": ["ctrl+m"], "command": "move_to", "args": {"to": "brackets"} }, + { "keys": ["ctrl+shift+j"], "command": "expand_selection", "args": {"to": "indentation"} }, + { "keys": ["ctrl+shift+a"], "command": "expand_selection", "args": {"to": "tag"} }, + + { "keys": ["alt+."], "command": "close_tag" }, + + { "keys": ["ctrl+alt+q"], "command": "toggle_record_macro" }, + { "keys": ["ctrl+alt+shift+q"], "command": "run_macro" }, + + { "keys": ["ctrl+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line.sublime-macro"} }, + { "keys": ["ctrl+shift+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line Before.sublime-macro"} }, + { "keys": ["enter"], "command": "commit_completion", "context": + [ + { "key": "auto_complete_visible" }, + { "key": "setting.auto_complete_commit_on_tab", "operand": false } + ] + }, + + { "keys": ["ctrl+p"], "command": "show_overlay", "args": {"overlay": "goto", "show_files": true} }, + { "keys": ["ctrl+shift+p"], "command": "show_overlay", "args": {"overlay": "command_palette"} }, + { "keys": ["ctrl+alt+p"], "command": "prompt_select_workspace" }, + { "keys": ["ctrl+r"], "command": "show_overlay", "args": {"overlay": "goto", "text": "@"} }, + { "keys": ["ctrl+g"], "command": "show_overlay", "args": {"overlay": "goto", "text": ":"} }, + { "keys": ["ctrl+;"], "command": "show_overlay", "args": {"overlay": "goto", "text": "#"} }, + { "keys": ["f12"], "command": "goto_definition" }, + { "keys": ["shift+f12"], "command": "goto_reference" }, + { "keys": ["ctrl+shift+r"], "command": "goto_symbol_in_project" }, + { "keys": ["alt+-"], "command": "jump_back" }, + { "keys": ["alt+shift+-"], "command": "jump_forward" }, + { "keys": ["alt+keypad_minus"], "command": "jump_back" }, + { "keys": ["alt+shift+keypad_minus"], "command": "jump_forward" }, + + { "keys": ["ctrl+i"], "command": "show_panel", "args": {"panel": "incremental_find", "reverse": false} }, + { "keys": ["ctrl+shift+i"], "command": "show_panel", "args": {"panel": "incremental_find", "reverse": true} }, + { "keys": ["ctrl+f"], "command": "show_panel", "args": {"panel": "find", "reverse": false} }, + { "keys": ["ctrl+h"], "command": "show_panel", "args": {"panel": "replace", "reverse": false} }, + { "keys": ["ctrl+shift+h"], "command": "replace_next" }, + { "keys": ["f3"], "command": "find_next" }, + { "keys": ["shift+f3"], "command": "find_prev" }, + { "keys": ["ctrl+f3"], "command": "find_under" }, + { "keys": ["ctrl+shift+f3"], "command": "find_under_prev" }, + { "keys": ["alt+f3"], "command": "find_all_under" }, + { "keys": ["ctrl+e"], "command": "slurp_find_string" }, + { "keys": ["ctrl+shift+e"], "command": "slurp_replace_string" }, + { "keys": ["ctrl+shift+f"], "command": "show_panel", "args": {"panel": "find_in_files"} }, + { "keys": ["f4"], "command": "next_result" }, + { "keys": ["shift+f4"], "command": "prev_result" }, + + { "keys": ["ctrl+."], "command": "next_modification" }, + { "keys": ["ctrl+,"], "command": "prev_modification" }, + { "keys": ["ctrl+k", "ctrl+z"], "command": "revert_modification" }, + { "keys": ["ctrl+k", "ctrl+/"], "command": "toggle_inline_diff" }, + { "keys": ["ctrl+k", "ctrl+;"], "command": "toggle_inline_diff", "args": { "prefer_hide": true } }, + + { "keys": ["f6"], "command": "toggle_setting", "args": {"setting": "spell_check"} }, + { "keys": ["ctrl+f6"], "command": "next_misspelling" }, + { "keys": ["ctrl+shift+f6"], "command": "prev_misspelling" }, + + { "keys": ["ctrl+shift+up"], "command": "swap_line_up" }, + { "keys": ["ctrl+shift+down"], "command": "swap_line_down" }, + + { "keys": ["ctrl+backspace"], "command": "delete_word", "args": { "forward": false } }, + { "keys": ["ctrl+shift+backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete to Hard BOL.sublime-macro"} }, + + { "keys": ["ctrl+delete"], "command": "delete_word", "args": { "forward": true } }, + { "keys": ["ctrl+shift+delete"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete to Hard EOL.sublime-macro"} }, + + { "keys": ["ctrl+/"], "command": "toggle_comment", "args": { "block": false } }, + { "keys": ["ctrl+shift+/"], "command": "toggle_comment", "args": { "block": true } }, + + { "keys": ["ctrl+j"], "command": "join_lines" }, + { "keys": ["ctrl+shift+d"], "command": "duplicate_line" }, + + { "keys": ["ctrl+`"], "command": "show_panel", "args": {"panel": "console", "toggle": true} }, + + { "keys": ["alt+/"], "command": "auto_complete" }, + { "keys": ["alt+/"], "command": "replace_completion_with_auto_complete", "context": + [ + { "key": "last_command", "operator": "equal", "operand": "insert_best_completion" }, + { "key": "auto_complete_visible", "operator": "equal", "operand": false }, + { "key": "setting.tab_completion", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["ctrl+alt+shift+p"], "command": "show_scope_name" }, + + { "keys": ["f7"], "command": "build" }, + { "keys": ["ctrl+b"], "command": "build" }, + { "keys": ["ctrl+shift+b"], "command": "build", "args": {"select": true} }, + { "keys": ["ctrl+break"], "command": "cancel_build" }, + + { "keys": ["ctrl+t"], "command": "transpose" }, + + { "keys": ["f9"], "command": "sort_lines", "args": {"case_sensitive": false} }, + { "keys": ["ctrl+f9"], "command": "sort_lines", "args": {"case_sensitive": true} }, + + // Auto-pair quotes + { "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"$0\""}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true }, + { "key": "preceding_text", "operator": "not_regex_contains", "operand": "[\"a-zA-Z0-9_]$", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true } + ] + }, + { "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"${0:$SELECTION}\""}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["\""], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }, + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }, + ] + }, + + // Auto-pair single quotes + { "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'$0'"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true }, + { "key": "preceding_text", "operator": "not_regex_contains", "operand": "['a-zA-Z0-9_]$", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true } + ] + }, + { "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'${0:$SELECTION}'"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["'"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true }, + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "'$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true }, + ] + }, + + // Auto-pair brackets + { "keys": ["("], "command": "insert_snippet", "args": {"contents": "($0)"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|;|\\}|$)", "match_all": true } + ] + }, + { "keys": ["("], "command": "insert_snippet", "args": {"contents": "(${0:$SELECTION})"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": [")"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\)", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\($", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\)", "match_all": true } + ] + }, + + // Auto-pair square brackets + { "keys": ["["], "command": "insert_snippet", "args": {"contents": "[$0]"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|;|\\}|$)", "match_all": true } + ] + }, + { "keys": ["["], "command": "insert_snippet", "args": {"contents": "[${0:$SELECTION}]"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["]"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\]", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\[$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\]", "match_all": true } + ] + }, + + // Auto-pair curly brackets + { "keys": ["{"], "command": "insert_snippet", "args": {"contents": "{$0}"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|$)", "match_all": true } + ] + }, + { "keys": ["{"], "command": "wrap_block", "args": {"begin": "{", "end": "}"}, "context": + [ + { "key": "indented_block", "match_all": true }, + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_match", "operand": "^$", "match_all": true }, + ] + }, + { "keys": ["{"], "command": "insert_snippet", "args": {"contents": "{${0:$SELECTION}}"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["}"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + + { "keys": ["enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line in Braces.sublime-macro"}, "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + { "keys": ["shift+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line in Braces.sublime-macro"}, "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + + { "keys": ["enter"], "command": "auto_indent_tag", "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "punctuation.definition.tag.begin", "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": ">$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^ Result { match &s[..] { "s" | "save" => Ok(Command::Save(None)), - "q" | "quit" => Ok(Command::Quit), - "b" | "back" => Ok(Command::Back), - "d" | "delete" => Ok(Command::Delete), - "bn" | "next-buffer" => Ok(Command::NextBuffer), - "bp" | "prev-buffer" => Ok(Command::PrevBuffer), + "q" | "quit" | "exit" => Ok(Command::Quit), + "b" | "back" | "left_delete" => Ok(Command::Back), + "d" | "delete" | "right_delete" => Ok(Command::Delete), + "bn" | "next-buffer" | "next_view" => Ok(Command::NextBuffer), + "bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer), "pd" | "page-down" => Ok(Command::PageDown), "pu" | "page-up" => Ok(Command::PageUp), "ml" | "move-left" => Ok(Command::MoveLeft), @@ -79,6 +81,7 @@ impl FromStr for Command { "mu" | "move-up" => Ok(Command::MoveUp), "md" | "move-down" => Ok(Command::MoveDown), "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), + "op" | "open-prompt" | "show_overlay" => Ok(Command::OpenPrompt), command => { let mut parts: Vec<&str> = command.split(' ').collect(); diff --git a/src/core/config.rs b/src/core/config.rs new file mode 100644 index 0000000..f20f17e --- /dev/null +++ b/src/core/config.rs @@ -0,0 +1,135 @@ +use crate::core::Command; +use termion::event::{Event, Key}; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use std::path::{Path, PathBuf}; +use std::fs; +use std::collections::HashMap; + +use std::str::FromStr; + +#[derive(Debug, Serialize, Deserialize)] +struct Keybinding { + keys: Vec, + command: String, + // For now, unstructured value + args: Option, + context: Option, +} + +pub struct KeybindingConfig { + pub keymap: HashMap, + pub config_path: PathBuf +} + +impl KeybindingConfig { + pub fn parse(config_path: &Path) -> Result> { + let entries = fs::read_to_string(config_path)?; + // Read the JSON contents of the file as an instance of `User`. + let bindings: Vec = json5::from_str(&entries)?; + error!("Bindings parsed!"); + + let mut keymap = HashMap::new(); + let mut found_cmds = Vec::new(); + for binding in bindings { + let cmd = match Command::from_str(&binding.command) { + Ok(cmd) => cmd, + // unimplemented command for now + Err(_) => continue, + }; + error!("{:?} = {:?}", cmd, binding.keys); + if found_cmds.contains(&cmd) { + continue; + } + let binding = KeybindingConfig::parse_keys(&binding.keys).ok_or("Could not parse keybindings - config")?; + keymap.insert(binding, cmd.clone()); + found_cmds.push(cmd); + } + + Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) + } + + fn parse_keys(keys: &Vec) -> Option { + if keys.len() != 1 { + return None; + } + + let key = &keys[0]; + match key.as_ref() { + "enter" => Some(Event::Key(Key::Char('\n'))), + "tab" => Some(Event::Key(Key::Char('\t'))), + "backspace" => Some(Event::Key(Key::Backspace)), + "left" => Some(Event::Key(Key::Left)), + "right" => Some(Event::Key(Key::Right)), + "up" => Some(Event::Key(Key::Up)), + "down" => Some(Event::Key(Key::Down)), + "home" => Some(Event::Key(Key::Home)), + "end" => Some(Event::Key(Key::End)), + "pageup" => Some(Event::Key(Key::PageUp)), + "pagedown" => Some(Event::Key(Key::PageDown)), + "delete" => Some(Event::Key(Key::Delete)), + "insert" => Some(Event::Key(Key::Insert)), + "escape" => Some(Event::Key(Key::Esc)), + + x if x.starts_with("f") => { + match x[1..].parse::() { + Ok(val) => Some(Event::Key(Key::F(val))), + Err(_) => { + warn!("Cannot parse {}", x); + None + } + } + } + + x if x.starts_with("ctrl+") || x.starts_with("alt+") => { + let is_alt = x.starts_with("alt+"); + let start_length = if is_alt {4} else {5}; + + let character; + // start_length + "shift+x".len() || start_length + "x".len() + if x.len() != start_length + 7 && x.len() != start_length + 1 { + warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7); + return None + } else { + if x.len() == start_length + 7 { + // With "+shift+", so we use an upper case letter + character = x.chars().last().unwrap().to_ascii_uppercase(); + } else { + character = x.chars().last().unwrap().to_ascii_lowercase(); + } + } + + if is_alt { + Some(Event::Key(Key::Alt(character))) + } else { + Some(Event::Key(Key::Ctrl(character))) + } + } + + x => { + warn!("Completely unknown argument {}", x); + None + }, + } + + + + + + + + + + + + + + + + + + + } +} \ No newline at end of file diff --git a/src/core/mod.rs b/src/core/mod.rs index aa1a23d..e5e1839 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,3 +6,6 @@ pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder}; mod cmd; pub use self::cmd::{Command, ParseCommandError}; + +mod config; +pub use self::config::{KeybindingConfig}; \ No newline at end of file diff --git a/src/core/tui.rs b/src/core/tui.rs index 05ed7a5..b3fb8e6 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -4,12 +4,12 @@ use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures::sync::oneshot::{self, Receiver, Sender}; use futures::{Async, Future, Poll, Sink, Stream}; -use termion::event::{Event, Key}; +use termion::event::{Event}; use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; use failure::Error; -use core::{Command, Terminal, TerminalEvent}; +use core::{Command, Terminal, TerminalEvent, KeybindingConfig}; use widgets::{CommandPrompt, Editor}; pub struct Tui { @@ -32,11 +32,13 @@ pub struct Tui { /// Stream of messages from Xi core. core_events: UnboundedReceiver, + + keybindings: KeybindingConfig, } impl Tui { /// Create a new Tui instance. - pub fn new(client: Client, events: UnboundedReceiver) -> Result { + pub fn new(client: Client, events: UnboundedReceiver, keybindings: KeybindingConfig) -> Result { Ok(Tui { terminal: Terminal::new()?, exit: false, @@ -44,6 +46,7 @@ impl Tui { editor: Editor::new(client), prompt: None, core_events: events, + keybindings: keybindings, }) } @@ -54,9 +57,8 @@ impl Tui { pub fn run_command(&mut self, cmd: Command) { match cmd { - Command::Cancel => { - self.prompt = None; - } + Command::OpenPrompt => self.open_prompt(), + Command::Cancel => self.prompt = None, Command::Quit => self.exit = true, Command::Save(view) => self.editor.save(view), Command::Back => self.editor.back(), @@ -75,40 +77,43 @@ impl Tui { } } + fn open_prompt(&mut self) { + if self.prompt.is_none() { + self.prompt = Some(CommandPrompt::default()); + } + } + /// Global keybindings can be parsed here fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); - match event { - Event::Key(Key::Ctrl('c')) => self.exit = true, - Event::Key(Key::Alt('x')) => { - if let Some(ref mut prompt) = self.prompt { - match prompt.handle_input(&event) { - Ok(None) => {} - Ok(Some(_)) => unreachable!(), - Err(_) => unreachable!(), - } - } else { - self.prompt = Some(CommandPrompt::default()); - } + + if let Some(cmd) = self.keybindings.keymap.get(&event) { + match cmd { + Command::OpenPrompt => { + if self.prompt.is_none() { + self.prompt = Some(CommandPrompt::default()); + } + return; }, + Command::Quit => { self.exit = true; return; }, + _ => {/* Somebody else has to deal with these commands */}, } - event => { - // No command prompt is active, process the event normally. - if self.prompt.is_none() { - self.editor.handle_input(event); - return; - } + } - // A command prompt is active. - let mut prompt = self.prompt.take().unwrap(); - match prompt.handle_input(&event) { - Ok(None) => { - self.prompt = Some(prompt); - } - Ok(Some(cmd)) => self.run_command(cmd), - Err(err) => { - error!("Failed to parse command: {:?}", err); - } - } + // No command prompt is active, process the event normally. + if self.prompt.is_none() { + self.editor.handle_input(event); + return; + } + + // A command prompt is active. + let mut prompt = self.prompt.take().unwrap(); + match prompt.handle_input(&event) { + Ok(None) => { + self.prompt = Some(prompt); + } + Ok(Some(cmd)) => self.run_command(cmd), + Err(err) => { + error!("Failed to parse command: {:?}", err); } } } diff --git a/src/main.rs b/src/main.rs index e9760c8..e0bbeba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,10 @@ extern crate tokio; extern crate xdg; extern crate xrl; +extern crate json5; +extern crate serde; +extern crate serde_json; + mod core; mod widgets; use xdg::BaseDirectories; @@ -29,7 +33,7 @@ use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Config, Logger, Root}; use xrl::spawn; -use core::{Command, Tui, TuiServiceBuilder}; +use core::{Command, Tui, TuiServiceBuilder, KeybindingConfig}; fn configure_logs(logfile: &str) { let tui = FileAppender::builder().build(logfile).unwrap(); @@ -93,6 +97,9 @@ fn run() -> Result<(), Error> { configure_logs(logfile); } + let configfile = std::path::Path::new("./configs/Default (Linux).sublime-keymap").to_owned(); + let keybindings = KeybindingConfig::parse(&configfile).map_err(Error::from_boxed_compat)?; + tokio::run(future::lazy(move || { info!("starting xi-core"); let (tui_service_builder, core_events_rx) = TuiServiceBuilder::new(); @@ -122,7 +129,7 @@ fn run() -> Result<(), Error> { .map_err(|e| error!("failed to send \"client_started\" {:?}", e)) .and_then(move |_| { info!("initializing the TUI"); - let mut tui = Tui::new(client_clone, core_events_rx) + let mut tui = Tui::new(client_clone, core_events_rx, keybindings) .expect("failed to initialize the TUI"); tui.run_command(Command::Open( matches.value_of("file").map(ToString::to_string), From 4f9d809e0bfbf5a0b539f0ddb52ca0156845229c Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Mon, 24 Jun 2019 15:23:57 +0200 Subject: [PATCH 03/36] Copy keybindings to editor and all views. Ugly, but I cannot find a nicer way right now. --- src/core/config.rs | 7 +++++-- src/core/mod.rs | 2 +- src/core/tui.rs | 2 +- src/widgets/editor.rs | 10 ++++++---- src/widgets/view/view.rs | 5 ++++- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index f20f17e..b3ec268 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -10,6 +10,8 @@ use std::collections::HashMap; use std::str::FromStr; +pub type Keymap = HashMap; + #[derive(Debug, Serialize, Deserialize)] struct Keybinding { keys: Vec, @@ -19,8 +21,9 @@ struct Keybinding { context: Option, } +#[derive(Clone)] pub struct KeybindingConfig { - pub keymap: HashMap, + pub keymap: Keymap, pub config_path: PathBuf } @@ -31,7 +34,7 @@ impl KeybindingConfig { let bindings: Vec = json5::from_str(&entries)?; error!("Bindings parsed!"); - let mut keymap = HashMap::new(); + let mut keymap = Keymap::new(); let mut found_cmds = Vec::new(); for binding in bindings { let cmd = match Command::from_str(&binding.command) { diff --git a/src/core/mod.rs b/src/core/mod.rs index e5e1839..0fa9caf 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -8,4 +8,4 @@ mod cmd; pub use self::cmd::{Command, ParseCommandError}; mod config; -pub use self::config::{KeybindingConfig}; \ No newline at end of file +pub use self::config::{KeybindingConfig, Keymap}; \ No newline at end of file diff --git a/src/core/tui.rs b/src/core/tui.rs index b3fb8e6..cac278c 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -43,7 +43,7 @@ impl Tui { terminal: Terminal::new()?, exit: false, term_size: (0, 0), - editor: Editor::new(client), + editor: Editor::new(client, keybindings.clone()), prompt: None, core_events: events, keybindings: keybindings, diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 78a620a..82090d5 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -9,9 +9,8 @@ use indexmap::IndexMap; use termion::event::Event as TermionEvent; use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; -use core::CoreEvent; +use core::{CoreEvent, KeybindingConfig}; use widgets::{View, ViewClient}; - /// The main interface to xi-core pub struct Editor { /// Channel from which the responses to "new_view" requests are @@ -44,11 +43,13 @@ pub struct Editor { pub size: (u16, u16), pub styles: HashMap, + + pub keymap: KeybindingConfig } /// Methods for general use. impl Editor { - pub fn new(client: Client) -> Editor { + pub fn new(client: Client, keymap: KeybindingConfig) -> Editor { let mut styles = HashMap::new(); styles.insert(0, Default::default()); let (new_view_tx, new_view_rx) = mpsc::unbounded::<(ViewId, Option)>(); @@ -62,6 +63,7 @@ impl Editor { client, size: (0, 0), styles, + keymap, } } } @@ -92,7 +94,7 @@ impl Future for Editor { Ok(Async::Ready(Some((view_id, file_path)))) => { info!("creating new view {:?}", view_id); let client = ViewClient::new(self.client.clone(), view_id); - let mut view = View::new(client, file_path); + let mut view = View::new(client, file_path, self.keymap.clone()); view.resize(self.size.1); self.views.insert(view_id, view); info!("switching to view {:?}", view_id); diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 7a53c53..a410467 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -7,6 +7,7 @@ use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; use termion::event::{Event, Key, MouseButton, MouseEvent}; use xrl::{ConfigChanges, Line, LineCache, Style, Update}; +use core::KeybindingConfig; use super::cfg::ViewConfig; use super::client::Client; @@ -26,10 +27,11 @@ pub struct View { file: Option, client: Client, cfg: ViewConfig, + keymap: KeybindingConfig } impl View { - pub fn new(client: Client, file: Option) -> View { + pub fn new(client: Client, file: Option, keymap: KeybindingConfig) -> View { View { cache: LineCache::default(), cursor: Default::default(), @@ -37,6 +39,7 @@ impl View { cfg: ViewConfig::default(), client, file, + keymap, } } From 2d2763e287af05130ba804b2b533bd8b90307794 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Mon, 24 Jun 2019 16:55:02 +0200 Subject: [PATCH 04/36] Reshaped Command to have relative and absolute move as variant instead of 'move left', 'move right' --- .gitignore | 1 + src/core/cmd.rs | 144 ++++++++++++++++++++++---- src/core/config.rs | 218 +++++++++++++++++++-------------------- src/core/mod.rs | 2 +- src/core/tui.rs | 40 +++++-- src/widgets/editor.rs | 12 +++ src/widgets/view/view.rs | 8 ++ 7 files changed, 289 insertions(+), 136 deletions(-) diff --git a/.gitignore b/.gitignore index 0152bd1..502707a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.err *.orig *.log +*.log.rpc *.rej *.swo *.swp diff --git a/src/core/cmd.rs b/src/core/cmd.rs index ee34567..f5e708f 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -4,6 +4,60 @@ use xrl::ViewId; use std::str::FromStr; +use serde::{Deserialize, Serialize}; + +#[allow(non_camel_case_types)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub enum RelativeMoveDistance { + /// Move only one character + characters, + /// Move a line + lines, + /// Move to new word + words, + /// Move to end of word + word_ends, + /// Move to new subword + subwords, + /// Move to end of subword + subword_ends, + /// Move a page + pages, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct RelativeMove { + pub by: RelativeMoveDistance, + pub forward: bool, + #[serde(default)] + pub extend: bool +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub enum AbsoluteMovePoint { + /// Beginning of file + bof, + /// End of file + eof, + /// Beginning of line + bol, + /// End of line + eol, + /// Enclosing brackets + brackets, + /// Line number + line(u64) +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct AbsoluteMove { + pub to: AbsoluteMovePoint, + #[serde(default)] + pub extend: bool +} #[derive(Debug, PartialEq, Clone)] pub enum Command { @@ -23,19 +77,11 @@ pub enum Command { NextBuffer, /// Cycle to the previous buffer. PrevBuffer, - /// Move cursor left. - MoveLeft, - /// Move cursor right. - MoveRight, - /// Move cursor up. - MoveUp, - /// Move cursor down. - MoveDown, - /// Page down - PageDown, - /// Page up - PageUp, - /// Change the syntax theme. + // Relative move like line up/down, page up/down, left, right, word left, .. + RelativeMove(RelativeMove), + // Relative move like line ending/beginning, file ending/beginning, line-number, ... + AbsoluteMove(AbsoluteMove), + SetTheme(String), /// Toggle displaying line numbers. ToggleLineNumbers, @@ -74,12 +120,72 @@ impl FromStr for Command { "d" | "delete" | "right_delete" => Ok(Command::Delete), "bn" | "next-buffer" | "next_view" => Ok(Command::NextBuffer), "bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer), - "pd" | "page-down" => Ok(Command::PageDown), - "pu" | "page-up" => Ok(Command::PageUp), - "ml" | "move-left" => Ok(Command::MoveLeft), - "mr" | "move-right" => Ok(Command::MoveRight), - "mu" | "move-up" => Ok(Command::MoveUp), - "md" | "move-down" => Ok(Command::MoveDown), + "md" | "move-down" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::lines, + forward: true, + extend: false + } + )), + "mu" | "move-up" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::lines, + forward: false, + extend: false + } + )), + "mr" | "move-right" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::characters, + forward: true, + extend: false + } + )), + "ml" | "move-left" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::characters, + forward: false, + extend: false + } + )), + "pd" | "page-down" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::pages, + forward: true, + extend: false + } + )), + "pu" | "page-up" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::pages, + forward: false, + extend: false + } + )), + "bof" | "beginning-of-file" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::bof, + extend: false + } + )), + "eof" | "end-of-file" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::eof, + extend: false + } + )), + "bol" | "beginning-of-line" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::bol, + extend: false + } + )), + "eol" | "end-of-line" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::eol, + extend: false + } + )), "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), "op" | "open-prompt" | "show_overlay" => Ok(Command::OpenPrompt), command => { diff --git a/src/core/config.rs b/src/core/config.rs index b3ec268..75f99d5 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,4 +1,4 @@ -use crate::core::Command; +use crate::core::{Command, RelativeMove, AbsoluteMove}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; @@ -23,116 +23,114 @@ struct Keybinding { #[derive(Clone)] pub struct KeybindingConfig { - pub keymap: Keymap, - pub config_path: PathBuf + pub keymap: Keymap, + pub config_path: PathBuf } impl KeybindingConfig { - pub fn parse(config_path: &Path) -> Result> { + pub fn parse(config_path: &Path) -> Result> { let entries = fs::read_to_string(config_path)?; - // Read the JSON contents of the file as an instance of `User`. - let bindings: Vec = json5::from_str(&entries)?; - error!("Bindings parsed!"); - - let mut keymap = Keymap::new(); - let mut found_cmds = Vec::new(); - for binding in bindings { - let cmd = match Command::from_str(&binding.command) { - Ok(cmd) => cmd, - // unimplemented command for now - Err(_) => continue, - }; - error!("{:?} = {:?}", cmd, binding.keys); - if found_cmds.contains(&cmd) { - continue; - } - let binding = KeybindingConfig::parse_keys(&binding.keys).ok_or("Could not parse keybindings - config")?; - keymap.insert(binding, cmd.clone()); - found_cmds.push(cmd); - } - - Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) - } - - fn parse_keys(keys: &Vec) -> Option { - if keys.len() != 1 { - return None; - } - - let key = &keys[0]; - match key.as_ref() { - "enter" => Some(Event::Key(Key::Char('\n'))), - "tab" => Some(Event::Key(Key::Char('\t'))), - "backspace" => Some(Event::Key(Key::Backspace)), - "left" => Some(Event::Key(Key::Left)), - "right" => Some(Event::Key(Key::Right)), - "up" => Some(Event::Key(Key::Up)), - "down" => Some(Event::Key(Key::Down)), - "home" => Some(Event::Key(Key::Home)), - "end" => Some(Event::Key(Key::End)), - "pageup" => Some(Event::Key(Key::PageUp)), - "pagedown" => Some(Event::Key(Key::PageDown)), - "delete" => Some(Event::Key(Key::Delete)), - "insert" => Some(Event::Key(Key::Insert)), - "escape" => Some(Event::Key(Key::Esc)), - - x if x.starts_with("f") => { - match x[1..].parse::() { - Ok(val) => Some(Event::Key(Key::F(val))), - Err(_) => { - warn!("Cannot parse {}", x); - None - } - } - } - - x if x.starts_with("ctrl+") || x.starts_with("alt+") => { - let is_alt = x.starts_with("alt+"); - let start_length = if is_alt {4} else {5}; - - let character; - // start_length + "shift+x".len() || start_length + "x".len() - if x.len() != start_length + 7 && x.len() != start_length + 1 { - warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7); - return None - } else { - if x.len() == start_length + 7 { - // With "+shift+", so we use an upper case letter - character = x.chars().last().unwrap().to_ascii_uppercase(); - } else { - character = x.chars().last().unwrap().to_ascii_lowercase(); - } - } - - if is_alt { - Some(Event::Key(Key::Alt(character))) - } else { - Some(Event::Key(Key::Ctrl(character))) - } - } - - x => { - warn!("Completely unknown argument {}", x); - None - }, - } - - - - - - - - - - - - - - - - - - - } -} \ No newline at end of file + // Read the JSON contents of the file as an instance of `User`. + let bindings: Vec = json5::from_str(&entries)?; + error!("Bindings parsed!"); + + let mut keymap = Keymap::new(); + let mut found_cmds = Vec::new(); + for binding in bindings { + let cmd = match binding.command.as_ref() { + "move" => { + let args = binding.args.ok_or("move binding incomplete! Missing \"args\"")?; + let cmd : RelativeMove = serde_json::from_value(args)?; + Command::RelativeMove(cmd) + }, + "move_to" => { + let args = binding.args.ok_or("move_to binding incomplete! Missing \"args\"")?; + let cmd : AbsoluteMove = serde_json::from_value(args)?; + Command::AbsoluteMove(cmd) + }, + x => match Command::from_str(x) { + Ok(cmd) => cmd, + // unimplemented command for now + Err(_) => continue, + } + }; + error!("{:?} = {:?}", cmd, binding.keys); + if found_cmds.contains(&cmd) { + continue; + } + if let Some(binding) = KeybindingConfig::parse_keys(&binding.keys) { + keymap.insert(binding, cmd.clone()); + found_cmds.push(cmd); + } else { + warn!("Skipping failed binding"); + continue; + } + } + + Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) + } + + fn parse_keys(keys: &Vec) -> Option { + if keys.len() != 1 { + return None; + } + + let key = &keys[0]; + match key.as_ref() { + "enter" => Some(Event::Key(Key::Char('\n'))), + "tab" => Some(Event::Key(Key::Char('\t'))), + "backspace" => Some(Event::Key(Key::Backspace)), + "left" => Some(Event::Key(Key::Left)), + "right" => Some(Event::Key(Key::Right)), + "up" => Some(Event::Key(Key::Up)), + "down" => Some(Event::Key(Key::Down)), + "home" => Some(Event::Key(Key::Home)), + "end" => Some(Event::Key(Key::End)), + "pageup" => Some(Event::Key(Key::PageUp)), + "pagedown" => Some(Event::Key(Key::PageDown)), + "delete" => Some(Event::Key(Key::Delete)), + "insert" => Some(Event::Key(Key::Insert)), + "escape" => Some(Event::Key(Key::Esc)), + + x if x.starts_with("f") => { + match x[1..].parse::() { + Ok(val) => Some(Event::Key(Key::F(val))), + Err(_) => { + warn!("Cannot parse {}", x); + None + } + } + } + + x if x.starts_with("ctrl+") || x.starts_with("alt+") => { + let is_alt = x.starts_with("alt+"); + let start_length = if is_alt {4} else {5}; + + let character; + // start_length + "shift+x".len() || start_length + "x".len() + if x.len() != start_length + 7 && x.len() != start_length + 1 { + warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7); + return None + } else { + if x.len() == start_length + 7 { + // With "+shift+", so we use an upper case letter + character = x.chars().last().unwrap().to_ascii_uppercase(); + } else { + character = x.chars().last().unwrap().to_ascii_lowercase(); + } + } + + if is_alt { + Some(Event::Key(Key::Alt(character))) + } else { + Some(Event::Key(Key::Ctrl(character))) + } + } + + x => { + warn!("Completely unknown argument {}", x); + None + }, + } + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 0fa9caf..b2c812f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -5,7 +5,7 @@ mod tui; pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder}; mod cmd; -pub use self::cmd::{Command, ParseCommandError}; +pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint}; mod config; pub use self::config::{KeybindingConfig, Keymap}; \ No newline at end of file diff --git a/src/core/tui.rs b/src/core/tui.rs index cac278c..5be7c6e 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -10,6 +10,7 @@ use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; use failure::Error; use core::{Command, Terminal, TerminalEvent, KeybindingConfig}; +use core::{AbsoluteMovePoint, RelativeMoveDistance}; use widgets::{CommandPrompt, Editor}; pub struct Tui { @@ -67,12 +68,39 @@ impl Tui { Command::SetTheme(theme) => self.editor.set_theme(&theme), Command::NextBuffer => self.editor.next_buffer(), Command::PrevBuffer => self.editor.prev_buffer(), - Command::MoveLeft => self.editor.move_left(), - Command::MoveRight => self.editor.move_right(), - Command::MoveUp => self.editor.move_up(), - Command::MoveDown => self.editor.move_down(), - Command::PageDown => self.editor.page_down(), - Command::PageUp => self.editor.page_up(), + Command::RelativeMove(x) => { + match x.by { + RelativeMoveDistance::characters => { + if x.forward { + self.editor.move_right() + } else { + self.editor.move_left() + } + }, + RelativeMoveDistance::pages => { + if x.forward { + self.editor.page_down() + } else { + self.editor.page_up() + } + }, + RelativeMoveDistance::lines => { + if x.forward { + self.editor.move_down() + } else { + self.editor.move_up() + } + }, + _ => unimplemented!() + } + } + Command::AbsoluteMove(x) => { + match x.to { + AbsoluteMovePoint::bol => self.editor.home(), + AbsoluteMovePoint::eol => self.editor.end(), + _ => unimplemented!() + } + } Command::ToggleLineNumbers => self.editor.toggle_line_numbers(), } } diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 82090d5..11d9809 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -292,6 +292,18 @@ impl Editor { } } + pub fn home(&mut self) { + if let Some(view) = self.views.get_mut(&self.current_view) { + view.home(); + } + } + + pub fn end(&mut self) { + if let Some(view) = self.views.get_mut(&self.current_view) { + view.end(); + } + } + pub fn toggle_line_numbers(&mut self) { if let Some(view) = self.views.get_mut(&self.current_view) { view.toggle_line_numbers(); diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index a410467..a736e93 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -126,6 +126,14 @@ impl View { self.client.down() } + pub fn home(&mut self) { + self.client.home() + } + + pub fn end(&mut self) { + self.client.end() + } + pub fn toggle_line_numbers(&mut self) { self.cfg.display_gutter = !self.cfg.display_gutter; } From a4e9f93bbafa724a8cb94330cf92e53576e431f3 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 25 Jun 2019 10:20:11 +0200 Subject: [PATCH 05/36] Editor now holds the keymap alone and translates it for the view. All keys are now mapped. --- configs/Default (Linux).sublime-keymap | 6 +- src/core/config.rs | 2 +- src/core/tui.rs | 54 ++-------------- src/widgets/editor.rs | 90 +++++++++++++++++++++++--- src/widgets/view/view.rs | 51 ++++----------- 5 files changed, 100 insertions(+), 103 deletions(-) diff --git a/configs/Default (Linux).sublime-keymap b/configs/Default (Linux).sublime-keymap index 2820361..9deaf2f 100644 --- a/configs/Default (Linux).sublime-keymap +++ b/configs/Default (Linux).sublime-keymap @@ -87,10 +87,8 @@ { "keys": ["ctrl+up"], "command": "scroll_lines", "args": {"amount": 1.0 } }, { "keys": ["ctrl+down"], "command": "scroll_lines", "args": {"amount": -1.0 } }, - { "keys": ["pagedown"], "command": "next_view" }, - { "keys": ["pageup"], "command": "prev_view" }, - // { "keys": ["ctrl+pagedown"], "command": "next_view" }, - // { "keys": ["ctrl+pageup"], "command": "prev_view" }, + { "keys": ["ctrl+pagedown"], "command": "next_view" }, + { "keys": ["ctrl+pageup"], "command": "prev_view" }, { "keys": ["ctrl+tab"], "command": "next_view_in_stack" }, { "keys": ["ctrl+shift+tab"], "command": "prev_view_in_stack" }, diff --git a/src/core/config.rs b/src/core/config.rs index 75f99d5..7bc405b 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -54,11 +54,11 @@ impl KeybindingConfig { Err(_) => continue, } }; - error!("{:?} = {:?}", cmd, binding.keys); if found_cmds.contains(&cmd) { continue; } if let Some(binding) = KeybindingConfig::parse_keys(&binding.keys) { + error!("{:?} = {:?}", cmd, binding); keymap.insert(binding, cmd.clone()); found_cmds.push(cmd); } else { diff --git a/src/core/tui.rs b/src/core/tui.rs index 5be7c6e..40f8ffd 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -10,7 +10,6 @@ use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; use failure::Error; use core::{Command, Terminal, TerminalEvent, KeybindingConfig}; -use core::{AbsoluteMovePoint, RelativeMoveDistance}; use widgets::{CommandPrompt, Editor}; pub struct Tui { @@ -33,8 +32,6 @@ pub struct Tui { /// Stream of messages from Xi core. core_events: UnboundedReceiver, - - keybindings: KeybindingConfig, } impl Tui { @@ -44,10 +41,9 @@ impl Tui { terminal: Terminal::new()?, exit: false, term_size: (0, 0), - editor: Editor::new(client, keybindings.clone()), + editor: Editor::new(client, keybindings), prompt: None, core_events: events, - keybindings: keybindings, }) } @@ -58,50 +54,12 @@ impl Tui { pub fn run_command(&mut self, cmd: Command) { match cmd { + // We handle these here, the rest is the job of the editor Command::OpenPrompt => self.open_prompt(), Command::Cancel => self.prompt = None, Command::Quit => self.exit = true, - Command::Save(view) => self.editor.save(view), - Command::Back => self.editor.back(), - Command::Delete => self.editor.delete(), - Command::Open(file) => self.editor.new_view(file), - Command::SetTheme(theme) => self.editor.set_theme(&theme), - Command::NextBuffer => self.editor.next_buffer(), - Command::PrevBuffer => self.editor.prev_buffer(), - Command::RelativeMove(x) => { - match x.by { - RelativeMoveDistance::characters => { - if x.forward { - self.editor.move_right() - } else { - self.editor.move_left() - } - }, - RelativeMoveDistance::pages => { - if x.forward { - self.editor.page_down() - } else { - self.editor.page_up() - } - }, - RelativeMoveDistance::lines => { - if x.forward { - self.editor.move_down() - } else { - self.editor.move_up() - } - }, - _ => unimplemented!() - } - } - Command::AbsoluteMove(x) => { - match x.to { - AbsoluteMovePoint::bol => self.editor.home(), - AbsoluteMovePoint::eol => self.editor.end(), - _ => unimplemented!() - } - } - Command::ToggleLineNumbers => self.editor.toggle_line_numbers(), + + editor_cmd => self.editor.handle_command(editor_cmd) } } @@ -114,8 +72,8 @@ impl Tui { /// Global keybindings can be parsed here fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); - - if let Some(cmd) = self.keybindings.keymap.get(&event) { + // TODO: Translate here to own enum which supports more event-types + if let Some(cmd) = self.editor.keybindings.keymap.get(&event) { match cmd { Command::OpenPrompt => { if self.prompt.is_none() { diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 11d9809..0f5899e 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -6,10 +6,12 @@ use futures::{Async, Future, Poll, Stream}; use failure::Error; use indexmap::IndexMap; -use termion::event::Event as TermionEvent; +use termion::event::{Event, Key}; + use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; -use core::{CoreEvent, KeybindingConfig}; +use core::{Command, CoreEvent, KeybindingConfig}; +use core::{AbsoluteMovePoint, RelativeMoveDistance}; use widgets::{View, ViewClient}; /// The main interface to xi-core pub struct Editor { @@ -44,12 +46,12 @@ pub struct Editor { pub size: (u16, u16), pub styles: HashMap, - pub keymap: KeybindingConfig + pub keybindings: KeybindingConfig } /// Methods for general use. impl Editor { - pub fn new(client: Client, keymap: KeybindingConfig) -> Editor { + pub fn new(client: Client, keybindings: KeybindingConfig) -> Editor { let mut styles = HashMap::new(); styles.insert(0, Default::default()); let (new_view_tx, new_view_rx) = mpsc::unbounded::<(ViewId, Option)>(); @@ -63,7 +65,7 @@ impl Editor { client, size: (0, 0), styles, - keymap, + keybindings, } } } @@ -94,7 +96,7 @@ impl Future for Editor { Ok(Async::Ready(Some((view_id, file_path)))) => { info!("creating new view {:?}", view_id); let client = ViewClient::new(self.client.clone(), view_id); - let mut view = View::new(client, file_path, self.keymap.clone()); + let mut view = View::new(client, file_path); view.resize(self.size.1); self.views.insert(view_id, view); info!("switching to view {:?}", view_id); @@ -107,7 +109,7 @@ impl Future for Editor { break; } Err(e) => { - error!("Uknown channel error: {:?}", e); + error!("Unkown channel error: {:?}", e); return Err(()); } } @@ -118,9 +120,77 @@ impl Future for Editor { impl Editor { /// Handle keyboard and mouse events - pub fn handle_input(&mut self, event: TermionEvent) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.handle_input(event) + pub fn handle_input(&mut self, event: Event) { + // We have to remove and insert again, to beat the borrow-checker + match event { + Event::Key(key) => { + match self.keybindings.keymap.get(&event).cloned() { + Some(cmd) => self.handle_command(cmd), + None => { + if let Some(view) = self.views.get_mut(&self.current_view) { + match key { + Key::Char(c) => match c { + '\n' => view.insert_newline(), + '\t' => view.insert_tab(), + _ => view.insert(c), + }, + k => error!("un-handled key {:?}", k) + } + } + }, + } + }, + Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), + ev => error!("un-handled event {:?}", ev), + } + } + + pub fn handle_command(&mut self, cmd: Command) { + match cmd { + Command::Cancel => {/* Handled by TUI */} + Command::OpenPrompt => {/* Handled by TUI */} + Command::Quit => {/* Handled by TUI */} + Command::SetTheme(theme) => self.set_theme(&theme), + Command::NextBuffer => self.next_buffer(), + Command::PrevBuffer => self.prev_buffer(), + Command::Save(view_id) => self.save(view_id), + Command::Open(file) => self.new_view(file), + Command::Back => self.back(), + Command::Delete => self.delete(), + Command::RelativeMove(x) => { + match x.by { + RelativeMoveDistance::characters => { + if x.forward { + self.move_right() + } else { + self.move_left() + } + }, + RelativeMoveDistance::pages => { + if x.forward { + self.page_down() + } else { + self.page_up() + } + }, + RelativeMoveDistance::lines => { + if x.forward { + self.move_down() + } else { + self.move_up() + } + }, + _ => unimplemented!() + } + } + Command::AbsoluteMove(x) => { + match x.to { + AbsoluteMovePoint::bol => self.home(), + AbsoluteMovePoint::eol => self.end(), + _ => unimplemented!() + } + } + Command::ToggleLineNumbers => self.toggle_line_numbers(), } } diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index a736e93..9b51893 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -5,9 +5,8 @@ use std::io::Write; use failure::Error; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; -use termion::event::{Event, Key, MouseButton, MouseEvent}; +use termion::event::{MouseButton, MouseEvent}; use xrl::{ConfigChanges, Line, LineCache, Style, Update}; -use core::KeybindingConfig; use super::cfg::ViewConfig; use super::client::Client; @@ -27,11 +26,10 @@ pub struct View { file: Option, client: Client, cfg: ViewConfig, - keymap: KeybindingConfig } impl View { - pub fn new(client: Client, file: Option, keymap: KeybindingConfig) -> View { + pub fn new(client: Client, file: Option) -> View { View { cache: LineCache::default(), cursor: Default::default(), @@ -39,7 +37,6 @@ impl View { cfg: ViewConfig::default(), client, file, - keymap, } } @@ -198,42 +195,16 @@ impl View { self.client.drag(line, column); } - pub fn handle_input(&mut self, event: Event) { - match event { - Event::Key(key) => match key { - Key::Char(c) => match c { - '\n' => self.insert_newline(), - '\t' => self.insert_tab(), - _ => self.insert(c), - }, - Key::Ctrl(c) => match c { - 'w' => self.save(), - 'h' => self.back(), - _ => error!("un-handled input ctrl+{}", c), - }, - Key::Backspace => self.back(), - Key::Delete => self.delete(), - Key::Left => self.client.left(), - Key::Right => self.client.right(), - Key::Up => self.client.up(), - Key::Down => self.client.down(), - Key::Home => self.client.home(), - Key::End => self.client.end(), - Key::PageUp => self.page_up(), - Key::PageDown => self.page_down(), - k => error!("un-handled key {:?}", k), + pub fn handle_mouse_event(&mut self, mouse_event: MouseEvent) { + match mouse_event { + MouseEvent::Press(press_event, y, x) => match press_event { + MouseButton::Left => self.click(u64::from(x) - 1, u64::from(y) - 1), + MouseButton::WheelUp => self.client.up(), + MouseButton::WheelDown => self.client.down(), + button => error!("un-handled button {:?}", button), }, - Event::Mouse(mouse_event) => match mouse_event { - MouseEvent::Press(press_event, y, x) => match press_event { - MouseButton::Left => self.click(u64::from(x) - 1, u64::from(y) - 1), - MouseButton::WheelUp => self.client.up(), - MouseButton::WheelDown => self.client.down(), - button => error!("un-handled button {:?}", button), - }, - MouseEvent::Release(..) => {} - MouseEvent::Hold(y, x) => self.drag(u64::from(x) - 1, u64::from(y) - 1), - }, - ev => error!("un-handled event {:?}", ev), + MouseEvent::Release(..) => {} + MouseEvent::Hold(y, x) => self.drag(u64::from(x) - 1, u64::from(y) - 1), } } From 386f22b8963c44db7d9fe0bbc725b7839c4b757c Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 25 Jun 2019 10:25:07 +0200 Subject: [PATCH 06/36] Rename functions to avoid key-name <-> functionality confusion --- src/widgets/editor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 0f5899e..52d5ede 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -185,8 +185,8 @@ impl Editor { } Command::AbsoluteMove(x) => { match x.to { - AbsoluteMovePoint::bol => self.home(), - AbsoluteMovePoint::eol => self.end(), + AbsoluteMovePoint::bol => self.move_bol(), + AbsoluteMovePoint::eol => self.move_eol(), _ => unimplemented!() } } @@ -362,13 +362,13 @@ impl Editor { } } - pub fn home(&mut self) { + pub fn move_bol(&mut self) { if let Some(view) = self.views.get_mut(&self.current_view) { view.home(); } } - pub fn end(&mut self) { + pub fn move_eol(&mut self) { if let Some(view) = self.views.get_mut(&self.current_view) { view.end(); } From 0699d1c46c05cb37ef11909f3fcda4cbb091c538 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 25 Jun 2019 11:37:46 +0200 Subject: [PATCH 07/36] Prepare to switch to rust edition 2018 --- src/core/tui.rs | 4 ++-- src/main.rs | 2 +- src/widgets/command_prompt.rs | 2 +- src/widgets/editor.rs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/tui.rs b/src/core/tui.rs index 40f8ffd..7de361f 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -9,8 +9,8 @@ use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; use failure::Error; -use core::{Command, Terminal, TerminalEvent, KeybindingConfig}; -use widgets::{CommandPrompt, Editor}; +use crate::core::{Command, Terminal, TerminalEvent, KeybindingConfig}; +use crate::widgets::{CommandPrompt, Editor}; pub struct Tui { /// The editor holds the text buffers (named "views" in xi diff --git a/src/main.rs b/src/main.rs index e0bbeba..e02a7f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Config, Logger, Root}; use xrl::spawn; -use core::{Command, Tui, TuiServiceBuilder, KeybindingConfig}; +use crate::core::{Command, Tui, TuiServiceBuilder, KeybindingConfig}; fn configure_logs(logfile: &str) { let tui = FileAppender::builder().build(logfile).unwrap(); diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index f3a6a0a..4bd5c17 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,7 +6,7 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use core::{Command, ParseCommandError}; +use crate::core::{Command, ParseCommandError}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 52d5ede..d50604b 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -10,9 +10,9 @@ use termion::event::{Event, Key}; use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; -use core::{Command, CoreEvent, KeybindingConfig}; -use core::{AbsoluteMovePoint, RelativeMoveDistance}; -use widgets::{View, ViewClient}; +use crate::core::{Command, CoreEvent, KeybindingConfig}; +use crate::core::{AbsoluteMovePoint, RelativeMoveDistance}; +use crate::widgets::{View, ViewClient}; /// The main interface to xi-core pub struct Editor { /// Channel from which the responses to "new_view" requests are From 0bc92694744b01107d135d0666a347f9895a17b1 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 25 Jun 2019 11:41:56 +0200 Subject: [PATCH 08/36] Rust 2018: run cargo fix --edition-idioms --- Cargo.toml | 3 ++- src/core/config.rs | 2 +- src/main.rs | 18 ++++-------------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e08e3d2..6adc279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ authors = ["Corentin Henry "] name = "xi-term" version = "0.1.0" +edition = "2018" [dependencies] clap = "2.33.0" @@ -16,4 +17,4 @@ indexmap = "1.0.2" xrl = "0.0.7" serde = { version = "1.0.93", features = ["derive"] } serde_json = "1.0.39" -json5 = "0.2.4" \ No newline at end of file +json5 = "0.2.4" diff --git a/src/core/config.rs b/src/core/config.rs index 7bc405b..fc9a668 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -28,7 +28,7 @@ pub struct KeybindingConfig { } impl KeybindingConfig { - pub fn parse(config_path: &Path) -> Result> { + pub fn parse(config_path: &Path) -> Result> { let entries = fs::read_to_string(config_path)?; // Read the JSON contents of the file as an instance of `User`. let bindings: Vec = json5::from_str(&entries)?; diff --git a/src/main.rs b/src/main.rs index e02a7f7..27d671d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,22 +5,12 @@ #[macro_use] extern crate clap; -extern crate failure; - #[macro_use] extern crate log; -extern crate log4rs; - -extern crate futures; -extern crate indexmap; -extern crate termion; -extern crate tokio; -extern crate xdg; -extern crate xrl; - -extern crate json5; -extern crate serde; -extern crate serde_json; + +use log4rs; +use tokio; +use xrl; mod core; mod widgets; From 117299f82223cf626355ccae241d5d36e3a42c1c Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 26 Jun 2019 09:43:20 +0200 Subject: [PATCH 09/36] Remove duplicate functions in editor, view and client. Now each object just handles the command supposed for itself and hands the rest down --- src/widgets/editor.rs | 109 ++----------------------------------- src/widgets/view/client.rs | 57 ++++++++++++++++++- src/widgets/view/view.rs | 49 +++-------------- 3 files changed, 68 insertions(+), 147 deletions(-) diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index d50604b..1c8e22c 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -11,7 +11,7 @@ use termion::event::{Event, Key}; use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; use crate::core::{Command, CoreEvent, KeybindingConfig}; -use crate::core::{AbsoluteMovePoint, RelativeMoveDistance}; + use crate::widgets::{View, ViewClient}; /// The main interface to xi-core pub struct Editor { @@ -147,50 +147,17 @@ impl Editor { pub fn handle_command(&mut self, cmd: Command) { match cmd { - Command::Cancel => {/* Handled by TUI */} - Command::OpenPrompt => {/* Handled by TUI */} - Command::Quit => {/* Handled by TUI */} Command::SetTheme(theme) => self.set_theme(&theme), Command::NextBuffer => self.next_buffer(), Command::PrevBuffer => self.prev_buffer(), Command::Save(view_id) => self.save(view_id), Command::Open(file) => self.new_view(file), - Command::Back => self.back(), - Command::Delete => self.delete(), - Command::RelativeMove(x) => { - match x.by { - RelativeMoveDistance::characters => { - if x.forward { - self.move_right() - } else { - self.move_left() - } - }, - RelativeMoveDistance::pages => { - if x.forward { - self.page_down() - } else { - self.page_up() - } - }, - RelativeMoveDistance::lines => { - if x.forward { - self.move_down() - } else { - self.move_up() + + view_command => { + if let Some(view) = self.views.get_mut(&self.current_view) { + view.handle_command(view_command) } - }, - _ => unimplemented!() - } - } - Command::AbsoluteMove(x) => { - match x.to { - AbsoluteMovePoint::bol => self.move_bol(), - AbsoluteMovePoint::eol => self.move_eol(), - _ => unimplemented!() - } } - Command::ToggleLineNumbers => self.toggle_line_numbers(), } } @@ -290,18 +257,6 @@ impl Editor { } } - pub fn back(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.back(); - } - } - - pub fn delete(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.delete(); - } - } - pub fn next_buffer(&mut self) { if let Some((dex, _, _)) = self.views.get_full(&self.current_view) { if dex + 1 == self.views.len() { @@ -325,60 +280,6 @@ impl Editor { } } } - - pub fn move_left(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.move_left(); - } - } - - pub fn move_right(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.move_right(); - } - } - - pub fn move_up(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.move_up(); - } - } - - pub fn move_down(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.move_down(); - } - } - - pub fn page_down(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.page_down(); - } - } - - pub fn page_up(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.page_up(); - } - } - - pub fn move_bol(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.home(); - } - } - - pub fn move_eol(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.end(); - } - } - - pub fn toggle_line_numbers(&mut self) { - if let Some(view) = self.views.get_mut(&self.current_view) { - view.toggle_line_numbers(); - } - } } /// Methods ment to be called by the tui struct diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 41a3ae3..fbef6ae 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -2,6 +2,8 @@ use futures::Future; use tokio::spawn; use xrl; +use crate::core::{Command, RelativeMoveDistance, AbsoluteMovePoint}; + pub struct Client { inner: xrl::Client, view_id: xrl::ViewId, @@ -15,6 +17,55 @@ impl Client { } } + pub fn handle_command(&mut self, cmd: Command) { + match cmd { + Command::Cancel => {/* Handled by TUI */} + Command::OpenPrompt => {/* Handled by TUI */} + Command::Quit => {/* Handled by TUI */} + Command::SetTheme(_theme) => { /* Handled by Editor */ }, + Command::NextBuffer => { /* Handled by Editor */ }, + Command::PrevBuffer => { /* Handled by Editor */ }, + Command::Save(_view_id) => { /* Handled by Editor */ }, + Command::Open(_file) => { /* Handled by Editor */ }, + Command::ToggleLineNumbers => { /* Handled by View */ }, + Command::Back => self.back(), + Command::Delete => self.delete(), + Command::RelativeMove(x) => { + match x.by { + RelativeMoveDistance::characters => { + if x.forward { + self.right() + } else { + self.left() + } + }, + RelativeMoveDistance::pages => { + if x.forward { + self.page_down() + } else { + self.page_up() + } + }, + RelativeMoveDistance::lines => { + if x.forward { + self.down() + } else { + self.up() + } + }, + _ => unimplemented!() + } + } + Command::AbsoluteMove(x) => { + match x.to { + AbsoluteMovePoint::bol => self.line_start(), + AbsoluteMovePoint::eol => self.line_end(), + _ => unimplemented!() + } + } + } + } + pub fn insert(&mut self, character: char) { let f = self.inner.char(self.view_id, character).map_err(|_| ()); spawn(f); @@ -65,12 +116,12 @@ impl Client { spawn(f); } - pub fn home(&mut self) { + pub fn line_start(&mut self) { let f = self.inner.line_start(self.view_id).map_err(|_| ()); spawn(f); } - pub fn end(&mut self) { + pub fn line_end(&mut self) { let f = self.inner.line_end(self.view_id).map_err(|_| ()); spawn(f); } @@ -80,7 +131,7 @@ impl Client { spawn(f); } - pub fn backspace(&mut self) { + pub fn back(&mut self) { let f = self.inner.backspace(self.view_id).map_err(|_| ()); spawn(f); } diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 9b51893..60f608d 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -8,6 +8,8 @@ use termion::cursor::Goto; use termion::event::{MouseButton, MouseEvent}; use xrl::{ConfigChanges, Line, LineCache, Style, Update}; +use crate::core::Command; + use super::cfg::ViewConfig; use super::client::Client; use super::style::{reset_style, set_style}; @@ -91,46 +93,6 @@ impl View { self.client.save(self.file.as_ref().unwrap()) } - pub fn back(&mut self) { - self.client.backspace() - } - - pub fn delete(&mut self) { - self.client.delete() - } - - pub fn page_down(&mut self) { - self.client.page_down() - } - - pub fn page_up(&mut self) { - self.client.page_up() - } - - pub fn move_left(&mut self) { - self.client.left() - } - - pub fn move_right(&mut self) { - self.client.right() - } - - pub fn move_up(&mut self) { - self.client.up() - } - - pub fn move_down(&mut self) { - self.client.down() - } - - pub fn home(&mut self) { - self.client.home() - } - - pub fn end(&mut self) { - self.client.end() - } - pub fn toggle_line_numbers(&mut self) { self.cfg.display_gutter = !self.cfg.display_gutter; } @@ -195,6 +157,13 @@ impl View { self.client.drag(line, column); } + pub fn handle_command(&mut self, cmd: Command) { + match cmd { + Command::ToggleLineNumbers => self.toggle_line_numbers(), + client_command => self.client.handle_command(client_command), + } + } + pub fn handle_mouse_event(&mut self, mouse_event: MouseEvent) { match mouse_event { MouseEvent::Press(press_event, y, x) => match press_event { From 702b140783e5e7223a251ddc22ccbfcb1a28ffef Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 26 Jun 2019 09:56:32 +0200 Subject: [PATCH 10/36] Make Insert a command as well and let the client handle it --- src/core/cmd.rs | 2 ++ src/widgets/editor.rs | 8 ++------ src/widgets/view/client.rs | 5 ++++- src/widgets/view/view.rs | 12 ------------ 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index f5e708f..05f948f 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -87,6 +87,8 @@ pub enum Command { ToggleLineNumbers, /// Open prompt for user-input OpenPrompt, + /// Insert a character + Insert(char), } #[derive(Debug)] diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 1c8e22c..e461401 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -129,15 +129,11 @@ impl Editor { None => { if let Some(view) = self.views.get_mut(&self.current_view) { match key { - Key::Char(c) => match c { - '\n' => view.insert_newline(), - '\t' => view.insert_tab(), - _ => view.insert(c), - }, + Key::Char(c) => view.handle_command(Command::Insert(c)), k => error!("un-handled key {:?}", k) } } - }, + } } }, Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index fbef6ae..8f2c2c7 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -29,7 +29,10 @@ impl Client { Command::Open(_file) => { /* Handled by Editor */ }, Command::ToggleLineNumbers => { /* Handled by View */ }, Command::Back => self.back(), - Command::Delete => self.delete(), + Command::Delete => self.delete(), + Command::Insert('\n') => self.insert_newline(), + Command::Insert('\t') => self.insert_tab(), + Command::Insert(c) => self.insert(c), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 60f608d..130d6e5 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -77,18 +77,6 @@ impl View { self.client.scroll(top, bottom); } - pub fn insert(&mut self, c: char) { - self.client.insert(c) - } - - pub fn insert_newline(&mut self) { - self.client.insert_newline() - } - - pub fn insert_tab(&mut self) { - self.client.insert_tab() - } - pub fn save(&mut self) { self.client.save(self.file.as_ref().unwrap()) } From 39995fd460bb1f719df377f94575ad6016e04b56 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 26 Jun 2019 10:21:46 +0200 Subject: [PATCH 11/36] Add command to hide prompt --- src/core/cmd.rs | 1 + src/core/tui.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 05f948f..f8b86f6 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -116,6 +116,7 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match &s[..] { + "hide_overlay" => Ok(Command::Cancel), "s" | "save" => Ok(Command::Save(None)), "q" | "quit" | "exit" => Ok(Command::Quit), "b" | "back" | "left_delete" => Ok(Command::Back), diff --git a/src/core/tui.rs b/src/core/tui.rs index 7de361f..cf738cd 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -73,7 +73,7 @@ impl Tui { fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); // TODO: Translate here to own enum which supports more event-types - if let Some(cmd) = self.editor.keybindings.keymap.get(&event) { + if let Some(cmd) = self.editor.keybindings.keymap.get_mut(&event) { match cmd { Command::OpenPrompt => { if self.prompt.is_none() { @@ -81,6 +81,7 @@ impl Tui { } return; }, Command::Quit => { self.exit = true; return; }, + Command::Cancel => { self.prompt = None; return; }, _ => {/* Somebody else has to deal with these commands */}, } } From 1c110995080c89a8ccab210f986cb15afdc685fb Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 26 Jun 2019 10:22:18 +0200 Subject: [PATCH 12/36] Add undo/redo (unfortunately, there is a bug in termion that does not do ctrl+shift+char --- src/core/cmd.rs | 6 ++++++ src/widgets/view/client.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index f8b86f6..078797b 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -89,6 +89,10 @@ pub enum Command { OpenPrompt, /// Insert a character Insert(char), + // Undo last action + Undo, + // Redo last undone action + Redo } #[derive(Debug)] @@ -123,6 +127,8 @@ impl FromStr for Command { "d" | "delete" | "right_delete" => Ok(Command::Delete), "bn" | "next-buffer" | "next_view" => Ok(Command::NextBuffer), "bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer), + "undo" => Ok(Command::Undo), + "redo" => Ok(Command::Redo), "md" | "move-down" => Ok(Command::RelativeMove( RelativeMove{ by: RelativeMoveDistance::lines, diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 8f2c2c7..877bbff 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -33,6 +33,8 @@ impl Client { Command::Insert('\n') => self.insert_newline(), Command::Insert('\t') => self.insert_tab(), Command::Insert(c) => self.insert(c), + Command::Undo => self.undo(), + Command::Redo => self.redo(), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { @@ -69,6 +71,16 @@ impl Client { } } + pub fn undo(&mut self) { + let f = self.inner.undo(self.view_id).map_err(|_| ()); + spawn(f); + } + + pub fn redo(&mut self) { + let f = self.inner.redo(self.view_id).map_err(|_| ()); + spawn(f); + } + pub fn insert(&mut self, character: char) { let f = self.inner.char(self.view_id, character).map_err(|_| ()); spawn(f); From db5488b012b922239bd4a68f60fd99f7dcbcedca Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 26 Jun 2019 16:10:54 +0200 Subject: [PATCH 13/36] Add multi-cursor support! Find current selection and add cursors on each occurence, as well as line-wise. --- configs/Default (Linux).sublime-keymap | 6 +- src/core/cmd.rs | 17 +++++- src/core/config.rs | 25 ++++++-- src/core/mod.rs | 2 +- src/core/tui.rs | 2 +- src/main.rs | 3 + src/widgets/editor.rs | 12 ++-- src/widgets/view/client.rs | 81 +++++++++++++++++++++++--- src/widgets/view/view.rs | 14 +++++ 9 files changed, 134 insertions(+), 28 deletions(-) diff --git a/configs/Default (Linux).sublime-keymap b/configs/Default (Linux).sublime-keymap index 9deaf2f..db5aa5e 100644 --- a/configs/Default (Linux).sublime-keymap +++ b/configs/Default (Linux).sublime-keymap @@ -31,9 +31,9 @@ { "keys": ["ctrl+u"], "command": "soft_undo" }, { "keys": ["ctrl+shift+u"], "command": "soft_redo" }, - { "keys": ["shift+delete"], "command": "cut" }, - { "keys": ["ctrl+insert"], "command": "copy" }, - { "keys": ["shift+insert"], "command": "paste" }, + //{ "keys": ["shift+delete"], "command": "cut" }, + //{ "keys": ["ctrl+insert"], "command": "copy" }, + //{ "keys": ["shift+insert"], "command": "paste" }, // These two key bindings should replace the above three if you'd prefer // the traditional X11 behavior of shift+insert pasting from the primary diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 078797b..7438b57 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -59,6 +59,12 @@ pub struct AbsoluteMove { pub extend: bool } +#[allow(non_camel_case_types)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct ExpandLinesDirection { + pub forward: bool +} + #[derive(Debug, PartialEq, Clone)] pub enum Command { /// Close the CommandPrompt. @@ -89,10 +95,14 @@ pub enum Command { OpenPrompt, /// Insert a character Insert(char), - // Undo last action + /// Undo last action Undo, - // Redo last undone action - Redo + /// Redo last undone action + Redo, + /// Find word and set another cursor there + FindUnderExpand, + /// Set a new cursor below or above current position + CursorExpandLines(ExpandLinesDirection), } #[derive(Debug)] @@ -120,6 +130,7 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match &s[..] { + "fue" | "find_under_expand" => Ok(Command::FindUnderExpand), "hide_overlay" => Ok(Command::Cancel), "s" | "save" => Ok(Command::Save(None)), "q" | "quit" | "exit" => Ok(Command::Quit), diff --git a/src/core/config.rs b/src/core/config.rs index fc9a668..bf5fd77 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,4 +1,4 @@ -use crate::core::{Command, RelativeMove, AbsoluteMove}; +use crate::core::{Command, RelativeMove, AbsoluteMove, ExpandLinesDirection}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; @@ -48,6 +48,11 @@ impl KeybindingConfig { let cmd : AbsoluteMove = serde_json::from_value(args)?; Command::AbsoluteMove(cmd) }, + "select_lines" => { + let args = binding.args.ok_or("select_lines binding incomplete! Missing \"args\"")?; + let cmd : ExpandLinesDirection = serde_json::from_value(args)?; + Command::CursorExpandLines(cmd) + } x => match Command::from_str(x) { Ok(cmd) => cmd, // unimplemented command for now @@ -62,7 +67,7 @@ impl KeybindingConfig { keymap.insert(binding, cmd.clone()); found_cmds.push(cmd); } else { - warn!("Skipping failed binding"); + // warn!("Skipping failed binding"); continue; } } @@ -91,12 +96,22 @@ impl KeybindingConfig { "delete" => Some(Event::Key(Key::Delete)), "insert" => Some(Event::Key(Key::Insert)), "escape" => Some(Event::Key(Key::Esc)), + "ctrl+right" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 67])), + "ctrl+left" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 68])), + "ctrl+shift+right" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 67])), + "ctrl+shift+left" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 68])), + "ctrl+shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 65])), + "ctrl+shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 66])), + "alt+shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 65])), + "alt+shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 66])), + // Not yet released + // "shift+tab" => Some(Event::Key(Key::Backtab)), x if x.starts_with("f") => { match x[1..].parse::() { Ok(val) => Some(Event::Key(Key::F(val))), Err(_) => { - warn!("Cannot parse {}", x); + // warn!("Cannot parse {}", x); None } } @@ -109,7 +124,7 @@ impl KeybindingConfig { let character; // start_length + "shift+x".len() || start_length + "x".len() if x.len() != start_length + 7 && x.len() != start_length + 1 { - warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7); + // warn!("Cannot parse {}. Length is = {}, which is neither {} nor {} ", x, x.len(), start_length + 1, start_length + 7); return None } else { if x.len() == start_length + 7 { @@ -128,7 +143,7 @@ impl KeybindingConfig { } x => { - warn!("Completely unknown argument {}", x); + // warn!("Completely unknown argument {}", x); None }, } diff --git a/src/core/mod.rs b/src/core/mod.rs index b2c812f..c26f99d 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -5,7 +5,7 @@ mod tui; pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder}; mod cmd; -pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint}; +pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint, ExpandLinesDirection}; mod config; pub use self::config::{KeybindingConfig, Keymap}; \ No newline at end of file diff --git a/src/core/tui.rs b/src/core/tui.rs index cf738cd..f8219fe 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -81,7 +81,7 @@ impl Tui { } return; }, Command::Quit => { self.exit = true; return; }, - Command::Cancel => { self.prompt = None; return; }, + Command::Cancel if !self.prompt.is_none() => { self.prompt = None; return; }, _ => {/* Somebody else has to deal with these commands */}, } } diff --git a/src/main.rs b/src/main.rs index 27d671d..5eec153 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,9 @@ extern crate clap; #[macro_use] extern crate log; +#[macro_use] +extern crate serde_json; + use log4rs; use tokio; use xrl; diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index e461401..a813c2f 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -123,21 +123,20 @@ impl Editor { pub fn handle_input(&mut self, event: Event) { // We have to remove and insert again, to beat the borrow-checker match event { - Event::Key(key) => { - match self.keybindings.keymap.get(&event).cloned() { + Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), + ev => { + match self.keybindings.keymap.get(&ev).cloned() { Some(cmd) => self.handle_command(cmd), None => { if let Some(view) = self.views.get_mut(&self.current_view) { - match key { - Key::Char(c) => view.handle_command(Command::Insert(c)), + match ev { + Event::Key(Key::Char(c)) => view.handle_command(Command::Insert(c)), k => error!("un-handled key {:?}", k) } } } } }, - Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), - ev => error!("un-handled event {:?}", ev), } } @@ -148,7 +147,6 @@ impl Editor { Command::PrevBuffer => self.prev_buffer(), Command::Save(view_id) => self.save(view_id), Command::Open(file) => self.new_view(file), - view_command => { if let Some(view) = self.views.get_mut(&self.current_view) { view.handle_command(view_command) diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 877bbff..7c60865 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -1,6 +1,7 @@ use futures::Future; use tokio::spawn; use xrl; +use serde_json::Value; use crate::core::{Command, RelativeMoveDistance, AbsoluteMovePoint}; @@ -28,6 +29,7 @@ impl Client { Command::Save(_view_id) => { /* Handled by Editor */ }, Command::Open(_file) => { /* Handled by Editor */ }, Command::ToggleLineNumbers => { /* Handled by View */ }, + Command::FindUnderExpand => { /* Handled by View */ }, Command::Back => self.back(), Command::Delete => self.delete(), Command::Insert('\n') => self.insert_newline(), @@ -35,13 +37,21 @@ impl Client { Command::Insert(c) => self.insert(c), Command::Undo => self.undo(), Command::Redo => self.redo(), + Command::CursorExpandLines(dir) => self.cursor_expand_line(dir.forward), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { if x.forward { - self.right() + self.right(x.extend) } else { - self.left() + self.left(x.extend) + } + }, + RelativeMoveDistance::words | RelativeMoveDistance::word_ends => { + if x.forward { + self.word_right(x.extend) + } else { + self.word_left(x.extend) } }, RelativeMoveDistance::pages => { @@ -71,6 +81,19 @@ impl Client { } } + pub fn find_under_expand_next(&mut self) { + let f = self.inner + .find_next(self.view_id, true, false, xrl::ModifySelection::Add) + .map_err(|_| ()); + spawn(f); + } + + pub fn find_under_expand(&mut self) { + let f = self.inner.edit_notify(self.view_id, "selection_for_find", Some(json!({"case_sensitive": true}))) + .map_err(|_| ()); + spawn(f); + } + pub fn undo(&mut self) { let f = self.inner.undo(self.view_id).map_err(|_| ()); spawn(f); @@ -111,14 +134,44 @@ impl Client { spawn(f); } - pub fn right(&mut self) { - let f = self.inner.right(self.view_id).map_err(|_| ()); - spawn(f); + pub fn right(&mut self, extend: bool) { + if extend { + let f = self.inner.right_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.right(self.view_id).map_err(|_| ()); + spawn(f); + } } - pub fn left(&mut self) { - let f = self.inner.left(self.view_id).map_err(|_| ()); - spawn(f); + pub fn left(&mut self, extend: bool) { + if extend { + let f = self.inner.left_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.left(self.view_id).map_err(|_| ()); + spawn(f); + } + } + + pub fn word_right(&mut self, extend: bool) { + if extend { + let f = self.inner.move_word_right_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.move_word_right(self.view_id).map_err(|_| ()); + spawn(f); + } + } + + pub fn word_left(&mut self, extend: bool) { + if extend { + let f = self.inner.move_word_left_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.move_word_left(self.view_id).map_err(|_| ()); + spawn(f); + } } pub fn page_down(&mut self) { @@ -168,4 +221,16 @@ impl Client { let f = self.inner.drag(self.view_id, line, column).map_err(|_| ()); spawn(f); } + + pub fn collapse_selections(&mut self) { + let f = self.inner.collapse_selections(self.view_id).map_err(|_| ()); + spawn(f); + } + + pub fn cursor_expand_line(&mut self, forward: bool) { + let command = if forward { "add_selection_below" } else { "add_selection_above" }; + let f = self.inner.edit_notify(self.view_id, command, None as Option) + .map_err(|_| ()); + spawn(f); + } } diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 130d6e5..2369bb0 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -28,6 +28,8 @@ pub struct View { file: Option, client: Client, cfg: ViewConfig, + + search_in_progress: bool } impl View { @@ -39,6 +41,7 @@ impl View { cfg: ViewConfig::default(), client, file, + search_in_progress: false } } @@ -145,9 +148,20 @@ impl View { self.client.drag(line, column); } + fn find_under_expand(&mut self) { + if self.search_in_progress { + self.client.find_under_expand_next() + } else { + self.search_in_progress = true; + self.client.find_under_expand() + } + } + pub fn handle_command(&mut self, cmd: Command) { match cmd { Command::ToggleLineNumbers => self.toggle_line_numbers(), + Command::FindUnderExpand => self.find_under_expand(), + Command::Cancel => { self.search_in_progress = false; self.client.collapse_selections() }, client_command => self.client.handle_command(client_command), } } From 1424a88561d6b3ed2cdbfb09da0f236674a83707 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 07:24:53 +0200 Subject: [PATCH 14/36] Implement copy&paste --- src/core/cmd.rs | 9 +++++++ src/core/config.rs | 2 +- src/widgets/view/client.rs | 16 ++++++++++++ src/widgets/view/view.rs | 52 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 7438b57..dc4fe75 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -103,6 +103,12 @@ pub enum Command { FindUnderExpand, /// Set a new cursor below or above current position CursorExpandLines(ExpandLinesDirection), + /// Copy the current selection + CopySelection, + /// Paste previously copied or cut text + Paste, + /// Copy the current selection + CutSelection, } #[derive(Debug)] @@ -130,6 +136,9 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match &s[..] { + "copy" => Ok(Command::CopySelection), + "cut" => Ok(Command::CutSelection), + "paste" => Ok(Command::Paste), "fue" | "find_under_expand" => Ok(Command::FindUnderExpand), "hide_overlay" => Ok(Command::Cancel), "s" | "save" => Ok(Command::Save(None)), diff --git a/src/core/config.rs b/src/core/config.rs index bf5fd77..5f46a07 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -142,7 +142,7 @@ impl KeybindingConfig { } } - x => { + _ => { // warn!("Completely unknown argument {}", x); None }, diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 7c60865..11112b6 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -30,6 +30,9 @@ impl Client { Command::Open(_file) => { /* Handled by Editor */ }, Command::ToggleLineNumbers => { /* Handled by View */ }, Command::FindUnderExpand => { /* Handled by View */ }, + Command::CopySelection => { /* Handled by View */ }, + Command::CutSelection => { /* Handled by View */ }, + Command::Paste => { /* Handled by View */ }, Command::Back => self.back(), Command::Delete => self.delete(), Command::Insert('\n') => self.insert_newline(), @@ -94,6 +97,19 @@ impl Client { spawn(f); } + pub fn copy(&mut self) -> impl Future { + self.inner.copy(self.view_id) + } + + pub fn cut(&mut self) -> impl Future { + self.inner.cut(self.view_id) + } + + pub fn paste(&mut self, content: &str) { + let f = self.inner.paste(self.view_id, content).map_err(|_| ()); + spawn(f); + } + pub fn undo(&mut self) { let f = self.inner.undo(self.view_id).map_err(|_| ()); spawn(f); diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 2369bb0..0d4ad5e 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -1,12 +1,15 @@ use std::cmp::max; use std::collections::HashMap; use std::io::Write; +use std::sync::{Arc, Mutex}; +use futures::future::Future; use failure::Error; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; use termion::event::{MouseButton, MouseEvent}; use xrl::{ConfigChanges, Line, LineCache, Style, Update}; +use serde_json::Value; use crate::core::Command; @@ -29,7 +32,8 @@ pub struct View { client: Client, cfg: ViewConfig, - search_in_progress: bool + search_in_progress: bool, + clipboard: Arc>>, } impl View { @@ -41,7 +45,8 @@ impl View { cfg: ViewConfig::default(), client, file, - search_in_progress: false + search_in_progress: false, + clipboard: Arc::new(Mutex::new(None)) } } @@ -157,11 +162,54 @@ impl View { } } + fn paste(&mut self) { + let clipboard = self.clipboard.lock().unwrap(); + match *clipboard { + Some(ref content) => self.client.paste(content), + None => {} + }; + } + + + fn cut(&mut self) { + let arc = self.clipboard.clone(); + let future = self.client.cut() + .and_then(move |x| { let mut clipboard = arc.lock().unwrap(); + *clipboard = match x { + Value::String(s) => Some(s), + z => { error!("ERROR when parsing copy-answer: Wrong type. {:?}", z); None }, + }; + error!("Clipboard is now: {:?}", *clipboard); + Ok(()) + }) + .map_err(|_| ()); + tokio::spawn(future); + } + + + fn copy(&mut self) { + let arc = self.clipboard.clone(); + let future = self.client.copy() + .and_then(move |x| { let mut clipboard = arc.lock().unwrap(); + *clipboard = match x { + Value::String(s) => Some(s), + z => { error!("ERROR when parsing copy-answer: Wrong type. {:?}", z); None }, + }; + error!("Clipboard is now: {:?}", *clipboard); + Ok(()) + }) + .map_err(|_| ()); + tokio::spawn(future); + } + pub fn handle_command(&mut self, cmd: Command) { match cmd { Command::ToggleLineNumbers => self.toggle_line_numbers(), Command::FindUnderExpand => self.find_under_expand(), Command::Cancel => { self.search_in_progress = false; self.client.collapse_selections() }, + Command::CopySelection => self.copy(), + Command::Paste => self.paste(), + Command::CutSelection => self.cut(), client_command => self.client.handle_command(client_command), } } From 2b38760306880c1a3383c7ac55841ac3163d71ca Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 10:20:26 +0200 Subject: [PATCH 15/36] Implement (almost) all move commands --- src/core/config.rs | 17 +++++- src/widgets/view/client.rs | 104 +++++++++++++++++++++++++++++-------- src/widgets/view/view.rs | 4 +- 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index 5f46a07..cebae33 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -98,12 +98,25 @@ impl KeybindingConfig { "escape" => Some(Event::Key(Key::Esc)), "ctrl+right" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 67])), "ctrl+left" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 68])), + "ctrl+home" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 72])), + "ctrl+end" => Some(Event::Unsupported(vec![27, 91, 49, 59, 53, 70])), "ctrl+shift+right" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 67])), "ctrl+shift+left" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 68])), "ctrl+shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 65])), "ctrl+shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 66])), - "alt+shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 65])), - "alt+shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 66])), + "ctrl+shift+home" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 72])), + "ctrl+shift+end" => Some(Event::Unsupported(vec![27, 91, 49, 59, 54, 70])), + "alt+shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 65])), + "alt+shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 52, 66])), + "shift+up" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 65])), + "shift+down" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 66])), + "shift+right" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 67])), + "shift+left" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 68])), + "shift+pageup" => Some(Event::Unsupported(vec![27, 91, 53, 59, 50, 126])), + "shift+pagedown" => Some(Event::Unsupported(vec![27, 91, 54, 59, 50, 126])), + "shift+end" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 70])), + "shift+home" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 72])), + // Not yet released // "shift+tab" => Some(Event::Key(Key::Backtab)), diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 11112b6..6c220dc 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -59,16 +59,16 @@ impl Client { }, RelativeMoveDistance::pages => { if x.forward { - self.page_down() + self.page_down(x.extend) } else { - self.page_up() + self.page_up(x.extend) } }, RelativeMoveDistance::lines => { if x.forward { - self.down() + self.down(x.extend) } else { - self.up() + self.up(x.extend) } }, _ => unimplemented!() @@ -76,8 +76,11 @@ impl Client { } Command::AbsoluteMove(x) => { match x.to { - AbsoluteMovePoint::bol => self.line_start(), - AbsoluteMovePoint::eol => self.line_end(), + AbsoluteMovePoint::bol => self.line_start(x.extend), + AbsoluteMovePoint::eol => self.line_end(x.extend), + AbsoluteMovePoint::bof => self.document_begin(x.extend), + AbsoluteMovePoint::eof => self.document_end(x.extend), + AbsoluteMovePoint::line(line) => self.goto_line(line), _ => unimplemented!() } } @@ -140,14 +143,24 @@ impl Client { spawn(f); } - pub fn down(&mut self) { - let f = self.inner.down(self.view_id).map_err(|_| ()); - spawn(f); + pub fn down(&mut self, extend: bool) { + if extend { + let f = self.inner.down_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.down(self.view_id).map_err(|_| ()); + spawn(f); + } } - pub fn up(&mut self) { - let f = self.inner.up(self.view_id).map_err(|_| ()); - spawn(f); + pub fn up(&mut self, extend: bool) { + if extend { + let f = self.inner.up_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.up(self.view_id).map_err(|_| ()); + spawn(f); + } } pub fn right(&mut self, extend: bool) { @@ -190,23 +203,68 @@ impl Client { } } - pub fn page_down(&mut self) { - let f = self.inner.page_down(self.view_id).map_err(|_| ()); - spawn(f); + pub fn page_down(&mut self, extend: bool) { + if extend { + let f = self.inner.page_down_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.page_down(self.view_id).map_err(|_| ()); + spawn(f); + } } - pub fn page_up(&mut self) { - let f = self.inner.page_up(self.view_id).map_err(|_| ()); - spawn(f); + pub fn page_up(&mut self, extend: bool) { + if extend { + let f = self.inner.page_up_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.page_up(self.view_id).map_err(|_| ()); + spawn(f); + } } - pub fn line_start(&mut self) { - let f = self.inner.line_start(self.view_id).map_err(|_| ()); - spawn(f); + pub fn line_start(&mut self, extend: bool) { + if extend { + let f = self.inner.line_start_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.line_start(self.view_id).map_err(|_| ()); + spawn(f); + } + } + + pub fn line_end(&mut self, extend: bool) { + if extend { + let f = self.inner.line_end_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.line_end(self.view_id).map_err(|_| ()); + spawn(f); + } + } + + pub fn document_begin(&mut self, extend: bool) { + if extend { + let f = self.inner.document_begin_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.document_begin(self.view_id).map_err(|_| ()); + spawn(f); + } + } + + pub fn document_end(&mut self, extend: bool) { + if extend { + let f = self.inner.document_end_sel(self.view_id).map_err(|_| ()); + spawn(f); + } else { + let f = self.inner.document_end(self.view_id).map_err(|_| ()); + spawn(f); + } } - pub fn line_end(&mut self) { - let f = self.inner.line_end(self.view_id).map_err(|_| ()); + pub fn goto_line(&mut self, line: u64) { + let f = self.inner.goto_line(self.view_id, line).map_err(|_| ()); spawn(f); } diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 0d4ad5e..7ca8f46 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -218,8 +218,8 @@ impl View { match mouse_event { MouseEvent::Press(press_event, y, x) => match press_event { MouseButton::Left => self.click(u64::from(x) - 1, u64::from(y) - 1), - MouseButton::WheelUp => self.client.up(), - MouseButton::WheelDown => self.client.down(), + MouseButton::WheelUp => self.client.up(false), + MouseButton::WheelDown => self.client.down(false), button => error!("un-handled button {:?}", button), }, MouseEvent::Release(..) => {} From decb28fa47ab5cb46372ac5d15bc692a18740427 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 12:40:41 +0200 Subject: [PATCH 16/36] Embedd sublime keybindings for now. This is stupid but easier for now during devel. --- src/core/config.rs | 13 +- src/core/default_keybindings.rs | 742 ++++++++++++++++++++++++++++++++ src/core/mod.rs | 5 +- src/main.rs | 7 +- 4 files changed, 757 insertions(+), 10 deletions(-) create mode 100644 src/core/default_keybindings.rs diff --git a/src/core/config.rs b/src/core/config.rs index cebae33..c7486db 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,4 +1,4 @@ -use crate::core::{Command, RelativeMove, AbsoluteMove, ExpandLinesDirection}; +use crate::core::{Command, RelativeMove, AbsoluteMove, ExpandLinesDirection, DEFAULT_KEYBINDINGS}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; @@ -24,14 +24,14 @@ struct Keybinding { #[derive(Clone)] pub struct KeybindingConfig { pub keymap: Keymap, - pub config_path: PathBuf + // pub config_path: PathBuf } impl KeybindingConfig { - pub fn parse(config_path: &Path) -> Result> { - let entries = fs::read_to_string(config_path)?; + pub fn parse() -> Result> { + // let entries = fs::read_to_string(config_path)?; // Read the JSON contents of the file as an instance of `User`. - let bindings: Vec = json5::from_str(&entries)?; + let bindings: Vec = json5::from_str(&DEFAULT_KEYBINDINGS)?; error!("Bindings parsed!"); let mut keymap = Keymap::new(); @@ -72,7 +72,8 @@ impl KeybindingConfig { } } - Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) + // Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) + Ok(KeybindingConfig{keymap}) } fn parse_keys(keys: &Vec) -> Option { diff --git a/src/core/default_keybindings.rs b/src/core/default_keybindings.rs new file mode 100644 index 0000000..d899bc7 --- /dev/null +++ b/src/core/default_keybindings.rs @@ -0,0 +1,742 @@ +pub static DEFAULT_KEYBINDINGS: &'static str = r####" +[ + { "keys": ["ctrl+q"], "command": "exit" }, + + { "keys": ["ctrl+shift+n"], "command": "new_window" }, + { "keys": ["ctrl+shift+w"], "command": "close_window" }, + { "keys": ["ctrl+o"], "command": "prompt_open_file" }, + { "keys": ["ctrl+shift+t"], "command": "reopen_last_file" }, + { "keys": ["alt+o"], "command": "switch_file", "args": {"extensions": ["cpp", "cxx", "cc", "c", "hpp", "hxx", "hh", "h", "ipp", "inl", "m", "mm"]} }, + { "keys": ["ctrl+n"], "command": "new_file" }, + { "keys": ["ctrl+s"], "command": "save" }, + { "keys": ["ctrl+shift+s"], "command": "prompt_save_as" }, + { "keys": ["ctrl+f4"], "command": "close_file" }, + { "keys": ["ctrl+w"], "command": "close" }, + + { "keys": ["ctrl+k", "ctrl+b"], "command": "toggle_side_bar" }, + { "keys": ["f11"], "command": "toggle_full_screen" }, + { "keys": ["shift+f11"], "command": "toggle_distraction_free" }, + + { "keys": ["backspace"], "command": "left_delete" }, + { "keys": ["shift+backspace"], "command": "left_delete" }, + { "keys": ["ctrl+shift+backspace"], "command": "left_delete" }, + { "keys": ["delete"], "command": "right_delete" }, + { "keys": ["enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["shift+enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["keypad_enter"], "command": "insert", "args": {"characters": "\n"} }, + { "keys": ["shift+keypad_enter"], "command": "insert", "args": {"characters": "\n"} }, + + { "keys": ["ctrl+z"], "command": "undo" }, + { "keys": ["ctrl+shift+z"], "command": "redo" }, + { "keys": ["ctrl+y"], "command": "redo_or_repeat" }, + { "keys": ["ctrl+u"], "command": "soft_undo" }, + { "keys": ["ctrl+shift+u"], "command": "soft_redo" }, + + //{ "keys": ["shift+delete"], "command": "cut" }, + //{ "keys": ["ctrl+insert"], "command": "copy" }, + //{ "keys": ["shift+insert"], "command": "paste" }, + + // These two key bindings should replace the above three if you'd prefer + // the traditional X11 behavior of shift+insert pasting from the primary + // selection. The above CUA keys are the default, to match most GTK + // applications. + //{ "keys": ["shift+insert"], "command": "paste", "args": {"clipboard": "selection"} }, + //{ "keys": ["shift+delete"], "command": "right_delete" }, + + { "keys": ["ctrl+x"], "command": "cut" }, + { "keys": ["ctrl+c"], "command": "copy" }, + { "keys": ["ctrl+v"], "command": "paste" }, + { "keys": ["ctrl+shift+v"], "command": "paste_and_indent" }, + { "keys": ["ctrl+k", "ctrl+v"], "command": "paste_from_history" }, + + { "keys": ["left"], "command": "move", "args": {"by": "characters", "forward": false} }, + { "keys": ["right"], "command": "move", "args": {"by": "characters", "forward": true} }, + { "keys": ["up"], "command": "move", "args": {"by": "lines", "forward": false} }, + { "keys": ["down"], "command": "move", "args": {"by": "lines", "forward": true} }, + { "keys": ["shift+left"], "command": "move", "args": {"by": "characters", "forward": false, "extend": true} }, + { "keys": ["shift+right"], "command": "move", "args": {"by": "characters", "forward": true, "extend": true} }, + { "keys": ["shift+up"], "command": "move", "args": {"by": "lines", "forward": false, "extend": true} }, + { "keys": ["shift+down"], "command": "move", "args": {"by": "lines", "forward": true, "extend": true} }, + + { "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} }, + { "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} }, + { "keys": ["ctrl+shift+left"], "command": "move", "args": {"by": "words", "forward": false, "extend": true} }, + { "keys": ["ctrl+shift+right"], "command": "move", "args": {"by": "word_ends", "forward": true, "extend": true} }, + + { "keys": ["alt+left"], "command": "move", "args": {"by": "subwords", "forward": false} }, + { "keys": ["alt+right"], "command": "move", "args": {"by": "subword_ends", "forward": true} }, + { "keys": ["alt+shift+left"], "command": "move", "args": {"by": "subwords", "forward": false, "extend": true} }, + { "keys": ["alt+shift+right"], "command": "move", "args": {"by": "subword_ends", "forward": true, "extend": true} }, + + { "keys": ["alt+shift+up"], "command": "select_lines", "args": {"forward": false} }, + { "keys": ["alt+shift+down"], "command": "select_lines", "args": {"forward": true} }, + + { "keys": ["pageup"], "command": "move", "args": {"by": "pages", "forward": false} }, + { "keys": ["pagedown"], "command": "move", "args": {"by": "pages", "forward": true} }, + { "keys": ["shift+pageup"], "command": "move", "args": {"by": "pages", "forward": false, "extend": true} }, + { "keys": ["shift+pagedown"], "command": "move", "args": {"by": "pages", "forward": true, "extend": true} }, + + { "keys": ["home"], "command": "move_to", "args": {"to": "bol", "extend": false} }, + { "keys": ["end"], "command": "move_to", "args": {"to": "eol", "extend": false} }, + { "keys": ["shift+home"], "command": "move_to", "args": {"to": "bol", "extend": true} }, + { "keys": ["shift+end"], "command": "move_to", "args": {"to": "eol", "extend": true} }, + { "keys": ["ctrl+home"], "command": "move_to", "args": {"to": "bof", "extend": false} }, + { "keys": ["ctrl+end"], "command": "move_to", "args": {"to": "eof", "extend": false} }, + { "keys": ["ctrl+shift+home"], "command": "move_to", "args": {"to": "bof", "extend": true} }, + { "keys": ["ctrl+shift+end"], "command": "move_to", "args": {"to": "eof", "extend": true} }, + + { "keys": ["ctrl+up"], "command": "scroll_lines", "args": {"amount": 1.0 } }, + { "keys": ["ctrl+down"], "command": "scroll_lines", "args": {"amount": -1.0 } }, + + { "keys": ["ctrl+pagedown"], "command": "next_view" }, + { "keys": ["ctrl+pageup"], "command": "prev_view" }, + + { "keys": ["ctrl+tab"], "command": "next_view_in_stack" }, + { "keys": ["ctrl+shift+tab"], "command": "prev_view_in_stack" }, + + { "keys": ["ctrl+a"], "command": "select_all" }, + { "keys": ["ctrl+shift+l"], "command": "split_selection_into_lines" }, + { "keys": ["escape"], "command": "single_selection", "context": + [ + { "key": "num_selections", "operator": "not_equal", "operand": 1 } + ] + }, + { "keys": ["escape"], "command": "clear_fields", "context": + [ + { "key": "has_next_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "clear_fields", "context": + [ + { "key": "has_prev_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_panel", "args": {"cancel": true}, + "context": + [ + { "key": "panel_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_overlay", "context": + [ + { "key": "overlay_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_popup", "context": + [ + { "key": "popup_visible", "operator": "equal", "operand": true } + ] + }, + { "keys": ["escape"], "command": "hide_auto_complete", "context": + [ + { "key": "auto_complete_visible", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["tab"], "command": "insert_best_completion", "args": {"default": "\t", "exact": true} }, + { "keys": ["tab"], "command": "insert_best_completion", "args": {"default": "\t", "exact": false}, + "context": + [ + { "key": "setting.tab_completion", "operator": "equal", "operand": true }, + { "key": "preceding_text", "operator": "not_regex_match", "operand": ".*\\b[0-9]+$", "match_all": true }, + ] + }, + { "keys": ["tab"], "command": "replace_completion_with_next_completion", "context": + [ + { "key": "last_command", "operator": "equal", "operand": "insert_best_completion" }, + { "key": "setting.tab_completion", "operator": "equal", "operand": true } + ] + }, + { "keys": ["tab"], "command": "reindent", "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_match", "operand": "^$", "match_all": true }, + { "key": "following_text", "operator": "regex_match", "operand": "^$", "match_all": true } + ] + }, + { "keys": ["tab"], "command": "indent", "context": + [ + { "key": "text", "operator": "regex_contains", "operand": "\n" } + ] + }, + { "keys": ["tab"], "command": "next_field", "context": + [ + { "key": "has_next_field", "operator": "equal", "operand": true } + ] + }, + { "keys": ["tab"], "command": "commit_completion", "context": + [ + { "key": "auto_complete_visible" }, + { "key": "setting.auto_complete_commit_on_tab" } + ] + }, + + { "keys": ["shift+tab"], "command": "insert", "args": {"characters": "\t"} }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "setting.shift_tab_unindent", "operator": "equal", "operand": true } + ] + }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "preceding_text", "operator": "regex_match", "operand": "^[\t ]*" } + ] + }, + { "keys": ["shift+tab"], "command": "unindent", "context": + [ + { "key": "text", "operator": "regex_contains", "operand": "\n" } + ] + }, + { "keys": ["shift+tab"], "command": "prev_field", "context": + [ + { "key": "has_prev_field", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["ctrl+]"], "command": "indent" }, + { "keys": ["ctrl+["], "command": "unindent" }, + + { "keys": ["insert"], "command": "toggle_overwrite" }, + + { "keys": ["ctrl+l"], "command": "expand_selection", "args": {"to": "line"} }, + { "keys": ["ctrl+d"], "command": "find_under_expand" }, + { "keys": ["ctrl+k", "ctrl+d"], "command": "find_under_expand_skip" }, + { "keys": ["ctrl+shift+space"], "command": "expand_selection", "args": {"to": "scope"} }, + { "keys": ["ctrl+shift+m"], "command": "expand_selection", "args": {"to": "brackets"} }, + { "keys": ["ctrl+m"], "command": "move_to", "args": {"to": "brackets"} }, + { "keys": ["ctrl+shift+j"], "command": "expand_selection", "args": {"to": "indentation"} }, + { "keys": ["ctrl+shift+a"], "command": "expand_selection", "args": {"to": "tag"} }, + + { "keys": ["alt+."], "command": "close_tag" }, + + { "keys": ["ctrl+alt+q"], "command": "toggle_record_macro" }, + { "keys": ["ctrl+alt+shift+q"], "command": "run_macro" }, + + { "keys": ["ctrl+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line.sublime-macro"} }, + { "keys": ["ctrl+shift+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line Before.sublime-macro"} }, + { "keys": ["enter"], "command": "commit_completion", "context": + [ + { "key": "auto_complete_visible" }, + { "key": "setting.auto_complete_commit_on_tab", "operand": false } + ] + }, + + { "keys": ["ctrl+p"], "command": "show_overlay", "args": {"overlay": "goto", "show_files": true} }, + { "keys": ["ctrl+shift+p"], "command": "show_overlay", "args": {"overlay": "command_palette"} }, + { "keys": ["ctrl+alt+p"], "command": "prompt_select_workspace" }, + { "keys": ["ctrl+r"], "command": "show_overlay", "args": {"overlay": "goto", "text": "@"} }, + { "keys": ["ctrl+g"], "command": "show_overlay", "args": {"overlay": "goto", "text": ":"} }, + { "keys": ["ctrl+;"], "command": "show_overlay", "args": {"overlay": "goto", "text": "#"} }, + { "keys": ["f12"], "command": "goto_definition" }, + { "keys": ["shift+f12"], "command": "goto_reference" }, + { "keys": ["ctrl+shift+r"], "command": "goto_symbol_in_project" }, + { "keys": ["alt+-"], "command": "jump_back" }, + { "keys": ["alt+shift+-"], "command": "jump_forward" }, + { "keys": ["alt+keypad_minus"], "command": "jump_back" }, + { "keys": ["alt+shift+keypad_minus"], "command": "jump_forward" }, + + { "keys": ["ctrl+i"], "command": "show_panel", "args": {"panel": "incremental_find", "reverse": false} }, + { "keys": ["ctrl+shift+i"], "command": "show_panel", "args": {"panel": "incremental_find", "reverse": true} }, + { "keys": ["ctrl+f"], "command": "show_panel", "args": {"panel": "find", "reverse": false} }, + { "keys": ["ctrl+h"], "command": "show_panel", "args": {"panel": "replace", "reverse": false} }, + { "keys": ["ctrl+shift+h"], "command": "replace_next" }, + { "keys": ["f3"], "command": "find_next" }, + { "keys": ["shift+f3"], "command": "find_prev" }, + { "keys": ["ctrl+f3"], "command": "find_under" }, + { "keys": ["ctrl+shift+f3"], "command": "find_under_prev" }, + { "keys": ["alt+f3"], "command": "find_all_under" }, + { "keys": ["ctrl+e"], "command": "slurp_find_string" }, + { "keys": ["ctrl+shift+e"], "command": "slurp_replace_string" }, + { "keys": ["ctrl+shift+f"], "command": "show_panel", "args": {"panel": "find_in_files"} }, + { "keys": ["f4"], "command": "next_result" }, + { "keys": ["shift+f4"], "command": "prev_result" }, + + { "keys": ["ctrl+."], "command": "next_modification" }, + { "keys": ["ctrl+,"], "command": "prev_modification" }, + { "keys": ["ctrl+k", "ctrl+z"], "command": "revert_modification" }, + { "keys": ["ctrl+k", "ctrl+/"], "command": "toggle_inline_diff" }, + { "keys": ["ctrl+k", "ctrl+;"], "command": "toggle_inline_diff", "args": { "prefer_hide": true } }, + + { "keys": ["f6"], "command": "toggle_setting", "args": {"setting": "spell_check"} }, + { "keys": ["ctrl+f6"], "command": "next_misspelling" }, + { "keys": ["ctrl+shift+f6"], "command": "prev_misspelling" }, + + { "keys": ["ctrl+shift+up"], "command": "swap_line_up" }, + { "keys": ["ctrl+shift+down"], "command": "swap_line_down" }, + + { "keys": ["ctrl+backspace"], "command": "delete_word", "args": { "forward": false } }, + { "keys": ["ctrl+shift+backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete to Hard BOL.sublime-macro"} }, + + { "keys": ["ctrl+delete"], "command": "delete_word", "args": { "forward": true } }, + { "keys": ["ctrl+shift+delete"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete to Hard EOL.sublime-macro"} }, + + { "keys": ["ctrl+/"], "command": "toggle_comment", "args": { "block": false } }, + { "keys": ["ctrl+shift+/"], "command": "toggle_comment", "args": { "block": true } }, + + { "keys": ["ctrl+j"], "command": "join_lines" }, + { "keys": ["ctrl+shift+d"], "command": "duplicate_line" }, + + { "keys": ["ctrl+`"], "command": "show_panel", "args": {"panel": "console", "toggle": true} }, + + { "keys": ["alt+/"], "command": "auto_complete" }, + { "keys": ["alt+/"], "command": "replace_completion_with_auto_complete", "context": + [ + { "key": "last_command", "operator": "equal", "operand": "insert_best_completion" }, + { "key": "auto_complete_visible", "operator": "equal", "operand": false }, + { "key": "setting.tab_completion", "operator": "equal", "operand": true } + ] + }, + + { "keys": ["ctrl+alt+shift+p"], "command": "show_scope_name" }, + + { "keys": ["f7"], "command": "build" }, + { "keys": ["ctrl+b"], "command": "build" }, + { "keys": ["ctrl+shift+b"], "command": "build", "args": {"select": true} }, + { "keys": ["ctrl+break"], "command": "cancel_build" }, + + { "keys": ["ctrl+t"], "command": "transpose" }, + + { "keys": ["f9"], "command": "sort_lines", "args": {"case_sensitive": false} }, + { "keys": ["ctrl+f9"], "command": "sort_lines", "args": {"case_sensitive": true} }, + + // Auto-pair quotes + { "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"$0\""}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true }, + { "key": "preceding_text", "operator": "not_regex_contains", "operand": "[\"a-zA-Z0-9_]$", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true } + ] + }, + { "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"${0:$SELECTION}\""}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["\""], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }, + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }, + ] + }, + + // Auto-pair single quotes + { "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'$0'"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true }, + { "key": "preceding_text", "operator": "not_regex_contains", "operand": "['a-zA-Z0-9_]$", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true } + ] + }, + { "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'${0:$SELECTION}'"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["'"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true }, + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "'$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true }, + { "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true }, + { "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true }, + ] + }, + + // Auto-pair brackets + { "keys": ["("], "command": "insert_snippet", "args": {"contents": "($0)"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|;|\\}|$)", "match_all": true } + ] + }, + { "keys": ["("], "command": "insert_snippet", "args": {"contents": "(${0:$SELECTION})"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": [")"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\)", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\($", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\)", "match_all": true } + ] + }, + + // Auto-pair square brackets + { "keys": ["["], "command": "insert_snippet", "args": {"contents": "[$0]"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|;|\\}|$)", "match_all": true } + ] + }, + { "keys": ["["], "command": "insert_snippet", "args": {"contents": "[${0:$SELECTION}]"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["]"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\]", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\[$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\]", "match_all": true } + ] + }, + + // Auto-pair curly brackets + { "keys": ["{"], "command": "insert_snippet", "args": {"contents": "{$0}"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|$)", "match_all": true } + ] + }, + { "keys": ["{"], "command": "wrap_block", "args": {"begin": "{", "end": "}"}, "context": + [ + { "key": "indented_block", "match_all": true }, + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_match", "operand": "^$", "match_all": true }, + ] + }, + { "keys": ["{"], "command": "insert_snippet", "args": {"contents": "{${0:$SELECTION}}"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true } + ] + }, + { "keys": ["}"], "command": "move", "args": {"by": "characters", "forward": true}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context": + [ + { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + + { "keys": ["enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line in Braces.sublime-macro"}, "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + { "keys": ["shift+enter"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Add Line in Braces.sublime-macro"}, "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^\\}", "match_all": true } + ] + }, + + { "keys": ["enter"], "command": "auto_indent_tag", "context": + [ + { "key": "setting.auto_indent", "operator": "equal", "operand": true }, + { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, + { "key": "selector", "operator": "equal", "operand": "punctuation.definition.tag.begin", "match_all": true }, + { "key": "preceding_text", "operator": "regex_contains", "operand": ">$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^$", "match_all": true }, + { "key": "following_text", "operator": "regex_contains", "operand": "^ Result<(), Error> { configure_logs(logfile); } - let configfile = std::path::Path::new("./configs/Default (Linux).sublime-keymap").to_owned(); - let keybindings = KeybindingConfig::parse(&configfile).map_err(Error::from_boxed_compat)?; + // let configfile = std::path::Path::new("./configs/Default (Linux).sublime-keymap").to_owned(); + // let keybindings = KeybindingConfig::parse(&configfile).map_err(Error::from_boxed_compat)?; + let keybindings = KeybindingConfig::parse().map_err(Error::from_boxed_compat)?; tokio::run(future::lazy(move || { info!("starting xi-core"); @@ -127,7 +128,7 @@ fn run() -> Result<(), Error> { tui.run_command(Command::Open( matches.value_of("file").map(ToString::to_string), )); - tui.run_command(Command::SetTheme("base16-eighties.dark".into())); + tui.run_command(Command::SetTheme("Solarized (dark)".into())); tui.map_err(|e| error!("TUI exited with an error: {:?}", e)) }) })); From c276d9bc9a3fe3f784ea406472c08ed3257b7877 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 16:22:12 +0200 Subject: [PATCH 17/36] Restructure Command a bit so that there is a single parsing function (with the exception of prompt commands with arguments). --- src/core/cmd.rs | 274 +++++++++++++++++++++------------- src/core/config.rs | 41 ++--- src/core/mod.rs | 4 +- src/widgets/command_prompt.rs | 6 +- 4 files changed, 184 insertions(+), 141 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index dc4fe75..9f493da 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -3,9 +3,14 @@ /// currently commands can only be input through the CommandPrompt. Vim style. use xrl::ViewId; -use std::str::FromStr; use serde::{Deserialize, Serialize}; +use crate::core::KeymapEntry; + +pub trait FromPrompt { + fn from_prompt(vals: &str) -> Result; +} + #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum RelativeMoveDistance { @@ -34,6 +39,66 @@ pub struct RelativeMove { pub extend: bool } +impl FromPrompt for RelativeMove { + fn from_prompt(args: &str) -> Result { + let vals : Vec<&str> = args.split(' ').collect(); + if vals.is_empty() { + return Err(ParseCommandError::ExpectedArgument{cmd: "move".to_string()}); + } + + if vals.len() > 2 { + return Err(ParseCommandError::TooManyArguments{cmd: "move".to_string(), expected: 2, found: vals.len()}); + } + + let extend = vals.len() == 2; + match vals[0] { + "d" | "down" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::lines, + forward: true, + extend + } + )), + "u" | "up" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::lines, + forward: false, + extend + } + )), + "r" | "right" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::characters, + forward: true, + extend + } + )), + "l" | "left" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::characters, + forward: false, + extend + } + )), + "pd" | "page-down" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::pages, + forward: true, + extend + } + )), + "pu" | "page-up" => Ok(Command::RelativeMove( + RelativeMove{ + by: RelativeMoveDistance::pages, + forward: false, + extend + } + )), + command => Err(ParseCommandError::UnknownCommand(command.into())) + } + } +} + #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum AbsoluteMovePoint { @@ -59,6 +124,49 @@ pub struct AbsoluteMove { pub extend: bool } +impl FromPrompt for AbsoluteMove { + fn from_prompt(args: &str) -> Result { + let vals : Vec<&str> = args.split(' ').collect(); + if vals.is_empty() { + return Err(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()}); + } + + if vals.len() > 2 { + return Err(ParseCommandError::TooManyArguments{cmd: "move_to".to_string(), expected: 2, found: vals.len()}); + } + + let extend = vals.len() == 2; + match vals[0] { + "bof" | "beginning-of-file" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::bof, + extend + } + )), + "eof" | "end-of-file" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::eof, + extend + } + )), + "bol" | "beginning-of-line" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::bol, + extend + } + )), + "eol" | "end-of-line" => Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::eol, + extend + } + )), + command => Err(ParseCommandError::UnknownCommand(command.into())) + } + + } +} + #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub struct ExpandLinesDirection { @@ -118,8 +226,8 @@ pub enum ParseCommandError { /// The given command expected an argument. ExpectedArgument { cmd: String, - expected: usize, - found: usize, + // expected: usize, + // found: usize, }, /// The given command was given to many arguments. TooManyArguments { @@ -131,11 +239,10 @@ pub enum ParseCommandError { UnknownCommand(String), } -impl FromStr for Command { - type Err = ParseCommandError; +impl Command { - fn from_str(s: &str) -> Result { - match &s[..] { + pub fn from_keymap_entry(val: KeymapEntry) -> Result { + match val.command.as_ref() { "copy" => Ok(Command::CopySelection), "cut" => Ok(Command::CutSelection), "paste" => Ok(Command::Paste), @@ -149,108 +256,67 @@ impl FromStr for Command { "bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer), "undo" => Ok(Command::Undo), "redo" => Ok(Command::Redo), - "md" | "move-down" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::lines, - forward: true, - extend: false - } - )), - "mu" | "move-up" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::lines, - forward: false, - extend: false - } - )), - "mr" | "move-right" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::characters, - forward: true, - extend: false - } - )), - "ml" | "move-left" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::characters, - forward: false, - extend: false - } - )), - "pd" | "page-down" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::pages, - forward: true, - extend: false - } - )), - "pu" | "page-up" => Ok(Command::RelativeMove( - RelativeMove{ - by: RelativeMoveDistance::pages, - forward: false, - extend: false - } - )), - "bof" | "beginning-of-file" => Ok(Command::AbsoluteMove( - AbsoluteMove{ - to: AbsoluteMovePoint::bof, - extend: false - } - )), - "eof" | "end-of-file" => Ok(Command::AbsoluteMove( - AbsoluteMove{ - to: AbsoluteMovePoint::eof, - extend: false - } - )), - "bol" | "beginning-of-line" => Ok(Command::AbsoluteMove( - AbsoluteMove{ - to: AbsoluteMovePoint::bol, - extend: false - } - )), - "eol" | "end-of-line" => Ok(Command::AbsoluteMove( - AbsoluteMove{ - to: AbsoluteMovePoint::eol, - extend: false - } - )), "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), "op" | "open-prompt" | "show_overlay" => Ok(Command::OpenPrompt), - command => { - let mut parts: Vec<&str> = command.split(' ').collect(); + "move" => { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; + let cmd : RelativeMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::RelativeMove(cmd)) + }, + "move_to" => { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()})?; + let cmd : AbsoluteMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::AbsoluteMove(cmd)) + }, + "select_lines" => { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "select_lines".to_string()})?; + let cmd : ExpandLinesDirection = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::CursorExpandLines(cmd)) + }, + command => Err(ParseCommandError::UnknownCommand(command.into())), + } + } +} + +impl FromPrompt for Command { + fn from_prompt(input: &str) -> Result { + let mut parts: Vec<&str> = input.splitn(2, ' ').collect(); + let cmd = parts.remove(0); - let cmd = parts.remove(0); - match cmd { - "t" | "theme" => { - if parts.is_empty() { - Err(ParseCommandError::ExpectedArgument { - cmd: "theme".into(), - expected: 1, - found: 0, - }) - } else if parts.len() > 1 { - Err(ParseCommandError::TooManyArguments { - cmd: cmd.to_owned(), - expected: 1, - found: parts.len(), - }) - } else { - Ok(Command::SetTheme(parts[0].to_owned())) - } - } - "o" | "open" => { - if parts.is_empty() { - Ok(Command::Open(None)) - } else if parts.len() > 1 { - Err(ParseCommandError::UnexpectedArgument) - } else { - Ok(Command::Open(Some(parts[0].to_owned()))) - } - } - _ => Err(ParseCommandError::UnknownCommand(command.into())), + // If no arguments are given, we can pass it along to the main parsing function + if parts.is_empty() { + return Command::from_keymap_entry(KeymapEntry{keys: Vec::new(), + command: cmd.to_string(), + args: None, + context: None}); + } + + // If we have prompt-arguments, we parse them directly to a command instead of going via json + let args = parts.remove(0); + match cmd.as_ref() { + "move" => RelativeMove::from_prompt(args), + "move_to" => AbsoluteMove::from_prompt(args), + "t" | "theme" => { + if args.is_empty() { + Err(ParseCommandError::ExpectedArgument { + cmd: "theme".into() + }) + } else { + Ok(Command::SetTheme(args.to_owned())) } } + "o" | "open" => { + let parts: Vec<&str> = args.split(' ').collect(); + if parts.is_empty() { + Ok(Command::Open(None)) + } else if parts.len() > 1 { + Err(ParseCommandError::UnexpectedArgument) + } else { + Ok(Command::Open(Some(parts[0].to_owned()))) + } + } + + command => Err(ParseCommandError::UnknownCommand(command.into())), } } } diff --git a/src/core/config.rs b/src/core/config.rs index c7486db..b384823 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,24 +1,19 @@ -use crate::core::{Command, RelativeMove, AbsoluteMove, ExpandLinesDirection, DEFAULT_KEYBINDINGS}; +use crate::core::{Command, DEFAULT_KEYBINDINGS}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; use serde_json::Value; - -use std::path::{Path, PathBuf}; -use std::fs; use std::collections::HashMap; -use std::str::FromStr; - pub type Keymap = HashMap; -#[derive(Debug, Serialize, Deserialize)] -struct Keybinding { - keys: Vec, - command: String, +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct KeymapEntry { + pub keys: Vec, + pub command: String, // For now, unstructured value - args: Option, - context: Option, + pub args: Option, + pub context: Option, } #[derive(Clone)] @@ -31,34 +26,18 @@ impl KeybindingConfig { pub fn parse() -> Result> { // let entries = fs::read_to_string(config_path)?; // Read the JSON contents of the file as an instance of `User`. - let bindings: Vec = json5::from_str(&DEFAULT_KEYBINDINGS)?; + let bindings: Vec = json5::from_str(&DEFAULT_KEYBINDINGS)?; error!("Bindings parsed!"); let mut keymap = Keymap::new(); let mut found_cmds = Vec::new(); for binding in bindings { - let cmd = match binding.command.as_ref() { - "move" => { - let args = binding.args.ok_or("move binding incomplete! Missing \"args\"")?; - let cmd : RelativeMove = serde_json::from_value(args)?; - Command::RelativeMove(cmd) - }, - "move_to" => { - let args = binding.args.ok_or("move_to binding incomplete! Missing \"args\"")?; - let cmd : AbsoluteMove = serde_json::from_value(args)?; - Command::AbsoluteMove(cmd) - }, - "select_lines" => { - let args = binding.args.ok_or("select_lines binding incomplete! Missing \"args\"")?; - let cmd : ExpandLinesDirection = serde_json::from_value(args)?; - Command::CursorExpandLines(cmd) - } - x => match Command::from_str(x) { + let cmd = match Command::from_keymap_entry(binding.clone()) { Ok(cmd) => cmd, // unimplemented command for now Err(_) => continue, - } }; + if found_cmds.contains(&cmd) { continue; } diff --git a/src/core/mod.rs b/src/core/mod.rs index 3af7d88..cc087a9 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -5,10 +5,10 @@ mod tui; pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder}; mod cmd; -pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint, ExpandLinesDirection}; +pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint, ExpandLinesDirection, FromPrompt}; mod config; -pub use self::config::{KeybindingConfig, Keymap}; +pub use self::config::{KeybindingConfig, Keymap, KeymapEntry}; mod default_keybindings; pub use self::default_keybindings::DEFAULT_KEYBINDINGS; \ No newline at end of file diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index 4bd5c17..9cdaeb1 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,12 +6,10 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use crate::core::{Command, ParseCommandError}; +use crate::core::{Command, ParseCommandError, FromPrompt}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; -use std::str::FromStr; - #[derive(Debug, Default)] pub struct CommandPrompt { dex: usize, @@ -72,7 +70,7 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { - Ok(Some(FromStr::from_str(&self.chars)?)) + Ok(Some(Command::from_prompt(&self.chars)?)) } pub fn render(&mut self, w: &mut W, row: u16) -> Result<(), Error> { From b6472f136a3bfb708f536966a9f43e0fe7ea6515 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 17:12:57 +0200 Subject: [PATCH 18/36] Make open-command work with tilde and env-variables. --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/core/cmd.rs | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c5465f3..ceb978f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -908,6 +908,11 @@ dependencies = [ "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shellexpand" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "signal-hook" version = "0.1.9" @@ -1361,6 +1366,7 @@ dependencies = [ "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1509,6 +1515,7 @@ dependencies = [ "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +"checksum shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de7a5b5a9142fd278a10e0209b021a1b85849352e6951f4f914735c976737564" "checksum signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "72ab58f1fda436857e6337dcb6a5aaa34f16c5ddc87b3a8b6ef7a212f90b9c5a" "checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" diff --git a/Cargo.toml b/Cargo.toml index 6adc279..064326f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ xrl = "0.0.7" serde = { version = "1.0.93", features = ["derive"] } serde_json = "1.0.39" json5 = "0.2.4" +shellexpand = "1.0" diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 9f493da..cd9654e 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -312,7 +312,8 @@ impl FromPrompt for Command { } else if parts.len() > 1 { Err(ParseCommandError::UnexpectedArgument) } else { - Ok(Command::Open(Some(parts[0].to_owned()))) + let file = shellexpand::full(parts[0]).map_err(|_| ParseCommandError::UnknownCommand(parts[0].to_string()))?; + Ok(Command::Open(Some(file.to_string()))) } } From fbd71c2b724bf24b0a6a3f9934dff0a7e8ece16e Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Thu, 27 Jun 2019 17:42:20 +0200 Subject: [PATCH 19/36] Implement close current view (last view always remains). --- src/core/cmd.rs | 3 +++ src/widgets/editor.rs | 22 ++++++++++++++++++++++ src/widgets/view/client.rs | 7 +++++++ 3 files changed, 32 insertions(+) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index cd9654e..1f832bb 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -217,6 +217,8 @@ pub enum Command { Paste, /// Copy the current selection CutSelection, + /// Close the current view + CloseCurrentView } #[derive(Debug)] @@ -243,6 +245,7 @@ impl Command { pub fn from_keymap_entry(val: KeymapEntry) -> Result { match val.command.as_ref() { + "close" => Ok(Command::CloseCurrentView), "copy" => Ok(Command::CopySelection), "cut" => Ok(Command::CutSelection), "paste" => Ok(Command::Paste), diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index a813c2f..681a0f0 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -147,6 +147,7 @@ impl Editor { Command::PrevBuffer => self.prev_buffer(), Command::Save(view_id) => self.save(view_id), Command::Open(file) => self.new_view(file), + Command::CloseCurrentView => self.close_view(None), view_command => { if let Some(view) = self.views.get_mut(&self.current_view) { view.handle_command(view_command) @@ -215,6 +216,27 @@ impl Editor { } } + /// Spawn a future that sends a "new_view" request to the core, + /// and forwards the response back to the `Editor`. + pub fn close_view(&mut self, view_id: Option) { + if self.views.len() <= 1 { + // We don't close the last view. + // TODO: Exit the editor instead + return; + } + + let mut closed = false; + let view_to_close = view_id.unwrap_or(self.current_view); + if let Some(view) = self.views.get_mut(&view_to_close) { + view.handle_command(Command::CloseCurrentView); + closed = true; + } + if closed { + self.prev_buffer(); + self.views.remove(&view_to_close); + } + } + /// Spawn a future that sends a "new_view" request to the core, /// and forwards the response back to the `Editor`. pub fn new_view(&mut self, file_path: Option) { diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 6c220dc..7c3dcc0 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -41,6 +41,7 @@ impl Client { Command::Undo => self.undo(), Command::Redo => self.redo(), Command::CursorExpandLines(dir) => self.cursor_expand_line(dir.forward), + Command::CloseCurrentView => self.close(), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { @@ -87,6 +88,12 @@ impl Client { } } + + pub fn close(&mut self) { + let f = self.inner.close_view(self.view_id).map_err(|_| ()); + spawn(f); + } + pub fn find_under_expand_next(&mut self) { let f = self.inner .find_next(self.view_id, true, false, xrl::ModifySelection::Add) From 574c9a93122c56e10cb73b2da7c2411d1689d4f6 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 08:04:27 +0200 Subject: [PATCH 20/36] Implement select_all command --- src/core/cmd.rs | 5 ++++- src/widgets/view/client.rs | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 1f832bb..2b33e42 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -218,7 +218,9 @@ pub enum Command { /// Copy the current selection CutSelection, /// Close the current view - CloseCurrentView + CloseCurrentView, + /// Select all text in the current view + SelectAll, } #[derive(Debug)] @@ -245,6 +247,7 @@ impl Command { pub fn from_keymap_entry(val: KeymapEntry) -> Result { match val.command.as_ref() { + "select_all" => Ok(Command::SelectAll), "close" => Ok(Command::CloseCurrentView), "copy" => Ok(Command::CopySelection), "cut" => Ok(Command::CutSelection), diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 7c3dcc0..d16f21f 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -42,6 +42,7 @@ impl Client { Command::Redo => self.redo(), Command::CursorExpandLines(dir) => self.cursor_expand_line(dir.forward), Command::CloseCurrentView => self.close(), + Command::SelectAll => self.select_all(), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { @@ -88,6 +89,10 @@ impl Client { } } + pub fn select_all(&mut self) { + let f = self.inner.select_all(self.view_id).map_err(|_| ()); + spawn(f); + } pub fn close(&mut self) { let f = self.inner.close_view(self.view_id).map_err(|_| ()); From 15ce710306b19c6432a4f94ffe39df17947925f4 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 08:40:53 +0200 Subject: [PATCH 21/36] Unify and simplify prompt parsing a bit --- src/core/cmd.rs | 64 +++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 2b33e42..ff10d1b 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -289,41 +289,47 @@ impl FromPrompt for Command { let mut parts: Vec<&str> = input.splitn(2, ' ').collect(); let cmd = parts.remove(0); - // If no arguments are given, we can pass it along to the main parsing function - if parts.is_empty() { - return Command::from_keymap_entry(KeymapEntry{keys: Vec::new(), - command: cmd.to_string(), - args: None, - context: None}); - } - // If we have prompt-arguments, we parse them directly to a command instead of going via json - let args = parts.remove(0); + let args = parts.get(0); match cmd.as_ref() { - "move" => RelativeMove::from_prompt(args), - "move_to" => AbsoluteMove::from_prompt(args), + // First, catch some prompt-specific commands (usually those with arguments), + // which need different parsing than whats coming from the keymap-file + "move" => { + let arg = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; + RelativeMove::from_prompt(arg) + }, + "move_to" => { + let arg = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; + AbsoluteMove::from_prompt(arg) + }, "t" | "theme" => { - if args.is_empty() { - Err(ParseCommandError::ExpectedArgument { - cmd: "theme".into() - }) - } else { - Ok(Command::SetTheme(args.to_owned())) - } - } + let theme = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "theme".to_string()})?; + Ok(Command::SetTheme(theme.to_string())) + }, "o" | "open" => { - let parts: Vec<&str> = args.split(' ').collect(); - if parts.is_empty() { - Ok(Command::Open(None)) - } else if parts.len() > 1 { - Err(ParseCommandError::UnexpectedArgument) - } else { - let file = shellexpand::full(parts[0]).map_err(|_| ParseCommandError::UnknownCommand(parts[0].to_string()))?; - Ok(Command::Open(Some(file.to_string()))) - } + // Don't split given arguments by space, as filenames can have spaces in them as well! + let filename = match args { + Some(name) => { + // We take the value given from the prompt and run it through shellexpand, + // to translate to a real path (e.g. "~/.bashrc" doesn't work without this) + let expanded_name = shellexpand::full(name) + .map_err(|_| ParseCommandError::UnknownCommand(name.to_string()))?; + Some(expanded_name.to_string()) + }, + + // If no args where given we open with "None", which is ok, too. + None => None, + }; + Ok(Command::Open(filename)) } - command => Err(ParseCommandError::UnknownCommand(command.into())), + // The stuff we don't handle here, we pass on to the default parsing function + // Since there is no way to know the shape of "args", we drop all + // potentially given prompt-args for this command here. + command => Command::from_keymap_entry(KeymapEntry{keys: Vec::new(), + command: command.to_string(), + args: None, + context: None}) } } } From 495083c8dbac04bb4ba069e2956afd9366a211fd Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 08:53:41 +0200 Subject: [PATCH 22/36] Add (terminal-specific) keys for next/prev buffer. --- src/core/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/config.rs b/src/core/config.rs index b384823..be25b26 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -96,6 +96,8 @@ impl KeybindingConfig { "shift+pagedown" => Some(Event::Unsupported(vec![27, 91, 54, 59, 50, 126])), "shift+end" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 70])), "shift+home" => Some(Event::Unsupported(vec![27, 91, 49, 59, 50, 72])), + "ctrl+pageup" => Some(Event::Unsupported(vec![27, 91, 53, 59, 53, 126])), + "ctrl+pagedown" => Some(Event::Unsupported(vec![27, 91, 54, 59, 53, 126])), // Not yet released // "shift+tab" => Some(Event::Key(Key::Backtab)), From e31365df7f1992d55f0d5eacba7a7a4d2adf63a6 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 10:40:04 +0200 Subject: [PATCH 23/36] First somewhat functioning implementation of Find() --- src/core/cmd.rs | 52 +++++++++++++++++++++++++++++++---- src/core/tui.rs | 13 ++++----- src/widgets/command_prompt.rs | 41 +++++++++++++++++++++++---- src/widgets/mod.rs | 2 +- src/widgets/view/client.rs | 31 ++++++++++++++++++++- 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index ff10d1b..6ac23a1 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -4,8 +4,10 @@ use xrl::ViewId; use serde::{Deserialize, Serialize}; +use serde_json::Value; use crate::core::KeymapEntry; +use crate::widgets::CommandPromptMode; pub trait FromPrompt { fn from_prompt(vals: &str) -> Result; @@ -191,22 +193,28 @@ pub enum Command { NextBuffer, /// Cycle to the previous buffer. PrevBuffer, - // Relative move like line up/down, page up/down, left, right, word left, .. + /// Relative move like line up/down, page up/down, left, right, word left, .. RelativeMove(RelativeMove), - // Relative move like line ending/beginning, file ending/beginning, line-number, ... + /// Relative move like line ending/beginning, file ending/beginning, line-number, ... AbsoluteMove(AbsoluteMove), - + /// Change current color theme SetTheme(String), /// Toggle displaying line numbers. ToggleLineNumbers, /// Open prompt for user-input - OpenPrompt, + OpenPrompt(CommandPromptMode), /// Insert a character Insert(char), /// Undo last action Undo, /// Redo last undone action Redo, + /// Find the given string + Find(String), + /// Find next occurence of active search + FindNext, + /// Find previous occurence of active search + FindPrev, /// Find word and set another cursor there FindUnderExpand, /// Set a new cursor below or above current position @@ -253,6 +261,8 @@ impl Command { "cut" => Ok(Command::CutSelection), "paste" => Ok(Command::Paste), "fue" | "find_under_expand" => Ok(Command::FindUnderExpand), + "fn" | "find_next" => Ok(Command::FindNext), + "fp" | "find_prev" => Ok(Command::FindPrev), "hide_overlay" => Ok(Command::Cancel), "s" | "save" => Ok(Command::Save(None)), "q" | "quit" | "exit" => Ok(Command::Quit), @@ -263,7 +273,34 @@ impl Command { "undo" => Ok(Command::Undo), "redo" => Ok(Command::Redo), "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), - "op" | "open-prompt" | "show_overlay" => Ok(Command::OpenPrompt), + "op" | "open-prompt" => Ok(Command::OpenPrompt(CommandPromptMode::Command)), + "show_overlay" => { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_overlay".to_string()})?; + match args.get("overlay") { + None => Err(ParseCommandError::UnexpectedArgument), + Some(value) => match value { + // We should catch "command_palette" here instead, but because of a bug in termion + // we can't parse ctrl+shift+p... + // Later on we might introduce another prompt mode for "goto" as well. + Value::String(x) if x == "goto" => Ok(Command::OpenPrompt(CommandPromptMode::Command)), + _ => Err(ParseCommandError::UnexpectedArgument), + } + } + } + + "show_panel" => { + error!("+++++++++++++++ A1"); + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_panel".to_string()})?; + match args.get("panel") { + None => Err(ParseCommandError::UnexpectedArgument), + Some(value) => { error!("+++++++++++++++ A2: {}", value);; match value { + Value::String(x) if x == "find" => Ok(Command::OpenPrompt(CommandPromptMode::Find)), + _ => Err(ParseCommandError::UnexpectedArgument), + }} + } + } + + "move" => { let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; let cmd : RelativeMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; @@ -323,6 +360,11 @@ impl FromPrompt for Command { Ok(Command::Open(filename)) } + "f" | "find" => { + let needle = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "find".to_string()})?; + Ok(Command::Find(needle.to_string())) + }, + // The stuff we don't handle here, we pass on to the default parsing function // Since there is no way to know the shape of "args", we drop all // potentially given prompt-args for this command here. diff --git a/src/core/tui.rs b/src/core/tui.rs index f8219fe..4ac0043 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -10,7 +10,7 @@ use xrl::{Client, Frontend, FrontendBuilder, MeasureWidth, XiNotification}; use failure::Error; use crate::core::{Command, Terminal, TerminalEvent, KeybindingConfig}; -use crate::widgets::{CommandPrompt, Editor}; +use crate::widgets::{CommandPrompt, CommandPromptMode, Editor}; pub struct Tui { /// The editor holds the text buffers (named "views" in xi @@ -55,7 +55,7 @@ impl Tui { pub fn run_command(&mut self, cmd: Command) { match cmd { // We handle these here, the rest is the job of the editor - Command::OpenPrompt => self.open_prompt(), + Command::OpenPrompt(x) => self.open_prompt(x), Command::Cancel => self.prompt = None, Command::Quit => self.exit = true, @@ -63,21 +63,20 @@ impl Tui { } } - fn open_prompt(&mut self) { + fn open_prompt(&mut self, mode: CommandPromptMode) { if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::default()); + self.prompt = Some(CommandPrompt::new(mode)); } } /// Global keybindings can be parsed here fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); - // TODO: Translate here to own enum which supports more event-types if let Some(cmd) = self.editor.keybindings.keymap.get_mut(&event) { match cmd { - Command::OpenPrompt => { + Command::OpenPrompt(x) => { if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::default()); + self.prompt = Some(CommandPrompt::new(*x)); } return; }, Command::Quit => { self.exit = true; return; }, diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index 9cdaeb1..8b2c3ef 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -10,13 +10,26 @@ use crate::core::{Command, ParseCommandError, FromPrompt}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; -#[derive(Debug, Default)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CommandPromptMode { + // Parse commands from user-input + Command, + // Switch directly to search-mode + Find, +} + +#[derive(Debug)] pub struct CommandPrompt { + mode: CommandPromptMode, dex: usize, chars: String, } impl CommandPrompt { + pub fn new(mode: CommandPromptMode) -> CommandPrompt { + CommandPrompt{mode, dex: 0, chars: Default::default()} + } + /// Process a terminal event for the command prompt. pub fn handle_input(&mut self, input: &Event) -> Result, ParseCommandError> { match input { @@ -70,19 +83,37 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { - Ok(Some(Command::from_prompt(&self.chars)?)) + match self.mode { + CommandPromptMode::Find => { + if self.chars.is_empty() { + Err(ParseCommandError::ExpectedArgument{cmd: "find".to_string()}) + } else { + Ok(Some(Command::Find(self.chars.clone()))) + } + }, + CommandPromptMode::Command => Ok(Some(Command::from_prompt(&self.chars)?)), + } + } pub fn render(&mut self, w: &mut W, row: u16) -> Result<(), Error> { + let mode_indicator = match self.mode { + CommandPromptMode::Find => "find", + CommandPromptMode::Command => "", + }; + + let cursor_start = (self.dex + 2 + mode_indicator.len()) as u16; + if let Err(err) = write!( w, - "{}{}:{}{}", + "{}{}{}:{}{}", Goto(1, row), ClearLine, + mode_indicator, self.chars, - Goto(self.dex as u16 + 2, row) + Goto(cursor_start, row) ) { - error!("faile to render status bar: {:?}", err); + error!("failed to render status bar: {:?}", err); } Ok(()) } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index be0207e..0b61b5b 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -7,4 +7,4 @@ mod editor; pub use self::editor::Editor; mod command_prompt; -pub use self::command_prompt::CommandPrompt; +pub use self::command_prompt::{CommandPrompt, CommandPromptMode}; diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index d16f21f..9dafccd 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -21,7 +21,7 @@ impl Client { pub fn handle_command(&mut self, cmd: Command) { match cmd { Command::Cancel => {/* Handled by TUI */} - Command::OpenPrompt => {/* Handled by TUI */} + Command::OpenPrompt(_) => {/* Handled by TUI */} Command::Quit => {/* Handled by TUI */} Command::SetTheme(_theme) => { /* Handled by Editor */ }, Command::NextBuffer => { /* Handled by Editor */ }, @@ -43,6 +43,9 @@ impl Client { Command::CursorExpandLines(dir) => self.cursor_expand_line(dir.forward), Command::CloseCurrentView => self.close(), Command::SelectAll => self.select_all(), + Command::Find(needle) => self.find(&needle), + Command::FindNext => self.find_next(), + Command::FindPrev => self.find_prev(), Command::RelativeMove(x) => { match x.by { RelativeMoveDistance::characters => { @@ -89,6 +92,32 @@ impl Client { } } + pub fn find(&mut self, needle: &str) { + // TODO: Rewrite search to have its own struct with all there parameters configurable + let view_id = self.view_id.clone(); + let inner = self.inner.clone(); + // The first search should automatically place the cursor to the first occurence. + // We do this by doing find_next with "allow_same" + let f = self.inner.find(self.view_id, needle, false, false, false) + .and_then(move |_| inner.find_next(view_id, true, true, xrl::ModifySelection::Set)) + .map_err(|_| ()); + spawn(f); + } + + pub fn find_next(&mut self) { + let f = self.inner + .find_next(self.view_id, true, false, xrl::ModifySelection::Set) + .map_err(|_| ()); + spawn(f); + } + + pub fn find_prev(&mut self) { + let f = self.inner + .find_prev(self.view_id, true, false, xrl::ModifySelection::Set) + .map_err(|_| ()); + spawn(f); + } + pub fn select_all(&mut self) { let f = self.inner.select_all(self.view_id).map_err(|_| ()); spawn(f); From 62a601fff3efe1dab212a5cc31e11993594df7c1 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 11:18:30 +0200 Subject: [PATCH 24/36] Implement configurable search --- src/core/cmd.rs | 68 ++++++++++++++++++++++++++++++++--- src/core/mod.rs | 2 +- src/widgets/command_prompt.rs | 10 ++---- src/widgets/view/client.rs | 7 ++-- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 6ac23a1..d0709ab 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -175,6 +175,65 @@ pub struct ExpandLinesDirection { pub forward: bool } +#[derive(Debug, Clone, PartialEq)] +pub struct FindConfig { + pub search_term: String, + pub case_sensitive: bool, + pub regex: bool, + pub whole_words: bool, +} + +impl FromPrompt for FindConfig { + fn from_prompt(args: &str) -> Result { + if args.is_empty() { + return Err(ParseCommandError::ExpectedArgument{cmd: "find".to_string()}) + } + + let mut search_term = args; + let mut case_sensitive = false; + let mut regex = false; + let mut whole_words = false; + + let argsvec : Vec<&str> = args.splitn(2, ' ').collect(); + + if argsvec.len() == 2 && argsvec[0].len() <= 3 { + // We might have search control characters here + let control_chars = argsvec[0]; + + let mut failed = false; + let mut shadows = [false, false, false]; + for cc in control_chars.chars() { + match cc { + 'c' => shadows[0] = true, + 'r' => shadows[1] = true, + 'w' => shadows[2] = true, + _ => { + // Ooops! This first part is NOT a control-sequence after all. Treat it as normal text + failed = true; + break; + } + } + } + + if !failed { + // Strip away control characters of search_term + search_term = argsvec[1]; + case_sensitive = shadows[0]; + regex = shadows[1]; + whole_words = shadows[2]; + } + } + + let config = FindConfig{ + search_term: search_term.to_string(), + case_sensitive, + regex, + whole_words, + }; + Ok(Command::Find(config)) + } +} + #[derive(Debug, PartialEq, Clone)] pub enum Command { /// Close the CommandPrompt. @@ -210,7 +269,7 @@ pub enum Command { /// Redo last undone action Redo, /// Find the given string - Find(String), + Find(FindConfig), /// Find next occurence of active search FindNext, /// Find previous occurence of active search @@ -289,14 +348,13 @@ impl Command { } "show_panel" => { - error!("+++++++++++++++ A1"); let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_panel".to_string()})?; match args.get("panel") { None => Err(ParseCommandError::UnexpectedArgument), - Some(value) => { error!("+++++++++++++++ A2: {}", value);; match value { + Some(value) => match value { Value::String(x) if x == "find" => Ok(Command::OpenPrompt(CommandPromptMode::Find)), _ => Err(ParseCommandError::UnexpectedArgument), - }} + } } } @@ -362,7 +420,7 @@ impl FromPrompt for Command { "f" | "find" => { let needle = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "find".to_string()})?; - Ok(Command::Find(needle.to_string())) + FindConfig::from_prompt(needle) }, // The stuff we don't handle here, we pass on to the default parsing function diff --git a/src/core/mod.rs b/src/core/mod.rs index cc087a9..21d4fe4 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -5,7 +5,7 @@ mod tui; pub use self::tui::{CoreEvent, Tui, TuiService, TuiServiceBuilder}; mod cmd; -pub use self::cmd::{Command, ParseCommandError, RelativeMove, AbsoluteMove, RelativeMoveDistance, AbsoluteMovePoint, ExpandLinesDirection, FromPrompt}; +pub use self::cmd::*; mod config; pub use self::config::{KeybindingConfig, Keymap, KeymapEntry}; diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index 8b2c3ef..22c80fc 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,7 +6,7 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use crate::core::{Command, ParseCommandError, FromPrompt}; +use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; @@ -84,13 +84,7 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { match self.mode { - CommandPromptMode::Find => { - if self.chars.is_empty() { - Err(ParseCommandError::ExpectedArgument{cmd: "find".to_string()}) - } else { - Ok(Some(Command::Find(self.chars.clone()))) - } - }, + CommandPromptMode::Find => Ok(Some(FindConfig::from_prompt(&self.chars)?)), CommandPromptMode::Command => Ok(Some(Command::from_prompt(&self.chars)?)), } diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index 9dafccd..c0bdcb5 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -3,7 +3,7 @@ use tokio::spawn; use xrl; use serde_json::Value; -use crate::core::{Command, RelativeMoveDistance, AbsoluteMovePoint}; +use crate::core::{Command, RelativeMoveDistance, AbsoluteMovePoint, FindConfig}; pub struct Client { inner: xrl::Client, @@ -92,13 +92,12 @@ impl Client { } } - pub fn find(&mut self, needle: &str) { - // TODO: Rewrite search to have its own struct with all there parameters configurable + pub fn find(&mut self, needle: &FindConfig) { let view_id = self.view_id.clone(); let inner = self.inner.clone(); // The first search should automatically place the cursor to the first occurence. // We do this by doing find_next with "allow_same" - let f = self.inner.find(self.view_id, needle, false, false, false) + let f = self.inner.find(self.view_id, &needle.search_term, needle.case_sensitive, needle.regex, needle.whole_words) .and_then(move |_| inner.find_next(view_id, true, true, xrl::ModifySelection::Set)) .map_err(|_| ()); spawn(f); From 6c32c20414ac179c6f4930ebbcf2863e0496f26d Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 11:30:08 +0200 Subject: [PATCH 25/36] Add explanation of how search works above the search prompt. --- src/widgets/command_prompt.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index 22c80fc..d7d3089 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -91,9 +91,26 @@ impl CommandPrompt { } pub fn render(&mut self, w: &mut W, row: u16) -> Result<(), Error> { - let mode_indicator = match self.mode { - CommandPromptMode::Find => "find", - CommandPromptMode::Command => "", + let mode_indicator; + + match self.mode { + CommandPromptMode::Find => { + mode_indicator = "find"; + + // Write a line explaining the search above the searchbar + if let Err(err) = write!( + w, + "{}{}Prefix your search with r, c and/or w \ + to configure search to be (r)egex, (c)ase_sensitive, (w)hole_words. \ + All false by default. Example: \"cw Needle\"", + Goto(1, row - 1), + ClearLine, + // Goto(cursor_start, row) + ) { + error!("failed to render status bar: {:?}", err); + } + } + CommandPromptMode::Command => {mode_indicator = "";}, }; let cursor_start = (self.dex + 2 + mode_indicator.len()) as u16; From 0e77c3d6bce72d78d67c1f8bbdffa063e03d0558 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 12:43:50 +0200 Subject: [PATCH 26/36] Activate move_to linenumber as prompt command --- src/core/cmd.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index d0709ab..2688618 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -163,7 +163,17 @@ impl FromPrompt for AbsoluteMove { extend } )), - command => Err(ParseCommandError::UnknownCommand(command.into())) + + command => { + let number = command.parse::().map_err(|_| ParseCommandError::UnknownCommand(command.into()))?; + Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::line(number), + extend: false + } + ) + ) + } } } From ab35a266e9c9b5ef4b8c980ec7afd24b364bd7bd Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 12:54:47 +0200 Subject: [PATCH 27/36] Implement function to set multiple cursor with clicking middle mouse button. --- src/widgets/view/client.rs | 31 ++++++++++++++++++++----------- src/widgets/view/view.rs | 6 ++++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index c0bdcb5..dde327b 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -323,6 +323,18 @@ impl Client { spawn(f); } + pub fn collapse_selections(&mut self) { + let f = self.inner.collapse_selections(self.view_id).map_err(|_| ()); + spawn(f); + } + + pub fn cursor_expand_line(&mut self, forward: bool) { + let command = if forward { "add_selection_below" } else { "add_selection_above" }; + let f = self.inner.edit_notify(self.view_id, command, None as Option) + .map_err(|_| ()); + spawn(f); + } + pub fn click(&mut self, line: u64, column: u64) { let f = self .inner @@ -331,20 +343,17 @@ impl Client { spawn(f); } - pub fn drag(&mut self, line: u64, column: u64) { - let f = self.inner.drag(self.view_id, line, column).map_err(|_| ()); - spawn(f); - } - - pub fn collapse_selections(&mut self) { - let f = self.inner.collapse_selections(self.view_id).map_err(|_| ()); + pub fn click_cursor_extend(&mut self, line: u64, column: u64) { + let f = self.inner.edit_notify( + self.view_id, + "gesture", + Some(json!({"line": line, "col": column, "ty": {"select": {"granularity": "point", "multi": true}}})), + ).map_err(|_| ()); spawn(f); } - pub fn cursor_expand_line(&mut self, forward: bool) { - let command = if forward { "add_selection_below" } else { "add_selection_above" }; - let f = self.inner.edit_notify(self.view_id, command, None as Option) - .map_err(|_| ()); + pub fn drag(&mut self, line: u64, column: u64) { + let f = self.inner.drag(self.view_id, line, column).map_err(|_| ()); spawn(f); } } diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 7ca8f46..3f606ee 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -148,6 +148,11 @@ impl View { self.client.click(line, column); } + fn click_cursor_extend(&mut self, x: u64, y: u64) { + let (line, column) = self.get_click_location(x, y); + self.client.click_cursor_extend(line, column); + } + fn drag(&mut self, x: u64, y: u64) { let (line, column) = self.get_click_location(x, y); self.client.drag(line, column); @@ -218,6 +223,7 @@ impl View { match mouse_event { MouseEvent::Press(press_event, y, x) => match press_event { MouseButton::Left => self.click(u64::from(x) - 1, u64::from(y) - 1), + MouseButton::Middle => self.click_cursor_extend(u64::from(x) - 1, u64::from(y) - 1), MouseButton::WheelUp => self.client.up(false), MouseButton::WheelDown => self.client.down(false), button => error!("un-handled button {:?}", button), From f19eb172716fee66dd2252b9a56d0bab6c700351 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 16:16:04 +0200 Subject: [PATCH 28/36] First, hacky and incomplete draft of commandprompt with suggestions --- src/core/config.rs | 24 +++++++++++++++++++++--- src/core/tui.rs | 14 +++++++++----- src/widgets/command_prompt.rs | 35 ++++++++++++++++++++++++++++++----- src/widgets/editor.rs | 2 +- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index be25b26..95f8c4a 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -pub type Keymap = HashMap; +pub type Keymap = HashMap; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct KeymapEntry { @@ -16,6 +16,15 @@ pub struct KeymapEntry { pub context: Option, } +#[derive(Debug, Clone)] +pub struct CommandMapEntry { + pub name: String, + pub command: Command, + pub keys: String, + pub keyevent: Event, + pub helptext: String, +} + #[derive(Clone)] pub struct KeybindingConfig { pub keymap: Keymap, @@ -41,9 +50,18 @@ impl KeybindingConfig { if found_cmds.contains(&cmd) { continue; } - if let Some(binding) = KeybindingConfig::parse_keys(&binding.keys) { + if let Some(keyevent) = KeybindingConfig::parse_keys(&binding.keys) { error!("{:?} = {:?}", cmd, binding); - keymap.insert(binding, cmd.clone()); + + let cmdentry = CommandMapEntry{ + name: binding.command.clone(), + command: cmd.clone(), + keys: binding.keys[0].clone(), // can't panix, as parse_keys bails out if != 1 + keyevent: keyevent.clone(), + helptext: String::new(), // TODO + // helptext: cmd.helptext(), + }; + keymap.insert(keyevent, cmdentry); found_cmds.push(cmd); } else { // warn!("Skipping failed binding"); diff --git a/src/core/tui.rs b/src/core/tui.rs index 4ac0043..a440a0c 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -65,7 +65,7 @@ impl Tui { fn open_prompt(&mut self, mode: CommandPromptMode) { if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::new(mode)); + self.prompt = Some(CommandPrompt::new(mode, self.editor.keybindings.keymap.clone())); } } @@ -73,10 +73,10 @@ impl Tui { fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); if let Some(cmd) = self.editor.keybindings.keymap.get_mut(&event) { - match cmd { + match cmd.command { Command::OpenPrompt(x) => { if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::new(*x)); + self.prompt = Some(CommandPrompt::new(x, self.editor.keybindings.keymap.clone())); } return; }, Command::Quit => { self.exit = true; return; }, @@ -105,10 +105,14 @@ impl Tui { } fn render(&mut self) -> Result<(), Error> { + // We first render always the editor and then let the prompt rewrite parts + // of the screen (if active). + // Yes this is a big wasteful to render the editor for each prompt-input, + // but we render the editor for each editor-input as well :-) + self.editor.render(self.terminal.stdout())?; + if let Some(ref mut prompt) = self.prompt { prompt.render(self.terminal.stdout(), self.term_size.1)?; - } else { - self.editor.render(self.terminal.stdout())?; } if let Err(e) = self.terminal.stdout().flush() { error!("failed to flush stdout: {}", e); diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index d7d3089..ed595b7 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,7 +6,7 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig}; +use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig, Keymap}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; @@ -23,11 +23,12 @@ pub struct CommandPrompt { mode: CommandPromptMode, dex: usize, chars: String, + keybindings: Keymap } impl CommandPrompt { - pub fn new(mode: CommandPromptMode) -> CommandPrompt { - CommandPrompt{mode, dex: 0, chars: Default::default()} + pub fn new(mode: CommandPromptMode, keybindings: Keymap) -> CommandPrompt { + CommandPrompt{mode, dex: 0, chars: Default::default(), keybindings} } /// Process a terminal event for the command prompt. @@ -90,6 +91,28 @@ impl CommandPrompt { } + fn render_suggestions(&mut self, w: &mut W, row: u16) -> Result<(), Error> { + if self.chars.is_empty() { + return Ok(()) + } + + let vals : Vec<_> = self.keybindings.values().filter(|x| x.name.starts_with(&self.chars)).take(4).collect(); + for (idx, val) in vals.iter().enumerate() { + if let Err(err) = write!( + w, + "{}{}-> {} [{}]", + Goto(1, row - 1 - idx as u16), + ClearLine, + val.name, + val.keys, + ) { + error!("failed to render status bar: {:?}", err); + // TODO: Return error + } + } + Ok(()) + } + pub fn render(&mut self, w: &mut W, row: u16) -> Result<(), Error> { let mode_indicator; @@ -105,12 +128,14 @@ impl CommandPrompt { All false by default. Example: \"cw Needle\"", Goto(1, row - 1), ClearLine, - // Goto(cursor_start, row) ) { error!("failed to render status bar: {:?}", err); } } - CommandPromptMode::Command => {mode_indicator = "";}, + CommandPromptMode::Command => { + mode_indicator = ""; + self.render_suggestions(w, row)?; + }, }; let cursor_start = (self.dex + 2 + mode_indicator.len()) as u16; diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 681a0f0..a7e0e46 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -126,7 +126,7 @@ impl Editor { Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), ev => { match self.keybindings.keymap.get(&ev).cloned() { - Some(cmd) => self.handle_command(cmd), + Some(cmd) => self.handle_command(cmd.command), None => { if let Some(view) = self.views.get_mut(&self.current_view) { match ev { From 8bfcfa13652d829a1bd063e5b87ae65caef33a1f Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Fri, 28 Jun 2019 16:54:37 +0200 Subject: [PATCH 29/36] Generate nicer names for prompt suggestions. This is really ugly and needs to be redesigned from scratch. --- src/core/cmd.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++ src/core/config.rs | 4 +-- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 2688618..d4266bc 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -13,6 +13,10 @@ pub trait FromPrompt { fn from_prompt(vals: &str) -> Result; } +pub trait ToPrompt { + fn to_prompt(&self) -> String; +} + #[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum RelativeMoveDistance { @@ -41,6 +45,28 @@ pub struct RelativeMove { pub extend: bool } +impl ToPrompt for RelativeMove { + fn to_prompt(&self) -> String { + use RelativeMoveDistance::*; + + let mut ret = "move ".to_string(); + match self.by { + characters => {ret.push_str( if self.forward {"left"} else {"right"} ) }, + lines => {ret.push_str( if self.forward {"down"} else {"up"} )}, + words => {ret.push_str( if self.forward {"wordleft"} else {"wordright"} )}, + word_ends => {ret.push_str( if self.forward {"wendleft"} else {"wendright"} )}, + subwords => {ret.push_str( if self.forward {"subwordleft"} else {"subwordright"} )}, + subword_ends => {ret.push_str( if self.forward {"subwendleft"} else {"subwendright"} )}, + pages => {ret.push_str( if self.forward {"page-down"} else {"page-up"} )}, + } + + if self.extend { + ret.push_str(" (e)xtend"); + } + ret + } +} + impl FromPrompt for RelativeMove { fn from_prompt(args: &str) -> Result { let vals : Vec<&str> = args.split(' ').collect(); @@ -126,6 +152,28 @@ pub struct AbsoluteMove { pub extend: bool } +impl ToPrompt for AbsoluteMove { + fn to_prompt(&self) -> String { + use AbsoluteMovePoint::*; + + let mut ret = "move ".to_string(); + match self.to { + bof => {ret.push_str("bof")} + eof => {ret.push_str("eof")} + bol => {ret.push_str("bol")} + eol => {ret.push_str("eol")} + brackets => {ret.push_str("brackets")} + line(_) => {ret.push_str("")} + } + + if self.extend { + ret.push_str(" (e)xtend"); + } + ret + } +} + + impl FromPrompt for AbsoluteMove { fn from_prompt(args: &str) -> Result { let vals : Vec<&str> = args.split(' ').collect(); @@ -443,3 +491,40 @@ impl FromPrompt for Command { } } } + +impl ToPrompt for Command { + fn to_prompt(&self) -> String { + use Command::*; + + let mut ret = String::new(); + match self { + Cancel => ret.push_str("cancel"), + Quit => ret.push_str("quit"), + Save(_) => ret.push_str("save"), + Back => ret.push_str("back"), + Delete => ret.push_str("delete"), + Open(_) => ret.push_str("open"), + NextBuffer => ret.push_str("buffernext"), + PrevBuffer => ret.push_str("bufferprev"), + RelativeMove(x) => ret.push_str(&x.to_prompt()), + AbsoluteMove(x) => ret.push_str(&x.to_prompt()), + SetTheme(_) => ret.push_str("settheme"), + ToggleLineNumbers => ret.push_str("togglelinenumbers"), + OpenPrompt(_) => ret.push_str("open-prompt"), + Insert(_) => ret.push_str("insert"), + Undo => ret.push_str("undo"), + Redo => ret.push_str("redo"), + Find(_) => ret.push_str("find"), + FindNext => ret.push_str("findnext"), + FindPrev => ret.push_str("findprev"), + FindUnderExpand => ret.push_str("find_under_expand"), + CursorExpandLines(_) => ret.push_str("cursor_expand_lines"), + CopySelection => ret.push_str("copy"), + Paste => ret.push_str("paste"), + CutSelection => ret.push_str("cut"), + CloseCurrentView => ret.push_str("close"), + SelectAll => ret.push_str("selecta_ll"), + } + ret + } +} diff --git a/src/core/config.rs b/src/core/config.rs index 95f8c4a..af8edb3 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,4 +1,4 @@ -use crate::core::{Command, DEFAULT_KEYBINDINGS}; +use crate::core::{Command, DEFAULT_KEYBINDINGS, ToPrompt}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; @@ -54,7 +54,7 @@ impl KeybindingConfig { error!("{:?} = {:?}", cmd, binding); let cmdentry = CommandMapEntry{ - name: binding.command.clone(), + name: cmd.to_prompt(), command: cmd.clone(), keys: binding.keys[0].clone(), // can't panix, as parse_keys bails out if != 1 keyevent: keyevent.clone(), From 694b3b102e2dba7140c75200dd200b34e1b58bb8 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Sat, 29 Jun 2019 13:42:30 +0200 Subject: [PATCH 30/36] Move clipboard to Editor. Had a clipboard for each view, which meant you couldn't copy from one view to another. --- src/widgets/editor.rs | 102 +++++++++++++++++++++++++++++++++---- src/widgets/view/client.rs | 2 +- src/widgets/view/view.rs | 46 +++-------------- 3 files changed, 99 insertions(+), 51 deletions(-) diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index a7e0e46..aef8026 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -7,22 +7,30 @@ use futures::{Async, Future, Poll, Stream}; use failure::Error; use indexmap::IndexMap; use termion::event::{Event, Key}; +use serde_json::Value; use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; use crate::core::{Command, CoreEvent, KeybindingConfig}; use crate::widgets::{View, ViewClient}; + +#[derive(Debug)] +pub enum XiReply { + NewView((ViewId, Option)), + CopiedText(Option), +} + /// The main interface to xi-core pub struct Editor { /// Channel from which the responses to "new_view" requests are /// received. Upon receiving a `ViewId`, the `Editdor` creates a /// new view. - pub new_view_rx: UnboundedReceiver<(ViewId, Option)>, + pub xi_reply_rx: UnboundedReceiver, /// Channel into which the responses to "new_view" requests are /// sent, when they are received from the core. - pub new_view_tx: UnboundedSender<(ViewId, Option)>, + pub xi_reply_tx: UnboundedSender, /// Store the events that we cannot process right away. /// @@ -46,7 +54,8 @@ pub struct Editor { pub size: (u16, u16), pub styles: HashMap, - pub keybindings: KeybindingConfig + pub keybindings: KeybindingConfig, + clipboard: Option, } /// Methods for general use. @@ -54,11 +63,11 @@ impl Editor { pub fn new(client: Client, keybindings: KeybindingConfig) -> Editor { let mut styles = HashMap::new(); styles.insert(0, Default::default()); - let (new_view_tx, new_view_rx) = mpsc::unbounded::<(ViewId, Option)>(); + let (xi_reply_tx, xi_reply_rx) = mpsc::unbounded::(); Editor { - new_view_rx, - new_view_tx, + xi_reply_rx, + xi_reply_tx, delayed_events: Vec::new(), views: IndexMap::new(), current_view: ViewId(0), @@ -66,6 +75,7 @@ impl Editor { size: (0, 0), styles, keybindings, + clipboard: None, } } } @@ -92,8 +102,8 @@ impl Future for Editor { debug!("polling 'new_view' responses"); loop { - match self.new_view_rx.poll() { - Ok(Async::Ready(Some((view_id, file_path)))) => { + match self.xi_reply_rx.poll() { + Ok(Async::Ready(Some(XiReply::NewView((view_id, file_path))))) => { info!("creating new view {:?}", view_id); let client = ViewClient::new(self.client.clone(), view_id); let mut view = View::new(client, file_path); @@ -102,6 +112,12 @@ impl Future for Editor { info!("switching to view {:?}", view_id); self.current_view = view_id; } + + Ok(Async::Ready(Some(XiReply::CopiedText(text)))) => { + info!("Got new text for clipboard {:?}", text); + self.clipboard = text; + } + // We own one of the senders so this cannot happen Ok(Async::Ready(None)) => unreachable!(), Ok(Async::NotReady) => { @@ -148,6 +164,9 @@ impl Editor { Command::Save(view_id) => self.save(view_id), Command::Open(file) => self.new_view(file), Command::CloseCurrentView => self.close_view(None), + Command::CopySelection => self.copy(), + Command::CutSelection => self.cut(), + Command::Paste => self.paste(), view_command => { if let Some(view) = self.views.get_mut(&self.current_view) { view.handle_command(view_command) @@ -237,10 +256,73 @@ impl Editor { } } + /// Spawn a future that sends a "copy" request to the core, + /// and forwards the response back to the `Editor`. + fn copy(&mut self) { + let response_tx = self.xi_reply_tx.clone(); + if let Some(view) = self.views.get_mut(&self.current_view) { + let future = view.copy() + .and_then(move |x| { + // when we get the response from the core, forward the copied + // text to the editor so that the clipboard can be filled/replaced + let text = match x { + Value::String(s) => Some(s), + z => { error!("ERROR when parsing copy-answer: Wrong type. {:?}", z); None }, + }; + response_tx + .unbounded_send(XiReply::CopiedText(text)) + .unwrap_or_else(|e| error!("failed to send \"CopiedText\" response: {:?}", e)); + Ok(()) + }) + .or_else(|client_error| { + error!("failed to send \"CopiedText\" response: {:?}", client_error); + Ok(()) + }); + tokio::spawn(future); + } + } + + + /// Spawn a future that sends a "cut" request to the core, + /// and forwards the response back to the `Editor`. + fn cut(&mut self) { + let response_tx = self.xi_reply_tx.clone(); + if let Some(view) = self.views.get_mut(&self.current_view) { + let future = view.cut() + .and_then(move |x| { + // when we get the response from the core, forward the copied + // text to the editor so that the clipboard can be filled/replaced + let text = match x { + Value::String(s) => Some(s), + z => { error!("ERROR when parsing cut-answer: Wrong type. {:?}", z); None }, + }; + response_tx + .unbounded_send(XiReply::CopiedText(text)) + .unwrap_or_else(|e| error!("failed to send \"CopiedText\" response: {:?}", e)); + Ok(()) + }) + .or_else(|client_error| { + error!("failed to send \"CopiedText\" response: {:?}", client_error); + Ok(()) + }); + tokio::spawn(future); + } + } + + // Paste clipboard + fn paste(&mut self) { + if let Some(view) = self.views.get_mut(&self.current_view) { + match self.clipboard { + Some(ref content) => view.paste(content), + None => {} + }; + } + } + /// Spawn a future that sends a "new_view" request to the core, /// and forwards the response back to the `Editor`. pub fn new_view(&mut self, file_path: Option) { - let response_tx = self.new_view_tx.clone(); + let response_tx = self.xi_reply_tx.clone(); let future = self .client .new_view(file_path.clone()) @@ -248,7 +330,7 @@ impl Editor { // when we get the response from the core, forward the new // view id to the editor so that the view can be created response_tx - .unbounded_send((id, file_path)) + .unbounded_send(XiReply::NewView((id, file_path))) .unwrap_or_else(|e| error!("failed to send \"new_view\" response: {:?}", e)); Ok(()) }) diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index dde327b..c66bdd9 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -28,9 +28,9 @@ impl Client { Command::PrevBuffer => { /* Handled by Editor */ }, Command::Save(_view_id) => { /* Handled by Editor */ }, Command::Open(_file) => { /* Handled by Editor */ }, + Command::CopySelection => { /* Handled by Editor */ }, Command::ToggleLineNumbers => { /* Handled by View */ }, Command::FindUnderExpand => { /* Handled by View */ }, - Command::CopySelection => { /* Handled by View */ }, Command::CutSelection => { /* Handled by View */ }, Command::Paste => { /* Handled by View */ }, Command::Back => self.back(), diff --git a/src/widgets/view/view.rs b/src/widgets/view/view.rs index 3f606ee..a720ce3 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -1,7 +1,6 @@ use std::cmp::max; use std::collections::HashMap; use std::io::Write; -use std::sync::{Arc, Mutex}; use futures::future::Future; use failure::Error; @@ -33,7 +32,6 @@ pub struct View { cfg: ViewConfig, search_in_progress: bool, - clipboard: Arc>>, } impl View { @@ -46,7 +44,6 @@ impl View { client, file, search_in_progress: false, - clipboard: Arc::new(Mutex::new(None)) } } @@ -167,44 +164,16 @@ impl View { } } - fn paste(&mut self) { - let clipboard = self.clipboard.lock().unwrap(); - match *clipboard { - Some(ref content) => self.client.paste(content), - None => {} - }; + pub fn paste(&mut self, text: &str) { + self.client.paste(text) } - - fn cut(&mut self) { - let arc = self.clipboard.clone(); - let future = self.client.cut() - .and_then(move |x| { let mut clipboard = arc.lock().unwrap(); - *clipboard = match x { - Value::String(s) => Some(s), - z => { error!("ERROR when parsing copy-answer: Wrong type. {:?}", z); None }, - }; - error!("Clipboard is now: {:?}", *clipboard); - Ok(()) - }) - .map_err(|_| ()); - tokio::spawn(future); + pub fn copy(&mut self) -> impl Future { + self.client.copy() } - - fn copy(&mut self) { - let arc = self.clipboard.clone(); - let future = self.client.copy() - .and_then(move |x| { let mut clipboard = arc.lock().unwrap(); - *clipboard = match x { - Value::String(s) => Some(s), - z => { error!("ERROR when parsing copy-answer: Wrong type. {:?}", z); None }, - }; - error!("Clipboard is now: {:?}", *clipboard); - Ok(()) - }) - .map_err(|_| ()); - tokio::spawn(future); + pub fn cut(&mut self) -> impl Future { + self.client.cut() } pub fn handle_command(&mut self, cmd: Command) { @@ -212,9 +181,6 @@ impl View { Command::ToggleLineNumbers => self.toggle_line_numbers(), Command::FindUnderExpand => self.find_under_expand(), Command::Cancel => { self.search_in_progress = false; self.client.collapse_selections() }, - Command::CopySelection => self.copy(), - Command::Paste => self.paste(), - Command::CutSelection => self.cut(), client_command => self.client.handle_command(client_command), } } From 88c8b353378d37304378746d2d53e068f7fe0f9c Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Sun, 30 Jun 2019 11:07:42 +0200 Subject: [PATCH 31/36] Major rework of command structure. Known bug: Keybindings of subcommands not yet working (needs a map of its own). --- src/core/cmd.rs | 417 +++++++++++++++++----------------- src/core/config.rs | 69 +++--- src/core/mod.rs | 2 +- src/core/tui.rs | 57 ++--- src/widgets/command_prompt.rs | 61 +++-- src/widgets/editor.rs | 12 +- 6 files changed, 312 insertions(+), 306 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index d4266bc..c20598a 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -8,13 +8,208 @@ use serde_json::Value; use crate::core::KeymapEntry; use crate::widgets::CommandPromptMode; +use std::collections::HashMap; -pub trait FromPrompt { - fn from_prompt(vals: &str) -> Result; +pub type ParserMap = HashMap<&'static str, CommandParser>; + +#[derive(Clone)] +pub struct CommandParser { + pub keybinding: Option, + pub from_prompt: fn(add_args: Option<&str>) -> Result, + // pub to_prompt: fn() -> String, + pub subcommands: Vec<&'static str>, + pub from_keymap_entry: Option Result>, } -pub trait ToPrompt { - fn to_prompt(&self) -> String; +pub fn get_parser_map() -> ParserMap { + let mut map = HashMap::new(); + + map.insert("select_all", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::SelectAll), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("close", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::CloseCurrentView), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("copy", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::CopySelection), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("cut", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::CutSelection), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("paste", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Paste), + subcommands: vec![], + from_keymap_entry: None}); + // "fue" | + map.insert("find_under_expand", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::FindUnderExpand), + subcommands: vec![], + from_keymap_entry: None}); + // "fn" | + map.insert("find_next", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::FindNext), + subcommands: vec![], + from_keymap_entry: None}); + // "fp" | + map.insert("find_prev", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::FindPrev), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("hide_overlay", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Cancel), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("save", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Save(None)), + subcommands: vec![], + from_keymap_entry: None}); + // "q" | "quit" + map.insert("exit", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Quit), + subcommands: vec![], + from_keymap_entry: None}); + // "b" | "back" | + map.insert("left_delete", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Back), + subcommands: vec![], + from_keymap_entry: None}); + // "d" | "delete" | + map.insert("right_delete", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Delete), + subcommands: vec![], + from_keymap_entry: None}); + // "bn" | "next-buffer" | + map.insert("next_view", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::NextBuffer), + subcommands: vec![], + from_keymap_entry: None}); + // "bp" | "prev-buffer" | + map.insert("prev_view", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::PrevBuffer), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("undo", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Undo), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("redo", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::Redo), + subcommands: vec![], + from_keymap_entry: None}); + // "ln" | + map.insert("line-numbers", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::ToggleLineNumbers), + subcommands: vec![], + from_keymap_entry: None}); + // "op" | + map.insert("open-prompt", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::OpenPrompt(CommandPromptMode::Command)), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("select_all", CommandParser{ keybinding: None, + from_prompt: |_| Ok(Command::SelectAll), + subcommands: vec![], + from_keymap_entry: None}); + map.insert("move", CommandParser{ keybinding: None, + from_prompt: RelativeMove::from_prompt, + subcommands: vec!["left", "right", "down", "up", "wordleft", "wordright", + "wendleft", "wendright", "subwordleft", "subwordright", + "subwendleft", "subwendright", "page-down", "page-up"], + from_keymap_entry: Some(|val| { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; + let cmd : RelativeMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::RelativeMove(cmd))})}); + map.insert("move_to", CommandParser{ keybinding: None, + from_prompt: AbsoluteMove::from_prompt, + subcommands: vec!["bof", "eof", "bol", "eol", "brackets", ""], + from_keymap_entry: Some(|val| { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()})?; + let cmd : AbsoluteMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::AbsoluteMove(cmd))})}); + map.insert("select_lines", CommandParser{ keybinding: None, + from_prompt: AbsoluteMove::from_prompt, + subcommands: vec!["above", "below"], + from_keymap_entry: Some(|val| { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "select_lines".to_string()})?; + let cmd : ExpandLinesDirection = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; + Ok(Command::CursorExpandLines(cmd))})}); + // "t" + map.insert("theme", CommandParser{ keybinding: None, + from_prompt: |args| { + let theme = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "theme".to_string()})?; + Ok(Command::SetTheme(theme.to_string())) + }, + subcommands: vec![""], // TODO: Get in here the available themes + from_keymap_entry: None}); + // "f" + map.insert("find", CommandParser{ keybinding: None, + from_prompt: |args| { + let needle = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "find".to_string()})?; + FindConfig::from_prompt(Some(needle)) + }, + subcommands: vec![""], + from_keymap_entry: None}); + // "o" + map.insert("open", CommandParser{ keybinding: None, + from_prompt: |args| { + // Don't split given arguments by space, as filenames can have spaces in them as well! + let filename = match args { + Some(name) => { + // We take the value given from the prompt and run it through shellexpand, + // to translate to a real path (e.g. "~/.bashrc" doesn't work without this) + let expanded_name = shellexpand::full(name) + .map_err(|_| ParseCommandError::UnknownCommand(name.to_string()))?; + Some(expanded_name.to_string()) + }, + + // If no args where given we open with "None", which is ok, too. + None => None, + }; + Ok(Command::Open(filename)) + }, + subcommands: vec![""], + from_keymap_entry: None}); + map.insert("show_overlay", CommandParser{ keybinding: None, + from_prompt: |_| { Err(ParseCommandError::UnexpectedArgument) }, + subcommands: vec![], + from_keymap_entry: Some(|val| { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_overlay".to_string()})?; + match args.get("overlay") { + None => Err(ParseCommandError::UnexpectedArgument), + Some(value) => match value { + // We should catch "command_palette" here instead, but because of a bug in termion + // we can't parse ctrl+shift+p... + // Later on we might introduce another prompt mode for "goto" as well. + Value::String(x) if x == "goto" => Ok(Command::OpenPrompt(CommandPromptMode::Command)), + _ => Err(ParseCommandError::UnexpectedArgument), + } + } + }) + }); + map.insert("show_panel", CommandParser{ keybinding: None, + from_prompt: |_| { Err(ParseCommandError::UnexpectedArgument) }, + subcommands: vec![], + from_keymap_entry: Some(|val| { + let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_panel".to_string()})?; + match args.get("panel") { + None => Err(ParseCommandError::UnexpectedArgument), + Some(value) => match value { + Value::String(x) if x == "find" => Ok(Command::OpenPrompt(CommandPromptMode::Find)), + _ => Err(ParseCommandError::UnexpectedArgument), + } + } + }) + }); + + map +} + +pub trait FromPrompt { + fn from_prompt(vals: Option<&str>) -> Result; } #[allow(non_camel_case_types)] @@ -45,30 +240,9 @@ pub struct RelativeMove { pub extend: bool } -impl ToPrompt for RelativeMove { - fn to_prompt(&self) -> String { - use RelativeMoveDistance::*; - - let mut ret = "move ".to_string(); - match self.by { - characters => {ret.push_str( if self.forward {"left"} else {"right"} ) }, - lines => {ret.push_str( if self.forward {"down"} else {"up"} )}, - words => {ret.push_str( if self.forward {"wordleft"} else {"wordright"} )}, - word_ends => {ret.push_str( if self.forward {"wendleft"} else {"wendright"} )}, - subwords => {ret.push_str( if self.forward {"subwordleft"} else {"subwordright"} )}, - subword_ends => {ret.push_str( if self.forward {"subwendleft"} else {"subwendright"} )}, - pages => {ret.push_str( if self.forward {"page-down"} else {"page-up"} )}, - } - - if self.extend { - ret.push_str(" (e)xtend"); - } - ret - } -} - impl FromPrompt for RelativeMove { - fn from_prompt(args: &str) -> Result { + fn from_prompt(args: Option<&str>) -> Result { + let args = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; let vals : Vec<&str> = args.split(' ').collect(); if vals.is_empty() { return Err(ParseCommandError::ExpectedArgument{cmd: "move".to_string()}); @@ -152,30 +326,9 @@ pub struct AbsoluteMove { pub extend: bool } -impl ToPrompt for AbsoluteMove { - fn to_prompt(&self) -> String { - use AbsoluteMovePoint::*; - - let mut ret = "move ".to_string(); - match self.to { - bof => {ret.push_str("bof")} - eof => {ret.push_str("eof")} - bol => {ret.push_str("bol")} - eol => {ret.push_str("eol")} - brackets => {ret.push_str("brackets")} - line(_) => {ret.push_str("")} - } - - if self.extend { - ret.push_str(" (e)xtend"); - } - ret - } -} - - impl FromPrompt for AbsoluteMove { - fn from_prompt(args: &str) -> Result { + fn from_prompt(args: Option<&str>) -> Result { + let args = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()})?; let vals : Vec<&str> = args.split(' ').collect(); if vals.is_empty() { return Err(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()}); @@ -242,7 +395,8 @@ pub struct FindConfig { } impl FromPrompt for FindConfig { - fn from_prompt(args: &str) -> Result { + fn from_prompt(args: Option<&str>) -> Result { + let args = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "find".to_string()})?; if args.is_empty() { return Err(ParseCommandError::ExpectedArgument{cmd: "find".to_string()}) } @@ -367,164 +521,3 @@ pub enum ParseCommandError { /// Invalid input was received. UnknownCommand(String), } - -impl Command { - - pub fn from_keymap_entry(val: KeymapEntry) -> Result { - match val.command.as_ref() { - "select_all" => Ok(Command::SelectAll), - "close" => Ok(Command::CloseCurrentView), - "copy" => Ok(Command::CopySelection), - "cut" => Ok(Command::CutSelection), - "paste" => Ok(Command::Paste), - "fue" | "find_under_expand" => Ok(Command::FindUnderExpand), - "fn" | "find_next" => Ok(Command::FindNext), - "fp" | "find_prev" => Ok(Command::FindPrev), - "hide_overlay" => Ok(Command::Cancel), - "s" | "save" => Ok(Command::Save(None)), - "q" | "quit" | "exit" => Ok(Command::Quit), - "b" | "back" | "left_delete" => Ok(Command::Back), - "d" | "delete" | "right_delete" => Ok(Command::Delete), - "bn" | "next-buffer" | "next_view" => Ok(Command::NextBuffer), - "bp" | "prev-buffer" | "prev_view" => Ok(Command::PrevBuffer), - "undo" => Ok(Command::Undo), - "redo" => Ok(Command::Redo), - "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), - "op" | "open-prompt" => Ok(Command::OpenPrompt(CommandPromptMode::Command)), - "show_overlay" => { - let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_overlay".to_string()})?; - match args.get("overlay") { - None => Err(ParseCommandError::UnexpectedArgument), - Some(value) => match value { - // We should catch "command_palette" here instead, but because of a bug in termion - // we can't parse ctrl+shift+p... - // Later on we might introduce another prompt mode for "goto" as well. - Value::String(x) if x == "goto" => Ok(Command::OpenPrompt(CommandPromptMode::Command)), - _ => Err(ParseCommandError::UnexpectedArgument), - } - } - } - - "show_panel" => { - let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "show_panel".to_string()})?; - match args.get("panel") { - None => Err(ParseCommandError::UnexpectedArgument), - Some(value) => match value { - Value::String(x) if x == "find" => Ok(Command::OpenPrompt(CommandPromptMode::Find)), - _ => Err(ParseCommandError::UnexpectedArgument), - } - } - } - - - "move" => { - let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; - let cmd : RelativeMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; - Ok(Command::RelativeMove(cmd)) - }, - "move_to" => { - let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move_to".to_string()})?; - let cmd : AbsoluteMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; - Ok(Command::AbsoluteMove(cmd)) - }, - "select_lines" => { - let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "select_lines".to_string()})?; - let cmd : ExpandLinesDirection = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; - Ok(Command::CursorExpandLines(cmd)) - }, - command => Err(ParseCommandError::UnknownCommand(command.into())), - } - } -} - -impl FromPrompt for Command { - fn from_prompt(input: &str) -> Result { - let mut parts: Vec<&str> = input.splitn(2, ' ').collect(); - let cmd = parts.remove(0); - - // If we have prompt-arguments, we parse them directly to a command instead of going via json - let args = parts.get(0); - match cmd.as_ref() { - // First, catch some prompt-specific commands (usually those with arguments), - // which need different parsing than whats coming from the keymap-file - "move" => { - let arg = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; - RelativeMove::from_prompt(arg) - }, - "move_to" => { - let arg = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "move".to_string()})?; - AbsoluteMove::from_prompt(arg) - }, - "t" | "theme" => { - let theme = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "theme".to_string()})?; - Ok(Command::SetTheme(theme.to_string())) - }, - "o" | "open" => { - // Don't split given arguments by space, as filenames can have spaces in them as well! - let filename = match args { - Some(name) => { - // We take the value given from the prompt and run it through shellexpand, - // to translate to a real path (e.g. "~/.bashrc" doesn't work without this) - let expanded_name = shellexpand::full(name) - .map_err(|_| ParseCommandError::UnknownCommand(name.to_string()))?; - Some(expanded_name.to_string()) - }, - - // If no args where given we open with "None", which is ok, too. - None => None, - }; - Ok(Command::Open(filename)) - } - - "f" | "find" => { - let needle = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "find".to_string()})?; - FindConfig::from_prompt(needle) - }, - - // The stuff we don't handle here, we pass on to the default parsing function - // Since there is no way to know the shape of "args", we drop all - // potentially given prompt-args for this command here. - command => Command::from_keymap_entry(KeymapEntry{keys: Vec::new(), - command: command.to_string(), - args: None, - context: None}) - } - } -} - -impl ToPrompt for Command { - fn to_prompt(&self) -> String { - use Command::*; - - let mut ret = String::new(); - match self { - Cancel => ret.push_str("cancel"), - Quit => ret.push_str("quit"), - Save(_) => ret.push_str("save"), - Back => ret.push_str("back"), - Delete => ret.push_str("delete"), - Open(_) => ret.push_str("open"), - NextBuffer => ret.push_str("buffernext"), - PrevBuffer => ret.push_str("bufferprev"), - RelativeMove(x) => ret.push_str(&x.to_prompt()), - AbsoluteMove(x) => ret.push_str(&x.to_prompt()), - SetTheme(_) => ret.push_str("settheme"), - ToggleLineNumbers => ret.push_str("togglelinenumbers"), - OpenPrompt(_) => ret.push_str("open-prompt"), - Insert(_) => ret.push_str("insert"), - Undo => ret.push_str("undo"), - Redo => ret.push_str("redo"), - Find(_) => ret.push_str("find"), - FindNext => ret.push_str("findnext"), - FindPrev => ret.push_str("findprev"), - FindUnderExpand => ret.push_str("find_under_expand"), - CursorExpandLines(_) => ret.push_str("cursor_expand_lines"), - CopySelection => ret.push_str("copy"), - Paste => ret.push_str("paste"), - CutSelection => ret.push_str("cut"), - CloseCurrentView => ret.push_str("close"), - SelectAll => ret.push_str("selecta_ll"), - } - ret - } -} diff --git a/src/core/config.rs b/src/core/config.rs index af8edb3..e6d5f5b 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,11 +1,11 @@ -use crate::core::{Command, DEFAULT_KEYBINDINGS, ToPrompt}; +use crate::core::{Command, DEFAULT_KEYBINDINGS, ParserMap, get_parser_map}; use termion::event::{Event, Key}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -pub type Keymap = HashMap; +pub type KeyMap = HashMap; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct KeymapEntry { @@ -16,18 +16,10 @@ pub struct KeymapEntry { pub context: Option, } -#[derive(Debug, Clone)] -pub struct CommandMapEntry { - pub name: String, - pub command: Command, - pub keys: String, - pub keyevent: Event, - pub helptext: String, -} - #[derive(Clone)] pub struct KeybindingConfig { - pub keymap: Keymap, + pub keymap: KeyMap, + pub parser_map: ParserMap, // pub config_path: PathBuf } @@ -37,40 +29,41 @@ impl KeybindingConfig { // Read the JSON contents of the file as an instance of `User`. let bindings: Vec = json5::from_str(&DEFAULT_KEYBINDINGS)?; error!("Bindings parsed!"); - - let mut keymap = Keymap::new(); + let mut parser_map = get_parser_map(); + let mut keymap = KeyMap::new(); let mut found_cmds = Vec::new(); for binding in bindings { - let cmd = match Command::from_keymap_entry(binding.clone()) { - Ok(cmd) => cmd, - // unimplemented command for now - Err(_) => continue, - }; + let cmd_name = binding.command.clone(); + if let Some(parser) = parser_map.get_mut::(&cmd_name) { + let cmd_res = match parser.from_keymap_entry { + Some(func) => func(binding.clone()), + None => (parser.from_prompt)(None), /* We don't have add. arguments here */ + }; + let cmd = match cmd_res { + Ok(cmd) => cmd, + // unimplemented command for now + Err(_) => continue, + }; - if found_cmds.contains(&cmd) { - continue; - } - if let Some(keyevent) = KeybindingConfig::parse_keys(&binding.keys) { - error!("{:?} = {:?}", cmd, binding); + // Multiple command (this is a hack and needs some thought how to do it right) + if found_cmds.contains(&cmd) { + continue; + } - let cmdentry = CommandMapEntry{ - name: cmd.to_prompt(), - command: cmd.clone(), - keys: binding.keys[0].clone(), // can't panix, as parse_keys bails out if != 1 - keyevent: keyevent.clone(), - helptext: String::new(), // TODO - // helptext: cmd.helptext(), - }; - keymap.insert(keyevent, cmdentry); - found_cmds.push(cmd); - } else { - // warn!("Skipping failed binding"); - continue; + if let Some(keyevent) = KeybindingConfig::parse_keys(&binding.keys) { + error!("{:?} = {:?}", cmd, binding); + keymap.insert(keyevent, cmd.clone()); + parser.keybinding = Some(binding.keys[0].clone()); + found_cmds.push(cmd); + } else { + // warn!("Skipping failed binding"); + continue; + } } } // Ok(KeybindingConfig{keymap: keymap, config_path: config_path.to_owned()}) - Ok(KeybindingConfig{keymap}) + Ok(KeybindingConfig{keymap, parser_map}) } fn parse_keys(keys: &Vec) -> Option { diff --git a/src/core/mod.rs b/src/core/mod.rs index 21d4fe4..fefdc98 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -8,7 +8,7 @@ mod cmd; pub use self::cmd::*; mod config; -pub use self::config::{KeybindingConfig, Keymap, KeymapEntry}; +pub use self::config::{KeybindingConfig, KeyMap, KeymapEntry}; mod default_keybindings; pub use self::default_keybindings::DEFAULT_KEYBINDINGS; \ No newline at end of file diff --git a/src/core/tui.rs b/src/core/tui.rs index a440a0c..c890db2 100644 --- a/src/core/tui.rs +++ b/src/core/tui.rs @@ -18,7 +18,7 @@ pub struct Tui { editor: Editor, /// The command prompt is where users can type commands. - prompt: Option, + prompt: CommandPrompt, /// The terminal is used to draw on the screen a get inputs from /// the user. @@ -41,8 +41,8 @@ impl Tui { terminal: Terminal::new()?, exit: false, term_size: (0, 0), - editor: Editor::new(client, keybindings), - prompt: None, + editor: Editor::new(client, keybindings.keymap), // Here we split the keybindings in two parts. + prompt: CommandPrompt::new(CommandPromptMode::Inactive, keybindings.parser_map), core_events: events, }) } @@ -55,53 +55,41 @@ impl Tui { pub fn run_command(&mut self, cmd: Command) { match cmd { // We handle these here, the rest is the job of the editor - Command::OpenPrompt(x) => self.open_prompt(x), - Command::Cancel => self.prompt = None, + Command::OpenPrompt(x) => self.prompt.set_mode(x), + Command::Cancel if self.prompt.is_active() => self.prompt.set_mode(CommandPromptMode::Inactive), Command::Quit => self.exit = true, editor_cmd => self.editor.handle_command(editor_cmd) } } - fn open_prompt(&mut self, mode: CommandPromptMode) { - if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::new(mode, self.editor.keybindings.keymap.clone())); - } - } - /// Global keybindings can be parsed here fn handle_input(&mut self, event: Event) { debug!("handling input {:?}", event); - if let Some(cmd) = self.editor.keybindings.keymap.get_mut(&event) { - match cmd.command { - Command::OpenPrompt(x) => { - if self.prompt.is_none() { - self.prompt = Some(CommandPrompt::new(x, self.editor.keybindings.keymap.clone())); - } - return; }, + if let Some(cmd) = self.editor.keymap.get_mut(&event) { + match cmd { + Command::OpenPrompt(x) => { self.prompt.set_mode(*x); return; }, Command::Quit => { self.exit = true; return; }, - Command::Cancel if !self.prompt.is_none() => { self.prompt = None; return; }, + Command::Cancel if self.prompt.is_active() => { self.prompt.set_mode(CommandPromptMode::Inactive); return; }, _ => {/* Somebody else has to deal with these commands */}, } } // No command prompt is active, process the event normally. - if self.prompt.is_none() { + if self.prompt.is_active() { + // // A command prompt is active. + // let mut prompt = self.prompt.take().unwrap(); + match self.prompt.handle_input(&event) { + Ok(Some(cmd)) => self.run_command(cmd), + Ok(None) => { /* Not a key that was relevant for prompt. Do nothing. */ } + Err(err) => { + error!("Failed to parse command: {:?}", err); + } + } + } else { self.editor.handle_input(event); - return; } - // A command prompt is active. - let mut prompt = self.prompt.take().unwrap(); - match prompt.handle_input(&event) { - Ok(None) => { - self.prompt = Some(prompt); - } - Ok(Some(cmd)) => self.run_command(cmd), - Err(err) => { - error!("Failed to parse command: {:?}", err); - } - } } fn render(&mut self) -> Result<(), Error> { @@ -111,9 +99,8 @@ impl Tui { // but we render the editor for each editor-input as well :-) self.editor.render(self.terminal.stdout())?; - if let Some(ref mut prompt) = self.prompt { - prompt.render(self.terminal.stdout(), self.term_size.1)?; - } + // If its inactive, this will be a no-op + self.prompt.render(self.terminal.stdout(), self.term_size.1)?; if let Err(e) = self.terminal.stdout().flush() { error!("failed to flush stdout: {}", e); } diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index ed595b7..d7fa643 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,29 +6,43 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig, Keymap}; +use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig, ParserMap}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; #[derive(Debug, Copy, Clone, PartialEq)] pub enum CommandPromptMode { - // Parse commands from user-input + /// Do not display Prompt + Inactive, + /// Parse commands from user-input Command, - // Switch directly to search-mode + /// Switch directly to search-mode Find, } -#[derive(Debug)] pub struct CommandPrompt { mode: CommandPromptMode, dex: usize, chars: String, - keybindings: Keymap + prompt_texts: Vec, + parser_map: ParserMap, } impl CommandPrompt { - pub fn new(mode: CommandPromptMode, keybindings: Keymap) -> CommandPrompt { - CommandPrompt{mode, dex: 0, chars: Default::default(), keybindings} + pub fn new(mode: CommandPromptMode, parser_map: ParserMap) -> CommandPrompt { + let mut prompt_texts = Vec::new(); + for (key, parser) in &parser_map { + let keybinding = parser.keybinding.clone().unwrap_or(String::new()); + if parser.subcommands.is_empty() { + prompt_texts.push(format!("{} <{}>", key, &keybinding)); + } else { + for subcommand in &parser.subcommands { + prompt_texts.push(format!("{} {} <{}>", key, subcommand, &keybinding)); + } + } + // prompt_texts.push(format!("{} {} [{}]", key, ); + } + CommandPrompt{mode, dex: 0, chars: Default::default(), prompt_texts, parser_map} } /// Process a terminal event for the command prompt. @@ -85,10 +99,21 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { match self.mode { - CommandPromptMode::Find => Ok(Some(FindConfig::from_prompt(&self.chars)?)), - CommandPromptMode::Command => Ok(Some(Command::from_prompt(&self.chars)?)), + CommandPromptMode::Find => Ok(Some(FindConfig::from_prompt(Some(&self.chars))?)), + CommandPromptMode::Command => { + // Split first word off, search for it in the map and hand the rest to the from_prompt-command + let mut splitvec = self.chars.splitn(1, ' '); + let cmd_name = splitvec.next().unwrap(); // Should not panic + let add_args = splitvec.next(); + if let Some(parser) = self.parser_map.get::(&cmd_name) { + Ok(Some((parser.from_prompt)(add_args)?)) + } else { + Err(ParseCommandError::UnexpectedArgument) + } + }, + // Shouldn't happen + CommandPromptMode::Inactive => Err(ParseCommandError::UnexpectedArgument) } - } fn render_suggestions(&mut self, w: &mut W, row: u16) -> Result<(), Error> { @@ -96,15 +121,14 @@ impl CommandPrompt { return Ok(()) } - let vals : Vec<_> = self.keybindings.values().filter(|x| x.name.starts_with(&self.chars)).take(4).collect(); + let vals : Vec<_> = self.prompt_texts.iter().filter(|x| x.starts_with(&self.chars)).take(4).collect(); for (idx, val) in vals.iter().enumerate() { if let Err(err) = write!( w, - "{}{}-> {} [{}]", + "{}{}-> {}", Goto(1, row - 1 - idx as u16), ClearLine, - val.name, - val.keys, + val, ) { error!("failed to render status bar: {:?}", err); // TODO: Return error @@ -113,10 +137,19 @@ impl CommandPrompt { Ok(()) } + pub fn is_active(&self) -> bool { + self.mode != CommandPromptMode::Inactive + } + + pub fn set_mode(&mut self, mode: CommandPromptMode) { + self.mode = mode; + } + pub fn render(&mut self, w: &mut W, row: u16) -> Result<(), Error> { let mode_indicator; match self.mode { + CommandPromptMode::Inactive => {return Ok(());} CommandPromptMode::Find => { mode_indicator = "find"; diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index aef8026..d3239e1 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -11,7 +11,7 @@ use serde_json::Value; use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; -use crate::core::{Command, CoreEvent, KeybindingConfig}; +use crate::core::{Command, CoreEvent, KeyMap}; use crate::widgets::{View, ViewClient}; @@ -54,13 +54,13 @@ pub struct Editor { pub size: (u16, u16), pub styles: HashMap, - pub keybindings: KeybindingConfig, + pub keymap: KeyMap, clipboard: Option, } /// Methods for general use. impl Editor { - pub fn new(client: Client, keybindings: KeybindingConfig) -> Editor { + pub fn new(client: Client, keymap: KeyMap) -> Editor { let mut styles = HashMap::new(); styles.insert(0, Default::default()); let (xi_reply_tx, xi_reply_rx) = mpsc::unbounded::(); @@ -74,7 +74,7 @@ impl Editor { client, size: (0, 0), styles, - keybindings, + keymap, clipboard: None, } } @@ -141,8 +141,8 @@ impl Editor { match event { Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), ev => { - match self.keybindings.keymap.get(&ev).cloned() { - Some(cmd) => self.handle_command(cmd.command), + match self.keymap.get(&ev).cloned() { + Some(cmd) => self.handle_command(cmd), None => { if let Some(view) = self.views.get_mut(&self.current_view) { match ev { From 3790457b8174de3baf81a502426ac56b36ff293a Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 2 Jul 2019 09:48:50 +0200 Subject: [PATCH 32/36] Remove old, left-over comment --- src/widgets/editor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index d3239e1..58843a9 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -137,7 +137,6 @@ impl Future for Editor { impl Editor { /// Handle keyboard and mouse events pub fn handle_input(&mut self, event: Event) { - // We have to remove and insert again, to beat the borrow-checker match event { Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), ev => { From b1f795c866b19c5f542f0bb79dbd448921e7f7c1 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Tue, 2 Jul 2019 09:51:42 +0200 Subject: [PATCH 33/36] Adjust logging levels to be more sensible. --- src/core/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/config.rs b/src/core/config.rs index e6d5f5b..8a3188e 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -28,7 +28,7 @@ impl KeybindingConfig { // let entries = fs::read_to_string(config_path)?; // Read the JSON contents of the file as an instance of `User`. let bindings: Vec = json5::from_str(&DEFAULT_KEYBINDINGS)?; - error!("Bindings parsed!"); + debug!("Bindings parsed!"); let mut parser_map = get_parser_map(); let mut keymap = KeyMap::new(); let mut found_cmds = Vec::new(); @@ -51,7 +51,7 @@ impl KeybindingConfig { } if let Some(keyevent) = KeybindingConfig::parse_keys(&binding.keys) { - error!("{:?} = {:?}", cmd, binding); + info!("{:?} = {:?}", cmd, binding); keymap.insert(keyevent, cmd.clone()); parser.keybinding = Some(binding.keys[0].clone()); found_cmds.push(cmd); From e94e3e9d41c472ce466cca45c880c4360f8b7e77 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 3 Jul 2019 15:51:08 +0200 Subject: [PATCH 34/36] Make enum-names rusty and use serdes rename_all to use it for parsing. --- src/core/cmd.rs | 55 ++++++++++++++++++-------------------- src/widgets/view/client.rs | 18 ++++++------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index c20598a..137bdcf 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -212,26 +212,25 @@ pub trait FromPrompt { fn from_prompt(vals: Option<&str>) -> Result; } -#[allow(non_camel_case_types)] +#[serde(rename_all = "snake_case")] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum RelativeMoveDistance { /// Move only one character - characters, + Characters, /// Move a line - lines, + Lines, /// Move to new word - words, + Words, /// Move to end of word - word_ends, + WordEnds, /// Move to new subword - subwords, + Subwords, /// Move to end of subword - subword_ends, + SubwordEnds, /// Move a page - pages, + Pages, } -#[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub struct RelativeMove { pub by: RelativeMoveDistance, @@ -256,42 +255,42 @@ impl FromPrompt for RelativeMove { match vals[0] { "d" | "down" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::lines, + by: RelativeMoveDistance::Lines, forward: true, extend } )), "u" | "up" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::lines, + by: RelativeMoveDistance::Lines, forward: false, extend } )), "r" | "right" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::characters, + by: RelativeMoveDistance::Characters, forward: true, extend } )), "l" | "left" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::characters, + by: RelativeMoveDistance::Characters, forward: false, extend } )), "pd" | "page-down" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::pages, + by: RelativeMoveDistance::Pages, forward: true, extend } )), "pu" | "page-up" => Ok(Command::RelativeMove( RelativeMove{ - by: RelativeMoveDistance::pages, + by: RelativeMoveDistance::Pages, forward: false, extend } @@ -301,24 +300,23 @@ impl FromPrompt for RelativeMove { } } -#[allow(non_camel_case_types)] +#[serde(rename_all = "lowercase")] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum AbsoluteMovePoint { /// Beginning of file - bof, + BOF, /// End of file - eof, + EOF, /// Beginning of line - bol, + BOL, /// End of line - eol, + EOL, /// Enclosing brackets - brackets, + Brackets, /// Line number - line(u64) + Line(u64) } -#[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub struct AbsoluteMove { pub to: AbsoluteMovePoint, @@ -342,25 +340,25 @@ impl FromPrompt for AbsoluteMove { match vals[0] { "bof" | "beginning-of-file" => Ok(Command::AbsoluteMove( AbsoluteMove{ - to: AbsoluteMovePoint::bof, + to: AbsoluteMovePoint::BOF, extend } )), "eof" | "end-of-file" => Ok(Command::AbsoluteMove( AbsoluteMove{ - to: AbsoluteMovePoint::eof, + to: AbsoluteMovePoint::EOF, extend } )), "bol" | "beginning-of-line" => Ok(Command::AbsoluteMove( AbsoluteMove{ - to: AbsoluteMovePoint::bol, + to: AbsoluteMovePoint::BOL, extend } )), "eol" | "end-of-line" => Ok(Command::AbsoluteMove( AbsoluteMove{ - to: AbsoluteMovePoint::eol, + to: AbsoluteMovePoint::EOL, extend } )), @@ -369,7 +367,7 @@ impl FromPrompt for AbsoluteMove { let number = command.parse::().map_err(|_| ParseCommandError::UnknownCommand(command.into()))?; Ok(Command::AbsoluteMove( AbsoluteMove{ - to: AbsoluteMovePoint::line(number), + to: AbsoluteMovePoint::Line(number), extend: false } ) @@ -380,7 +378,6 @@ impl FromPrompt for AbsoluteMove { } } -#[allow(non_camel_case_types)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] pub struct ExpandLinesDirection { pub forward: bool diff --git a/src/widgets/view/client.rs b/src/widgets/view/client.rs index c66bdd9..4576d4d 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -48,28 +48,28 @@ impl Client { Command::FindPrev => self.find_prev(), Command::RelativeMove(x) => { match x.by { - RelativeMoveDistance::characters => { + RelativeMoveDistance::Characters => { if x.forward { self.right(x.extend) } else { self.left(x.extend) } }, - RelativeMoveDistance::words | RelativeMoveDistance::word_ends => { + RelativeMoveDistance::Words | RelativeMoveDistance::WordEnds => { if x.forward { self.word_right(x.extend) } else { self.word_left(x.extend) } }, - RelativeMoveDistance::pages => { + RelativeMoveDistance::Pages => { if x.forward { self.page_down(x.extend) } else { self.page_up(x.extend) } }, - RelativeMoveDistance::lines => { + RelativeMoveDistance::Lines => { if x.forward { self.down(x.extend) } else { @@ -81,11 +81,11 @@ impl Client { } Command::AbsoluteMove(x) => { match x.to { - AbsoluteMovePoint::bol => self.line_start(x.extend), - AbsoluteMovePoint::eol => self.line_end(x.extend), - AbsoluteMovePoint::bof => self.document_begin(x.extend), - AbsoluteMovePoint::eof => self.document_end(x.extend), - AbsoluteMovePoint::line(line) => self.goto_line(line), + AbsoluteMovePoint::BOL => self.line_start(x.extend), + AbsoluteMovePoint::EOL => self.line_end(x.extend), + AbsoluteMovePoint::BOF => self.document_begin(x.extend), + AbsoluteMovePoint::EOF => self.document_end(x.extend), + AbsoluteMovePoint::Line(line) => self.goto_line(line), _ => unimplemented!() } } From 7e1aea184102816f2840a89f14c8634d1db05072 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 3 Jul 2019 16:21:55 +0200 Subject: [PATCH 35/36] Prompt: Fix prompt parsing and close prompt after finalizing. --- src/widgets/command_prompt.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index d7fa643..689f23a 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -98,11 +98,11 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { - match self.mode { + let res = match self.mode { CommandPromptMode::Find => Ok(Some(FindConfig::from_prompt(Some(&self.chars))?)), CommandPromptMode::Command => { // Split first word off, search for it in the map and hand the rest to the from_prompt-command - let mut splitvec = self.chars.splitn(1, ' '); + let mut splitvec = self.chars.splitn(2, ' '); let cmd_name = splitvec.next().unwrap(); // Should not panic let add_args = splitvec.next(); if let Some(parser) = self.parser_map.get::(&cmd_name) { @@ -113,7 +113,10 @@ impl CommandPrompt { }, // Shouldn't happen CommandPromptMode::Inactive => Err(ParseCommandError::UnexpectedArgument) - } + }; + // Prompt was finalized. Close it now. + self.mode = CommandPromptMode::Inactive; + res } fn render_suggestions(&mut self, w: &mut W, row: u16) -> Result<(), Error> { From 75dff114d624f730717bc5ac62113025feca0904 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus Date: Wed, 3 Jul 2019 16:22:09 +0200 Subject: [PATCH 36/36] Fix prompt parsing of select_lines --- src/core/cmd.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/core/cmd.rs b/src/core/cmd.rs index 137bdcf..0aeaffb 100644 --- a/src/core/cmd.rs +++ b/src/core/cmd.rs @@ -131,7 +131,7 @@ pub fn get_parser_map() -> ParserMap { let cmd : AbsoluteMove = serde_json::from_value(args).map_err(|_| ParseCommandError::UnexpectedArgument)?; Ok(Command::AbsoluteMove(cmd))})}); map.insert("select_lines", CommandParser{ keybinding: None, - from_prompt: AbsoluteMove::from_prompt, + from_prompt: ExpandLinesDirection::from_prompt, subcommands: vec!["above", "below"], from_keymap_entry: Some(|val| { let args = val.args.ok_or(ParseCommandError::ExpectedArgument{cmd: "select_lines".to_string()})?; @@ -383,6 +383,22 @@ pub struct ExpandLinesDirection { pub forward: bool } +impl FromPrompt for ExpandLinesDirection { + fn from_prompt(args: Option<&str>) -> Result { + let arg = args.ok_or(ParseCommandError::ExpectedArgument{cmd: "select_lines".to_string()})?; + match arg { + "a" | "above" => Ok(Command::CursorExpandLines( + ExpandLinesDirection{forward: false} + )), + "b" | "below" => Ok(Command::CursorExpandLines( + ExpandLinesDirection{forward: true} + )), + command => Err(ParseCommandError::UnknownCommand(command.into())) + } + } +} + + #[derive(Debug, Clone, PartialEq)] pub struct FindConfig { pub search_term: String,