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/Cargo.lock b/Cargo.lock index b0f3fa8..ceb978f 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,26 @@ 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 = "shellexpand" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "signal-hook" version = "0.1.9" @@ -869,8 +992,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 +1259,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,12 +1361,16 @@ 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)", + "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)", - "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,13 +1381,13 @@ 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)", "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 +1415,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 +1432,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 +1459,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 +1475,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 +1509,13 @@ 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 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" @@ -1396,6 +1548,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" @@ -1410,5 +1564,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" diff --git a/Cargo.toml b/Cargo.toml index b350d8f..064326f 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" @@ -14,3 +15,7 @@ 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" +shellexpand = "1.0" diff --git a/configs/Default (Linux).sublime-keymap b/configs/Default (Linux).sublime-keymap new file mode 100644 index 0000000..db5aa5e --- /dev/null +++ b/configs/Default (Linux).sublime-keymap @@ -0,0 +1,740 @@ +[ + { "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": "^; + +#[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 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: 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()})?; + 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; +} + +#[serde(rename_all = "snake_case")] +#[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 + WordEnds, + /// Move to new subword + Subwords, + /// Move to end of subword + SubwordEnds, + /// Move a page + Pages, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct RelativeMove { + pub by: RelativeMoveDistance, + pub forward: bool, + #[serde(default)] + pub extend: bool +} + +impl FromPrompt for RelativeMove { + 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()}); + } + + 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())) + } + } +} + +#[serde(rename_all = "lowercase")] +#[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) +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct AbsoluteMove { + pub to: AbsoluteMovePoint, + #[serde(default)] + pub extend: bool +} + +impl FromPrompt for AbsoluteMove { + 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()}); + } + + 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 => { + let number = command.parse::().map_err(|_| ParseCommandError::UnknownCommand(command.into()))?; + Ok(Command::AbsoluteMove( + AbsoluteMove{ + to: AbsoluteMovePoint::Line(number), + extend: false + } + ) + ) + } + } + + } +} + +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +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, + pub case_sensitive: bool, + pub regex: bool, + pub whole_words: bool, +} + +impl FromPrompt for FindConfig { + 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()}) + } + + 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. Cancel, @@ -23,22 +477,42 @@ 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), + /// Change current color theme SetTheme(String), /// Toggle displaying line numbers. ToggleLineNumbers, + /// Open prompt for user-input + OpenPrompt(CommandPromptMode), + /// Insert a character + Insert(char), + /// Undo last action + Undo, + /// Redo last undone action + Redo, + /// Find the given string + Find(FindConfig), + /// 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 + CursorExpandLines(ExpandLinesDirection), + /// Copy the current selection + CopySelection, + /// Paste previously copied or cut text + Paste, + /// Copy the current selection + CutSelection, + /// Close the current view + CloseCurrentView, + /// Select all text in the current view + SelectAll, } #[derive(Debug)] @@ -48,8 +522,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 { @@ -60,59 +534,3 @@ pub enum ParseCommandError { /// Invalid input was received. UnknownCommand(String), } - -impl FromStr for Command { - type Err = ParseCommandError; - - fn from_str(s: &str) -> 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), - "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), - "ln" | "line-numbers" => Ok(Command::ToggleLineNumbers), - command => { - let mut parts: Vec<&str> = command.split(' ').collect(); - - 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())), - } - } - } - } -} diff --git a/src/core/config.rs b/src/core/config.rs new file mode 100644 index 0000000..8a3188e --- /dev/null +++ b/src/core/config.rs @@ -0,0 +1,157 @@ +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; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct KeymapEntry { + pub keys: Vec, + pub command: String, + // For now, unstructured value + pub args: Option, + pub context: Option, +} + +#[derive(Clone)] +pub struct KeybindingConfig { + pub keymap: KeyMap, + pub parser_map: ParserMap, + // pub config_path: PathBuf +} + +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)?; + debug!("Bindings parsed!"); + let mut parser_map = get_parser_map(); + let mut keymap = KeyMap::new(); + let mut found_cmds = Vec::new(); + for binding in bindings { + 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, + }; + + // Multiple command (this is a hack and needs some thought how to do it right) + if found_cmds.contains(&cmd) { + continue; + } + + if let Some(keyevent) = KeybindingConfig::parse_keys(&binding.keys) { + info!("{:?} = {:?}", 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, parser_map}) + } + + 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)), + "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])), + "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])), + "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)), + + 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))) + } + } + + _ => { + // warn!("Completely unknown argument {}", x); + None + }, + } + } +} 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": "^, + prompt: CommandPrompt, /// The terminal is used to draw on the screen a get inputs from /// the user. @@ -36,13 +36,13 @@ pub struct Tui { 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, term_size: (0, 0), - editor: Editor::new(client), - 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, }) } @@ -54,71 +54,53 @@ impl Tui { pub fn run_command(&mut self, cmd: Command) { match cmd { - Command::Cancel => { - self.prompt = None; - } + // We handle these here, the rest is the job of the editor + 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, - 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::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::ToggleLineNumbers => self.editor.toggle_line_numbers(), + + editor_cmd => self.editor.handle_command(editor_cmd) } } /// 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.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_active() => { self.prompt.set_mode(CommandPromptMode::Inactive); 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_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); } + } fn render(&mut self) -> Result<(), Error> { - if let Some(ref mut prompt) = self.prompt { - prompt.render(self.terminal.stdout(), self.term_size.1)?; - } else { - self.editor.render(self.terminal.stdout())?; - } + // 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 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/main.rs b/src/main.rs index e9760c8..d6249e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,18 +5,15 @@ #[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; +#[macro_use] +extern crate serde_json; + +use log4rs; +use tokio; +use xrl; mod core; mod widgets; @@ -29,7 +26,7 @@ use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Config, Logger, Root}; use xrl::spawn; -use core::{Command, Tui, TuiServiceBuilder}; +use crate::core::{Command, Tui, TuiServiceBuilder, KeybindingConfig}; fn configure_logs(logfile: &str) { let tui = FileAppender::builder().build(logfile).unwrap(); @@ -93,6 +90,10 @@ 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)?; + let keybindings = KeybindingConfig::parse().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,12 +123,12 @@ 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), )); - 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)) }) })); diff --git a/src/widgets/command_prompt.rs b/src/widgets/command_prompt.rs index f3a6a0a..689f23a 100644 --- a/src/widgets/command_prompt.rs +++ b/src/widgets/command_prompt.rs @@ -6,19 +6,45 @@ use std::io::Error; use std::io::Write; use termion::event::{Event, Key}; -use core::{Command, ParseCommandError}; +use crate::core::{Command, ParseCommandError, FromPrompt, FindConfig, ParserMap}; use termion::clear::CurrentLine as ClearLine; use termion::cursor::Goto; -use std::str::FromStr; +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CommandPromptMode { + /// Do not display Prompt + Inactive, + /// Parse commands from user-input + Command, + /// Switch directly to search-mode + Find, +} -#[derive(Debug, Default)] pub struct CommandPrompt { + mode: CommandPromptMode, dex: usize, chars: String, + prompt_texts: Vec, + parser_map: ParserMap, } impl CommandPrompt { + 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. pub fn handle_input(&mut self, input: &Event) -> Result, ParseCommandError> { match input { @@ -72,19 +98,94 @@ impl CommandPrompt { /// Gets called when return is pressed, fn finalize(&mut self) -> Result, ParseCommandError> { - Ok(Some(FromStr::from_str(&self.chars)?)) + 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(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) { + Ok(Some((parser.from_prompt)(add_args)?)) + } else { + Err(ParseCommandError::UnexpectedArgument) + } + }, + // 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> { + if self.chars.is_empty() { + return Ok(()) + } + + 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, + ) { + error!("failed to render status bar: {:?}", err); + // TODO: Return error + } + } + 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"; + + // 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, + ) { + error!("failed to render status bar: {:?}", err); + } + } + CommandPromptMode::Command => { + mode_indicator = ""; + self.render_suggestions(w, row)?; + }, + }; + + 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/editor.rs b/src/widgets/editor.rs index 78a620a..58843a9 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -6,22 +6,31 @@ use futures::{Async, Future, Poll, Stream}; use failure::Error; use indexmap::IndexMap; -use termion::event::Event as TermionEvent; +use termion::event::{Event, Key}; +use serde_json::Value; + use xrl::{Client, ConfigChanged, ScrollTo, Style, Update, ViewId, XiNotification}; -use core::CoreEvent; -use widgets::{View, ViewClient}; +use crate::core::{Command, CoreEvent, KeyMap}; + +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. /// @@ -44,24 +53,29 @@ pub struct Editor { pub size: (u16, u16), pub styles: HashMap, + + pub keymap: KeyMap, + clipboard: Option, } /// Methods for general use. impl Editor { - pub fn new(client: Client) -> Editor { + pub fn new(client: Client, keymap: KeyMap) -> 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), client, size: (0, 0), styles, + keymap, + clipboard: None, } } } @@ -88,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); @@ -98,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) => { @@ -105,7 +125,7 @@ impl Future for Editor { break; } Err(e) => { - error!("Uknown channel error: {:?}", e); + error!("Unkown channel error: {:?}", e); return Err(()); } } @@ -116,9 +136,41 @@ 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) { + match event { + Event::Mouse(mouse_event) => self.views.get_mut(&self.current_view).unwrap().handle_mouse_event(mouse_event), + ev => { + 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 { + Event::Key(Key::Char(c)) => view.handle_command(Command::Insert(c)), + k => error!("un-handled key {:?}", k) + } + } + } + } + }, + } + } + + pub fn handle_command(&mut self, cmd: Command) { + match cmd { + 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::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) + } + } } } @@ -182,10 +234,94 @@ 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 "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()) @@ -193,7 +329,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(()) }) @@ -218,18 +354,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() { @@ -253,48 +377,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 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/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 41a3ae3..4576d4d 100644 --- a/src/widgets/view/client.rs +++ b/src/widgets/view/client.rs @@ -1,6 +1,9 @@ use futures::Future; use tokio::spawn; use xrl; +use serde_json::Value; + +use crate::core::{Command, RelativeMoveDistance, AbsoluteMovePoint, FindConfig}; pub struct Client { inner: xrl::Client, @@ -15,6 +18,151 @@ 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::CopySelection => { /* Handled by Editor */ }, + Command::ToggleLineNumbers => { /* Handled by View */ }, + Command::FindUnderExpand => { /* 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(), + Command::Insert('\t') => self.insert_tab(), + Command::Insert(c) => self.insert(c), + Command::Undo => self.undo(), + Command::Redo => self.redo(), + 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 => { + if x.forward { + self.right(x.extend) + } else { + self.left(x.extend) + } + }, + RelativeMoveDistance::Words | RelativeMoveDistance::WordEnds => { + if x.forward { + self.word_right(x.extend) + } else { + self.word_left(x.extend) + } + }, + RelativeMoveDistance::Pages => { + if x.forward { + self.page_down(x.extend) + } else { + self.page_up(x.extend) + } + }, + RelativeMoveDistance::Lines => { + if x.forward { + self.down(x.extend) + } else { + self.up(x.extend) + } + }, + _ => unimplemented!() + } + } + 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), + _ => unimplemented!() + } + } + } + } + + 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.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); + } + + 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); + } + + 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) + .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 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); + } + + 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); @@ -35,43 +183,128 @@ 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) { - 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 page_down(&mut self) { - let f = self.inner.page_down(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 page_up(&mut self) { - let f = self.inner.page_up(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 home(&mut self) { - let f = self.inner.line_start(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, 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, 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 end(&mut self) { - let f = self.inner.line_end(self.view_id).map_err(|_| ()); + 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 goto_line(&mut self, line: u64) { + let f = self.inner.goto_line(self.view_id, line).map_err(|_| ()); spawn(f); } @@ -80,7 +313,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); } @@ -90,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 @@ -98,6 +343,15 @@ impl Client { spawn(f); } + 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 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 7a53c53..a720ce3 100644 --- a/src/widgets/view/view.rs +++ b/src/widgets/view/view.rs @@ -1,12 +1,16 @@ use std::cmp::max; use std::collections::HashMap; use std::io::Write; +use futures::future::Future; 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 serde_json::Value; + +use crate::core::Command; use super::cfg::ViewConfig; use super::client::Client; @@ -26,6 +30,8 @@ pub struct View { file: Option, client: Client, cfg: ViewConfig, + + search_in_progress: bool, } impl View { @@ -37,6 +43,7 @@ impl View { cfg: ViewConfig::default(), client, file, + search_in_progress: false, } } @@ -75,54 +82,10 @@ 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()) } - 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 toggle_line_numbers(&mut self) { self.cfg.display_gutter = !self.cfg.display_gutter; } @@ -182,47 +145,57 @@ 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); } - 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), - }, - 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), + 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 paste(&mut self, text: &str) { + self.client.paste(text) + } + + pub fn copy(&mut self) -> impl Future { + self.client.copy() + } + + pub fn cut(&mut self) -> impl Future { + self.client.cut() + } + + 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), + } + } + + 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::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), }, - ev => error!("un-handled event {:?}", ev), + MouseEvent::Release(..) => {} + MouseEvent::Hold(y, x) => self.drag(u64::from(x) - 1, u64::from(y) - 1), } }