aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/event.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/event.rs')
-rw-r--r--alacritty/src/event.rs134
1 files changed, 94 insertions, 40 deletions
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index db3c84fb..4369a689 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -2,6 +2,7 @@
use std::borrow::Cow;
use std::cmp::{max, min};
+use std::collections::VecDeque;
use std::env;
use std::fmt::Debug;
#[cfg(not(any(target_os = "macos", windows)))]
@@ -59,6 +60,9 @@ pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500);
/// Maximum number of lines for the blocking search while still typing the search regex.
const MAX_SEARCH_WHILE_TYPING: Option<usize> = Some(1000);
+/// Maximum number of search terms stored in the history.
+const MAX_HISTORY_SIZE: usize = 255;
+
/// Events dispatched through the UI event loop.
#[derive(Debug, Clone)]
pub enum Event {
@@ -85,9 +89,6 @@ impl From<TerminalEvent> for Event {
/// Regex search state.
pub struct SearchState {
- /// Search string regex.
- regex: Option<String>,
-
/// Search direction.
direction: Direction,
@@ -99,6 +100,16 @@ pub struct SearchState {
/// Focused match during active search.
focused_match: Option<RangeInclusive<Point<usize>>>,
+
+ /// Search regex and history.
+ ///
+ /// When a search is currently active, the first element will be what the user can modify in
+ /// the current search session. While going through history, the [`history_index`] will point
+ /// to the element in history which is currently being previewed.
+ history: VecDeque<String>,
+
+ /// Current position in the search history.
+ history_index: Option<usize>,
}
impl SearchState {
@@ -108,7 +119,7 @@ impl SearchState {
/// Search regex text if a search is active.
pub fn regex(&self) -> Option<&String> {
- self.regex.as_ref()
+ self.history_index.and_then(|index| self.history.get(index))
}
/// Direction of the search from the search origin.
@@ -120,16 +131,22 @@ impl SearchState {
pub fn focused_match(&self) -> Option<&RangeInclusive<Point<usize>>> {
self.focused_match.as_ref()
}
+
+ /// Search regex text if a search is active.
+ fn regex_mut(&mut self) -> Option<&mut String> {
+ self.history_index.and_then(move |index| self.history.get_mut(index))
+ }
}
impl Default for SearchState {
fn default() -> Self {
Self {
direction: Direction::Right,
- display_offset_delta: 0,
- origin: Point::default(),
- focused_match: None,
- regex: None,
+ display_offset_delta: Default::default(),
+ focused_match: Default::default(),
+ history_index: Default::default(),
+ history: Default::default(),
+ origin: Default::default(),
}
}
}
@@ -397,9 +414,15 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
let num_lines = self.terminal.screen_lines();
let num_cols = self.terminal.cols();
- self.search_state.focused_match = None;
- self.search_state.regex = Some(String::new());
+ // Only create new history entry if the previous regex wasn't empty.
+ if self.search_state.history.get(0).map_or(true, |regex| !regex.is_empty()) {
+ self.search_state.history.push_front(String::new());
+ self.search_state.history.truncate(MAX_HISTORY_SIZE);
+ }
+
+ self.search_state.history_index = Some(0);
self.search_state.direction = direction;
+ self.search_state.focused_match = None;
// Store original search position as origin and reset location.
self.search_state.display_offset_delta = 0;
@@ -452,36 +475,70 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
#[inline]
fn search_input(&mut self, c: char) {
- if let Some(regex) = self.search_state.regex.as_mut() {
- match c {
- // Handle backspace/ctrl+h.
- '\x08' | '\x7f' => {
- let _ = regex.pop();
- },
- // Add ascii and unicode text.
- ' '..='~' | '\u{a0}'..='\u{10ffff}' => regex.push(c),
- // Ignore non-printable characters.
- _ => return,
- }
+ match self.search_state.history_index {
+ Some(0) => (),
+ // When currently in history, replace active regex with history on change.
+ Some(index) => {
+ self.search_state.history[0] = self.search_state.history[index].clone();
+ self.search_state.history_index = Some(0);
+ },
+ None => return,
+ }
+ let regex = &mut self.search_state.history[0];
- if !self.terminal.mode().contains(TermMode::VI) {
- // Clear selection so we do not obstruct any matches.
- self.terminal.selection = None;
- }
+ match c {
+ // Handle backspace/ctrl+h.
+ '\x08' | '\x7f' => {
+ let _ = regex.pop();
+ },
+ // Add ascii and unicode text.
+ ' '..='~' | '\u{a0}'..='\u{10ffff}' => regex.push(c),
+ // Ignore non-printable characters.
+ _ => return,
+ }
- self.update_search();
+ if !self.terminal.mode().contains(TermMode::VI) {
+ // Clear selection so we do not obstruct any matches.
+ self.terminal.selection = None;
}
+
+ self.update_search();
}
#[inline]
- fn pop_word_search(&mut self) {
- if let Some(regex) = self.search_state.regex.as_mut() {
+ fn search_pop_word(&mut self) {
+ if let Some(regex) = self.search_state.regex_mut() {
*regex = regex.trim_end().to_owned();
regex.truncate(regex.rfind(' ').map(|i| i + 1).unwrap_or(0));
self.update_search();
}
}
+ /// Go to the previous regex in the search history.
+ #[inline]
+ fn search_history_previous(&mut self) {
+ let index = match &mut self.search_state.history_index {
+ None => return,
+ Some(index) if *index + 1 >= self.search_state.history.len() => return,
+ Some(index) => index,
+ };
+
+ *index += 1;
+ self.update_search();
+ }
+
+ /// Go to the previous regex in the search history.
+ #[inline]
+ fn search_history_next(&mut self) {
+ let index = match &mut self.search_state.history_index {
+ Some(0) | None => return,
+ Some(index) => index,
+ };
+
+ *index -= 1;
+ self.update_search();
+ }
+
#[inline]
fn advance_search_origin(&mut self, direction: Direction) {
let origin = self.absolute_origin();
@@ -534,7 +591,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
#[inline]
fn search_active(&self) -> bool {
- self.search_state.regex.is_some()
+ self.search_state.history_index.is_some()
}
fn message(&self) -> Option<&Message> {
@@ -564,7 +621,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
fn update_search(&mut self) {
- let regex = match self.search_state.regex.as_mut() {
+ let regex = match self.search_state.regex() {
Some(regex) => regex,
None => return,
};
@@ -615,10 +672,9 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
/// Jump to the first regex match from the search origin.
fn goto_match(&mut self, mut limit: Option<usize>) {
- let regex = match self.search_state.regex.take() {
- Some(regex) => regex,
- None => return,
- };
+ if self.search_state.history_index.is_none() {
+ return;
+ }
// Limit search only when enough lines are available to run into the limit.
limit = limit.filter(|&limit| limit <= self.terminal.total_lines());
@@ -664,8 +720,6 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
self.search_state.focused_match = None;
},
}
-
- self.search_state.regex = Some(regex);
}
/// Cleanup the search state.
@@ -679,7 +733,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
}
self.display_update_pending.dirty = true;
- self.search_state.regex = None;
+ self.search_state.history_index = None;
self.terminal.dirty = true;
// Clear focused match.
@@ -918,7 +972,7 @@ impl<N: Notify + OnResize> Processor<N> {
let mut terminal = terminal.lock();
let mut display_update_pending = DisplayUpdate::default();
- let old_is_searching = self.search_state.regex.is_some();
+ let old_is_searching = self.search_state.history_index.is_some();
let context = ActionContext {
terminal: &mut terminal,
@@ -1256,13 +1310,13 @@ impl<N: Notify + OnResize> Processor<N> {
terminal,
&mut self.notifier,
&self.message_buffer,
- self.search_state.regex.is_some(),
+ self.search_state.history_index.is_some(),
&self.config,
display_update_pending,
);
// Scroll to make sure search origin is visible and content moves as little as possible.
- if !old_is_searching && self.search_state.regex.is_some() {
+ if !old_is_searching && self.search_state.history_index.is_some() {
let display_offset = terminal.grid().display_offset();
if display_offset == 0 && cursor_at_bottom && !origin_at_bottom {
terminal.scroll_display(Scroll::Delta(1));