diff options
author | Christian Duerr <contact@christianduerr.com> | 2020-07-09 21:45:22 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-09 21:45:22 +0000 |
commit | 46c0f352c40ecb68653421cb178a297acaf00c6d (patch) | |
tree | 3e1985f8237f7c8268703634f8c8ccb25f7823a5 /alacritty | |
parent | 9974bc8baa45fda0b4ba3db2ae615fb7f90f7029 (diff) | |
download | r-alacritty-46c0f352c40ecb68653421cb178a297acaf00c6d.tar.gz r-alacritty-46c0f352c40ecb68653421cb178a297acaf00c6d.tar.bz2 r-alacritty-46c0f352c40ecb68653421cb178a297acaf00c6d.zip |
Add regex scrollback buffer search
This adds a new regex search which allows searching the entire
scrollback and jumping between matches using the vi mode.
All visible matches should be highlighted unless their lines are
excessively long. This should help with performance since highlighting
is done during render time.
Fixes #1017.
Diffstat (limited to 'alacritty')
-rw-r--r-- | alacritty/Cargo.toml | 1 | ||||
-rw-r--r-- | alacritty/src/config/bindings.rs | 18 | ||||
-rw-r--r-- | alacritty/src/display.rs | 173 | ||||
-rw-r--r-- | alacritty/src/event.rs | 350 | ||||
-rw-r--r-- | alacritty/src/input.rs | 154 | ||||
-rw-r--r-- | alacritty/src/renderer/mod.rs | 20 | ||||
-rw-r--r-- | alacritty/src/scheduler.rs | 36 | ||||
-rw-r--r-- | alacritty/src/url.rs | 2 | ||||
-rw-r--r-- | alacritty/src/window.rs | 7 |
9 files changed, 634 insertions, 127 deletions
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 184f2102..62942985 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -23,6 +23,7 @@ parking_lot = "0.10.2" font = { path = "../font" } urlocator = "0.1.3" copypasta = { version = "0.7.0", default-features = false } +unicode-width = "0.1" [build-dependencies] gl_generator = "0.14.0" diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 547e168c..81a46d66 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -176,6 +176,12 @@ pub enum Action { /// Allow receiving char input. ReceiveChar, + /// Start a buffer search. + Search, + + /// Start a reverse buffer search. + SearchReverse, + /// No action. None, } @@ -208,6 +214,14 @@ pub enum ViAction { ToggleBlockSelection, /// Toggle semantic vi selection. ToggleSemanticSelection, + /// Jump to the beginning of the next match. + SearchNext, + /// Jump to the beginning of the previous match. + SearchPrevious, + /// Jump to the end of the next match. + SearchEndNext, + /// Jump to the end of the previous match. + SearchEndPrevious, /// Launch the URL below the vi mode cursor. Open, } @@ -364,10 +378,14 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { D, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageDown; Y, +TermMode::VI; Action::Copy; Y, +TermMode::VI; Action::ClearSelection; + Slash, +TermMode::VI; Action::Search; + Slash, ModifiersState::SHIFT, +TermMode::VI; Action::SearchReverse; V, +TermMode::VI; ViAction::ToggleNormalSelection; V, ModifiersState::SHIFT, +TermMode::VI; ViAction::ToggleLineSelection; V, ModifiersState::CTRL, +TermMode::VI; ViAction::ToggleBlockSelection; V, ModifiersState::ALT, +TermMode::VI; ViAction::ToggleSemanticSelection; + N, +TermMode::VI; ViAction::SearchNext; + N, ModifiersState::SHIFT, +TermMode::VI; ViAction::SearchPrevious; Return, +TermMode::VI; ViAction::Open; K, +TermMode::VI; ViMotion::Up; J, +TermMode::VI; ViMotion::Down; diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index d61a5bbd..53e6fc58 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -1,6 +1,7 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. +use std::cmp::min; use std::f64; use std::fmt::{self, Formatter}; #[cfg(not(any(target_os = "macos", windows)))] @@ -15,6 +16,7 @@ use glutin::platform::unix::EventLoopWindowTargetExtUnix; use glutin::window::CursorIcon; use log::{debug, info}; use parking_lot::MutexGuard; +use unicode_width::UnicodeWidthChar; #[cfg(not(any(target_os = "macos", windows)))] use wayland_client::{Display as WaylandDisplay, EventQueue}; @@ -23,21 +25,23 @@ use font::set_font_smoothing; use font::{self, Rasterize}; use alacritty_terminal::config::{Font, StartupMode}; -use alacritty_terminal::event::OnResize; -use alacritty_terminal::index::Line; +use alacritty_terminal::event::{EventListener, OnResize}; +use alacritty_terminal::grid::Dimensions; +use alacritty_terminal::index::{Column, Line, Point}; use alacritty_terminal::message_bar::MessageBuffer; use alacritty_terminal::meter::Meter; use alacritty_terminal::selection::Selection; -use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode}; use crate::config::Config; -use crate::event::{DisplayUpdate, Mouse}; +use crate::event::Mouse; use crate::renderer::rects::{RenderLines, RenderRect}; use crate::renderer::{self, GlyphCache, QuadRenderer}; use crate::url::{Url, Urls}; use crate::window::{self, Window}; +const SEARCH_LABEL: &str = "Search: "; + #[derive(Debug)] pub enum Error { /// Error with window management. @@ -99,6 +103,44 @@ impl From<glutin::ContextError> for Error { } } +#[derive(Default, Clone, Debug, PartialEq)] +pub struct DisplayUpdate { + pub dirty: bool, + + dimensions: Option<PhysicalSize<u32>>, + font: Option<Font>, + cursor_dirty: bool, +} + +impl DisplayUpdate { + pub fn dimensions(&self) -> Option<PhysicalSize<u32>> { + self.dimensions + } + + pub fn font(&self) -> Option<&Font> { + self.font.as_ref() + } + + pub fn cursor_dirty(&self) -> bool { + self.cursor_dirty + } + + pub fn set_dimensions(&mut self, dimensions: PhysicalSize<u32>) { + self.dimensions = Some(dimensions); + self.dirty = true; + } + + pub fn set_font(&mut self, font: Font) { + self.font = Some(font); + self.dirty = true; + } + + pub fn set_cursor_dirty(&mut self) { + self.cursor_dirty = true; + self.dirty = true; + } +} + /// The display wraps a window, font rasterizer, and GPU renderer. pub struct Display { pub size_info: SizeInfo, @@ -300,7 +342,7 @@ impl Display { } /// Update font size and cell dimensions. - fn update_glyph_cache(&mut self, config: &Config, font: Font) { + fn update_glyph_cache(&mut self, config: &Config, font: &Font) { let size_info = &mut self.size_info; let cache = &mut self.glyph_cache; @@ -328,13 +370,16 @@ impl Display { terminal: &mut Term<T>, pty_resize_handle: &mut dyn OnResize, message_buffer: &MessageBuffer, + search_active: bool, config: &Config, update_pending: DisplayUpdate, - ) { + ) where + T: EventListener, + { // Update font size and cell dimensions. - if let Some(font) = update_pending.font { + if let Some(font) = update_pending.font() { self.update_glyph_cache(config, font); - } else if update_pending.cursor { + } else if update_pending.cursor_dirty() { self.clear_glyph_cache(); } @@ -346,7 +391,7 @@ impl Display { let mut padding_y = f32::from(config.window.padding.y) * self.size_info.dpr as f32; // Update the window dimensions. - if let Some(size) = update_pending.dimensions { + if let Some(size) = update_pending.dimensions() { // Ensure we have at least one column and row. self.size_info.width = (size.width as f32).max(cell_width + 2. * padding_x); self.size_info.height = (size.height as f32).max(cell_height + 2. * padding_y); @@ -369,6 +414,11 @@ impl Display { pty_size.height -= pty_size.cell_height * lines as f32; } + // Add an extra line for the current search regex. + if search_active { + pty_size.height -= pty_size.cell_height; + } + // Resize PTY. pty_resize_handle.on_resize(&pty_size); @@ -393,8 +443,10 @@ impl Display { config: &Config, mouse: &Mouse, mods: ModifiersState, + search_regex: Option<&String>, ) { let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect(); + let search_regex = search_regex.map(|regex| Self::format_search(®ex)); let visual_bell_intensity = terminal.visual_bell.intensity(); let background_color = terminal.background_color(); let metrics = self.glyph_cache.font_metrics(); @@ -413,7 +465,17 @@ impl Display { // Update IME position. #[cfg(not(windows))] - self.window.update_ime_position(&terminal, &self.size_info); + { + let point = match &search_regex { + Some(regex) => { + let column = min(regex.len() + SEARCH_LABEL.len() - 1, terminal.cols().0 - 1); + Point::new(terminal.screen_lines() - 1, Column(column)) + }, + None => terminal.grid().cursor.point, + }; + + self.window.update_ime_position(point, &self.size_info); + } // Drop terminal as early as possible to free lock. drop(terminal); @@ -484,11 +546,13 @@ impl Display { rects.push(visual_bell_rect); } + let mut message_bar_lines = 0; if let Some(message) = message_buffer.message() { let text = message.text(&size_info); + message_bar_lines = text.len(); // Create a new rectangle for the background. - let start_line = size_info.lines().0 - text.len(); + let start_line = size_info.lines().0 - message_bar_lines; let y = size_info.cell_height.mul_add(start_line as f32, size_info.padding_y); let message_bar_rect = RenderRect::new(0., y, size_info.width, size_info.height - y, message.color(), 1.); @@ -500,31 +564,25 @@ impl Display { self.renderer.draw_rects(&size_info, rects); // Relay messages to the user. - let mut offset = 1; - for message_text in text.iter().rev() { + let fg = config.colors.primary.background; + for (i, message_text) in text.iter().rev().enumerate() { self.renderer.with_api(&config, &size_info, |mut api| { api.render_string( - &message_text, - Line(size_info.lines().saturating_sub(offset)), glyph_cache, + Line(size_info.lines().saturating_sub(i + 1)), + &message_text, + fg, None, ); }); - offset += 1; } } else { // Draw rectangles. self.renderer.draw_rects(&size_info, rects); } - // Draw render timer. - if config.render_timer() { - let timing = format!("{:.3} usec", self.meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - self.renderer.with_api(&config, &size_info, |mut api| { - api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color)); - }); - } + self.draw_search(config, &size_info, message_bar_lines, search_regex); + self.draw_render_timer(config, &size_info); // Frame event should be requested before swaping buffers, since it requires surface // `commit`, which is done by swap buffers under the hood. @@ -546,6 +604,73 @@ impl Display { } } + /// Format search regex to account for the cursor and fullwidth characters. + fn format_search(search_regex: &str) -> String { + // Add spacers for wide chars. + let mut text = String::with_capacity(search_regex.len()); + for c in search_regex.chars() { + text.push(c); + if c.width() == Some(2) { + text.push(' '); + } + } + + // Add cursor to show whitespace. + text.push('_'); + + text + } + + /// Draw current search regex. + fn draw_search( + &mut self, + config: &Config, + size_info: &SizeInfo, + message_bar_lines: usize, + search_regex: Option<String>, + ) { + let search_regex = match search_regex { + Some(search_regex) => search_regex, + None => return, + }; + let glyph_cache = &mut self.glyph_cache; + + let label_len = SEARCH_LABEL.len(); + let num_cols = size_info.cols().0; + + // Truncate beginning of text when it exceeds viewport width. + let text_len = search_regex.len(); + let truncate_len = min((text_len + label_len).saturating_sub(num_cols), text_len); + let text = &search_regex[truncate_len..]; + + // Assure text length is at least num_cols. + let padding_len = num_cols.saturating_sub(label_len); + let text = format!("{}{:<2$}", SEARCH_LABEL, text, padding_len); + + let fg = config.colors.search_bar_foreground(); + let bg = config.colors.search_bar_background(); + let line = size_info.lines() - message_bar_lines - 1; + self.renderer.with_api(&config, &size_info, |mut api| { + api.render_string(glyph_cache, line, &text, fg, Some(bg)); + }); + } + + /// Draw render timer. + fn draw_render_timer(&mut self, config: &Config, size_info: &SizeInfo) { + if !config.render_timer() { + return; + } + let glyph_cache = &mut self.glyph_cache; + + let timing = format!("{:.3} usec", self.meter.average()); + let fg = config.colors.normal().black; + let bg = config.colors.normal().red; + + self.renderer.with_api(&config, &size_info, |mut api| { + api.render_string(glyph_cache, size_info.lines() - 2, &timing[..], fg, Some(bg)); + }); + } + /// Requst a new frame for a window on Wayland. #[inline] #[cfg(not(any(target_os = "macos", windows)))] diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 630e8ef0..edbc4086 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -1,7 +1,7 @@ //! Process window events. use std::borrow::Cow; -use std::cmp::max; +use std::cmp::{max, min}; use std::env; #[cfg(unix)] use std::fs; @@ -12,7 +12,7 @@ use std::path::PathBuf; #[cfg(not(any(target_os = "macos", windows)))] use std::sync::atomic::Ordering; use std::sync::Arc; -use std::time::Instant; +use std::time::{Duration, Instant}; use glutin::dpi::PhysicalSize; use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, MouseButton, WindowEvent}; @@ -27,12 +27,10 @@ use serde_json as json; use font::set_font_smoothing; use font::{self, Size}; -use alacritty_terminal::config::Font; use alacritty_terminal::config::LOG_TARGET_CONFIG; -use alacritty_terminal::event::OnResize; -use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify}; -use alacritty_terminal::grid::Scroll; -use alacritty_terminal::index::{Column, Line, Point, Side}; +use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify, OnResize}; +use alacritty_terminal::grid::{Dimensions, Scroll}; +use alacritty_terminal::index::{Column, Direction, Line, Point, Side}; use alacritty_terminal::message_bar::{Message, MessageBuffer}; use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::sync::FairMutex; @@ -46,12 +44,18 @@ use crate::cli::Options; use crate::clipboard::Clipboard; use crate::config; use crate::config::Config; -use crate::display::Display; +use crate::display::{Display, DisplayUpdate}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; -use crate::scheduler::Scheduler; +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); + +/// Maximum number of lines for the blocking search while still typing the search regex. +const MAX_SEARCH_WHILE_TYPING: Option<usize> = Some(1000); + /// Events dispatched through the UI event loop. #[derive(Debug, Clone)] pub enum Event { @@ -60,6 +64,7 @@ pub enum Event { Scroll(Scroll), ConfigReload(PathBuf), Message(Message), + SearchNext, } impl From<Event> for GlutinEvent<'_, Event> { @@ -74,17 +79,35 @@ impl From<TerminalEvent> for Event { } } -#[derive(Default, Clone, Debug, PartialEq)] -pub struct DisplayUpdate { - pub dimensions: Option<PhysicalSize<u32>>, - pub message_buffer: bool, - pub font: Option<Font>, - pub cursor: bool, +/// Regex search state. +pub struct SearchState { + /// Search string regex. + regex: Option<String>, + + /// Search direction. + direction: Direction, + + /// Change in display offset since the beginning of the search. + display_offset_delta: isize, + + /// Vi cursor position before search. + vi_cursor_point: Point, } -impl DisplayUpdate { - fn is_empty(&self) -> bool { - self.dimensions.is_none() && self.font.is_none() && !self.message_buffer && !self.cursor +impl SearchState { + fn new() -> Self { + Self::default() + } +} + +impl Default for SearchState { + fn default() -> Self { + Self { + direction: Direction::Right, + display_offset_delta: 0, + vi_cursor_point: Point::default(), + regex: None, + } } } @@ -104,6 +127,7 @@ pub struct ActionContext<'a, N, T> { pub event_loop: &'a EventLoopWindowTarget<Event>, pub urls: &'a Urls, pub scheduler: &'a mut Scheduler, + pub search_state: &'a mut SearchState, font_size: &'a mut Size, } @@ -294,19 +318,148 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon 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.font.clone().with_size(*self.font_size); - self.display_update_pending.font = Some(font); + self.display_update_pending.set_font(font); self.terminal.dirty = true; } fn reset_font_size(&mut self) { *self.font_size = self.config.font.size; - self.display_update_pending.font = Some(self.config.font.clone()); + self.display_update_pending.set_font(self.config.font.clone()); self.terminal.dirty = true; } + #[inline] fn pop_message(&mut self) { - self.display_update_pending.message_buffer = true; - self.message_buffer.pop(); + if !self.message_buffer.is_empty() { + self.display_update_pending.dirty = true; + self.message_buffer.pop(); + } + } + + #[inline] + fn start_search(&mut self, direction: Direction) { + let num_lines = self.terminal.screen_lines(); + let num_cols = self.terminal.cols(); + + self.search_state.regex = Some(String::new()); + self.search_state.direction = direction; + + // Store original vi cursor position as search origin and for resetting. + self.search_state.vi_cursor_point = if self.terminal.mode().contains(TermMode::VI) { + self.terminal.vi_mode_cursor.point + } else { + match direction { + Direction::Right => Point::new(Line(0), Column(0)), + Direction::Left => Point::new(num_lines - 2, num_cols - 1), + } + }; + + self.display_update_pending.dirty = true; + self.terminal.dirty = true; + } + + #[inline] + fn confirm_search(&mut self) { + // Enter vi mode once search is confirmed. + self.terminal.set_vi_mode(); + + // Force unlimited search if the previous one was interrupted. + if self.scheduler.scheduled(TimerId::DelayedSearch) { + self.goto_match(None); + } + + // Move vi cursor down if resize will pull content from history. + if self.terminal.history_size() != 0 && self.terminal.grid().display_offset() == 0 { + self.terminal.vi_mode_cursor.point.line += 1; + } + + // Clear reset state. + self.search_state.display_offset_delta = 0; + + self.display_update_pending.dirty = true; + self.search_state.regex = None; + self.terminal.dirty = true; + } + + #[inline] + fn cancel_search(&mut self) { + self.terminal.cancel_search(); + + // Recover pre-search state. + self.search_reset_state(); + + // Move vi cursor down if resize will pull from history. + if self.terminal.history_size() != 0 && self.terminal.grid().display_offset() == 0 { + self.terminal.vi_mode_cursor.point.line += 1; + } + + self.display_update_pending.dirty = true; + self.search_state.regex = None; + self.terminal.dirty = true; + } + + #[inline] + fn push_search(&mut self, c: char) { + let regex = match self.search_state.regex.as_mut() { + Some(regex) => regex, + None => return, + }; + + // Hide cursor while typing into the search bar. + if self.config.ui_config.mouse.hide_when_typing { + self.window.set_mouse_visible(false); + } + + // Add new char to search string. + regex.push(c); + + // Create terminal search from the new regex string. + self.terminal.start_search(®ex); + + // Update search highlighting. + self.goto_match(MAX_SEARCH_WHILE_TYPING); + + self.terminal.dirty = true; + } + + #[inline] + fn pop_search(&mut self) { + let regex = match self.search_state.regex.as_mut() { + Some(regex) => regex, + None => return, + }; + + // Hide cursor while typing into the search bar. + if self.config.ui_config.mouse.hide_when_typing { + self.window.set_mouse_visible(false); + } + + // Remove last char from search string. + regex.pop(); + + if regex.is_empty() { + // Stop search if there's nothing to search for. + self.search_reset_state(); + self.terminal.cancel_search(); + } else { + // Create terminal search from the new regex string. + self.terminal.start_search(®ex); + + // Update search highlighting. + self.goto_match(MAX_SEARCH_WHILE_TYPING); + } + + self.terminal.dirty = true; + } + + #[inline] + fn search_direction(&self) -> Direction { + self.search_state.direction + } + + #[inline] + fn search_active(&self) -> bool { + self.search_state.regex.is_some() } fn message(&self) -> Option<&Message> { @@ -334,6 +487,73 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon } } +impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> { + /// Reset terminal to the state before search was started. + fn search_reset_state(&mut self) { + // Reset display offset. + self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta)); + self.search_state.display_offset_delta = 0; + + // Reset vi mode cursor. + let mut vi_cursor_point = self.search_state.vi_cursor_point; + vi_cursor_point.line = min(vi_cursor_point.line, self.terminal.screen_lines() - 1); + vi_cursor_point.col = min(vi_cursor_point.col, self.terminal.cols() - 1); + self.terminal.vi_mode_cursor.point = vi_cursor_point; + + // Unschedule pending timers. + self.scheduler.unschedule(TimerId::DelayedSearch); + } + + /// 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, + }; + + // Limit search only when enough lines are available to run into the limit. + limit = limit.filter(|&limit| limit <= self.terminal.total_lines()); + + // Use original position as search origin. + let mut vi_cursor_point = self.search_state.vi_cursor_point; + vi_cursor_point.line = min(vi_cursor_point.line, self.terminal.screen_lines() - 1); + let mut origin = self.terminal.visible_to_buffer(vi_cursor_point); + origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize; + + // Jump to the next match. + let direction = self.search_state.direction; + match self.terminal.search_next(origin, direction, Side::Left, limit) { + Some(regex_match) => { + let old_offset = self.terminal.grid().display_offset() as isize; + + self.terminal.vi_goto_point(*regex_match.start()); + + // Store number of lines the viewport had to be moved. + let display_offset = self.terminal.grid().display_offset(); + self.search_state.display_offset_delta += old_offset - display_offset as isize; + + // Since we found a result, we require no delayed re-search. + self.scheduler.unschedule(TimerId::DelayedSearch); + }, + // Reset viewport only when we know there is no match, to prevent unnecessary jumping. + None if limit.is_none() => self.search_reset_state(), + None => { + // Schedule delayed search if we ran into our search limit. + if !self.scheduler.scheduled(TimerId::DelayedSearch) { + self.scheduler.schedule( + Event::SearchNext.into(), + TYPING_SEARCH_DELAY, + false, + TimerId::DelayedSearch, + ); + } + }, + } + + self.search_state.regex = Some(regex); + } +} + #[derive(Debug, Eq, PartialEq)] pub enum ClickState { None, @@ -400,6 +620,7 @@ pub struct Processor<N> { display: Display, font_size: Size, event_queue: Vec<GlutinEvent<'static, Event>>, + search_state: SearchState, } impl<N: Notify + OnResize> Processor<N> { @@ -429,6 +650,7 @@ impl<N: Notify + OnResize> Processor<N> { display, event_queue: Vec::new(), clipboard, + search_state: SearchState::new(), } } @@ -513,6 +735,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 context = ActionContext { terminal: &mut terminal, @@ -530,6 +753,7 @@ impl<N: Notify + OnResize> Processor<N> { config: &mut self.config, urls: &self.display.urls, scheduler: &mut scheduler, + search_state: &mut self.search_state, event_loop, }; let mut processor = input::Processor::new(context, &self.display.highlighted_url); @@ -539,14 +763,8 @@ impl<N: Notify + OnResize> Processor<N> { } // Process DisplayUpdate events. - if !display_update_pending.is_empty() { - self.display.handle_update( - &mut terminal, - &mut self.notifier, - &self.message_buffer, - &self.config, - display_update_pending, - ); + if display_update_pending.dirty { + self.submit_display_update(&mut terminal, old_is_searching, display_update_pending); } #[cfg(not(any(target_os = "macos", windows)))] @@ -575,12 +793,15 @@ impl<N: Notify + OnResize> Processor<N> { &self.config, &self.mouse, self.modifiers, + self.search_state.regex.as_ref(), ); } }); // Write ref tests to disk. - self.write_ref_test_results(&terminal.lock()); + if self.config.debug.ref_test { + self.write_ref_test_results(&terminal.lock()); + } } /// Handle events from glutin. @@ -598,20 +819,21 @@ impl<N: Notify + OnResize> Processor<N> { let display_update_pending = &mut processor.ctx.display_update_pending; // Push current font to update its DPR. - display_update_pending.font = - Some(processor.ctx.config.font.clone().with_size(*processor.ctx.font_size)); + let font = processor.ctx.config.font.clone(); + display_update_pending.set_font(font.with_size(*processor.ctx.font_size)); // Resize to event's dimensions, since no resize event is emitted on Wayland. - display_update_pending.dimensions = Some(PhysicalSize::new(width, height)); + display_update_pending.set_dimensions(PhysicalSize::new(width, height)); processor.ctx.size_info.dpr = scale_factor; processor.ctx.terminal.dirty = true; }, Event::Message(message) => { processor.ctx.message_buffer.push(message); - processor.ctx.display_update_pending.message_buffer = true; + processor.ctx.display_update_pending.dirty = true; processor.ctx.terminal.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::TerminalEvent(event) => match event { @@ -647,7 +869,7 @@ impl<N: Notify + OnResize> Processor<N> { } } - processor.ctx.display_update_pending.dimensions = Some(size); + processor.ctx.display_update_pending.set_dimensions(size); processor.ctx.terminal.dirty = true; }, WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => { @@ -741,14 +963,14 @@ impl<N: Notify + OnResize> Processor<N> { } } - pub fn reload_config<T>( - path: &PathBuf, - processor: &mut input::Processor<T, ActionContext<N, T>>, - ) where + fn reload_config<T>(path: &PathBuf, processor: &mut input::Processor<T, ActionContext<N, T>>) + where T: EventListener, { - processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG); - processor.ctx.display_update_pending.message_buffer = true; + if !processor.ctx.message_buffer.is_empty() { + processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG); + processor.ctx.display_update_pending.dirty = true; + } let config = match config::reload_from(&path) { Ok(config) => config, @@ -764,7 +986,7 @@ impl<N: Notify + OnResize> Processor<N> { if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs() > std::f64::EPSILON { - processor.ctx.display_update_pending.cursor = true; + processor.ctx.display_update_pending.set_cursor_dirty(); } if processor.ctx.config.font != config.font { @@ -774,7 +996,7 @@ impl<N: Notify + OnResize> Processor<N> { } let font = config.font.clone().with_size(*processor.ctx.font_size); - processor.ctx.display_update_pending.font = Some(font); + processor.ctx.display_update_pending.set_font(font); } #[cfg(not(any(target_os = "macos", windows)))] @@ -793,12 +1015,44 @@ impl<N: Notify + OnResize> Processor<N> { processor.ctx.terminal.dirty = true; } - // Write the ref test results to the disk. - pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) { - if !self.config.debug.ref_test { - return; + /// Submit the pending changes to the `Display`. + fn submit_display_update<T>( + &mut self, + terminal: &mut Term<T>, + old_is_searching: bool, + display_update_pending: DisplayUpdate, + ) where + T: EventListener, + { + // Compute cursor positions before resize. + let num_lines = terminal.screen_lines(); + let cursor_at_bottom = terminal.grid().cursor.point.line + 1 == num_lines; + let origin_at_bottom = (!terminal.mode().contains(TermMode::VI) + && self.search_state.direction == Direction::Left) + || terminal.vi_mode_cursor.point.line == num_lines - 1; + + self.display.handle_update( + terminal, + &mut self.notifier, + &self.message_buffer, + self.search_state.regex.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() { + let display_offset = terminal.grid().display_offset(); + if display_offset == 0 && cursor_at_bottom && !origin_at_bottom { + terminal.scroll_display(Scroll::Delta(1)); + } else if display_offset != 0 && origin_at_bottom { + terminal.scroll_display(Scroll::Delta(-1)); + } } + } + /// Write the ref test results to the disk. + fn write_ref_test_results<T>(&self, terminal: &Term<T>) { // Dump grid state. let mut grid = terminal.grid().clone(); grid.initialize_all(Cell::default()); diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 2819c850..48450b12 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -15,6 +15,7 @@ use log::{debug, trace, warn}; use glutin::dpi::PhysicalPosition; use glutin::event::{ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, + VirtualKeyCode, }; use glutin::event_loop::EventLoopWindowTarget; #[cfg(target_os = "macos")] @@ -23,8 +24,8 @@ use glutin::window::CursorIcon; use alacritty_terminal::ansi::{ClearMode, Handler}; use alacritty_terminal::event::EventListener; -use alacritty_terminal::grid::Scroll; -use alacritty_terminal::index::{Column, Line, Point, Side}; +use alacritty_terminal::grid::{Dimensions, Scroll}; +use alacritty_terminal::index::{Column, Direction, Line, Point, Side}; use alacritty_terminal::message_bar::{self, Message}; use alacritty_terminal::selection::SelectionType; use alacritty_terminal::term::mode::TermMode; @@ -34,7 +35,7 @@ use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; use crate::config::{Action, Binding, Config, Key, ViAction}; -use crate::event::{ClickState, Event, Mouse}; +use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; use crate::window::Window; @@ -93,6 +94,13 @@ pub trait ActionContext<T: EventListener> { fn mouse_mode(&self) -> bool; fn clipboard_mut(&mut self) -> &mut Clipboard; fn scheduler_mut(&mut self) -> &mut Scheduler; + fn start_search(&mut self, direction: Direction); + fn confirm_search(&mut self); + fn cancel_search(&mut self); + fn push_search(&mut self, c: char); + fn pop_search(&mut self); + fn search_direction(&self) -> Direction; + fn search_active(&self) -> bool; } trait Execute<T: EventListener> { @@ -159,6 +167,13 @@ impl<T: EventListener> Execute<T> for Action { }, Action::ClearSelection => ctx.clear_selection(), Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(), + Action::ViMotion(motion) => { + if ctx.config().ui_config.mouse.hide_when_typing { + ctx.window_mut().set_mouse_visible(false); + } + + ctx.terminal_mut().vi_motion(motion) + }, Action::ViAction(ViAction::ToggleNormalSelection) => { Self::toggle_selection(ctx, SelectionType::Simple) }, @@ -177,13 +192,44 @@ impl<T: EventListener> Execute<T> for Action { ctx.launch_url(url); } }, - Action::ViMotion(motion) => { - if ctx.config().ui_config.mouse.hide_when_typing { - ctx.window_mut().set_mouse_visible(false); + Action::ViAction(ViAction::SearchNext) => { + let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point); + let direction = ctx.search_direction(); + + let regex_match = ctx.terminal().search_next(origin, direction, Side::Left, None); + if let Some(regex_match) = regex_match { + ctx.terminal_mut().vi_goto_point(*regex_match.start()); } + }, + Action::ViAction(ViAction::SearchPrevious) => { + let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point); + let direction = ctx.search_direction().opposite(); - ctx.terminal_mut().vi_motion(motion) + let regex_match = ctx.terminal().search_next(origin, direction, Side::Left, None); + if let Some(regex_match) = regex_match { + ctx.terminal_mut().vi_goto_point(*regex_match.start()); + } + }, + Action::ViAction(ViAction::SearchEndNext) => { + let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point); + let direction = ctx.search_direction(); + + let regex_match = ctx.terminal().search_next(origin, direction, Side::Right, None); + if let Some(regex_match) = regex_match { + ctx.terminal_mut().vi_goto_point(*regex_match.end()); + } }, + Action::ViAction(ViAction::SearchEndPrevious) => { + let origin = ctx.terminal().visible_to_buffer(ctx.terminal().vi_mode_cursor.point); + let direction = ctx.search_direction().opposite(); + + let regex_match = ctx.terminal().search_next(origin, direction, Side::Right, None); + if let Some(regex_match) = regex_match { + ctx.terminal_mut().vi_goto_point(*regex_match.end()); + } + }, + Action::Search => ctx.start_search(Direction::Right), + Action::SearchReverse => ctx.start_search(Direction::Left), Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(), #[cfg(target_os = "macos")] Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(), @@ -199,7 +245,7 @@ impl<T: EventListener> Execute<T> for Action { Action::ScrollPageUp => { // Move vi mode cursor. let term = ctx.terminal_mut(); - let scroll_lines = term.grid().num_lines().0 as isize; + let scroll_lines = term.grid().screen_lines().0 as isize; term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); ctx.scroll(Scroll::PageUp); @@ -207,7 +253,7 @@ impl<T: EventListener> Execute<T> for Action { Action::ScrollPageDown => { // Move vi mode cursor. let term = ctx.terminal_mut(); - let scroll_lines = -(term.grid().num_lines().0 as isize); + let scroll_lines = -(term.grid().screen_lines().0 as isize); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); ctx.scroll(Scroll::PageDown); @@ -215,29 +261,29 @@ impl<T: EventListener> Execute<T> for Action { Action::ScrollHalfPageUp => { // Move vi mode cursor. let term = ctx.terminal_mut(); - let scroll_lines = term.grid().num_lines().0 as isize / 2; + let scroll_lines = term.grid().screen_lines().0 as isize / 2; term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); - ctx.scroll(Scroll::Lines(scroll_lines)); + ctx.scroll(Scroll::Delta(scroll_lines)); }, Action::ScrollHalfPageDown => { // Move vi mode cursor. let term = ctx.terminal_mut(); - let scroll_lines = -(term.grid().num_lines().0 as isize / 2); + let scroll_lines = -(term.grid().screen_lines().0 as isize / 2); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); - ctx.scroll(Scroll::Lines(scroll_lines)); + ctx.scroll(Scroll::Delta(scroll_lines)); }, Action::ScrollLineUp => { // Move vi mode cursor. let term = ctx.terminal(); if term.grid().display_offset() != term.grid().history_size() - && term.vi_mode_cursor.point.line + 1 != term.grid().num_lines() + && term.vi_mode_cursor.point.line + 1 != term.grid().screen_lines() { ctx.terminal_mut().vi_mode_cursor.point.line += 1; } - ctx.scroll(Scroll::Lines(1)); + ctx.scroll(Scroll::Delta(1)); }, Action::ScrollLineDown => { // Move vi mode cursor. @@ -247,7 +293,7 @@ impl<T: EventListener> Execute<T> for Action { ctx.terminal_mut().vi_mode_cursor.point.line -= 1; } - ctx.scroll(Scroll::Lines(-1)); + ctx.scroll(Scroll::Delta(-1)); }, Action::ScrollToTop => { ctx.scroll(Scroll::Top); @@ -261,7 +307,7 @@ impl<T: EventListener> Execute<T> for Action { // Move vi mode cursor. let term = ctx.terminal_mut(); - term.vi_mode_cursor.point.line = term.grid().num_lines() - 1; + term.vi_mode_cursor.point.line = term.grid().screen_lines() - 1; // Move to beginning twice, to always jump across linewraps. term.vi_motion(ViMotion::FirstOccupied); @@ -360,7 +406,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { self.update_url_state(&mouse_state); self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); - let last_term_line = self.ctx.terminal().grid().num_lines() - 1; + let last_term_line = self.ctx.terminal().grid().screen_lines() - 1; if (lmb_pressed || self.ctx.mouse().right_button_state == ElementState::Pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) { @@ -520,7 +566,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { // Load mouse point, treating message bar and padding as the closest cell. let mouse = self.ctx.mouse(); let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y); - point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1); + point.line = min(point.line, self.ctx.terminal().grid().screen_lines() - 1); match button { MouseButton::Left => self.on_left_click(point), @@ -690,7 +736,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { let term = self.ctx.terminal(); let absolute = term.visible_to_buffer(term.vi_mode_cursor.point); - self.ctx.scroll(Scroll::Lines(lines as isize)); + self.ctx.scroll(Scroll::Delta(lines as isize)); // Try to restore vi mode cursor position, to keep it above its previous content. let term = self.ctx.terminal_mut(); @@ -733,7 +779,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { // Reset cursor when message bar height changed or all messages are gone. let size = self.ctx.size_info(); - let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0; + let current_lines = (size.lines() - self.ctx.terminal().grid().screen_lines()).0; let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0); let new_icon = match current_lines.cmp(&new_lines) { @@ -749,7 +795,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { }; self.ctx.window_mut().set_mouse_cursor(new_icon); - } else { + } else if !self.ctx.search_active() { match state { ElementState::Pressed => { self.process_mouse_bindings(button); @@ -763,6 +809,28 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { /// Process key input. pub fn key_input(&mut self, input: KeyboardInput) { match input.state { + ElementState::Pressed if self.ctx.search_active() => { + match input.virtual_keycode { + Some(VirtualKeyCode::Back) => { + self.ctx.pop_search(); + *self.ctx.suppress_chars() = true; + }, + Some(VirtualKeyCode::Return) => { + self.ctx.confirm_search(); + *self.ctx.suppress_chars() = true; + }, + Some(VirtualKeyCode::Escape) => { + self.ctx.cancel_search(); + *self.ctx.suppress_chars() = true; + }, + _ => (), + } + + // Reset search delay when the user is still typing. + if let Some(timer) = self.ctx.scheduler_mut().get_mut(TimerId::DelayedSearch) { + timer.deadline = Instant::now() + TYPING_SEARCH_DELAY; + } + }, ElementState::Pressed => { *self.ctx.received_count() = 0; self.process_key_bindings(input); @@ -783,7 +851,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { /// Process a received character. pub fn received_char(&mut self, c: char) { - if *self.ctx.suppress_chars() || self.ctx.terminal().mode().contains(TermMode::VI) { + let suppress_chars = *self.ctx.suppress_chars(); + let search_active = self.ctx.search_active(); + if suppress_chars || self.ctx.terminal().mode().contains(TermMode::VI) || search_active { + if search_active { + // Skip control characters. + let c_decimal = c as isize; + let is_printable = (c_decimal >= 0x20 && c_decimal < 0x7f) || c_decimal >= 0xa0; + + if !suppress_chars && is_printable { + self.ctx.push_search(c); + } + } + return; } @@ -877,15 +957,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { /// Check if the cursor is hovering above the message bar. fn message_at_cursor(&mut self) -> bool { - self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines() + self.ctx.mouse().line >= self.ctx.terminal().grid().screen_lines() } /// Whether the point is over the message bar's close button. fn message_close_at_cursor(&self) -> bool { let mouse = self.ctx.mouse(); + + // Since search is above the message bar, the button is offset by search's height. + let search_height = if self.ctx.search_active() { 1 } else { 0 }; + mouse.inside_grid && mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= self.ctx.size_info().cols() - && mouse.line == self.ctx.terminal().grid().num_lines() + && mouse.line == self.ctx.terminal().grid().screen_lines() + search_height } /// Copy text selection. @@ -967,7 +1051,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> { // Scale number of lines scrolled based on distance to boundary. let delta = delta as isize / step as isize; - let event = Event::Scroll(Scroll::Lines(delta)); + let event = Event::Scroll(Scroll::Delta(delta)); // Schedule event. match scheduler.get_mut(TimerId::SelectionScrolling) { @@ -1036,6 +1120,24 @@ mod tests { fn reset_font_size(&mut self) {} + fn start_search(&mut self, _direction: Direction) {} + + fn confirm_search(&mut self) {} + + fn cancel_search(&mut self) {} + + fn push_search(&mut self, _c: char) {} + + fn pop_search(&mut self) {} + + fn search_direction(&self) -> Direction { + Direction::Right + } + + fn search_active(&self) -> bool { + false + } + fn terminal(&self) -> &Term<T> { &self.terminal } diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index b2940a93..7dc037a1 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -298,7 +298,7 @@ impl GlyphCache { pub fn update_font_size<L: LoadGlyph>( &mut self, - font: config::Font, + font: &config::Font, dpr: f64, loader: &mut L, ) -> Result<(), font::Error> { @@ -307,7 +307,7 @@ impl GlyphCache { // Recompute font keys. let (regular, bold, italic, bold_italic) = - Self::compute_font_keys(&font, &mut self.rasterizer)?; + Self::compute_font_keys(font, &mut self.rasterizer)?; self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; let metrics = self.rasterizer.metrics(regular, font.size)?; @@ -968,29 +968,29 @@ impl<'a, C> RenderApi<'a, C> { /// errors. pub fn render_string( &mut self, - string: &str, - line: Line, glyph_cache: &mut GlyphCache, - color: Option<Rgb>, + line: Line, + string: &str, + fg: Rgb, + bg: Option<Rgb>, ) { - let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0); - let col = Column(0); + let bg_alpha = bg.map(|_| 1.0).unwrap_or(0.0); let cells = string .chars() .enumerate() .map(|(i, c)| RenderableCell { line, - column: col + i, + column: Column(i), inner: RenderableCellContent::Chars({ let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1]; chars[0] = c; chars }), - bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), - fg: Rgb { r: 0, g: 0, b: 0 }, flags: Flags::empty(), bg_alpha, + fg, + bg: bg.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), }) .collect::<Vec<_>>(); diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs index 673029ae..db6180ca 100644 --- a/alacritty/src/scheduler.rs +++ b/alacritty/src/scheduler.rs @@ -9,6 +9,22 @@ use crate::event::Event as AlacrittyEvent; type Event = GlutinEvent<'static, AlacrittyEvent>; +/// ID uniquely identifying a timer. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TimerId { + SelectionScrolling, + DelayedSearch, +} + +/// Event scheduled to be emitted at a specific time. +pub struct Timer { + pub deadline: Instant, + pub event: Event, + + interval: Option<Duration>, + id: TimerId, +} + /// Scheduler tracking all pending timers. pub struct Scheduler { timers: VecDeque<Timer>, @@ -74,23 +90,13 @@ impl Scheduler { self.timers.remove(index).map(|timer| timer.event) } + /// Check if a timer is already scheduled. + pub fn scheduled(&mut self, id: TimerId) -> bool { + self.timers.iter().any(|timer| timer.id == id) + } + /// Access a staged event by ID. pub fn get_mut(&mut self, id: TimerId) -> Option<&mut Timer> { self.timers.iter_mut().find(|timer| timer.id == id) } } - -/// ID uniquely identifying a timer. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum TimerId { - SelectionScrolling, -} - -/// Event scheduled to be emitted at a specific time. -pub struct Timer { - pub deadline: Instant, - pub event: Event, - - interval: Option<Duration>, - id: TimerId, -} diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs index f7c7105c..08a85f1b 100644 --- a/alacritty/src/url.rs +++ b/alacritty/src/url.rs @@ -90,7 +90,7 @@ impl Urls { self.last_point = Some(end); // Extend current state if a wide char spacer is encountered. - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { + if cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) { if let UrlLocation::Url(_, mut end_offset) = self.state { if end_offset != 0 { end_offset += 1; diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 4275f859..450329d4 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -35,7 +35,9 @@ use winapi::shared::minwindef::WORD; use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig}; #[cfg(not(windows))] -use alacritty_terminal::term::{SizeInfo, Term}; +use alacritty_terminal::index::Point; +#[cfg(not(windows))] +use alacritty_terminal::term::SizeInfo; use crate::config::Config; use crate::gl; @@ -398,8 +400,7 @@ impl Window { /// Adjust the IME editor position according to the new location of the cursor. #[cfg(not(windows))] - pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) { - let point = terminal.grid().cursor.point; + pub fn update_ime_position(&mut self, point: Point, size_info: &SizeInfo) { let SizeInfo { cell_width, cell_height, padding_x, padding_y, .. } = size_info; let nspot_x = f64::from(padding_x + point.col.0 as f32 * cell_width); |