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};
|
2023-02-13 22:35:18 +00:00
|
|
|
use std::collections::{HashMap, HashSet, VecDeque};
|
2021-10-23 07:16:47 +00:00
|
|
|
use std::error::Error;
|
2021-12-18 22:18:42 +00:00
|
|
|
use std::ffi::OsStr;
|
2020-07-10 19:32:44 +00:00
|
|
|
use std::fmt::Debug;
|
2021-12-18 22:18:42 +00:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
use std::os::unix::io::RawFd;
|
2021-10-23 07:16:47 +00:00
|
|
|
use std::path::PathBuf;
|
2022-08-31 22:48:38 +00:00
|
|
|
use std::rc::Rc;
|
2022-12-30 16:25:04 +00:00
|
|
|
use std::sync::atomic::Ordering;
|
2020-07-09 21:45:22 +00:00
|
|
|
use std::time::{Duration, Instant};
|
2021-10-11 00:54:18 +00:00
|
|
|
use std::{env, f32, mem};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2022-11-03 16:37:54 +00:00
|
|
|
use log::{debug, error, info, warn};
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
use wayland_client::{Display as WaylandDisplay, EventQueue};
|
|
|
|
use winit::dpi::PhysicalSize;
|
|
|
|
use winit::event::{
|
2023-02-13 22:35:18 +00:00
|
|
|
ElementState, Event as WinitEvent, Ime, ModifiersState, MouseButton, StartCause,
|
|
|
|
Touch as TouchEvent, WindowEvent,
|
2022-08-10 12:48:46 +00:00
|
|
|
};
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::event_loop::{
|
2022-08-10 12:48:46 +00:00
|
|
|
ControlFlow, DeviceEventFilter, EventLoop, EventLoopProxy, EventLoopWindowTarget,
|
|
|
|
};
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::platform::run_return::EventLoopExtRunReturn;
|
2021-10-23 07:16:47 +00:00
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
2023-02-02 08:30:23 +00:00
|
|
|
use winit::platform::wayland::EventLoopWindowTargetExtWayland;
|
2022-11-03 16:37:54 +00:00
|
|
|
use winit::window::WindowId;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
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;
|
2021-10-23 07:16:47 +00:00
|
|
|
use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify};
|
|
|
|
use alacritty_terminal::event_loop::Notifier;
|
2020-07-09 21:45:22 +00:00
|
|
|
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};
|
2021-01-24 21:45:36 +00:00
|
|
|
use alacritty_terminal::term::search::{Match, RegexSearch};
|
2022-05-26 21:30:33 +00:00
|
|
|
use alacritty_terminal::term::{self, ClipboardType, Term, TermMode};
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2022-08-31 22:48:38 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
use crate::cli::IpcConfig;
|
2022-01-03 18:55:22 +00:00
|
|
|
use crate::cli::{Options as CliOptions, WindowOptions};
|
2020-06-06 21:33:20 +00:00
|
|
|
use crate::clipboard::Clipboard;
|
2021-04-03 23:52:44 +00:00
|
|
|
use crate::config::ui_config::{HintAction, HintInternalAction};
|
2021-11-22 18:34:09 +00:00
|
|
|
use crate::config::{self, UiConfig};
|
2021-12-18 17:27:10 +00:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
use crate::daemon::foreground_process_path;
|
2021-12-18 22:18:42 +00:00
|
|
|
use crate::daemon::spawn_daemon;
|
2021-04-13 03:24:42 +00:00
|
|
|
use crate::display::hint::HintMatch;
|
2021-01-24 21:45:36 +00:00
|
|
|
use crate::display::window::Window;
|
2022-08-29 13:29:13 +00:00
|
|
|
use crate::display::{Display, Preedit, SizeInfo};
|
2019-11-11 21:05:24 +00:00
|
|
|
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
|
2020-07-11 17:03:09 +00:00
|
|
|
use crate::message_bar::{Message, MessageBuffer};
|
2021-10-23 07:16:47 +00:00
|
|
|
use crate::scheduler::{Scheduler, TimerId, Topic};
|
|
|
|
use crate::window_context::WindowContext;
|
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.
|
2021-03-01 19:50:39 +00:00
|
|
|
const MAX_SEARCH_HISTORY_SIZE: usize = 255;
|
2020-12-20 04:27:08 +00:00
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
/// Touch zoom speed.
|
|
|
|
const TOUCH_ZOOM_FACTOR: f32 = 0.01;
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
/// Alacritty events.
|
2020-06-18 01:02:56 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2021-10-23 07:16:47 +00:00
|
|
|
pub struct Event {
|
|
|
|
/// Limit event to a specific window.
|
|
|
|
window_id: Option<WindowId>,
|
|
|
|
|
|
|
|
/// Event payload.
|
|
|
|
payload: EventType,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Event {
|
|
|
|
pub fn new<I: Into<Option<WindowId>>>(payload: EventType, window_id: I) -> Self {
|
|
|
|
Self { window_id: window_id.into(), payload }
|
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 16:37:54 +00:00
|
|
|
impl From<Event> for WinitEvent<'_, Event> {
|
2020-06-18 01:02:56 +00:00
|
|
|
fn from(event: Event) -> Self {
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(event)
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
/// Alacritty events.
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum EventType {
|
2022-02-17 22:27:10 +00:00
|
|
|
ScaleFactorChanged(f64, (u32, u32)),
|
2021-10-23 07:16:47 +00:00
|
|
|
Terminal(TerminalEvent),
|
|
|
|
ConfigReload(PathBuf),
|
|
|
|
Message(Message),
|
|
|
|
Scroll(Scroll),
|
2022-01-03 18:55:22 +00:00
|
|
|
CreateWindow(WindowOptions),
|
2022-08-31 22:48:38 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
IpcConfig(IpcConfig),
|
2021-10-23 07:16:47 +00:00
|
|
|
BlinkCursor,
|
2022-07-01 08:40:27 +00:00
|
|
|
BlinkCursorTimeout,
|
2021-10-23 07:16:47 +00:00
|
|
|
SearchNext,
|
2022-12-30 16:25:04 +00:00
|
|
|
Frame,
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TerminalEvent> for EventType {
|
2020-06-18 01:02:56 +00:00
|
|
|
fn from(event: TerminalEvent) -> Self {
|
2021-10-23 07:16:47 +00:00
|
|
|
Self::Terminal(event)
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-09 21:45:22 +00:00
|
|
|
/// Regex search state.
|
|
|
|
pub struct SearchState {
|
|
|
|
/// Search direction.
|
2021-10-23 07:16:47 +00:00
|
|
|
pub direction: Direction,
|
|
|
|
|
|
|
|
/// Current position in the search history.
|
|
|
|
pub history_index: Option<usize>,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
/// Change in display offset since the beginning of the search.
|
2021-03-30 23:25:38 +00:00
|
|
|
display_offset_delta: i32,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
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.
|
2021-03-30 23:25:38 +00:00
|
|
|
focused_match: Option<Match>,
|
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>,
|
|
|
|
|
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 {
|
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.
|
2021-03-30 23:25:38 +00:00
|
|
|
pub fn focused_match(&self) -> Option<&Match> {
|
2020-11-13 05:40:09 +00:00
|
|
|
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,
|
2023-02-13 22:35:18 +00:00
|
|
|
pub touch: &'a mut TouchPurpose,
|
2019-10-05 00:29:26 +00:00
|
|
|
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,
|
2022-08-31 22:48:38 +00:00
|
|
|
pub config: &'a UiConfig,
|
2022-07-01 08:40:27 +00:00
|
|
|
pub cursor_blink_timed_out: &'a mut bool,
|
2020-02-07 13:44:11 +00:00
|
|
|
pub event_loop: &'a EventLoopWindowTarget<Event>,
|
2021-10-23 07:16:47 +00:00
|
|
|
pub event_proxy: &'a EventLoopProxy<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,
|
2021-10-23 07:16:47 +00:00
|
|
|
pub font_size: &'a mut Size,
|
|
|
|
pub dirty: &'a mut bool,
|
2022-08-11 09:06:19 +00:00
|
|
|
pub occluded: &'a mut bool,
|
2022-01-05 23:13:55 +00:00
|
|
|
pub preserve_title: bool,
|
2021-12-18 22:18:42 +00:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
pub master_fd: RawFd,
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
pub shell_pid: u32,
|
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]
|
2021-04-03 23:52:44 +00:00
|
|
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&self, val: B) {
|
2019-10-05 00:29:26 +00:00
|
|
|
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) {
|
2021-03-30 23:25:38 +00:00
|
|
|
let old_offset = self.terminal.grid().display_offset() as i32;
|
2020-07-15 21:27:32 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
self.terminal.scroll_display(scroll);
|
|
|
|
|
2022-07-24 23:17:57 +00:00
|
|
|
let lines_changed = old_offset - self.terminal.grid().display_offset() as i32;
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
// Keep track of manual display offset changes during search.
|
|
|
|
if self.search_active() {
|
2022-07-24 23:17:57 +00:00
|
|
|
self.search_state.display_offset_delta += lines_changed;
|
2020-07-15 21:27:32 +00:00
|
|
|
}
|
|
|
|
|
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)
|
2022-07-24 23:17:57 +00:00
|
|
|
&& self.terminal.selection.as_ref().map_or(false, |s| !s.is_empty())
|
2020-03-18 02:35:08 +00:00
|
|
|
{
|
|
|
|
self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right);
|
2021-04-15 22:16:31 +00:00
|
|
|
} else if self.mouse.left_button_state == ElementState::Pressed
|
|
|
|
|| self.mouse.right_button_state == ElementState::Pressed
|
2020-07-27 19:05:25 +00:00
|
|
|
{
|
2021-04-13 03:24:42 +00:00
|
|
|
let display_offset = self.terminal.grid().display_offset();
|
2021-04-15 22:16:31 +00:00
|
|
|
let point = self.mouse.point(&self.size_info(), display_offset);
|
|
|
|
self.update_selection(point, self.mouse.cell_side);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2021-01-24 21:45:36 +00:00
|
|
|
|
2022-07-24 23:17:57 +00:00
|
|
|
// Update dirty if actually scrolled or we're in the Vi mode.
|
|
|
|
*self.dirty |= lines_changed != 0;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2021-04-03 23:52:44 +00:00
|
|
|
// Copy text selection.
|
2019-10-05 00:29:26 +00:00
|
|
|
fn copy_selection(&mut self, ty: ClipboardType) {
|
2021-04-03 23:52:44 +00:00
|
|
|
let text = match self.terminal.selection_to_string().filter(|s| !s.is_empty()) {
|
|
|
|
Some(text) => text,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
2021-11-22 18:34:09 +00:00
|
|
|
if ty == ClipboardType::Selection && self.config.terminal_config.selection.save_to_clipboard
|
|
|
|
{
|
2021-04-03 23:52:44 +00:00
|
|
|
self.clipboard.store(ClipboardType::Clipboard, text.clone());
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2021-04-03 23:52:44 +00:00
|
|
|
self.clipboard.store(ty, text);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn selection_is_empty(&self) -> bool {
|
2022-07-20 08:24:27 +00:00
|
|
|
self.terminal.selection.as_ref().map_or(true, Selection::is_empty)
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn clear_selection(&mut self) {
|
2022-07-24 23:17:57 +00:00
|
|
|
// Clear the selection on the terminal.
|
|
|
|
let selection = self.terminal.selection.take();
|
|
|
|
// Mark the terminal as dirty when selection wasn't empty.
|
|
|
|
*self.dirty |= selection.map_or(false, |s| !s.is_empty());
|
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.
|
2021-03-30 23:25:38 +00:00
|
|
|
point.line = min(point.line, self.terminal.bottommost_line());
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-08-14 07:26:42 +00:00
|
|
|
// Update selection.
|
2021-03-30 23:25:38 +00:00
|
|
|
selection.update(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) {
|
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;
|
2021-04-03 23:52:44 +00:00
|
|
|
|
|
|
|
self.copy_selection(ClipboardType::Selection);
|
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;
|
2021-04-03 23:52:44 +00:00
|
|
|
|
|
|
|
self.copy_selection(ClipboardType::Selection);
|
2020-03-18 02:35:08 +00:00
|
|
|
},
|
|
|
|
_ => self.start_selection(ty, point, side),
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
#[inline]
|
|
|
|
fn touch_purpose(&mut self) -> &mut TouchPurpose {
|
|
|
|
self.touch
|
|
|
|
}
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
#[inline]
|
|
|
|
fn received_count(&mut self) -> &mut usize {
|
2021-11-22 18:34:09 +00:00
|
|
|
self.received_count
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn suppress_chars(&mut self) -> &mut bool {
|
2021-11-22 18:34:09 +00:00
|
|
|
self.suppress_chars
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-11-11 21:05:24 +00:00
|
|
|
fn modifiers(&mut self) -> &mut ModifiersState {
|
2021-11-22 18:34:09 +00:00
|
|
|
self.modifiers
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2021-04-13 03:24:42 +00:00
|
|
|
fn window(&mut self) -> &mut Window {
|
|
|
|
&mut self.display.window
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2021-04-13 03:24:42 +00:00
|
|
|
fn display(&mut self) -> &mut Display {
|
2021-11-22 18:34:09 +00:00
|
|
|
self.display
|
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
|
|
|
|
2021-12-18 17:27:10 +00:00
|
|
|
let mut args: Vec<String> = Vec::new();
|
2020-09-06 15:38:49 +00:00
|
|
|
|
|
|
|
// Reuse the arguments passed to Alacritty for the new instance.
|
2021-12-18 17:27:10 +00:00
|
|
|
#[allow(clippy::while_let_on_iterator)]
|
2020-09-06 15:38:49 +00:00
|
|
|
while let Some(arg) = env_args.next() {
|
2022-05-23 00:35:09 +00:00
|
|
|
// New instances shouldn't inherit command.
|
|
|
|
if arg == "-e" || arg == "--command" {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-12-18 17:27:10 +00:00
|
|
|
// On unix, the working directory of the foreground shell is used by `start_daemon`.
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
if arg == "--working-directory" {
|
2020-09-06 15:38:49 +00:00
|
|
|
let _ = env_args.next();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-12-18 17:27:10 +00:00
|
|
|
args.push(arg);
|
2020-09-06 15:38:49 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2021-12-18 22:18:42 +00:00
|
|
|
self.spawn_daemon(&alacritty, &args);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-22 18:34:09 +00:00
|
|
|
#[cfg(not(windows))]
|
|
|
|
fn create_new_window(&mut self) {
|
2022-01-03 18:55:22 +00:00
|
|
|
let mut options = WindowOptions::default();
|
2021-12-23 10:23:06 +00:00
|
|
|
if let Ok(working_directory) = foreground_process_path(self.master_fd, self.shell_pid) {
|
2022-01-03 18:55:22 +00:00
|
|
|
options.terminal_options.working_directory = Some(working_directory);
|
2021-12-23 10:23:06 +00:00
|
|
|
}
|
2021-11-22 18:34:09 +00:00
|
|
|
|
|
|
|
let _ = self.event_proxy.send_event(Event::new(EventType::CreateWindow(options), None));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
2021-10-23 07:16:47 +00:00
|
|
|
fn create_new_window(&mut self) {
|
2021-12-23 10:23:06 +00:00
|
|
|
let _ = self
|
|
|
|
.event_proxy
|
2022-01-03 18:55:22 +00:00
|
|
|
.send_event(Event::new(EventType::CreateWindow(WindowOptions::default()), None));
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
|
2021-12-18 22:18:42 +00:00
|
|
|
fn spawn_daemon<I, S>(&self, program: &str, args: I)
|
|
|
|
where
|
|
|
|
I: IntoIterator<Item = S> + Debug + Copy,
|
|
|
|
S: AsRef<OsStr>,
|
|
|
|
{
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
let result = spawn_daemon(program, args, self.master_fd, self.shell_pid);
|
|
|
|
#[cfg(windows)]
|
|
|
|
let result = spawn_daemon(program, args);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(_) => debug!("Launched {} with args {:?}", program, args),
|
|
|
|
Err(_) => warn!("Unable to launch {} with args {:?}", program, args),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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));
|
2021-11-22 18:34:09 +00:00
|
|
|
let font = self.config.font.clone().with_size(*self.font_size);
|
2021-10-23 07:16:47 +00:00
|
|
|
self.display.pending_update.set_font(font);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn reset_font_size(&mut self) {
|
2021-11-22 18:34:09 +00:00
|
|
|
*self.font_size = self.config.font.size();
|
|
|
|
self.display.pending_update.set_font(self.config.font.clone());
|
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() {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.display.pending_update.dirty = true;
|
2020-07-09 21:45:22 +00:00
|
|
|
self.message_buffer.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn start_search(&mut self, direction: Direction) {
|
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());
|
2021-03-01 19:50:39 +00:00
|
|
|
self.search_state.history.truncate(MAX_SEARCH_HISTORY_SIZE);
|
2020-12-20 04:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2021-09-28 08:30:41 +00:00
|
|
|
|
|
|
|
// Adjust origin for content moving upward on search start.
|
|
|
|
if self.terminal.grid().cursor.point.line + 1 == self.terminal.screen_lines() {
|
|
|
|
self.search_state.origin.line -= 1;
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
} else {
|
2021-07-12 01:22:49 +00:00
|
|
|
let viewport_top = Line(-(self.terminal.grid().display_offset() as i32)) - 1;
|
|
|
|
let viewport_bottom = viewport_top + self.terminal.bottommost_line();
|
2021-03-30 23:25:38 +00:00
|
|
|
let last_column = self.terminal.last_column();
|
|
|
|
self.search_state.origin = match direction {
|
2021-07-12 01:22:49 +00:00
|
|
|
Direction::Right => Point::new(viewport_top, Column(0)),
|
|
|
|
Direction::Left => Point::new(viewport_bottom, last_column),
|
2021-03-30 23:25:38 +00:00
|
|
|
};
|
2021-01-01 09:21:02 +00:00
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2022-08-29 13:29:13 +00:00
|
|
|
// Enable IME so we can input into the search bar with it if we were in Vi mode.
|
|
|
|
self.window().set_ime_allowed(true);
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
self.display.pending_update.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.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::DelayedSearch, self.display.window.id());
|
|
|
|
if self.scheduler.scheduled(timer_id) {
|
2020-07-09 21:45:22 +00:00
|
|
|
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.
|
2021-03-30 23:25:38 +00:00
|
|
|
let start = *focused_match.start();
|
|
|
|
let end = *focused_match.end();
|
2020-11-13 05:40:09 +00:00
|
|
|
self.start_selection(SelectionType::Simple, start, Side::Left);
|
|
|
|
self.update_selection(end, Side::Right);
|
2021-04-17 19:28:23 +00:00
|
|
|
self.copy_selection(ClipboardType::Selection);
|
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();
|
2022-07-20 08:24:27 +00:00
|
|
|
regex.truncate(regex.rfind(' ').map_or(0, |i| i + 1));
|
2020-07-10 21:00:33 +00:00
|
|
|
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-03-30 23:25:38 +00:00
|
|
|
Direction::Right => focused_match.end().add(self.terminal, Boundary::None, 1),
|
|
|
|
Direction::Left => focused_match.start().sub(self.terminal, Boundary::None, 1),
|
2020-07-15 21:27:32 +00:00
|
|
|
};
|
|
|
|
|
2021-01-01 09:21:02 +00:00
|
|
|
self.terminal.scroll_to_point(new_origin);
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
self.search_state.display_offset_delta = 0;
|
2021-03-30 23:25:38 +00:00
|
|
|
self.search_state.origin = new_origin;
|
2020-07-15 21:27:32 +00:00
|
|
|
}
|
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.
|
2021-03-30 23:25:38 +00:00
|
|
|
let old_display_offset = self.terminal.grid().display_offset() as i32;
|
2021-01-01 09:21:02 +00:00
|
|
|
self.terminal.scroll_to_point(new_origin);
|
2021-03-30 23:25:38 +00:00
|
|
|
let new_display_offset = self.terminal.grid().display_offset() as i32;
|
2021-01-01 09:21:02 +00:00
|
|
|
self.search_state.display_offset_delta = new_display_offset - old_display_offset;
|
|
|
|
|
|
|
|
// Store origin and scroll back to the match.
|
|
|
|
self.terminal.scroll_display(Scroll::Delta(-self.search_state.display_offset_delta));
|
2021-03-30 23:25:38 +00:00
|
|
|
self.search_state.origin = new_origin;
|
2020-07-15 21:27:32 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
/// Find the next search match.
|
2021-03-30 23:25:38 +00:00
|
|
|
fn search_next(&mut self, origin: Point, direction: Direction, side: Side) -> Option<Match> {
|
2021-01-24 21:45:36 +00:00
|
|
|
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.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::BlinkCursor, self.display.window.id());
|
2022-07-01 08:40:27 +00:00
|
|
|
if self.scheduler.unschedule(timer_id).is_some() {
|
|
|
|
self.schedule_blinking();
|
2022-07-24 23:17:57 +00:00
|
|
|
|
|
|
|
// Mark the cursor as visible and queue redraw if the cursor was hidden.
|
|
|
|
if mem::take(&mut self.display.cursor_hidden) {
|
|
|
|
*self.dirty = true;
|
|
|
|
}
|
2022-07-01 08:40:27 +00:00
|
|
|
} else if *self.cursor_blink_timed_out {
|
|
|
|
self.update_cursor_blinking();
|
2020-11-23 23:11:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hide mouse cursor.
|
2021-11-22 18:34:09 +00:00
|
|
|
if self.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-03-01 19:50:39 +00:00
|
|
|
/// Process a new character for keyboard hints.
|
|
|
|
fn hint_input(&mut self, c: char) {
|
2021-04-13 03:24:42 +00:00
|
|
|
if let Some(hint) = self.display.hint_state.keyboard_input(self.terminal, c) {
|
|
|
|
self.mouse.block_hint_launcher = false;
|
|
|
|
self.trigger_hint(&hint);
|
|
|
|
}
|
2021-03-01 19:50:39 +00:00
|
|
|
*self.dirty = true;
|
2021-04-13 03:24:42 +00:00
|
|
|
}
|
2021-04-03 23:52:44 +00:00
|
|
|
|
2021-04-13 03:24:42 +00:00
|
|
|
/// Trigger a hint action.
|
|
|
|
fn trigger_hint(&mut self, hint: &HintMatch) {
|
|
|
|
if self.mouse.block_hint_launcher {
|
|
|
|
return;
|
|
|
|
}
|
2021-04-03 23:52:44 +00:00
|
|
|
|
2022-07-10 17:11:28 +00:00
|
|
|
let hint_bounds = hint.bounds();
|
|
|
|
let text = match hint.hyperlink() {
|
|
|
|
Some(hyperlink) => hyperlink.uri().to_owned(),
|
|
|
|
None => self.terminal.bounds_to_string(*hint_bounds.start(), *hint_bounds.end()),
|
|
|
|
};
|
|
|
|
|
|
|
|
match &hint.action() {
|
2021-04-03 23:52:44 +00:00
|
|
|
// Launch an external program.
|
|
|
|
HintAction::Command(command) => {
|
|
|
|
let mut args = command.args().to_vec();
|
|
|
|
args.push(text);
|
2021-12-18 22:18:42 +00:00
|
|
|
self.spawn_daemon(command.program(), &args);
|
2021-04-03 23:52:44 +00:00
|
|
|
},
|
|
|
|
// Copy the text to the clipboard.
|
|
|
|
HintAction::Action(HintInternalAction::Copy) => {
|
|
|
|
self.clipboard.store(ClipboardType::Clipboard, text);
|
|
|
|
},
|
|
|
|
// Write the text to the PTY/search.
|
|
|
|
HintAction::Action(HintInternalAction::Paste) => {
|
|
|
|
self.paste(&text);
|
|
|
|
},
|
|
|
|
// Select the text.
|
|
|
|
HintAction::Action(HintInternalAction::Select) => {
|
2022-07-10 17:11:28 +00:00
|
|
|
self.start_selection(SelectionType::Simple, *hint_bounds.start(), Side::Left);
|
|
|
|
self.update_selection(*hint_bounds.end(), Side::Right);
|
2021-04-17 19:28:23 +00:00
|
|
|
self.copy_selection(ClipboardType::Selection);
|
2021-04-03 23:52:44 +00:00
|
|
|
},
|
2021-04-15 21:25:49 +00:00
|
|
|
// Move the vi mode cursor.
|
|
|
|
HintAction::Action(HintInternalAction::MoveViModeCursor) => {
|
|
|
|
// Enter vi mode if we're not in it already.
|
|
|
|
if !self.terminal.mode().contains(TermMode::VI) {
|
|
|
|
self.terminal.toggle_vi_mode();
|
|
|
|
}
|
|
|
|
|
2022-07-10 17:11:28 +00:00
|
|
|
self.terminal.vi_goto_point(*hint_bounds.start());
|
2022-07-24 23:17:57 +00:00
|
|
|
self.mark_dirty();
|
2021-04-15 21:25:49 +00:00
|
|
|
},
|
2021-04-03 23:52:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 15:40:51 +00:00
|
|
|
/// Expand the selection to the current mouse cursor position.
|
|
|
|
#[inline]
|
|
|
|
fn expand_selection(&mut self) {
|
|
|
|
let selection_type = match self.mouse().click_state {
|
|
|
|
ClickState::Click => {
|
|
|
|
if self.modifiers().ctrl() {
|
|
|
|
SelectionType::Block
|
|
|
|
} else {
|
|
|
|
SelectionType::Simple
|
|
|
|
}
|
|
|
|
},
|
|
|
|
ClickState::DoubleClick => SelectionType::Semantic,
|
|
|
|
ClickState::TripleClick => SelectionType::Lines,
|
|
|
|
ClickState::None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Load mouse point, treating message bar and padding as the closest cell.
|
|
|
|
let display_offset = self.terminal().grid().display_offset();
|
|
|
|
let point = self.mouse().point(&self.size_info(), display_offset);
|
|
|
|
|
|
|
|
let cell_side = self.mouse().cell_side;
|
|
|
|
|
|
|
|
let selection = match &mut self.terminal_mut().selection {
|
|
|
|
Some(selection) => selection,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
selection.ty = selection_type;
|
|
|
|
self.update_selection(point, cell_side);
|
|
|
|
|
|
|
|
// Move vi mode cursor to mouse click position.
|
|
|
|
if self.terminal().mode().contains(TermMode::VI) && !self.search_active() {
|
|
|
|
self.terminal_mut().vi_mode_cursor.point = point;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-03 23:52:44 +00:00
|
|
|
/// Paste a text into the terminal.
|
|
|
|
fn paste(&mut self, text: &str) {
|
|
|
|
if self.search_active() {
|
|
|
|
for c in text.chars() {
|
|
|
|
self.search_input(c);
|
|
|
|
}
|
|
|
|
} else if self.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
|
|
|
|
self.write_to_pty(&b"\x1b[200~"[..]);
|
2023-02-23 18:57:18 +00:00
|
|
|
|
|
|
|
// Write filtered escape sequences.
|
|
|
|
//
|
|
|
|
// We remove `\x1b` to ensure it's impossible for the pasted text to write the bracketed
|
|
|
|
// paste end escape `\x1b[201~` and `\x03` since some shells incorrectly terminate
|
|
|
|
// bracketed paste on its receival.
|
|
|
|
let filtered = text.replace('\x1b', "").replace('\x03', "");
|
|
|
|
self.write_to_pty(filtered.into_bytes());
|
|
|
|
|
2021-04-03 23:52:44 +00:00
|
|
|
self.write_to_pty(&b"\x1b[201~"[..]);
|
|
|
|
} else {
|
|
|
|
// In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
|
|
|
|
// pasted data from keystrokes.
|
|
|
|
// In theory, we should construct the keystrokes needed to produce the data we are
|
|
|
|
// pasting... since that's neither practical nor sensible (and probably an impossible
|
|
|
|
// task to solve in a general way), we'll just replace line breaks (windows and unix
|
|
|
|
// style) with a single carriage return (\r, which is what the Enter key produces).
|
2021-12-23 10:23:06 +00:00
|
|
|
self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r").into_bytes());
|
2021-04-03 23:52:44 +00:00
|
|
|
}
|
2021-03-01 19:50:39 +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) {
|
2022-08-29 13:29:13 +00:00
|
|
|
let was_in_vi_mode = self.terminal.mode().contains(TermMode::VI);
|
|
|
|
if was_in_vi_mode {
|
2022-02-02 17:20:14 +00:00
|
|
|
// If we had search running when leaving Vi mode we should mark terminal fully damaged
|
|
|
|
// to cleanup highlighted results.
|
2022-05-23 00:01:46 +00:00
|
|
|
if self.search_state.dfas.take().is_some() {
|
2022-02-02 17:20:14 +00:00
|
|
|
self.terminal.mark_fully_damaged();
|
|
|
|
} else {
|
2022-05-26 21:30:33 +00:00
|
|
|
// Damage line indicator.
|
2022-02-02 17:20:14 +00:00
|
|
|
self.terminal.damage_line(0, 0, self.terminal.columns() - 1);
|
|
|
|
}
|
2022-02-01 21:12:58 +00:00
|
|
|
} else {
|
2021-01-24 21:45:36 +00:00
|
|
|
self.clear_selection();
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2022-05-23 00:01:46 +00:00
|
|
|
if self.search_active() {
|
|
|
|
self.cancel_search();
|
|
|
|
}
|
|
|
|
|
2022-08-29 13:29:13 +00:00
|
|
|
// We don't want IME in Vi mode.
|
|
|
|
self.window().set_ime_allowed(was_in_vi_mode);
|
|
|
|
|
2021-01-24 21:45:36 +00:00
|
|
|
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
|
|
|
|
2021-11-22 18:34:09 +00:00
|
|
|
fn config(&self) -> &UiConfig {
|
2019-11-03 19:02:26 +00:00
|
|
|
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
|
|
|
|
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.
|
2021-11-22 18:34:09 +00:00
|
|
|
if self.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.
|
2021-07-03 03:06:52 +00:00
|
|
|
self.search_state.dfas = RegexSearch::new(regex).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.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::DelayedSearch, self.display.window.id());
|
|
|
|
self.scheduler.unschedule(timer_id);
|
2021-01-01 09:21:02 +00:00
|
|
|
|
2021-02-22 23:54:12 +00:00
|
|
|
// Clear focused match.
|
|
|
|
self.search_state.focused_match = None;
|
|
|
|
|
2021-01-01 09:21:02 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2021-03-30 23:25:38 +00:00
|
|
|
// Reset display offset and cursor position.
|
2022-03-10 19:45:20 +00:00
|
|
|
self.terminal.vi_mode_cursor.point = self.search_state.origin;
|
2020-07-09 21:45:22 +00:00
|
|
|
self.terminal.scroll_display(Scroll::Delta(self.search_state.display_offset_delta));
|
|
|
|
self.search_state.display_offset_delta = 0;
|
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-03-30 23:25:38 +00:00
|
|
|
let clamped_origin = self.search_state.origin.grid_clamp(self.terminal, Boundary::Grid);
|
|
|
|
match self.terminal.search_next(dfas, clamped_origin, direction, Side::Left, limit) {
|
2020-07-09 21:45:22 +00:00
|
|
|
Some(regex_match) => {
|
2021-03-30 23:25:38 +00:00
|
|
|
let old_offset = self.terminal.grid().display_offset() as i32;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
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();
|
2021-07-12 01:22:49 +00:00
|
|
|
self.search_state.display_offset_delta += old_offset - display_offset as i32;
|
2020-07-09 21:45:22 +00:00
|
|
|
|
|
|
|
// Since we found a result, we require no delayed re-search.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::DelayedSearch, self.display.window.id());
|
|
|
|
self.scheduler.unschedule(timer_id);
|
2020-07-09 21:45:22 +00:00
|
|
|
},
|
|
|
|
// 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.
|
2021-10-23 07:16:47 +00:00
|
|
|
let timer_id = TimerId::new(Topic::DelayedSearch, self.display.window.id());
|
|
|
|
if !self.scheduler.scheduled(timer_id) {
|
|
|
|
let event = Event::new(EventType::SearchNext, self.display.window.id());
|
|
|
|
self.scheduler.schedule(event, TYPING_SEARCH_DELAY, false, timer_id);
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
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) {
|
2022-08-29 13:29:13 +00:00
|
|
|
let vi_mode = self.terminal.mode().contains(TermMode::VI);
|
|
|
|
self.window().set_ime_allowed(!vi_mode);
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
self.display.pending_update.dirty = true;
|
2020-12-20 04:27:08 +00:00
|
|
|
self.search_state.history_index = None;
|
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-11-23 23:11:03 +00:00
|
|
|
/// Update the cursor blinking state.
|
|
|
|
fn update_cursor_blinking(&mut self) {
|
|
|
|
// Get config cursor style.
|
2021-11-22 18:34:09 +00:00
|
|
|
let mut cursor_style = self.config.terminal_config.cursor.style;
|
2022-01-11 20:58:50 +00:00
|
|
|
let vi_mode = self.terminal.mode().contains(TermMode::VI);
|
|
|
|
if vi_mode {
|
2021-11-22 18:34:09 +00:00
|
|
|
cursor_style = self.config.terminal_config.cursor.vi_mode_style.unwrap_or(cursor_style);
|
2022-01-11 20:58:50 +00:00
|
|
|
}
|
2020-11-23 23:11:03 +00:00
|
|
|
|
|
|
|
// Check terminal cursor style.
|
|
|
|
let terminal_blinking = self.terminal.cursor_style().blinking;
|
2022-01-11 20:58:50 +00:00
|
|
|
let mut blinking = cursor_style.blinking_override().unwrap_or(terminal_blinking);
|
2022-08-29 13:29:13 +00:00
|
|
|
blinking &= (vi_mode || self.terminal().mode().contains(TermMode::SHOW_CURSOR))
|
|
|
|
&& self.display().ime.preedit().is_none();
|
2020-11-23 23:11:03 +00:00
|
|
|
|
|
|
|
// Update cursor blinking state.
|
2022-07-01 08:40:27 +00:00
|
|
|
let window_id = self.display.window.id();
|
|
|
|
self.scheduler.unschedule(TimerId::new(Topic::BlinkCursor, window_id));
|
|
|
|
self.scheduler.unschedule(TimerId::new(Topic::BlinkTimeout, window_id));
|
|
|
|
|
|
|
|
// Reset blinkinig timeout.
|
|
|
|
*self.cursor_blink_timed_out = false;
|
|
|
|
|
2020-11-23 23:11:03 +00:00
|
|
|
if blinking && self.terminal.is_focused {
|
2022-07-01 08:40:27 +00:00
|
|
|
self.schedule_blinking();
|
|
|
|
self.schedule_blinking_timeout();
|
2020-11-23 23:11:03 +00:00
|
|
|
} 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
|
|
|
}
|
|
|
|
}
|
2022-07-01 08:40:27 +00:00
|
|
|
|
|
|
|
fn schedule_blinking(&mut self) {
|
|
|
|
let window_id = self.display.window.id();
|
|
|
|
let timer_id = TimerId::new(Topic::BlinkCursor, window_id);
|
|
|
|
let event = Event::new(EventType::BlinkCursor, window_id);
|
|
|
|
let blinking_interval =
|
|
|
|
Duration::from_millis(self.config.terminal_config.cursor.blink_interval());
|
|
|
|
self.scheduler.schedule(event, blinking_interval, true, timer_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn schedule_blinking_timeout(&mut self) {
|
|
|
|
let blinking_timeout = self.config.terminal_config.cursor.blink_timeout();
|
|
|
|
if blinking_timeout == 0 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let window_id = self.display.window.id();
|
|
|
|
let blinking_timeout_interval = Duration::from_secs(blinking_timeout);
|
|
|
|
let event = Event::new(EventType::BlinkCursorTimeout, window_id);
|
|
|
|
let timer_id = TimerId::new(Topic::BlinkTimeout, window_id);
|
|
|
|
|
|
|
|
self.scheduler.schedule(event, blinking_timeout_interval, false, timer_id);
|
|
|
|
}
|
2020-07-09 21:45:22 +00:00
|
|
|
}
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
/// Identified purpose of the touch input.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum TouchPurpose {
|
2019-10-05 00:29:26 +00:00
|
|
|
None,
|
2023-02-13 22:35:18 +00:00
|
|
|
Select(TouchEvent),
|
|
|
|
Scroll(TouchEvent),
|
|
|
|
Zoom(TouchZoom),
|
|
|
|
Tap(TouchEvent),
|
|
|
|
Invalid(HashSet<u64>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TouchPurpose {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Touch zooming state.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TouchZoom {
|
|
|
|
slots: (TouchEvent, TouchEvent),
|
|
|
|
fractions: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TouchZoom {
|
|
|
|
pub fn new(slots: (TouchEvent, TouchEvent)) -> Self {
|
|
|
|
Self { slots, fractions: Default::default() }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get slot distance change since last update.
|
|
|
|
pub fn font_delta(&mut self, slot: TouchEvent) -> f32 {
|
|
|
|
let old_distance = self.distance();
|
|
|
|
|
|
|
|
// Update touch slots.
|
|
|
|
if slot.id == self.slots.0.id {
|
|
|
|
self.slots.0 = slot;
|
|
|
|
} else {
|
|
|
|
self.slots.1 = slot;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate font change in `FONT_SIZE_STEP` increments.
|
|
|
|
let delta = (self.distance() - old_distance) * TOUCH_ZOOM_FACTOR + self.fractions;
|
|
|
|
let font_delta = (delta.abs() / FONT_SIZE_STEP).floor() * FONT_SIZE_STEP * delta.signum();
|
|
|
|
self.fractions = delta - font_delta;
|
|
|
|
|
|
|
|
font_delta
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get active touch slots.
|
|
|
|
pub fn slots(&self) -> HashSet<u64> {
|
|
|
|
let mut set = HashSet::new();
|
|
|
|
set.insert(self.slots.0.id);
|
|
|
|
set.insert(self.slots.1.id);
|
|
|
|
set
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Calculate distance between slots.
|
|
|
|
fn distance(&self) -> f32 {
|
|
|
|
let delta_x = self.slots.0.location.x - self.slots.1.location.x;
|
|
|
|
let delta_y = self.slots.0.location.y - self.slots.1.location.y;
|
|
|
|
delta_x.hypot(delta_y) as f32
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
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 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,
|
2023-01-16 17:22:01 +00:00
|
|
|
pub accumulated_scroll: AccumulatedScroll,
|
2019-10-05 00:29:26 +00:00
|
|
|
pub cell_side: Side,
|
|
|
|
pub lines_scrolled: f32,
|
2021-04-13 03:24:42 +00:00
|
|
|
pub block_hint_launcher: bool,
|
|
|
|
pub hint_highlight_dirty: bool,
|
2020-08-14 07:26:42 +00:00
|
|
|
pub inside_text_area: bool,
|
2021-03-30 23:25:38 +00:00
|
|
|
pub x: usize,
|
|
|
|
pub y: usize,
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Mouse {
|
|
|
|
fn default() -> Mouse {
|
|
|
|
Mouse {
|
|
|
|
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,
|
|
|
|
cell_side: Side::Left,
|
2021-04-13 03:24:42 +00:00
|
|
|
hint_highlight_dirty: Default::default(),
|
|
|
|
block_hint_launcher: Default::default(),
|
2021-03-30 23:25:38 +00:00
|
|
|
inside_text_area: Default::default(),
|
|
|
|
lines_scrolled: Default::default(),
|
2023-01-16 17:22:01 +00:00
|
|
|
accumulated_scroll: Default::default(),
|
2021-03-30 23:25:38 +00:00
|
|
|
x: Default::default(),
|
|
|
|
y: Default::default(),
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 22:16:31 +00:00
|
|
|
impl Mouse {
|
|
|
|
/// Convert mouse pixel coordinates to viewport point.
|
|
|
|
///
|
|
|
|
/// If the coordinates are outside of the terminal grid, like positions inside the padding, the
|
|
|
|
/// coordinates will be clamped to the closest grid coordinates.
|
|
|
|
#[inline]
|
|
|
|
pub fn point(&self, size: &SizeInfo, display_offset: usize) -> Point {
|
|
|
|
let col = self.x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
|
|
|
|
let col = min(Column(col), size.last_column());
|
|
|
|
|
|
|
|
let line = self.y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
|
|
|
|
let line = min(line, size.bottommost_line().0 as usize);
|
|
|
|
|
2022-05-26 21:30:33 +00:00
|
|
|
term::viewport_to_point(display_offset, Point::new(line, col))
|
2021-04-15 22:16:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:35:18 +00:00
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
pub enum ClickState {
|
|
|
|
None,
|
|
|
|
Click,
|
|
|
|
DoubleClick,
|
|
|
|
TripleClick,
|
|
|
|
}
|
|
|
|
|
2023-01-16 17:22:01 +00:00
|
|
|
/// The amount of scroll accumulated from the pointer events.
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
pub struct AccumulatedScroll {
|
|
|
|
/// Scroll we should perform along `x` axis.
|
|
|
|
pub x: f64,
|
|
|
|
|
|
|
|
/// Scroll we should perform along `y` axis.
|
|
|
|
pub y: f64,
|
|
|
|
}
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
|
2022-11-03 16:37:54 +00:00
|
|
|
/// Handle events from winit.
|
|
|
|
pub fn handle_event(&mut self, event: WinitEvent<'_, Event>) {
|
2019-10-05 00:29:26 +00:00
|
|
|
match event {
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(Event { payload, .. }) => match payload {
|
2022-02-17 22:27:10 +00:00
|
|
|
EventType::ScaleFactorChanged(scale_factor, (width, height)) => {
|
2021-10-23 07:16:47 +00:00
|
|
|
let display_update_pending = &mut self.ctx.display.pending_update;
|
2020-01-10 01:51:37 +00:00
|
|
|
|
2022-02-17 22:27:10 +00:00
|
|
|
// Push current font to update its scale factor.
|
2021-11-22 18:34:09 +00:00
|
|
|
let font = self.ctx.config.font.clone();
|
2021-10-23 07:16:47 +00:00
|
|
|
display_update_pending.set_font(font.with_size(*self.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
|
|
|
|
2022-02-17 22:27:10 +00:00
|
|
|
self.ctx.window().scale_factor = scale_factor;
|
2020-01-10 01:51:37 +00:00
|
|
|
},
|
2022-12-30 16:25:04 +00:00
|
|
|
EventType::Frame => {
|
|
|
|
self.ctx.display.window.has_frame.store(true, Ordering::Relaxed);
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
EventType::SearchNext => self.ctx.goto_match(None),
|
|
|
|
EventType::Scroll(scroll) => self.ctx.scroll(scroll),
|
|
|
|
EventType::BlinkCursor => {
|
|
|
|
self.ctx.display.cursor_hidden ^= true;
|
|
|
|
*self.ctx.dirty = true;
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2022-07-01 08:40:27 +00:00
|
|
|
EventType::BlinkCursorTimeout => {
|
|
|
|
// Disable blinking after timeout reached.
|
|
|
|
let timer_id = TimerId::new(Topic::BlinkCursor, self.ctx.display.window.id());
|
|
|
|
self.ctx.scheduler.unschedule(timer_id);
|
|
|
|
*self.ctx.cursor_blink_timed_out = true;
|
2022-07-24 23:17:57 +00:00
|
|
|
self.ctx.display.cursor_hidden = false;
|
2022-07-01 08:40:27 +00:00
|
|
|
*self.ctx.dirty = true;
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
EventType::Message(message) => {
|
|
|
|
self.ctx.message_buffer.push(message);
|
|
|
|
self.ctx.display.pending_update.dirty = true;
|
2020-11-23 23:11:03 +00:00
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
EventType::Terminal(event) => match event {
|
2020-07-11 17:03:09 +00:00
|
|
|
TerminalEvent::Title(title) => {
|
2022-01-05 23:13:55 +00:00
|
|
|
if !self.ctx.preserve_title && self.ctx.config.window.dynamic_title {
|
2022-01-03 18:55:22 +00:00
|
|
|
self.ctx.window().set_title(title);
|
2020-07-11 17:03:09 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
TerminalEvent::ResetTitle => {
|
2022-01-03 18:55:22 +00:00
|
|
|
let window_config = &self.ctx.config.window;
|
|
|
|
if window_config.dynamic_title {
|
|
|
|
self.ctx.display.window.set_title(window_config.identity.title.clone());
|
2020-07-11 17:03:09 +00:00
|
|
|
}
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
TerminalEvent::Wakeup => *self.ctx.dirty = true,
|
2020-07-10 19:32:44 +00:00
|
|
|
TerminalEvent::Bell => {
|
2021-01-24 21:45:36 +00:00
|
|
|
// Set window urgency.
|
2021-10-23 07:16:47 +00:00
|
|
|
if self.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) {
|
|
|
|
let focused = self.ctx.terminal.is_focused;
|
|
|
|
self.ctx.window().set_urgent(!focused);
|
2021-01-24 21:45:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ring visual bell.
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.display.visual_bell.ring();
|
2021-01-24 21:45:36 +00:00
|
|
|
|
|
|
|
// Execute bell command.
|
2021-11-22 18:34:09 +00:00
|
|
|
if let Some(bell_command) = &self.ctx.config.bell.command {
|
2021-12-18 22:18:42 +00:00
|
|
|
self.ctx.spawn_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) => {
|
2022-04-03 20:08:44 +00:00
|
|
|
if self.ctx.terminal.is_focused {
|
|
|
|
self.ctx.clipboard.store(clipboard_type, content);
|
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
},
|
|
|
|
TerminalEvent::ClipboardLoad(clipboard_type, format) => {
|
2022-04-03 20:08:44 +00:00
|
|
|
if self.ctx.terminal.is_focused {
|
|
|
|
let text = format(self.ctx.clipboard.load(clipboard_type).as_str());
|
|
|
|
self.ctx.write_to_pty(text.into_bytes());
|
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
},
|
2021-01-24 21:45:36 +00:00
|
|
|
TerminalEvent::ColorRequest(index, format) => {
|
2022-01-15 03:35:05 +00:00
|
|
|
let color = self.ctx.terminal().colors()[index]
|
|
|
|
.unwrap_or(self.ctx.display.colors[index]);
|
|
|
|
self.ctx.write_to_pty(format(color).into_bytes());
|
2021-01-24 21:45:36 +00:00
|
|
|
},
|
2022-04-06 10:06:39 +00:00
|
|
|
TerminalEvent::TextAreaSizeRequest(format) => {
|
|
|
|
let text = format(self.ctx.size_info().into());
|
|
|
|
self.ctx.write_to_pty(text.into_bytes());
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
TerminalEvent::PtyWrite(text) => self.ctx.write_to_pty(text.into_bytes()),
|
|
|
|
TerminalEvent::MouseCursorDirty => self.reset_mouse_cursor(),
|
2020-06-18 01:02:56 +00:00
|
|
|
TerminalEvent::Exit => (),
|
2021-10-23 07:16:47 +00:00
|
|
|
TerminalEvent::CursorBlinkingChange => self.ctx.update_cursor_blinking(),
|
2020-06-06 21:33:20 +00:00
|
|
|
},
|
2022-08-31 22:48:38 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
EventType::IpcConfig(_) => (),
|
2021-11-22 18:34:09 +00:00
|
|
|
EventType::ConfigReload(_) | EventType::CreateWindow(_) => (),
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::RedrawRequested(_) => *self.ctx.dirty = true,
|
|
|
|
WinitEvent::WindowEvent { event, .. } => {
|
2019-10-05 00:29:26 +00:00
|
|
|
match event {
|
2021-10-23 07:16:47 +00:00
|
|
|
WindowEvent::CloseRequested => self.ctx.terminal.exit(),
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::Resized(size) => {
|
2022-12-30 15:39:39 +00:00
|
|
|
// Ignore resize events to zero in any dimension, to avoid issues with Winit
|
|
|
|
// and the ConPTY. A 0x0 resize will also occur when the window is minimized
|
|
|
|
// on Windows.
|
|
|
|
if size.width == 0 || size.height == 0 {
|
2020-07-28 10:00:55 +00:00
|
|
|
return;
|
2019-12-09 17:26:31 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.display.pending_update.set_dimensions(size);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.key_input(input);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
WindowEvent::ModifiersChanged(modifiers) => self.modifiers_input(modifiers),
|
|
|
|
WindowEvent::ReceivedCharacter(c) => self.received_char(c),
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::MouseInput { state, button, .. } => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.window().set_mouse_visible(true);
|
|
|
|
self.mouse_input(state, button);
|
2020-03-11 22:31:59 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::CursorMoved { position, .. } => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.window().set_mouse_visible(true);
|
|
|
|
self.mouse_moved(position);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.window().set_mouse_visible(true);
|
|
|
|
self.mouse_wheel_input(delta, phase);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2023-02-13 22:35:18 +00:00
|
|
|
WindowEvent::Touch(touch) => self.touch(touch),
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::Focused(is_focused) => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.terminal.is_focused = is_focused;
|
2022-07-24 23:17:57 +00:00
|
|
|
|
|
|
|
// When the unfocused hollow is used we must redraw on focus change.
|
|
|
|
if self.ctx.config.terminal_config.cursor.unfocused_hollow {
|
|
|
|
*self.ctx.dirty = true;
|
|
|
|
}
|
2021-10-23 07:16:47 +00:00
|
|
|
|
|
|
|
if is_focused {
|
|
|
|
self.ctx.window().set_urgent(false);
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2021-10-23 07:16:47 +00:00
|
|
|
|
|
|
|
self.ctx.update_cursor_blinking();
|
|
|
|
self.on_focus_change(is_focused);
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2022-08-11 09:06:19 +00:00
|
|
|
WindowEvent::Occluded(occluded) => {
|
|
|
|
*self.ctx.occluded = occluded;
|
|
|
|
},
|
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();
|
2023-02-27 07:30:39 +00:00
|
|
|
self.ctx.paste(&(path + " "));
|
2019-10-05 00:29:26 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::CursorLeft { .. } => {
|
2021-10-23 07:16:47 +00:00
|
|
|
self.ctx.mouse.inside_text_area = false;
|
2019-11-03 20:59:28 +00:00
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
if self.ctx.display().highlighted_hint.is_some() {
|
|
|
|
*self.ctx.dirty = true;
|
2019-11-03 20:59:28 +00:00
|
|
|
}
|
|
|
|
},
|
2022-08-29 13:29:13 +00:00
|
|
|
WindowEvent::Ime(ime) => match ime {
|
|
|
|
Ime::Commit(text) => {
|
|
|
|
*self.ctx.dirty = true;
|
|
|
|
|
2022-08-10 12:48:46 +00:00
|
|
|
for ch in text.chars() {
|
2022-08-29 13:29:13 +00:00
|
|
|
self.received_char(ch);
|
2022-08-10 12:48:46 +00:00
|
|
|
}
|
2022-08-29 13:29:13 +00:00
|
|
|
|
|
|
|
self.ctx.update_cursor_blinking();
|
|
|
|
},
|
|
|
|
Ime::Preedit(text, cursor_offset) => {
|
|
|
|
let preedit = if text.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(Preedit::new(text, cursor_offset.map(|offset| offset.0)))
|
|
|
|
};
|
|
|
|
|
2022-09-15 20:51:06 +00:00
|
|
|
if self.ctx.display.ime.preedit() != preedit.as_ref() {
|
|
|
|
self.ctx.display.ime.set_preedit(preedit);
|
|
|
|
self.ctx.update_cursor_blinking();
|
|
|
|
*self.ctx.dirty = true;
|
|
|
|
}
|
2022-08-29 13:29:13 +00:00
|
|
|
},
|
|
|
|
Ime::Enabled => {
|
|
|
|
self.ctx.display.ime.set_enabled(true);
|
|
|
|
*self.ctx.dirty = true;
|
|
|
|
},
|
|
|
|
Ime::Disabled => {
|
|
|
|
self.ctx.display.ime.set_enabled(false);
|
|
|
|
*self.ctx.dirty = true;
|
|
|
|
},
|
2022-08-10 12:48:46 +00:00
|
|
|
},
|
2020-03-07 22:17:38 +00:00
|
|
|
WindowEvent::KeyboardInput { is_synthetic: true, .. }
|
|
|
|
| WindowEvent::TouchpadPressure { .. }
|
2023-02-02 08:30:23 +00:00
|
|
|
| WindowEvent::TouchpadMagnify { .. }
|
|
|
|
| WindowEvent::TouchpadRotate { .. }
|
|
|
|
| WindowEvent::SmartMagnify { .. }
|
2020-03-07 22:17:38 +00:00
|
|
|
| WindowEvent::ScaleFactorChanged { .. }
|
|
|
|
| WindowEvent::CursorEntered { .. }
|
|
|
|
| WindowEvent::AxisMotion { .. }
|
|
|
|
| WindowEvent::HoveredFileCancelled
|
|
|
|
| WindowEvent::Destroyed
|
|
|
|
| WindowEvent::ThemeChanged(_)
|
|
|
|
| WindowEvent::HoveredFile(_)
|
|
|
|
| WindowEvent::Moved(_) => (),
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
},
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::Suspended { .. }
|
|
|
|
| WinitEvent::NewEvents { .. }
|
|
|
|
| WinitEvent::DeviceEvent { .. }
|
|
|
|
| WinitEvent::MainEventsCleared
|
|
|
|
| WinitEvent::RedrawEventsCleared
|
|
|
|
| WinitEvent::Resumed
|
|
|
|
| WinitEvent::LoopDestroyed => (),
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The event processor.
|
|
|
|
///
|
|
|
|
/// Stores some state from received events and dispatches actions when they are
|
|
|
|
/// triggered.
|
|
|
|
pub struct Processor {
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
wayland_event_queue: Option<EventQueue>,
|
|
|
|
windows: HashMap<WindowId, WindowContext>,
|
|
|
|
cli_options: CliOptions,
|
2022-08-31 22:48:38 +00:00
|
|
|
config: Rc<UiConfig>,
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Processor {
|
|
|
|
/// Create a new event processor.
|
|
|
|
///
|
|
|
|
/// Takes a writer which is expected to be hooked up to the write end of a PTY.
|
|
|
|
pub fn new(
|
2021-11-22 18:34:09 +00:00
|
|
|
config: UiConfig,
|
2021-10-23 07:16:47 +00:00
|
|
|
cli_options: CliOptions,
|
|
|
|
_event_loop: &EventLoop<Event>,
|
|
|
|
) -> Processor {
|
|
|
|
// Initialize Wayland event queue, to handle Wayland callbacks.
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
let wayland_event_queue = _event_loop.wayland_display().map(|display| {
|
|
|
|
let display = unsafe { WaylandDisplay::from_external_display(display as _) };
|
|
|
|
display.create_event_queue()
|
|
|
|
});
|
|
|
|
|
|
|
|
Processor {
|
|
|
|
windows: HashMap::new(),
|
2022-08-31 22:48:38 +00:00
|
|
|
config: Rc::new(config),
|
2021-10-23 07:16:47 +00:00
|
|
|
cli_options,
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
wayland_event_queue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-03 16:37:54 +00:00
|
|
|
/// Create initial window and load GL platform.
|
|
|
|
///
|
|
|
|
/// This will initialize the OpenGL Api and pick a config that
|
|
|
|
/// will be used for the rest of the windows.
|
|
|
|
pub fn create_initial_window(
|
|
|
|
&mut self,
|
|
|
|
event_loop: &EventLoopWindowTarget<Event>,
|
|
|
|
proxy: EventLoopProxy<Event>,
|
|
|
|
options: WindowOptions,
|
|
|
|
) -> Result<(), Box<dyn Error>> {
|
|
|
|
let window_context = WindowContext::initial(
|
|
|
|
event_loop,
|
|
|
|
proxy,
|
|
|
|
self.config.clone(),
|
|
|
|
options,
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
self.wayland_event_queue.as_ref(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
self.windows.insert(window_context.id(), window_context);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
/// Create a new terminal window.
|
|
|
|
pub fn create_window(
|
|
|
|
&mut self,
|
|
|
|
event_loop: &EventLoopWindowTarget<Event>,
|
|
|
|
proxy: EventLoopProxy<Event>,
|
2022-01-03 18:55:22 +00:00
|
|
|
options: WindowOptions,
|
2021-10-23 07:16:47 +00:00
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2022-11-03 16:37:54 +00:00
|
|
|
let window = self.windows.iter().next().as_ref().unwrap().1;
|
|
|
|
let window_context = window.additional(
|
2021-10-23 07:16:47 +00:00
|
|
|
event_loop,
|
|
|
|
proxy,
|
2022-11-03 16:37:54 +00:00
|
|
|
self.config.clone(),
|
|
|
|
options,
|
2021-10-23 07:16:47 +00:00
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
self.wayland_event_queue.as_ref(),
|
|
|
|
)?;
|
2022-11-03 16:37:54 +00:00
|
|
|
|
2021-10-23 07:16:47 +00:00
|
|
|
self.windows.insert(window_context.id(), window_context);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Run the event loop.
|
2022-08-10 12:48:46 +00:00
|
|
|
///
|
|
|
|
/// The result is exit code generate from the loop.
|
|
|
|
pub fn run(
|
|
|
|
&mut self,
|
|
|
|
mut event_loop: EventLoop<Event>,
|
|
|
|
initial_window_options: WindowOptions,
|
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2021-10-23 07:16:47 +00:00
|
|
|
let proxy = event_loop.create_proxy();
|
|
|
|
let mut scheduler = Scheduler::new(proxy.clone());
|
2022-08-10 12:48:46 +00:00
|
|
|
let mut initial_window_options = Some(initial_window_options);
|
2021-10-23 07:16:47 +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();
|
|
|
|
|
2022-08-10 12:48:46 +00:00
|
|
|
// Disable all device events, since we don't care about them.
|
|
|
|
event_loop.set_device_event_filter(DeviceEventFilter::Always);
|
|
|
|
|
|
|
|
let exit_code = event_loop.run_return(move |event, event_loop, control_flow| {
|
2021-11-22 18:34:09 +00:00
|
|
|
if self.config.debug.print_events {
|
2022-11-03 16:37:54 +00:00
|
|
|
info!("winit event: {:?}", event);
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore all events we do not care about.
|
|
|
|
if Self::skip_event(&event) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match event {
|
2022-08-10 12:48:46 +00:00
|
|
|
// The event loop just got initialized. Create a window.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::Resumed => {
|
2022-08-10 12:48:46 +00:00
|
|
|
// Creating window inside event loop is required for platforms like macOS to
|
|
|
|
// properly initialize state, like tab management. Othwerwise the first window
|
|
|
|
// won't handle tabs.
|
|
|
|
let initial_window_options = match initial_window_options.take() {
|
|
|
|
Some(initial_window_options) => initial_window_options,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
2022-11-03 16:37:54 +00:00
|
|
|
if let Err(err) = self.create_initial_window(
|
|
|
|
event_loop,
|
|
|
|
proxy.clone(),
|
|
|
|
initial_window_options,
|
|
|
|
) {
|
2022-08-10 12:48:46 +00:00
|
|
|
// Log the error right away since we can't return it.
|
|
|
|
eprintln!("Error: {}", err);
|
|
|
|
*control_flow = ControlFlow::ExitWithCode(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
info!("Initialisation complete");
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
// Check for shutdown.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(Event {
|
2021-10-23 07:16:47 +00:00
|
|
|
window_id: Some(window_id),
|
|
|
|
payload: EventType::Terminal(TerminalEvent::Exit),
|
|
|
|
}) => {
|
|
|
|
// Remove the closed terminal.
|
|
|
|
let window_context = match self.windows.remove(&window_id) {
|
|
|
|
Some(window_context) => window_context,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Unschedule pending events.
|
|
|
|
scheduler.unschedule_window(window_context.id());
|
|
|
|
|
|
|
|
// Shutdown if no more terminals are open.
|
|
|
|
if self.windows.is_empty() {
|
|
|
|
// Write ref tests of last window to disk.
|
2021-11-22 18:34:09 +00:00
|
|
|
if self.config.debug.ref_test {
|
2021-10-23 07:16:47 +00:00
|
|
|
window_context.write_ref_test_results();
|
|
|
|
}
|
|
|
|
|
|
|
|
*control_flow = ControlFlow::Exit;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Process all pending events.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::RedrawEventsCleared => {
|
2021-10-23 07:16:47 +00:00
|
|
|
// Check for pending frame callbacks on Wayland.
|
|
|
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
|
|
if let Some(wayland_event_queue) = self.wayland_event_queue.as_mut() {
|
|
|
|
wayland_event_queue
|
|
|
|
.dispatch_pending(&mut (), |_, _, _| {})
|
|
|
|
.expect("failed to dispatch wayland event queue");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dispatch event to all windows.
|
|
|
|
for window_context in self.windows.values_mut() {
|
|
|
|
window_context.handle_event(
|
|
|
|
event_loop,
|
|
|
|
&proxy,
|
|
|
|
&mut clipboard,
|
|
|
|
&mut scheduler,
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::RedrawEventsCleared,
|
2021-10-23 07:16:47 +00:00
|
|
|
);
|
|
|
|
}
|
2022-12-30 16:25:04 +00:00
|
|
|
|
|
|
|
// Update the scheduler after event processing to ensure
|
|
|
|
// the event loop deadline is as accurate as possible.
|
|
|
|
*control_flow = match scheduler.update() {
|
|
|
|
Some(instant) => ControlFlow::WaitUntil(instant),
|
|
|
|
None => ControlFlow::Wait,
|
|
|
|
};
|
2021-10-23 07:16:47 +00:00
|
|
|
},
|
|
|
|
// Process config update.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(Event { payload: EventType::ConfigReload(path), .. }) => {
|
2021-10-23 07:16:47 +00:00
|
|
|
// Clear config logs from message bar for all terminals.
|
|
|
|
for window_context in self.windows.values_mut() {
|
|
|
|
if !window_context.message_buffer.is_empty() {
|
|
|
|
window_context.message_buffer.remove_target(LOG_TARGET_CONFIG);
|
|
|
|
window_context.display.pending_update.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load config and update each terminal.
|
|
|
|
if let Ok(config) = config::reload(&path, &self.cli_options) {
|
2022-08-31 22:48:38 +00:00
|
|
|
self.config = Rc::new(config);
|
2021-10-23 07:16:47 +00:00
|
|
|
|
|
|
|
for window_context in self.windows.values_mut() {
|
2022-08-31 22:48:38 +00:00
|
|
|
window_context.update_config(self.config.clone());
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2022-08-31 22:48:38 +00:00
|
|
|
// Process IPC config update.
|
|
|
|
#[cfg(unix)]
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(Event {
|
2022-08-31 22:48:38 +00:00
|
|
|
payload: EventType::IpcConfig(ipc_config),
|
|
|
|
window_id,
|
|
|
|
}) => {
|
|
|
|
for (_, window_context) in self
|
|
|
|
.windows
|
|
|
|
.iter_mut()
|
|
|
|
.filter(|(id, _)| window_id.is_none() || window_id == Some(**id))
|
|
|
|
{
|
|
|
|
window_context.update_ipc_config(self.config.clone(), ipc_config.clone());
|
|
|
|
}
|
|
|
|
},
|
2021-10-23 07:16:47 +00:00
|
|
|
// Create a new terminal window.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(Event {
|
2021-11-22 18:34:09 +00:00
|
|
|
payload: EventType::CreateWindow(options), ..
|
|
|
|
}) => {
|
2022-06-29 19:01:32 +00:00
|
|
|
// XXX Ensure that no context is current when creating a new window, otherwise
|
|
|
|
// it may lock the backing buffer of the surface of current context when asking
|
|
|
|
// e.g. EGL on Wayland to create a new context.
|
|
|
|
for window_context in self.windows.values_mut() {
|
2022-11-03 16:37:54 +00:00
|
|
|
window_context.display.make_not_current();
|
2022-06-29 19:01:32 +00:00
|
|
|
}
|
|
|
|
|
2021-12-23 10:23:06 +00:00
|
|
|
if let Err(err) = self.create_window(event_loop, proxy.clone(), options) {
|
2021-10-23 07:16:47 +00:00
|
|
|
error!("Could not open window: {:?}", err);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Process events affecting all windows.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::UserEvent(event @ Event { window_id: None, .. }) => {
|
2021-10-23 07:16:47 +00:00
|
|
|
for window_context in self.windows.values_mut() {
|
|
|
|
window_context.handle_event(
|
|
|
|
event_loop,
|
|
|
|
&proxy,
|
|
|
|
&mut clipboard,
|
|
|
|
&mut scheduler,
|
|
|
|
event.clone().into(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
// Process window-specific events.
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::WindowEvent { window_id, .. }
|
|
|
|
| WinitEvent::UserEvent(Event { window_id: Some(window_id), .. })
|
|
|
|
| WinitEvent::RedrawRequested(window_id) => {
|
2021-10-23 07:16:47 +00:00
|
|
|
if let Some(window_context) = self.windows.get_mut(&window_id) {
|
|
|
|
window_context.handle_event(
|
|
|
|
event_loop,
|
|
|
|
&proxy,
|
|
|
|
&mut clipboard,
|
|
|
|
&mut scheduler,
|
|
|
|
event,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
});
|
2022-08-10 12:48:46 +00:00
|
|
|
|
|
|
|
if exit_code == 0 {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(format!("Event loop terminated with code: {}", exit_code).into())
|
|
|
|
}
|
2021-10-23 07:16:47 +00:00
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Check if an event is irrelevant and can be skipped.
|
2022-11-03 16:37:54 +00:00
|
|
|
fn skip_event(event: &WinitEvent<'_, Event>) -> bool {
|
2019-10-05 00:29:26 +00:00
|
|
|
match event {
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::NewEvents(StartCause::Init) => false,
|
|
|
|
WinitEvent::WindowEvent { event, .. } => matches!(
|
2020-08-06 01:09:46 +00:00
|
|
|
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::Moved(_)
|
|
|
|
),
|
2022-11-03 16:37:54 +00:00
|
|
|
WinitEvent::Suspended { .. }
|
|
|
|
| WinitEvent::NewEvents { .. }
|
|
|
|
| WinitEvent::MainEventsCleared
|
|
|
|
| WinitEvent::LoopDestroyed => true,
|
2019-10-05 00:29:26 +00:00
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2021-10-23 07:16:47 +00:00
|
|
|
pub struct EventProxy {
|
|
|
|
proxy: EventLoopProxy<Event>,
|
|
|
|
window_id: WindowId,
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
impl EventProxy {
|
2021-10-23 07:16:47 +00:00
|
|
|
pub fn new(proxy: EventLoopProxy<Event>, window_id: WindowId) -> Self {
|
|
|
|
Self { proxy, window_id }
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
2020-06-18 01:02:56 +00:00
|
|
|
|
|
|
|
/// Send an event to the event loop.
|
2021-10-23 07:16:47 +00:00
|
|
|
pub fn send_event(&self, event: EventType) {
|
|
|
|
let _ = self.proxy.send_event(Event::new(event, self.window_id));
|
2020-06-18 01:02:56 +00:00
|
|
|
}
|
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) {
|
2021-10-23 07:16:47 +00:00
|
|
|
let _ = self.proxy.send_event(Event::new(event.into(), self.window_id));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|