From ddca44e02b700660225ae8f048b1c168a4cd62b8 Mon Sep 17 00:00:00 2001 From: mattwparas Date: Sun, 7 May 2023 21:53:19 -0700 Subject: [PATCH 001/124] work in progress adding of steel --- Cargo.lock | 593 ++++++++++++++++++++++++++++-- helix-loader/src/lib.rs | 4 + helix-term/Cargo.toml | 3 + helix-term/src/application.rs | 6 + helix-term/src/commands.rs | 65 +++- helix-term/src/commands/engine.rs | 327 ++++++++++++++++ helix-term/src/commands/typed.rs | 74 +++- helix-term/src/config.rs | 30 +- helix-term/src/keymap.rs | 3 + helix-term/src/main.rs | 3 + helix.scm | 178 +++++++++ 11 files changed, 1252 insertions(+), 34 deletions(-) create mode 100644 helix-term/src/commands/engine.rs create mode 100644 helix.scm diff --git a/Cargo.lock b/Cargo.lock index d8a07f41bd34..fc57a4fee183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,12 +70,44 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -88,6 +120,15 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "bstr" version = "1.4.0" @@ -121,6 +162,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.4.0" @@ -163,8 +210,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", + "js-sys", "num-integer", "num-traits", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -185,6 +235,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" +[[package]] +name = "codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d" +dependencies = [ + "indexmap", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -195,6 +254,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "content_inspector" version = "0.2.4" @@ -204,6 +274,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "coolor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7a805ca0d92f8c61a31c809d4323fdaa939b0b440e544d21db7797c5aaad" +dependencies = [ + "crossterm 0.23.2", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -219,6 +298,89 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossterm" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm" version = "0.26.1" @@ -266,8 +428,8 @@ dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "scratch", "syn 2.0.15", ] @@ -284,11 +446,21 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 2.0.15", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -310,6 +482,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlopen" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" +dependencies = [ + "dlopen_derive", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "dlopen_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" +dependencies = [ + "libc", + "quote 0.6.13", + "syn 0.15.44", +] + [[package]] name = "dunce" version = "1.0.4" @@ -436,6 +631,31 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.28" @@ -453,6 +673,29 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2 1.0.56", + "quote 1.0.26", + "syn 2.0.15", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + [[package]] name = "futures-task" version = "0.3.28" @@ -465,8 +708,13 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -481,6 +729,15 @@ dependencies = [ "thread_local", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "getrandom" version = "0.2.9" @@ -489,7 +746,7 @@ checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -655,7 +912,7 @@ dependencies = [ "bstr", "itoa", "thiserror", - "time", + "time 0.3.20", ] [[package]] @@ -1087,6 +1344,16 @@ dependencies = [ "memmap2", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.6", + "serde", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1203,7 +1470,7 @@ dependencies = [ "arc-swap", "chrono", "content_inspector", - "crossterm", + "crossterm 0.26.1", "fern", "futures-util", "fuzzy-matcher", @@ -1227,6 +1494,7 @@ dependencies = [ "signal-hook", "signal-hook-tokio", "smallvec", + "steel-core", "tempfile", "tokio", "tokio-stream", @@ -1240,7 +1508,7 @@ version = "0.6.0" dependencies = [ "bitflags 2.2.1", "cassowary", - "crossterm", + "crossterm 0.26.1", "helix-core", "helix-view", "log", @@ -1274,7 +1542,7 @@ dependencies = [ "bitflags 2.2.1", "chardetng", "clipboard-win", - "crossterm", + "crossterm 0.26.1", "futures-util", "helix-core", "helix-dap", @@ -1296,6 +1564,15 @@ dependencies = [ "which", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1377,6 +1654,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "im-lists" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f93ebe9d5265409edc0b5c2ebd96bf7dcd4125c1626bff0ece34b9300e490a" + +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "imara-diff" version = "0.1.5" @@ -1433,6 +1730,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1457,6 +1763,17 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lasso" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0" +dependencies = [ + "dashmap", + "hashbrown 0.11.2", + "serde", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1513,6 +1830,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2 1.0.56", + "quote 1.0.26", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + [[package]] name = "lsp-types" version = "0.94.0" @@ -1541,6 +1881,24 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimad" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1b13e2000bd8e238d97a97de6fc30224f89a08b0aa5aaa09ed1bd68ba2fa1" +dependencies = [ + "once_cell", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1564,7 +1922,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] @@ -1663,6 +2021,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pretty" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" +dependencies = [ + "arrayvec", + "log", + "typed-arena", + "unicode-segmentation", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + [[package]] name = "proc-macro2" version = "1.0.56" @@ -1698,13 +2077,32 @@ dependencies = [ "rand", ] +[[package]] +name = "quickscope" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d47bcfc3e13850589cf9338a02b6dfb5aebb3748a0f93a392e8df91d6193b6b" +dependencies = [ + "indexmap", + "smallvec", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.56", ] [[package]] @@ -1725,6 +2123,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1849,8 +2256,8 @@ version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 2.0.15", ] @@ -1871,8 +2278,8 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 2.0.15", ] @@ -1933,6 +2340,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.8" @@ -1990,6 +2407,60 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "steel-core" +version = "0.2.0" +dependencies = [ + "anyhow", + "bincode", + "chrono", + "codespan-reporting", + "colored", + "dlopen", + "dlopen_derive", + "futures", + "fxhash", + "im-lists", + "im-rc", + "itertools", + "lasso", + "lazy_static", + "log", + "logos", + "once_cell", + "pretty", + "quickscope", + "serde", + "serde_derive", + "serde_json", + "slotmap", + "smallvec", + "steel-gen", + "steel-parser", + "termimad", + "thiserror", + "weak-table", +] + +[[package]] +name = "steel-gen" +version = "0.2.0" +dependencies = [ + "codegen", + "itertools", + "serde", + "serde_derive", +] + +[[package]] +name = "steel-parser" +version = "0.2.0" +dependencies = [ + "logos", + "serde", + "serde_derive", +] + [[package]] name = "str-buf" version = "1.0.6" @@ -2002,14 +2473,25 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid", +] + [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "unicode-ident", ] @@ -2019,8 +2501,8 @@ version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "unicode-ident", ] @@ -2046,6 +2528,20 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termimad" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8743d628f9b0eb33087c1e1c4915d91efca23ae69f7c81981489128a0e17d300" +dependencies = [ + "coolor", + "crossbeam", + "crossterm 0.23.2", + "minimad", + "thiserror", + "unicode-width", +] + [[package]] name = "termini" version = "0.1.4" @@ -2081,8 +2577,8 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 2.0.15", ] @@ -2105,6 +2601,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.20" @@ -2174,8 +2681,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 2.0.15", ] @@ -2234,6 +2741,18 @@ dependencies = [ "regex", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "unicase" version = "2.6.0" @@ -2298,6 +2817,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "url" version = "2.3.1" @@ -2326,6 +2851,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2351,8 +2882,8 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 1.0.109", "wasm-bindgen-shared", ] @@ -2363,7 +2894,7 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ - "quote", + "quote 1.0.26", "wasm-bindgen-macro-support", ] @@ -2373,8 +2904,8 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.56", + "quote 1.0.26", "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -2386,6 +2917,12 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + [[package]] name = "which" version = "4.4.0" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 6c7169758df0..f4f03dd5286a 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -128,6 +128,10 @@ pub fn config_file() -> PathBuf { .unwrap_or_else(|| config_dir().join("config.toml")) } +pub fn helix_module_file() -> PathBuf { + config_dir().join("helix.scm") +} + pub fn workspace_config_file() -> PathBuf { find_workspace().0.join(".helix").join("config.toml") } diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 7fb6b890a0da..116522d7b603 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -66,6 +66,9 @@ serde = { version = "1.0", features = ["derive"] } grep-regex = "0.1.11" grep-searcher = "0.1.11" +# plugin support +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow"] } + [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } libc = "0.2.142" diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b54d6835a5af..7ae603519d9d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -249,6 +249,11 @@ impl Application { last_render: Instant::now(), }; + // // Initialize the engine before we boot up! + // crate::commands::ENGINE.with(|x| { + // let _ = x.borrow().globals(); + // }); + Ok(app) } @@ -416,6 +421,7 @@ impl Application { Ok(()) } + // TODO: @Matt - consider querying the engine for keybindings fn refresh_config(&mut self) { let mut refresh_config = || -> Result<(), Error> { let default_config = Config::load_default() diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 88393ff4cd8d..fe2fcb6fed11 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,10 +1,14 @@ pub(crate) mod dap; +pub(crate) mod engine; pub(crate) mod lsp; pub(crate) mod typed; pub use dap::*; use helix_vcs::Hunk; pub use lsp::*; + +pub use engine::initialize_engine; +use steel::rvals::IntoSteelVal; use tokio::sync::oneshot; use tui::widgets::Row; pub use typed::*; @@ -184,6 +188,7 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { + // TODO: @Matt - Add delegating to the engine to run scripts here Self::Typable { name, args, doc: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { @@ -195,6 +200,33 @@ impl MappableCommand { if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) { cx.editor.set_error(format!("{}", e)); } + } else if ENGINE.with(|x| x.borrow().global_exists(name)) { + let args = steel::List::from( + args.iter() + .map(|x| x.clone().into_steelval().unwrap()) + .collect::>(), + ); + + if let Err(e) = ENGINE.with(|x| { + let mut guard = x.borrow_mut(); + + { + guard + .register_value("_helix_args", steel::rvals::SteelVal::ListV(args)); + + let res = guard.run_with_reference::( + cx, + "*context*", + &format!("(apply {} (cons *context* _helix_args))", name), + ); + + guard.register_value("_helix_args", steel::rvals::SteelVal::Void); + + res + } + }) { + cx.editor.set_error(format!("{}", e)); + } } } Self::Static { fun, .. } => (fun)(cx), @@ -514,7 +546,24 @@ impl std::str::FromStr for MappableCommand { .map(|cmd| MappableCommand::Typable { name: cmd.name.to_owned(), doc: format!(":{} {:?}", cmd.name, args), - args, + args: args.clone(), + }) + .or_else(|| { + if let Some(doc) = self::engine::ExportedIdentifiers::engine_get_doc(name) { + Some(MappableCommand::Typable { + name: name.to_owned(), + args, + doc, + }) + } else if self::engine::ExportedIdentifiers::is_exported(name) { + Some(MappableCommand::Typable { + name: name.to_owned(), + args, + doc: "plugin function".to_string(), + }) + } else { + None + } }) .ok_or_else(|| anyhow!("No TypableCommand named '{}'", s)) } else { @@ -593,6 +642,8 @@ fn move_impl(cx: &mut Context, move_fn: MoveFn, dir: Direction, behaviour: Movem use helix_core::movement::{move_horizontally, move_vertically}; +use self::engine::ENGINE; + fn move_char_left(cx: &mut Context) { move_impl(cx, move_horizontally, Direction::Backward, Movement::Move) } @@ -3287,6 +3338,18 @@ pub mod insert { } } + pub fn insert_string(cx: &mut Context, string: String) { + let (view, doc) = current!(cx.editor); + + let indent = Tendril::from(string); + let transaction = Transaction::insert( + doc.text(), + &doc.selection(view.id).clone().cursors(doc.text().slice(..)), + indent, + ); + doc.apply(&transaction, view.id); + } + pub fn insert_tab(cx: &mut Context) { let (view, doc) = current!(cx.editor); // TODO: round out to nearest indentation level (for example a line with 3 spaces should diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs new file mode 100644 index 000000000000..324c7d8af56d --- /dev/null +++ b/helix-term/src/commands/engine.rs @@ -0,0 +1,327 @@ +use fuzzy_matcher::FuzzyMatcher; +use helix_core::{graphemes, Tendril}; +use helix_view::{document::Mode, Editor}; +use once_cell::sync::Lazy; +use steel::{ + gc::unsafe_erased_pointers::CustomReference, + rvals::{IntoSteelVal, SteelString}, + steel_vm::register_fn::RegisterFn, +}; + +use std::{ + borrow::Cow, + collections::{HashMap, VecDeque}, + sync::Mutex, +}; +use std::{ + collections::HashSet, + sync::{Arc, RwLock}, +}; + +use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule}; + +use crate::{ + compositor::{self, Compositor}, + job::{self, Callback}, + keymap::{merge_keys, Keymap}, + ui::{self, Popup, PromptEvent}, +}; + +use super::{ + insert::{insert_char, insert_string}, + Context, MappableCommand, TYPABLE_COMMAND_LIST, +}; + +thread_local! { + pub static ENGINE: std::rc::Rc> = configure_engine(); +} + +pub fn initialize_engine() { + ENGINE.with(|x| x.borrow().globals().first().copied()); +} + +pub static KEYBINDING_QUEUE: Lazy = + Lazy::new(|| SharedKeyBindingsEventQueue::new()); + +pub static EXPORTED_IDENTIFIERS: Lazy = + Lazy::new(|| ExportedIdentifiers::default()); + +/// In order to send events from the engine back to the configuration, we can created a shared +/// queue that the engine and the config push and pull from. Alternatively, we could use a channel +/// directly, however this was easy enough to set up. +pub struct SharedKeyBindingsEventQueue { + raw_bindings: Arc>>, +} + +impl SharedKeyBindingsEventQueue { + pub fn new() -> Self { + Self { + raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(VecDeque::new())), + } + } + + pub fn merge(other_as_json: String) { + KEYBINDING_QUEUE + .raw_bindings + .lock() + .unwrap() + .push_back(other_as_json); + } + + pub fn get() -> Option> { + let mut guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap(); + + if let Some(initial) = guard.pop_front() { + let mut initial = serde_json::from_str(&initial).unwrap(); + + while let Some(remaining_event) = guard.pop_front() { + let bindings = serde_json::from_str(&remaining_event).unwrap(); + + merge_keys(&mut initial, bindings); + } + + return Some(initial); + } + + None + } +} + +impl Custom for PromptEvent {} + +impl<'a> CustomReference for Context<'a> {} + +fn configure_engine() -> std::rc::Rc> { + let mut engine = steel::steel_vm::engine::Engine::new(); + + let mut module = BuiltInModule::new("helix/core/keybindings".to_string()); + module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge); + + engine.register_module(module); + + let mut module = BuiltInModule::new("helix/core/typable".to_string()); + + module.register_value( + "PromptEvent::Validate", + PromptEvent::Validate.into_steelval().unwrap(), + ); + + // Register everything in the typable command list. Now these are all available + for command in TYPABLE_COMMAND_LIST { + let func = |cx: &mut Context, args: &[Cow], event: PromptEvent| { + let mut cx = compositor::Context { + editor: cx.editor, + scroll: None, + jobs: cx.jobs, + }; + + (command.fun)(&mut cx, args, event) + }; + + module.register_fn(command.name, func); + } + + engine.register_module(module); + + let mut module = BuiltInModule::new("helix/core/static".to_string()); + + // Register everything in the static command list as well + // These just accept the context, no arguments + for command in MappableCommand::STATIC_COMMAND_LIST { + if let MappableCommand::Static { name, fun, .. } = command { + module.register_fn(name, fun); + } + } + + module.register_fn("insert_char", insert_char); + module.register_fn("insert_string", insert_string); + module.register_fn("current_selection", get_selection); + module.register_fn("current-highlighted-text!", get_highlighted_text); + module.register_fn("run-in-engine!", run_in_engine); + module.register_fn("get-helix-scm-path", get_helix_scm_path); + + engine.register_module(module); + + let helix_module_path = helix_loader::helix_module_file(); + + engine + .run(&format!( + r#"(require "{}")"#, + helix_module_path.to_str().unwrap() + )) + .unwrap(); + + // __module-mangler/home/matt/Documents/steel/cogs/logging/log.scm + + // TODO: Use the helix.scm file located in the configuration directory instead + // let mut working_directory = std::env::current_dir().unwrap(); + + // working_directory.push("helix.scm"); + + // working_directory = working_directory.canonicalize().unwrap(); + + let helix_path = + "__module-mangler".to_string() + helix_module_path.as_os_str().to_str().unwrap(); + + let module = engine.extract_value(&helix_path).unwrap(); + + if let steel::rvals::SteelVal::HashMapV(m) = module { + let exported = m + .iter() + .filter(|(_, v)| v.is_function()) + .map(|(k, _)| { + if let steel::rvals::SteelVal::SymbolV(s) = k { + s.to_string() + } else { + panic!("Found a non symbol!") + } + }) + .collect::>(); + + *EXPORTED_IDENTIFIERS.identifiers.write().unwrap() = exported; + + let docs = m + .iter() + .filter_map(|(k, v)| { + if let steel::rvals::SteelVal::SymbolV(s) = k { + if s.ends_with("__doc__") { + if let steel::rvals::SteelVal::StringV(d) = v { + return Some(( + s.strip_suffix("__doc__").unwrap().to_string(), + d.to_string(), + )); + } + } + } + + None + }) + .collect::>(); + + *EXPORTED_IDENTIFIERS.docs.write().unwrap() = docs; + } else { + panic!("Unable to parse exported identifiers from helix module!") + } + + std::rc::Rc::new(std::cell::RefCell::new(engine)) +} + +#[derive(Default, Debug)] +pub struct ExportedIdentifiers { + identifiers: Arc>>, + docs: Arc>>, +} + +impl ExportedIdentifiers { + pub(crate) fn fuzzy_match<'a>( + fuzzy_matcher: &'a fuzzy_matcher::skim::SkimMatcherV2, + input: &'a str, + ) -> Vec<(String, i64)> { + EXPORTED_IDENTIFIERS + .identifiers + .read() + .unwrap() + .iter() + .filter_map(|name| { + fuzzy_matcher + .fuzzy_match(name, input) + .map(|score| (name, score)) + }) + .map(|x| (x.0.to_string(), x.1)) + .collect::>() + } + + pub(crate) fn is_exported(ident: &str) -> bool { + EXPORTED_IDENTIFIERS + .identifiers + .read() + .unwrap() + .contains(ident) + } + + pub(crate) fn engine_get_doc(ident: &str) -> Option { + EXPORTED_IDENTIFIERS.get_doc(ident) + } + + fn get_doc(&self, ident: &str) -> Option { + self.docs.read().unwrap().get(ident).cloned() + } +} + +fn get_highlighted_text(cx: &mut Context) -> String { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text().slice(..); + doc.selection(view.id).primary().slice(text).to_string() +} + +fn get_selection(cx: &mut Context) -> String { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text().slice(..); + + let grapheme_start = doc.selection(view.id).primary().cursor(text); + let grapheme_end = graphemes::next_grapheme_boundary(text, grapheme_start); + + if grapheme_start == grapheme_end { + return "".into(); + } + + let grapheme = text.slice(grapheme_start..grapheme_end).to_string(); + + let printable = grapheme.chars().fold(String::new(), |mut s, c| { + match c { + '\0' => s.push_str("\\0"), + '\t' => s.push_str("\\t"), + '\n' => s.push_str("\\n"), + '\r' => s.push_str("\\r"), + _ => s.push(c), + } + + s + }); + + printable +} + +fn run_in_engine(cx: &mut Context, arg: String) -> anyhow::Result<()> { + let callback = async move { + let output = ENGINE + .with(|x| x.borrow_mut().run(&arg)) + .map(|x| format!("{:?}", x)); + + let (output, success) = match output { + Ok(v) => (Tendril::from(v), true), + Err(e) => (Tendril::from(e.to_string()), false), + }; + + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + if !output.is_empty() { + let contents = ui::Markdown::new( + format!("```\n{}\n```", output), + editor.syn_loader.clone(), + ); + let popup = Popup::new("engine", contents).position(Some( + helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), + )); + compositor.replace_or_push("engine", popup); + } + if success { + editor.set_status("Command succeeded"); + } else { + editor.set_error("Command failed"); + } + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); + + Ok(()) +} + +fn get_helix_scm_path() -> String { + helix_loader::helix_module_file() + .to_str() + .unwrap() + .to_string() +} diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index fe92798baae6..bd42194e60b0 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,4 +1,3 @@ -use std::fmt::Write; use std::ops::Deref; use crate::job::Job; @@ -11,6 +10,8 @@ use helix_view::editor::{Action, CloseError, ConfigEvent}; use serde_json::Value; use ui::completers::{self, Completer}; +use std::fmt::Write; + #[derive(Clone)] pub struct TypableCommand { pub name: &'static str, @@ -2745,7 +2746,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: run_shell_command, signature: CommandSignature::all(completers::filename) }, - TypableCommand { + TypableCommand { name: "reset-diff-change", aliases: &["diffget", "diffg"], doc: "Reset the diff change at the cursor position.", @@ -2785,14 +2786,23 @@ pub(super) fn command_mode(cx: &mut Context) { let words = shellwords.words(); if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) { + let globals = crate::commands::engine::ExportedIdentifiers::fuzzy_match( + &FUZZY_MATCHER, + input, + ) + .into_iter() + .map(|x| (Cow::from(x.0), x.1)) + .collect::>(); + // If the command has not been finished yet, complete commands. let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST .iter() .filter_map(|command| { FUZZY_MATCHER .fuzzy_match(command.name, input) - .map(|score| (command.name, score)) + .map(|score| (Cow::from(command.name), score)) }) + .chain(globals) .collect(); matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score)); @@ -2848,14 +2858,60 @@ pub(super) fn command_mode(cx: &mut Context) { return; } + // TODO: @Matt - Add completion for added scripting commands here // Handle typable commands if let Some(cmd) = typed::TYPABLE_COMMAND_MAP.get(parts[0]) { let shellwords = Shellwords::from(input); let args = shellwords.words(); + log::warn!("calling builtin: {}...", parts[0]); + if let Err(e) = (cmd.fun)(cx, &args[1..], event) { cx.editor.set_error(format!("{}", e)); } + } else if ENGINE.with(|x| x.borrow().global_exists(parts[0])) { + let shellwords = Shellwords::from(input); + let args = shellwords.words(); + + // We're finalizing the event - we actually want to call the function + if event == PromptEvent::Validate { + if let Err(e) = ENGINE.with(|x| { + let args = steel::List::from( + args[1..] + .iter() + .map(|x| x.clone().into_steelval().unwrap()) + .collect::>(), + ); + + let mut guard = x.borrow_mut(); + + let mut cx = Context { + register: None, + count: std::num::NonZeroUsize::new(1), + editor: cx.editor, + callback: None, + on_next_key_callback: None, + jobs: cx.jobs, + }; + + { + guard + .register_value("_helix_args", steel::rvals::SteelVal::ListV(args)); + + let res = guard.run_with_reference::( + &mut cx, + "*context*", + &format!("(apply {} (cons *context* _helix_args))", parts[0]), + ); + + guard.register_value("_helix_args", steel::rvals::SteelVal::Void); + + res + } + }) { + cx.editor.set_error(format!("{}", e)); + }; + } } else if event == PromptEvent::Validate { cx.editor .set_error(format!("no such command: '{}'", parts[0])); @@ -2872,6 +2928,18 @@ pub(super) fn command_mode(cx: &mut Context) { return Some((*doc).into()); } return Some(format!("{}\nAliases: {}", doc, aliases.join(", ")).into()); + } else if ENGINE.with(|x| x.borrow().global_exists(part)) { + if let Some(v) = super::engine::ExportedIdentifiers::engine_get_doc(part) { + return Some(v.into()); + } + + // if let Ok(v) = ENGINE.with(|x| x.borrow().extract_value(&format!("{part}__doc__"))) { + // if let steel::rvals::SteelVal::StringV(s) = v { + // return Some(s.to_string().into()); + // } + // } + + return Some("Run this plugin command!".into()); } None diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 9776ef7a461a..d06be8898a52 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -59,11 +59,13 @@ impl Config { pub fn load( global: Result, local: Result, + engine_overlay: Option>, ) -> Result { let global_config: Result = global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); let local_config: Result = local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); + let res = match (global_config, local_config) { (Ok(global), Ok(local)) => { let mut keys = keymap::default(); @@ -74,6 +76,10 @@ impl Config { merge_keys(&mut keys, local_keys) } + if let Some(overlay) = engine_overlay { + merge_keys(&mut keys, overlay); + } + let editor = match (global.editor, local.editor) { (None, None) => helix_view::editor::Config::default(), (None, Some(val)) | (Some(val), None) => { @@ -100,6 +106,11 @@ impl Config { if let Some(keymap) = config.keys { merge_keys(&mut keys, keymap); } + + if let Some(overlay) = engine_overlay { + merge_keys(&mut keys, overlay); + } + Config { theme: config.theme, keys, @@ -116,12 +127,27 @@ impl Config { Ok(res) } + // TODO: @Matt -> Add key binding here by reading value from steel engine pub fn load_default() -> Result { let global_config = fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); let local_config = fs::read_to_string(helix_loader::workspace_config_file()) .map_err(ConfigLoadError::Error); - Config::load(global_config, local_config) + + // let binding = crate::commands::ENGINE.with(|x| { + // x.borrow_mut() + // .run("(value->jsexpr-string *KEYBINDINGS*)") + // .unwrap() + // }); + // let keybindings_as_str = binding[0] + // .string_or_else(|| panic!("Should always be a string")) + // .unwrap(); + + // let bindings: HashMap = serde_json::from_str(&keybindings_as_str).unwrap(); + + let bindings = crate::commands::engine::SharedKeyBindingsEventQueue::get(); + + Config::load(global_config, local_config, bindings) } } @@ -131,7 +157,7 @@ mod tests { impl Config { fn load_test(config: &str) -> Config { - Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default())).unwrap() + Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default()), None).unwrap() } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 3033c6a4883c..b644316a602e 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -143,6 +143,9 @@ impl DerefMut for KeyTrieNode { } } +// TODO: impl FromSteelVal and IntoSteelVal for this - or expose methods +// that allow Steel to integrate with the keybindings on the editor + #[derive(Debug, Clone, PartialEq)] pub enum KeyTrie { Leaf(MappableCommand), diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index e0c3f6e701e0..f58a78983fbb 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -126,6 +126,9 @@ FLAGS: helix_loader::initialize_config_file(args.config_file.clone()); + // Initialize the engine before we boot up! + let _ = helix_term::commands::initialize_engine(); + let config = match Config::load_default() { Ok(config) => config, Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => { diff --git a/helix.scm b/helix.scm new file mode 100644 index 000000000000..077f9cae9616 --- /dev/null +++ b/helix.scm @@ -0,0 +1,178 @@ +(require-builtin helix/core/typable as helix.) +(require-builtin helix/core/static as helix.static.) +(require-builtin helix/core/keybindings as helix.keybindings.) + + +(provide + set-theme-dracula + set-theme-dracula__doc__ + set-theme-custom + set-theme-custom__doc__ + theme-then-vsplit + theme-then-vsplit__doc__ + custom-undo + custom-undo__doc__ + lam + lam__doc__ + delete-word-forward + insert-string-at-selection + highlight-to-matching-paren + highlight-to-matching-paren__doc__ + delete-sexpr + delete-sexpr__doc__ + run-expr + run-highlight + make-minor-mode! + git-status + reload-helix-scm +) + + +;;@doc +;; Sets the theme to be the dracula theme +(define (set-theme-dracula cx) + (helix.theme cx (list "dracula") helix.PromptEvent::Validate)) + +;;@doc +;; Sets the theme to be the theme passed in +(define (set-theme-custom cx entered-theme) + (helix.theme cx (list entered-theme) helix.PromptEvent::Validate)) + +;;@doc +;; Switch theme to the entered theme, then split the current file into +;; a vsplit +(define (theme-then-vsplit cx entered-theme) + (set-theme-custom cx entered-theme) + (helix.vsplit cx '() helix.PromptEvent::Validate)) + +;;@doc +;; Perform an undo +(define (custom-undo cx) + (helix.static.undo cx)) + +;;@doc +;; Insert a lambda +(define (lam cx) + (helix.static.insert_char cx #\λ) + (helix.static.insert_mode cx)) + +;;@doc +;; Insert the string at the selection and go back into insert mode +(define (insert-string-at-selection cx str) + (helix.static.insert_string cx str) + (helix.static.insert_mode cx)) + +;;@doc +;; Delete the word forward +(define (delete-word-forward cx) + (helix.static.delete_word_forward cx)) + + +;;@doc +;; Registers a minor mode with the registered modifer and key map +;; +;; Examples: +;; ```scheme +;; (make-minor-mode! "+" +;; (hash "P" ":lam")) +;; ``` +(define (make-minor-mode! modifier bindings) + (~> (hash "normal" (hash modifier bindings)) + (value->jsexpr-string) + (helix.keybindings.set-keybindings!))) + + + +(define-syntax minor-mode! + (syntax-rules (=>) + [(minor-mode! modifier (key => function)) + (make-minor-mode! modifier (minor-mode-cruncher (key => function)))] + + [(minor-mode! modifier (key => (function ...))) + (make-minor-mode! modifier (minor-mode-cruncher (key => (function ...))))] + + [(minor-mode! modifier (key => function) remaining ...) + (make-minor-mode! modifier (minor-mode-cruncher (key => function) remaining ...))] + + [(minor-mode! modifier (key => (function ...)) remaining ...) + (make-minor-mode! modifier (minor-mode-cruncher (key => function) ... remaining ...))] + + + + )) + +(define-syntax minor-mode-cruncher + (syntax-rules (=>) + + + [(minor-mode-cruncher (key => (function ...))) + (hash key (map (lambda (x) + (string-append ":" (symbol->string x))) + (quote (function ...))))] + + [(minor-mode-cruncher (key => function)) + (hash key (string-append ":" (symbol->string (quote function))))] + + [(minor-mode-cruncher (key => (function ...)) remaining ...) + (hash-insert + (minor-mode-cruncher remaining ...) + key (map (lambda (x) + (string-append ":" (symbol->string x))) + (quote (function ...))))] + + [(minor-mode-cruncher (key => function) remaining ...) + (hash-insert + (minor-mode-cruncher remaining ...) + key + (string-append ":" (symbol->string (quote function))))] + + + + )) + +;;@doc +;; Highlight to the matching paren +(define (highlight-to-matching-paren cx) + (helix.static.select_mode cx) + (helix.static.match_brackets cx)) + +(define (run-expr cx) + (define current-selection (helix.static.current_selection cx)) + (when (or (equal? "(" current-selection) + (equal? ")" current-selection)) + (highlight-to-matching-paren cx) + (helix.static.run-in-engine! cx (helix.static.current-highlighted-text! cx)) + (helix.static.normal_mode cx))) + + +(define (run-highlight cx) + (helix.static.run-in-engine! cx (helix.static.current-highlighted-text! cx))) + +;;@doc +;; Delete the s-expression matching this bracket +;; If the current selection is not on a bracket, this is a no-op +(define (delete-sexpr cx) + (define current-selection (helix.static.current_selection cx)) + (when (or (equal? "(" current-selection) + (equal? ")" current-selection)) + (highlight-to-matching-paren cx) + (helix.static.delete_selection cx))) + +; (minor-mode! "+" ("l" => lam) +; ("q" => (set-theme-dracula lam))) + +(minor-mode! "P" ("l" => lam) + ("p" => highlight-to-matching-paren) + ("d" => delete-sexpr) + ("r" => run-expr)) + +(make-minor-mode! "+" (hash "l" ":lam")) + +(define (git-status cx) + (helix.run-shell-command cx '("git" "status") helix.PromptEvent::Validate)) + +(minor-mode! "G" ("s" => git-status)) + +(define (reload-helix-scm cx) + (helix.static.run-in-engine! cx + (string-append "(require \"" (helix.static.get-helix.scm-path) "\")"))) \ No newline at end of file From 798c6a5180e4a39aed3b85365153a69a7c723a73 Mon Sep 17 00:00:00 2001 From: mattwparas Date: Mon, 8 May 2023 22:20:02 -0700 Subject: [PATCH 002/124] line up stuff --- Cargo.lock | 1 + helix-term/src/commands/engine.rs | 43 ++++++++++++++++++++----------- helix-view/Cargo.toml | 3 +++ helix-view/src/editor.rs | 2 ++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc57a4fee183..655a516856bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,6 +1557,7 @@ dependencies = [ "serde", "serde_json", "slotmap", + "steel-core", "tokio", "tokio-stream", "toml", diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 324c7d8af56d..ce559d3faea0 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -91,12 +91,27 @@ impl Custom for PromptEvent {} impl<'a> CustomReference for Context<'a> {} +fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor { + cx.editor +} + fn configure_engine() -> std::rc::Rc> { let mut engine = steel::steel_vm::engine::Engine::new(); let mut module = BuiltInModule::new("helix/core/keybindings".to_string()); module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge); + RegisterFn::< + _, + steel::steel_vm::register_fn::MarkerWrapper7<( + Context<'_>, + helix_view::Editor, + helix_view::Editor, + Context<'static>, + )>, + helix_view::Editor, + >::register_fn(&mut engine, "cx-editor!", get_editor); + engine.register_module(module); let mut module = BuiltInModule::new("helix/core/typable".to_string()); @@ -163,6 +178,10 @@ fn configure_engine() -> std::rc::Rc std::rc::Rc>(); - *EXPORTED_IDENTIFIERS.identifiers.write().unwrap() = exported; - - let docs = m + let docs = exported .iter() - .filter_map(|(k, v)| { - if let steel::rvals::SteelVal::SymbolV(s) = k { - if s.ends_with("__doc__") { - if let steel::rvals::SteelVal::StringV(d) = v { - return Some(( - s.strip_suffix("__doc__").unwrap().to_string(), - d.to_string(), - )); - } - } + .filter_map(|x| { + if let Ok(steel::rvals::SteelVal::StringV(d)) = + engine.extract_value(&(module_prefix.to_string() + x.as_str() + "__doc__")) + { + Some((x.to_string(), d.to_string())) + } else { + None } - - None }) .collect::>(); + *EXPORTED_IDENTIFIERS.identifiers.write().unwrap() = exported; *EXPORTED_IDENTIFIERS.docs.write().unwrap() = docs; } else { panic!("Unable to parse exported identifiers from helix module!") diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 049705956b2a..28d419bdf6ec 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -45,6 +45,9 @@ log = "~0.4" which = "4.4" parking_lot = "0.12.1" +# plugin support +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow"] } + [target.'cfg(windows)'.dependencies] clipboard-win = { version = "4.5", features = ["std"] } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 005c66674483..7cb36a883f31 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -799,6 +799,8 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; +impl steel::gc::unsafe_erased_pointers::CustomReference for Editor {} + pub struct Editor { /// Current editing mode. pub mode: Mode, From c0ea1ebbf4de554ee40daa4e19bc90dff2802e74 Mon Sep 17 00:00:00 2001 From: mattwparas Date: Wed, 10 May 2023 07:48:02 -0700 Subject: [PATCH 003/124] a little more behind the scenes stuff --- Cargo.lock | 120 ++++++++++++++++++++++++++++++ helix-loader/src/lib.rs | 4 + helix-term/Cargo.toml | 2 +- helix-term/src/application.rs | 18 +++-- helix-term/src/commands.rs | 2 +- helix-term/src/commands/engine.rs | 77 +++++++++++++++++++ helix-term/src/commands/typed.rs | 47 ++++++++++++ helix-term/src/main.rs | 2 +- helix-term/src/ui/statusline.rs | 15 +++- helix-view/Cargo.toml | 2 +- helix-view/src/editor.rs | 3 + 11 files changed, 282 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 655a516856bc..e9bd25f472d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "beef" version = "0.5.2" @@ -2022,6 +2028,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "pretty" version = "0.11.3" @@ -2112,6 +2124,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -2191,6 +2215,21 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "ropey" version = "1.6.0" @@ -2215,6 +2254,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2242,6 +2293,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.160" @@ -2402,6 +2463,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2431,6 +2498,7 @@ dependencies = [ "once_cell", "pretty", "quickscope", + "rand", "serde", "serde_derive", "serde_json", @@ -2440,6 +2508,7 @@ dependencies = [ "steel-parser", "termimad", "thiserror", + "ureq", "weak-table", ] @@ -2824,6 +2893,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "url", + "webpki", + "webpki-roots", +] + [[package]] name = "url" version = "2.3.1" @@ -2924,6 +3015,35 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "4.4.0" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index f4f03dd5286a..0dae0a925b37 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -132,6 +132,10 @@ pub fn helix_module_file() -> PathBuf { config_dir().join("helix.scm") } +pub fn steel_init_file() -> PathBuf { + config_dir().join("init.scm") +} + pub fn workspace_config_file() -> PathBuf { find_workspace().0.join(".helix").join("config.toml") } diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 116522d7b603..014c2879a28b 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -67,7 +67,7 @@ grep-regex = "0.1.11" grep-searcher = "0.1.11" # plugin support -steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow"] } +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests"] } [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 7ae603519d9d..62ddf76675c0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -233,7 +233,7 @@ impl Application { let signals = Signals::new([signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1]) .context("build signal handler")?; - let app = Self { + let mut app = Self { compositor, terminal, editor, @@ -249,10 +249,18 @@ impl Application { last_render: Instant::now(), }; - // // Initialize the engine before we boot up! - // crate::commands::ENGINE.with(|x| { - // let _ = x.borrow().globals(); - // }); + { + let mut cx = crate::commands::Context { + register: None, + count: std::num::NonZeroUsize::new(1), + editor: &mut app.editor, + callback: None, + on_next_key_callback: None, + jobs: &mut app.jobs, + }; + + crate::commands::run_initialization_script(&mut cx); + } Ok(app) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fe2fcb6fed11..b1f6adb2e45b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -7,7 +7,7 @@ pub use dap::*; use helix_vcs::Hunk; pub use lsp::*; -pub use engine::initialize_engine; +pub use engine::{initialize_engine, run_initialization_script}; use steel::rvals::IntoSteelVal; use tokio::sync::oneshot; use tui::widgets::Row; diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index ce559d3faea0..b01f1be51981 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -40,12 +40,57 @@ pub fn initialize_engine() { ENGINE.with(|x| x.borrow().globals().first().copied()); } +/// Run the initialization script located at `$helix_config/init.scm` +/// This runs the script in the global environment, and does _not_ load it as a module directly +pub fn run_initialization_script(cx: &mut Context) { + log::info!("Loading init.scm..."); + + let helix_module_path = helix_loader::steel_init_file(); + + if let Ok(contents) = std::fs::read_to_string(&helix_module_path) { + ENGINE.with(|x| { + x.borrow_mut() + .run_with_reference::(cx, "*helix.cx*", &contents) + .unwrap() + }); + + log::info!("Finished loading init.scm!") + } else { + log::info!("No init.scm found, skipping loading.") + } + + // Start the worker thread - i.e. message passing to the workers + configure_background_thread() +} + pub static KEYBINDING_QUEUE: Lazy = Lazy::new(|| SharedKeyBindingsEventQueue::new()); pub static EXPORTED_IDENTIFIERS: Lazy = Lazy::new(|| ExportedIdentifiers::default()); +pub static STATUS_LINE_MESSAGE: Lazy = Lazy::new(|| StatusLineMessage::new()); + +pub struct StatusLineMessage { + message: Arc>>, +} + +impl StatusLineMessage { + pub fn new() -> Self { + Self { + message: std::sync::Arc::new(std::sync::RwLock::new(None)), + } + } + + pub fn set(message: String) { + *STATUS_LINE_MESSAGE.message.write().unwrap() = Some(message); + } + + pub fn get() -> Option { + STATUS_LINE_MESSAGE.message.read().unwrap().clone() + } +} + /// In order to send events from the engine back to the configuration, we can created a shared /// queue that the engine and the config push and pull from. Alternatively, we could use a channel /// directly, however this was easy enough to set up. @@ -95,6 +140,27 @@ fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor { cx.editor } +fn get_themes(cx: &mut Context) -> Vec { + ui::completers::theme(cx.editor, "") + .into_iter() + .map(|x| x.1.to_string()) + .collect() +} + +fn configure_background_thread() { + std::thread::spawn(move || { + let mut engine = steel::steel_vm::engine::Engine::new(); + + engine.register_fn("set-status-line!", StatusLineMessage::set); + + let helix_module_path = helix_loader::config_dir().join("background.scm"); + + if let Ok(contents) = std::fs::read_to_string(&helix_module_path) { + engine.run(&contents).ok(); + } + }); +} + fn configure_engine() -> std::rc::Rc> { let mut engine = steel::steel_vm::engine::Engine::new(); @@ -112,6 +178,9 @@ fn configure_engine() -> std::rc::Rc::register_fn(&mut engine, "cx-editor!", get_editor); + engine.register_fn("cx->themes", get_themes); + engine.register_fn("set-status-line!", StatusLineMessage::set); + engine.register_module(module); let mut module = BuiltInModule::new("helix/core/typable".to_string()); @@ -154,6 +223,7 @@ fn configure_engine() -> std::rc::Rc String { .unwrap() .to_string() } + +fn get_init_scm_path() -> String { + helix_loader::steel_init_file() + .to_str() + .unwrap() + .to_string() +} diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index bd42194e60b0..f57170f5086a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2074,6 +2074,43 @@ fn pipe_impl( Ok(()) } +fn run_shell_command_text( + cx: &mut compositor::Context, + args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let shell = cx.editor.config().shell.clone(); + let args = args.join(" "); + + let callback = async move { + let (output, success) = shell_impl_async(&shell, &args, None).await?; + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + if !output.is_empty() { + let contents = ui::Text::new(format!("{}", output)); + let popup = Popup::new("shell", contents).position(Some( + helix_core::Position::new(editor.cursor().0.unwrap_or_default().row, 2), + )); + compositor.replace_or_push("shell", popup); + } + if success { + editor.set_status("Command succeeded"); + } else { + editor.set_error("Command failed"); + } + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); + + Ok(()) +} + fn run_shell_command( cx: &mut compositor::Context, args: &[Cow], @@ -2746,6 +2783,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: run_shell_command, signature: CommandSignature::all(completers::filename) }, + TypableCommand { + name: "run-shell-command-text", + aliases: &["sh"], + doc: "Run a shell command", + fun: run_shell_command_text, + signature: CommandSignature::all(completers::filename) + }, TypableCommand { name: "reset-diff-change", aliases: &["diffget", "diffg"], @@ -2875,6 +2919,9 @@ pub(super) fn command_mode(cx: &mut Context) { // We're finalizing the event - we actually want to call the function if event == PromptEvent::Validate { + // TODO: @Matt - extract this whole API cal here to just be inside the engine module + // For what its worth, also explore a more elegant API for calling apply with some arguments, + // this does work, but its a little opaque. if let Err(e) = ENGINE.with(|x| { let args = steel::List::from( args[1..] diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index f58a78983fbb..9d06e283d365 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -127,7 +127,7 @@ FLAGS: helix_loader::initialize_config_file(args.config_file.clone()); // Initialize the engine before we boot up! - let _ = helix_term::commands::initialize_engine(); + helix_term::commands::initialize_engine(); let config = match Config::load_default() { Ok(config) => config, diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 887863519319..784f746c3ba5 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -8,7 +8,10 @@ use helix_view::{ Document, Editor, View, }; -use crate::ui::ProgressSpinners; +use crate::{ + commands::engine::{StatusLineMessage, STATUS_LINE_MESSAGE}, + ui::ProgressSpinners, +}; use helix_view::editor::StatusLineElement as StatusLineElementID; use tui::buffer::Buffer as Surface; @@ -160,6 +163,7 @@ where helix_view::editor::StatusLineElement::Separator => render_separator, helix_view::editor::StatusLineElement::Spacer => render_spacer, helix_view::editor::StatusLineElement::VersionControl => render_version_control, + helix_view::editor::StatusLineElement::Custom => render_custom_text, } } @@ -490,3 +494,12 @@ where write(context, head, None); } + +fn render_custom_text(context: &mut RenderContext, write: F) +where + F: Fn(&mut RenderContext, String, Option