mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Semantic Selection
Fix tests and add line select Refactor BidirectionalIter to remove if blocks Allow for cells tagged with WRAPLINE to continue expanding the selection Reorganize config into structs Add test coverage that callbacks are called Cleanup mouse config - Uses Duration type for ClickHandler::threshold - Removes `action` property from ClickHandler--this can be added in a backwards compatible way later on - Renames ClickState::DblClick to DoubleClick fixup! Cleanup mouse config
This commit is contained in:
parent
59295e4431
commit
92e1cec088
8 changed files with 567 additions and 24 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -2,3 +2,9 @@ target
|
||||||
FlameGraph
|
FlameGraph
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
|
# vim temporary files
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# other ignores
|
||||||
|
*.DS_Store
|
||||||
|
|
|
@ -212,6 +212,12 @@ key_bindings:
|
||||||
mouse_bindings:
|
mouse_bindings:
|
||||||
- { mouse: Middle, action: PasteSelection }
|
- { mouse: Middle, action: PasteSelection }
|
||||||
|
|
||||||
|
mouse:
|
||||||
|
double_click: { threshold: 300 }
|
||||||
|
triple_click: { threshold: 300 }
|
||||||
|
|
||||||
|
selection:
|
||||||
|
semantic_escape_chars: ",│`|:\"' ()[]{}<>"
|
||||||
|
|
||||||
# Shell
|
# Shell
|
||||||
#
|
#
|
||||||
|
|
|
@ -212,6 +212,13 @@ key_bindings:
|
||||||
mouse_bindings:
|
mouse_bindings:
|
||||||
- { mouse: Middle, action: PasteSelection }
|
- { mouse: Middle, action: PasteSelection }
|
||||||
|
|
||||||
|
mouse:
|
||||||
|
double_click: { threshold: 300 }
|
||||||
|
triple_click: { threshold: 300 }
|
||||||
|
|
||||||
|
selection:
|
||||||
|
semantic_escape_chars: ",│`|:\"' ()[]{}<>"
|
||||||
|
|
||||||
# Shell
|
# Shell
|
||||||
#
|
#
|
||||||
# You can set shell.program to the path of your favorite shell, e.g. /bin/fish.
|
# You can set shell.program to the path of your favorite shell, e.g. /bin/fish.
|
||||||
|
|
|
@ -3,21 +3,22 @@
|
||||||
//! Alacritty reads from a config file at startup to determine various runtime
|
//! Alacritty reads from a config file at startup to determine various runtime
|
||||||
//! parameters including font family and style, font size, etc. In the future,
|
//! parameters including font family and style, font size, etc. In the future,
|
||||||
//! the config file will also hold user and platform specific keybindings.
|
//! the config file will also hold user and platform specific keybindings.
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fs::File;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::time::Duration;
|
||||||
use std::fs::File;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use ::Rgb;
|
use ::Rgb;
|
||||||
use font::Size;
|
use font::Size;
|
||||||
use serde_yaml;
|
use serde_yaml;
|
||||||
use serde::{self, de};
|
use serde::{self, de, Deserialize};
|
||||||
use serde::de::Error as SerdeError;
|
use serde::de::Error as SerdeError;
|
||||||
use serde::de::{Visitor, MapVisitor, Unexpected};
|
use serde::de::{Visitor, MapVisitor, Unexpected};
|
||||||
use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op};
|
use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op};
|
||||||
|
@ -32,6 +33,52 @@ fn true_bool() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Selection {
|
||||||
|
pub semantic_escape_chars: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Selection {
|
||||||
|
fn default() -> Selection {
|
||||||
|
Selection {
|
||||||
|
semantic_escape_chars: String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct ClickHandler {
|
||||||
|
#[serde(deserialize_with="deserialize_duration_ms")]
|
||||||
|
pub threshold: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_duration_ms<D>(deserializer: D) -> ::std::result::Result<Duration, D::Error>
|
||||||
|
where D: de::Deserializer
|
||||||
|
{
|
||||||
|
let threshold_ms = u64::deserialize(deserializer)?;
|
||||||
|
Ok(Duration::from_millis(threshold_ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Mouse {
|
||||||
|
pub double_click: ClickHandler,
|
||||||
|
pub triple_click: ClickHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Mouse {
|
||||||
|
fn default() -> Mouse {
|
||||||
|
Mouse {
|
||||||
|
double_click: ClickHandler {
|
||||||
|
threshold: Duration::from_millis(300),
|
||||||
|
},
|
||||||
|
triple_click: ClickHandler {
|
||||||
|
threshold: Duration::from_millis(300),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// List of indexed colors
|
/// List of indexed colors
|
||||||
///
|
///
|
||||||
/// The first 16 entries are the standard ansi named colors. Items 16..232 are
|
/// The first 16 entries are the standard ansi named colors. Items 16..232 are
|
||||||
|
@ -248,6 +295,12 @@ pub struct Config {
|
||||||
#[serde(default="default_mouse_bindings")]
|
#[serde(default="default_mouse_bindings")]
|
||||||
mouse_bindings: Vec<MouseBinding>,
|
mouse_bindings: Vec<MouseBinding>,
|
||||||
|
|
||||||
|
#[serde(default="default_selection")]
|
||||||
|
selection: Selection,
|
||||||
|
|
||||||
|
#[serde(default="default_mouse")]
|
||||||
|
mouse: Mouse,
|
||||||
|
|
||||||
/// Path to a shell program to run on startup
|
/// Path to a shell program to run on startup
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
shell: Option<Shell<'static>>,
|
shell: Option<Shell<'static>>,
|
||||||
|
@ -266,6 +319,10 @@ fn default_config() -> Config {
|
||||||
.expect("default config is valid")
|
.expect("default config is valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_selection() -> Selection {
|
||||||
|
default_config().selection
|
||||||
|
}
|
||||||
|
|
||||||
fn default_key_bindings() -> Vec<KeyBinding> {
|
fn default_key_bindings() -> Vec<KeyBinding> {
|
||||||
default_config().key_bindings
|
default_config().key_bindings
|
||||||
}
|
}
|
||||||
|
@ -274,6 +331,10 @@ fn default_mouse_bindings() -> Vec<MouseBinding> {
|
||||||
default_config().mouse_bindings
|
default_config().mouse_bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_mouse() -> Mouse {
|
||||||
|
default_config().mouse
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Config {
|
fn default() -> Config {
|
||||||
Config {
|
Config {
|
||||||
|
@ -286,6 +347,8 @@ impl Default for Config {
|
||||||
colors: Default::default(),
|
colors: Default::default(),
|
||||||
key_bindings: Vec::new(),
|
key_bindings: Vec::new(),
|
||||||
mouse_bindings: Vec::new(),
|
mouse_bindings: Vec::new(),
|
||||||
|
selection: Default::default(),
|
||||||
|
mouse: Default::default(),
|
||||||
shell: None,
|
shell: None,
|
||||||
config_path: None,
|
config_path: None,
|
||||||
}
|
}
|
||||||
|
@ -964,6 +1027,14 @@ impl Config {
|
||||||
&self.mouse_bindings[..]
|
&self.mouse_bindings[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mouse(&self) -> &Mouse {
|
||||||
|
&self.mouse
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection(&self) -> &Selection {
|
||||||
|
&self.selection
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn draw_bold_text_with_bright_colors(&self) -> bool {
|
pub fn draw_bold_text_with_bright_colors(&self) -> bool {
|
||||||
self.draw_bold_text_with_bright_colors
|
self.draw_bold_text_with_bright_colors
|
||||||
|
|
32
src/event.rs
32
src/event.rs
|
@ -3,13 +3,14 @@ use std::borrow::Cow;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use std::time::{Instant};
|
||||||
|
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
use parking_lot::MutexGuard;
|
use parking_lot::MutexGuard;
|
||||||
use glutin::{self, ElementState};
|
use glutin::{self, ElementState};
|
||||||
use copypasta::{Clipboard, Load, Store};
|
use copypasta::{Clipboard, Load, Store};
|
||||||
|
|
||||||
use config::Config;
|
use config::{self, Config};
|
||||||
use cli::Options;
|
use cli::Options;
|
||||||
use display::OnResize;
|
use display::OnResize;
|
||||||
use index::{Line, Column, Side, Point};
|
use index::{Line, Column, Side, Point};
|
||||||
|
@ -71,17 +72,38 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
|
||||||
self.selection.update(point, side);
|
self.selection.update(point, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn semantic_selection(&mut self, point: Point) {
|
||||||
|
self.terminal.semantic_selection(&mut self.selection, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_selection(&mut self, point: Point) {
|
||||||
|
self.terminal.line_selection(&mut self.selection, point)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_coords(&self) -> Option<Point> {
|
||||||
|
self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mouse_mut(&mut self) -> &mut Mouse {
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
||||||
self.mouse
|
self.mouse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ClickState {
|
||||||
|
None,
|
||||||
|
Click,
|
||||||
|
DoubleClick,
|
||||||
|
TripleClick,
|
||||||
|
}
|
||||||
|
|
||||||
/// State of the mouse
|
/// State of the mouse
|
||||||
pub struct Mouse {
|
pub struct Mouse {
|
||||||
pub x: u32,
|
pub x: u32,
|
||||||
pub y: u32,
|
pub y: u32,
|
||||||
pub left_button_state: ElementState,
|
pub left_button_state: ElementState,
|
||||||
|
pub last_click_timestamp: Instant,
|
||||||
|
pub click_state: ClickState,
|
||||||
pub scroll_px: i32,
|
pub scroll_px: i32,
|
||||||
pub line: Line,
|
pub line: Line,
|
||||||
pub column: Column,
|
pub column: Column,
|
||||||
|
@ -93,7 +115,9 @@ impl Default for Mouse {
|
||||||
Mouse {
|
Mouse {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
last_click_timestamp: Instant::now(),
|
||||||
left_button_state: ElementState::Released,
|
left_button_state: ElementState::Released,
|
||||||
|
click_state: ClickState::None,
|
||||||
scroll_px: 0,
|
scroll_px: 0,
|
||||||
line: Line(0),
|
line: Line(0),
|
||||||
column: Column(0),
|
column: Column(0),
|
||||||
|
@ -109,6 +133,7 @@ impl Default for Mouse {
|
||||||
pub struct Processor<N> {
|
pub struct Processor<N> {
|
||||||
key_bindings: Vec<KeyBinding>,
|
key_bindings: Vec<KeyBinding>,
|
||||||
mouse_bindings: Vec<MouseBinding>,
|
mouse_bindings: Vec<MouseBinding>,
|
||||||
|
mouse_config: config::Mouse,
|
||||||
print_events: bool,
|
print_events: bool,
|
||||||
notifier: N,
|
notifier: N,
|
||||||
mouse: Mouse,
|
mouse: Mouse,
|
||||||
|
@ -143,6 +168,7 @@ impl<N: Notify> Processor<N> {
|
||||||
Processor {
|
Processor {
|
||||||
key_bindings: config.key_bindings().to_vec(),
|
key_bindings: config.key_bindings().to_vec(),
|
||||||
mouse_bindings: config.mouse_bindings().to_vec(),
|
mouse_bindings: config.mouse_bindings().to_vec(),
|
||||||
|
mouse_config: config.mouse().to_owned(),
|
||||||
print_events: options.print_events,
|
print_events: options.print_events,
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
resize_tx: resize_tx,
|
resize_tx: resize_tx,
|
||||||
|
@ -263,8 +289,9 @@ impl<N: Notify> Processor<N> {
|
||||||
|
|
||||||
processor = input::Processor {
|
processor = input::Processor {
|
||||||
ctx: context,
|
ctx: context,
|
||||||
|
mouse_config: &self.mouse_config,
|
||||||
key_bindings: &self.key_bindings[..],
|
key_bindings: &self.key_bindings[..],
|
||||||
mouse_bindings: &self.mouse_bindings[..]
|
mouse_bindings: &self.mouse_bindings[..],
|
||||||
};
|
};
|
||||||
|
|
||||||
process!(event);
|
process!(event);
|
||||||
|
@ -284,5 +311,6 @@ impl<N: Notify> Processor<N> {
|
||||||
pub fn update_config(&mut self, config: &Config) {
|
pub fn update_config(&mut self, config: &Config) {
|
||||||
self.key_bindings = config.key_bindings().to_vec();
|
self.key_bindings = config.key_bindings().to_vec();
|
||||||
self.mouse_bindings = config.mouse_bindings().to_vec();
|
self.mouse_bindings = config.mouse_bindings().to_vec();
|
||||||
|
self.mouse_config = config.mouse().to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
114
src/grid.rs
114
src/grid.rs
|
@ -26,13 +26,18 @@ use std::iter::IntoIterator;
|
||||||
use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut};
|
use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut};
|
||||||
use std::slice::{self, Iter, IterMut};
|
use std::slice::{self, Iter, IterMut};
|
||||||
|
|
||||||
use index::{self, Point, IndexRange, RangeInclusive};
|
use index::{self, Point, Line, Column, IndexRange, RangeInclusive};
|
||||||
|
|
||||||
/// Convert a type to a linear index range.
|
/// Convert a type to a linear index range.
|
||||||
pub trait ToRange {
|
pub trait ToRange {
|
||||||
fn to_range(&self, columns: index::Column) -> RangeInclusive<index::Linear>;
|
fn to_range(&self, columns: index::Column) -> RangeInclusive<index::Linear>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bidirection iterator
|
||||||
|
pub trait BidirectionalIterator: Iterator {
|
||||||
|
fn prev(&mut self) -> Option<Self::Item>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the terminal display contents
|
/// Represents the terminal display contents
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
||||||
pub struct Grid<T> {
|
pub struct Grid<T> {
|
||||||
|
@ -49,6 +54,11 @@ pub struct Grid<T> {
|
||||||
lines: index::Line,
|
lines: index::Line,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GridIterator<'a, T: 'a> {
|
||||||
|
grid: &'a Grid<T>,
|
||||||
|
pub cur: Point,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone> Grid<T> {
|
impl<T: Clone> Grid<T> {
|
||||||
pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid<T> {
|
pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid<T> {
|
||||||
let mut raw = Vec::with_capacity(*lines);
|
let mut raw = Vec::with_capacity(*lines);
|
||||||
|
@ -139,6 +149,13 @@ impl<T> Grid<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter_from(&self, point: Point) -> GridIterator<T> {
|
||||||
|
GridIterator {
|
||||||
|
grid: self,
|
||||||
|
cur: point,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self, point: &Point) -> bool {
|
pub fn contains(&self, point: &Point) -> bool {
|
||||||
self.lines > point.line && self.cols > point.col
|
self.lines > point.line && self.cols > point.col
|
||||||
|
@ -193,6 +210,49 @@ impl<T> Grid<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Iterator for GridIterator<'a, T> {
|
||||||
|
type Item = &'a T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let last_line = self.grid.num_lines() - Line(1);
|
||||||
|
let last_col = self.grid.num_cols() - Column(1);
|
||||||
|
match self.cur {
|
||||||
|
Point { line, col } if
|
||||||
|
(line == last_line) &&
|
||||||
|
(col == last_col) => None,
|
||||||
|
Point { col, .. } if
|
||||||
|
(col == last_col) => {
|
||||||
|
self.cur.line += Line(1);
|
||||||
|
self.cur.col = Column(0);
|
||||||
|
Some(&self.grid[self.cur.line][self.cur.col])
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.cur.col += Column(1);
|
||||||
|
Some(&self.grid[self.cur.line][self.cur.col])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
|
||||||
|
fn prev(&mut self) -> Option<Self::Item> {
|
||||||
|
let num_cols = self.grid.num_cols();
|
||||||
|
|
||||||
|
match self.cur {
|
||||||
|
Point { line: Line(0), col: Column(0) } => None,
|
||||||
|
Point { col: Column(0), .. } => {
|
||||||
|
self.cur.line -= Line(1);
|
||||||
|
self.cur.col = num_cols - Column(1);
|
||||||
|
Some(&self.grid[self.cur.line][self.cur.col])
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.cur.col -= Column(1);
|
||||||
|
Some(&self.grid[self.cur.line][self.cur.col])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Index<index::Line> for Grid<T> {
|
impl<T> Index<index::Line> for Grid<T> {
|
||||||
type Output = Row<T>;
|
type Output = Row<T>;
|
||||||
|
|
||||||
|
@ -464,8 +524,8 @@ clear_region_impl!(RangeFrom<index::Line>);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Grid;
|
use super::{Grid, BidirectionalIterator};
|
||||||
use index::{Line, Column};
|
use index::{Point, Line, Column};
|
||||||
#[test]
|
#[test]
|
||||||
fn grid_swap_lines_ok() {
|
fn grid_swap_lines_ok() {
|
||||||
let mut grid = Grid::new(Line(10), Column(1), &0);
|
let mut grid = Grid::new(Line(10), Column(1), &0);
|
||||||
|
@ -588,4 +648,52 @@ mod tests {
|
||||||
assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]);
|
assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that GridIterator works
|
||||||
|
#[test]
|
||||||
|
fn test_iter() {
|
||||||
|
info!("");
|
||||||
|
|
||||||
|
let mut grid = Grid::new(Line(5), Column(5), &0);
|
||||||
|
for i in 0..5 {
|
||||||
|
for j in 0..5 {
|
||||||
|
grid[Line(i)][Column(j)] = i*5 + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("grid: {:?}", grid);
|
||||||
|
|
||||||
|
let mut iter = grid.iter_from(Point {
|
||||||
|
line: Line(0),
|
||||||
|
col: Column(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(None, iter.prev());
|
||||||
|
assert_eq!(Some(&1), iter.next());
|
||||||
|
assert_eq!(Column(1), iter.cur.col);
|
||||||
|
assert_eq!(Line(0), iter.cur.line);
|
||||||
|
|
||||||
|
assert_eq!(Some(&2), iter.next());
|
||||||
|
assert_eq!(Some(&3), iter.next());
|
||||||
|
assert_eq!(Some(&4), iter.next());
|
||||||
|
|
||||||
|
// test linewrapping
|
||||||
|
assert_eq!(Some(&5), iter.next());
|
||||||
|
assert_eq!(Column(0), iter.cur.col);
|
||||||
|
assert_eq!(Line(1), iter.cur.line);
|
||||||
|
|
||||||
|
assert_eq!(Some(&4), iter.prev());
|
||||||
|
assert_eq!(Column(4), iter.cur.col);
|
||||||
|
assert_eq!(Line(0), iter.cur.line);
|
||||||
|
|
||||||
|
|
||||||
|
// test that iter ends at end of grid
|
||||||
|
let mut final_iter = grid.iter_from(Point {
|
||||||
|
line: Line(4),
|
||||||
|
col: Column(4),
|
||||||
|
});
|
||||||
|
assert_eq!(None, final_iter.next());
|
||||||
|
assert_eq!(Some(&23), final_iter.prev());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
202
src/input.rs
202
src/input.rs
|
@ -20,16 +20,18 @@
|
||||||
//! determine what to do when a non-modifier key is pressed.
|
//! determine what to do when a non-modifier key is pressed.
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
use copypasta::{Clipboard, Load, Buffer};
|
use copypasta::{Clipboard, Load, Buffer};
|
||||||
use glutin::{ElementState, VirtualKeyCode, MouseButton};
|
use glutin::{ElementState, VirtualKeyCode, MouseButton};
|
||||||
use glutin::{Mods, mods};
|
use glutin::{Mods, mods};
|
||||||
use glutin::{TouchPhase, MouseScrollDelta};
|
use glutin::{TouchPhase, MouseScrollDelta};
|
||||||
|
|
||||||
use event::{Mouse};
|
use config;
|
||||||
|
use event::{ClickState, Mouse};
|
||||||
use index::{Line, Column, Side, Point};
|
use index::{Line, Column, Side, Point};
|
||||||
use term::mode::{self, TermMode};
|
|
||||||
use term::SizeInfo;
|
use term::SizeInfo;
|
||||||
|
use term::mode::{self, TermMode};
|
||||||
use util::fmt::Red;
|
use util::fmt::Red;
|
||||||
|
|
||||||
/// Processes input from glutin.
|
/// Processes input from glutin.
|
||||||
|
@ -41,6 +43,7 @@ use util::fmt::Red;
|
||||||
pub struct Processor<'a, A: 'a> {
|
pub struct Processor<'a, A: 'a> {
|
||||||
pub key_bindings: &'a [KeyBinding],
|
pub key_bindings: &'a [KeyBinding],
|
||||||
pub mouse_bindings: &'a [MouseBinding],
|
pub mouse_bindings: &'a [MouseBinding],
|
||||||
|
pub mouse_config: &'a config::Mouse,
|
||||||
pub ctx: A,
|
pub ctx: A,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +54,10 @@ pub trait ActionContext {
|
||||||
fn copy_selection(&self, Buffer);
|
fn copy_selection(&self, Buffer);
|
||||||
fn clear_selection(&mut self);
|
fn clear_selection(&mut self);
|
||||||
fn update_selection(&mut self, Point, Side);
|
fn update_selection(&mut self, Point, Side);
|
||||||
|
fn semantic_selection(&mut self, Point);
|
||||||
|
fn line_selection(&mut self, Point);
|
||||||
fn mouse_mut(&mut self) -> &mut Mouse;
|
fn mouse_mut(&mut self) -> &mut Mouse;
|
||||||
|
fn mouse_coords(&self) -> Option<Point>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a state and action to take in that state
|
/// Describes a state and action to take in that state
|
||||||
|
@ -266,13 +272,43 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_mouse_press(&mut self) {
|
pub fn on_mouse_double_click(&mut self) {
|
||||||
if self.ctx.terminal_mode().intersects(mode::MOUSE_REPORT_CLICK | mode::MOUSE_MOTION) {
|
if let Some(point) = self.ctx.mouse_coords() {
|
||||||
self.mouse_report(0);
|
self.ctx.semantic_selection(point);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.ctx.clear_selection();
|
pub fn on_mouse_triple_click(&mut self) {
|
||||||
|
if let Some(point) = self.ctx.mouse_coords() {
|
||||||
|
self.ctx.line_selection(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_press(&mut self) {
|
||||||
|
let now = Instant::now();
|
||||||
|
let elapsed = self.ctx.mouse_mut().last_click_timestamp.elapsed();
|
||||||
|
self.ctx.mouse_mut().last_click_timestamp = now;
|
||||||
|
|
||||||
|
self.ctx.mouse_mut().click_state = match self.ctx.mouse_mut().click_state {
|
||||||
|
ClickState::Click if elapsed < self.mouse_config.double_click.threshold => {
|
||||||
|
self.on_mouse_double_click();
|
||||||
|
ClickState::DoubleClick
|
||||||
|
},
|
||||||
|
ClickState::DoubleClick if elapsed < self.mouse_config.triple_click.threshold => {
|
||||||
|
self.on_mouse_triple_click();
|
||||||
|
ClickState::TripleClick
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let report_modes = mode::MOUSE_REPORT_CLICK | mode::MOUSE_MOTION;
|
||||||
|
if self.ctx.terminal_mode().intersects(report_modes) {
|
||||||
|
self.mouse_report(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ctx.clear_selection();
|
||||||
|
ClickState::Click
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_mouse_release(&mut self) {
|
pub fn on_mouse_release(&mut self) {
|
||||||
|
@ -422,14 +458,136 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use glutin::{mods, VirtualKeyCode};
|
use std::borrow::Cow;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use term::mode;
|
use glutin::{mods, VirtualKeyCode, Event, ElementState, MouseButton};
|
||||||
|
|
||||||
use super::{Action, Binding};
|
use term::{SizeInfo, Term, TermMode, mode};
|
||||||
|
use event::{Mouse, ClickState};
|
||||||
|
use config::{self, Config, ClickHandler};
|
||||||
|
use selection::Selection;
|
||||||
|
use index::{Point, Side};
|
||||||
|
|
||||||
|
use super::{Action, Binding, Processor};
|
||||||
|
|
||||||
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
|
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum MultiClick {
|
||||||
|
DoubleClick,
|
||||||
|
TripleClick,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActionContext<'a> {
|
||||||
|
pub terminal: &'a mut Term,
|
||||||
|
pub selection: &'a mut Selection,
|
||||||
|
pub size_info: &'a SizeInfo,
|
||||||
|
pub mouse: &'a mut Mouse,
|
||||||
|
pub last_action: MultiClick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a>super::ActionContext for ActionContext<'a> {
|
||||||
|
fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {
|
||||||
|
// STUBBED
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminal_mode(&self) -> TermMode {
|
||||||
|
*self.terminal.mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_info(&self) -> SizeInfo {
|
||||||
|
*self.size_info
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_selection(&self, _buffer: ::copypasta::Buffer) {
|
||||||
|
// STUBBED
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_selection(&mut self) { }
|
||||||
|
|
||||||
|
fn update_selection(&mut self, point: Point, side: Side) {
|
||||||
|
self.selection.update(point, side);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn semantic_selection(&mut self, _point: Point) {
|
||||||
|
// set something that we can check for here
|
||||||
|
self.last_action = MultiClick::DoubleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_selection(&mut self, _point: Point) {
|
||||||
|
self.last_action = MultiClick::TripleClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mouse_coords(&self) -> Option<Point> {
|
||||||
|
self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mouse_mut(&mut self) -> &mut Mouse {
|
||||||
|
self.mouse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! test_clickstate {
|
||||||
|
{
|
||||||
|
name: $name:ident,
|
||||||
|
initial_state: $initial_state:expr,
|
||||||
|
input: $input:expr,
|
||||||
|
end_state: $end_state:pat,
|
||||||
|
last_action: $last_action:expr
|
||||||
|
} => {
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
let config = Config::default();
|
||||||
|
let size = SizeInfo {
|
||||||
|
width: 21.0,
|
||||||
|
height: 51.0,
|
||||||
|
cell_width: 3.0,
|
||||||
|
cell_height: 3.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut terminal = Term::new(&config, size);
|
||||||
|
|
||||||
|
let mut mouse = Mouse::default();
|
||||||
|
let mut selection = Selection::new();
|
||||||
|
mouse.click_state = $initial_state;
|
||||||
|
|
||||||
|
let context = ActionContext {
|
||||||
|
terminal: &mut terminal,
|
||||||
|
selection: &mut selection,
|
||||||
|
mouse: &mut mouse,
|
||||||
|
size_info: &size,
|
||||||
|
last_action: MultiClick::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut processor = Processor {
|
||||||
|
ctx: context,
|
||||||
|
mouse_config: &config::Mouse {
|
||||||
|
double_click: ClickHandler {
|
||||||
|
threshold: Duration::from_millis(1000),
|
||||||
|
},
|
||||||
|
triple_click: ClickHandler {
|
||||||
|
threshold: Duration::from_millis(1000),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key_bindings: &config.key_bindings()[..],
|
||||||
|
mouse_bindings: &config.mouse_bindings()[..],
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Event::MouseInput(state, input) = $input {
|
||||||
|
processor.mouse_input(state, input);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(match mouse.click_state {
|
||||||
|
$end_state => processor.ctx.last_action == $last_action,
|
||||||
|
_ => false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! test_process_binding {
|
macro_rules! test_process_binding {
|
||||||
{
|
{
|
||||||
name: $name:ident,
|
name: $name:ident,
|
||||||
|
@ -449,6 +607,30 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_clickstate! {
|
||||||
|
name: single_click,
|
||||||
|
initial_state: ClickState::None,
|
||||||
|
input: Event::MouseInput(ElementState::Pressed, MouseButton::Left),
|
||||||
|
end_state: ClickState::Click,
|
||||||
|
last_action: MultiClick::None
|
||||||
|
}
|
||||||
|
|
||||||
|
test_clickstate! {
|
||||||
|
name: double_click,
|
||||||
|
initial_state: ClickState::Click,
|
||||||
|
input: Event::MouseInput(ElementState::Pressed, MouseButton::Left),
|
||||||
|
end_state: ClickState::DoubleClick,
|
||||||
|
last_action: MultiClick::DoubleClick
|
||||||
|
}
|
||||||
|
|
||||||
|
test_clickstate! {
|
||||||
|
name: triple_click,
|
||||||
|
initial_state: ClickState::DoubleClick,
|
||||||
|
input: Event::MouseInput(ElementState::Pressed, MouseButton::Left),
|
||||||
|
end_state: ClickState::TripleClick,
|
||||||
|
last_action: MultiClick::TripleClick
|
||||||
|
}
|
||||||
|
|
||||||
test_process_binding! {
|
test_process_binding! {
|
||||||
name: process_binding_nomode_shiftmod_require_shift,
|
name: process_binding_nomode_shiftmod_require_shift,
|
||||||
binding: Binding { trigger: KEY, mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE },
|
binding: Binding { trigger: KEY, mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE },
|
||||||
|
|
145
src/term/mod.rs
145
src/term/mod.rs
|
@ -20,8 +20,8 @@ use std::cmp::min;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
|
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
|
||||||
use grid::{Grid, ClearRegion, ToRange};
|
use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange};
|
||||||
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive};
|
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side};
|
||||||
use selection::{Span, Selection};
|
use selection::{Span, Selection};
|
||||||
use config::{Config};
|
use config::{Config};
|
||||||
|
|
||||||
|
@ -352,6 +352,8 @@ pub struct Term {
|
||||||
|
|
||||||
/// Saved cursor from alt grid
|
/// Saved cursor from alt grid
|
||||||
cursor_save_alt: Cursor,
|
cursor_save_alt: Cursor,
|
||||||
|
|
||||||
|
semantic_escape_chars: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminal size info
|
/// Terminal size info
|
||||||
|
@ -436,11 +438,13 @@ impl Term {
|
||||||
size_info: size,
|
size_info: size,
|
||||||
empty_cell: template,
|
empty_cell: template,
|
||||||
custom_cursor_colors: config.custom_cursor_colors(),
|
custom_cursor_colors: config.custom_cursor_colors(),
|
||||||
|
semantic_escape_chars: config.selection().semantic_escape_chars.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_config(&mut self, config: &Config) {
|
pub fn update_config(&mut self, config: &Config) {
|
||||||
self.custom_cursor_colors = config.custom_cursor_colors()
|
self.custom_cursor_colors = config.custom_cursor_colors();
|
||||||
|
self.semantic_escape_chars = config.selection().semantic_escape_chars.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -448,6 +452,64 @@ impl Term {
|
||||||
self.dirty
|
self.dirty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn line_selection(&self, selection: &mut Selection, point: Point) {
|
||||||
|
selection.clear();
|
||||||
|
selection.update(Point {
|
||||||
|
line: point.line,
|
||||||
|
col: Column(0),
|
||||||
|
}, Side::Left);
|
||||||
|
selection.update(Point {
|
||||||
|
line: point.line,
|
||||||
|
col: self.grid.num_cols() - Column(1),
|
||||||
|
}, Side::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn semantic_selection(&self, selection: &mut Selection, point: Point) {
|
||||||
|
let mut side_left = Point {
|
||||||
|
line: point.line,
|
||||||
|
col: point.col
|
||||||
|
};
|
||||||
|
let mut side_right = Point {
|
||||||
|
line: point.line,
|
||||||
|
col: point.col
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut left_iter = self.grid.iter_from(point);
|
||||||
|
let mut right_iter = self.grid.iter_from(point);
|
||||||
|
|
||||||
|
let last_col = self.grid.num_cols() - Column(1);
|
||||||
|
|
||||||
|
while let Some(cell) = left_iter.prev() {
|
||||||
|
if self.semantic_escape_chars.contains(cell.c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if left_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
|
||||||
|
break; // cut off if on new line or hit escape char
|
||||||
|
}
|
||||||
|
|
||||||
|
side_left.col = left_iter.cur.col;
|
||||||
|
side_left.line = left_iter.cur.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(cell) = right_iter.next() {
|
||||||
|
if self.semantic_escape_chars.contains(cell.c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
side_right.col = right_iter.cur.col;
|
||||||
|
side_right.line = right_iter.cur.line;
|
||||||
|
|
||||||
|
if right_iter.cur.col == last_col && !cell.flags.contains(cell::WRAPLINE) {
|
||||||
|
break; // cut off if on new line or hit escape char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.clear();
|
||||||
|
selection.update(side_left, Side::Left);
|
||||||
|
selection.update(side_right, Side::Right);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn string_from_selection(&self, span: &Span) -> String {
|
pub fn string_from_selection(&self, span: &Span) -> String {
|
||||||
/// Need a generic push() for the Append trait
|
/// Need a generic push() for the Append trait
|
||||||
trait PushChar {
|
trait PushChar {
|
||||||
|
@ -1283,12 +1345,85 @@ impl ansi::Handler for Term {
|
||||||
mod tests {
|
mod tests {
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use super::{Term, limit, SizeInfo};
|
use super::{Cell, Term, limit, SizeInfo};
|
||||||
|
use term::cell;
|
||||||
|
|
||||||
use grid::Grid;
|
use grid::Grid;
|
||||||
use index::{Point, Line, Column};
|
use index::{Point, Line, Column};
|
||||||
use term::{Cell};
|
|
||||||
use ansi::{Handler, CharsetIndex, StandardCharset};
|
use ansi::{Handler, CharsetIndex, StandardCharset};
|
||||||
|
use selection::Selection;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn semantic_selection_works() {
|
||||||
|
let size = SizeInfo {
|
||||||
|
width: 21.0,
|
||||||
|
height: 51.0,
|
||||||
|
cell_width: 3.0,
|
||||||
|
cell_height: 3.0,
|
||||||
|
};
|
||||||
|
let mut term = Term::new(&Default::default(), size);
|
||||||
|
let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), &Cell::default());
|
||||||
|
for i in 0..5 {
|
||||||
|
for j in 0..2 {
|
||||||
|
grid[Line(j)][Column(i)].c = 'a';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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::WRAPLINE);
|
||||||
|
|
||||||
|
let mut escape_chars = String::from("\"");
|
||||||
|
|
||||||
|
mem::swap(&mut term.grid, &mut grid);
|
||||||
|
mem::swap(&mut term.semantic_escape_chars, &mut escape_chars);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut selection = Selection::new();
|
||||||
|
term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(1) });
|
||||||
|
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aa");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut selection = Selection::new();
|
||||||
|
term.semantic_selection(&mut selection, Point { line: Line(0), col: Column(4) });
|
||||||
|
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut selection = Selection::new();
|
||||||
|
term.semantic_selection(&mut selection, Point { line: Line(1), col: Column(1) });
|
||||||
|
assert_eq!(term.string_from_selection(&selection.span().unwrap()), "aaa");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_selection_works() {
|
||||||
|
let size = SizeInfo {
|
||||||
|
width: 21.0,
|
||||||
|
height: 51.0,
|
||||||
|
cell_width: 3.0,
|
||||||
|
cell_height: 3.0,
|
||||||
|
};
|
||||||
|
let mut term = Term::new(&Default::default(), size);
|
||||||
|
let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), &Cell::default());
|
||||||
|
for i in 0..5 {
|
||||||
|
grid[Line(0)][Column(i)].c = 'a';
|
||||||
|
}
|
||||||
|
grid[Line(0)][Column(0)].c = '"';
|
||||||
|
grid[Line(0)][Column(3)].c = '"';
|
||||||
|
|
||||||
|
|
||||||
|
mem::swap(&mut term.grid, &mut grid);
|
||||||
|
|
||||||
|
let mut selection = Selection::new();
|
||||||
|
term.line_selection(&mut selection, Point { line: Line(0), col: Column(3) });
|
||||||
|
match selection.span() {
|
||||||
|
Some(span) => assert_eq!(term.string_from_selection(&span), "\"aa\"a"),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that the grid can be serialized back and forth losslessly
|
/// Check that the grid can be serialized back and forth losslessly
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue