Skip to content

Commit

Permalink
Keybindings should provide Action to command bar (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
ulyssa committed Aug 10, 2024
1 parent f1760d6 commit 7d8a6c3
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 333 deletions.
4 changes: 3 additions & 1 deletion crates/modalkit-ratatui/examples/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
116 changes: 57 additions & 59 deletions crates/modalkit-ratatui/src/cmdbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,7 +34,8 @@ use super::{
/// Persistent state for rendering [CommandBar].
pub struct CommandBarState<I: ApplicationInfo> {
scrollback: ScrollbackState,
searchdir: MoveDir1D,
prompt: String,
action: Option<(Action<I>, EditContext)>,
cmdtype: CommandType,
tbox_cmd: TextBoxState<I>,
tbox_search: TextBoxState<I>,
Expand All @@ -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),
Expand All @@ -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<I>, 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].
Expand Down Expand Up @@ -124,31 +120,13 @@ where
ctx: &EditContext,
store: &mut Store<I>,
) -> EditResult<Vec<(Action<I>, 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(
Expand All @@ -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())])
}
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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::<EmptyInfo>::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);
}
}
10 changes: 5 additions & 5 deletions crates/modalkit-ratatui/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down
14 changes: 10 additions & 4 deletions crates/modalkit-ratatui/src/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,16 @@ where
self.last_message = false;
}

fn focus_command(&mut self, ct: CommandType, dir: MoveDir1D) -> EditResult<EditInfo, I> {
fn focus_command(
&mut self,
prompt: &str,
ct: CommandType,
act: &Action<I>,
ctx: &EditContext,
) -> EditResult<EditInfo, I> {
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)
Expand All @@ -364,11 +370,11 @@ where
/// Perform a command bar action.
pub fn command_bar(
&mut self,
act: &CommandBarAction,
act: &CommandBarAction<I>,
ctx: &EditContext,
) -> EditResult<EditInfo, I> {
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(),
}
}
Expand Down
30 changes: 17 additions & 13 deletions crates/modalkit/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use crate::{
commands::{Command, CommandMachine},
editing::application::*,
editing::context::{EditContext, Resolve},
editing::store::RegisterStore,
errors::{EditResult, UIResult},
keybindings::SequenceStatus,
prelude::*,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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).
Expand All @@ -338,6 +339,7 @@ where
&mut self,
action: &CommandAction,
ctx: &C::Context,
rstore: &mut RegisterStore,
) -> UIResult<Vec<(C::Action, C::Context)>, I>;
}

Expand All @@ -350,11 +352,12 @@ where
&mut self,
action: &CommandAction,
ctx: &C::Context,
rstore: &mut RegisterStore,
) -> UIResult<Vec<(Action<I>, 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())];
Expand All @@ -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)
Expand All @@ -378,9 +382,9 @@ where

/// Command bar actions
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CommandBarAction {
pub enum CommandBarAction<I: ApplicationInfo> {
/// Focus the command bar
Focus(CommandType),
Focus(String, CommandType, Box<Action<I>>),

/// Unfocus the command bar.
Unfocus,
Expand Down Expand Up @@ -735,7 +739,7 @@ pub enum Action<I: ApplicationInfo = EmptyInfo> {
Command(CommandAction),

/// Perform a command bar-related action.
CommandBar(CommandBarAction),
CommandBar(CommandBarAction<I>),

/// Perform a prompt-related action.
Prompt(PromptAction),
Expand Down Expand Up @@ -905,8 +909,8 @@ impl<I: ApplicationInfo> From<CommandAction> for Action<I> {
}
}

impl<I: ApplicationInfo> From<CommandBarAction> for Action<I> {
fn from(act: CommandBarAction) -> Self {
impl<I: ApplicationInfo> From<CommandBarAction<I>> for Action<I> {
fn from(act: CommandBarAction<I>) -> Self {
Action::CommandBar(act)
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/modalkit/src/editing/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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;
}

Expand Down
6 changes: 3 additions & 3 deletions crates/modalkit/src/editing/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -397,7 +397,7 @@ where
}

fn _get_regex(&self, store: &Store<I>) -> EditResult<Regex, I> {
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);
Expand Down Expand Up @@ -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));
Expand Down
Loading

0 comments on commit 7d8a6c3

Please sign in to comment.