Skip to content

Commit

Permalink
feat(term): add search
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed Apr 26, 2024
1 parent 6e3c885 commit ad68a60
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 30 deletions.
59 changes: 59 additions & 0 deletions crates/synd_term/src/application/event/key_handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::{cell::RefCell, rc::Rc};

use crossterm::event::KeyEvent;

use crate::{application::event::KeyEventResult, keymap::Keymaps, ui::widgets::prompt::Prompt};

pub enum KeyHandler {
Prompt(Rc<RefCell<Prompt>>),
Keymaps(Keymaps),
}

impl KeyHandler {
fn handle(&mut self, event: &KeyEvent) -> KeyEventResult {
match self {
KeyHandler::Prompt(prompt) => prompt.borrow_mut().handle_key_event(event),
KeyHandler::Keymaps(keymaps) => keymaps.search(event),
}
}
}

pub struct KeyHandlers {
handlers: Vec<KeyHandler>,
}

impl KeyHandlers {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
}
}

pub fn push(&mut self, handler: KeyHandler) {
self.handlers.push(handler);
}

pub fn remove_prompt(&mut self) {
self.handlers
.retain(|h| !matches!(h, KeyHandler::Prompt(_)));
}

pub fn keymaps_mut(&mut self) -> Option<&mut Keymaps> {
for handler in &mut self.handlers {
match handler {
KeyHandler::Keymaps(keymaps) => return Some(keymaps),
KeyHandler::Prompt(_) => continue,
}
}
None
}

pub fn handle(&mut self, event: KeyEvent) -> KeyEventResult {
for handler in self.handlers.iter_mut().rev() {
if let KeyEventResult::Consumed(r) = handler.handle(&event) {
return KeyEventResult::Consumed(r);
}
}
KeyEventResult::Ignored
}
}
9 changes: 9 additions & 0 deletions crates/synd_term/src/application/event/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::command::Command;

mod key_handlers;
pub use key_handlers::{KeyHandler, KeyHandlers};

pub enum KeyEventResult {
Consumed(Option<Command>),
Ignored,
}
56 changes: 43 additions & 13 deletions crates/synd_term/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use synd_feed::types::FeedUrl;
use tokio::time::{Instant, Sleep};

use crate::{
application::event::KeyEventResult,
auth::{AuthenticationProvider, Credential},
client::{mutation::subscribe_feed::SubscribeFeedInput, Client},
command::Command,
Expand Down Expand Up @@ -41,6 +42,8 @@ use input_parser::InputParser;
mod authenticator;
pub use authenticator::{Authenticator, DeviceFlows, JwtService};

pub(crate) mod event;

enum Screen {
Login,
Browse,
Expand Down Expand Up @@ -82,7 +85,7 @@ pub struct Application {
theme: Theme,
idle_timer: Pin<Box<Sleep>>,
config: Config,
keymaps: Keymaps,
key_handlers: event::KeyHandlers,
categories: Categories,

screen: Screen,
Expand All @@ -105,6 +108,9 @@ impl Application {
keymaps.enable(KeymapId::Global);
keymaps.enable(KeymapId::Login);

let mut key_handlers = event::KeyHandlers::new();
key_handlers.push(event::KeyHandler::Keymaps(keymaps));

Self {
terminal,
client,
Expand All @@ -117,7 +123,7 @@ impl Application {
idle_timer: Box::pin(tokio::time::sleep(config.idle_timer_interval)),
screen: Screen::Login,
config,
keymaps,
key_handlers,
categories,
should_quit: false,
should_render: false,
Expand All @@ -141,13 +147,17 @@ impl Application {
&self.authenticator.jwt_service
}

fn keymaps(&mut self) -> &mut Keymaps {
self.key_handlers.keymaps_mut().unwrap()
}

pub fn set_credential(&mut self, cred: Credential) {
self.client.set_credential(cred);
self.components.auth.authenticated();
self.keymaps.disable(KeymapId::Login);
self.keymaps.enable(KeymapId::Tabs);
self.keymaps.enable(KeymapId::Entries);
self.keymaps.enable(KeymapId::Filter);
self.keymaps().disable(KeymapId::Login);
self.keymaps().enable(KeymapId::Tabs);
self.keymaps().enable(KeymapId::Entries);
self.keymaps().enable(KeymapId::Filter);
self.initial_fetch();
self.screen = Screen::Browse;
self.should_render = true;
Expand Down Expand Up @@ -300,8 +310,8 @@ impl Application {
self.complete_device_authroize_flow(provider, device_access_token);
}
Command::MoveTabSelection(direction) => {
self.keymaps.toggle(KeymapId::Entries);
self.keymaps.toggle(KeymapId::Subscription);
self.keymaps().toggle(KeymapId::Entries);
self.keymaps().toggle(KeymapId::Subscription);

match self.components.tabs.move_selection(&direction) {
Tab::Feeds if !self.components.subscription.has_subscription() => {
Expand Down Expand Up @@ -439,12 +449,25 @@ impl Application {
}
Command::ActivateCategoryFilterling => {
let keymap = self.components.filter.activate_category_filtering();
self.keymaps.update(KeymapId::CategoryFiltering, keymap);
self.keymaps().update(KeymapId::CategoryFiltering, keymap);
self.should_render = true;
}
Command::DeactivateCategoryFiltering => {
self.components.filter.deactivate_category_filtering();
self.keymaps.disable(KeymapId::CategoryFiltering);
Command::ActivateSearchFiltering => {
let prompt = self.components.filter.activate_search_filtering();
self.key_handlers.push(event::KeyHandler::Prompt(prompt));
self.should_render = true;
}
Command::PromptChanged => {
if self.components.filter.is_search_active() {
let filter = self.components.filter.filter();
self.apply_feed_filter(filter);
self.should_render = true;
}
}
Command::DeactivateFiltering => {
self.components.filter.deactivate_filtering();
self.keymaps().disable(KeymapId::CategoryFiltering);
self.key_handlers.remove_prompt();
self.should_render = true;
}
Command::ToggleFilterCategory { category } => {
Expand Down Expand Up @@ -504,7 +527,14 @@ impl Application {
tracing::debug!("Handle key event: {key:?}");

self.reset_idle_timer();
self.keymaps.search(key)

match self.key_handlers.handle(key) {
KeyEventResult::Consumed(cmd) => {
self.should_render = true;
cmd
}
KeyEventResult::Ignored => None,
}
}
_ => None,
}
Expand Down
11 changes: 8 additions & 3 deletions crates/synd_term/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ pub enum Command {
// Filter
MoveFilterRequirement(Direction),
ActivateCategoryFilterling,
DeactivateCategoryFiltering,
ActivateSearchFiltering,
PromptChanged,
DeactivateFiltering,
ToggleFilterCategory {
category: Category<'static>,
},
Expand Down Expand Up @@ -188,7 +190,10 @@ impl Command {
pub fn activate_category_filtering() -> Self {
Command::ActivateCategoryFilterling
}
pub fn deactivate_category_filtering() -> Self {
Command::DeactivateCategoryFiltering
pub fn activate_search_filtering() -> Self {
Command::ActivateSearchFiltering
}
pub fn deactivate_filtering() -> Self {
Command::DeactivateFiltering
}
}
3 changes: 2 additions & 1 deletion crates/synd_term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ pub fn default() -> KeymapsConfig {
"h" | "left" => move_filter_requirement_left,
"l" | "right" => move_filter_requirement_right,
"c" => activate_category_filtering,
"esc" => deactivate_category_filtering,
"/" => activate_search_filtering,
"esc" => deactivate_filtering,
});
let global = keymap!({
"q" | "C-c" => quit ,
Expand Down
10 changes: 5 additions & 5 deletions crates/synd_term/src/keymap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod default;

pub mod macros;

use crate::command::Command;
use crate::{application::event::KeyEventResult, command::Command};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeymapId {
Expand Down Expand Up @@ -125,13 +125,13 @@ impl Keymaps {
self.keymaps[id as usize] = keymap;
}

pub fn search(&mut self, event: KeyEvent) -> Option<Command> {
pub fn search(&mut self, event: &KeyEvent) -> KeyEventResult {
for keymap in self.keymaps.iter_mut().rev().filter(|k| k.enable) {
if let Some(cmd) = keymap.search(&event) {
return Some(cmd);
if let Some(cmd) = keymap.search(event) {
return KeyEventResult::Consumed(Some(cmd));
}
}
None
KeyEventResult::Ignored
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/synd_term/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod config;
pub mod interact;
pub mod job;
pub mod keymap;
pub mod matcher;
pub mod terminal;
pub mod types;
pub mod ui;
60 changes: 60 additions & 0 deletions crates/synd_term/src/matcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::{cell::RefCell, fmt, rc::Rc};

use nucleo::{
pattern::{AtomKind, CaseMatching, Normalization, Pattern},
Utf32Str,
};

#[derive(Clone)]
pub struct Matcher {
matcher: Rc<RefCell<nucleo::Matcher>>,
needle: Option<Pattern>,
// For Utf32 conversion
buf: Rc<RefCell<Vec<char>>>,
}

impl Matcher {
pub fn new() -> Self {
Self {
matcher: Rc::new(RefCell::new(nucleo::Matcher::default())),
needle: None,
buf: Rc::new(RefCell::new(Vec::new())),
}
}

pub fn update_needle(&mut self, needle: &str) {
if needle.is_empty() {
self.needle = None;
} else {
let needle = Pattern::new(
needle,
CaseMatching::Smart,
Normalization::Smart,
AtomKind::Substring,
);
self.needle = Some(needle);
}
}

pub fn r#match(&self, haystack: &str) -> bool {
match self.needle.as_ref() {
Some(needle) => {
let mut buf = self.buf.borrow_mut();
let haystack = Utf32Str::new(haystack, &mut buf);
needle
.score(haystack, &mut self.matcher.borrow_mut())
.unwrap_or(0)
> 0
}
None => true,
}
}
}

impl fmt::Debug for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Matcher")
.field("needle", &self.needle)
.finish_non_exhaustive()
}
}

0 comments on commit ad68a60

Please sign in to comment.