1
0
Fork 0
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:
Xiaoyu Yin 2017-01-14 17:53:48 -08:00 committed by Joe Wilm
parent 59295e4431
commit 92e1cec088
8 changed files with 567 additions and 24 deletions

6
.gitignore vendored
View file

@ -2,3 +2,9 @@ target
FlameGraph FlameGraph
.idea .idea
*.iml *.iml
# vim temporary files
*.swp
# other ignores
*.DS_Store

View file

@ -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
# #

View file

@ -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.

View file

@ -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

View file

@ -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();
} }
} }

View file

@ -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());
}
} }

View file

@ -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 },

View file

@ -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
/// ///