diff --git a/Cargo.lock b/Cargo.lock index 84e155f85..37f9439c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,54 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abi_stable" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f69d9465d88d24382d43fa68335a92fe9d3c53a918549c693403ed9a85eff50" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "const_panic", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading 0.7.4", + "lock_api", + "parking_lot", + "paste", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aecd3efa5a5294f5c67913d45f985ccb382b3c93327581529610eeecdf4821a" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2 1.0.56", + "quote 1.0.26", + "rustc_version", + "syn 1.0.109", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b5df7688c123e63f4d4d649cba63f2967ba7f7861b1664fca3f77d3dad2b63" +dependencies = [ + "core_extensions", +] + [[package]] name = "adler" version = "1.0.2" @@ -25,7 +73,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "getrandom", "once_cell", "version_check", @@ -76,6 +124,18 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "as_derive_utils" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff3c96645900a44cf11941c111bd08a6573b0e2f9f69bc9264b179d8fae753c4" +dependencies = [ + "core_extensions", + "proc-macro2 1.0.56", + "quote 1.0.26", + "syn 1.0.109", +] + [[package]] name = "atty" version = "0.2.14" @@ -192,6 +252,12 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -204,7 +270,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "encoding_rs", "memchr", ] @@ -271,6 +337,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "const_panic" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" + [[package]] name = "content_inspector" version = "0.2.4" @@ -295,13 +367,28 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core_extensions" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c71dc07c9721607e7a16108336048ee978c3a8b129294534272e8bac96c0ee" +dependencies = [ + "core_extensions_proc_macros", +] + +[[package]] +name = "core_extensions_proc_macros" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" + [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -310,7 +397,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", @@ -324,7 +411,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -334,7 +421,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -346,7 +433,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "memoffset", "scopeguard", @@ -358,7 +445,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -368,7 +455,7 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -413,6 +500,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "custom-commands" +version = "0.1.0" +dependencies = [ + "anyhow", + "helix-core", + "helix-term", + "helix-view", + "steel-core", +] + [[package]] name = "cxx" version = "1.0.94" @@ -463,7 +561,7 @@ version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "num_cpus", ] @@ -473,7 +571,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -529,7 +627,7 @@ version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -578,7 +676,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51822eedc6129d8c4d96cec86d56b785e983f943c9ce9fb892e0c2a99a7f47a0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "home", ] @@ -606,7 +704,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", "windows-sys 0.48.0", @@ -744,13 +842,22 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generational-arena" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] + [[package]] name = "getrandom" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] @@ -1403,6 +1510,7 @@ dependencies = [ "slotmap", "smallvec", "smartstring", + "steel-core", "textwrap", "toml", "tree-sitter", @@ -1433,7 +1541,7 @@ dependencies = [ "anyhow", "cc", "etcetera", - "libloading", + "libloading 0.8.0", "log", "once_cell", "serde", @@ -1477,6 +1585,8 @@ dependencies = [ "chrono", "content_inspector", "crossterm 0.26.1", + "dlopen", + "dlopen_derive", "fern", "futures-util", "fuzzy-matcher", @@ -1520,6 +1630,7 @@ dependencies = [ "log", "once_cell", "serde", + "steel-core", "termini", "unicode-segmentation", ] @@ -1713,7 +1824,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1793,13 +1904,23 @@ version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "libloading" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-sys 0.48.0", ] @@ -1834,7 +1955,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -2003,13 +2124,19 @@ version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -2215,6 +2342,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "repr_offset" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1070755bd29dffc19d0971cab794e607839ba2ef4b69a9e6fbc8733c1b72ea" +dependencies = [ + "tstr", +] + [[package]] name = "ring" version = "0.16.20" @@ -2240,6 +2376,15 @@ dependencies = [ "str_indices", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.37.15" @@ -2303,6 +2448,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.160" @@ -2479,6 +2630,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "steel-core" version = "0.2.0" dependencies = [ + "abi_stable", "anyhow", "bincode", "chrono", @@ -2582,7 +2734,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", "rustix", @@ -2658,7 +2810,7 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -2811,6 +2963,21 @@ dependencies = [ "regex", ] +[[package]] +name = "tstr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca3264971090dec0feef3b455a3c178f02762f7550cf4592991ac64b3be2d7e" +dependencies = [ + "tstr_proc_macros", +] + +[[package]] +name = "tstr_proc_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78122066b0cb818b8afd08f7ed22f7fdbc3e90815035726f0840d0d26c0747a" + [[package]] name = "typed-arena" version = "2.0.2" @@ -2963,7 +3130,7 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index c63518897..1ebd46e1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "helix-loader", "helix-vcs", "helix-parsec", + "custom-commands", "xtask", ] @@ -18,7 +19,7 @@ default-members = [ [profile.release] lto = "thin" -# debug = true +debug = true [profile.opt] inherits = "release" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index c10ed735e..212528ede 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -48,6 +48,9 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std" etcetera = "0.7" textwrap = "0.16.0" +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests", "dylibs"] } + + [dev-dependencies] quickcheck = { version = "1", default-features = false } indoc = "2.0.1" diff --git a/helix-core/src/extensions.rs b/helix-core/src/extensions.rs new file mode 100644 index 000000000..cb16e77de --- /dev/null +++ b/helix-core/src/extensions.rs @@ -0,0 +1 @@ +impl steel::rvals::Custom for crate::Position {} diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 069e80b6a..2fa389cc0 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -761,6 +761,160 @@ static LISP_WORDS: Lazy> = Lazy::new(|| words.iter().copied().collect() }); +// TODO: Allow for injecting hooks on indent +#[allow(clippy::too_many_arguments)] +fn call_indent_hook( + language_config: Option<&LanguageConfiguration>, + syntax: Option<&Syntax>, + indent_style: &IndentStyle, + tab_width: usize, + text: RopeSlice, + line_before: usize, + line_before_end_pos: usize, + current_line: usize, +) -> Option { + if let Some(config) = language_config { + // TODO: If possible, this would be very cool to be implemented in steel itself. If not, + // a rust native method that is embedded in a dylib that this uses would also be helpful + if config.language_id == "scheme" { + log::info!("Implement better scheme indent mode!"); + + // TODO: walk backwards to find the previous s-expression? + + // log::info!("{}", text); + // log::info!("{}", text.line(line_before)); + + let byte_pos = text.char_to_byte(line_before_end_pos); + + let text_up_to_cursor = text.byte_slice(0..byte_pos); + + let mut cursor = line_before; + let mut depth = 0; + + // for line in text_up_to_cursor.lines().reversed() { + loop { + let line = text_up_to_cursor.line(cursor); + + // We want to ignore comments + if let Some(l) = line.as_str() { + if l.starts_with(";") { + if cursor == 0 { + break; + } + + cursor -= 1; + + continue; + } + } + + // log::info!("Line: {}", line); + + for (index, char) in line.chars_at(line.len_chars()).reversed().enumerate() { + match char { + ')' | ']' | '}' => { + depth += 1; + } + '(' | '[' | '{' => { + // stack.push('(') + + if depth == 0 { + log::info!( + "Found unmatched paren on line, index: {}, {}", + line, + index + ); + + // TODO: Here, then walk FORWARD, parsing the identifiers until there is a thing to line up with, for example: + // (define (foo-bar) RET) <- + // ^probably indent to here + + let offset = line.len_chars() - index; + + let mut char_iter_from_paren = + line.chars_at(line.len_chars() - index).enumerate(); + + let end; + + // Walk until we've found whitespace, and then crunch the whitespace until the start of the next symbol + // if there is _no_ symbol after that, we should just default to the default behavior + while let Some((index, char)) = char_iter_from_paren.next() { + if char.is_whitespace() { + let mut last = index; + + // This is the end of our range + end = index; + + // If we have multiple parens in a row, match to the start: + // for instance, (cond [(equal? x 10) RET]) + // ^ We want to line up to this + // + // To do so, just create an indent that is the width of the offset. + match line.get_char(offset) { + Some('(' | '[' | '{') => { + return Some(" ".repeat(offset)); + } + _ => {} + } + + // TODO: Don't unwrap here, we don't want that + if LISP_WORDS.contains( + line.slice(offset..offset + end).as_str().unwrap(), + ) { + return Some(" ".repeat(offset + 1)); + } + + for _ in char_iter_from_paren + .take_while(|(_, x)| x.is_whitespace()) + { + last += 1; + } + + // If we have something like (list RET) + // We want the result to look like: + // (list + // ) + // + // So we special case the lack of an additional word after + // the first symbol + if line.len_chars() == last + offset + 1 { + if let Some(c) = line.get_char(last + offset) { + if c.is_whitespace() { + return Some(" ".repeat(offset + 1)); + } + } + } + + return Some(" ".repeat(last + offset + 1)); + } + } + + log::info!("Found no symbol after the initial opening symbol"); + + return Some(" ".repeat(offset + 1)); + } + + depth -= 1; + } + _ => {} + } + } + + if cursor == 0 { + break; + } + + cursor -= 1; + } + + // TODO: Implement heuristic for large files so we don't necessarily traverse the entire file backwards to check the matched parens? + return Some("".to_string()); + } + } + + None +} + /// TODO: Come up with some elegant enough FFI for this, so that Steel can expose an API for this. /// Problem is - the issues with the `Any` type and using things with type id. #[allow(clippy::too_many_arguments)] diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index b67e2c8a3..b5c2065b6 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -30,6 +30,8 @@ pub mod textobject; mod transaction; pub mod wrap; +pub mod extensions; + pub mod unicode { pub use unicode_general_category as category; pub use unicode_segmentation as segmentation; diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 014c2879a..d30daeaa2 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -67,7 +67,9 @@ 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", "blocking_requests"] } +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests", "dylibs"] } +dlopen = "0.1.8" +dlopen_derive = "0.1.4" [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/commands.rs b/helix-term/src/commands.rs index b1f6adb2e..09268aa41 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,6 +1,7 @@ pub(crate) mod dap; pub(crate) mod engine; pub(crate) mod lsp; +pub mod plugin; pub(crate) mod typed; pub use dap::*; @@ -83,6 +84,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; pub type OnKeyCallback = Box; +#[repr(C)] pub struct Context<'a> { pub register: Option, pub count: Option, diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index 5f8998ed9..bf59c2f68 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -4,8 +4,9 @@ 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, + rvals::{FromSteelVal, IntoSteelVal, SteelString}, + steel_vm::{engine::Engine, register_fn::RegisterFn}, + SteelVal, }; use std::{ @@ -21,14 +22,15 @@ use std::{ use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule}; use crate::{ - compositor::{self, Compositor}, + compositor::{self, Component, Compositor}, job::{self, Callback}, keymap::{merge_keys, Keymap}, - ui::{self, Popup, PromptEvent}, + ui::{self, overlay::overlaid, Popup, PromptEvent}, }; use super::{ insert::{insert_char, insert_string}, + plugin::{DylibContainers, ExternalModule}, shell_impl, Context, MappableCommand, TYPABLE_COMMAND_LIST, }; @@ -36,6 +38,40 @@ thread_local! { pub static ENGINE: std::rc::Rc> = configure_engine(); } +pub struct ExternalContainersAndModules { + containers: DylibContainers, + modules: Vec, +} + +// External modules that can load via rust dylib. These can then be consumed from +// steel as needed, via the standard FFI for plugin functions. +pub(crate) static EXTERNAL_DYLIBS: Lazy>> = + Lazy::new(|| { + let mut containers = DylibContainers::new(); + + // Load the plugins with respect to the extensions directory. + // containers.load_modules_from_directory(Some( + // helix_loader::config_dir() + // .join("extensions") + // .to_str() + // .unwrap() + // .to_string(), + // )); + + println!("Found dylibs: {}", containers.containers.len()); + + let modules = containers.create_commands(); + + println!("Modules length: {}", modules.len()); + + Arc::new(RwLock::new(ExternalContainersAndModules { + containers, + modules, + })) + + // Arc::new(RwLock::new(containers)) + }); + pub fn initialize_engine() { ENGINE.with(|x| x.borrow().globals().first().copied()); } @@ -60,7 +96,7 @@ pub fn run_initialization_script(cx: &mut Context) { } // Start the worker thread - i.e. message passing to the workers - configure_background_thread() + // configure_background_thread() } pub static KEYBINDING_QUEUE: Lazy = @@ -140,6 +176,10 @@ fn get_editor<'a>(cx: &'a mut Context<'a>) -> &'a mut Editor { cx.editor } +fn get_ro_editor<'a>(cx: &'a mut Context<'a>) -> &'a Editor { + &cx.editor +} + fn get_themes(cx: &mut Context) -> Vec { ui::completers::theme(cx.editor, "") .into_iter() @@ -147,25 +187,499 @@ fn get_themes(cx: &mut Context) -> Vec { .collect() } -fn configure_background_thread() { - std::thread::spawn(move || { - let mut engine = steel::steel_vm::engine::Engine::new(); +// TODO: This is not necessary anymore. We can get away with native threads in steel, and otherwise run background tasks +// that may or may not live the duration of the editor time in there. +// 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); +// engine.register_fn("set-status-line!", StatusLineMessage::set); - let helix_module_path = helix_loader::config_dir().join("background.scm"); +// 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(); +// if let Ok(contents) = std::fs::read_to_string(&helix_module_path) { +// engine.run(&contents).ok(); +// } +// }); +// } + +/// A dynamic component, used for rendering thing +#[derive(Clone)] +// TODO: Implement `trace` method for objects that hold steel vals +struct SteelDynamicComponent { + name: String, + // This _should_ be a struct, but in theory can be whatever you want. It will be the first argument + // passed to the functions in the remainder of the struct. + state: SteelVal, + handle_event: Option, + should_update: Option, + render: SteelVal, + cursor: Option, + required_size: Option, +} + +impl SteelDynamicComponent { + fn new(name: String, state: SteelVal, render: SteelVal, h: HashMap) -> Self { + // if let SteelVal::HashMapV(h) = functions { + + Self { + name, + state, + render, + handle_event: h.get("handle_event").cloned(), + should_update: h.get("should_update").cloned(), + cursor: h.get("cursor").cloned(), + required_size: h.get("required_size").cloned(), } - }); + + // } else { + // panic!("Implement better error handling") + // } + } + + fn new_dyn( + name: String, + state: SteelVal, + render: SteelVal, + h: HashMap, + ) -> WrappedDynComponent { + let s = Self::new(name, state, render, h); + + WrappedDynComponent { + inner: Some(Box::new(s)), + } + } + + fn get_state(&self) -> SteelVal { + self.state.clone() + } + + fn get_render(&self) -> SteelVal { + self.render.clone() + } + + fn get_handle_event(&self) -> Option { + self.handle_event.clone() + } + + fn get_should_update(&self) -> Option { + self.should_update.clone() + } + + fn get_cursor(&self) -> Option { + self.cursor.clone() + } + + fn get_required_size(&self) -> Option { + self.required_size.clone() + } +} + +impl Custom for SteelDynamicComponent {} +impl Custom for compositor::EventResult {} +impl FromSteelVal for compositor::EventResult { + fn from_steelval(val: &SteelVal) -> steel::rvals::Result { + match val { + SteelVal::SymbolV(v) if v.as_str() == "EventResult::Ignored" => { + Ok(compositor::EventResult::Ignored(None)) + } + SteelVal::SymbolV(v) if v.as_str() == "EventResult::Consumed" => { + Ok(compositor::EventResult::Consumed(None)) + } + _ => Err(steel::SteelErr::new( + steel::rerrs::ErrorKind::TypeMismatch, + "Unable to convert value to event result".to_string(), + )), + } + } +} + +// impl CustomReference for tui::buffer::Buffer {} + +// TODO: Call the function inside the component, using the global engine. Consider running in its own engine +// but leaving it all in the same one is kinda nice +impl Component for SteelDynamicComponent { + fn render( + &mut self, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut compositor::Context, + ) { + let mut ctx = Context { + register: None, + count: None, + editor: ctx.editor, + callback: None, + on_next_key_callback: None, + jobs: ctx.jobs, + }; + + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, f, c| { + engine.call_function_with_args( + self.render.clone(), + vec![self.state.clone(), area.into_steelval().unwrap(), f, c], + ) + }; + + ENGINE + .with(|x| { + x.borrow_mut() + .with_mut_reference::(frame) + .with_mut_reference::(&mut ctx) + .consume(|engine, args| { + let mut arg_iter = args.into_iter(); + + (thunk)(engine, arg_iter.next().unwrap(), arg_iter.next().unwrap()) + }) + + // .run_with_references::( + // frame, &mut ctx, thunk, + // ) + }) + .unwrap(); + + log::info!("Calling dynamic render!"); + } + + // TODO: Pass in event as well? Need to have immutable reference type + // Otherwise, we're gonna be in a bad spot. For now - just clone the object and pass it through. + // Clong is _not_ ideal, but it might be all we can do for now. + fn handle_event( + &mut self, + event: &helix_view::input::Event, + ctx: &mut compositor::Context, + ) -> compositor::EventResult { + if let Some(handle_event) = &mut self.handle_event { + let mut ctx = Context { + register: None, + count: None, + editor: ctx.editor, + callback: None, + on_next_key_callback: None, + jobs: ctx.jobs, + }; + + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, c| { + engine.call_function_with_args( + handle_event.clone(), + vec![ + self.state.clone(), + // TODO: We do _not_ want to clone here, we would need to create a bunch of methods on the engine for various + // combinations of reference passing to do this safely. Right now its limited to mutable references, but we should + // expose more - investigate macros on how to do that with recursively crunching the list to generate the combinations. + // Experimentation needed. + event.clone().into_steelval().unwrap(), + c, + ], + ) + }; + + match ENGINE.with(|x| { + x.borrow_mut() + .run_thunk_with_reference::(&mut ctx, thunk) + }) { + Ok(v) => compositor::EventResult::from_steelval(&v) + .unwrap_or_else(|_| compositor::EventResult::Ignored(None)), + Err(_) => compositor::EventResult::Ignored(None), + } + } else { + compositor::EventResult::Ignored(None) + } + } + + fn should_update(&self) -> bool { + if let Some(should_update) = &self.should_update { + match ENGINE.with(|x| { + x.borrow_mut() + .call_function_with_args(should_update.clone(), vec![self.state.clone()]) + }) { + Ok(v) => bool::from_steelval(&v).unwrap_or(true), + Err(_) => true, + } + } else { + true + } + } + + // TODO: Implement immutable references. Right now I'm only supporting mutable references. + fn cursor( + &self, + area: helix_view::graphics::Rect, + ctx: &Editor, + ) -> ( + Option, + helix_view::graphics::CursorKind, + ) { + if let Some(cursor) = &self.cursor { + // Pass the `state` object through - this can be used for storing the state of whatever plugin thing we're + // attempting to render + let thunk = |engine: &mut Engine, e| { + engine.call_function_with_args( + cursor.clone(), + vec![self.state.clone(), area.into_steelval().unwrap(), e], + ) + }; + + <( + Option, + helix_view::graphics::CursorKind, + )>::from_steelval(&ENGINE.with(|x| { + x.borrow_mut() + .run_thunk_with_ro_reference::(ctx, thunk) + .unwrap() + })) + .unwrap() + } else { + (None, helix_view::graphics::CursorKind::Hidden) + } + } + + fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { + let name = self.type_name(); + + if let Some(required_size) = &mut self.required_size { + log::info!("Calling required-size inside: {}", name); + + // TODO: Create some token that we can grab to enqueue function calls internally. Referencing + // the external API would cause problems - we just need to include a handle to the interpreter + // instance. Something like: + // ENGINE.call_function_or_enqueue? OR - this is the externally facing render function. Internal + // render calls do _not_ go through this interface. Instead, they are just called directly. + // + // If we go through this interface, we're going to get an already borrowed mut error, since it is + // re-entrant attempting to grab the ENGINE instead mutably, since we have to break the recursion + // somehow. By putting it at the edge, we then say - hey for these functions on this interface, + // call the engine instance. Otherwise, all computation happens inside the engine. + let res = ENGINE + .with(|x| { + x.borrow_mut().call_function_with_args( + required_size.clone(), + vec![self.state.clone(), viewport.into_steelval().unwrap()], + ) + }) + .and_then(|x| Option::<(u16, u16)>::from_steelval(&x)) + .unwrap(); + + res + } else { + None + } + } + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + + fn id(&self) -> Option<&'static str> { + None + } +} + +// Does this work? +impl Custom for Box {} + +struct WrappedDynComponent { + inner: Option>, +} + +impl Custom for WrappedDynComponent {} + +struct BoxDynComponent { + inner: Box, +} + +impl BoxDynComponent { + pub fn new(inner: Box) -> Self { + Self { inner } + } +} + +impl Component for BoxDynComponent { + fn handle_event( + &mut self, + _event: &helix_view::input::Event, + _ctx: &mut compositor::Context, + ) -> compositor::EventResult { + self.inner.handle_event(_event, _ctx) + } + + fn should_update(&self) -> bool { + self.inner.should_update() + } + + fn cursor( + &self, + _area: helix_view::graphics::Rect, + _ctx: &Editor, + ) -> ( + Option, + helix_view::graphics::CursorKind, + ) { + self.inner.cursor(_area, _ctx) + } + + fn required_size(&mut self, _viewport: (u16, u16)) -> Option<(u16, u16)> { + self.inner.required_size(_viewport) + } + + fn type_name(&self) -> &'static str { + std::any::type_name::() + } + + fn id(&self) -> Option<&'static str> { + None + } + + fn render( + &mut self, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut compositor::Context, + ) { + self.inner.render(area, frame, ctx) + } } fn configure_engine() -> std::rc::Rc> { let mut engine = steel::steel_vm::engine::Engine::new(); + println!("Loading engine!"); + + // Load native modules from the directory. Another idea - have a separate dlopen loading system + // in place that does not use the type id, and instead we generate the module after the dylib + // is added. That way functions _must_ have a specific signature, and then we add the integration + // later. + // engine.load_modules_from_directory( + // helix_loader::config_dir() + // .join("extensions") + // .to_str() + // .unwrap() + // .to_string(), + // ); + // Get the current OS engine.register_fn("current-os!", || std::env::consts::OS); + engine.register_fn("new-component!", SteelDynamicComponent::new_dyn); + + engine.register_fn("SteelDynamicComponent?", |object: SteelVal| { + if let SteelVal::Custom(v) = object { + if let Some(wrapped) = v.borrow().as_any_ref().downcast_ref::() { + return wrapped.inner.as_any().is::(); + } else { + false + } + } else { + false + } + }); + + engine.register_fn( + "SteelDynamicComponent-state", + SteelDynamicComponent::get_state, + ); + engine.register_fn( + "SteelDynamicComponent-render", + SteelDynamicComponent::get_render, + ); + engine.register_fn( + "SteelDynamicComponent-handle-event", + SteelDynamicComponent::get_handle_event, + ); + engine.register_fn( + "SteelDynamicComponent-should-update", + SteelDynamicComponent::should_update, + ); + engine.register_fn( + "SteelDynamicComponent-cursor", + SteelDynamicComponent::cursor, + ); + engine.register_fn( + "SteelDynamicComponent-required-size", + SteelDynamicComponent::get_required_size, + ); + + // engine.register_fn("WrappedComponent", WrappedDynComponent::new) + + engine.register_fn( + "Popup::new", + |contents: &mut WrappedDynComponent, + position: helix_core::Position| + -> WrappedDynComponent { + let inner = contents.inner.take().unwrap(); // Panic, for now + + WrappedDynComponent { + inner: Some(Box::new( + Popup::::new("popup", BoxDynComponent::new(inner)) + .position(Some(position)), + )), + } + }, + ); + + // engine.register_fn( + // "Picker::new", + // |contents: &mut Wrapped + // ) + + engine.register_fn("Component::Text", |contents: String| WrappedDynComponent { + inner: Some(Box::new(crate::ui::Text::new(contents))), + }); + + // Separate this out into its own component module - This just lets us call the underlying + // component, not sure if we can go from trait object -> trait object easily but we'll see! + engine.register_fn( + "Component::render", + |t: &mut WrappedDynComponent, + area: helix_view::graphics::Rect, + frame: &mut tui::buffer::Buffer, + ctx: &mut Context| { + t.inner.as_mut().unwrap().render( + area, + frame, + &mut compositor::Context { + jobs: ctx.jobs, + editor: ctx.editor, + scroll: None, + }, + ) + }, + ); + + engine.register_fn( + "Component::handle-event", + |s: &mut WrappedDynComponent, event: &helix_view::input::Event, ctx: &mut Context| { + s.inner.as_mut().unwrap().handle_event( + event, + &mut compositor::Context { + jobs: ctx.jobs, + editor: ctx.editor, + scroll: None, + }, + ) + }, + ); + + engine.register_fn("Component::should-update", |s: &mut WrappedDynComponent| { + s.inner.as_mut().unwrap().should_update() + }); + + engine.register_fn( + "Component::cursor", + |s: &WrappedDynComponent, area: helix_view::graphics::Rect, ctx: &Editor| { + s.inner.as_ref().unwrap().cursor(area, ctx) + }, + ); + + engine.register_fn( + "Component::required-size", + |s: &mut WrappedDynComponent, viewport: (u16, u16)| { + s.inner.as_mut().unwrap().required_size(viewport) + }, + ); let mut module = BuiltInModule::new("helix/core/keybindings".to_string()); module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge); @@ -181,6 +695,29 @@ fn configure_engine() -> std::rc::Rc::register_fn(&mut engine, "cx-editor!", get_editor); + // 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-ro!", get_ro_editor); + + engine.register_fn("editor-cursor", Editor::cursor); + + engine.register_fn("cx->cursor", |cx: &mut Context| cx.editor.cursor()); + + // TODO: + // Position related functions. These probably should be defined alongside the actual impl for Custom in the core crate + engine.register_fn("Position::new", helix_core::Position::new); + engine.register_fn("Position::default", helix_core::Position::default); + engine.register_fn("Position-row", |position: helix_core::Position| { + position.row + }); + engine.register_fn("cx->themes", get_themes); engine.register_fn("set-status-line!", StatusLineMessage::set); @@ -208,6 +745,59 @@ fn configure_engine() -> std::rc::Rc]>, + // event: PromptEvent| + // -> anyhow::Result<()> { + // // Ensure the lifetime of these variables + // let _config = cx.editor.config.clone(); + // let _theme_loader = cx.editor.theme_loader.clone(); + // let _syn_loader = cx.editor.syn_loader.clone(); + + // println!("{}", Arc::strong_count(&_config)); + // println!("{}", Arc::strong_count(&_theme_loader)); + // println!("{}", Arc::strong_count(&_syn_loader)); + // // println!("{:p}", _theme_loader); + // // println!("{:p}", _syn_loader); + + // (inner)(cx, &_theme_loader, &_syn_loader, args, &event) + // }; + + // module.register_owned_fn(command.name.to_string(), func); + // } + + // engine.register_module(module); + // } + engine.register_module(module); let mut module = BuiltInModule::new("helix/core/static".to_string()); @@ -231,6 +821,8 @@ fn configure_engine() -> std::rc::Rc) { +// cx.push_layer(component); +// } diff --git a/helix-term/src/commands/plugin.rs b/helix-term/src/commands/plugin.rs new file mode 100644 index 000000000..47e0e7901 --- /dev/null +++ b/helix-term/src/commands/plugin.rs @@ -0,0 +1,112 @@ +use std::{borrow::Cow, path::PathBuf, sync::Arc}; + +use dlopen::wrapper::{Container, WrapperApi}; +use dlopen_derive::WrapperApi; + +use crate::ui::PromptEvent; + +use super::{CommandSignature, Context}; + +// use super::builtin::BuiltInModule; + +#[repr(C)] +#[derive(Clone)] +pub struct ExternalModule { + pub name: Box, + pub commands: Box<[CrossBoundaryTypableCommand]>, +} + +impl ExternalModule { + pub fn new(name: String, commands: Vec) -> Self { + println!("Name: {}", name); + + Self { + name: name.into_boxed_str(), + commands: commands.into_boxed_slice(), + } + } + + pub fn get_name(&self) -> &str { + &self.name + } +} + +// pub syn_loader: Arc, +// pub theme_loader: Arc, + +#[repr(C)] +#[derive(Clone)] +pub struct CrossBoundaryTypableCommand { + pub name: Box, + pub aliases: Box<[String]>, + pub doc: Box, + pub fun: for<'a> extern "C" fn( + &mut Context<'a>, + &helix_view::theme::Loader, + &helix_core::syntax::Loader, + Box<[Box]>, + *const PromptEvent, + ) -> anyhow::Result<()>, + pub signature: CommandSignature, +} + +#[derive(WrapperApi, Clone)] +pub struct ModuleApi { + generate_module: fn() -> ExternalModule, +} + +#[derive(Clone)] +pub(crate) struct DylibContainers { + pub(crate) containers: Vec>>, +} + +impl DylibContainers { + pub fn new() -> Self { + Self { + containers: Vec::new(), + } + } + + pub fn load_modules_from_directory(&mut self, home: Option) { + if let Some(home) = home { + let mut home = PathBuf::from(home); + home.push("native"); + + if home.exists() { + let paths = std::fs::read_dir(home).unwrap(); + + for path in paths { + println!("{:?}", path); + + let path = path.unwrap().path(); + + if path.extension().unwrap() != "so" && path.extension().unwrap() != "dylib" { + continue; + } + + let path_name = path.file_name().and_then(|x| x.to_str()).unwrap(); + log::info!(target: "dylibs", "Loading dylib: {}", path_name); + // Load in the dylib + let cont: Container = unsafe { Container::load(path) } + .expect("Could not open library or load symbols"); + + // Keep the container alive for the duration of the program + // This should probably just get wrapped up with the engine as well, when registering modules, directly + // register an external dylib + self.containers.push(Arc::new(cont)); + } + } else { + log::warn!(target: "dylibs", "$STEEL_HOME/native directory does not exist") + } + } else { + log::warn!(target: "dylibs", "STEEL_HOME variable missing - unable to read shared dylibs") + } + } + + pub fn create_commands(&self) -> Vec { + self.containers + .iter() + .map(|x| x.generate_module()) + .collect() + } +} diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index f57170f50..acb80e060 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -33,6 +33,7 @@ impl TypableCommand { } #[derive(Clone)] +#[repr(C)] pub struct CommandSignature { // Arguments with specific completion methods based on their position. positional_args: &'static [Completer], @@ -42,21 +43,21 @@ pub struct CommandSignature { } impl CommandSignature { - const fn none() -> Self { + pub const fn none() -> Self { Self { positional_args: &[], var_args: completers::none, } } - const fn positional(completers: &'static [Completer]) -> Self { + pub const fn positional(completers: &'static [Completer]) -> Self { Self { positional_args: completers, var_args: completers::none, } } - const fn all(completer: Completer) -> Self { + pub const fn all(completer: Completer) -> Self { Self { positional_args: &[], var_args: completer, diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index dff7b2319..984e89fc9 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -3,6 +3,7 @@ use crate::{ compositor::{Callback, Component, Context, Event, EventResult}, ctrl, key, }; +use steel::rvals::Custom; use tui::buffer::Buffer as Surface; use helix_core::Position; @@ -14,6 +15,8 @@ use helix_view::{ // TODO: share logic with Menu, it's essentially Popup(render_fn), but render fn needs to return // a width/height hint. maybe Popup(Box) +impl Custom for Popup {} + pub struct Popup { contents: T, position: Option, diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index a9ccfb737..2f37cd7c8 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -35,6 +35,7 @@ pub struct Prompt { } #[derive(Clone, Copy, PartialEq, Eq)] +#[repr(C)] pub enum PromptEvent { /// The prompt input has been updated. Update, diff --git a/helix-term/src/ui/text.rs b/helix-term/src/ui/text.rs index a379536f8..67b62bc02 100644 --- a/helix-term/src/ui/text.rs +++ b/helix-term/src/ui/text.rs @@ -3,6 +3,8 @@ use tui::buffer::Buffer as Surface; use helix_view::graphics::Rect; +impl steel::rvals::Custom for Text {} + pub struct Text { pub(crate) contents: tui::text::Text<'static>, size: (u16, u16), diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 735669298..681b20be4 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -26,3 +26,4 @@ once_cell = "1.17" log = "~0.4" helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests", "dylibs"] } \ No newline at end of file diff --git a/helix-tui/src/extension.rs b/helix-tui/src/extension.rs new file mode 100644 index 000000000..47fe62bf4 --- /dev/null +++ b/helix-tui/src/extension.rs @@ -0,0 +1,5 @@ +use crate::{buffer::Buffer, widgets::Widget}; + +use steel::{gc::unsafe_erased_pointers::CustomReference, rvals::Custom}; + +impl CustomReference for Buffer {} diff --git a/helix-tui/src/lib.rs b/helix-tui/src/lib.rs index 59327d7c3..a4ffc186c 100644 --- a/helix-tui/src/lib.rs +++ b/helix-tui/src/lib.rs @@ -130,6 +130,7 @@ pub mod backend; pub mod buffer; +pub mod extension; pub mod layout; pub mod symbols; pub mod terminal; diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index da28de29f..a8973473b 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -46,7 +46,7 @@ which = "4.4" parking_lot = "0.12.1" # plugin support -steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests"] } +steel-core = { path = "../../../steel/crates/steel-core", version = "0.2.0", features = ["modules", "anyhow", "blocking_requests", "dylibs"] } [target.'cfg(windows)'.dependencies] diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 518de7203..000f9bb7b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -804,6 +804,7 @@ use futures_util::stream::{Flatten, Once}; impl steel::gc::unsafe_erased_pointers::CustomReference for Editor {} +#[repr(C)] pub struct Editor { /// Current editing mode. pub mode: Mode, diff --git a/helix-view/src/extension.rs b/helix-view/src/extension.rs new file mode 100644 index 000000000..39bcb3471 --- /dev/null +++ b/helix-view/src/extension.rs @@ -0,0 +1,7 @@ +use steel::{gc::unsafe_erased_pointers::CustomReference, rvals::Custom}; + +use crate::{graphics::Rect, input::Event}; + +impl CustomReference for Event {} +impl Custom for Rect {} +impl Custom for crate::graphics::CursorKind {} diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index d8832adce..02bc06c5a 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -17,6 +17,8 @@ pub enum Event { IdleTimeout, } +impl steel::rvals::Custom for Event {} + #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub struct MouseEvent { /// The kind of mouse event that was caused. diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index c3f67345b..8926111ea 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -19,6 +19,8 @@ pub mod theme; pub mod tree; pub mod view; +pub mod extension; + use std::num::NonZeroUsize; // uses NonZeroUsize so Option use a byte rather than two