2020-05-05 22:50:23 +00:00
|
|
|
//! Process window events.
|
2020-06-06 18:49:14 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::borrow::Cow;
|
2020-07-09 21:45:22 +00:00
|
|
|
use std::cmp::{max, min};
|
2020-12-20 04:27:08 +00:00
|
|
|
use std::collections::VecDeque;
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::env;
|
2020-12-21 02:44:38 +00:00
|
|
|
use std::f32;
|
2020-07-10 19:32:44 +00:00
|
|
|
use std::fmt::Debug;
|
2020-11-07 04:48:48 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
2020-01-10 01:51:37 +00:00
|
|
|
use std::mem;
|
2020-11-13 05:40:09 +00:00
|
|
|
use std::ops::RangeInclusive;
|
2021-01-24 21:45:36 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2020-05-03 23:29:11 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
|
|
|
use std::sync::atomic::Ordering;
|
2019-10-05 00:29:26 +00:00
|
|
|
use std::sync::Arc;
|
2020-07-09 21:45:22 +00:00
|
|
|
use std::time::{Duration, Instant};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
use glutin::dpi::PhysicalSize;
|
2020-06-23 09:57:15 +00:00
|
|
|
use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, MouseButton, WindowEvent};
|
2020-02-07 13:44:11 +00:00
|
|
|
use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
2020-12-11 00:40:32 +00:00
|
|
|
use glutin::platform::run_return::EventLoopExtRunReturn;
|
2020-10-12 09:22:36 +00:00
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
2020-04-09 01:02:10 +00:00
|
|
|
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
|
2020-07-10 19:32:44 +00:00
|
|
|
use log::info;
|
2019-10-05 00:29:26 +00:00
|
|
|
use serde_json as json;
|
|
|
|
|
2020-07-18 01:27:41 +00:00
|
|
|
use crossfont::{self, Size};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
use alacritty_terminal::config::LOG_TARGET_CONFIG;
|
2020-07-09 21:45:22 +00:00
|
|
|
use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify, OnResize};
|
|
|
|
use alacritty_terminal::grid::{Dimensions, Scroll};
|
2020-07-15 21:27:32 +00:00
|
|
|
use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side};
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::selection::{Selection, SelectionType};
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::sync::FairMutex;
|
2021-01-24 21:45:36 +00:00
|
|
|
use alacritty_terminal::term::search::{Match, RegexSearch};
|
2020-06-06 21:33:20 +00:00
|
|
|
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
|
2019-11-16 21:11:56 +00:00
|
|
|
#[cfg(not(windows))]
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::tty;
|
|
|
|
|
2020-08-22 20:55:27 +00:00
|
|
|
use crate::cli::Options as CLIOptions;
|
2020-06-06 21:33:20 +00:00
|
|
|
use crate::clipboard::Clipboard;
|
2019-10-05 00:29:26 +00:00
|
|
|
use crate::config;
|
|
|
|
use crate::config::Config;
|
2020-07-10 19:32:44 +00:00
|
|
|
use crate::daemon::start_daemon;
|
2021-01-24 21:45:36 +00:00
|
|
|
use crate::display::window::Window;
|
2020-07-09 21:45:22 +00:00
|
|
|
use crate::display::{Display, DisplayUpdate};
|
2019-11-11 21:05:24 +00:00
|
|
|
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
|
2020-11-07 04:48:48 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
use crate::macos;
|
2020-07-11 17:03:09 +00:00
|
|
|
use crate::message_bar::{Message, MessageBuffer};
|
2020-07-09 21:45:22 +00:00
|
|
|
use crate::scheduler::{Scheduler, TimerId};
|
2020-03-18 02:35:08 +00:00
|
|
|
use crate::url::{Url, Urls};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// 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);
|
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
/// Maximum number of search terms stored in the history.
|
|
|
|
const MAX_HISTORY_SIZE: usize = 255;
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
/// Events dispatched through the UI event loop.
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum Event {
|
|
|
|
TerminalEvent(TerminalEvent),
|
2021-02-13 18:15:57 +00:00
|
|
|
DprChanged(f64, (u32, u32)),
|
2020-06-18 01:02:56 +00:00
|
|
|
Scroll(Scroll),
|
|
|
|
ConfigReload(PathBuf),
|
|
|
|
Message(Message),
|
2020-11-23 23:11:03 +00:00
|
|
|
BlinkCursor,
|
2020-07-09 21:45:22 +00:00
|
|
|
SearchNext,
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Event> for GlutinEvent<'_, Event> {
|
|
|
|
fn from(event: Event) -> Self {
|
|
|
|
GlutinEvent::UserEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TerminalEvent> for Event {
|
|
|
|
fn from(event: TerminalEvent) -> Self {
|
|
|
|
Event::TerminalEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// Regex search state.
|
|
|
|
pub struct SearchState {
|
|
|
|
/// Search direction.
|
|
|
|
direction: Direction,
|
|
|
|
|
|
|
|
/// Change in display offset since the beginning of the search.
|
|
|
|
display_offset_delta: isize,
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
/// Search origin in viewport coordinates relative to original display offset.
|
|
|
|
origin: Point,
|
2020-11-13 05:40:09 +00:00
|
|
|
|
|
|
|
/// Focused match during active search.
|
|
|
|
focused_match: Option<RangeInclusive<Point<usize>>>,
|
2020-12-20 04:27:08 +00:00
|
|
|
|
|
|
|
/// Search regex and history.
|
|
|
|
///
|
2021-01-24 21:45:36 +00:00
|
|
|
/// 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.
|
2020-12-20 04:27:08 +00:00
|
|
|
history: VecDeque<String>,
|
|
|
|
|
|
|
|
/// Current position in the search history.
|
|
|
|
history_index: Option<usize>,
|
2021-01-24 21:45:36 +00:00
|
|
|
|
|
|
|
/// Compiled search automatons.
|
|
|
|
dfas: Option<RegexSearch>,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
impl SearchState {
|
|
|
|
fn new() -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
2020-07-17 01:26:53 +00:00
|
|
|
|
|
|
|
/// Search regex text if a search is active.
|
|
|
|
pub fn regex(&self) -> Option<&String> {
|
2020-12-20 04:27:08 +00:00
|
|
|
self.history_index.and_then(|index| self.history.get(index))
|
2020-07-17 01:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Direction of the search from the search origin.
|
|
|
|
pub fn direction(&self) -> Direction {
|
|
|
|
self.direction
|
|
|
|
}
|
2020-11-13 05:40:09 +00:00
|
|
|
|
|
|
|
/// Focused match during vi-less search.
|
|
|
|
pub fn focused_match(&self) -> Option<&RangeInclusive<Point<usize>>> {
|
|
|
|
self.focused_match.as_ref()
|
|
|
|
}
|
2020-12-20 04:27:08 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// Active search dfas.
|
|
|
|
pub fn dfas(&self) -> Option<&RegexSearch> {
|
|
|
|
self.dfas.as_ref()
|
|
|
|
}
|
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
/// 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))
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SearchState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
direction: Direction::Right,
|
2020-12-20 04:27:08 +00:00
|
|
|
display_offset_delta: Default::default(),
|
|
|
|
focused_match: Default::default(),
|
|
|
|
history_index: Default::default(),
|
|
|
|
history: Default::default(),
|
|
|
|
origin: Default::default(),
|
2021-01-24 21:45:36 +00:00
|
|
|
dfas: Default::default(),
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ActionContext<'a, N, T> {
|
|
|
|
pub notifier: &'a mut N,
|
|
|
|
pub terminal: &'a mut Term<T>,
|
2020-06-06 21:33:20 +00:00
|
|
|
pub clipboard: &'a mut Clipboard,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub mouse: &'a mut Mouse,
|
|
|
|
pub received_count: &'a mut usize,
|
|
|
|
pub suppress_chars: &'a mut bool,
|
2019-11-11 21:05:24 +00:00
|
|
|
pub modifiers: &'a mut ModifiersState,
|
2021-01-24 21:45:36 +00:00
|
|
|
pub display: &'a mut Display,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub message_buffer: &'a mut MessageBuffer,
|
2019-11-03 19:02:26 +00:00
|
|
|
pub display_update_pending: &'a mut DisplayUpdate,
|
|
|
|
pub config: &'a mut Config,
|
2020-02-07 13:44:11 +00:00
|
|
|
pub event_loop: &'a EventLoopWindowTarget<Event>,
|
2020-06-18 01:02:56 +00:00
|
|
|
pub scheduler: &'a mut Scheduler,
|
2020-07-09 21:45:22 +00:00
|
|
|
pub search_state: &'a mut SearchState,
|
2020-08-22 20:55:27 +00:00
|
|
|
cli_options: &'a CLIOptions,
|
2019-11-03 19:02:26 +00:00
|
|
|
font_size: &'a mut Size,
|
2021-01-27 16:21:38 +00:00
|
|
|
dirty: &'a mut bool,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionContext<'a, N, T> {
|
2021-01-24 21:45:36 +00:00
|
|
|
#[inline]
|
2019-10-05 00:29:26 +00:00
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) {
|
|
|
|
self.notifier.notify(val);
|
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// Request a redraw.
|
|
|
|
#[inline]
|
|
|
|
fn mark_dirty(&mut self) {
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2021-01-24 21:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-10-05 00:29:26 +00:00
|
|
|
fn size_info(&self) -> SizeInfo {
|
2021-01-24 21:45:36 +00:00
|
|
|
self.display.size_info
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn scroll(&mut self, scroll: Scroll) {
|
2020-07-15 21:27:32 +00:00
|
|
|
let old_offset = self.terminal.grid().display_offset() as isize;
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
self.terminal.scroll_display(scroll);
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
// Keep track of manual display offset changes during search.
|
|
|
|
if self.search_active() {
|
|
|
|
let display_offset = self.terminal.grid().display_offset();
|
|
|
|
self.search_state.display_offset_delta += old_offset - display_offset as isize;
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Update selection.
|
2020-03-18 02:35:08 +00:00
|
|
|
if self.terminal.mode().contains(TermMode::VI)
|
2020-05-30 20:45:44 +00:00
|
|
|
&& self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true)
|
2020-03-18 02:35:08 +00:00
|
|
|
{
|
|
|
|
self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right);
|
2020-07-27 19:05:25 +00:00
|
|
|
} else if self.mouse().left_button_state == ElementState::Pressed
|
|
|
|
|| self.mouse().right_button_state == ElementState::Pressed
|
|
|
|
{
|
2020-08-14 07:26:42 +00:00
|
|
|
let point = self.size_info().pixels_to_coords(self.mouse().x, self.mouse().y);
|
2019-10-05 00:29:26 +00:00
|
|
|
let cell_side = self.mouse().cell_side;
|
2021-01-24 21:45:36 +00:00
|
|
|
self.update_selection(point, cell_side);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2021-01-24 21:45:36 +00:00
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn copy_selection(&mut self, ty: ClipboardType) {
|
|
|
|
if let Some(selected) = self.terminal.selection_to_string() {
|
|
|
|
if !selected.is_empty() {
|
2020-06-06 21:33:20 +00:00
|
|
|
self.clipboard.store(ty, selected);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn selection_is_empty(&self) -> bool {
|
2020-05-30 20:45:44 +00:00
|
|
|
self.terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true)
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_selection(&mut self) {
|
2020-05-30 20:45:44 +00:00
|
|
|
self.terminal.selection = None;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
fn update_selection(&mut self, mut point: Point, side: Side) {
|
|
|
|
let mut selection = match self.terminal.selection.take() {
|
|
|
|
Some(selection) => selection,
|
|
|
|
None => return,
|
|
|
|
};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
// Treat motion over message bar like motion over the last line.
|
|
|
|
point.line = min(point.line, self.terminal.screen_lines() - 1);
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
// Update selection.
|
|
|
|
let absolute_point = self.terminal.visible_to_buffer(point);
|
|
|
|
selection.update(absolute_point, side);
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
// Move vi cursor and expand selection.
|
2020-11-13 05:40:09 +00:00
|
|
|
if self.terminal.mode().contains(TermMode::VI) && !self.search_active() {
|
2020-08-14 07:26:42 +00:00
|
|
|
self.terminal.vi_mode_cursor.point = point;
|
|
|
|
selection.include_all();
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
2020-08-14 07:26:42 +00:00
|
|
|
|
|
|
|
self.terminal.selection = Some(selection);
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
|
2019-10-05 00:29:26 +00:00
|
|
|
let point = self.terminal.visible_to_buffer(point);
|
2020-05-30 20:45:44 +00:00
|
|
|
self.terminal.selection = Some(Selection::new(ty, point, side));
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
fn toggle_selection(&mut self, ty: SelectionType, point: Point, side: Side) {
|
2020-05-30 20:45:44 +00:00
|
|
|
match &mut self.terminal.selection {
|
2020-03-18 02:35:08 +00:00
|
|
|
Some(selection) if selection.ty == ty && !selection.is_empty() => {
|
|
|
|
self.clear_selection();
|
|
|
|
},
|
|
|
|
Some(selection) if !selection.is_empty() => {
|
|
|
|
selection.ty = ty;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
|
|
|
_ => self.start_selection(ty, point, side),
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn mouse_coords(&self) -> Option<Point> {
|
|
|
|
let x = self.mouse.x as usize;
|
|
|
|
let y = self.mouse.y as usize;
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if self.display.size_info.contains_point(x, y) {
|
|
|
|
Some(self.display.size_info.pixels_to_coords(x, y))
|
2019-10-05 00:29:26 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
#[inline]
|
|
|
|
fn mouse_mode(&self) -> bool {
|
|
|
|
self.terminal.mode().intersects(TermMode::MOUSE_MODE)
|
|
|
|
&& !self.terminal.mode().contains(TermMode::VI)
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
#[inline]
|
|
|
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn mouse(&self) -> &Mouse {
|
|
|
|
self.mouse
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn received_count(&mut self) -> &mut usize {
|
|
|
|
&mut self.received_count
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn suppress_chars(&mut self) -> &mut bool {
|
|
|
|
&mut self.suppress_chars
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-11-11 21:05:24 +00:00
|
|
|
fn modifiers(&mut self) -> &mut ModifiersState {
|
2019-10-05 00:29:26 +00:00
|
|
|
&mut self.modifiers
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn window(&self) -> &Window {
|
2021-01-24 21:45:36 +00:00
|
|
|
&self.display.window
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn window_mut(&mut self) -> &mut Window {
|
2021-01-24 21:45:36 +00:00
|
|
|
&mut self.display.window
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn terminal(&self) -> &Term<T> {
|
|
|
|
self.terminal
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn terminal_mut(&mut self) -> &mut Term<T> {
|
|
|
|
self.terminal
|
|
|
|
}
|
|
|
|
|
|
|
|
fn spawn_new_instance(&mut self) {
|
2020-09-06 15:38:49 +00:00
|
|
|
let mut env_args = env::args();
|
|
|
|
let alacritty = env_args.next().unwrap();
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
#[cfg(unix)]
|
2020-09-06 15:38:49 +00:00
|
|
|
let mut args = {
|
2020-08-06 00:42:49 +00:00
|
|
|
// Use working directory of controlling process, or fallback to initial shell.
|
|
|
|
let mut pid = unsafe { libc::tcgetpgrp(tty::master_fd()) };
|
|
|
|
if pid < 0 {
|
|
|
|
pid = tty::child_pid();
|
|
|
|
}
|
|
|
|
|
2020-11-07 04:48:48 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
|
2020-08-06 00:42:49 +00:00
|
|
|
let link_path = format!("/proc/{}/cwd", pid);
|
2019-10-05 00:29:26 +00:00
|
|
|
#[cfg(target_os = "freebsd")]
|
2020-08-06 00:42:49 +00:00
|
|
|
let link_path = format!("/compat/linux/proc/{}/cwd", pid);
|
2020-11-07 04:48:48 +00:00
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
|
|
let cwd = fs::read_link(link_path);
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
let cwd = macos::proc::cwd(pid);
|
2020-08-06 00:42:49 +00:00
|
|
|
|
2020-09-06 15:38:49 +00:00
|
|
|
// Add the current working directory as parameter.
|
2020-11-07 04:48:48 +00:00
|
|
|
cwd.map(|path| vec!["--working-directory".into(), path]).unwrap_or_default()
|
2019-10-05 00:29:26 +00:00
|
|
|
};
|
2020-09-06 15:38:49 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
#[cfg(not(unix))]
|
2020-09-06 15:38:49 +00:00
|
|
|
let mut args: Vec<PathBuf> = Vec::new();
|
|
|
|
|
|
|
|
let working_directory_set = !args.is_empty();
|
|
|
|
|
|
|
|
// Reuse the arguments passed to Alacritty for the new instance.
|
|
|
|
while let Some(arg) = env_args.next() {
|
|
|
|
// Drop working directory from existing parameters.
|
|
|
|
if working_directory_set && arg == "--working-directory" {
|
|
|
|
let _ = env_args.next();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
args.push(arg.into());
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-07-10 19:32:44 +00:00
|
|
|
start_daemon(&alacritty, &args);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
/// Spawn URL launcher when clicking on URLs.
|
|
|
|
fn launch_url(&self, url: Url) {
|
|
|
|
if self.mouse.block_url_launcher {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher {
|
|
|
|
let mut args = launcher.args().to_vec();
|
|
|
|
let start = self.terminal.visible_to_buffer(url.start());
|
|
|
|
let end = self.terminal.visible_to_buffer(url.end());
|
|
|
|
args.push(self.terminal.bounds_to_string(start, end));
|
|
|
|
|
2020-07-10 19:32:44 +00:00
|
|
|
start_daemon(launcher.program(), &args);
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
fn highlighted_url(&self) -> Option<&Url> {
|
|
|
|
self.display.highlighted_url.as_ref()
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
fn change_font_size(&mut self, delta: f32) {
|
2019-11-03 19:02:26 +00:00
|
|
|
*self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP));
|
2020-07-11 17:03:09 +00:00
|
|
|
let font = self.config.ui_config.font.clone().with_size(*self.font_size);
|
2020-07-09 21:45:22 +00:00
|
|
|
self.display_update_pending.set_font(font);
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn reset_font_size(&mut self) {
|
2020-12-21 02:44:38 +00:00
|
|
|
*self.font_size = self.config.ui_config.font.size();
|
2020-07-11 17:03:09 +00:00
|
|
|
self.display_update_pending.set_font(self.config.ui_config.font.clone());
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
#[inline]
|
2019-10-05 00:29:26 +00:00
|
|
|
fn pop_message(&mut self) {
|
2020-07-09 21:45:22 +00:00
|
|
|
if !self.message_buffer.is_empty() {
|
|
|
|
self.display_update_pending.dirty = true;
|
|
|
|
self.message_buffer.pop();
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn start_search(&mut self, direction: Direction) {
|
|
|
|
let num_lines = self.terminal.screen_lines();
|
|
|
|
let num_cols = self.terminal.cols();
|
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
// Only create new history entry if the previous regex wasn't empty.
|
|
|
|
if self.search_state.history.get(0).map_or(true, |regex| !regex.is_empty()) {
|
|
|
|
self.search_state.history.push_front(String::new());
|
|
|
|
self.search_state.history.truncate(MAX_HISTORY_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.search_state.history_index = Some(0);
|
2020-07-09 21:45:22 +00:00
|
|
|
self.search_state.direction = direction;
|
2020-12-20 04:27:08 +00:00
|
|
|
self.search_state.focused_match = None;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
// Store original search position as origin and reset location.
|
2021-01-01 09:21:02 +00:00
|
|
|
if self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
self.search_state.origin = self.terminal.vi_mode_cursor.point;
|
|
|
|
self.search_state.display_offset_delta = 0;
|
2020-07-09 21:45:22 +00:00
|
|
|
} else {
|
|
|
|
match direction {
|
2021-01-03 11:24:04 +00:00
|
|
|
Direction::Right => self.search_state.origin = Point::new(Line(0), Column(0)),
|
2021-01-01 09:21:02 +00:00
|
|
|
Direction::Left => {
|
2021-01-03 11:24:04 +00:00
|
|
|
self.search_state.origin = Point::new(num_lines - 2, num_cols - 1);
|
2021-01-01 09:21:02 +00:00
|
|
|
},
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2021-01-01 09:21:02 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
self.display_update_pending.dirty = true;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn confirm_search(&mut self) {
|
2020-12-19 04:07:20 +00:00
|
|
|
// Just cancel search when not in vi mode.
|
|
|
|
if !self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
self.cancel_search();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
// Force unlimited search if the previous one was interrupted.
|
|
|
|
if self.scheduler.scheduled(TimerId::DelayedSearch) {
|
|
|
|
self.goto_match(None);
|
|
|
|
}
|
|
|
|
|
2020-09-27 22:36:08 +00:00
|
|
|
self.exit_search();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn cancel_search(&mut self) {
|
2020-07-25 14:05:11 +00:00
|
|
|
if self.terminal.mode().contains(TermMode::VI) {
|
2020-11-13 05:40:09 +00:00
|
|
|
// Recover pre-search state in vi mode.
|
2020-07-25 14:05:11 +00:00
|
|
|
self.search_reset_state();
|
2020-11-13 05:40:09 +00:00
|
|
|
} else if let Some(focused_match) = &self.search_state.focused_match {
|
|
|
|
// Create a selection for the focused match.
|
|
|
|
let start = self.terminal.grid().clamp_buffer_to_visible(*focused_match.start());
|
|
|
|
let end = self.terminal.grid().clamp_buffer_to_visible(*focused_match.end());
|
|
|
|
self.start_selection(SelectionType::Simple, start, Side::Left);
|
|
|
|
self.update_selection(end, Side::Right);
|
2020-07-25 14:05:11 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
self.search_state.dfas = None;
|
|
|
|
|
2020-09-27 22:36:08 +00:00
|
|
|
self.exit_search();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-12-19 04:07:20 +00:00
|
|
|
fn search_input(&mut self, c: char) {
|
2020-12-20 04:27:08 +00:00
|
|
|
match self.search_state.history_index {
|
|
|
|
Some(0) => (),
|
|
|
|
// When currently in history, replace active regex with history on change.
|
|
|
|
Some(index) => {
|
|
|
|
self.search_state.history[0] = self.search_state.history[index].clone();
|
|
|
|
self.search_state.history_index = Some(0);
|
|
|
|
},
|
|
|
|
None => return,
|
|
|
|
}
|
|
|
|
let regex = &mut self.search_state.history[0];
|
2020-12-19 04:07:20 +00:00
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
match c {
|
|
|
|
// Handle backspace/ctrl+h.
|
|
|
|
'\x08' | '\x7f' => {
|
|
|
|
let _ = regex.pop();
|
|
|
|
},
|
|
|
|
// Add ascii and unicode text.
|
|
|
|
' '..='~' | '\u{a0}'..='\u{10ffff}' => regex.push(c),
|
|
|
|
// Ignore non-printable characters.
|
|
|
|
_ => return,
|
|
|
|
}
|
2020-07-17 00:30:34 +00:00
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
if !self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
// Clear selection so we do not obstruct any matches.
|
|
|
|
self.terminal.selection = None;
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2020-12-20 04:27:08 +00:00
|
|
|
|
|
|
|
self.update_search();
|
2020-07-10 21:00:33 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2020-07-10 21:00:33 +00:00
|
|
|
#[inline]
|
2020-12-20 04:27:08 +00:00
|
|
|
fn search_pop_word(&mut self) {
|
|
|
|
if let Some(regex) = self.search_state.regex_mut() {
|
2020-07-10 21:00:33 +00:00
|
|
|
*regex = regex.trim_end().to_owned();
|
|
|
|
regex.truncate(regex.rfind(' ').map(|i| i + 1).unwrap_or(0));
|
|
|
|
self.update_search();
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-20 04:27:08 +00:00
|
|
|
/// Go to the previous regex in the search history.
|
|
|
|
#[inline]
|
|
|
|
fn search_history_previous(&mut self) {
|
|
|
|
let index = match &mut self.search_state.history_index {
|
|
|
|
None => return,
|
|
|
|
Some(index) if *index + 1 >= self.search_state.history.len() => return,
|
|
|
|
Some(index) => index,
|
|
|
|
};
|
|
|
|
|
|
|
|
*index += 1;
|
|
|
|
self.update_search();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Go to the previous regex in the search history.
|
|
|
|
#[inline]
|
|
|
|
fn search_history_next(&mut self) {
|
|
|
|
let index = match &mut self.search_state.history_index {
|
|
|
|
Some(0) | None => return,
|
|
|
|
Some(index) => index,
|
|
|
|
};
|
|
|
|
|
|
|
|
*index -= 1;
|
|
|
|
self.update_search();
|
|
|
|
}
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
#[inline]
|
|
|
|
fn advance_search_origin(&mut self, direction: Direction) {
|
2021-01-01 09:21:02 +00:00
|
|
|
// Use focused match as new search origin if available.
|
|
|
|
if let Some(focused_match) = &self.search_state.focused_match {
|
|
|
|
let new_origin = match direction {
|
2021-01-03 11:24:04 +00:00
|
|
|
Direction::Right => {
|
|
|
|
focused_match.end().add_absolute(self.terminal, Boundary::Wrap, 1)
|
|
|
|
},
|
|
|
|
Direction::Left => {
|
|
|
|
focused_match.start().sub_absolute(self.terminal, Boundary::Wrap, 1)
|
|
|
|
},
|
2020-07-15 21:27:32 +00:00
|
|
|
};
|
|
|
|
|
2021-01-01 09:21:02 +00:00
|
|
|
self.terminal.scroll_to_point(new_origin);
|
|
|
|
|
|
|
|
let origin_relative = self.terminal.grid().clamp_buffer_to_visible(new_origin);
|
2020-07-15 21:27:32 +00:00
|
|
|
self.search_state.origin = origin_relative;
|
|
|
|
self.search_state.display_offset_delta = 0;
|
|
|
|
}
|
2021-01-01 09:21:02 +00:00
|
|
|
|
|
|
|
// Search for the next match using the supplied direction.
|
|
|
|
let search_direction = mem::replace(&mut self.search_state.direction, direction);
|
|
|
|
self.goto_match(None);
|
|
|
|
self.search_state.direction = search_direction;
|
|
|
|
|
|
|
|
// If we found a match, we set the search origin right in front of it to make sure that
|
|
|
|
// after modifications to the regex the search is started without moving the focused match
|
|
|
|
// around.
|
|
|
|
let focused_match = match &self.search_state.focused_match {
|
|
|
|
Some(focused_match) => focused_match,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set new origin to the left/right of the match, depending on search direction.
|
|
|
|
let new_origin = match self.search_state.direction {
|
2021-01-03 11:24:04 +00:00
|
|
|
Direction::Right => *focused_match.start(),
|
|
|
|
Direction::Left => *focused_match.end(),
|
2021-01-01 09:21:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Store the search origin with display offset by checking how far we need to scroll to it.
|
|
|
|
let old_display_offset = self.terminal.grid().display_offset() as isize;
|
|
|
|
self.terminal.scroll_to_point(new_origin);
|
|
|
|
let new_display_offset = self.terminal.grid().display_offset() as isize;
|
|
|
|
self.search_state.display_offset_delta = new_display_offset - old_display_offset;
|
|
|
|
|
|
|
|
// Store origin and scroll back to the match.
|
|
|
|
let origin_relative = self.terminal.grid().clamp_buffer_to_visible(new_origin);
|
|
|
|
self.terminal.scroll_display(Scroll::Delta(-self.search_state.display_offset_delta));
|
|
|
|
self.search_state.origin = origin_relative;
|
2020-07-15 21:27:32 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// 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()
|
|
|
|
}
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
/// Handle keyboard typing start.
|
|
|
|
///
|
|
|
|
/// This will temporarily disable some features like terminal cursor blinking or the mouse
|
|
|
|
/// cursor.
|
|
|
|
///
|
|
|
|
/// All features are re-enabled again automatically.
|
|
|
|
#[inline]
|
|
|
|
fn on_typing_start(&mut self) {
|
|
|
|
// Disable cursor blinking.
|
|
|
|
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);
|
2021-01-24 21:45:36 +00:00
|
|
|
self.display.cursor_hidden = false;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-11-23 23:11:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hide mouse cursor.
|
|
|
|
if self.config.ui_config.mouse.hide_when_typing {
|
2021-01-24 21:45:36 +00:00
|
|
|
self.display.window.set_mouse_visible(false);
|
2020-11-23 23:11:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// Toggle the vi mode status.
|
2020-07-09 21:45:22 +00:00
|
|
|
#[inline]
|
2021-01-24 21:45:36 +00:00
|
|
|
fn toggle_vi_mode(&mut self) {
|
|
|
|
if !self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
self.clear_selection();
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
self.cancel_search();
|
|
|
|
self.terminal.toggle_vi_mode();
|
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> Option<&Message> {
|
|
|
|
self.message_buffer.message()
|
|
|
|
}
|
2019-11-03 19:02:26 +00:00
|
|
|
|
|
|
|
fn config(&self) -> &Config {
|
|
|
|
self.config
|
|
|
|
}
|
2020-02-07 13:44:11 +00:00
|
|
|
|
|
|
|
fn event_loop(&self) -> &EventLoopWindowTarget<Event> {
|
|
|
|
self.event_loop
|
|
|
|
}
|
2020-03-18 02:35:08 +00:00
|
|
|
|
|
|
|
fn urls(&self) -> &Urls {
|
2021-01-24 21:45:36 +00:00
|
|
|
&self.display.urls
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
fn clipboard_mut(&mut self) -> &mut Clipboard {
|
2020-06-06 21:33:20 +00:00
|
|
|
self.clipboard
|
|
|
|
}
|
|
|
|
|
2020-06-18 01:02:56 +00:00
|
|
|
fn scheduler_mut(&mut self) -> &mut Scheduler {
|
|
|
|
self.scheduler
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
|
2020-07-10 21:00:33 +00:00
|
|
|
fn update_search(&mut self) {
|
2020-12-20 04:27:08 +00:00
|
|
|
let regex = match self.search_state.regex() {
|
2020-07-10 21:00:33 +00:00
|
|
|
Some(regex) => regex,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Hide cursor while typing into the search bar.
|
|
|
|
if self.config.ui_config.mouse.hide_when_typing {
|
2021-01-24 21:45:36 +00:00
|
|
|
self.display.window.set_mouse_visible(false);
|
2020-07-10 21:00:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if regex.is_empty() {
|
|
|
|
// Stop search if there's nothing to search for.
|
|
|
|
self.search_reset_state();
|
2021-01-24 21:45:36 +00:00
|
|
|
self.search_state.dfas = None;
|
2020-07-10 21:00:33 +00:00
|
|
|
} else {
|
2021-01-24 21:45:36 +00:00
|
|
|
// Create search dfas for the new regex string.
|
|
|
|
self.search_state.dfas = RegexSearch::new(®ex).ok();
|
2020-07-10 21:00:33 +00:00
|
|
|
|
|
|
|
// Update search highlighting.
|
|
|
|
self.goto_match(MAX_SEARCH_WHILE_TYPING);
|
|
|
|
}
|
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-07-10 21:00:33 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// Reset terminal to the state before search was started.
|
|
|
|
fn search_reset_state(&mut self) {
|
2021-01-01 09:21:02 +00:00
|
|
|
// Unschedule pending timers.
|
|
|
|
self.scheduler.unschedule(TimerId::DelayedSearch);
|
|
|
|
|
|
|
|
// The viewport reset logic is only needed for vi mode, since without it our origin is
|
|
|
|
// always at the current display offset instead of at the vi cursor position which we need
|
|
|
|
// to recover to.
|
|
|
|
if !self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
// Reset display offset.
|
|
|
|
self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
|
|
|
|
self.search_state.display_offset_delta = 0;
|
|
|
|
|
2020-11-13 05:40:09 +00:00
|
|
|
// Clear focused match.
|
|
|
|
self.search_state.focused_match = None;
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
// Reset vi mode cursor.
|
2020-07-15 21:27:32 +00:00
|
|
|
let mut origin = self.search_state.origin;
|
|
|
|
origin.line = min(origin.line, self.terminal.screen_lines() - 1);
|
2021-01-24 21:45:36 +00:00
|
|
|
origin.column = min(origin.column, self.terminal.cols() - 1);
|
2020-07-15 21:27:32 +00:00
|
|
|
self.terminal.vi_mode_cursor.point = origin;
|
2021-01-24 21:45:36 +00:00
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Jump to the first regex match from the search origin.
|
|
|
|
fn goto_match(&mut self, mut limit: Option<usize>) {
|
2021-01-24 21:45:36 +00:00
|
|
|
let dfas = match &self.search_state.dfas {
|
|
|
|
Some(dfas) => dfas,
|
|
|
|
None => return,
|
|
|
|
};
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
// 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;
|
2021-01-24 21:45:36 +00:00
|
|
|
let origin = self.absolute_origin();
|
|
|
|
match self.terminal.search_next(dfas, origin, direction, Side::Left, limit) {
|
2020-07-09 21:45:22 +00:00
|
|
|
Some(regex_match) => {
|
|
|
|
let old_offset = self.terminal.grid().display_offset() as isize;
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
if self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
// Move vi cursor to the start of the match.
|
|
|
|
self.terminal.vi_goto_point(*regex_match.start());
|
|
|
|
} else {
|
|
|
|
// Select the match when vi mode is not active.
|
|
|
|
self.terminal.scroll_to_point(*regex_match.start());
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2020-11-13 05:40:09 +00:00
|
|
|
// Update the focused match.
|
|
|
|
self.search_state.focused_match = Some(regex_match);
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
// 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,
|
|
|
|
);
|
|
|
|
}
|
2020-11-13 05:40:09 +00:00
|
|
|
|
|
|
|
// Clear focused match.
|
|
|
|
self.search_state.focused_match = None;
|
2020-07-09 21:45:22 +00:00
|
|
|
},
|
|
|
|
}
|
2021-01-01 09:21:02 +00:00
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2020-07-15 21:27:32 +00:00
|
|
|
|
2020-11-13 05:40:09 +00:00
|
|
|
/// Cleanup the search state.
|
2020-09-27 22:36:08 +00:00
|
|
|
fn exit_search(&mut self) {
|
|
|
|
// 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.screen_lines() > self.terminal.vi_mode_cursor.point.line + 1
|
|
|
|
{
|
|
|
|
self.terminal.vi_mode_cursor.point.line += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.display_update_pending.dirty = true;
|
2020-12-20 04:27:08 +00:00
|
|
|
self.search_state.history_index = None;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-11-13 05:40:09 +00:00
|
|
|
|
|
|
|
// Clear focused match.
|
|
|
|
self.search_state.focused_match = None;
|
2020-09-27 22:36:08 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
/// Get the absolute position of the search origin.
|
|
|
|
///
|
|
|
|
/// This takes the relative motion of the viewport since the start of the search into account.
|
|
|
|
/// So while the absolute point of the origin might have changed since new content was printed,
|
|
|
|
/// this will still return the correct absolute position.
|
|
|
|
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);
|
2021-01-24 21:45:36 +00:00
|
|
|
relative_origin.column = min(relative_origin.column, self.terminal.cols() - 1);
|
2020-07-15 21:27:32 +00:00
|
|
|
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
|
|
|
|
}
|
2020-11-23 23:11:03 +00:00
|
|
|
|
|
|
|
/// Update the cursor blinking state.
|
|
|
|
fn update_cursor_blinking(&mut self) {
|
|
|
|
// Get config cursor style.
|
|
|
|
let mut cursor_style = self.config.cursor.style;
|
|
|
|
if self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
cursor_style = self.config.cursor.vi_mode_style.unwrap_or(cursor_style);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check terminal cursor style.
|
|
|
|
let terminal_blinking = self.terminal.cursor_style().blinking;
|
|
|
|
let blinking = cursor_style.blinking_override().unwrap_or(terminal_blinking);
|
|
|
|
|
|
|
|
// Update cursor blinking state.
|
|
|
|
self.scheduler.unschedule(TimerId::BlinkCursor);
|
|
|
|
if blinking && self.terminal.is_focused {
|
|
|
|
self.scheduler.schedule(
|
|
|
|
GlutinEvent::UserEvent(Event::BlinkCursor),
|
|
|
|
Duration::from_millis(self.config.cursor.blink_interval()),
|
|
|
|
true,
|
|
|
|
TimerId::BlinkCursor,
|
|
|
|
)
|
|
|
|
} else {
|
2021-01-24 21:45:36 +00:00
|
|
|
self.display.cursor_hidden = false;
|
2021-01-27 16:21:38 +00:00
|
|
|
*self.dirty = true;
|
2020-11-23 23:11:03 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 18:23:50 +00:00
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
2019-10-05 00:29:26 +00:00
|
|
|
pub enum ClickState {
|
|
|
|
None,
|
|
|
|
Click,
|
|
|
|
DoubleClick,
|
|
|
|
TripleClick,
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// State of the mouse.
|
2020-03-18 02:35:08 +00:00
|
|
|
#[derive(Debug)]
|
2019-10-05 00:29:26 +00:00
|
|
|
pub struct Mouse {
|
|
|
|
pub x: usize,
|
|
|
|
pub y: usize,
|
|
|
|
pub left_button_state: ElementState,
|
|
|
|
pub middle_button_state: ElementState,
|
|
|
|
pub right_button_state: ElementState,
|
|
|
|
pub last_click_timestamp: Instant,
|
2020-06-23 09:57:15 +00:00
|
|
|
pub last_click_button: MouseButton,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub click_state: ClickState,
|
2020-03-02 05:32:18 +00:00
|
|
|
pub scroll_px: f64,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub line: Line,
|
|
|
|
pub column: Column,
|
|
|
|
pub cell_side: Side,
|
|
|
|
pub lines_scrolled: f32,
|
|
|
|
pub block_url_launcher: bool,
|
2020-08-14 07:26:42 +00:00
|
|
|
pub inside_text_area: bool,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Mouse {
|
|
|
|
fn default() -> Mouse {
|
|
|
|
Mouse {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
last_click_timestamp: Instant::now(),
|
2020-06-23 09:57:15 +00:00
|
|
|
last_click_button: MouseButton::Left,
|
2019-10-05 00:29:26 +00:00
|
|
|
left_button_state: ElementState::Released,
|
|
|
|
middle_button_state: ElementState::Released,
|
|
|
|
right_button_state: ElementState::Released,
|
|
|
|
click_state: ClickState::None,
|
2020-03-02 05:32:18 +00:00
|
|
|
scroll_px: 0.,
|
2019-10-05 00:29:26 +00:00
|
|
|
line: Line(0),
|
|
|
|
column: Column(0),
|
|
|
|
cell_side: Side::Left,
|
2020-03-02 05:32:18 +00:00
|
|
|
lines_scrolled: 0.,
|
2019-10-05 00:29:26 +00:00
|
|
|
block_url_launcher: false,
|
2020-08-14 07:26:42 +00:00
|
|
|
inside_text_area: false,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// The event processor.
|
2019-10-05 00:29:26 +00:00
|
|
|
///
|
|
|
|
/// Stores some state from received events and dispatches actions when they are
|
|
|
|
/// triggered.
|
|
|
|
pub struct Processor<N> {
|
|
|
|
notifier: N,
|
|
|
|
mouse: Mouse,
|
|
|
|
received_count: usize,
|
|
|
|
suppress_chars: bool,
|
2019-11-11 21:05:24 +00:00
|
|
|
modifiers: ModifiersState,
|
2019-10-05 00:29:26 +00:00
|
|
|
config: Config,
|
|
|
|
message_buffer: MessageBuffer,
|
|
|
|
display: Display,
|
2019-11-03 19:02:26 +00:00
|
|
|
font_size: Size,
|
2020-06-18 01:02:56 +00:00
|
|
|
event_queue: Vec<GlutinEvent<'static, Event>>,
|
2020-07-09 21:45:22 +00:00
|
|
|
search_state: SearchState,
|
2020-08-22 20:55:27 +00:00
|
|
|
cli_options: CLIOptions,
|
2021-01-27 16:21:38 +00:00
|
|
|
dirty: bool,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2019-12-14 21:32:24 +00:00
|
|
|
impl<N: Notify + OnResize> Processor<N> {
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Create a new event processor.
|
2019-10-05 00:29:26 +00:00
|
|
|
///
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Takes a writer which is expected to be hooked up to the write end of a PTY.
|
2019-10-05 00:29:26 +00:00
|
|
|
pub fn new(
|
|
|
|
notifier: N,
|
|
|
|
message_buffer: MessageBuffer,
|
|
|
|
config: Config,
|
|
|
|
display: Display,
|
2020-08-22 20:55:27 +00:00
|
|
|
cli_options: CLIOptions,
|
2019-10-05 00:29:26 +00:00
|
|
|
) -> Processor<N> {
|
|
|
|
Processor {
|
|
|
|
notifier,
|
|
|
|
mouse: Default::default(),
|
|
|
|
received_count: 0,
|
|
|
|
suppress_chars: false,
|
|
|
|
modifiers: Default::default(),
|
2020-12-21 02:44:38 +00:00
|
|
|
font_size: config.ui_config.font.size(),
|
2019-10-05 00:29:26 +00:00
|
|
|
config,
|
|
|
|
message_buffer,
|
|
|
|
display,
|
2020-05-03 23:29:11 +00:00
|
|
|
event_queue: Vec::new(),
|
2020-07-09 21:45:22 +00:00
|
|
|
search_state: SearchState::new(),
|
2020-08-22 20:55:27 +00:00
|
|
|
cli_options,
|
2021-01-27 16:21:38 +00:00
|
|
|
dirty: false,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
/// Return `true` if `event_queue` is empty, `false` otherwise.
|
|
|
|
#[inline]
|
2020-10-12 09:22:36 +00:00
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
2020-05-03 23:29:11 +00:00
|
|
|
fn event_queue_empty(&mut self) -> bool {
|
|
|
|
let wayland_event_queue = match self.display.wayland_event_queue.as_mut() {
|
|
|
|
Some(wayland_event_queue) => wayland_event_queue,
|
|
|
|
// Since frame callbacks do not exist on X11, just check for event queue.
|
|
|
|
None => return self.event_queue.is_empty(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Check for pending frame callbacks on Wayland.
|
|
|
|
let events_dispatched = wayland_event_queue
|
|
|
|
.dispatch_pending(&mut (), |_, _, _| {})
|
|
|
|
.expect("failed to dispatch event queue");
|
|
|
|
|
|
|
|
self.event_queue.is_empty() && events_dispatched == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return `true` if `event_queue` is empty, `false` otherwise.
|
|
|
|
#[inline]
|
2020-10-12 22:13:38 +00:00
|
|
|
#[cfg(any(not(feature = "wayland"), target_os = "macos", windows))]
|
2020-05-03 23:29:11 +00:00
|
|
|
fn event_queue_empty(&mut self) -> bool {
|
|
|
|
self.event_queue.is_empty()
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Run the event loop.
|
|
|
|
pub fn run<T>(&mut self, terminal: Arc<FairMutex<Term<T>>>, mut event_loop: EventLoop<Event>)
|
|
|
|
where
|
|
|
|
T: EventListener,
|
|
|
|
{
|
2020-06-18 01:02:56 +00:00
|
|
|
let mut scheduler = Scheduler::new();
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
// Start the initial cursor blinking timer.
|
|
|
|
if self.config.cursor.style().blinking {
|
|
|
|
let event: Event = TerminalEvent::CursorBlinkingChange(true).into();
|
|
|
|
self.event_queue.push(event.into());
|
|
|
|
}
|
|
|
|
|
2021-01-29 22:41:15 +00:00
|
|
|
// NOTE: Since this takes a pointer to the winit event loop, it MUST be dropped first.
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
let mut clipboard = unsafe { Clipboard::new(event_loop.wayland_display()) };
|
|
|
|
#[cfg(any(not(feature = "wayland"), target_os = "macos", windows))]
|
|
|
|
let mut clipboard = Clipboard::new();
|
|
|
|
|
2020-02-07 13:44:11 +00:00
|
|
|
event_loop.run_return(|event, event_loop, control_flow| {
|
2020-07-11 17:03:09 +00:00
|
|
|
if self.config.ui_config.debug.print_events {
|
2019-10-05 00:29:26 +00:00
|
|
|
info!("glutin event: {:?}", event);
|
|
|
|
}
|
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
// Ignore all events we do not care about.
|
2020-01-10 01:51:37 +00:00
|
|
|
if Self::skip_event(&event) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match event {
|
2020-05-03 23:29:11 +00:00
|
|
|
// Check for shutdown.
|
2020-06-18 01:02:56 +00:00
|
|
|
GlutinEvent::UserEvent(Event::TerminalEvent(TerminalEvent::Exit)) => {
|
2019-10-05 00:29:26 +00:00
|
|
|
*control_flow = ControlFlow::Exit;
|
|
|
|
return;
|
|
|
|
},
|
2020-05-03 23:29:11 +00:00
|
|
|
// Process events.
|
2020-01-05 01:00:50 +00:00
|
|
|
GlutinEvent::RedrawEventsCleared => {
|
2020-06-18 01:02:56 +00:00
|
|
|
*control_flow = match scheduler.update(&mut self.event_queue) {
|
|
|
|
Some(instant) => ControlFlow::WaitUntil(instant),
|
|
|
|
None => ControlFlow::Wait,
|
|
|
|
};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
if self.event_queue_empty() {
|
2019-10-05 00:29:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
},
|
2020-05-03 23:29:11 +00:00
|
|
|
// Remap DPR change event to remove lifetime.
|
2020-01-10 01:51:37 +00:00
|
|
|
GlutinEvent::WindowEvent {
|
|
|
|
event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size },
|
|
|
|
..
|
|
|
|
} => {
|
2019-10-05 00:29:26 +00:00
|
|
|
*control_flow = ControlFlow::Poll;
|
2020-01-10 01:51:37 +00:00
|
|
|
let size = (new_inner_size.width, new_inner_size.height);
|
2021-02-13 18:15:57 +00:00
|
|
|
self.event_queue.push(Event::DprChanged(scale_factor, size).into());
|
2020-01-10 01:51:37 +00:00
|
|
|
return;
|
|
|
|
},
|
|
|
|
// Transmute to extend lifetime, which exists only for `ScaleFactorChanged` event.
|
|
|
|
// Since we remap that event to remove the lifetime, this is safe.
|
|
|
|
event => unsafe {
|
|
|
|
*control_flow = ControlFlow::Poll;
|
2020-05-03 23:29:11 +00:00
|
|
|
self.event_queue.push(mem::transmute(event));
|
2019-10-05 00:29:26 +00:00
|
|
|
return;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut terminal = terminal.lock();
|
|
|
|
|
2019-11-03 19:02:26 +00:00
|
|
|
let mut display_update_pending = DisplayUpdate::default();
|
2020-12-20 04:27:08 +00:00
|
|
|
let old_is_searching = self.search_state.history_index.is_some();
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
let context = ActionContext {
|
|
|
|
terminal: &mut terminal,
|
|
|
|
notifier: &mut self.notifier,
|
|
|
|
mouse: &mut self.mouse,
|
2021-01-29 22:41:15 +00:00
|
|
|
clipboard: &mut clipboard,
|
2019-10-05 00:29:26 +00:00
|
|
|
received_count: &mut self.received_count,
|
|
|
|
suppress_chars: &mut self.suppress_chars,
|
|
|
|
modifiers: &mut self.modifiers,
|
|
|
|
message_buffer: &mut self.message_buffer,
|
2019-11-03 19:02:26 +00:00
|
|
|
display_update_pending: &mut display_update_pending,
|
2021-01-24 21:45:36 +00:00
|
|
|
display: &mut self.display,
|
2019-11-03 19:02:26 +00:00
|
|
|
font_size: &mut self.font_size,
|
|
|
|
config: &mut self.config,
|
2020-06-18 01:02:56 +00:00
|
|
|
scheduler: &mut scheduler,
|
2020-07-09 21:45:22 +00:00
|
|
|
search_state: &mut self.search_state,
|
2020-08-22 20:55:27 +00:00
|
|
|
cli_options: &self.cli_options,
|
2021-01-27 16:21:38 +00:00
|
|
|
dirty: &mut self.dirty,
|
2020-02-07 13:44:11 +00:00
|
|
|
event_loop,
|
2019-10-05 00:29:26 +00:00
|
|
|
};
|
2021-01-24 21:45:36 +00:00
|
|
|
let mut processor = input::Processor::new(context);
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
for event in self.event_queue.drain(..) {
|
2019-10-05 00:29:26 +00:00
|
|
|
Processor::handle_event(event, &mut processor);
|
|
|
|
}
|
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
// Process DisplayUpdate events.
|
2020-07-09 21:45:22 +00:00
|
|
|
if display_update_pending.dirty {
|
|
|
|
self.submit_display_update(&mut terminal, old_is_searching, display_update_pending);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-07-28 10:00:55 +00:00
|
|
|
// Skip rendering on Wayland until we get frame event from compositor.
|
2020-05-03 23:29:11 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
2020-10-12 09:22:36 +00:00
|
|
|
if !self.display.is_x11 && !self.display.window.should_draw.load(Ordering::Relaxed) {
|
2020-07-28 10:00:55 +00:00
|
|
|
return;
|
2020-05-03 23:29:11 +00:00
|
|
|
}
|
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
if self.dirty {
|
|
|
|
self.dirty = false;
|
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
// Request immediate re-draw if visual bell animation is not finished yet.
|
2021-01-24 21:45:36 +00:00
|
|
|
if !self.display.visual_bell.completed() {
|
2020-06-18 01:02:56 +00:00
|
|
|
let event: Event = TerminalEvent::Wakeup.into();
|
|
|
|
self.event_queue.push(event.into());
|
2020-10-30 06:22:13 +00:00
|
|
|
|
|
|
|
*control_flow = ControlFlow::Poll;
|
2019-10-24 22:26:33 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-05-03 23:29:11 +00:00
|
|
|
// Redraw screen.
|
2019-11-03 20:59:28 +00:00
|
|
|
self.display.draw(
|
|
|
|
terminal,
|
|
|
|
&self.message_buffer,
|
|
|
|
&self.config,
|
|
|
|
&self.mouse,
|
2019-11-11 21:05:24 +00:00
|
|
|
self.modifiers,
|
2020-07-17 01:26:53 +00:00
|
|
|
&self.search_state,
|
2019-11-03 20:59:28 +00:00
|
|
|
);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Write ref tests to disk.
|
2020-07-11 17:03:09 +00:00
|
|
|
if self.config.ui_config.debug.ref_test {
|
2020-07-09 21:45:22 +00:00
|
|
|
self.write_ref_test_results(&terminal.lock());
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Handle events from glutin.
|
2019-10-05 00:29:26 +00:00
|
|
|
///
|
2019-10-24 22:26:33 +00:00
|
|
|
/// Doesn't take self mutably due to borrow checking.
|
2019-10-05 00:29:26 +00:00
|
|
|
fn handle_event<T>(
|
2020-10-29 04:17:33 +00:00
|
|
|
event: GlutinEvent<'_, Event>,
|
2021-01-24 21:45:36 +00:00
|
|
|
processor: &mut input::Processor<T, ActionContext<'_, N, T>>,
|
2019-10-05 00:29:26 +00:00
|
|
|
) where
|
|
|
|
T: EventListener,
|
|
|
|
{
|
|
|
|
match event {
|
|
|
|
GlutinEvent::UserEvent(event) => match event {
|
2021-02-13 18:15:57 +00:00
|
|
|
Event::DprChanged(scale_factor, (width, height)) => {
|
2020-01-10 01:51:37 +00:00
|
|
|
let display_update_pending = &mut processor.ctx.display_update_pending;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Push current font to update its DPR.
|
2020-07-11 17:03:09 +00:00
|
|
|
let font = processor.ctx.config.ui_config.font.clone();
|
2020-07-09 21:45:22 +00:00
|
|
|
display_update_pending.set_font(font.with_size(*processor.ctx.font_size));
|
2020-01-10 01:51:37 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Resize to event's dimensions, since no resize event is emitted on Wayland.
|
2020-07-09 21:45:22 +00:00
|
|
|
display_update_pending.set_dimensions(PhysicalSize::new(width, height));
|
2020-01-10 01:51:37 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().dpr = scale_factor;
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2020-01-10 01:51:37 +00:00
|
|
|
},
|
2019-10-05 00:29:26 +00:00
|
|
|
Event::Message(message) => {
|
|
|
|
processor.ctx.message_buffer.push(message);
|
2020-07-09 21:45:22 +00:00
|
|
|
processor.ctx.display_update_pending.dirty = true;
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-07-09 21:45:22 +00:00
|
|
|
Event::SearchNext => processor.ctx.goto_match(None),
|
2020-06-18 01:02:56 +00:00
|
|
|
Event::ConfigReload(path) => Self::reload_config(&path, processor),
|
|
|
|
Event::Scroll(scroll) => processor.ctx.scroll(scroll),
|
2020-11-23 23:11:03 +00:00
|
|
|
Event::BlinkCursor => {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.display.cursor_hidden ^= true;
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2020-11-23 23:11:03 +00:00
|
|
|
},
|
2020-06-18 01:02:56 +00:00
|
|
|
Event::TerminalEvent(event) => match event {
|
2020-07-11 17:03:09 +00:00
|
|
|
TerminalEvent::Title(title) => {
|
|
|
|
let ui_config = &processor.ctx.config.ui_config;
|
2020-12-21 02:44:38 +00:00
|
|
|
if ui_config.window.dynamic_title {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_title(&title);
|
2020-07-11 17:03:09 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
TerminalEvent::ResetTitle => {
|
|
|
|
let ui_config = &processor.ctx.config.ui_config;
|
2020-12-21 02:44:38 +00:00
|
|
|
if ui_config.window.dynamic_title {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.display.window.set_title(&ui_config.window.title);
|
2020-07-11 17:03:09 +00:00
|
|
|
}
|
|
|
|
},
|
2021-01-27 16:21:38 +00:00
|
|
|
TerminalEvent::Wakeup => *processor.ctx.dirty = true,
|
2020-07-10 19:32:44 +00:00
|
|
|
TerminalEvent::Bell => {
|
2021-01-24 21:45:36 +00:00
|
|
|
// Set window urgency.
|
2020-10-10 21:24:40 +00:00
|
|
|
if processor.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) {
|
2021-01-24 21:45:36 +00:00
|
|
|
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());
|
2020-10-10 21:24:40 +00:00
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
},
|
|
|
|
TerminalEvent::ClipboardStore(clipboard_type, content) => {
|
|
|
|
processor.ctx.clipboard.store(clipboard_type, content);
|
|
|
|
},
|
|
|
|
TerminalEvent::ClipboardLoad(clipboard_type, format) => {
|
|
|
|
let text = format(processor.ctx.clipboard.load(clipboard_type).as_str());
|
|
|
|
processor.ctx.write_to_pty(text.into_bytes());
|
|
|
|
},
|
2021-01-24 21:45:36 +00:00
|
|
|
TerminalEvent::ColorRequest(index, format) => {
|
|
|
|
let text = format(processor.ctx.display.colors[index]);
|
|
|
|
processor.ctx.write_to_pty(text.into_bytes());
|
|
|
|
},
|
2020-06-18 01:02:56 +00:00
|
|
|
TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(),
|
|
|
|
TerminalEvent::Exit => (),
|
2020-11-23 23:11:03 +00:00
|
|
|
TerminalEvent::CursorBlinkingChange(_) => {
|
|
|
|
processor.ctx.update_cursor_blinking();
|
|
|
|
},
|
2020-06-06 21:33:20 +00:00
|
|
|
},
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2021-01-27 16:21:38 +00:00
|
|
|
GlutinEvent::RedrawRequested(_) => *processor.ctx.dirty = true,
|
2019-10-05 00:29:26 +00:00
|
|
|
GlutinEvent::WindowEvent { event, window_id, .. } => {
|
|
|
|
match event {
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::CloseRequested => processor.ctx.terminal.exit(),
|
|
|
|
WindowEvent::Resized(size) => {
|
2020-07-28 10:00:55 +00:00
|
|
|
// Minimizing the window sends a Resize event with zero width and
|
|
|
|
// height. But there's no need to ever actually resize to this.
|
2021-01-01 05:07:39 +00:00
|
|
|
// ConPTY has issues when resizing down to zero size and back.
|
2019-12-09 17:26:31 +00:00
|
|
|
#[cfg(windows)]
|
2020-07-28 10:00:55 +00:00
|
|
|
if size.width == 0 && size.height == 0 {
|
|
|
|
return;
|
2019-12-09 17:26:31 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
processor.ctx.display_update_pending.set_dimensions(size);
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => {
|
2019-11-11 21:05:24 +00:00
|
|
|
processor.key_input(input);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2021-01-24 21:45:36 +00:00
|
|
|
WindowEvent::ModifiersChanged(modifiers) => {
|
|
|
|
processor.modifiers_input(modifiers)
|
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::ReceivedCharacter(c) => processor.received_char(c),
|
|
|
|
WindowEvent::MouseInput { state, button, .. } => {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_mouse_visible(true);
|
2020-02-27 23:42:39 +00:00
|
|
|
processor.mouse_input(state, button);
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2020-03-11 22:31:59 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::CursorMoved { position, .. } => {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_mouse_visible(true);
|
2020-06-18 01:02:56 +00:00
|
|
|
processor.mouse_moved(position);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_mouse_visible(true);
|
2020-01-05 01:00:50 +00:00
|
|
|
processor.mouse_wheel_input(delta, phase);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::Focused(is_focused) => {
|
2021-01-24 21:45:36 +00:00
|
|
|
if window_id == processor.ctx.window().window_id() {
|
2019-10-05 00:29:26 +00:00
|
|
|
processor.ctx.terminal.is_focused = is_focused;
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
if is_focused {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_urgent(false);
|
2019-10-05 00:29:26 +00:00
|
|
|
} else {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_mouse_visible(true);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
processor.ctx.update_cursor_blinking();
|
2019-10-05 00:29:26 +00:00
|
|
|
processor.on_focus_change(is_focused);
|
|
|
|
}
|
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::DroppedFile(path) => {
|
2019-10-05 00:29:26 +00:00
|
|
|
let path: String = path.to_string_lossy().into();
|
2020-06-03 00:01:26 +00:00
|
|
|
processor.ctx.write_to_pty((path + " ").into_bytes());
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::CursorLeft { .. } => {
|
2020-08-14 07:26:42 +00:00
|
|
|
processor.ctx.mouse.inside_text_area = false;
|
2019-11-03 20:59:28 +00:00
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
if processor.ctx.highlighted_url().is_some() {
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2019-11-03 20:59:28 +00:00
|
|
|
}
|
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::KeyboardInput { is_synthetic: true, .. }
|
|
|
|
| WindowEvent::TouchpadPressure { .. }
|
|
|
|
| WindowEvent::ScaleFactorChanged { .. }
|
|
|
|
| WindowEvent::CursorEntered { .. }
|
|
|
|
| WindowEvent::AxisMotion { .. }
|
|
|
|
| WindowEvent::HoveredFileCancelled
|
|
|
|
| WindowEvent::Destroyed
|
|
|
|
| WindowEvent::ThemeChanged(_)
|
|
|
|
| WindowEvent::HoveredFile(_)
|
|
|
|
| WindowEvent::Touch(_)
|
|
|
|
| WindowEvent::Moved(_) => (),
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
},
|
2019-11-11 21:05:24 +00:00
|
|
|
GlutinEvent::Suspended { .. }
|
2019-10-05 00:29:26 +00:00
|
|
|
| GlutinEvent::NewEvents { .. }
|
2020-03-11 22:31:59 +00:00
|
|
|
| GlutinEvent::DeviceEvent { .. }
|
2020-01-05 01:00:50 +00:00
|
|
|
| GlutinEvent::MainEventsCleared
|
|
|
|
| GlutinEvent::RedrawEventsCleared
|
2019-10-05 00:29:26 +00:00
|
|
|
| GlutinEvent::Resumed
|
|
|
|
| GlutinEvent::LoopDestroyed => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Check if an event is irrelevant and can be skipped.
|
2020-10-29 04:17:33 +00:00
|
|
|
fn skip_event(event: &GlutinEvent<'_, Event>) -> bool {
|
2019-10-05 00:29:26 +00:00
|
|
|
match event {
|
2020-08-06 01:09:46 +00:00
|
|
|
GlutinEvent::WindowEvent { event, .. } => matches!(
|
|
|
|
event,
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::KeyboardInput { is_synthetic: true, .. }
|
2020-08-06 01:09:46 +00:00
|
|
|
| WindowEvent::TouchpadPressure { .. }
|
|
|
|
| WindowEvent::CursorEntered { .. }
|
|
|
|
| WindowEvent::AxisMotion { .. }
|
|
|
|
| WindowEvent::HoveredFileCancelled
|
|
|
|
| WindowEvent::Destroyed
|
|
|
|
| WindowEvent::HoveredFile(_)
|
|
|
|
| WindowEvent::Touch(_)
|
|
|
|
| WindowEvent::Moved(_)
|
|
|
|
),
|
2019-11-11 21:05:24 +00:00
|
|
|
GlutinEvent::Suspended { .. }
|
2019-10-05 00:29:26 +00:00
|
|
|
| GlutinEvent::NewEvents { .. }
|
2020-01-05 01:00:50 +00:00
|
|
|
| GlutinEvent::MainEventsCleared
|
2019-10-05 00:29:26 +00:00
|
|
|
| GlutinEvent::LoopDestroyed => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// Reload the configuration files from disk.
|
|
|
|
fn reload_config<T>(path: &Path, processor: &mut input::Processor<T, ActionContext<'_, N, T>>)
|
|
|
|
where
|
2020-04-09 01:02:10 +00:00
|
|
|
T: EventListener,
|
|
|
|
{
|
2020-07-09 21:45:22 +00:00
|
|
|
if !processor.ctx.message_buffer.is_empty() {
|
|
|
|
processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG);
|
|
|
|
processor.ctx.display_update_pending.dirty = true;
|
|
|
|
}
|
2020-04-09 01:02:10 +00:00
|
|
|
|
2020-08-22 20:55:27 +00:00
|
|
|
let config = match config::reload(&path, &processor.ctx.cli_options) {
|
2020-04-09 01:02:10 +00:00
|
|
|
Ok(config) => config,
|
|
|
|
Err(_) => return,
|
|
|
|
};
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.display.update_config(&config);
|
2020-04-09 01:02:10 +00:00
|
|
|
processor.ctx.terminal.update_config(&config);
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
// Reload cursor if its thickness has changed.
|
2020-04-15 03:50:34 +00:00
|
|
|
if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs()
|
2020-12-21 02:44:38 +00:00
|
|
|
> f32::EPSILON
|
2020-04-15 03:50:34 +00:00
|
|
|
{
|
2020-07-09 21:45:22 +00:00
|
|
|
processor.ctx.display_update_pending.set_cursor_dirty();
|
2020-04-15 03:50:34 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:03:09 +00:00
|
|
|
if processor.ctx.config.ui_config.font != config.ui_config.font {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Do not update font size if it has been changed at runtime.
|
2020-12-21 02:44:38 +00:00
|
|
|
if *processor.ctx.font_size == processor.ctx.config.ui_config.font.size() {
|
|
|
|
*processor.ctx.font_size = config.ui_config.font.size();
|
2020-04-09 01:02:10 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 17:03:09 +00:00
|
|
|
let font = config.ui_config.font.clone().with_size(*processor.ctx.font_size);
|
2020-07-09 21:45:22 +00:00
|
|
|
processor.ctx.display_update_pending.set_font(font);
|
2020-04-09 01:02:10 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 19:59:32 +00:00
|
|
|
// Update display if padding options were changed.
|
|
|
|
let window_config = &processor.ctx.config.ui_config.window;
|
2020-09-27 22:36:08 +00:00
|
|
|
if window_config.padding(1.) != config.ui_config.window.padding(1.)
|
2020-07-15 19:59:32 +00:00
|
|
|
|| window_config.dynamic_padding != config.ui_config.window.dynamic_padding
|
|
|
|
{
|
|
|
|
processor.ctx.display_update_pending.dirty = true;
|
|
|
|
}
|
|
|
|
|
2020-07-11 17:03:09 +00:00
|
|
|
// Live title reload.
|
2020-12-21 02:44:38 +00:00
|
|
|
if !config.ui_config.window.dynamic_title
|
2020-07-11 17:03:09 +00:00
|
|
|
|| processor.ctx.config.ui_config.window.title != config.ui_config.window.title
|
|
|
|
{
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_title(&config.ui_config.window.title);
|
2020-07-11 17:03:09 +00:00
|
|
|
}
|
|
|
|
|
2020-10-12 09:22:36 +00:00
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
2020-07-28 10:00:55 +00:00
|
|
|
if processor.ctx.event_loop.is_wayland() {
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_wayland_theme(&config.ui_config.colors);
|
2020-04-09 01:02:10 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 21:14:13 +00:00
|
|
|
// Set subpixel anti-aliasing.
|
|
|
|
#[cfg(target_os = "macos")]
|
2020-12-21 02:44:38 +00:00
|
|
|
crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes);
|
2020-05-17 21:14:13 +00:00
|
|
|
|
2020-12-22 04:25:43 +00:00
|
|
|
// Disable shadows for transparent windows on macOS.
|
|
|
|
#[cfg(target_os = "macos")]
|
2021-01-24 21:45:36 +00:00
|
|
|
processor.ctx.window_mut().set_has_shadow(config.ui_config.background_opacity() >= 1.0);
|
2020-12-22 04:25:43 +00:00
|
|
|
|
2020-04-09 01:02:10 +00:00
|
|
|
*processor.ctx.config = config;
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
// Update cursor blinking.
|
|
|
|
processor.ctx.update_cursor_blinking();
|
|
|
|
|
2021-01-27 16:21:38 +00:00
|
|
|
*processor.ctx.dirty = true;
|
2020-04-09 01:02:10 +00:00
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// 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;
|
2020-07-15 21:27:32 +00:00
|
|
|
let origin_at_bottom = if terminal.mode().contains(TermMode::VI) {
|
|
|
|
terminal.vi_mode_cursor.point.line == num_lines - 1
|
|
|
|
} else {
|
|
|
|
self.search_state.direction == Direction::Left
|
|
|
|
};
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
self.display.handle_update(
|
|
|
|
terminal,
|
|
|
|
&mut self.notifier,
|
|
|
|
&self.message_buffer,
|
2020-12-20 04:27:08 +00:00
|
|
|
self.search_state.history_index.is_some(),
|
2020-07-09 21:45:22 +00:00
|
|
|
&self.config,
|
|
|
|
display_update_pending,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Scroll to make sure search origin is visible and content moves as little as possible.
|
2020-12-20 04:27:08 +00:00
|
|
|
if !old_is_searching && self.search_state.history_index.is_some() {
|
2020-07-09 21:45:22 +00:00
|
|
|
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));
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// Write the ref test results to the disk.
|
|
|
|
fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Dump grid state.
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut grid = terminal.grid().clone();
|
2020-11-05 04:45:14 +00:00
|
|
|
grid.initialize_all();
|
2019-10-05 00:29:26 +00:00
|
|
|
grid.truncate();
|
|
|
|
|
|
|
|
let serialized_grid = json::to_string(&grid).expect("serialize grid");
|
|
|
|
|
|
|
|
let serialized_size = json::to_string(&self.display.size_info).expect("serialize size");
|
|
|
|
|
|
|
|
let serialized_config = format!("{{\"history_size\":{}}}", grid.history_size());
|
|
|
|
|
|
|
|
File::create("./grid.json")
|
|
|
|
.and_then(|mut f| f.write_all(serialized_grid.as_bytes()))
|
|
|
|
.expect("write grid.json");
|
|
|
|
|
|
|
|
File::create("./size.json")
|
|
|
|
.and_then(|mut f| f.write_all(serialized_size.as_bytes()))
|
|
|
|
.expect("write size.json");
|
|
|
|
|
|
|
|
File::create("./config.json")
|
|
|
|
.and_then(|mut f| f.write_all(serialized_config.as_bytes()))
|
|
|
|
.expect("write config.json");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct EventProxy(EventLoopProxy<Event>);
|
|
|
|
|
|
|
|
impl EventProxy {
|
|
|
|
pub fn new(proxy: EventLoopProxy<Event>) -> Self {
|
|
|
|
EventProxy(proxy)
|
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
/// Send an event to the event loop.
|
|
|
|
pub fn send_event(&self, event: Event) {
|
|
|
|
let _ = self.0.send_event(event);
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EventListener for EventProxy {
|
2020-06-18 01:02:56 +00:00
|
|
|
fn send_event(&self, event: TerminalEvent) {
|
|
|
|
let _ = self.0.send_event(Event::TerminalEvent(event));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|