mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
parent
fa6ceacfa4
commit
b47a88b142
17 changed files with 703 additions and 626 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<glutin::ContextError> for Error {
|
|||
pub struct Display {
|
||||
pub size_info: SizeInfo,
|
||||
pub window: Window,
|
||||
pub urls: Urls,
|
||||
|
||||
/// Currently highlighted URL.
|
||||
pub highlighted_url: Option<Url>,
|
||||
|
||||
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<T>>,
|
||||
message_buffer: &MessageBuffer,
|
||||
config: &Config,
|
||||
mouse: &Mouse,
|
||||
mods: ModifiersState,
|
||||
) {
|
||||
let grid_cells: Vec<RenderableCell> = 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.);
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> 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<N: Notify> Processor<N> {
|
|||
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<N: Notify> Processor<N> {
|
|||
}
|
||||
|
||||
// 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<N: Notify> Processor<N> {
|
|||
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<N: Notify> Processor<N> {
|
|||
match event {
|
||||
TouchpadPressure { .. }
|
||||
| CursorEntered { .. }
|
||||
| CursorLeft { .. }
|
||||
| AxisMotion { .. }
|
||||
| HoveredFileCancelled
|
||||
| Destroyed
|
||||
|
|
|
@ -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<T: EventListener, A: ActionContext<T>> {
|
||||
pub struct Processor<'a, T: EventListener, A: ActionContext<T>> {
|
||||
pub ctx: A,
|
||||
pub urls: &'a Urls,
|
||||
pub highlighted_url: &'a Option<Url>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
|
@ -89,42 +93,59 @@ pub trait ActionContext<T: EventListener> {
|
|||
|
||||
#[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<Modifiers> 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<T: EventListener, A: ActionContext<T>>(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<MouseState> for CursorIcon {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
||||
pub fn new(ctx: A) -> Self {
|
||||
Self { ctx, _phantom: Default::default() }
|
||||
impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
||||
pub fn new(
|
||||
ctx: A,
|
||||
urls: &'a Urls,
|
||||
highlighted_url: &'a Option<Url>,
|
||||
) -> 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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
|
||||
#[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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option<Point>) {
|
||||
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<Point>) {
|
||||
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<Point>,
|
||||
) {
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
|
||||
// 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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn on_mouse_release(
|
||||
&mut self,
|
||||
button: MouseButton,
|
||||
modifiers: ModifiersState,
|
||||
point: Option<Point>,
|
||||
) {
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
};
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
}
|
||||
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
|
||||
/// 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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
|
||||
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<Point>) -> Option<Message> {
|
||||
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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
|
|||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ mod display;
|
|||
mod event;
|
||||
mod input;
|
||||
mod logging;
|
||||
mod url;
|
||||
mod window;
|
||||
|
||||
use crate::cli::Options;
|
||||
|
|
179
alacritty/src/url.rs
Normal file
179
alacritty/src/url.rs
Normal file
|
@ -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<RenderLine>,
|
||||
end_offset: u16,
|
||||
num_cols: usize,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
|
||||
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<Url>,
|
||||
last_point: Option<Point>,
|
||||
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<Url> {
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<Option<Attr>> {
|
|||
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<Color> {
|
|||
*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;
|
||||
}
|
||||
|
|
|
@ -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<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
|
|||
&& 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<T> {
|
|||
|
||||
#[serde(default)]
|
||||
max_scroll_limit: usize,
|
||||
|
||||
/// Range for URL hover highlights
|
||||
#[serde(default)]
|
||||
pub url_highlight: Option<RangeInclusive<index::Linear>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -132,7 +127,6 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
|||
scroll_limit: 0,
|
||||
selection: None,
|
||||
max_scroll_limit: scrollback,
|
||||
url_highlight: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,7 +392,6 @@ impl<T: GridCell + Copy + Clone> Grid<T> {
|
|||
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<T: GridCell + Copy + Clone> Grid<T> {
|
|||
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<T: GridCell + Copy + Clone> Grid<T> {
|
|||
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<T: GridCell + Copy + Clone> Grid<T> {
|
|||
|
||||
self.display_offset = 0;
|
||||
self.selection = None;
|
||||
self.url_highlight = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<L> Point<L> {
|
|||
pub fn new(line: L, col: Column) -> Point<L> {
|
||||
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<L>
|
||||
where
|
||||
L: Copy + Sub<usize, Output = L>,
|
||||
{
|
||||
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<L>
|
||||
where
|
||||
L: Copy + Add<usize, Output = L>,
|
||||
{
|
||||
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<T> From<Range<T>> for IndexRange<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// can be removed if range_contains is stabilized
|
||||
pub trait Contains {
|
||||
type Content;
|
||||
fn contains_(&self, item: Self::Content) -> bool;
|
||||
}
|
||||
|
||||
impl<T: PartialOrd<T>> Contains for Range<T> {
|
||||
type Content = T;
|
||||
|
||||
fn contains_(&self, item: Self::Content) -> bool {
|
||||
(self.start <= item) && (item < self.end)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd<T>> Contains for RangeInclusive<T> {
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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<RenderRect> {
|
||||
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<RenderRect> {
|
||||
pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
|
||||
self.inner
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|(flag, lines)| -> Vec<RenderRect> {
|
||||
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();
|
||||
|
|
|
@ -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<isize>, end: Point<isize>) -> 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<isize>, Point<isize>)> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<T> Search for Term<T> {
|
|||
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<T> Search for Term<T> {
|
|||
|
||||
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<C>,
|
||||
colors: &'a color::List,
|
||||
selection: Option<SelectionRange>,
|
||||
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
|
||||
}
|
||||
|
||||
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<C>(
|
||||
config: &Config<C>,
|
||||
colors: &color::List,
|
||||
fg: Color,
|
||||
flags: cell::Flags,
|
||||
) -> Rgb {
|
||||
fn compute_fg_rgb<C>(config: &Config<C>, 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<T> {
|
||||
/// Terminal focus
|
||||
pub is_focused: bool,
|
||||
|
||||
/// The grid
|
||||
grid: Grid<Cell>,
|
||||
|
||||
|
@ -762,9 +745,6 @@ pub struct Term<T> {
|
|||
/// 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<T> Term<T> {
|
|||
{
|
||||
self.event_proxy.send_event(Event::MouseCursorDirty);
|
||||
self.grid.scroll_display(scroll);
|
||||
self.reset_url_highlight();
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
|
@ -923,118 +901,84 @@ impl<T> Term<T> {
|
|||
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<String> {
|
||||
trait Append {
|
||||
fn append(
|
||||
&mut self,
|
||||
append_newline: bool,
|
||||
grid: &Grid<Cell>,
|
||||
tabs: &TabStops,
|
||||
line: usize,
|
||||
cols: Range<Column>,
|
||||
);
|
||||
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<Cell>,
|
||||
tabs: &TabStops,
|
||||
mut line: usize,
|
||||
cols: Range<Column>,
|
||||
) {
|
||||
// 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<usize>, end: Point<usize>) -> 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<Column>) -> 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<usize> {
|
||||
|
@ -1097,7 +1041,6 @@ impl<T> Term<T> {
|
|||
|
||||
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<T> Term<T> {
|
|||
self.event_proxy.send_event(Event::Exit);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_url_highlight(&mut self, hl: RangeInclusive<index::Linear>) {
|
||||
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<Url> {
|
||||
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<T> TermInfo for Term<T> {
|
||||
|
@ -1387,7 +1247,7 @@ impl<T: EventListener> ansi::Handler for Term<T> {
|
|||
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<T: EventListener> ansi::Handler for Term<T> {
|
|||
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<T: EventListener> ansi::Handler for Term<T> {
|
|||
|
||||
// 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<T: EventListener> ansi::Handler for Term<T> {
|
|||
#[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<T: EventListener> ansi::Handler for Term<T> {
|
|||
#[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<T: EventListener> ansi::Handler for Term<T> {
|
|||
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<T: EventListener> ansi::Handler for Term<T> {
|
|||
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("\"");
|
||||
|
||||
|
|
|
@ -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<usize>,
|
||||
pub end: Point<usize>,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn new(start: Point<usize>, 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<Point<usize>>) -> 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<T>(&self, terminal: &Term<T>) -> RangeInclusive<Linear> {
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue