From 3ee5829ed7eb7fa90cd014056ab426ec05b5127a Mon Sep 17 00:00:00 2001 From: mattwparas Date: Tue, 11 Jul 2023 21:23:03 -0700 Subject: [PATCH] buffer specific keybindings --- Cargo.lock | 2 +- helix-core/Cargo.toml | 2 +- helix-term/Cargo.toml | 2 +- helix-term/src/commands.rs | 55 +----- helix-term/src/commands/engine.rs | 305 +++++++++++++++++++++++++++--- helix-term/src/commands/typed.rs | 78 +------- helix-term/src/keymap.rs | 19 +- helix-term/src/ui/editor.rs | 46 ++--- helix-tui/Cargo.toml | 2 +- helix-view/Cargo.toml | 2 +- helix-view/src/editor.rs | 2 + helix-view/src/info.rs | 29 ++- 12 files changed, 343 insertions(+), 201 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 418926622..926221595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2732,7 +2732,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "steel-core" -version = "0.2.0" +version = "0.4.0" dependencies = [ "abi_stable", "anyhow", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 25b1bbd4f..71394db63 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -48,7 +48,7 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std" etcetera = "0.8" textwrap = "0.16.0" -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.4.0", features = ["modules", "anyhow", "blocking_requests"] } [dev-dependencies] diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index c08d6a867..7397e2aa2 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", "blocking_requests"] } +steel-core = { path = "../../../steel/crates/steel-core", version = "0.4.0", features = ["modules", "anyhow", "blocking_requests"] } dlopen = "0.1.8" dlopen_derive = "0.1.4" diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6e40a191f..d254608a0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -55,7 +55,7 @@ use movement::Movement; use crate::{ args, - commands::engine::CallbackQueue, + commands::engine::{CallbackQueue, ScriptingEngine}, compositor::{self, Component, Compositor}, filter_picker_entry, job::Callback, @@ -200,29 +200,7 @@ impl MappableCommand { pub fn execute(&self, cx: &mut Context) { log::info!("Running command"); - // TODO: Move this out to a standalone function - while let Some(callback) = CallbackQueue::dequeue() { - log::info!("Found callback: {}", callback); - - if let Err(e) = ENGINE.with(|x| { - let mut guard = x.borrow_mut(); - - { - let res = guard.run_with_reference::( - cx, - "*context*", - &format!("({} *context*)", callback), - ); - - res - } - }) { - cx.editor.set_error(format!("{}", e)); - } - } - 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(); // TODO: Swap the order to allow overriding the existing commands? @@ -235,33 +213,8 @@ 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)); - } + } else { + ScriptingEngine::call_function_if_global_exists(cx, name, args) } } Self::Static { fun, .. } => (fun)(cx), @@ -682,8 +635,6 @@ 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) } diff --git a/helix-term/src/commands/engine.rs b/helix-term/src/commands/engine.rs index a91e34d8d..d3aa241a4 100644 --- a/helix-term/src/commands/engine.rs +++ b/helix-term/src/commands/engine.rs @@ -1,7 +1,8 @@ use fuzzy_matcher::FuzzyMatcher; -use helix_core::{graphemes, Selection, Tendril}; +use helix_core::{graphemes, shellwords::Shellwords, Selection, Tendril}; use helix_view::{ - document::Mode, editor::Action, extension::document_id_to_usize, Document, DocumentId, Editor, + document::Mode, editor::Action, extension::document_id_to_usize, input::KeyEvent, Document, + DocumentId, Editor, }; use once_cell::sync::Lazy; use steel::{ @@ -14,6 +15,8 @@ use steel::{ use std::{ borrow::Cow, collections::{HashMap, VecDeque}, + marker::PhantomData, + path::PathBuf, sync::Mutex, }; use std::{ @@ -25,8 +28,9 @@ use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule}; use crate::{ compositor::{self, Component, Compositor}, + config::Config, job::{self, Callback}, - keymap::{merge_keys, KeyTrie}, + keymap::{self, merge_keys, KeyTrie, KeymapResult, Keymaps}, ui::{self, menu::Item, overlay::overlaid, Popup, Prompt, PromptEvent}, }; @@ -49,6 +53,189 @@ pub struct ExternalContainersAndModules { mod components; +// pub struct PluginEngine(PhantomData); + +pub struct ScriptingEngine; + +pub trait PluginSystem { + fn initialize(); + fn run_initialization_script(cx: &mut Context); + + fn call_function_if_global_exists(cx: &mut Context, name: &str, args: Vec>); + fn call_typed_command_if_global_exists<'a>( + cx: &mut compositor::Context, + input: &'a str, + parts: &'a [&'a str], + event: PromptEvent, + ) -> bool; + + fn get_doc_for_identifier(ident: &str) -> Option; +} + +impl ScriptingEngine { + pub fn initialize() { + initialize_engine(); + } + + pub fn run_initialization_script(cx: &mut Context) { + run_initialization_script(cx) + } + + // Attempt to fetch the keymap for the extension + pub fn get_keymap_for_extension<'a>(cx: &'a mut Context) -> Option { + // Get the currently activated extension, also need to check the + // buffer type. + let extension = { + let current_focus = cx.editor.tree.focus; + let view = cx.editor.tree.get(current_focus); + let doc = &view.doc; + let current_doc = cx.editor.documents.get(doc); + + current_doc + .and_then(|x| x.path()) + .and_then(|x| x.extension()) + .and_then(|x| x.to_str()) + }; + + if let Some(extension) = extension { + let special_buffer_map = "*buffer-or-extension-keybindings*"; + + let value = ENGINE.with(|x| x.borrow().extract_value(special_buffer_map).clone()); + + if let Ok(SteelVal::HashMapV(map)) = value { + if let Some(value) = map.get(&SteelVal::StringV(extension.into())) { + if let SteelVal::Custom(inner) = value { + if let Some(_) = steel::rvals::as_underlying_type::( + inner.borrow().as_ref(), + ) { + return Some(value.clone()); + } + } + } + } + } + + None + } + + pub fn call_function_if_global_exists(cx: &mut Context, name: &str, args: Vec>) { + 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)); + } + } + } + + pub fn call_typed_command_if_global_exists<'a>( + cx: &mut compositor::Context, + input: &'a str, + parts: &'a [&'a str], + event: PromptEvent, + ) -> bool { + 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 { + // TODO: @Matt - extract this whole API call 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..] + .iter() + .map(|x| x.clone().into_steelval().unwrap()) + .collect::>(), + ); + + let mut guard = x.borrow_mut(); + // let mut maybe_callback = None; + + let res = { + 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); + + // if let Some(callback) = cx.callback.take() { + // panic!("Found a callback!"); + // maybe_callback = Some(callback); + // } + + res + }; + + // TODO: Recursively (or otherwise) keep retrying until we're back + // into the engine context, executing a function. We might need to set up + // some sort of fuel or something + // if let Some(callback) = maybe_callback { + // (callback)(_, cx); + // } + + res + }) { + compositor_present_error(cx, e); + }; + } + + // Global exists + true + } else { + // Global does not exist + false + } + } + + pub fn get_doc_for_identifier(ident: &str) -> Option { + if ENGINE.with(|x| x.borrow().global_exists(ident)) { + if let Some(v) = super::engine::ExportedIdentifiers::engine_get_doc(ident) { + return Some(v.into()); + } + + return Some("Run this plugin command!".into()); + } + + None + } +} + // 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>> = @@ -134,6 +321,39 @@ pub fn present_error(cx: &mut Context, e: SteelErr) { cx.jobs.callback(callback); } +// Key maps +#[derive(Clone, Debug)] +pub struct EmbeddedKeyMap(pub HashMap); +impl Custom for EmbeddedKeyMap {} + +pub fn get_keymap() -> EmbeddedKeyMap { + // Snapsnot current configuration for use in forking the keymap + let keymap = Config::load_default().unwrap(); + + // These are the actual mappings that we want + let map = keymap.keys; + + EmbeddedKeyMap(map) +} + +// Base level - no configuration +pub fn default_keymap() -> EmbeddedKeyMap { + EmbeddedKeyMap(keymap::default()) +} + +// Completely empty, allow for overriding +pub fn empty_keymap() -> EmbeddedKeyMap { + EmbeddedKeyMap(HashMap::default()) +} + +pub fn string_to_embedded_keymap(value: String) -> EmbeddedKeyMap { + EmbeddedKeyMap(serde_json::from_str(&value).unwrap()) +} + +pub fn merge_keybindings(left: &mut EmbeddedKeyMap, right: EmbeddedKeyMap) { + merge_keys(&mut left.0, right.0) +} + /// 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) { @@ -238,13 +458,13 @@ impl CallbackQueue { /// 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>>, + raw_bindings: Arc>>, } impl SharedKeyBindingsEventQueue { pub fn new() -> Self { Self { - raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(VecDeque::new())), + raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), } } @@ -253,17 +473,18 @@ impl SharedKeyBindingsEventQueue { .raw_bindings .lock() .unwrap() - .push_back(other_as_json); + .push(other_as_json); } pub fn get() -> Option> { - let mut guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap(); + let guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap(); - if let Some(initial) = guard.pop_front() { - let mut initial = serde_json::from_str(&initial).unwrap(); + if let Some(first) = guard.get(0).clone() { + let mut initial = serde_json::from_str(first).unwrap(); - while let Some(remaining_event) = guard.pop_front() { - let bindings = serde_json::from_str(&remaining_event).unwrap(); + // while let Some(remaining_event) = guard.pop_front() { + for remaining_event in guard.iter() { + let bindings = serde_json::from_str(remaining_event).unwrap(); merge_keys(&mut initial, bindings); } @@ -419,6 +640,14 @@ fn configure_engine() -> std::rc::Rckeymap", string_to_embedded_keymap); + + // Use this to get at buffer specific keybindings + engine.register_value("*buffer-or-extension-keybindings*", SteelVal::Void); // Find the workspace engine.register_fn("helix-find-workspace", || { @@ -509,18 +738,18 @@ fn configure_engine() -> std::rc::Rc(&mut ctx) - .consume(move |engine, mut args| { - // Add the string as an argument to the callback - args.push(input.into_steelval().unwrap()); + if let Err(e) = ENGINE.with(|x| { + x.borrow_mut() + .with_mut_reference::(&mut ctx) + .consume(move |engine, mut args| { + // Add the string as an argument to the callback + args.push(input.into_steelval().unwrap()); - engine.call_function_with_args(cloned_func.clone(), args) - }) - }) - .unwrap(); + engine.call_function_with_args(cloned_func.clone(), args) + }) + }) { + present_error(&mut ctx, e); + } }, ); @@ -792,6 +1021,9 @@ fn configure_engine() -> std::rc::Rc(&mut ctx) - .consume(move |engine, args| { - engine.call_function_with_args(cloned_func.clone(), args) - }) - }) - .unwrap(); + if let Err(e) = ENGINE.with(|x| { + x.borrow_mut() + .with_mut_reference::(&mut ctx) + .consume(move |engine, args| { + engine.call_function_with_args(cloned_func.clone(), args) + }) + }) { + present_error(&mut ctx, e); + } }, ); Ok(call) @@ -1147,6 +1379,17 @@ fn enqueue_command(cx: &mut Context, callback_fn: SteelVal) { cx.jobs.local_callback(callback); } +// Check that we successfully created a directory? +fn create_directory(path: String) { + let path = helix_core::path::get_canonicalized_path(&PathBuf::from(path)).unwrap(); + + if path.exists() { + return; + } else { + std::fs::create_dir(path).unwrap(); + } +} + // fn enqueue_callback(cx: &mut Context, thunk: SteelVal) { // log::info!("Enqueueing callback!"); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index d179da477..dfe70e5b6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -3004,67 +3004,9 @@ pub(super) fn command_mode(cx: &mut Context) { 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 { - // TODO: @Matt - extract this whole API call 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..] - .iter() - .map(|x| x.clone().into_steelval().unwrap()) - .collect::>(), - ); - - let mut guard = x.borrow_mut(); - // let mut maybe_callback = None; - - let res = { - 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); - - // if let Some(callback) = cx.callback.take() { - // panic!("Found a callback!"); - // maybe_callback = Some(callback); - // } - - res - }; - - // TODO: Recursively (or otherwise) keep retrying until we're back - // into the engine context, executing a function. We might need to set up - // some sort of fuel or something - // if let Some(callback) = maybe_callback { - // (callback)(_, cx); - // } - - res - }) { - compositor_present_error(cx, e) - }; - } + } else if ScriptingEngine::call_typed_command_if_global_exists(cx, input, &parts, event) + { + // Engine handles the other cases } else if event == PromptEvent::Validate { cx.editor .set_error(format!("no such command: '{}'", parts[0])); @@ -3081,18 +3023,8 @@ 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()); + } else if let Some(doc) = ScriptingEngine::get_doc_for_identifier(part) { + return Some(doc.into()); } None diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 5a72a35a5..e097c8140 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -303,12 +303,14 @@ impl Keymaps { self.sticky.as_ref() } - /// Lookup `key` in the keymap to try and find a command to execute. Escape - /// key cancels pending keystrokes. If there are no pending keystrokes but a - /// sticky node is in use, it will be cleared. - pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + pub(crate) fn get_with_map( + &mut self, + keymaps: &HashMap, + mode: Mode, + key: KeyEvent, + ) -> KeymapResult { // TODO: remove the sticky part and look up manually - let keymaps = &*self.map(); + // let keymaps = &*self.map(); let keymap = &keymaps[&mode]; if key!(Esc) == key { @@ -356,6 +358,13 @@ impl Keymaps { None => KeymapResult::Cancelled(self.state.drain(..).collect()), } } + + /// Lookup `key` in the keymap to try and find a command to execute. Escape + /// key cancels pending keystrokes. If there are no pending keystrokes but a + /// sticky node is in use, it will be cleared. + pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { + self.get_with_map(&*self.map(), mode, key) + } } impl Default for Keymaps { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index afbe46c55..50135432a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1,5 +1,5 @@ use crate::{ - commands::{self, OnKeyCallback}, + commands::{self, engine::ScriptingEngine, OnKeyCallback}, compositor::{Component, Context, Event, EventResult}, job::{self, Callback}, key, @@ -798,20 +798,26 @@ impl EditorView { ) -> Option { let mut last_mode = mode; self.pseudo_pending.extend(self.keymaps.pending()); - let key_result = self.keymaps.get(mode, event); - cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); - // Get the currently activated minor modes - // let extension = { - // let current_focus = cxt.editor.tree.focus; - // let view = cxt.editor.tree.get(current_focus); - // let doc = &view.doc; - // let current_doc = cxt.editor.documents.get(doc); + // let key_result = self.keymaps.get(mode, event); - // current_doc - // .and_then(|x| x.path()) - // .and_then(|x| x.extension()); - // }; + // Check the engine for any buffer specific keybindings first + let key_result = ScriptingEngine::get_keymap_for_extension(cxt) + .and_then(|map| { + if let steel::SteelVal::Custom(inner) = map { + if let Some(underlying) = steel::rvals::as_underlying_type::< + commands::engine::EmbeddedKeyMap, + >(inner.borrow().as_ref()) + { + return Some(self.keymaps.get_with_map(&underlying.0, mode, event)); + } + } + + None + }) + .unwrap_or_else(|| self.keymaps.get(mode, event)); + + cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); let mut execute_command = |command: &commands::MappableCommand| { command.execute(cxt); @@ -849,20 +855,6 @@ impl EditorView { match &key_result { KeymapResult::Matched(command) => { - // TODO: @Matt - check minor modes here. - // Check current path: - - // let current_focus = cxt.editor.tree.focus; - // let view = cxt.editor.tree.get(current_focus); - // let doc = &view.doc; - // let current_doc = cxt.editor.documents.get(doc); - - // let extension = - - // current_doc.and_then(|x| x.path().and_then(|x| x.to_str().map(|x| x.to_string()))) - - // cxt.editor. - execute_command(command); } KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 9116f39d4..ace8df276 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -26,4 +26,4 @@ once_cell = "1.18" 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"] } \ No newline at end of file +steel-core = { path = "../../../steel/crates/steel-core", version = "0.4.0", features = ["modules", "anyhow", "blocking_requests"] } \ No newline at end of file diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 89faa7f58..318edd489 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.4.0", features = ["modules", "anyhow", "blocking_requests"] } [target.'cfg(windows)'.dependencies] diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index f727bad8c..03ae4f93e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1571,6 +1571,8 @@ impl Editor { if prev_id != view_id { log::info!("Changing focus: {:?}", view_id); + // TODO: Consult map for modes to change given file type? + self.enter_normal_mode(); self.ensure_cursor_in_view(view_id); diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 1503e855e..20e893ee9 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -36,20 +36,33 @@ impl Info { .unwrap(); let mut text = String::new(); + let mut height = 0; + for (item, desc) in body { - let _ = writeln!( - text, - "{:width$} {}", - item.as_ref(), - desc.as_ref(), - width = item_width - ); + let mut line_iter = desc.as_ref().lines(); + + if let Some(first_line) = line_iter.next() { + let _ = writeln!( + text, + "{:width$} {}", + item.as_ref(), + first_line, + width = item_width + ); + height += 1; + } + + for line in line_iter { + let _ = writeln!(text, "{:width$} {}", "", line, width = item_width); + height += 1; + } } Self { title: title.to_string(), width: text.lines().map(|l| l.width()).max().unwrap() as u16, - height: body.len() as u16, + // height: (body.len() + newlines) as u16, + height, text, } }