Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live search preview #51

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 55 additions & 23 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::error::Error;
use crate::event::EventSender;
use crate::file::FileInfo;
use crate::prompt::Prompt;
use crate::screen::Screen;
use crate::screen::{Screen, Scroll};
use crate::search::{MatchMotion, Search, SearchKind};

/// Go to a line (Shortcut: ':')
Expand Down Expand Up @@ -37,7 +37,7 @@ pub(crate) fn goto() -> Prompt {
value_percent += 100;
}
let value = value_percent * (lines - 1) / 100;
screen.scroll_to(value as usize);
screen.scroll_to(Scroll::Center(value as usize));
}
Err(e) => {
screen.error = Some(e.to_string());
Expand All @@ -56,7 +56,7 @@ pub(crate) fn goto() -> Prompt {
} else {
value - 1
};
screen.scroll_to(value as usize);
screen.scroll_to(Scroll::Center(value as usize));
}
Err(e) => {
screen.error = Some(e.to_string());
Expand All @@ -72,27 +72,59 @@ pub(crate) fn goto() -> Prompt {
/// Search for text (Shortcuts: '/', '<', '>')
///
/// Prompts the user for text to search.
pub(crate) fn search(kind: SearchKind, event_sender: EventSender) -> Prompt {
Prompt::new(
"search",
"Search:",
Box::new(
move |screen: &mut Screen, value: &str| -> Result<DisplayAction, Error> {
screen.refresh_matched_lines();
if value.is_empty() {
match kind {
SearchKind::First | SearchKind::FirstAfter(_) => {
screen.move_match(MatchMotion::NextLine)
pub(crate) fn search(kind: SearchKind, event_sender: EventSender, preview: bool) -> Prompt {
let mut prompt = {
let event_sender = event_sender.clone();
Prompt::new(
"search",
"Search:",
Box::new(
move |screen: &mut Screen, value: &str| -> Result<DisplayAction, Error> {
screen.refresh_matched_lines();
if value.is_empty() {
match kind {
SearchKind::First | SearchKind::FirstAfter(_) => {
screen.move_match(MatchMotion::NextLine)
}
SearchKind::FirstBefore(_) => {
screen.move_match(MatchMotion::PreviousLine)
}
}
SearchKind::FirstBefore(_) => screen.move_match(MatchMotion::PreviousLine),
} else {
screen.set_search(
Search::new(&screen.file, value, kind, event_sender.clone()).ok(),
);
}
} else {
screen.set_search(
Search::new(&screen.file, value, kind, event_sender.clone()).ok(),
);
Ok(DisplayAction::Render)
},
),
)
};
if preview {
let mut orig_top = None;
let mut orig_search = None;
let preview = move |screen: &mut Screen, value: &str| -> Result<(), Error> {
if orig_top.is_none() {
// Remember the original top line position.
orig_top = Some(screen.top_line());
orig_search = screen.take_search();
}
screen.refresh_matched_lines();
if value.is_empty() {
// Cancel a search. Restore to the previous state.
screen.set_search(orig_search.take());
if let Some(top) = orig_top {
// Restore the original top line position.
screen.scroll_to(Scroll::Top(top));
}
Ok(DisplayAction::Render)
},
),
)
orig_top = None;
} else {
screen
.set_search(Search::new(&screen.file, value, kind, event_sender.clone()).ok());
}
Ok(())
};
prompt = prompt.with_preview(Box::new(preview));
}
prompt
}
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ pub struct Config {

/// Specify the name of the default key map.
pub keymap: KeymapConfig,

/// Specify whether search is highlighted when typing.
pub highlight_search: bool,
}

impl Default for Config {
Expand All @@ -187,6 +190,7 @@ impl Default for Config {
show_ruler: true,
wrapping_mode: Default::default(),
keymap: Default::default(),
highlight_search: true,
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,12 @@ pub(crate) fn start(
}
Some(Event::Input(InputEvent::Paste(ref text))) => {
let width = screen.width();
let preview = screen.config().highlight_search;
screen
.prompt()
.get_or_insert_with(|| {
// Assume the user wanted to search for what they're pasting.
command::search(SearchKind::First, event_sender.clone())
command::search(SearchKind::First, event_sender.clone(), preview)
})
.paste(text, width)
}
Expand Down
5 changes: 5 additions & 0 deletions src/pager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ impl Pager {
self.events.action_sender()
}

/// Configure the pager.
pub fn configure(&mut self, mut func: impl FnMut(&mut Config)) {
func(&mut self.config)
}

/// Run Stream Pager.
pub fn run(self) -> Result<()> {
crate::display::start(
Expand Down
37 changes: 35 additions & 2 deletions src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::char;
use std::fmt::Write;
use std::sync::Arc;
use std::sync::Mutex;

use termwiz::cell::{AttributeChange, CellAttributes};
use termwiz::color::{AnsiColor, ColorAttribute};
Expand All @@ -17,6 +19,7 @@ use crate::screen::Screen;
use crate::util;

type PromptRunFn = dyn FnMut(&mut Screen, &str) -> Result<DisplayAction, Error>;
type PromptPreviewFn = dyn FnMut(&mut Screen, &str) -> Result<(), Error>;

/// A prompt for input from the user.
pub(crate) struct Prompt {
Expand All @@ -28,6 +31,10 @@ pub(crate) struct Prompt {

/// The closure to run when the user presses Return. Will only be called once.
run: Option<Box<PromptRunFn>>,

/// The closure to run when the user changes input.
/// Also called with empty text when the prompt is canceled.
preview: Option<Arc<Mutex<Box<PromptPreviewFn>>>>,
}

pub(crate) struct PromptState {
Expand Down Expand Up @@ -330,9 +337,15 @@ impl Prompt {
prompt: prompt.to_string(),
history: PromptHistory::open(ident),
run: Some(run),
preview: None,
}
}

pub(crate) fn with_preview(mut self, preview: Box<PromptPreviewFn>) -> Self {
self.preview = Some(Arc::new(Mutex::new(preview)));
self
}

fn state(&self) -> &PromptState {
self.history.state()
}
Expand Down Expand Up @@ -365,19 +378,24 @@ impl Prompt {
self.state_mut().render(changes, offset, width);
}

/// Current text input by the user.
fn value(&self) -> String {
self.state().value[..].iter().collect()
}

/// Dispatch a key press to the prompt.
pub(crate) fn dispatch_key(&mut self, key: KeyEvent, width: usize) -> DisplayAction {
use termwiz::input::{KeyCode::*, Modifiers};
const CTRL: Modifiers = Modifiers::CTRL;
const NONE: Modifiers = Modifiers::NONE;
const ALT: Modifiers = Modifiers::ALT;
let value_width = width - self.prompt.width() - 4;
let value: String = self.value();
let action = match (key.modifiers, key.key) {
(NONE, Enter) | (CTRL, Char('J')) | (CTRL, Char('M')) => {
// Finish.
let _ = self.history.save();
let mut run = self.run.take();
let value: String = self.state().value[..].iter().collect();
return DisplayAction::Run(Box::new(move |screen: &mut Screen| {
screen.clear_prompt();
if let Some(ref mut run) = run {
Expand All @@ -389,7 +407,12 @@ impl Prompt {
}
(NONE, Escape) | (CTRL, Char('C')) => {
// Cancel.
return DisplayAction::Run(Box::new(|screen: &mut Screen| {
let preview = self.preview.clone();
return DisplayAction::Run(Box::new(move |screen: &mut Screen| {
if let Some(preview) = &preview {
let mut preview = preview.lock().unwrap();
preview(screen, "")?;
}
screen.clear_prompt();
Ok(DisplayAction::Render)
}));
Expand All @@ -413,6 +436,16 @@ impl Prompt {
_ => return DisplayAction::None,
};
self.state_mut().clamp_offset(value_width);
let new_value: String = self.value();
if let Some(preview) = self.preview.clone() {
if value != new_value && !matches!(action, DisplayAction::Run(_)) {
return DisplayAction::Run(Box::new(move |screen: &mut Screen| {
let mut preview = preview.lock().unwrap();
preview(screen, &new_value)?;
Ok(DisplayAction::Render)
}));
}
}
action
}

Expand Down
Loading