diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c80e89..b29c6c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Terminal escape bindings with combined modifiers - Bindings for ScrollToTop and ScrollToBottom actions - `ReceiveChar` key binding action to insert the key's text character -- Live reload font size from config - New CLI flag `--hold` for keeping Alacritty opened after its child process exits - Escape sequence to save and restore window title from stack - Alternate scroll escape sequence (`CSI ? 1007 h` / `CSI ? 1007 l`) - Print name of launch command if Alacritty failed to execute it +- Live reload font settings from config ### Changed diff --git a/alacritty.yml b/alacritty.yml index 5e9ca2af..fa40e0b1 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -99,7 +99,7 @@ # change the `it` value in terminfo when altering this setting. #tabspaces: 8 -# Font configuration (changes require restart) +# Font configuration #font: # Normal (roman) font face #normal: diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index ff91852f..07810ecd 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -14,7 +14,6 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. -use std::cmp::max; use std::f64; use std::fmt; use std::time::Instant; @@ -24,9 +23,9 @@ use glutin::event_loop::EventLoop; use log::{debug, info}; use parking_lot::MutexGuard; -use font::{self, Rasterize, Size}; +use font::{self, Rasterize}; -use alacritty_terminal::config::StartupMode; +use alacritty_terminal::config::{Font, StartupMode}; use alacritty_terminal::event::{Event, OnResize}; use alacritty_terminal::index::Line; use alacritty_terminal::message_bar::MessageBuffer; @@ -37,12 +36,9 @@ use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{RenderableCell, SizeInfo, Term}; use crate::config::Config; -use crate::event::{FontResize, Resize}; +use crate::event::DisplayUpdate; use crate::window::{self, Window}; -/// Font size change interval -pub const FONT_SIZE_STEP: f32 = 0.5; - #[derive(Debug)] pub enum Error { /// Error with window management @@ -116,7 +112,6 @@ impl From for Error { /// The display wraps a window, font rasterizer, and GPU renderer pub struct Display { pub size_info: SizeInfo, - pub font_size: Size, pub window: Window, renderer: QuadRenderer, @@ -228,14 +223,7 @@ impl Display { _ => (), } - Ok(Display { - window, - renderer, - glyph_cache, - meter: Meter::new(), - size_info, - font_size: config.font.size, - }) + Ok(Display { window, renderer, glyph_cache, meter: Meter::new(), size_info }) } fn new_glyph_cache( @@ -270,12 +258,10 @@ impl Display { } /// Update font size and cell dimensions - fn update_glyph_cache(&mut self, config: &Config, size: Size) { + fn update_glyph_cache(&mut self, config: &Config, font: Font) { let size_info = &mut self.size_info; let cache = &mut self.glyph_cache; - let font = config.font.clone().with_size(size); - self.renderer.with_loader(|mut api| { let _ = cache.update_font_size(font, size_info.dpr, &mut api); }); @@ -286,27 +272,22 @@ impl Display { size_info.cell_height = cell_height; } - /// Process resize events - pub fn handle_resize( + /// Process update events + pub fn handle_update( &mut self, terminal: &mut Term, pty_resize_handle: &mut dyn OnResize, message_buffer: &MessageBuffer, config: &Config, - resize_pending: Resize, + update_pending: DisplayUpdate, ) { // Update font size and cell dimensions - if let Some(resize) = resize_pending.font_size { - self.font_size = match resize { - FontResize::Delta(delta) => max(self.font_size + delta, FONT_SIZE_STEP.into()), - FontResize::Reset => config.font.size, - }; - - self.update_glyph_cache(config, self.font_size); + if let Some(font) = update_pending.font { + self.update_glyph_cache(config, font); } // Update the window dimensions - if let Some(size) = resize_pending.dimensions { + if let Some(size) = update_pending.dimensions { self.size_info.width = size.width as f32; self.size_info.height = size.height as f32; } diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 225dc2b5..75e06f30 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -1,5 +1,6 @@ //! Process window events use std::borrow::Cow; +use std::cmp::max; use std::env; #[cfg(unix)] use std::fs; @@ -20,6 +21,7 @@ use serde_json as json; use font::Size; use alacritty_terminal::clipboard::ClipboardType; +use alacritty_terminal::config::Font; use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::event::OnResize; use alacritty_terminal::event::{Event, EventListener, Notify}; @@ -36,25 +38,19 @@ use alacritty_terminal::util::{limit, start_daemon}; use crate::config; use crate::config::Config; use crate::display::Display; -use crate::input::{self, ActionContext as _, Modifiers}; +use crate::input::{self, ActionContext as _, Modifiers, FONT_SIZE_STEP}; use crate::window::Window; -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum FontResize { - Delta(f32), - Reset, -} - -#[derive(Default, Copy, Clone, Debug, PartialEq)] -pub struct Resize { +#[derive(Default, Clone, Debug, PartialEq)] +pub struct DisplayUpdate { pub dimensions: Option, pub message_buffer: Option<()>, - pub font_size: Option, + pub font: Option, } -impl Resize { +impl DisplayUpdate { fn is_empty(&self) -> bool { - self.dimensions.is_none() && self.font_size.is_none() && self.message_buffer.is_none() + self.dimensions.is_none() && self.font.is_none() && self.message_buffer.is_none() } } @@ -68,8 +64,9 @@ pub struct ActionContext<'a, N, T> { pub modifiers: &'a mut Modifiers, pub window: &'a mut Window, pub message_buffer: &'a mut MessageBuffer, - pub resize_pending: &'a mut Resize, - pub font_size: &'a Size, + pub display_update_pending: &'a mut DisplayUpdate, + pub config: &'a mut Config, + font_size: &'a mut Size, } impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionContext<'a, N, T> { @@ -227,23 +224,30 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } fn change_font_size(&mut self, delta: f32) { - self.resize_pending.font_size = Some(FontResize::Delta(delta)); + *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); + let font = self.config.font.clone().with_size(*self.font_size); + self.display_update_pending.font = Some(font); self.terminal.dirty = true; } fn reset_font_size(&mut self) { - self.resize_pending.font_size = Some(FontResize::Reset); + *self.font_size = self.config.font.size; + self.display_update_pending.font = Some(self.config.font.clone()); self.terminal.dirty = true; } fn pop_message(&mut self) { - self.resize_pending.message_buffer = Some(()); + self.display_update_pending.message_buffer = Some(()); self.message_buffer.pop(); } fn message(&self) -> Option<&Message> { self.message_buffer.message() } + + fn config(&self) -> &Config { + self.config + } } pub enum ClickState { @@ -306,6 +310,7 @@ pub struct Processor { pty_resize_handle: Box, message_buffer: MessageBuffer, display: Display, + font_size: Size, } impl Processor { @@ -326,6 +331,7 @@ impl Processor { received_count: 0, suppress_chars: false, modifiers: Default::default(), + font_size: config.font.size, config, pty_resize_handle, message_buffer, @@ -374,7 +380,7 @@ impl Processor { let mut terminal = terminal.lock(); - let mut resize_pending = Resize::default(); + let mut display_update_pending = DisplayUpdate::default(); let context = ActionContext { terminal: &mut terminal, @@ -385,11 +391,12 @@ impl Processor { suppress_chars: &mut self.suppress_chars, modifiers: &mut self.modifiers, message_buffer: &mut self.message_buffer, - resize_pending: &mut resize_pending, + display_update_pending: &mut display_update_pending, window: &mut self.display.window, - font_size: &self.display.font_size, + font_size: &mut self.font_size, + config: &mut self.config, }; - let mut processor = input::Processor::new(context, &mut self.config); + let mut processor = input::Processor::new(context); for event in event_queue.drain(..) { Processor::handle_event(event, &mut processor); @@ -407,21 +414,21 @@ impl Processor { let size = self.display.window.inner_size().to_physical(dpr); - resize_pending.font_size = Some(FontResize::Delta(0.)); - resize_pending.dimensions = Some(size); + display_update_pending.font = Some(self.config.font.clone()); + display_update_pending.dimensions = Some(size); terminal.dirty = true; } } - // Process resize events - if !resize_pending.is_empty() { - self.display.handle_resize( + // Process DisplayUpdate events + if !display_update_pending.is_empty() { + self.display.handle_update( &mut terminal, self.pty_resize_handle.as_mut(), &self.message_buffer, &self.config, - resize_pending, + display_update_pending, ); } @@ -460,23 +467,29 @@ impl Processor { }, Event::ConfigReload(path) => { processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG); - processor.ctx.resize_pending.message_buffer = Some(()); + processor.ctx.display_update_pending.message_buffer = Some(()); if let Ok(config) = config::reload_from(&path) { processor.ctx.terminal.update_config(&config); - if *processor.ctx.font_size == processor.config.font.size { - processor.ctx.resize_pending.font_size = Some(FontResize::Reset); + if processor.ctx.config.font != config.font { + // Do not update font size if it has been changed at runtime + if *processor.ctx.font_size == processor.ctx.config.font.size { + *processor.ctx.font_size = config.font.size; + } + + let font = config.font.clone().with_size(*processor.ctx.font_size); + processor.ctx.display_update_pending.font = Some(font); } - *processor.config = config; + *processor.ctx.config = config; processor.ctx.terminal.dirty = true; } }, Event::Message(message) => { processor.ctx.message_buffer.push(message); - processor.ctx.resize_pending.message_buffer = Some(()); + processor.ctx.display_update_pending.message_buffer = Some(()); processor.ctx.terminal.dirty = true; }, Event::MouseCursorDirty => processor.reset_mouse_cursor(), @@ -488,14 +501,14 @@ impl Processor { CloseRequested => processor.ctx.terminal.exit(), Resized(lsize) => { let psize = lsize.to_physical(processor.ctx.size_info.dpr); - processor.ctx.resize_pending.dimensions = Some(psize); + processor.ctx.display_update_pending.dimensions = Some(psize); processor.ctx.terminal.dirty = true; }, KeyboardInput { input, .. } => { processor.process_key(input); if input.state == ElementState::Pressed { // Hide cursor while typing - if processor.config.ui_config.mouse.hide_when_typing { + if processor.ctx.config.ui_config.mouse.hide_when_typing { processor.ctx.window.set_mouse_visible(false); } } @@ -540,17 +553,18 @@ impl Processor { }, HiDpiFactorChanged(dpr) => { let dpr_change = (dpr / processor.ctx.size_info.dpr) as f32; - let resize_pending = &mut processor.ctx.resize_pending; + let display_update_pending = &mut processor.ctx.display_update_pending; // Push current font to update its DPR - resize_pending.font_size = Some(FontResize::Delta(0.)); + display_update_pending.font = Some(processor.ctx.config.font.clone()); // Scale window dimensions with new DPR let old_width = processor.ctx.size_info.width; let old_height = processor.ctx.size_info.height; - let dimensions = resize_pending.dimensions.get_or_insert_with(|| { - PhysicalSize::new(f64::from(old_width), f64::from(old_height)) - }); + let dimensions = + display_update_pending.dimensions.get_or_insert_with(|| { + PhysicalSize::new(f64::from(old_width), f64::from(old_height)) + }); dimensions.width *= f64::from(dpr_change); dimensions.height *= f64::from(dpr_change); diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index b9b99565..5404085e 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -42,17 +42,18 @@ use alacritty_terminal::url::Url; use alacritty_terminal::util::start_daemon; use crate::config::{Action, Binding, Config, Key, RelaxedEq}; -use crate::display::FONT_SIZE_STEP; use crate::event::{ClickState, Mouse}; use crate::window::Window; +/// Font size change interval +pub const FONT_SIZE_STEP: f32 = 0.5; + /// Processes input from glutin. /// /// An escape sequence may be emitted in case specific keys or key combinations /// are activated. -pub struct Processor<'a, T: EventListener, A: ActionContext + 'a> { +pub struct Processor> { pub ctx: A, - pub config: &'a mut Config, _phantom: PhantomData, } @@ -83,6 +84,7 @@ pub trait ActionContext { fn reset_font_size(&mut self); fn pop_message(&mut self); fn message(&self) -> Option<&Message>; + fn config(&self) -> &Config; } #[derive(Debug, Default, Copy, Clone)] @@ -226,9 +228,9 @@ impl From for CursorIcon { } } -impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { - pub fn new(ctx: A, config: &'a mut Config) -> Self { - Self { ctx, config, _phantom: Default::default() } +impl> Processor { + pub fn new(ctx: A) -> Self { + Self { ctx, _phantom: Default::default() } } fn mouse_state(&mut self, point: Point, mods: ModifiersState) -> MouseState { @@ -245,9 +247,9 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { } // Check for URL at point with required modifiers held - if self.config.ui_config.mouse.url.mods().relaxed_eq(mods) + if self.ctx.config().ui_config.mouse.url.mods().relaxed_eq(mods) && (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift) - && self.config.ui_config.mouse.url.launcher.is_some() + && self.ctx.config().ui_config.mouse.url.launcher.is_some() && self.ctx.selection_is_empty() && self.ctx.mouse().left_button_state != ElementState::Pressed { @@ -426,7 +428,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state { ClickState::Click if !button_changed - && elapsed < self.config.ui_config.mouse.double_click.threshold => + && elapsed < self.ctx.config().ui_config.mouse.double_click.threshold => { self.ctx.mouse_mut().block_url_launcher = true; self.on_mouse_double_click(button, point); @@ -434,7 +436,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { } ClickState::DoubleClick if !button_changed - && elapsed < self.config.ui_config.mouse.triple_click.threshold => + && elapsed < self.ctx.config().ui_config.mouse.triple_click.threshold => { self.ctx.mouse_mut().block_url_launcher = true; self.on_mouse_triple_click(button, point); @@ -512,7 +514,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { return; } - if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher { + if let Some(ref launcher) = self.ctx.config().ui_config.mouse.url.launcher { let mut args = launcher.args().to_vec(); args.push(self.ctx.terminal().url_to_string(url)); @@ -566,10 +568,11 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { } } else if self.ctx.terminal().mode().contains(alt_scroll_modes) && !modifiers.shift { let multiplier = i32::from( - self.config + self.ctx + .config() .scrolling .faux_multiplier() - .unwrap_or_else(|| self.config.scrolling.multiplier()), + .unwrap_or_else(|| self.ctx.config().scrolling.multiplier()), ); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; @@ -584,7 +587,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { } self.ctx.write_to_pty(content); } else { - let multiplier = i32::from(self.config.scrolling.multiplier()); + let multiplier = i32::from(self.ctx.config().scrolling.multiplier()); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let lines = self.ctx.mouse().scroll_px / height; @@ -677,7 +680,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { c.encode_utf8(&mut bytes[..]); } - if self.config.alt_send_esc() + if self.ctx.config().alt_send_esc() && *self.ctx.received_count() == 0 && self.ctx.modifiers().alt() && utf8_len == 1 @@ -698,7 +701,9 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { fn process_key_bindings(&mut self, input: KeyboardInput) { let mut suppress_chars = None; - for binding in &self.config.ui_config.key_bindings { + for i in 0..self.ctx.config().ui_config.key_bindings.len() { + let binding = &self.ctx.config().ui_config.key_bindings[i]; + let key = match (binding.trigger, input.virtual_keycode) { (Key::Scancode(_), _) => Key::Scancode(input.scancode), (_, Some(key)) => Key::from_glutin_input(key), @@ -707,6 +712,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { if binding.is_triggered_by(*self.ctx.terminal().mode(), input.modifiers, &key, false) { // Binding was triggered; run the action + let binding = binding.clone(); binding.execute(&mut self.ctx, false); // Don't suppress when there has been a `ReceiveChar` action @@ -723,7 +729,9 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { /// The provided mode, mods, and key must match what is allowed by a binding /// for its action to be executed. fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) { - for binding in &self.config.ui_config.mouse_bindings { + for i in 0..self.ctx.config().ui_config.mouse_bindings.len() { + let binding = &self.ctx.config().ui_config.mouse_bindings[i]; + if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) { // binding was triggered; run the action let mouse_mode = !mods.shift @@ -732,6 +740,8 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION, ); + + let binding = binding.clone(); binding.execute(&mut self.ctx, mouse_mode); } } @@ -780,7 +790,7 @@ impl<'a, T: EventListener, A: ActionContext + 'a> Processor<'a, T, A> { /// Copy text selection. fn copy_selection(&mut self) { - if self.config.selection.save_to_clipboard { + if self.ctx.config().selection.save_to_clipboard { self.ctx.copy_selection(ClipboardType::Clipboard); } self.ctx.copy_selection(ClipboardType::Selection); @@ -849,6 +859,7 @@ mod tests { pub received_count: usize, pub suppress_chars: bool, pub modifiers: Modifiers, + config: &'a Config, } impl<'a, T: EventListener> super::ActionContext for ActionContext<'a, T> { @@ -947,6 +958,10 @@ mod tests { fn message(&self) -> Option<&Message> { self.message_buffer.message() } + + fn config(&self) -> &Config { + self.config + } } macro_rules! test_clickstate { @@ -1002,9 +1017,10 @@ mod tests { suppress_chars: false, modifiers: Modifiers::default(), message_buffer: &mut message_buffer, + config: &cfg, }; - let mut processor = Processor::new(context, &mut cfg); + let mut processor = Processor::new(context); if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input { processor.mouse_input(state, button, modifiers);