aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src')
-rw-r--r--alacritty/src/config/bindings.rs18
-rw-r--r--alacritty/src/display.rs173
-rw-r--r--alacritty/src/event.rs350
-rw-r--r--alacritty/src/input.rs154
-rw-r--r--alacritty/src/renderer/mod.rs20
-rw-r--r--alacritty/src/scheduler.rs36
-rw-r--r--alacritty/src/url.rs2
-rw-r--r--alacritty/src/window.rs7
8 files changed, 633 insertions, 127 deletions
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(&regex));
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(&regex);
+
+ // 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(&regex);
+
+ // 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);