diff --git a/Cargo.lock b/Cargo.lock index 27059cc..ca1907f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,7 @@ dependencies = [ "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "urlocator 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "x11-dl 2.18.4 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -69,7 +70,6 @@ dependencies = [ "notify 4.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rfind_url 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -627,7 +627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -699,7 +699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -770,7 +770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "getrandom" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -970,7 +970,7 @@ name = "jobserver" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1578,7 +1578,7 @@ name = "rand" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1621,7 +1621,7 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1786,11 +1786,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rfind_url" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "rust-argon2" version = "0.5.1" @@ -1917,7 +1912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2075,7 +2070,7 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2090,7 +2085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2217,6 +2212,11 @@ dependencies = [ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "urlocator" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "utf8-ranges" version = "1.0.4" @@ -2405,7 +2405,7 @@ dependencies = [ [[package]] name = "winit" version = "0.20.0-alpha4" -source = "git+https://github.com/rust-windowing/winit#6608a0241d7fa02acaf56ff1c63e2126edb73d9a" +source = "git+https://github.com/rust-windowing/winit#05a1f4280c357c8080272f4fe55e30e9f1469eef" dependencies = [ "android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2609,7 +2609,7 @@ dependencies = [ "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" +"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)" = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" "checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" @@ -2724,7 +2724,6 @@ dependencies = [ "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rfind_url 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9f29ac4039e039858e04a3ed023c2b2d100768311372fa09ab0b1550f297d6" "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_tools_util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b725dadae9fabc488df69a287f5a99c5eaf5d10853842a8a3dfac52476f544ee" @@ -2759,7 +2758,7 @@ dependencies = [ "checksum stb_truetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "824210d6fb52cbc3ad2545270ead6860c7311aa5450642b078da4515937b6f7a" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum syn 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ebead3e516ca7fe682c71c3f235bf5b7d9e73268df8111c6edd9eb44091f2ebb" "checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" @@ -2776,6 +2775,7 @@ dependencies = [ "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" +"checksum urlocator 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80aaa156652b4f93faf4f77d5646c3195dbe578927d8c6481b673d33b5be1700" "checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 7d49fc6..b47ff29 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -23,6 +23,7 @@ libc = "0.2" unicode-width = "0.1" parking_lot = "0.9" font = { path = "../font" } +urlocator = "0.1.0" [build-dependencies] rustc_tools_util = "0.2.0" diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 07810ec..53a5f47 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -19,7 +19,9 @@ use std::fmt; use std::time::Instant; use glutin::dpi::{PhysicalPosition, PhysicalSize}; +use glutin::event::ModifiersState; use glutin::event_loop::EventLoop; +use glutin::window::CursorIcon; use log::{debug, info}; use parking_lot::MutexGuard; @@ -32,11 +34,13 @@ use alacritty_terminal::message_bar::MessageBuffer; use alacritty_terminal::meter::Meter; use alacritty_terminal::renderer::rects::{RenderLines, RenderRect}; use alacritty_terminal::renderer::{self, GlyphCache, QuadRenderer}; +use alacritty_terminal::selection::Selection; use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::{RenderableCell, SizeInfo, Term}; +use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode}; use crate::config::Config; -use crate::event::DisplayUpdate; +use crate::event::{DisplayUpdate, Mouse}; +use crate::url::{Url, Urls}; use crate::window::{self, Window}; #[derive(Debug)] @@ -113,6 +117,10 @@ impl From for Error { pub struct Display { pub size_info: SizeInfo, pub window: Window, + pub urls: Urls, + + /// Currently highlighted URL. + pub highlighted_url: Option, renderer: QuadRenderer, glyph_cache: GlyphCache, @@ -223,7 +231,15 @@ impl Display { _ => (), } - Ok(Display { window, renderer, glyph_cache, meter: Meter::new(), size_info }) + Ok(Display { + window, + renderer, + glyph_cache, + meter: Meter::new(), + size_info, + urls: Urls::new(), + highlighted_url: None, + }) } fn new_glyph_cache( @@ -341,6 +357,8 @@ impl Display { terminal: MutexGuard<'_, Term>, message_buffer: &MessageBuffer, config: &Config, + mouse: &Mouse, + mods: ModifiersState, ) { let grid_cells: Vec = terminal.renderable_cells(config).collect(); let visual_bell_intensity = terminal.visual_bell.intensity(); @@ -349,6 +367,9 @@ impl Display { let glyph_cache = &mut self.glyph_cache; let size_info = self.size_info; + let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true); + let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE); + // Update IME position #[cfg(not(windows))] self.window.update_ime_position(&terminal, &self.size_info); @@ -361,6 +382,7 @@ impl Display { }); let mut lines = RenderLines::new(); + let mut urls = Urls::new(); // Draw grid { @@ -369,6 +391,9 @@ impl Display { self.renderer.with_api(&config, &size_info, |mut api| { // Iterate over all non-empty cells in the grid for cell in grid_cells { + // Update URL underlines + urls.update(size_info.cols().0, cell); + // Update underline/strikeout lines.update(cell); @@ -378,9 +403,27 @@ impl Display { }); } - let mut rects = lines.into_rects(&metrics, &size_info); + let mut rects = lines.rects(&metrics, &size_info); - // Push visual bell after underline/strikeout rects + // Update visible URLs + self.urls = urls; + if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) { + rects.append(&mut url.rects(&metrics, &size_info)); + + self.window.set_mouse_cursor(CursorIcon::Hand); + + self.highlighted_url = Some(url); + } else if self.highlighted_url.is_some() { + self.highlighted_url = None; + + if mouse_mode { + self.window.set_mouse_cursor(CursorIcon::Default); + } else { + self.window.set_mouse_cursor(CursorIcon::Text); + } + } + + // Push visual bell after url/underline/strikeout rects if visual_bell_intensity != 0. { let visual_bell_rect = RenderRect::new( 0., @@ -398,7 +441,7 @@ impl Display { // Create a new rectangle for the background let start_line = size_info.lines().0 - text.len(); - let y = size_info.padding_y + size_info.cell_height * start_line as f32; + let y = size_info.cell_height.mul_add(start_line as f32, size_info.padding_y); let message_bar_rect = RenderRect::new(0., y, size_info.width, size_info.height - y, message.color(), 1.); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 75e06f3..5a48620 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -146,7 +146,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon let x = self.mouse.x as usize; let y = self.mouse.y as usize; - if self.size_info.contains_point(x, y, true) { + if self.size_info.contains_point(x, y) { Some(self.size_info.pixels_to_coords(x, y)) } else { None @@ -273,6 +273,7 @@ pub struct Mouse { pub lines_scrolled: f32, pub block_url_launcher: bool, pub last_button: MouseButton, + pub inside_grid: bool, } impl Default for Mouse { @@ -292,6 +293,7 @@ impl Default for Mouse { lines_scrolled: 0.0, block_url_launcher: false, last_button: MouseButton::Other(0), + inside_grid: false, } } } @@ -396,7 +398,11 @@ impl Processor { font_size: &mut self.font_size, config: &mut self.config, }; - let mut processor = input::Processor::new(context); + let mut processor = input::Processor::new( + context, + &self.display.urls, + &self.display.highlighted_url, + ); for event in event_queue.drain(..) { Processor::handle_event(event, &mut processor); @@ -441,7 +447,13 @@ impl Processor { } // Redraw screen - self.display.draw(terminal, &self.message_buffer, &self.config); + self.display.draw( + terminal, + &self.message_buffer, + &self.config, + &self.mouse, + self.modifiers.into(), + ); } }); @@ -572,9 +584,15 @@ impl Processor { processor.ctx.size_info.dpr = dpr; }, RedrawRequested => processor.ctx.terminal.dirty = true, + CursorLeft { .. } => { + processor.ctx.mouse.inside_grid = false; + + if processor.highlighted_url.is_some() { + processor.ctx.terminal.dirty = true; + } + }, TouchpadPressure { .. } | CursorEntered { .. } - | CursorLeft { .. } | AxisMotion { .. } | HoveredFileCancelled | Destroyed @@ -603,7 +621,6 @@ impl Processor { match event { TouchpadPressure { .. } | CursorEntered { .. } - | CursorLeft { .. } | AxisMotion { .. } | HoveredFileCancelled | Destroyed diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 5404085..a8b2320 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -19,9 +19,10 @@ //! needs to be tracked. Additionally, we need a bit of a state machine to //! determine what to do when a non-modifier key is pressed. use std::borrow::Cow; +use std::cmp::min; use std::marker::PhantomData; -use std::mem; use std::time::Instant; +use std::cmp::Ordering; use glutin::event::{ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, @@ -36,13 +37,14 @@ use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Column, Line, Point, Side}; use alacritty_terminal::message_bar::{self, Message}; +use alacritty_terminal::selection::Selection; use alacritty_terminal::term::mode::TermMode; use alacritty_terminal::term::{SizeInfo, Term}; -use alacritty_terminal::url::Url; use alacritty_terminal::util::start_daemon; -use crate::config::{Action, Binding, Config, Key, RelaxedEq}; +use crate::config::{Action, Binding, Config, Key}; use crate::event::{ClickState, Mouse}; +use crate::url::{Url, Urls}; use crate::window::Window; /// Font size change interval @@ -52,8 +54,10 @@ pub const FONT_SIZE_STEP: f32 = 0.5; /// /// An escape sequence may be emitted in case specific keys or key combinations /// are activated. -pub struct Processor> { +pub struct Processor<'a, T: EventListener, A: ActionContext> { pub ctx: A, + pub urls: &'a Urls, + pub highlighted_url: &'a Option, _phantom: PhantomData, } @@ -89,42 +93,59 @@ pub trait ActionContext { #[derive(Debug, Default, Copy, Clone)] pub struct Modifiers { - mods: ModifiersState, + glutin_mods: ModifiersState, lshift: bool, rshift: bool, + lalt: bool, + ralt: bool, + lctrl: bool, + rctrl: bool, } impl Modifiers { - pub fn update(&mut self, input: KeyboardInput) { + pub fn update_keys(&mut self, input: KeyboardInput) { match input.virtual_keycode { Some(VirtualKeyCode::LShift) => self.lshift = input.state == ElementState::Pressed, Some(VirtualKeyCode::RShift) => self.rshift = input.state == ElementState::Pressed, + Some(VirtualKeyCode::LAlt) => self.lalt = input.state == ElementState::Pressed, + Some(VirtualKeyCode::RAlt) => self.ralt = input.state == ElementState::Pressed, + Some(VirtualKeyCode::LControl) => self.lctrl = input.state == ElementState::Pressed, + Some(VirtualKeyCode::RControl) => self.rctrl = input.state == ElementState::Pressed, _ => (), } - self.mods = input.modifiers; + self.update_mods(input.modifiers); + } + + pub fn update_mods(&mut self, mods: ModifiersState) { + self.glutin_mods = mods; } pub fn shift(self) -> bool { - self.lshift || self.rshift + self.glutin_mods.shift || self.lshift || self.rshift } pub fn ctrl(self) -> bool { - self.mods.ctrl + self.glutin_mods.ctrl || self.lctrl || self.rctrl } pub fn logo(self) -> bool { - self.mods.logo + self.glutin_mods.logo } pub fn alt(self) -> bool { - self.mods.alt + self.glutin_mods.alt || self.lalt || self.ralt } } -impl From<&mut Modifiers> for ModifiersState { - fn from(mods: &mut Modifiers) -> ModifiersState { - ModifiersState { shift: mods.shift(), ..mods.mods } +impl From for ModifiersState { + fn from(mods: Modifiers) -> ModifiersState { + ModifiersState { + shift: mods.shift(), + ctrl: mods.ctrl(), + logo: mods.logo(), + alt: mods.alt(), + } } } @@ -209,7 +230,7 @@ fn paste>(ctx: &mut A, contents: &str) { } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum MouseState { Url(Url), MessageBar, @@ -228,40 +249,38 @@ impl From for CursorIcon { } } -impl> Processor { - pub fn new(ctx: A) -> Self { - Self { ctx, _phantom: Default::default() } +impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { + pub fn new( + ctx: A, + urls: &'a Urls, + highlighted_url: &'a Option, + ) -> Self { + Self { ctx, urls, highlighted_url, _phantom: Default::default() } } - fn mouse_state(&mut self, point: Point, mods: ModifiersState) -> MouseState { - let mouse_mode = - TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; - + fn mouse_state(&mut self, mods: ModifiersState) -> MouseState { // Check message bar before URL to ignore URLs in the message bar - if let Some(message) = self.message_at_point(Some(point)) { - if self.message_close_at_point(point, message) { - return MouseState::MessageBarButton; - } else { - return MouseState::MessageBar; - } + if self.message_close_at_cursor() { + return MouseState::MessageBarButton; + } else if self.message_at_cursor() { + return MouseState::MessageBar; } - // Check for URL at point with required modifiers held - if self.ctx.config().ui_config.mouse.url.mods().relaxed_eq(mods) - && (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift) - && self.ctx.config().ui_config.mouse.url.launcher.is_some() - && self.ctx.selection_is_empty() - && self.ctx.mouse().left_button_state != ElementState::Pressed + // Check for URL at mouse cursor + let selection = + !self.ctx.terminal().selection().as_ref().map(Selection::is_empty).unwrap_or(true); + let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE); + let highlighted_url = + self.urls.highlighted(self.ctx.config(), self.ctx.mouse(), mods, mouse_mode, selection); + + if let Some(url) = highlighted_url { + return MouseState::Url(url); + } + + // Check mouse mode if location is not special + if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) + && !self.ctx.modifiers().shift() { - let buffer_point = self.ctx.terminal().visible_to_buffer(point); - if let Some(url) = - self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point)) - { - return MouseState::Url(url); - } - } - - if self.ctx.terminal().mode().intersects(mouse_mode) && !self.ctx.modifiers().shift() { MouseState::Mouse } else { MouseState::Text @@ -270,52 +289,53 @@ impl> Processor { #[inline] pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) { + self.ctx.modifiers().update_mods(modifiers); + + let size_info = self.ctx.size_info(); + self.ctx.mouse_mut().x = x; self.ctx.mouse_mut().y = y; - let size_info = self.ctx.size_info(); + let inside_grid = size_info.contains_point(x, y); let point = size_info.pixels_to_coords(x, y); - let cell_side = self.get_mouse_side(); - let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side); - let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line); - let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col); - - let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; - let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; let cell_changed = - prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column; + point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column; // If the mouse hasn't changed cells, do nothing - if !cell_changed && prev_side == cell_side { + if !cell_changed + && self.ctx.mouse().cell_side == cell_side + && self.ctx.mouse().inside_grid == inside_grid + { return; } + self.ctx.mouse_mut().inside_grid = inside_grid; + self.ctx.mouse_mut().cell_side = cell_side; + self.ctx.mouse_mut().line = point.line; + self.ctx.mouse_mut().column = point.col; + // Don't launch URLs if mouse has moved self.ctx.mouse_mut().block_url_launcher = true; - let mouse_state = self.mouse_state(point, modifiers); - self.update_mouse_cursor(mouse_state); - match mouse_state { - MouseState::Url(url) => { - let url_bounds = url.linear_bounds(self.ctx.terminal()); - self.ctx.terminal_mut().set_url_highlight(url_bounds); - }, - MouseState::MessageBar | MouseState::MessageBarButton => { - self.ctx.terminal_mut().reset_url_highlight(); - return; - }, - _ => self.ctx.terminal_mut().reset_url_highlight(), - } + // Update mouse state and check for URL change + let mouse_state = self.mouse_state(modifiers); + self.update_url_state(&mouse_state); + self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); + let last_term_line = self.ctx.terminal().grid().num_lines() - 1; if self.ctx.mouse().left_button_state == ElementState::Pressed - && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) + && (modifiers.shift || !self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE)) { - self.ctx.update_selection(Point { line: point.line, col: point.col }, cell_side); - } else if self.ctx.terminal().mode().intersects(motion_mode) - && size_info.contains_point(x, y, false) + // Treat motion over message bar like motion over the last line + let line = min(point.line, last_term_line); + + self.ctx.update_selection(Point { line, col: point.col }, cell_side); + } else if inside_grid && cell_changed + && point.line <= last_term_line + && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { if self.ctx.mouse().left_button_state == ElementState::Pressed { self.mouse_report(32, ElementState::Pressed, modifiers); @@ -401,30 +421,32 @@ impl> Processor { } } - pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option) { - if let (Some(point), true) = (point, button == MouseButton::Left) { + pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) { + if button == MouseButton::Left { self.ctx.semantic_selection(point); } } - pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Option) { - if let (Some(point), true) = (point, button == MouseButton::Left) { + pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) { + if button == MouseButton::Left { self.ctx.line_selection(point); } } - pub fn on_mouse_press( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option, - ) { + pub fn on_mouse_press(&mut self, button: MouseButton, modifiers: ModifiersState) { + self.ctx.modifiers().update_mods(modifiers); + let now = Instant::now(); let elapsed = self.ctx.mouse().last_click_timestamp.elapsed(); self.ctx.mouse_mut().last_click_timestamp = now; let button_changed = self.ctx.mouse().last_button != button; + // Load mouse point, treating message bar and padding as closest cell + let mouse = self.ctx.mouse(); + let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y); + point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1); + self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state { ClickState::Click if !button_changed @@ -450,17 +472,13 @@ impl> Processor { // Start new empty selection let side = self.ctx.mouse().cell_side; - if let Some(point) = point { - if modifiers.ctrl { - self.ctx.block_selection(point, side); - } else { - self.ctx.simple_selection(point, side); - } + if modifiers.ctrl { + self.ctx.block_selection(point, side); + } else { + self.ctx.simple_selection(point, side); } - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { + if !modifiers.shift && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) { let code = match button { MouseButton::Left => 0, MouseButton::Middle => 1, @@ -477,15 +495,8 @@ impl> Processor { }; } - pub fn on_mouse_release( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option, - ) { - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { + pub fn on_mouse_release(&mut self, button: MouseButton, modifiers: ModifiersState) { + if !modifiers.shift && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) { let code = match button { MouseButton::Left => 0, MouseButton::Middle => 1, @@ -495,14 +506,10 @@ impl> Processor { }; self.mouse_report(code, ElementState::Released, modifiers); return; - } else if let (Some(point), true) = (point, button == MouseButton::Left) { - let mouse_state = self.mouse_state(point, modifiers); - self.update_mouse_cursor(mouse_state); - if let MouseState::Url(url) = mouse_state { - let url_bounds = url.linear_bounds(self.ctx.terminal()); - self.ctx.terminal_mut().set_url_highlight(url_bounds); - self.launch_url(url); - } + } else if let (MouseButton::Left, MouseState::Url(url)) = + (button, self.mouse_state(modifiers)) + { + self.launch_url(url); } self.copy_selection(); @@ -516,7 +523,9 @@ impl> Processor { 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)); + let start = self.ctx.terminal().visible_to_buffer(url.start()); + let end = self.ctx.terminal().visible_to_buffer(url.end()); + args.push(self.ctx.terminal().bounds_to_string(start, end)); match start_daemon(launcher.program(), &args) { Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), @@ -552,12 +561,9 @@ impl> Processor { } fn scroll_terminal(&mut self, modifiers: ModifiersState, new_scroll_px: i32) { - let mouse_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - let alt_scroll_modes = TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL; let height = self.ctx.size_info().cell_height as i32; - if self.ctx.terminal().mode().intersects(mouse_modes) { + if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) { self.ctx.mouse_mut().scroll_px += new_scroll_px; let code = if new_scroll_px > 0 { 64 } else { 65 }; @@ -566,7 +572,13 @@ impl> Processor { for _ in 0..lines { self.mouse_report(code, ElementState::Pressed, modifiers); } - } else if self.ctx.terminal().mode().contains(alt_scroll_modes) && !modifiers.shift { + } else if self + .ctx + .terminal() + .mode() + .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) + && !modifiers.shift + { let multiplier = i32::from( self.ctx .config() @@ -620,20 +632,35 @@ impl> Processor { _ => (), } - let point = self.ctx.mouse_coords(); - // Skip normal mouse events if the message bar has been clicked - if let Some(message) = self.message_at_point(point) { - // Message should never be `Some` if point is `None` - debug_assert!(point.is_some()); - self.on_message_bar_click(state, point.unwrap(), message, modifiers); + if self.message_close_at_cursor() && state == ElementState::Pressed { + self.ctx.clear_selection(); + self.ctx.pop_message(); + + // Reset cursor when message bar height changed or all messages are gone + let size = self.ctx.size_info(); + let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE); + let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0; + let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0); + + let new_icon = match current_lines.cmp(&new_lines) { + Ordering::Less => CursorIcon::Default, + Ordering::Equal => CursorIcon::Hand, + Ordering::Greater => if mouse_mode { + CursorIcon::Default + } else { + CursorIcon::Text + }, + }; + + self.ctx.window_mut().set_mouse_cursor(new_icon); } else { match state { ElementState::Pressed => { self.process_mouse_bindings(modifiers, button); - self.on_mouse_press(button, modifiers, point); + self.on_mouse_press(button, modifiers); }, - ElementState::Released => self.on_mouse_release(button, modifiers, point), + ElementState::Released => self.on_mouse_release(button, modifiers), } } @@ -642,18 +669,13 @@ impl> Processor { /// Process key input. pub fn process_key(&mut self, input: KeyboardInput) { - self.ctx.modifiers().update(input); + self.ctx.modifiers().update_keys(input); - // Update mouse cursor for temporarily disabling mouse mode - if input.virtual_keycode == Some(VirtualKeyCode::LShift) - || input.virtual_keycode == Some(VirtualKeyCode::RShift) - { - if let Some(point) = self.ctx.mouse_coords() { - let mods = self.ctx.modifiers().into(); - let mouse_state = self.mouse_state(point, mods); - self.update_mouse_cursor(mouse_state); - } - } + // Update mouse state and check for URL change + let mods = (*self.ctx.modifiers()).into(); + let mouse_state = self.mouse_state(mods); + self.update_url_state(&mouse_state); + self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); match input.state { ElementState::Pressed => { @@ -734,58 +756,26 @@ impl> Processor { if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) { // binding was triggered; run the action - let mouse_mode = !mods.shift - && self.ctx.terminal().mode().intersects( - TermMode::MOUSE_REPORT_CLICK - | TermMode::MOUSE_DRAG - | TermMode::MOUSE_MOTION, - ); + let mouse_mode_active = + !mods.shift && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE); let binding = binding.clone(); - binding.execute(&mut self.ctx, mouse_mode); + binding.execute(&mut self.ctx, mouse_mode_active); } } } - /// Return the message bar's message if there is some at the specified point - fn message_at_point(&mut self, point: Option) -> Option { - let size = &self.ctx.size_info(); - if let (Some(point), Some(message)) = (point, self.ctx.message()) { - if point.line.0 >= size.lines().saturating_sub(message.text(size).len()) { - return Some(message.to_owned()); - } - } - - None + /// Check if the cursor is hovering above the message bar. + fn message_at_cursor(&mut self) -> bool { + self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines() } /// Whether the point is over the message bar's close button - fn message_close_at_point(&self, point: Point, message: Message) -> bool { - let size = self.ctx.size_info(); - point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols() - && point.line == size.lines() - message.text(&size).len() - } - - /// Handle clicks on the message bar. - fn on_message_bar_click( - &mut self, - button_state: ElementState, - point: Point, - message: Message, - mods: ModifiersState, - ) { - match button_state { - ElementState::Released => self.copy_selection(), - ElementState::Pressed => { - if self.message_close_at_point(point, message) { - let mouse_state = self.mouse_state(point, mods); - self.update_mouse_cursor(mouse_state); - self.ctx.pop_message(); - } - - self.ctx.clear_selection(); - }, - } + fn message_close_at_cursor(&self) -> bool { + let mouse = self.ctx.mouse(); + mouse.inside_grid + && mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= self.ctx.size_info().cols() + && mouse.line == self.ctx.terminal().grid().num_lines() } /// Copy text selection. @@ -796,18 +786,23 @@ impl> Processor { self.ctx.copy_selection(ClipboardType::Selection); } + /// Trigger redraw when URL highlight changed. #[inline] - fn update_mouse_cursor(&mut self, mouse_state: MouseState) { - self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); + fn update_url_state(&mut self, mouse_state: &MouseState) { + if let MouseState::Url(url) = mouse_state { + if Some(url) != self.highlighted_url.as_ref() { + self.ctx.terminal_mut().dirty = true; + } + } else if self.highlighted_url.is_some() { + self.ctx.terminal_mut().dirty = true; + } } #[inline] pub fn reset_mouse_cursor(&mut self) { - if let Some(point) = self.ctx.mouse_coords() { - let mods = self.ctx.modifiers().into(); - let mouse_state = self.mouse_state(point, mods); - self.update_mouse_cursor(mouse_state); - } + let mods = (*self.ctx.modifiers()).into(); + let mouse_state = self.mouse_state(mods); + self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); } } @@ -830,6 +825,7 @@ mod tests { use crate::config::{ClickHandler, Config}; use crate::event::{ClickState, Mouse}; + use crate::url::Urls; use crate::window::Window; use super::{Action, Binding, Modifiers, Processor}; @@ -914,7 +910,7 @@ mod tests { let x = self.mouse.x as usize; let y = self.mouse.y as usize; - if self.size_info.contains_point(x, y, true) { + if self.size_info.contains_point(x, y) { Some(self.size_info.pixels_to_coords(x, y)) } else { None @@ -1020,9 +1016,19 @@ mod tests { config: &cfg, }; - let mut processor = Processor::new(context); + let urls = Urls::new(); + let mut processor = Processor::new(context, &urls, &None); - if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input { + if let Event::WindowEvent { + event: WindowEvent::MouseInput { + state, + button, + modifiers, + .. + }, + .. + } = $input + { processor.mouse_input(state, button, modifiers); }; diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index d3419f7..80d3170 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -55,6 +55,7 @@ mod display; mod event; mod input; mod logging; +mod url; mod window; use crate::cli::Options; diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs new file mode 100644 index 0000000..849e7a4 --- /dev/null +++ b/alacritty/src/url.rs @@ -0,0 +1,179 @@ +use std::cmp::min; +use std::mem; + +use glutin::event::{ElementState, ModifiersState}; +use urlocator::{UrlLocation, UrlLocator}; + +use font::Metrics; + +use alacritty_terminal::index::Point; +use alacritty_terminal::renderer::rects::{RenderLine, RenderRect}; +use alacritty_terminal::term::cell::Flags; +use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo}; + +use crate::config::{Config, RelaxedEq}; +use crate::event::Mouse; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Url { + lines: Vec, + end_offset: u16, + num_cols: usize, +} + +impl Url { + pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec { + let end = self.end(); + self.lines + .iter() + .filter(|line| line.start <= end) + .map(|line| { + let mut rect_line = *line; + rect_line.end = min(line.end, end); + rect_line.rects(Flags::UNDERLINE, metrics, size) + }) + .flatten() + .collect() + } + + pub fn start(&self) -> Point { + self.lines[0].start + } + + pub fn end(&self) -> Point { + self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize) + } +} + +pub struct Urls { + locator: UrlLocator, + urls: Vec, + last_point: Option, + state: UrlLocation, +} + +impl Default for Urls { + fn default() -> Self { + Self { + locator: UrlLocator::new(), + urls: Vec::new(), + state: UrlLocation::Reset, + last_point: None, + } + } +} + +impl Urls { + pub fn new() -> Self { + Self::default() + } + + // Update tracked URLs + pub fn update(&mut self, num_cols: usize, cell: RenderableCell) { + // Ignore double-width spacers to prevent reset + if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { + return; + } + + // Convert cell to character + let c = match cell.inner { + RenderableCellContent::Chars(chars) => chars[0], + RenderableCellContent::Cursor(_) => return, + }; + + let point: Point = cell.into(); + + // Reset URL when empty cells have been skipped + if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point { + self.reset(); + } + self.last_point = Some(point); + + // Advance parser + let last_state = mem::replace(&mut self.state, self.locator.advance(c)); + match (self.state, last_state) { + (UrlLocation::Url(_length, end_offset), _) => { + let mut end = point; + + // Extend by one cell for double-width characters + if cell.flags.contains(Flags::WIDE_CHAR) { + end.col += 1; + + self.last_point = Some(end); + } + + if let Some(url) = self.urls.last_mut() { + let last_index = url.lines.len() - 1; + let last_line = &mut url.lines[last_index]; + + if last_line.color == cell.fg { + // Update existing line + last_line.end = end; + } else { + // Create new line with different color + url.lines.push(RenderLine { start: point, end, color: cell.fg }); + } + + // Update offset + url.end_offset = end_offset; + } + }, + (UrlLocation::Reset, UrlLocation::Scheme) => { + self.urls.pop(); + }, + (UrlLocation::Scheme, UrlLocation::Reset) => { + self.urls.push(Url { + lines: vec![RenderLine { start: point, end: point, color: cell.fg }], + end_offset: 0, + num_cols, + }); + }, + (UrlLocation::Scheme, _) => { + if let Some(url) = self.urls.last_mut() { + if url.lines[url.lines.len() - 1].color != cell.fg { + url.lines.push(RenderLine { start: point, end: point, color: cell.fg }); + } + } + }, + _ => (), + } + + // Reset at un-wrapped linebreak + if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { + self.reset(); + } + } + + pub fn highlighted( + &self, + config: &Config, + mouse: &Mouse, + mods: ModifiersState, + mouse_mode: bool, + selection: bool, + ) -> Option { + // Make sure all prerequisites for highlighting are met + if selection + || (mouse_mode && !mods.shift) + || !mouse.inside_grid + || config.ui_config.mouse.url.launcher.is_none() + || !config.ui_config.mouse.url.mods().relaxed_eq(mods) + || mouse.left_button_state == ElementState::Pressed + { + return None; + } + + for url in &self.urls { + if (url.start()..=url.end()).contains(&Point::new(mouse.line, mouse.column)) { + return Some(url.clone()); + } + } + + None + } + + fn reset(&mut self) { + self.locator = UrlLocator::new(); + self.state = UrlLocation::Reset; + } +} diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 7502ce7..d42295b 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -27,7 +27,6 @@ base64 = "0.10.0" terminfo = "0.6.1" url = "2" copypasta = { path = "../copypasta" } -rfind_url = "0.4.0" [target.'cfg(unix)'.dependencies] nix = "0.15.0" diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index d989b78..a05904d 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use vte; -use crate::index::{Column, Contains, Line}; +use crate::index::{Column, Line}; use crate::term::color::Rgb; // Parse colors in XParseColor format @@ -629,8 +629,8 @@ pub enum Attr { Dim, /// Italic text Italic, - /// Underscore text - Underscore, + /// Underline text + Underline, /// Blink cursor slowly BlinkSlow, /// Blink cursor fast @@ -1163,7 +1163,7 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec> { 1 => Some(Attr::Bold), 2 => Some(Attr::Dim), 3 => Some(Attr::Italic), - 4 => Some(Attr::Underscore), + 4 => Some(Attr::Underline), 5 => Some(Attr::BlinkSlow), 6 => Some(Attr::BlinkFast), 7 => Some(Attr::Reverse), @@ -1260,7 +1260,7 @@ fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option { *i += 4; let range = 0..256; - if !range.contains_(r) || !range.contains_(g) || !range.contains_(b) { + if !range.contains(&r) || !range.contains(&g) || !range.contains(&b) { debug!("Invalid RGB color spec: ({}, {}, {})", r, g, b); return None; } diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 5f5b84f..3141208 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -15,7 +15,7 @@ //! A specialized 2d grid implementation optimized for use in a terminal. use std::cmp::{max, min, Ordering}; -use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}; +use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo}; use serde::{Deserialize, Serialize}; @@ -63,7 +63,6 @@ impl ::std::cmp::PartialEq for Grid { && self.display_offset.eq(&other.display_offset) && self.scroll_limit.eq(&other.scroll_limit) && self.selection.eq(&other.selection) - && self.url_highlight.eq(&other.url_highlight) } } @@ -106,10 +105,6 @@ pub struct Grid { #[serde(default)] max_scroll_limit: usize, - - /// Range for URL hover highlights - #[serde(default)] - pub url_highlight: Option>, } #[derive(Copy, Clone)] @@ -132,7 +127,6 @@ impl Grid { scroll_limit: 0, selection: None, max_scroll_limit: scrollback, - url_highlight: None, } } @@ -398,7 +392,6 @@ impl Grid { let prev = self.lines; self.selection = None; - self.url_highlight = None; self.raw.rotate(*prev as isize - *target as isize); self.raw.shrink_visible_lines(target); self.lines = target; @@ -434,7 +427,6 @@ impl Grid { if let Some(ref mut selection) = self.selection { selection.rotate(-(*positions as isize)); } - self.url_highlight = None; self.decrease_scroll_limit(*positions); @@ -479,7 +471,6 @@ impl Grid { if let Some(ref mut selection) = self.selection { selection.rotate(*positions as isize); } - self.url_highlight = None; // // This next loop swaps "fixed" lines outside of a scroll region // // back into place after the rotation. The work is done in buffer- @@ -524,7 +515,6 @@ impl Grid { self.display_offset = 0; self.selection = None; - self.url_highlight = None; } } diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs index 0a100e1..d40245f 100644 --- a/alacritty_terminal/src/index.rs +++ b/alacritty_terminal/src/index.rs @@ -17,7 +17,7 @@ /// Indexing types and implementations for Grid and Line use std::cmp::{Ord, Ordering}; use std::fmt; -use std::ops::{self, Add, AddAssign, Deref, Range, RangeInclusive, Sub, SubAssign}; +use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign}; use serde::{Deserialize, Serialize}; @@ -41,6 +41,30 @@ impl Point { pub fn new(line: L, col: Column) -> Point { Point { line, col } } + + #[inline] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn sub(mut self, num_cols: usize, length: usize) -> Point + where + L: Copy + Sub, + { + let line_changes = f32::ceil(length.saturating_sub(self.col.0) as f32 / num_cols as f32); + self.line = self.line - line_changes as usize; + self.col = Column((num_cols + self.col.0 - length % num_cols) % num_cols); + self + } + + #[inline] + #[must_use = "this returns the result of the operation, without modifying the original"] + pub fn add(mut self, num_cols: usize, length: usize) -> Point + where + L: Copy + Add, + { + let line_changes = length.saturating_sub(self.col.0) / num_cols; + self.line = self.line + line_changes; + self.col = Column((self.col.0 + length) % num_cols); + self + } } impl Ord for Point { @@ -253,28 +277,6 @@ impl From> for IndexRange { } } -// can be removed if range_contains is stabilized -pub trait Contains { - type Content; - fn contains_(&self, item: Self::Content) -> bool; -} - -impl> Contains for Range { - type Content = T; - - fn contains_(&self, item: Self::Content) -> bool { - (self.start <= item) && (item < self.end) - } -} - -impl> Contains for RangeInclusive { - type Content = T; - - fn contains_(&self, item: Self::Content) -> bool { - (self.start() <= &item) && (&item <= self.end()) - } -} - macro_rules! ops { ($ty:ty, $construct:expr) => { add!($ty, $construct); diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs index dc26046..82c8385 100644 --- a/alacritty_terminal/src/lib.rs +++ b/alacritty_terminal/src/lib.rs @@ -38,7 +38,6 @@ pub mod selection; pub mod sync; pub mod term; pub mod tty; -pub mod url; pub mod util; pub use crate::grid::Grid; diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs index 76daac9..b200743 100644 --- a/alacritty_terminal/src/renderer/mod.rs +++ b/alacritty_terminal/src/renderer/mod.rs @@ -382,8 +382,8 @@ impl GlyphCache { let grid_width = cell_width as u32 * dimensions.columns_u32(); let grid_height = cell_height as u32 * dimensions.lines_u32(); - let width = (f64::from(grid_width) + 2. * padding_x).floor(); - let height = (f64::from(grid_height) + 2. * padding_y).floor(); + let width = padding_x.mul_add(2., f64::from(grid_width)).floor(); + let height = padding_y.mul_add(2., f64::from(grid_height)).floor(); Some((width, height)) } diff --git a/alacritty_terminal/src/renderer/rects.rs b/alacritty_terminal/src/renderer/rects.rs index c105c2e..d564582 100644 --- a/alacritty_terminal/src/renderer/rects.rs +++ b/alacritty_terminal/src/renderer/rects.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; use font::Metrics; -use crate::index::Point; +use crate::index::{Column, Point}; use crate::term::cell::Flags; use crate::term::color::Rgb; use crate::term::{RenderableCell, SizeInfo}; @@ -36,16 +36,42 @@ impl RenderRect { } } -struct RenderLine { - start: Point, - end: Point, - color: Rgb, +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RenderLine { + pub start: Point, + pub end: Point, + pub color: Rgb, } impl RenderLine { - fn into_rect(self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> RenderRect { - let start_x = self.start.col.0 as f32 * size.cell_width; - let end_x = (self.end.col.0 + 1) as f32 * size.cell_width; + pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec { + let mut rects = Vec::new(); + + let mut start = self.start; + while start.line < self.end.line { + let mut end = start; + end.col = size.cols() - 1; + rects.push(Self::create_rect(metrics, size, flag, start, end, self.color)); + + start.col = Column(0); + start.line += 1; + } + + rects.push(Self::create_rect(metrics, size, flag, start, self.end, self.color)); + + rects + } + + fn create_rect( + metrics: &Metrics, + size: &SizeInfo, + flag: Flags, + start: Point, + end: Point, + color: Rgb, + ) -> RenderRect { + let start_x = start.col.0 as f32 * size.cell_width; + let end_x = (end.col.0 + 1) as f32 * size.cell_width; let width = end_x - start_x; let (position, mut height) = match flag { @@ -57,7 +83,7 @@ impl RenderLine { // Make sure lines are always visible height = height.max(1.); - let line_bottom = (self.start.line.0 as f32 + 1.) * size.cell_height; + let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height; let baseline = line_bottom + metrics.descent; let mut y = baseline - position - height / 2.; @@ -66,7 +92,7 @@ impl RenderLine { y = max_y; } - RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, height, self.color, 1.) + RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, height, color, 1.) } } @@ -81,11 +107,11 @@ impl RenderLines { Self::default() } - pub fn into_rects(self, metrics: &Metrics, size: &SizeInfo) -> Vec { + pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec { self.inner - .into_iter() + .iter() .map(|(flag, lines)| -> Vec { - lines.into_iter().map(|line| line.into_rect(flag, &metrics, &size)).collect() + lines.iter().map(|line| line.rects(*flag, metrics, size)).flatten().collect() }) .flatten() .collect() @@ -100,9 +126,9 @@ impl RenderLines { // Check if there's an active line if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) { - if cell.line == line.start.line - && cell.fg == line.color + if cell.fg == line.color && cell.column == line.end.col + 1 + && cell.line == line.end.line { // Update the length of the line line.end = cell.into(); diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs index 29934e5..5f94634 100644 --- a/alacritty_terminal/src/selection.rs +++ b/alacritty_terminal/src/selection.rs @@ -144,22 +144,22 @@ impl Selection { // Simple selection is empty when the points are identical // or two adjacent cells have the sides right -> left start == end - || (start.side == Side::Left - && end.side == Side::Right + || (start.side == Side::Right + && end.side == Side::Left && (start.point.line == end.point.line) - && start.point.col == end.point.col + 1) + && start.point.col + 1 == end.point.col) }, Selection::Block { region: Range { ref start, ref end } } => { // Block selection is empty when the points' columns and sides are identical // or two cells with adjacent columns have the sides right -> left, // regardless of their lines (start.point.col == end.point.col && start.side == end.side) - || (start.point.col == end.point.col + 1 + || (start.point.col + 1 == end.point.col + && start.side == Side::Right + && end.side == Side::Left) + || (end.point.col + 1 == start.point.col && start.side == Side::Left && end.side == Side::Right) - || (end.point.col == start.point.col + 1 - && end.side == Side::Left - && start.side == Side::Right) }, Selection::Semantic { .. } | Selection::Lines { .. } => false, } @@ -214,16 +214,16 @@ impl Selection { span.map(|mut span| { let grid = term.grid(); - if span.end.col < cols - && grid[span.end.line][span.end.col].flags.contains(Flags::WIDE_CHAR_SPACER) + if span.start.col < cols + && grid[span.start.line][span.start.col].flags.contains(Flags::WIDE_CHAR_SPACER) { - span.end.col = Column(span.end.col.saturating_sub(1)); + span.start.col = Column(span.start.col.saturating_sub(1)); } - if span.start.col.0 < cols.saturating_sub(1) - && grid[span.start.line][span.start.col].flags.contains(Flags::WIDE_CHAR) + if span.end.col.0 < cols.saturating_sub(1) + && grid[span.end.line][span.end.col].flags.contains(Flags::WIDE_CHAR) { - span.start.col += 1; + span.end.col += 1; } span @@ -232,7 +232,7 @@ impl Selection { // Bring start and end points in the correct order fn points_need_swap(start: Point, end: Point) -> bool { - start.line > end.line || start.line == end.line && start.col < end.col + start.line < end.line || start.line == end.line && start.col > end.col } // Clamp selection inside the grid to prevent out of bounds errors @@ -242,26 +242,26 @@ impl Selection { lines: isize, cols: Column, ) -> Option<(Point, Point)> { - if end.line >= lines { + if start.line >= lines { // Don't show selection above visible region - if start.line >= lines { + if end.line >= lines { return None; } // Clamp selection above viewport to visible region - end.line = lines - 1; - end.col = Column(0); + start.line = lines - 1; + start.col = Column(0); } - if start.line < 0 { + if end.line < 0 { // Don't show selection below visible region - if end.line < 0 { + if start.line < 0 { return None; } // Clamp selection below viewport to visible region - start.line = 0; - start.col = cols - 1; + end.line = 0; + end.col = cols - 1; } Some((start, end)) @@ -275,10 +275,10 @@ impl Selection { if let Some(end) = term.bracket_search(start.into()) { (start.into(), end) } else { - (term.semantic_search_right(start.into()), term.semantic_search_left(end.into())) + (term.semantic_search_left(start.into()), term.semantic_search_right(end.into())) } } else { - (term.semantic_search_right(start.into()), term.semantic_search_left(end.into())) + (term.semantic_search_left(start.into()), term.semantic_search_right(end.into())) }; Some(Span { start, end, is_block: false }) @@ -288,8 +288,8 @@ impl Selection { where T: Dimensions, { - start.col = term.dimensions().col - 1; - end.col = Column(0); + end.col = term.dimensions().col - 1; + start.col = Column(0); Some(Span { start: start.into(), end: end.into(), is_block: false }) } @@ -310,19 +310,19 @@ impl Selection { } // Remove last cell if selection ends to the left of a cell - if start_side == Side::Left && start != end { - // Special case when selection starts to left of first cell - if start.col == Column(0) { - start.col = term.dimensions().col - 1; - start.line += 1; + if end_side == Side::Left && start != end { + // Special case when selection ends to left of first cell + if end.col == Column(0) { + end.col = term.dimensions().col - 1; + end.line += 1; } else { - start.col -= 1; + end.col -= 1; } } // Remove first cell if selection starts at the right of a cell - if end_side == Side::Right && start != end { - end.col += 1; + if start_side == Side::Right && start != end { + start.col += 1; } // Return the selection with all cells inclusive @@ -340,20 +340,20 @@ impl Selection { return None; } - // Always go bottom-right -> top-left - if start.col < end.col { + // Always go top-left -> bottom-right + if start.col > end.col { std::mem::swap(&mut start_side, &mut end_side); std::mem::swap(&mut start.col, &mut end.col); } // Remove last cell if selection ends to the left of a cell - if start_side == Side::Left && start != end && start.col.0 > 0 { - start.col -= 1; + if end_side == Side::Left && start != end && end.col.0 > 0 { + end.col -= 1; } // Remove first cell if selection starts at the right of a cell - if end_side == Side::Right && start != end { - end.col += 1; + if start_side == Side::Right && start != end { + start.col += 1; } // Return the selection with all cells inclusive @@ -508,8 +508,8 @@ mod test { selection.update(Point::new(0, Column(1)), Side::Right); assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span { - start: Point::new(0, Column(1)), - end: Point::new(1, Column(2)), + start: Point::new(1, Column(2)), + end: Point::new(0, Column(1)), is_block: false, }); } @@ -532,8 +532,8 @@ mod test { selection.update(Point::new(1, Column(0)), Side::Right); assert_eq!(selection.to_span(&term(5, 2)).unwrap(), Span { - start: Point::new(0, Column(1)), - end: Point::new(1, Column(1)), + start: Point::new(1, Column(1)), + end: Point::new(0, Column(1)), is_block: false, }); } @@ -545,8 +545,8 @@ mod test { selection.rotate(-3); assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { - start: Point::new(0, Column(4)), - end: Point::new(2, Column(0)), + start: Point::new(2, Column(0)), + end: Point::new(0, Column(4)), is_block: false, }); } @@ -558,8 +558,8 @@ mod test { selection.rotate(-3); assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { - start: Point::new(0, Column(4)), - end: Point::new(2, Column(3)), + start: Point::new(2, Column(3)), + end: Point::new(0, Column(4)), is_block: false, }); } @@ -571,8 +571,8 @@ mod test { selection.rotate(-3); assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { - start: Point::new(0, Column(4)), - end: Point::new(2, Column(4)), + start: Point::new(2, Column(4)), + end: Point::new(0, Column(4)), is_block: false, }); } @@ -584,8 +584,8 @@ mod test { selection.rotate(-3); assert_eq!(selection.to_span(&term(5, 10)).unwrap(), Span { - start: Point::new(0, Column(4)), - end: Point::new(2, Column(4)), + start: Point::new(2, Column(4)), + end: Point::new(0, Column(4)), is_block: true, }); } @@ -604,8 +604,8 @@ mod test { selection.update(Point::new(0, Column(8)), Side::Right); assert_eq!(selection.to_span(&term).unwrap(), Span { - start: Point::new(0, Column(9)), - end: Point::new(0, Column(0)), + start: Point::new(0, Column(0)), + end: Point::new(0, Column(9)), is_block: false, }); } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index bc7235c..63a5e04 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -14,12 +14,11 @@ // //! Exports the `Term` type which is a high-level API for the Grid use std::cmp::{max, min}; -use std::ops::{Index, IndexMut, Range, RangeInclusive}; +use std::ops::{Index, IndexMut, Range}; use std::time::{Duration, Instant}; use std::{io, mem, ptr}; use log::{debug, trace}; -use rfind_url::{Parser, ParserState}; use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; @@ -33,13 +32,12 @@ use crate::event::{Event, EventListener}; use crate::grid::{ BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, }; -use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point}; +use crate::index::{self, Column, IndexRange, Line, Point}; use crate::selection::{self, Selection, SelectionRange, Span}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::Rgb; #[cfg(windows)] use crate::tty; -use crate::url::Url; pub mod cell; pub mod color; @@ -76,7 +74,7 @@ impl Search for Term { break; } - if iter.point().col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } @@ -100,7 +98,7 @@ impl Search for Term { point = iter.point(); - if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } } @@ -181,7 +179,6 @@ pub struct RenderableCellsIter<'a, C> { config: &'a Config, colors: &'a color::List, selection: Option, - url_highlight: &'a Option>, } impl<'a, C> RenderableCellsIter<'a, C> { @@ -202,7 +199,7 @@ impl<'a, C> RenderableCellsIter<'a, C> { let selection_range = selection.map(|span| { let (limit_start, limit_end) = if span.is_block { - (span.end.col, span.start.col) + (span.start.col, span.end.col) } else { (Column(0), term.cols() - 1) }; @@ -211,11 +208,6 @@ impl<'a, C> RenderableCellsIter<'a, C> { let mut start = term.buffer_to_visible(span.start); let mut end = term.buffer_to_visible(span.end); - // Start and end lines are swapped as we switch from buffer to line coordinates - if start > end { - mem::swap(&mut start, &mut end); - } - // Trim start/end with partially visible block selection start.col = max(limit_start, start.col); end.col = min(limit_end, end.col); @@ -227,8 +219,8 @@ impl<'a, C> RenderableCellsIter<'a, C> { let cursor = &term.cursor.point; let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); let cursor_key = if cursor_visible { - let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR) - && (cursor.col + 1) < grid.num_cols(); + let is_wide = + grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols(); Some(CursorKey { style: cursor_style, is_wide }) } else { // Use hidden cursor so text will not get inverted @@ -242,7 +234,6 @@ impl<'a, C> RenderableCellsIter<'a, C> { grid, inner, selection: selection_range, - url_highlight: &grid.url_highlight, config, colors: &term.colors, cursor_key, @@ -266,7 +257,7 @@ pub struct RenderableCell { pub fg: Rgb, pub bg: Rgb, pub bg_alpha: f32, - pub flags: cell::Flags, + pub flags: Flags, } impl RenderableCell { @@ -315,27 +306,22 @@ impl RenderableCell { } } - fn compute_fg_rgb( - config: &Config, - colors: &color::List, - fg: Color, - flags: cell::Flags, - ) -> Rgb { + fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: Flags) -> Rgb { match fg { Color::Spec(rgb) => rgb, Color::Named(ansi) => { match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist - (_, cell::Flags::DIM_BOLD) + (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground && config.colors.primary.bright_foreground.is_none() => { colors[NamedColor::DimForeground] }, // Draw bold text in bright colors *and* contains bold flag. - (true, cell::Flags::BOLD) => colors[ansi.to_bright()], + (true, Flags::BOLD) => colors[ansi.to_bright()], // Cell is marked as dim and not bold - (_, cell::Flags::DIM) | (false, cell::Flags::DIM_BOLD) => colors[ansi.to_dim()], + (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()], // None of the above, keep original color. _ => colors[ansi], } @@ -346,9 +332,9 @@ impl RenderableCell { flags & Flags::DIM_BOLD, idx, ) { - (true, cell::Flags::BOLD, 0..=7) => idx as usize + 8, - (false, cell::Flags::DIM, 8..=15) => idx as usize - 8, - (false, cell::Flags::DIM, 0..=7) => idx as usize + 260, + (true, Flags::BOLD, 0..=7) => idx as usize + 8, + (false, Flags::DIM, 8..=15) => idx as usize - 8, + (false, Flags::DIM, 0..=7) => idx as usize + 260, _ => idx as usize, }; @@ -429,7 +415,7 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { return Some(cell); } } else { - let mut cell = self.inner.next()?; + let cell = self.inner.next()?; let selected = self .selection @@ -437,13 +423,6 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { .map(|range| range.contains(cell.column, cell.line)) .unwrap_or(false); - // Underline URL highlights - let index = Linear::new(self.grid.num_cols(), cell.column, cell.line); - if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false) - { - cell.inner.flags.insert(Flags::UNDERLINE); - } - if !cell.is_empty() || selected { return Some(RenderableCell::new(self.config, self.colors, cell, selected)); } @@ -471,6 +450,7 @@ pub mod mode { const FOCUS_IN_OUT = 0b000_1000_0000_0000; const ALT_SCREEN = 0b001_0000_0000_0000; const MOUSE_DRAG = 0b010_0000_0000_0000; + const MOUSE_MODE = 0b010_0000_0100_1000; const ALTERNATE_SCROLL = 0b100_0000_0000_0000; const ANY = 0b111_1111_1111_1111; const NONE = 0; @@ -688,6 +668,9 @@ impl VisualBell { } pub struct Term { + /// Terminal focus + pub is_focused: bool, + /// The grid grid: Grid, @@ -762,9 +745,6 @@ pub struct Term { /// Proxy for sending events to the event loop event_proxy: T, - /// Terminal focus - pub is_focused: bool, - /// Current title of the window title: String, @@ -810,15 +790,14 @@ impl SizeInfo { Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) } - pub fn contains_point(&self, x: usize, y: usize, include_padding: bool) -> bool { - if include_padding { - x < self.width as usize && y < self.height as usize - } else { - x < (self.width - self.padding_x) as usize - && x >= self.padding_x as usize - && y < (self.height - self.padding_y) as usize - && y >= self.padding_y as usize - } + /// Check if coordinates are inside the terminal grid. + /// + /// The padding is not counted as part of the grid. + pub fn contains_point(&self, x: usize, y: usize) -> bool { + x < (self.width - self.padding_x) as usize + && x >= self.padding_x as usize + && y < (self.height - self.padding_y) as usize + && y >= self.padding_y as usize } pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { @@ -848,7 +827,6 @@ impl Term { { self.event_proxy.send_event(Event::MouseCursorDirty); self.grid.scroll_display(scroll); - self.reset_url_highlight(); self.dirty = true; } @@ -923,118 +901,84 @@ impl Term { self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template); } + /// Convert the active selection to a String. pub fn selection_to_string(&self) -> Option { - trait Append { - fn append( - &mut self, - append_newline: bool, - grid: &Grid, - tabs: &TabStops, - line: usize, - cols: Range, - ); + let selection = self.grid.selection.clone()?; + let Span { start, end, is_block } = selection.to_span(self)?; + + let mut res = String::new(); + + if is_block { + for line in (end.line + 1..=start.line).rev() { + res += &(self.line_to_string(line, start.col..end.col) + "\n"); + } + res += &self.line_to_string(end.line, start.col..end.col); + } else { + res = self.bounds_to_string(start, end); } - impl Append for String { - fn append( - &mut self, - append_newline: bool, - grid: &Grid, - tabs: &TabStops, - mut line: usize, - cols: Range, - ) { - // Select until last line still within the buffer - line = min(line, grid.len() - 1); + Some(res) + } - let grid_line = &grid[line]; - let line_length = grid_line.line_length(); - let line_end = min(line_length, cols.end + 1); + /// Convert range between two points to a String. + pub fn bounds_to_string(&self, start: Point, end: Point) -> String { + let mut res = String::new(); - if cols.start < line_end { - let mut tab_mode = false; + for line in (end.line..=start.line).rev() { + let start_col = if line == start.line { start.col } else { Column(0) }; + let end_col = if line == end.line { end.col } else { self.cols() - 1 }; - for col in IndexRange::from(cols.start..line_end) { - let cell = grid_line[col]; + res += &self.line_to_string(line, start_col..end_col); + } - if tab_mode { - // Skip over whitespace until next tab-stop once a tab was found - if tabs[col] { - tab_mode = false; - } else if cell.c == ' ' { - continue; - } - } + res + } - if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - self.push(cell.c); - for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') { - self.push(*c); - } - } + /// Convert a single line in the grid to a String. + fn line_to_string(&self, line: usize, cols: Range) -> String { + let mut text = String::new(); - if cell.c == '\t' { - tab_mode = true; - } - } + let grid_line = &self.grid[line]; + let line_length = grid_line.line_length(); + let line_end = min(line_length, cols.end + 1); + + let mut tab_mode = false; + + for col in IndexRange::from(cols.start..line_end) { + let cell = grid_line[col]; + + // Skip over cells until next tab-stop once a tab was found + if tab_mode { + if self.tabs[col] { + tab_mode = false; + } else { + continue; } + } - if append_newline - || (cols.end >= grid.num_cols() - 1 - && (line_end == Column(0) - || !grid[line][line_end - 1].flags.contains(cell::Flags::WRAPLINE))) - { - self.push('\n'); + if cell.c == '\t' { + tab_mode = true; + } + + if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { + // Push cells primary character + text.push(cell.c); + + // Push zero-width characters + for c in (&cell.chars()[1..]).iter().take_while(|c| **c != ' ') { + text.push(*c); } } } - let selection = self.grid.selection.clone()?; - let Span { mut start, mut end, is_block } = selection.to_span(self)?; - - let mut res = String::new(); - - if start > end { - ::std::mem::swap(&mut start, &mut end); + if cols.end >= self.cols() - 1 + && (line_end == Column(0) + || !self.grid[line][line_end - 1].flags.contains(cell::Flags::WRAPLINE)) + { + text.push('\n'); } - let line_count = end.line - start.line; - - // Setup block selection start/end point limits - let (limit_start, limit_end) = - if is_block { (end.col, start.col) } else { (Column(0), self.grid.num_cols()) }; - - match line_count { - // Selection within single line - 0 => { - res.append(false, &self.grid, &self.tabs, start.line, start.col..end.col); - }, - - // Selection ends on line following start - 1 => { - // Ending line - res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end); - - // Starting line - res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col); - }, - - // Multi line selection - _ => { - // Ending line - res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end); - - let middle_range = (start.line + 1)..(end.line); - for line in middle_range.rev() { - res.append(is_block, &self.grid, &self.tabs, line, limit_start..limit_end); - } - - // Starting line - res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col); - }, - } - - Some(res) + text } pub fn visible_to_buffer(&self, point: Point) -> Point { @@ -1097,7 +1041,6 @@ impl Term { self.grid.selection = None; self.alt_grid.selection = None; - self.grid.url_highlight = None; // Should not allow less than 1 col, causes all sorts of checks to be required. if num_cols <= Column(1) { @@ -1233,92 +1176,9 @@ impl Term { self.event_proxy.send_event(Event::Exit); } - #[inline] - pub fn set_url_highlight(&mut self, hl: RangeInclusive) { - self.grid.url_highlight = Some(hl); - self.dirty = true; - } - - #[inline] - pub fn reset_url_highlight(&mut self) { - self.grid.url_highlight = None; - self.dirty = true; - } - pub fn clipboard(&mut self) -> &mut Clipboard { &mut self.clipboard } - - pub fn urls(&self) -> Vec { - let display_offset = self.grid.display_offset(); - let num_cols = self.grid.num_cols().0; - let last_col = Column(num_cols - 1); - let last_line = self.grid.num_lines() - 1; - - let grid_end_point = Point::new(0, last_col); - let mut iter = self.grid.iter_from(grid_end_point); - - let mut parser = Parser::new(); - let mut extra_url_len = 0; - let mut urls = Vec::new(); - - let mut c = Some(iter.cell()); - while let Some(cell) = c { - let point = iter.point(); - c = iter.prev(); - - // Skip double-width cell but extend URL length - if cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - extra_url_len += 1; - continue; - } - - // Interrupt URLs on line break - if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { - extra_url_len = 0; - parser.reset(); - } - - // Advance parser - match parser.advance(cell.c) { - ParserState::Url(length) => { - urls.push(Url::new(point, length + extra_url_len, num_cols)) - }, - ParserState::NoUrl => { - extra_url_len = 0; - - // Stop searching for URLs once the viewport has been left without active URL - if point.line > last_line.0 + display_offset { - break; - } - }, - _ => (), - } - } - - urls - } - - pub fn url_to_string(&self, url: Url) -> String { - let mut url_text = String::new(); - - let mut iter = self.grid.iter_from(url.start); - - let mut c = Some(iter.cell()); - while let Some(cell) = c { - if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - url_text.push(cell.c); - } - - if iter.point() == url.end { - break; - } - - c = iter.next(); - } - - url_text - } } impl TermInfo for Term { @@ -1387,7 +1247,7 @@ impl ansi::Handler for Term { let location = Point { line: self.cursor.point.line, col: self.cursor.point.col }; let cell = &mut self.grid[&location]; - cell.flags.insert(cell::Flags::WRAPLINE); + cell.flags.insert(Flags::WRAPLINE); } if (self.cursor.point.line + 1) >= self.scroll_region.end { @@ -1422,7 +1282,7 @@ impl ansi::Handler for Term { if width == 0 { let mut col = self.cursor.point.col.0.saturating_sub(1); let line = self.cursor.point.line; - if self.grid[line][Column(col)].flags.contains(cell::Flags::WIDE_CHAR_SPACER) { + if self.grid[line][Column(col)].flags.contains(Flags::WIDE_CHAR_SPACER) { col = col.saturating_sub(1); } self.grid[line][Column(col)].push_extra(c); @@ -1435,13 +1295,13 @@ impl ansi::Handler for Term { // Handle wide chars if width == 2 { - cell.flags.insert(cell::Flags::WIDE_CHAR); + cell.flags.insert(Flags::WIDE_CHAR); if self.cursor.point.col + 1 < num_cols { self.cursor.point.col += 1; let spacer = &mut self.grid[&self.cursor.point]; *spacer = self.cursor.template; - spacer.flags.insert(cell::Flags::WIDE_CHAR_SPACER); + spacer.flags.insert(Flags::WIDE_CHAR_SPACER); } } } @@ -1702,7 +1562,7 @@ impl ansi::Handler for Term { #[inline] fn insert_blank_lines(&mut self, lines: Line) { trace!("Inserting blank {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { + if self.scroll_region.contains(&self.cursor.point.line) { let origin = self.cursor.point.line; self.scroll_down_relative(origin, lines); } @@ -1711,7 +1571,7 @@ impl ansi::Handler for Term { #[inline] fn delete_lines(&mut self, lines: Line) { trace!("Deleting {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { + if self.scroll_region.contains(&self.cursor.point.line) { let origin = self.cursor.point.line; self.scroll_up_relative(origin, lines); } @@ -1868,9 +1728,8 @@ impl ansi::Handler for Term { let mut template = self.cursor.template; template.flags ^= template.flags; - // Remove active selections and URL highlights + // Remove active selections self.grid.selection = None; - self.grid.url_highlight = None; match mode { ansi::ClearMode::Below => { @@ -1959,24 +1818,22 @@ impl ansi::Handler for Term { Attr::Reset => { self.cursor.template.fg = Color::Named(NamedColor::Foreground); self.cursor.template.bg = Color::Named(NamedColor::Background); - self.cursor.template.flags = cell::Flags::empty(); + self.cursor.template.flags = Flags::empty(); }, - Attr::Reverse => self.cursor.template.flags.insert(cell::Flags::INVERSE), - Attr::CancelReverse => self.cursor.template.flags.remove(cell::Flags::INVERSE), - Attr::Bold => self.cursor.template.flags.insert(cell::Flags::BOLD), - Attr::CancelBold => self.cursor.template.flags.remove(cell::Flags::BOLD), - Attr::Dim => self.cursor.template.flags.insert(cell::Flags::DIM), - Attr::CancelBoldDim => { - self.cursor.template.flags.remove(cell::Flags::BOLD | cell::Flags::DIM) - }, - Attr::Italic => self.cursor.template.flags.insert(cell::Flags::ITALIC), - Attr::CancelItalic => self.cursor.template.flags.remove(cell::Flags::ITALIC), - Attr::Underscore => self.cursor.template.flags.insert(cell::Flags::UNDERLINE), - Attr::CancelUnderline => self.cursor.template.flags.remove(cell::Flags::UNDERLINE), - Attr::Hidden => self.cursor.template.flags.insert(cell::Flags::HIDDEN), - Attr::CancelHidden => self.cursor.template.flags.remove(cell::Flags::HIDDEN), - Attr::Strike => self.cursor.template.flags.insert(cell::Flags::STRIKEOUT), - Attr::CancelStrike => self.cursor.template.flags.remove(cell::Flags::STRIKEOUT), + Attr::Reverse => self.cursor.template.flags.insert(Flags::INVERSE), + Attr::CancelReverse => self.cursor.template.flags.remove(Flags::INVERSE), + Attr::Bold => self.cursor.template.flags.insert(Flags::BOLD), + Attr::CancelBold => self.cursor.template.flags.remove(Flags::BOLD), + Attr::Dim => self.cursor.template.flags.insert(Flags::DIM), + Attr::CancelBoldDim => self.cursor.template.flags.remove(Flags::BOLD | Flags::DIM), + Attr::Italic => self.cursor.template.flags.insert(Flags::ITALIC), + Attr::CancelItalic => self.cursor.template.flags.remove(Flags::ITALIC), + Attr::Underline => self.cursor.template.flags.insert(Flags::UNDERLINE), + Attr::CancelUnderline => self.cursor.template.flags.remove(Flags::UNDERLINE), + Attr::Hidden => self.cursor.template.flags.insert(Flags::HIDDEN), + Attr::CancelHidden => self.cursor.template.flags.remove(Flags::HIDDEN), + Attr::Strike => self.cursor.template.flags.insert(Flags::STRIKEOUT), + Attr::CancelStrike => self.cursor.template.flags.remove(Flags::STRIKEOUT), _ => { debug!("Term got unhandled attr: {:?}", attr); }, @@ -2189,7 +2046,8 @@ mod tests { use crate::grid::{Grid, Scroll}; use crate::index::{Column, Line, Point, Side}; use crate::selection::Selection; - use crate::term::{cell, Cell, SizeInfo, Term}; + use crate::term::cell::{Cell, Flags}; + use crate::term::{SizeInfo, Term}; struct Mock; impl EventListener for Mock { @@ -2217,7 +2075,7 @@ mod tests { grid[Line(0)][Column(0)].c = '"'; grid[Line(0)][Column(3)].c = '"'; grid[Line(1)][Column(2)].c = '"'; - grid[Line(0)][Column(4)].flags.insert(cell::Flags::WRAPLINE); + grid[Line(0)][Column(4)].flags.insert(Flags::WRAPLINE); let mut escape_chars = String::from("\""); diff --git a/alacritty_terminal/src/url.rs b/alacritty_terminal/src/url.rs deleted file mode 100644 index 9e8ecd4..0000000 --- a/alacritty_terminal/src/url.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::ansi::TermInfo; -use crate::index::{Column, Linear, Point}; -use crate::term::Term; -use std::ops::RangeInclusive; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)] -pub struct Url { - pub start: Point, - pub end: Point, -} - -impl Url { - pub fn new(start: Point, length: usize, num_cols: usize) -> Self { - let unwrapped_end_col = start.col.0 + length - 1; - let end_col = unwrapped_end_col % num_cols; - let end_line = start.line - unwrapped_end_col / num_cols; - - Url { end: Point::new(end_line, Column(end_col)), start } - } - - /// Check if point is within this URL - pub fn contains(&self, point: impl Into>) -> bool { - let point = point.into(); - - point.line <= self.start.line - && point.line >= self.end.line - && (point.line != self.start.line || point.col >= self.start.col) - && (point.line != self.end.line || point.col <= self.end.col) - } - - /// Convert URLs bounding points to linear indices - pub fn linear_bounds(&self, terminal: &Term) -> RangeInclusive { - let mut start = self.start; - let mut end = self.end; - - start = terminal.buffer_to_visible(start); - end = terminal.buffer_to_visible(end); - - let start = Linear::from_point(terminal.cols(), start); - let end = Linear::from_point(terminal.cols(), end); - - RangeInclusive::new(start, end) - } -}