diff --git a/Cargo.lock b/Cargo.lock index 655a51685..e9bd25f47 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 f4f03dd52..0dae0a925 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 116522d7b..014c2879a 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 7ae603519..62ddf7667 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 fe2fcb6fe..b1f6adb2e 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 ce559d3fa..b01f1be51 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 bd42194e6..f57170f50 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 f58a78983..9d06e283d 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 887863519..784f746c3 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