From 7d8a6c3564b1acd6f5de26915a1e8256e6ac09d8 Mon Sep 17 00:00:00 2001 From: Ulyssa Date: Fri, 9 Aug 2024 20:01:07 -0700 Subject: [PATCH] Keybindings should provide Action to command bar (#144) --- crates/modalkit-ratatui/examples/editor.rs | 4 +- crates/modalkit-ratatui/src/cmdbar.rs | 116 ++++++----- crates/modalkit-ratatui/src/list.rs | 10 +- crates/modalkit-ratatui/src/screen.rs | 14 +- crates/modalkit/src/actions.rs | 30 +-- crates/modalkit/src/editing/application.rs | 4 +- crates/modalkit/src/editing/buffer/mod.rs | 6 +- .../modalkit/src/editing/buffer/selection.rs | 4 +- crates/modalkit/src/editing/rope/mod.rs | 6 + crates/modalkit/src/editing/store/mod.rs | 74 ------- crates/modalkit/src/editing/store/register.rs | 85 ++++++-- crates/modalkit/src/env/emacs/keybindings.rs | 37 +++- crates/modalkit/src/env/vim/keybindings.rs | 79 +++++--- crates/modalkit/src/env/vim/mod.rs | 18 +- crates/modalkit/src/prelude.rs | 24 +-- crates/scansion/src/editor.rs | 6 +- crates/scansion/src/lib.rs | 181 ++++++++---------- 17 files changed, 365 insertions(+), 333 deletions(-) diff --git a/crates/modalkit-ratatui/examples/editor.rs b/crates/modalkit-ratatui/examples/editor.rs index c324b0b..c908b02 100644 --- a/crates/modalkit-ratatui/examples/editor.rs +++ b/crates/modalkit-ratatui/examples/editor.rs @@ -681,7 +681,9 @@ impl Editor { None }, Action::Command(act) => { - let acts = self.store.application.cmds.command(&act, &ctx)?; + let astore = &mut self.store.application; + let rstore = &mut self.store.registers; + let acts = astore.cmds.command(&act, &ctx, rstore)?; self.action_prepend(acts); None diff --git a/crates/modalkit-ratatui/src/cmdbar.rs b/crates/modalkit-ratatui/src/cmdbar.rs index cd53aae..c14d802 100644 --- a/crates/modalkit-ratatui/src/cmdbar.rs +++ b/crates/modalkit-ratatui/src/cmdbar.rs @@ -13,14 +13,7 @@ use std::ops::{Deref, DerefMut}; use ratatui::{buffer::Buffer, layout::Rect, text::Span, widgets::StatefulWidget}; -use modalkit::actions::{ - Action, - CommandAction, - CommandBarAction, - EditorAction, - PromptAction, - Promptable, -}; +use modalkit::actions::{Action, CommandBarAction, PromptAction, Promptable}; use modalkit::editing::{ application::ApplicationInfo, completion::CompletionList, @@ -41,7 +34,8 @@ use super::{ /// Persistent state for rendering [CommandBar]. pub struct CommandBarState { scrollback: ScrollbackState, - searchdir: MoveDir1D, + prompt: String, + action: Option<(Action, EditContext)>, cmdtype: CommandType, tbox_cmd: TextBoxState, tbox_search: TextBoxState, @@ -58,7 +52,8 @@ where CommandBarState { scrollback: ScrollbackState::Pending, - searchdir: MoveDir1D::Next, + prompt: String::new(), + action: None, cmdtype: CommandType::Command, tbox_cmd: TextBoxState::new(buffer_cmd), tbox_search: TextBoxState::new(buffer_search), @@ -71,9 +66,10 @@ where } /// Set the type of command that the bar is being used for. - pub fn set_type(&mut self, ct: CommandType, dir: MoveDir1D) { + pub fn set_type(&mut self, prompt: &str, ct: CommandType, act: &Action, ctx: &EditContext) { + self.prompt = prompt.into(); + self.action = Some((act.clone(), ctx.clone())); self.cmdtype = ct; - self.searchdir = dir; } /// Reset the contents of the bar, and return the contents as an [EditRope]. @@ -124,31 +120,13 @@ where ctx: &EditContext, store: &mut Store, ) -> EditResult, EditContext)>, I> { - let unfocus = CommandBarAction::Unfocus.into(); - - let action = match self.cmdtype { - CommandType::Command => { - let rope = self.reset(); - let text = rope.to_string(); - - store.set_last_cmd(rope); - - CommandAction::Execute(text).into() - }, - CommandType::Search => { - let text = self.reset().trim(); + let rope = self.reset().trim_end_matches(|c| c == '\n'); + store.registers.set_last_command(self.cmdtype, rope); - store.set_last_search(text); + let mut acts = vec![(CommandBarAction::Unfocus.into(), ctx.clone())]; + acts.extend(self.action.take()); - let dir = MoveDirMod::Same; - let count = Count::Contextual; - let target = EditTarget::Search(SearchType::Regex, dir, count); - - EditorAction::Edit(Default::default(), target).into() - }, - }; - - Ok(vec![(unfocus, ctx.clone()), (action, ctx.clone())]) + Ok(acts) } fn abort( @@ -161,15 +139,7 @@ where let act = Action::CommandBar(CommandBarAction::Unfocus); let text = self.reset().trim(); - - match self.cmdtype { - CommandType::Search => { - store.set_aborted_search(text); - }, - CommandType::Command => { - store.set_aborted_cmd(text); - }, - } + store.registers.set_aborted_command(self.cmdtype, text); Ok(vec![(act, ctx.clone())]) } @@ -185,14 +155,8 @@ where let count = ctx.resolve(count); let rope = self.deref().get(); - let text = match self.cmdtype { - CommandType::Search => { - store.searches.recall(&rope, &mut self.scrollback, *dir, prefixed, count) - }, - CommandType::Command => { - store.commands.recall(&rope, &mut self.scrollback, *dir, prefixed, count) - }, - }; + let hist = store.registers.get_command_history(self.cmdtype); + let text = hist.recall(&rope, &mut self.scrollback, *dir, prefixed, count); if let Some(text) = text { self.set_text(text); @@ -261,13 +225,7 @@ where fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { if self.focused { - let prompt = match (state.cmdtype, state.searchdir) { - (CommandType::Command, _) => ":", - (CommandType::Search, MoveDir1D::Next) => "/", - (CommandType::Search, MoveDir1D::Previous) => "?", - }; - - let tbox = TextBox::new().prompt(prompt).oneline(); + let tbox = TextBox::new().prompt(&state.prompt).oneline(); let tbox_state = match state.cmdtype { CommandType::Command => &mut state.tbox_cmd, CommandType::Search => &mut state.tbox_search, @@ -288,3 +246,43 @@ where CommandBar::new() } } + +#[cfg(test)] +mod tests { + use super::*; + use modalkit::editing::application::EmptyInfo; + use modalkit::editing::context::EditContextBuilder; + + #[test] + fn test_set_type_submit() { + let mut store = Store::::default(); + let mut cmdbar = CommandBarState::new(&mut store); + + // Verify that set_type() action and context are returned when the bar is submitted. + let act = Action::Suspend; + let ctx = EditContextBuilder::default().search_regex_dir(MoveDir1D::Previous).build(); + cmdbar.set_type(":", CommandType::Command, &act, &ctx); + + let res = cmdbar.submit(&EditContext::default(), &mut store).unwrap(); + assert_eq!(res.len(), 2); + assert_eq!(res[0].0, Action::from(CommandBarAction::Unfocus)); + assert_eq!(res[0].1, EditContext::default()); + assert_eq!(res[1].0, act); + assert_eq!(res[1].1, ctx); + + // Verify that the most recent set_type() call wins. + let act1 = Action::Suspend; + let ctx1 = EditContextBuilder::default().search_regex_dir(MoveDir1D::Previous).build(); + cmdbar.set_type(":", CommandType::Command, &act1, &ctx1); + let act2 = Action::KeywordLookup; + let ctx2 = EditContextBuilder::default().search_regex_dir(MoveDir1D::Next).build(); + cmdbar.set_type(":", CommandType::Command, &act2, &ctx2); + + let res = cmdbar.submit(&EditContext::default(), &mut store).unwrap(); + assert_eq!(res.len(), 2); + assert_eq!(res[0].0, Action::from(CommandBarAction::Unfocus)); + assert_eq!(res[0].1, EditContext::default()); + assert_eq!(res[1].0, act2); + assert_eq!(res[1].1, ctx2); + } +} diff --git a/crates/modalkit-ratatui/src/list.rs b/crates/modalkit-ratatui/src/list.rs index a83e50f..3963cc2 100644 --- a/crates/modalkit-ratatui/src/list.rs +++ b/crates/modalkit-ratatui/src/list.rs @@ -624,8 +624,8 @@ where let dir = ctx.get_search_regex_dir(); let dir = flip.resolve(&dir); - let lsearch = store.registers.get(&Register::LastSearch)?; - let lsearch = lsearch.value.to_string(); + let lsearch = store.registers.get_last_search(); + let lsearch = lsearch.to_string(); let needle = Regex::new(lsearch.as_ref())?; self.find_regex(&self.cursor, dir, &needle, count).map(|r| r.start) @@ -690,8 +690,8 @@ where let dir = ctx.get_search_regex_dir(); let dir = flip.resolve(&dir); - let lsearch = store.registers.get(&Register::LastSearch)?; - let lsearch = lsearch.value.to_string(); + let lsearch = store.registers.get_last_search(); + let lsearch = lsearch.to_string(); let needle = Regex::new(lsearch.as_ref())?; self.find_regex(&self.cursor, dir, &needle, count) @@ -1610,7 +1610,7 @@ mod tests { fn test_search() { let (mut list, ctx, mut store) = mklist(); - store.set_last_search("on"); + store.registers.set_last_search("on"); assert_eq!(list.cursor.position, 0); diff --git a/crates/modalkit-ratatui/src/screen.rs b/crates/modalkit-ratatui/src/screen.rs index 9019d44..033662d 100644 --- a/crates/modalkit-ratatui/src/screen.rs +++ b/crates/modalkit-ratatui/src/screen.rs @@ -345,10 +345,16 @@ where self.last_message = false; } - fn focus_command(&mut self, ct: CommandType, dir: MoveDir1D) -> EditResult { + fn focus_command( + &mut self, + prompt: &str, + ct: CommandType, + act: &Action, + ctx: &EditContext, + ) -> EditResult { self.focused = CurrentFocus::Command; self.cmdbar.reset(); - self.cmdbar.set_type(ct, dir); + self.cmdbar.set_type(prompt, ct, act, ctx); self.clear_message(); Ok(None) @@ -364,11 +370,11 @@ where /// Perform a command bar action. pub fn command_bar( &mut self, - act: &CommandBarAction, + act: &CommandBarAction, ctx: &EditContext, ) -> EditResult { match act { - CommandBarAction::Focus(ct) => self.focus_command(*ct, ctx.get_search_regex_dir()), + CommandBarAction::Focus(s, ct, act) => self.focus_command(s, *ct, act, ctx), CommandBarAction::Unfocus => self.focus_window(), } } diff --git a/crates/modalkit/src/actions.rs b/crates/modalkit/src/actions.rs index c81d766..98f2da3 100644 --- a/crates/modalkit/src/actions.rs +++ b/crates/modalkit/src/actions.rs @@ -33,6 +33,7 @@ use crate::{ commands::{Command, CommandMachine}, editing::application::*, editing::context::{EditContext, Resolve}, + editing::store::RegisterStore, errors::{EditResult, UIResult}, keybindings::SequenceStatus, prelude::*, @@ -203,7 +204,7 @@ pub enum SelectionAction { /// what you want with [TargetShape::BlockWise] selections. Expand(SelectionBoundary, TargetShapeFilter), - /// Filter selections using the [Register::LastSearch] regular expression. + /// Filter selections using the last regular expression entered for [CommandType::Search]. /// /// The [bool] argument indicates whether we should drop selections that match instead of /// keeping them. @@ -318,13 +319,13 @@ impl CursorAction { #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum CommandAction { - /// Execute a command string. + /// Run a command string. /// /// This should update [Register::LastCommand]. - Execute(String), + Run(String), - /// Repeat the last executed command [*n* times](Count). - Repeat(Count), + /// Execute the last [CommandType::Command] entry [*n* times](Count). + Execute(Count), } /// Trait for objects which can process [CommandActions](CommandAction). @@ -338,6 +339,7 @@ where &mut self, action: &CommandAction, ctx: &C::Context, + rstore: &mut RegisterStore, ) -> UIResult, I>; } @@ -350,11 +352,12 @@ where &mut self, action: &CommandAction, ctx: &C::Context, + rstore: &mut RegisterStore, ) -> UIResult, C::Context)>, I> { match action { - CommandAction::Repeat(count) => { + CommandAction::Execute(count) => { let count = ctx.resolve(count); - let cmd = self.get_last_command(); + let cmd = rstore.get_last_cmd().to_string(); let msg = format!(":{cmd}"); let msg = Action::ShowInfoMessage(msg.into()); let mut acts = vec![(msg, ctx.clone())]; @@ -367,7 +370,8 @@ where Ok(acts) }, - CommandAction::Execute(cmd) => { + CommandAction::Run(cmd) => { + rstore.set_last_cmd(cmd.as_str()); let acts = self.input_cmd(cmd, ctx.clone())?; Ok(acts) @@ -378,9 +382,9 @@ where /// Command bar actions #[derive(Clone, Debug, Eq, PartialEq)] -pub enum CommandBarAction { +pub enum CommandBarAction { /// Focus the command bar - Focus(CommandType), + Focus(String, CommandType, Box>), /// Unfocus the command bar. Unfocus, @@ -735,7 +739,7 @@ pub enum Action { Command(CommandAction), /// Perform a command bar-related action. - CommandBar(CommandBarAction), + CommandBar(CommandBarAction), /// Perform a prompt-related action. Prompt(PromptAction), @@ -905,8 +909,8 @@ impl From for Action { } } -impl From for Action { - fn from(act: CommandBarAction) -> Self { +impl From> for Action { + fn from(act: CommandBarAction) -> Self { Action::CommandBar(act) } } diff --git a/crates/modalkit/src/editing/application.rs b/crates/modalkit/src/editing/application.rs index 7e15510..0e84715 100644 --- a/crates/modalkit/src/editing/application.rs +++ b/crates/modalkit/src/editing/application.rs @@ -161,7 +161,7 @@ use crate::{ /// create additional keybindings and commands on top of the defaults provided within /// [modalkit::env](crate::env). /// -/// [Action::Application]: super::actions::Action::Application +/// [Action::Application]: crate::actions::Action::Application pub trait ApplicationAction: Clone + Debug + Eq + PartialEq + Send { /// Allows controlling how application-specific actions are included in /// [RepeatType::EditSequence](crate::prelude::RepeatType::EditSequence). @@ -177,7 +177,7 @@ pub trait ApplicationAction: Clone + Debug + Eq + PartialEq + Send { /// Allows controlling whether an application-specific action can cause /// a buffer switch on an - /// [EditError::WrongBuffer](crate::actions::EditError::WrongBuffer). + /// [EditError::WrongBuffer](crate::errors::EditError::WrongBuffer). fn is_switchable(&self, ctx: &EditContext) -> bool; } diff --git a/crates/modalkit/src/editing/buffer/mod.rs b/crates/modalkit/src/editing/buffer/mod.rs index 80cb5c4..c3dd0c7 100644 --- a/crates/modalkit/src/editing/buffer/mod.rs +++ b/crates/modalkit/src/editing/buffer/mod.rs @@ -367,7 +367,7 @@ where Regex::new(word.as_str()) }?; - store.set_last_search(needle.to_string()); + store.registers.set_last_command(CommandType::Search, needle.to_string()); let res = self.text.find_regex(&cursor, dir, &needle, count); @@ -397,7 +397,7 @@ where } fn _get_regex(&self, store: &Store) -> EditResult { - let lsearch = store.registers.get(&Register::LastSearch)?.value; + let lsearch = store.registers.get_last_search(); let regex = Regex::new(lsearch.to_string().as_ref())?; return Ok(regex); @@ -2034,7 +2034,7 @@ mod tests { let op = EditAction::Motion; let mv = EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual); - store.set_last_search("he"); + store.registers.set_last_search("he"); // Move to (0, 6) to begin. ebuf.set_leader(gid, Cursor::new(0, 6)); diff --git a/crates/modalkit/src/editing/buffer/selection.rs b/crates/modalkit/src/editing/buffer/selection.rs index 133e50e..73409af 100644 --- a/crates/modalkit/src/editing/buffer/selection.rs +++ b/crates/modalkit/src/editing/buffer/selection.rs @@ -1778,7 +1778,7 @@ mod tests { assert_eq!(ebuf.get_follower_selections(curid), Some(fsels.clone())); // Set regex to /he/. - store.set_last_search("he"); + store.registers.set_last_search("he"); // Keep selections matching /he/. ebuf.selection_filter(false, ctx!(curid, vwctx, vctx), &mut store).unwrap(); @@ -1830,7 +1830,7 @@ mod tests { assert_eq!(ebuf.get_follower_selections(curid), Some(fsels.clone())); // Set regex to /he/. - store.set_last_search("he"); + store.registers.set_last_search("he"); // Drop selections matching /he/. ebuf.selection_filter(true, ctx!(curid, vwctx, vctx), &mut store).unwrap(); diff --git a/crates/modalkit/src/editing/rope/mod.rs b/crates/modalkit/src/editing/rope/mod.rs index 83afd88..fb20360 100644 --- a/crates/modalkit/src/editing/rope/mod.rs +++ b/crates/modalkit/src/editing/rope/mod.rs @@ -2241,6 +2241,12 @@ impl EditRope { } } +impl Default for EditRope { + fn default() -> Self { + EditRope::empty() + } +} + impl Add for EditRope { type Output = Self; diff --git a/crates/modalkit/src/editing/store/mod.rs b/crates/modalkit/src/editing/store/mod.rs index 95fdacb..e3bc042 100644 --- a/crates/modalkit/src/editing/store/mod.rs +++ b/crates/modalkit/src/editing/store/mod.rs @@ -22,8 +22,6 @@ use std::sync::{Arc, RwLock}; use crate::editing::application::ApplicationInfo; -use crate::editing::history::HistoryList; -use crate::editing::rope::EditRope; mod buffer; mod complete; @@ -37,9 +35,6 @@ pub use self::cursor::{AdjustStore, CursorStore, GlobalAdjustable}; pub use self::digraph::DigraphStore; pub use self::register::{RegisterCell, RegisterError, RegisterPutFlags, RegisterStore}; -const COMMAND_HISTORY_LEN: usize = 50; -const SEARCH_HISTORY_LEN: usize = 50; - /// Global editing context pub struct Store { /// Tracks what [buffers](crate::editing::buffer::EditBuffer) have been created. @@ -57,12 +52,6 @@ pub struct Store { /// Tracks globally-relevant cursors and cursor groups. pub cursors: CursorStore, - /// Tracks previous commands. - pub commands: HistoryList, - - /// Tracks previous search expressions. - pub searches: HistoryList, - /// Application-specific storage. pub application: I::Store, } @@ -83,9 +72,6 @@ where registers: RegisterStore::default(), cursors: CursorStore::default(), - commands: HistoryList::new("".into(), COMMAND_HISTORY_LEN), - searches: HistoryList::new("".into(), SEARCH_HISTORY_LEN), - application, } } @@ -99,66 +85,6 @@ where pub fn load_buffer(&mut self, id: I::ContentId) -> SharedBuffer { self.buffers.load(id) } - - /// Add a command to the command history after the prompt has been aborted. - /// - /// This will not update [Register::LastCommand]. - /// - /// [Register::LastCommand]: crate::prelude::Register::LastCommand - pub fn set_aborted_cmd>(&mut self, text: T) { - let rope = text.into(); - - if rope.is_empty() { - let _ = self.commands.end(); - } else { - self.commands.select(rope); - } - } - - /// Add a search query to the search history after the prompt has been aborted. - /// - /// This will not update [Register::LastSearch]. - /// - /// [Register::LastSearch]: crate::prelude::Register::LastSearch - pub fn set_aborted_search>(&mut self, text: T) { - let rope = text.into(); - - if rope.is_empty() { - let _ = self.searches.end(); - } else { - self.searches.select(rope); - } - } - - /// Add a command to the command history, and set [Register::LastCommand]. - /// - /// [Register::LastCommand]: crate::prelude::Register::LastCommand - pub fn set_last_cmd>(&mut self, text: T) { - let rope = text.into(); - - if rope.is_empty() { - // Disallow empty commands. - return; - } - - self.commands.select(rope.clone()); - self.registers.set_last_cmd(rope); - } - - /// Add a search query to the search history, and set [Register::LastSearch]. - /// - /// [Register::LastSearch]: crate::prelude::Register::LastSearch - pub fn set_last_search>(&mut self, text: T) { - let rope = text.into(); - - if rope.is_empty() { - // Disallow empty searches. - return; - } - - self.searches.select(rope.clone()); - self.registers.set_last_search(rope); - } } impl Default for Store diff --git a/crates/modalkit/src/editing/store/register.rs b/crates/modalkit/src/editing/store/register.rs index 500676f..82fd953 100644 --- a/crates/modalkit/src/editing/store/register.rs +++ b/crates/modalkit/src/editing/store/register.rs @@ -9,9 +9,10 @@ use arboard::{GetExtLinux, LinuxClipboardKind, SetExtLinux}; #[cfg(feature = "clipboard")] use std::cell::{RefCell, RefMut}; +use crate::editing::history::HistoryList; use crate::editing::rope::EditRope; -use crate::prelude::Register; use crate::prelude::TargetShape::{self, BlockWise, CharWise, LineWise}; +use crate::prelude::{CommandType, Register}; #[cfg(all(feature = "clipboard", target_os = "linux"))] mod clipboard { @@ -102,6 +103,12 @@ pub struct RegisterCell { pub value: EditRope, } +#[derive(Default)] +struct CommandHistory { + history: HistoryList, + last_used: EditRope, +} + /// Storage for [Register] values. /// /// Registers are used to save different types of values during editing: @@ -114,12 +121,12 @@ pub struct RegisterCell { /// [EditAction::Yank]: crate::actions::EditAction::Yank /// [MacroAction::ToggleRecording]: crate::actions::MacroAction::ToggleRecording pub struct RegisterStore { + last_commands: HashMap, + altbufname: RegisterCell, curbufname: RegisterCell, - last_command: RegisterCell, last_inserted: RegisterCell, - last_search: RegisterCell, last_yanked: RegisterCell, last_deleted: Vec, last_macro: Option, @@ -219,12 +226,12 @@ impl From<(TargetShape, &str)> for RegisterCell { impl RegisterStore { fn new() -> Self { RegisterStore { + last_commands: HashMap::default(), + altbufname: RegisterCell::default(), curbufname: RegisterCell::default(), - last_command: RegisterCell::default(), last_inserted: RegisterCell::default(), - last_search: RegisterCell::default(), last_yanked: RegisterCell::default(), last_deleted: vec![RegisterCell::default(); 9], last_macro: None, @@ -268,7 +275,6 @@ impl RegisterStore { Register::SmallDelete => self.small_delete.clone(), Register::Named(name) => self.named.get(name).cloned().unwrap_or_default(), Register::AltBufName => self.altbufname.clone(), - Register::LastSearch => self.last_search.clone(), Register::LastYanked => self.last_yanked.clone(), /* @@ -317,7 +323,7 @@ impl RegisterStore { * Read-only registers. */ Register::CurBufName => self.curbufname.clone(), - Register::LastCommand => self.last_command.clone(), + Register::LastCommand(ct) => self._get_last_cmd(*ct).into(), Register::LastInserted => self.last_inserted.clone(), /* @@ -420,9 +426,8 @@ impl RegisterStore { * Read-only registers don't write anywhere. */ Register::CurBufName => cell, - Register::LastCommand => cell, + Register::LastCommand(_) => cell, Register::LastInserted => cell, - Register::LastSearch => cell, }; if !flags.contains(RegisterPutFlags::NOTEXT) { @@ -450,12 +455,66 @@ impl RegisterStore { } } - pub(super) fn set_last_cmd>(&mut self, rope: T) { - self.last_command = RegisterCell::from(rope.into()); + #[inline] + pub(crate) fn _get_last_cmd(&self, ct: CommandType) -> EditRope { + if let Some(hist) = self.last_commands.get(&ct) { + hist.last_used.clone() + } else { + EditRope::empty() + } + } + + /// Update the value and history of [Register::LastCommand] for the given [CommandType]. + pub fn get_command_history(&mut self, ct: CommandType) -> &mut HistoryList { + &mut self.last_commands.entry(ct).or_default().history + } + + /// Update the value and history of [Register::LastCommand] for the given [CommandType]. + pub fn set_last_command>(&mut self, ct: CommandType, rope: T) { + let rope = rope.into(); + + if rope.is_empty() { + // Disallow updating with an empty value. + return; + } + + let hist = self.last_commands.entry(ct).or_default(); + hist.history.select(rope.clone()); + hist.last_used = rope; + } + + /// Add an item to the history for [CommandType] without updating the last used value. + pub fn set_aborted_command>(&mut self, ct: CommandType, text: T) { + let rope = text.into(); + let hist = self.last_commands.entry(ct).or_default(); + + if rope.is_empty() { + let _ = hist.history.end(); + } else { + hist.history.select(rope); + } + } + + /// Get the value of `Register::LastCommand(CommandType::Command)`. + pub fn get_last_cmd(&self) -> EditRope { + self._get_last_cmd(CommandType::Command) + } + + /// Add a command to the command history, and set + /// `Register::LastCommand(CommandType::Command)`. + pub fn set_last_cmd>(&mut self, rope: T) { + self.set_last_command(CommandType::Command, rope); + } + + /// Add a command to the command history, and set + /// `Register::LastCommand(CommandType::Search)`. + pub fn set_last_search>(&mut self, rope: T) { + self.set_last_command(CommandType::Search, rope); } - pub(super) fn set_last_search>(&mut self, rope: T) { - self.last_search = RegisterCell::from(rope.into()); + /// Get the value of `Register::LastCommand(CommandType::Search)`. + pub fn get_last_search(&self) -> EditRope { + self._get_last_cmd(CommandType::Search) } } diff --git a/crates/modalkit/src/env/emacs/keybindings.rs b/crates/modalkit/src/env/emacs/keybindings.rs index 573d2e1..a019521 100644 --- a/crates/modalkit/src/env/emacs/keybindings.rs +++ b/crates/modalkit/src/env/emacs/keybindings.rs @@ -16,6 +16,7 @@ use bitflags::bitflags; use crate::{ actions::{ Action, + CommandAction, CommandBarAction, EditAction, EditorAction, @@ -371,8 +372,8 @@ macro_rules! just_one_space { } macro_rules! cmdbar_focus { - ($type: expr, $nm: expr) => { - cmdbar!(CommandBarAction::Focus($type), $nm) + ($type: expr, $nm: expr, $act: expr) => { + cmdbar!(CommandBarAction::Focus(":".into(), $type, Box::new(Action::from($act))), $nm) }; } @@ -380,7 +381,20 @@ macro_rules! cmdbar_search { ($dir: expr) => { is!( InternalAction::SetSearchRegexParams($dir, true), - Action::CommandBar(CommandBarAction::Focus(CommandType::Search)), + Action::CommandBar(CommandBarAction::Focus( + match $dir { + MoveDir1D::Next => "/".into(), + MoveDir1D::Previous => "?".into(), + }, + CommandType::Search, + Box::new( + EditorAction::Edit( + Specifier::Contextual, + EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual) + ) + .into() + ) + )), EmacsMode::Search ) }; @@ -577,7 +591,7 @@ fn default_keys() -> Vec<(MappedModes, &'static str, InputSt ( IMAP, ".", search_word!(WordStyle::Big) ), ( IMAP, "", unmapped!() ), ( IMAP, "", scroll2d!(MoveDir2D::Up, ScrollSize::Page) ), - ( IMAP, "", cmdbar_focus!(CommandType::Command, EmacsMode::Command) ), + ( IMAP, "", cmdbar_focus!(CommandType::Command, EmacsMode::Command, CommandAction::Execute(1.into())) ), ( IMAP, "{char}", kill_target!(EditTarget::Search(SearchType::Char(true), MoveDirMod::Same, Count::Contextual)) ), ( IMAP, ".", search_word!(WordStyle::Big) ), ( IMAP, "", erase_range!(RangeType::Word(WordStyle::Whitespace(false))) ), @@ -769,7 +783,16 @@ mod tests { } const CMDBAR_ABORT: Action = Action::Prompt(PromptAction::Abort(false)); - const CMDBAR_SEARCH: Action = Action::CommandBar(CommandBarAction::Focus(CommandType::Search)); + + fn cmdbar_focus(s: &str, ct: CommandType, act: Action) -> Action { + Action::CommandBar(CommandBarAction::Focus(s.into(), ct, act.into())) + } + + fn cmdbar_search(s: &str) -> Action { + let search = EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual); + let search = EditorAction::Edit(Specifier::Contextual, search); + cmdbar_focus(s, CommandType::Search, search.into()) + } fn mkctx() -> EditContext { EditContextBuilder::default() @@ -840,7 +863,7 @@ mod tests { // ^R moves to Search mode. ctx.search_regex_dir = MoveDir1D::Previous; vm.input_key(ctl!('r')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("?"), ctx); assert_eq!(vm.mode(), EmacsMode::Search); // Unmapped, typable character types a character. @@ -870,7 +893,7 @@ mod tests { // ^S moves to Search mode. vm.input_key(ctl!('s')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("/"), ctx); assert_eq!(vm.mode(), EmacsMode::Search); // ^G leaves Search mode. diff --git a/crates/modalkit/src/env/vim/keybindings.rs b/crates/modalkit/src/env/vim/keybindings.rs index 4ea7d29..37c8692 100644 --- a/crates/modalkit/src/env/vim/keybindings.rs +++ b/crates/modalkit/src/env/vim/keybindings.rs @@ -275,11 +275,11 @@ impl InternalAction { } } -#[derive(Debug)] +#[derive(Clone, Debug)] enum ExternalAction { Something(Action), CountAlters(Vec>, Vec>), - CommandEnter(CommandType, VimMode), + CommandEnter(String, CommandType, Action, VimMode), CommandExit(PromptAction), MacroToggle(bool), PostAction, @@ -296,8 +296,8 @@ impl ExternalAction { acts2.clone() } }, - ExternalAction::CommandEnter(ct, mode) => { - let act = CommandBarAction::Focus(*ct); + ExternalAction::CommandEnter(s, ct, act, mode) => { + let act = CommandBarAction::Focus(s.clone(), *ct, Box::new(act.clone())); let mode = context.action.postmode.take().unwrap_or(*mode); let shape = context.persist.shape.take(); let actx = Box::new(context.action.clone()).into(); @@ -340,21 +340,6 @@ impl ExternalAction { } } -impl Clone for ExternalAction { - fn clone(&self) -> Self { - match self { - ExternalAction::Something(act) => ExternalAction::Something(act.clone()), - ExternalAction::CountAlters(act1, act2) => { - ExternalAction::CountAlters(act1.clone(), act2.clone()) - }, - ExternalAction::CommandEnter(ct, mode) => ExternalAction::CommandEnter(*ct, *mode), - ExternalAction::CommandExit(act) => ExternalAction::CommandExit(act.clone()), - ExternalAction::MacroToggle(reqrec) => ExternalAction::MacroToggle(*reqrec), - ExternalAction::PostAction => ExternalAction::PostAction, - } - } -} - /// Description of actions to take after an input sequence. #[derive(Debug, Default)] pub struct InputStep { @@ -1252,7 +1237,16 @@ macro_rules! window_file { macro_rules! cmdbar_focus { ($type: expr, $mode: expr) => { - isv!(vec![], vec![ExternalAction::CommandEnter($type, $mode)], VimMode::Command) + isv!( + vec![], + vec![ExternalAction::CommandEnter( + ":".into(), + $type, + CommandAction::Execute(1.into()).into(), + $mode + )], + VimMode::Command + ) }; } @@ -1260,7 +1254,19 @@ macro_rules! search { ($dir: expr, $mode: expr) => { isv!( vec![InternalAction::SetSearchRegexParams($dir, false)], - vec![ExternalAction::CommandEnter(CommandType::Search, $mode)], + vec![ExternalAction::CommandEnter( + match $dir { + MoveDir1D::Next => "/".into(), + MoveDir1D::Previous => "?".into(), + }, + CommandType::Search, + EditorAction::Edit( + Specifier::Contextual, + EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual) + ) + .into(), + $mode + )], VimMode::Command ) }; @@ -1598,7 +1604,7 @@ fn default_keys() -> Vec<(MappedModes, &'static str, InputSt ( NMAP, "~", tilde!() ), ( NMAP, ".", act!(Action::Repeat(RepeatType::EditSequence)) ), ( NMAP, "@{register}", act!(MacroAction::Execute(Count::Contextual)) ), - ( NMAP, "@:", command!(CommandAction::Repeat(Count::Contextual)) ), + ( NMAP, "@:", command!(CommandAction::Execute(Count::Contextual)) ), ( NMAP, "@@", act!(MacroAction::Repeat(Count::Contextual)) ), ( NMAP, "", edit_target!(EditAction::ChangeNumber(NumberChange::Increase(Count::Contextual), false), EditTarget::Motion(MoveType::LinePos(MovePosition::End), 0.into())) ), ( NMAP, "", jump!(PositionList::JumpList, MoveDir1D::Next) ), @@ -2234,9 +2240,7 @@ mod tests { EditTarget::Motion(MoveType::Column(MoveDir1D::Previous, false), Count::Exact(1)), )); const CHECKPOINT: Action = Action::Editor(EditorAction::History(HistoryAction::Checkpoint)); - const CMDBAR: Action = Action::CommandBar(CommandBarAction::Focus(CommandType::Command)); const CMDBAR_ABORT: Action = Action::Prompt(PromptAction::Abort(false)); - const CMDBAR_SEARCH: Action = Action::CommandBar(CommandBarAction::Focus(CommandType::Search)); const CURSOR_CLOSE: Action = Action::Editor(EditorAction::Cursor(CursorAction::Close(CursorCloseTarget::Followers))); const CURSOR_SPLIT: Action = @@ -2259,6 +2263,21 @@ mod tests { InsertTextAction::Type(Specifier::Contextual, MoveDir1D::Previous, Count::Exact(1)), )); + fn cmdbar_focus(s: &str, ct: CommandType, act: Action) -> Action { + Action::CommandBar(CommandBarAction::Focus(s.into(), ct, act.into())) + } + + fn cmdbar_command() -> Action { + let exec = CommandAction::Execute(1.into()); + cmdbar_focus(":", CommandType::Command, exec.into()) + } + + fn cmdbar_search(s: &str) -> Action { + let search = EditTarget::Search(SearchType::Regex, MoveDirMod::Same, Count::Contextual); + let search = EditorAction::Edit(Specifier::Contextual, search); + cmdbar_focus(s, CommandType::Search, search.into()) + } + #[test] fn test_transitions_normal() { let mut vm: VimMachine = default_vim_keys(); @@ -2395,7 +2414,7 @@ mod tests { // Move to Command mode using ":". vm.input_key(key!(':')); - assert_pop2!(vm, CMDBAR, ctx); + assert_pop2!(vm, cmdbar_command(), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. @@ -2429,7 +2448,7 @@ mod tests { // Move to Command mode (forward search) using "/". ctx.search_regex_dir = MoveDir1D::Next; vm.input_key(key!('/')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("/"), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. @@ -2452,7 +2471,7 @@ mod tests { // Move to Command mode (reverse search) using "?". ctx.search_regex_dir = MoveDir1D::Previous; vm.input_key(key!('?')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("?"), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. @@ -2482,7 +2501,7 @@ mod tests { ctx.target_shape = None; ctx.search_regex_dir = MoveDir1D::Next; vm.input_key(key!('/')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("/"), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. @@ -2511,7 +2530,7 @@ mod tests { ctx.target_shape = None; ctx.search_regex_dir = MoveDir1D::Next; vm.input_key(key!('/')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("/"), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. @@ -2543,7 +2562,7 @@ mod tests { vm.input_key(key!('2')); vm.input_key(key!('c')); vm.input_key(key!('/')); - assert_pop2!(vm, CMDBAR_SEARCH, ctx); + assert_pop2!(vm, cmdbar_search("/"), ctx); assert_eq!(vm.mode(), VimMode::Command); // Unmapped key types that character. diff --git a/crates/modalkit/src/env/vim/mod.rs b/crates/modalkit/src/env/vim/mod.rs index 9563319..cd45e50 100644 --- a/crates/modalkit/src/env/vim/mod.rs +++ b/crates/modalkit/src/env/vim/mod.rs @@ -558,9 +558,9 @@ fn register_to_char((reg, append): &(Register, bool)) -> Option { Register::UnnamedMacro => '@', Register::UnnamedCursorGroup => return None, Register::SmallDelete => '-', - Register::LastCommand => ':', + Register::LastCommand(CommandType::Command) => ':', + Register::LastCommand(CommandType::Search) => '/', Register::LastInserted => '.', - Register::LastSearch => '/', Register::LastYanked => '0', Register::AltBufName => '#', Register::CurBufName => '%', @@ -598,9 +598,9 @@ fn char_to_register(c: char) -> Option<(Register, bool)> { '#' => Register::AltBufName, '_' => Register::Blackhole, '%' => Register::CurBufName, - ':' => Register::LastCommand, + ':' => Register::LastCommand(CommandType::Command), + '/' => Register::LastCommand(CommandType::Search), '.' => Register::LastInserted, - '/' => Register::LastSearch, '*' => Register::SelectionPrimary, '+' => Register::SelectionClipboard, @@ -698,7 +698,10 @@ mod tests { assert_eq!(char_to_register('1'), Some((Register::RecentlyDeleted(0), false))); assert_eq!(char_to_register('3'), Some((Register::RecentlyDeleted(2), false))); assert_eq!(char_to_register('"'), Some((Register::Unnamed, false))); - assert_eq!(char_to_register('/'), Some((Register::LastSearch, false))); + assert_eq!( + char_to_register('/'), + Some((Register::LastCommand(CommandType::Search), false)) + ); // Unmapped names. assert_eq!(char_to_register('['), None); @@ -712,7 +715,10 @@ mod tests { assert_eq!(register_to_char(&(Register::RecentlyDeleted(0), false)).unwrap(), "1"); assert_eq!(register_to_char(&(Register::RecentlyDeleted(2), false)).unwrap(), "3"); assert_eq!(register_to_char(&(Register::Unnamed, false)).unwrap(), "\""); - assert_eq!(register_to_char(&(Register::LastSearch, false)).unwrap(), "/"); + assert_eq!( + register_to_char(&(Register::LastCommand(CommandType::Search), false)).unwrap(), + "/" + ); // Registers that don't have names. assert_eq!(register_to_char(&(Register::UnnamedCursorGroup, false)), None); diff --git a/crates/modalkit/src/prelude.rs b/crates/modalkit/src/prelude.rs index f319a12..1e6cd25 100644 --- a/crates/modalkit/src/prelude.rs +++ b/crates/modalkit/src/prelude.rs @@ -4,7 +4,7 @@ //! //! These types are used to specify the details of [actions]. //! -//! [actions]: crate::action +//! [actions]: crate::actions use std::fmt::{self, Debug, Display, Formatter}; use std::hash::Hash; @@ -265,12 +265,12 @@ pub enum SearchType { /// Search for a regular expression. Regex, - /// Search for the word currently under the cursor, and update [Register::LastSearch] via - /// [Store::set_last_search]. + /// Search for the word currently under the cursor, and update the last [CommandType::Search]. + /// via [RegisterStore::set_last_search]. /// /// [bool] controls whether matches should be checked for using word boundaries. /// - /// [Store::set_last_search]: crate::editing::store::Store::set_last_search + /// [RegisterStore::set_last_search]: crate::editing::store::RegisterStore::set_last_search Word(WordStyle, bool), } @@ -1373,20 +1373,15 @@ pub enum Register { /// For example, `"-` in Vim. SmallDelete, - /// A register containing the last executed command. - /// - /// For example, `":` in Vim. - LastCommand, - /// A register containing the last inserted text. /// /// For example, `".` in Vim. LastInserted, - /// A register containing the last search expression. + /// A register containing the last value entered for a [CommandType]. /// - /// For example, `"/` in Vim. - LastSearch, + /// For example, `":` and `"/` in Vim. + LastCommand(CommandType), /// A register containing the last copied text. /// @@ -1440,9 +1435,8 @@ impl Register { Register::UnnamedMacro => false, Register::RecentlyDeleted(_) => false, Register::SmallDelete => false, - Register::LastCommand => false, + Register::LastCommand(_) => false, Register::LastInserted => false, - Register::LastSearch => false, Register::LastYanked => false, Register::AltBufName => false, Register::CurBufName => false, @@ -1521,7 +1515,7 @@ pub enum RangeEndingType { /// The position of a given [Mark]. Mark(Specifier), - /// The line matching a search using the value of [Register::LastSearch]. + /// The line matching a search using the last value of [CommandType::Search]. Search(MoveDir1D), /// Perform a search using the last substitution pattern. diff --git a/crates/scansion/src/editor.rs b/crates/scansion/src/editor.rs index 8e14e4d..87e5f41 100644 --- a/crates/scansion/src/editor.rs +++ b/crates/scansion/src/editor.rs @@ -137,7 +137,7 @@ where fn _redraw_wrap( &mut self, - prompt: &Option, + prompt: Option<&str>, off: u16, context: &mut EditorContext, ) -> Result { @@ -265,7 +265,7 @@ where fn _redraw_nowrap( &mut self, - _: &Option, + _: Option<&str>, _: u16, _: &mut EditorContext, ) -> Result { @@ -278,7 +278,7 @@ where pub fn redraw( &mut self, - prompt: &Option, + prompt: Option<&str>, off: u16, context: &mut EditorContext, ) -> Result { diff --git a/crates/scansion/src/lib.rs b/crates/scansion/src/lib.rs index e4d4d9e..136f411 100644 --- a/crates/scansion/src/lib.rs +++ b/crates/scansion/src/lib.rs @@ -51,6 +51,7 @@ #![allow(clippy::needless_return)] #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] +use std::collections::VecDeque; use std::io::{self, Write}; use std::process; use std::time::Duration; @@ -101,8 +102,9 @@ fn is_newline(c: char) -> bool { c == '\n' || c == '\r' } -enum InternalResult { +enum InternalResult { Submitted(EditRope), + Actions(Vec<(Action, EditContext)>), Nothing, } @@ -181,6 +183,7 @@ pub struct ReadLine where I: ApplicationInfo, { + actstack: VecDeque<(Action, EditContext)>, bindings: KeyManager, RepeatType>, store: Store, @@ -188,8 +191,10 @@ where context: EditorContext, dimensions: (u16, u16), + ct: Option, - sd: MoveDir1D, + act: Option<(Action, EditContext)>, + cprompt: String, line: Editor, cmd: Editor, @@ -219,10 +224,12 @@ where cmd.resize(dimensions.0, dimensions.1); search.resize(dimensions.0, dimensions.1); + let actstack = VecDeque::default(); let bindings = KeyManager::new(bindings); let history = HistoryList::new("".into(), HISTORY_LENGTH); - let rl = ReadLine { + Ok(ReadLine { + actstack, bindings, store, @@ -230,15 +237,15 @@ where context, dimensions, + ct: None, - sd: MoveDir1D::Next, + act: None, + cprompt: String::new(), line, cmd, search, - }; - - return Ok(rl); + }) } /// Prompt the user for input. @@ -252,9 +259,13 @@ where self.bindings.input_key(key); - while let Some((action, ctx)) = self.bindings.pop() { + while let Some((action, ctx)) = self.action_pop() { match self.act(action, ctx) { Ok(InternalResult::Nothing) => continue, + Ok(InternalResult::Actions(acts)) => { + self.action_prepend(acts); + continue; + }, Ok(InternalResult::Submitted(res)) => { self.linebreak()?; @@ -274,9 +285,23 @@ where } } + fn action_prepend(&mut self, acts: Vec<(Action, EditContext)>) { + let mut acts = VecDeque::from(acts); + acts.append(&mut self.actstack); + self.actstack = acts; + } + + fn action_pop(&mut self) -> Option<(Action, EditContext)> { + if let res @ Some(_) = self.actstack.pop_front() { + res + } else { + self.bindings.pop() + } + } + fn step(&mut self, prompt: &Option) -> Result> { loop { - self.redraw(prompt)?; + self.redraw(prompt.as_deref())?; if !poll(Duration::from_millis(500))? { continue; @@ -306,7 +331,7 @@ where } } - fn suspend(&mut self) -> Result> { + fn suspend(&mut self) -> Result, ReadLineError> { // Restore old terminal state. crossterm::terminal::disable_raw_mode()?; self.context.stdout.queue(CursorShow)?.flush()?; @@ -332,13 +357,14 @@ where fn command_bar( &mut self, - act: &CommandBarAction, + act: &CommandBarAction, ctx: EditContext, - ) -> Result> { + ) -> Result, ReadLineError> { match act { - CommandBarAction::Focus(ct) => { + CommandBarAction::Focus(s, ct, act) => { self.ct = Some(*ct); - self.sd = ctx.get_search_regex_dir(); + self.act = Some((act.as_ref().clone(), ctx)); + self.cprompt = s.clone(); Ok(InternalResult::Nothing) }, @@ -354,13 +380,13 @@ where &mut self, act: PromptAction, ctx: EditContext, - ) -> Result> { + ) -> Result, ReadLineError> { match act { PromptAction::Submit => { let res = self.submit(); self.ct = None; - return res; + Ok(res) }, PromptAction::Abort(empty) => { self.abort(empty); @@ -376,26 +402,14 @@ where } fn abort(&mut self, empty: bool) { - match self.ct { - None => {}, - Some(CommandType::Search) => { - if empty && !self.cmd.is_blank() { - return; - } - - let txt = self.reset_cmd(); - self.store.set_aborted_search(txt); - self.ct = None; - }, - Some(CommandType::Command) => { - if empty && !self.cmd.is_blank() { - return; - } + if empty && !self.cmd.is_blank() { + return; + } - let txt = self.reset_cmd(); - self.store.set_aborted_cmd(txt); - self.ct = None; - }, + if let Some(ct) = self.ct { + let txt = self.reset_cmd(); + self.store.registers.set_aborted_command(ct, txt); + self.ct = None; } } @@ -408,18 +422,14 @@ where self.line.set_text(text); } }, - Some(CommandType::Search) => { - let text = self.cmd.recall(&mut self.store.searches, dir, prefixed, count); + Some(ct) => { + let hist = self.store.registers.get_command_history(ct); + let text = self.cmd.recall(hist, dir, prefixed, count); if let Some(text) = text { self.cmd.set_text(text); } }, - Some(CommandType::Command) => { - // Does nothing for now. - - return; - }, } } @@ -435,9 +445,10 @@ where // If the search bar is focused, but nothing has been typed, we move backwards to the // previously typed search and use that. + let hist = self.store.registers.get_command_history(CommandType::Search); let text = self .cmd - .recall(&mut self.store.searches, MoveDir1D::Previous, false, 1) + .recall(hist, MoveDir1D::Previous, false, 1) .ok_or(EditError::NoSearch)?; let re = Regex::new(text.to_string().as_ref())?; @@ -451,7 +462,7 @@ where let re = if let Some(CommandType::Search) = self.ct { self.get_cmd_regex()? } else { - let text = self.store.registers.get(&Register::LastSearch)?.value; + let text = self.store.registers.get_last_search(); Regex::new(text.to_string().as_ref())? }; @@ -485,18 +496,21 @@ where Ok(None) } - fn incsearch(&mut self, ctx: &EditContext) -> Result<(), EditError> { - if let Some(CommandType::Search) = self.ct { - if !ctx.is_search_incremental() { - return Ok(()); - } + fn incsearch(&mut self, ctx: &EditContext) -> EditResult<(), I> { + let Some(CommandType::Search) = self.ct else { + return Ok(()); + }; - let needle = self.cmd.get_trim().to_string(); - let needle = Regex::new(needle.as_ref())?; + if !ctx.is_search_incremental() { + return Ok(()); + } - if let Some(text) = self.line.find(&mut self.history, &needle, self.sd, true) { - self.line.set_text(text); - } + let needle = self.cmd.get_trim().to_string(); + let needle = Regex::new(needle.as_ref())?; + let dir = ctx.get_search_regex_dir(); + + if let Some(text) = self.line.find(&mut self.history, &needle, dir, true) { + self.line.set_text(text); } Ok(()) @@ -535,49 +549,24 @@ where } } - fn submit(&mut self) -> Result> { - match self.ct { - None => { - let text = focused_mut!(self).reset(); - - self.history.select(text.clone()); - - return Ok(InternalResult::Submitted(text)); - }, - Some(CommandType::Search) => { - let text = self.reset_cmd(); - let needle = match Regex::new(text.to_string().as_ref()) { - Err(e) => return Err(EditError::from(e).into()), - Ok(r) => r, - }; - - self.store.set_last_search(text); + fn submit(&mut self) -> InternalResult { + if let Some(ct) = self.ct { + let text = self.reset_cmd(); + self.store.registers.set_last_command(ct, text); - if let Some(text) = self.line.find(&mut self.history, &needle, self.sd, false) { - self.line.set_text(text); - } - - return Ok(InternalResult::Nothing); - }, - Some(CommandType::Command) => { - let cmd = self.reset_cmd().trim().to_string(); - let err = ReadLineError::UnknownCommand(cmd); - - return Err(err); - }, - } - } - - fn cmd_prompt(&self) -> Option { - match (&self.ct, &self.sd) { - (Some(CommandType::Search), MoveDir1D::Next) => Some("/".into()), - (Some(CommandType::Search), MoveDir1D::Previous) => Some("?".into()), - (Some(CommandType::Command), _) => Some(":".into()), - (None, _) => None, + if let Some(act) = self.act.take() { + InternalResult::Actions(vec![act]) + } else { + InternalResult::Nothing + } + } else { + let text = self.line.reset(); + self.history.select(text.clone()); + InternalResult::Submitted(text) } } - fn redraw(&mut self, prompt: &Option) -> Result<(), io::Error> { + fn redraw(&mut self, prompt: Option<&str>) -> Result<(), io::Error> { self.context.stdout.queue(CursorHide)?; self.context @@ -588,8 +577,8 @@ where let lines = self.line.redraw(prompt, 0, &mut self.context)?; if self.ct.is_some() { - let p = self.cmd_prompt(); - let _ = self.cmd.redraw(&p, lines, &mut self.context); + let p = Some(self.cprompt.as_str()); + let _ = self.cmd.redraw(p, lines, &mut self.context); } self.context.stdout.queue(CursorShow)?; @@ -646,7 +635,7 @@ where &mut self, action: Action, ctx: EditContext, - ) -> Result> { + ) -> Result, ReadLineError> { let store = &mut self.store; let _ = match action {