diff options
Diffstat (limited to 'alacritty/src/event.rs')
-rw-r--r-- | alacritty/src/event.rs | 249 |
1 files changed, 156 insertions, 93 deletions
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index d83469ee..26f781dc 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -12,7 +12,7 @@ use std::fs::File; use std::io::Write; use std::mem; use std::ops::RangeInclusive; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(not(any(target_os = "macos", windows)))] use std::sync::atomic::Ordering; use std::sync::Arc; @@ -35,6 +35,7 @@ use alacritty_terminal::grid::{Dimensions, Scroll}; use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::sync::FairMutex; +use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; #[cfg(not(windows))] use alacritty_terminal::tty; @@ -44,6 +45,7 @@ use crate::clipboard::Clipboard; use crate::config; use crate::config::Config; use crate::daemon::start_daemon; +use crate::display::window::Window; use crate::display::{Display, DisplayUpdate}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; #[cfg(target_os = "macos")] @@ -51,7 +53,6 @@ use crate::macos; use crate::message_bar::{Message, MessageBuffer}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; -use crate::window::Window; /// Duration after the last user input until an unlimited search is performed. pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500); @@ -102,13 +103,17 @@ pub struct SearchState { /// 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. + /// During an active search, the first element is the user's current input. + /// + /// While going through history, the [`SearchState::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>, + + /// Compiled search automatons. + dfas: Option<RegexSearch>, } impl SearchState { @@ -131,6 +136,11 @@ impl SearchState { self.focused_match.as_ref() } + /// Active search dfas. + pub fn dfas(&self) -> Option<&RegexSearch> { + self.dfas.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)) @@ -146,6 +156,7 @@ impl Default for SearchState { history_index: Default::default(), history: Default::default(), origin: Default::default(), + dfas: Default::default(), } } } @@ -154,31 +165,37 @@ pub struct ActionContext<'a, N, T> { pub notifier: &'a mut N, pub terminal: &'a mut Term<T>, pub clipboard: &'a mut Clipboard, - pub size_info: &'a mut SizeInfo, pub mouse: &'a mut Mouse, pub received_count: &'a mut usize, pub suppress_chars: &'a mut bool, pub modifiers: &'a mut ModifiersState, - pub window: &'a mut Window, + pub display: &'a mut Display, pub message_buffer: &'a mut MessageBuffer, pub display_update_pending: &'a mut DisplayUpdate, pub config: &'a mut Config, pub event_loop: &'a EventLoopWindowTarget<Event>, - pub urls: &'a Urls, pub scheduler: &'a mut Scheduler, pub search_state: &'a mut SearchState, - cursor_hidden: &'a mut bool, cli_options: &'a CLIOptions, font_size: &'a mut Size, + dirty: bool, } impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionContext<'a, N, T> { + #[inline] fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) { self.notifier.notify(val); } + /// Request a redraw. + #[inline] + fn mark_dirty(&mut self) { + self.dirty = true; + } + + #[inline] fn size_info(&self) -> SizeInfo { - *self.size_info + self.display.size_info } fn scroll(&mut self, scroll: Scroll) { @@ -202,8 +219,10 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon { let point = self.size_info().pixels_to_coords(self.mouse().x, self.mouse().y); let cell_side = self.mouse().cell_side; - self.update_selection(Point { line: point.line, col: point.col }, cell_side); + self.update_selection(point, cell_side); } + + self.dirty = true; } fn copy_selection(&mut self, ty: ClipboardType) { @@ -220,7 +239,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon fn clear_selection(&mut self) { self.terminal.selection = None; - self.terminal.dirty = true; + self.dirty = true; } fn update_selection(&mut self, mut point: Point, side: Side) { @@ -243,13 +262,13 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } self.terminal.selection = Some(selection); - self.terminal.dirty = true; + self.dirty = true; } fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) { let point = self.terminal.visible_to_buffer(point); self.terminal.selection = Some(Selection::new(ty, point, side)); - self.terminal.dirty = true; + self.dirty = true; } fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) { @@ -259,7 +278,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon }, Some(selection) if !selection.is_empty() => { selection.ty = ty; - self.terminal.dirty = true; + self.dirty = true; }, _ => self.start_selection(ty, point, side), } @@ -269,8 +288,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon let x = self.mouse.x as usize; let y = self.mouse.y as usize; - if self.size_info.contains_point(x, y) { - Some(self.size_info.pixels_to_coords(x, y)) + if self.display.size_info.contains_point(x, y) { + Some(self.display.size_info.pixels_to_coords(x, y)) } else { None } @@ -309,12 +328,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon #[inline] fn window(&self) -> &Window { - self.window + &self.display.window } #[inline] fn window_mut(&mut self) -> &mut Window { - self.window + &mut self.display.window } #[inline] @@ -387,17 +406,21 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } } + fn highlighted_url(&self) -> Option<&Url> { + self.display.highlighted_url.as_ref() + } + fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); let font = self.config.ui_config.font.clone().with_size(*self.font_size); self.display_update_pending.set_font(font); - self.terminal.dirty = true; + self.dirty = true; } fn reset_font_size(&mut self) { *self.font_size = self.config.ui_config.font.size(); self.display_update_pending.set_font(self.config.ui_config.font.clone()); - self.terminal.dirty = true; + self.dirty = true; } #[inline] @@ -405,6 +428,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon if !self.message_buffer.is_empty() { self.display_update_pending.dirty = true; self.message_buffer.pop(); + self.dirty = true; } } @@ -437,7 +461,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } self.display_update_pending.dirty = true; - self.terminal.dirty = true; + self.dirty = true; } #[inline] @@ -458,8 +482,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon #[inline] fn cancel_search(&mut self) { - self.terminal.cancel_search(); - if self.terminal.mode().contains(TermMode::VI) { // Recover pre-search state in vi mode. self.search_reset_state(); @@ -471,6 +493,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon self.update_selection(end, Side::Right); } + self.search_state.dfas = None; + self.exit_search(); } @@ -591,6 +615,29 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon self.search_state.origin = origin_relative; } + /// Find the next search match. + fn search_next( + &mut self, + origin: Point<usize>, + direction: Direction, + side: Side, + ) -> Option<Match> { + self.search_state + .dfas + .as_ref() + .and_then(|dfas| self.terminal.search_next(dfas, origin, direction, side, None)) + } + + #[inline] + fn search_direction(&self) -> Direction { + self.search_state.direction + } + + #[inline] + fn search_active(&self) -> bool { + self.search_state.history_index.is_some() + } + /// Handle keyboard typing start. /// /// This will temporarily disable some features like terminal cursor blinking or the mouse @@ -603,24 +650,27 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon let blink_interval = self.config.cursor.blink_interval(); if let Some(timer) = self.scheduler.get_mut(TimerId::BlinkCursor) { timer.deadline = Instant::now() + Duration::from_millis(blink_interval); - *self.cursor_hidden = false; - self.terminal.dirty = true; + self.display.cursor_hidden = false; + self.dirty = true; } // Hide mouse cursor. if self.config.ui_config.mouse.hide_when_typing { - self.window.set_mouse_visible(false); + self.display.window.set_mouse_visible(false); } } + /// Toggle the vi mode status. #[inline] - fn search_direction(&self) -> Direction { - self.search_state.direction - } + fn toggle_vi_mode(&mut self) { + if !self.terminal.mode().contains(TermMode::VI) { + self.clear_selection(); + } - #[inline] - fn search_active(&self) -> bool { - self.search_state.history_index.is_some() + self.cancel_search(); + self.terminal.toggle_vi_mode(); + + self.dirty = true; } fn message(&self) -> Option<&Message> { @@ -636,7 +686,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } fn urls(&self) -> &Urls { - self.urls + &self.display.urls } fn clipboard_mut(&mut self) -> &mut Clipboard { @@ -657,22 +707,22 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { // Hide cursor while typing into the search bar. if self.config.ui_config.mouse.hide_when_typing { - self.window.set_mouse_visible(false); + self.display.window.set_mouse_visible(false); } if regex.is_empty() { // Stop search if there's nothing to search for. self.search_reset_state(); - self.terminal.cancel_search(); + self.search_state.dfas = None; } else { - // Create terminal search from the new regex string. - self.terminal.start_search(®ex); + // Create search dfas for the new regex string. + self.search_state.dfas = RegexSearch::new(®ex).ok(); // Update search highlighting. self.goto_match(MAX_SEARCH_WHILE_TYPING); } - self.terminal.dirty = true; + self.dirty = true; } /// Reset terminal to the state before search was started. @@ -697,22 +747,26 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { // Reset vi mode cursor. let mut origin = self.search_state.origin; origin.line = min(origin.line, self.terminal.screen_lines() - 1); - origin.col = min(origin.col, self.terminal.cols() - 1); + origin.column = min(origin.column, self.terminal.cols() - 1); self.terminal.vi_mode_cursor.point = origin; + + self.dirty = true; } /// Jump to the first regex match from the search origin. fn goto_match(&mut self, mut limit: Option<usize>) { - if self.search_state.history_index.is_none() { - return; - } + let dfas = match &self.search_state.dfas { + Some(dfas) => dfas, + None => return, + }; // Limit search only when enough lines are available to run into the limit. limit = limit.filter(|&limit| limit <= self.terminal.total_lines()); // Jump to the next match. let direction = self.search_state.direction; - match self.terminal.search_next(self.absolute_origin(), direction, Side::Left, limit) { + let origin = self.absolute_origin(); + match self.terminal.search_next(dfas, origin, direction, Side::Left, limit) { Some(regex_match) => { let old_offset = self.terminal.grid().display_offset() as isize; @@ -752,7 +806,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { }, } - self.terminal.dirty = true; + self.dirty = true; } /// Cleanup the search state. @@ -767,7 +821,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { self.display_update_pending.dirty = true; self.search_state.history_index = None; - self.terminal.dirty = true; + self.dirty = true; // Clear focused match. self.search_state.focused_match = None; @@ -781,7 +835,7 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { fn absolute_origin(&self) -> Point<usize> { let mut relative_origin = self.search_state.origin; relative_origin.line = min(relative_origin.line, self.terminal.screen_lines() - 1); - relative_origin.col = min(relative_origin.col, self.terminal.cols() - 1); + relative_origin.column = min(relative_origin.column, self.terminal.cols() - 1); let mut origin = self.terminal.visible_to_buffer(relative_origin); origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize; origin @@ -809,8 +863,8 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { TimerId::BlinkCursor, ) } else { - *self.cursor_hidden = false; - self.terminal.dirty = true; + self.display.cursor_hidden = false; + self.dirty = true; } } } @@ -1012,27 +1066,26 @@ impl<N: Notify + OnResize> Processor<N> { notifier: &mut self.notifier, mouse: &mut self.mouse, clipboard: &mut self.clipboard, - size_info: &mut self.display.size_info, received_count: &mut self.received_count, suppress_chars: &mut self.suppress_chars, modifiers: &mut self.modifiers, message_buffer: &mut self.message_buffer, display_update_pending: &mut display_update_pending, - window: &mut self.display.window, + display: &mut self.display, font_size: &mut self.font_size, config: &mut self.config, - urls: &self.display.urls, scheduler: &mut scheduler, search_state: &mut self.search_state, cli_options: &self.cli_options, - cursor_hidden: &mut self.display.cursor_hidden, + dirty: false, event_loop, }; - let mut processor = input::Processor::new(context, &self.display.highlighted_url); + let mut processor = input::Processor::new(context); for event in self.event_queue.drain(..) { Processor::handle_event(event, &mut processor); } + let dirty = processor.ctx.dirty; // Process DisplayUpdate events. if display_update_pending.dirty { @@ -1045,11 +1098,9 @@ impl<N: Notify + OnResize> Processor<N> { return; } - if terminal.dirty { - terminal.dirty = false; - + if dirty { // Request immediate re-draw if visual bell animation is not finished yet. - if !terminal.visual_bell.completed() { + if !self.display.visual_bell.completed() { let event: Event = TerminalEvent::Wakeup.into(); self.event_queue.push(event.into()); @@ -1079,7 +1130,7 @@ impl<N: Notify + OnResize> Processor<N> { /// Doesn't take self mutably due to borrow checking. fn handle_event<T>( event: GlutinEvent<'_, Event>, - processor: &mut input::Processor<'_, T, ActionContext<'_, N, T>>, + processor: &mut input::Processor<T, ActionContext<'_, N, T>>, ) where T: EventListener, { @@ -1095,40 +1146,48 @@ impl<N: Notify + OnResize> Processor<N> { // Resize to event's dimensions, since no resize event is emitted on Wayland. display_update_pending.set_dimensions(PhysicalSize::new(width, height)); - processor.ctx.window.dpr = scale_factor; - processor.ctx.terminal.dirty = true; + processor.ctx.window_mut().dpr = scale_factor; + processor.ctx.dirty = true; }, Event::Message(message) => { processor.ctx.message_buffer.push(message); processor.ctx.display_update_pending.dirty = true; - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; }, Event::SearchNext => processor.ctx.goto_match(None), Event::ConfigReload(path) => Self::reload_config(&path, processor), Event::Scroll(scroll) => processor.ctx.scroll(scroll), Event::BlinkCursor => { - *processor.ctx.cursor_hidden ^= true; - processor.ctx.terminal.dirty = true; + processor.ctx.display.cursor_hidden ^= true; + processor.ctx.dirty = true; }, Event::TerminalEvent(event) => match event { TerminalEvent::Title(title) => { let ui_config = &processor.ctx.config.ui_config; if ui_config.window.dynamic_title { - processor.ctx.window.set_title(&title); + processor.ctx.window_mut().set_title(&title); } }, TerminalEvent::ResetTitle => { let ui_config = &processor.ctx.config.ui_config; if ui_config.window.dynamic_title { - processor.ctx.window.set_title(&ui_config.window.title); + processor.ctx.display.window.set_title(&ui_config.window.title); } }, - TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true, + TerminalEvent::Wakeup => processor.ctx.dirty = true, TerminalEvent::Bell => { - let bell_command = processor.ctx.config.bell().command.as_ref(); - let _ = bell_command.map(|cmd| start_daemon(cmd.program(), cmd.args())); + // Set window urgency. if processor.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) { - processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused); + let focused = processor.ctx.terminal.is_focused; + processor.ctx.window_mut().set_urgent(!focused); + } + + // Ring visual bell. + processor.ctx.display.visual_bell.ring(); + + // Execute bell command. + if let Some(bell_command) = &processor.ctx.config.ui_config.bell.command { + start_daemon(bell_command.program(), bell_command.args()); } }, TerminalEvent::ClipboardStore(clipboard_type, content) => { @@ -1138,6 +1197,10 @@ impl<N: Notify + OnResize> Processor<N> { let text = format(processor.ctx.clipboard.load(clipboard_type).as_str()); processor.ctx.write_to_pty(text.into_bytes()); }, + TerminalEvent::ColorRequest(index, format) => { + let text = format(processor.ctx.display.colors[index]); + processor.ctx.write_to_pty(text.into_bytes()); + }, TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(), TerminalEvent::Exit => (), TerminalEvent::CursorBlinkingChange(_) => { @@ -1145,7 +1208,7 @@ impl<N: Notify + OnResize> Processor<N> { }, }, }, - GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true, + GlutinEvent::RedrawRequested(_) => processor.ctx.dirty = true, GlutinEvent::WindowEvent { event, window_id, .. } => { match event { WindowEvent::CloseRequested => processor.ctx.terminal.exit(), @@ -1159,37 +1222,37 @@ impl<N: Notify + OnResize> Processor<N> { } processor.ctx.display_update_pending.set_dimensions(size); - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; }, WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => { processor.key_input(input); }, + WindowEvent::ModifiersChanged(modifiers) => { + processor.modifiers_input(modifiers) + }, WindowEvent::ReceivedCharacter(c) => processor.received_char(c), WindowEvent::MouseInput { state, button, .. } => { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); processor.mouse_input(state, button); - processor.ctx.terminal.dirty = true; - }, - WindowEvent::ModifiersChanged(modifiers) => { - processor.modifiers_input(modifiers) + processor.ctx.dirty = true; }, WindowEvent::CursorMoved { position, .. } => { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); processor.mouse_moved(position); }, WindowEvent::MouseWheel { delta, phase, .. } => { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); processor.mouse_wheel_input(delta, phase); }, WindowEvent::Focused(is_focused) => { - if window_id == processor.ctx.window.window_id() { + if window_id == processor.ctx.window().window_id() { processor.ctx.terminal.is_focused = is_focused; - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; if is_focused { - processor.ctx.window.set_urgent(false); + processor.ctx.window_mut().set_urgent(false); } else { - processor.ctx.window.set_mouse_visible(true); + processor.ctx.window_mut().set_mouse_visible(true); } processor.ctx.update_cursor_blinking(); @@ -1203,8 +1266,8 @@ impl<N: Notify + OnResize> Processor<N> { WindowEvent::CursorLeft { .. } => { processor.ctx.mouse.inside_text_area = false; - if processor.highlighted_url.is_some() { - processor.ctx.terminal.dirty = true; + if processor.ctx.highlighted_url().is_some() { + processor.ctx.dirty = true; } }, WindowEvent::KeyboardInput { is_synthetic: true, .. } @@ -1253,10 +1316,9 @@ impl<N: Notify + OnResize> Processor<N> { } } - fn reload_config<T>( - path: &PathBuf, - processor: &mut input::Processor<'_, T, ActionContext<'_, N, T>>, - ) where + /// Reload the configuration files from disk. + fn reload_config<T>(path: &Path, processor: &mut input::Processor<T, ActionContext<'_, N, T>>) + where T: EventListener, { if !processor.ctx.message_buffer.is_empty() { @@ -1269,6 +1331,7 @@ impl<N: Notify + OnResize> Processor<N> { Err(_) => return, }; + processor.ctx.display.update_config(&config); processor.ctx.terminal.update_config(&config); // Reload cursor if its thickness has changed. @@ -1300,12 +1363,12 @@ impl<N: Notify + OnResize> Processor<N> { if !config.ui_config.window.dynamic_title || processor.ctx.config.ui_config.window.title != config.ui_config.window.title { - processor.ctx.window.set_title(&config.ui_config.window.title); + processor.ctx.window_mut().set_title(&config.ui_config.window.title); } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] if processor.ctx.event_loop.is_wayland() { - processor.ctx.window.set_wayland_theme(&config.colors); + processor.ctx.window_mut().set_wayland_theme(&config.ui_config.colors); } // Set subpixel anti-aliasing. @@ -1314,14 +1377,14 @@ impl<N: Notify + OnResize> Processor<N> { // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - processor.ctx.window.set_has_shadow(config.ui_config.background_opacity() >= 1.0); + processor.ctx.window_mut().set_has_shadow(config.ui_config.background_opacity() >= 1.0); *processor.ctx.config = config; // Update cursor blinking. processor.ctx.update_cursor_blinking(); - processor.ctx.terminal.dirty = true; + processor.ctx.dirty = true; } /// Submit the pending changes to the `Display`. |