Skip to content

Commit

Permalink
Add shorthand for filters
Browse files Browse the repository at this point in the history
  • Loading branch information
Avarel committed Dec 25, 2023
1 parent 1cd0352 commit 329394e
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 78 deletions.
1 change: 1 addition & 0 deletions crates/cli/app/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub enum CommandAction {
Paste(String),
Backspace,
Submit,
Complete,
}

#[derive(Clone, Copy)]
Expand Down
9 changes: 6 additions & 3 deletions crates/cli/app/keybinding.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
actions::{Action, CommandAction, CommandJump, Delta, FilterAction, NormalAction, VisualAction},
InputMode,
InputMode, PromptMode,
};
use crate::direction::Direction;
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers};
Expand Down Expand Up @@ -149,7 +149,7 @@ impl Keybinding {
},
_ => None,
},
InputMode::Command => match event {
InputMode::Command(_) => match event {
Event::Paste(paste) => {
Some(Action::Command(CommandAction::Paste(std::mem::take(paste))))
}
Expand Down Expand Up @@ -190,6 +190,7 @@ impl Keybinding {
}
c => Some(Action::Command(CommandAction::Type(c))),
},
KeyCode::Tab => Some(Action::Command(CommandAction::Complete)),
_ => None,
},
_ => None,
Expand All @@ -200,7 +201,9 @@ impl Keybinding {
fn mode_independent_bind(_input_mode: InputMode, event: &mut Event) -> Option<Action> {
match event {
Event::Key(key) => match key.code {
KeyCode::Char(':') => Some(Action::SwitchMode(InputMode::Command)),
KeyCode::Char(':') => Some(Action::SwitchMode(InputMode::Command(PromptMode::Command))),
KeyCode::Char('+') => Some(Action::SwitchMode(InputMode::Command(PromptMode::NewFilter))),
KeyCode::Char('-') => Some(Action::SwitchMode(InputMode::Command(PromptMode::NewLit))),
KeyCode::Tab => Some(Action::SwitchMode(InputMode::Filter)),
KeyCode::Char(c @ ('`' | '~')) => Some(Action::Normal(NormalAction::SwitchActive(
Direction::back_if(c == '~'),
Expand Down
118 changes: 66 additions & 52 deletions crates/cli/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ use self::{
actions::{Action, CommandAction, Delta, NormalAction, VisualAction},
keybinding::Keybinding,
mouse::MouseHandler,
widgets::{CommandWidget, MultiplexerWidget},
widgets::{MultiplexerWidget, PromptWidget},
};
use crate::components::{
command::{self, CommandApp, PromptMovement},
filters::Filter,
mux::MultiplexerApp,
prompt::{self, PromptApp, PromptMovement},
status::StatusApp,
viewer::Instance,
};
Expand All @@ -27,32 +27,39 @@ use crossterm::{
};
use ratatui::{prelude::*, widgets::Widget};
use regex::bytes::RegexBuilder;
use std::{num::NonZeroUsize, path::Path, time::Duration};
use std::{borrow::Cow, num::NonZeroUsize, path::Path, time::Duration};

pub type Backend<'a> = ratatui::backend::CrosstermBackend<std::io::StdoutLock<'a>>;
pub type Terminal<'a> = ratatui::Terminal<Backend<'a>>;

#[derive(PartialEq, Clone, Copy)]
pub enum InputMode {
Command,
Command(PromptMode),
Normal,
Visual,
Filter,
}

#[derive(PartialEq, Clone, Copy)]
pub enum PromptMode {
Command,
NewFilter,
NewLit,
}

pub struct App {
mode: InputMode,
mux: MultiplexerApp,
status: StatusApp,
command: CommandApp,
prompt: PromptApp,
keybinds: Keybinding,
}

impl App {
pub fn new() -> Self {
Self {
mode: InputMode::Normal,
command: CommandApp::new(),
prompt: PromptApp::new(),
mux: MultiplexerApp::new(),
status: StatusApp::new(),
keybinds: Keybinding::Hardcoded,
Expand Down Expand Up @@ -140,7 +147,7 @@ impl App {
match action {
Action::Exit => return false,
Action::SwitchMode(new_mode) => {
self.command.submit();
self.prompt.submit();
self.mode = new_mode;

if new_mode == InputMode::Visual {
Expand Down Expand Up @@ -283,37 +290,73 @@ impl App {
direction,
select,
jump,
} => self.command.move_cursor(
} => self.prompt.move_cursor(
direction,
PromptMovement::new(
select,
match jump {
actions::CommandJump::Word => command::PromptJump::Word,
actions::CommandJump::Boundary => command::PromptJump::Boundary,
actions::CommandJump::None => command::PromptJump::Delta(1),
actions::CommandJump::Word => prompt::PromptJump::Word,
actions::CommandJump::Boundary => prompt::PromptJump::Boundary,
actions::CommandJump::None => prompt::PromptJump::Delta(1),
},
),
),
CommandAction::Type(c) => self.command.enter_char(c),
CommandAction::Paste(s) => self.command.enter_str(&s),
CommandAction::Type(c) => self.prompt.enter_char(c),
CommandAction::Paste(s) => self.prompt.enter_str(&s),
CommandAction::Backspace => {
if !self.command.delete() {
if !self.prompt.delete() {
self.mode = InputMode::Normal;
}
}
CommandAction::Submit => {
let command = self.command.submit();
if !self.process_command(command) {
return false;
}
let command = self.prompt.submit();
let result = match self.mode {
InputMode::Command(PromptMode::Command) => self.process_command(command),
InputMode::Command(PromptMode::NewFilter) => {
self.process_search(&command, false)
}
InputMode::Command(PromptMode::NewLit) => {
self.process_search(&command, true)
}
InputMode::Normal | InputMode::Visual | InputMode::Filter => unreachable!(),
};
self.mode = InputMode::Normal;
return result;
}
CommandAction::Complete => {
()
}
},
};

true
}

fn process_search(&mut self, pat: &str, escaped: bool) -> bool {
let pat = if escaped {
Cow::Owned(regex::escape(pat))
} else {
Cow::Borrowed(pat)
};
let regex = match RegexBuilder::new(&pat).case_insensitive(true).build() {
Ok(r) => r,
Err(err) => {
self.status.submit_message(
format!("Invalid regex `{pat}`: {err}"),
Some(Duration::from_secs(2)),
);
return true;
}
};

if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.filter_search(regex);
viewer.filterer.compute_composite();
}

true
}

fn process_command(&mut self, command: String) -> bool {
if command == "q" {
return false;
Expand All @@ -334,38 +377,9 @@ impl App {
viewer.filterer.compute_composite();
}
} else if let Some(pat) = command.strip_prefix("find ") {
let regex = match RegexBuilder::new(pat).case_insensitive(true).build() {
Ok(r) => r,
Err(err) => {
self.status.submit_message(
format!("Invalid regex `{pat}`: {err}"),
Some(Duration::from_secs(2)),
);
return true;
}
};

if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.filter_search(regex);
viewer.filterer.compute_composite();
}
return self.process_search(pat, false);
} else if let Some(pat) = command.strip_prefix("findl ") {
let pat = regex::escape(pat);
let regex = match RegexBuilder::new(&pat).case_insensitive(true).build() {
Ok(r) => r,
Err(err) => {
self.status.submit_message(
format!("Invalid regex `{pat}`: {err}"),
Some(Duration::from_secs(2)),
);
return true;
}
};

if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.filter_search(regex);
viewer.filterer.compute_composite();
}
return self.process_search(pat, true);
} else if let Ok(n) = command.parse::<usize>() {
if let Some(viewer) = self.mux.active_viewer_mut() {
viewer.viewport_mut().jump_to(n.saturating_sub(1));
Expand All @@ -390,9 +404,9 @@ impl App {
.render(mux_chunk, f.buffer_mut(), handler);

let mut cursor = None;
CommandWidget {
active: self.mode == InputMode::Command,
inner: &self.command,
PromptWidget {
mode: self.mode,
inner: &self.prompt,
cursor: &mut cursor,
}
.render(cmd_chunk, f.buffer_mut());
Expand Down
45 changes: 25 additions & 20 deletions crates/cli/app/widgets.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::{
actions::{Action, Delta, FilterAction, NormalAction},
mouse::MouseHandler,
InputMode,
InputMode, PromptMode,
};
use crate::{
colors,
components::{
command::CommandApp,
prompt::PromptApp,
cursor::{Cursor, SelectionOrigin},
filters::{FilterData, FilterType},
mux::{MultiplexerApp, MultiplexerMode},
Expand Down Expand Up @@ -36,7 +36,7 @@ impl<'a> Widget for StatusWidget<'a> {
.bg(colors::STATUS_BAR);

let accent_color = match self.input_mode {
InputMode::Command => colors::COMMAND_ACCENT,
InputMode::Command(_) => colors::COMMAND_ACCENT,
InputMode::Normal => colors::VIEWER_ACCENT,
InputMode::Visual => colors::SELECT_ACCENT,
InputMode::Filter => colors::FILTER_ACCENT,
Expand All @@ -46,7 +46,7 @@ impl<'a> Widget for StatusWidget<'a> {

v.push(
Span::from(match self.input_mode {
InputMode::Command => " COMMAND ",
InputMode::Command(_) => " COMMAND ",
InputMode::Normal => " NORMAL ",
InputMode::Visual => " VISUAL ",
InputMode::Filter => " FILTER ",
Expand Down Expand Up @@ -74,41 +74,46 @@ impl<'a> Widget for StatusWidget<'a> {
}
}

pub struct CommandWidget<'a> {
pub inner: &'a CommandApp,
pub struct PromptWidget<'a> {
pub inner: &'a PromptApp,
pub mode: InputMode,
pub cursor: &'a mut Option<(u16, u16)>,
pub active: bool,
}

impl Widget for CommandWidget<'_> {
impl Widget for PromptWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if !self.active {
let InputMode::Command(mode) = self.mode else {
const WIDGET_BLOCK: Block = Block::new().style(Style::new().bg(colors::BG));
WIDGET_BLOCK.render(area, buf);
return;
}
};

let c = match mode {
PromptMode::Command => ":",
PromptMode::NewFilter => "+",
PromptMode::NewLit => "-",
};

let input = Paragraph::new(Line::from(match *self.inner.cursor() {
Cursor::Singleton(_) => {
vec![Span::from(":"), Span::from(self.inner.buf())]
vec![Span::from(c), Span::from(self.inner.buf())]
}
Cursor::Selection(start, end, _) => vec![
Span::from(":"),
Span::from(c),
Span::from(&self.inner.buf()[..start]),
Span::from(&self.inner.buf()[start..end]).bg(colors::COMMAND_BAR_SELECT),
Span::from(&self.inner.buf()[end..]),
],
}))
.bg(colors::BG);

if self.active {
let i = match *self.inner.cursor() {
Cursor::Singleton(i)
| Cursor::Selection(_, i, SelectionOrigin::Right)
| Cursor::Selection(i, _, SelectionOrigin::Left) => i,
};
*self.cursor = Some((area.x + i as u16 + 1, area.y));
}
let i = match *self.inner.cursor() {
Cursor::Singleton(i)
| Cursor::Selection(_, i, SelectionOrigin::Right)
| Cursor::Selection(i, _, SelectionOrigin::Left) => i,
};
*self.cursor = Some((area.x + i as u16 + 1, area.y));

input.render(area, buf);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod cursor;
pub mod viewport;

pub mod command;
pub mod prompt;
pub mod filters;
pub mod mux;
pub mod status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ impl PromptMovement {
}
}

pub struct CommandApp {
pub struct PromptApp {
buf: String,
cursor: CursorState,
}

impl CommandApp {
impl PromptApp {
pub fn new() -> Self {
Self {
buf: String::new(),
Expand Down

0 comments on commit 329394e

Please sign in to comment.