summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.rs79
-rw-r--r--src/event.rs32
-rw-r--r--src/grid.rs114
-rw-r--r--src/input.rs202
-rw-r--r--src/term/mod.rs145
5 files changed, 548 insertions, 24 deletions
diff --git a/src/config.rs b/src/config.rs
index 362fe645..c610f419 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -3,21 +3,22 @@
//! Alacritty reads from a config file at startup to determine various runtime
//! parameters including font family and style, font size, etc. In the future,
//! the config file will also hold user and platform specific keybindings.
+use std::borrow::Cow;
use std::env;
use std::fmt;
+use std::fs::File;
use std::fs;
use std::io::{self, Read, Write};
+use std::ops::{Index, IndexMut};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::mpsc;
-use std::ops::{Index, IndexMut};
-use std::fs::File;
-use std::borrow::Cow;
+use std::time::Duration;
use ::Rgb;
use font::Size;
use serde_yaml;
-use serde::{self, de};
+use serde::{self, de, Deserialize};
use serde::de::Error as SerdeError;
use serde::de::{Visitor, MapVisitor, Unexpected};
use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op};
@@ -32,6 +33,52 @@ fn true_bool() -> bool {
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
///
/// 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")]
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
#[serde(default)]
shell: Option<Shell<'static>>,
@@ -266,6 +319,10 @@ fn default_config() -> Config {
.expect("default config is valid")
}
+fn default_selection() -> Selection {
+ default_config().selection
+}
+
fn default_key_bindings() -> Vec<KeyBinding> {
default_config().key_bindings
}
@@ -274,6 +331,10 @@ fn default_mouse_bindings() -> Vec<MouseBinding> {
default_config().mouse_bindings
}
+fn default_mouse() -> Mouse {
+ default_config().mouse
+}
+
impl Default for Config {
fn default() -> Config {
Config {
@@ -286,6 +347,8 @@ impl Default for Config {
colors: Default::default(),
key_bindings: Vec::new(),
mouse_bindings: Vec::new(),
+ selection: Default::default(),
+ mouse: Default::default(),
shell: None,
config_path: None,
}
@@ -964,6 +1027,14 @@ impl Config {
&self.mouse_bindings[..]
}
+ pub fn mouse(&self) -> &Mouse {
+ &self.mouse
+ }
+
+ pub fn selection(&self) -> &Selection {
+ &self.selection
+ }
+
#[inline]
pub fn draw_bold_text_with_bright_colors(&self) -> bool {
self.draw_bold_text_with_bright_colors
diff --git a/src/event.rs b/src/event.rs
index 295cb6b1..f643c115 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -3,13 +3,14 @@ use std::borrow::Cow;
use std::fs::File;
use std::io::Write;
use std::sync::mpsc;
+use std::time::{Instant};
use serde_json as json;
use parking_lot::MutexGuard;
use glutin::{self, ElementState};
use copypasta::{Clipboard, Load, Store};
-use config::Config;
+use config::{self, Config};
use cli::Options;
use display::OnResize;
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);
}
+ 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]
fn mouse_mut(&mut self) -> &mut Mouse {
self.mouse
}
}
+pub enum ClickState {
+ None,
+ Click,
+ DoubleClick,
+ TripleClick,
+}
+
/// State of the mouse
pub struct Mouse {
pub x: u32,
pub y: u32,
pub left_button_state: ElementState,
+ pub last_click_timestamp: Instant,
+ pub click_state: ClickState,
pub scroll_px: i32,
pub line: Line,
pub column: Column,
@@ -93,7 +115,9 @@ impl Default for Mouse {
Mouse {
x: 0,
y: 0,
+ last_click_timestamp: Instant::now(),
left_button_state: ElementState::Released,
+ click_state: ClickState::None,
scroll_px: 0,
line: Line(0),
column: Column(0),
@@ -109,6 +133,7 @@ impl Default for Mouse {
pub struct Processor<N> {
key_bindings: Vec<KeyBinding>,
mouse_bindings: Vec<MouseBinding>,
+ mouse_config: config::Mouse,
print_events: bool,
notifier: N,
mouse: Mouse,
@@ -143,6 +168,7 @@ impl<N: Notify> Processor<N> {
Processor {
key_bindings: config.key_bindings().to_vec(),
mouse_bindings: config.mouse_bindings().to_vec(),
+ mouse_config: config.mouse().to_owned(),
print_events: options.print_events,
notifier: notifier,
resize_tx: resize_tx,
@@ -263,8 +289,9 @@ impl<N: Notify> Processor<N> {
processor = input::Processor {
ctx: context,
+ mouse_config: &self.mouse_config,
key_bindings: &self.key_bindings[..],
- mouse_bindings: &self.mouse_bindings[..]
+ mouse_bindings: &self.mouse_bindings[..],
};
process!(event);
@@ -284,5 +311,6 @@ impl<N: Notify> Processor<N> {
pub fn update_config(&mut self, config: &Config) {
self.key_bindings = config.key_bindings().to_vec();
self.mouse_bindings = config.mouse_bindings().to_vec();
+ self.mouse_config = config.mouse().to_owned();
}
}
diff --git a/src/grid.rs b/src/grid.rs
index d913e640..c886807b 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -26,13 +26,18 @@ use std::iter::IntoIterator;
use std::ops::{Deref, DerefMut, Range, RangeTo, RangeFrom, RangeFull, Index, IndexMut};
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.
pub trait ToRange {
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
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct Grid<T> {
@@ -49,6 +54,11 @@ pub struct Grid<T> {
lines: index::Line,
}
+pub struct GridIterator<'a, T: 'a> {
+ grid: &'a Grid<T>,
+ pub cur: Point,
+}
+
impl<T: Clone> Grid<T> {
pub fn new(lines: index::Line, cols: index::Column, template: &T) -> Grid<T> {
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]
pub fn contains(&self, point: &Point) -> bool {
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> {
type Output = Row<T>;
@@ -464,8 +524,8 @@ clear_region_impl!(RangeFrom<index::Line>);
#[cfg(test)]
mod tests {
- use super::Grid;
- use index::{Line, Column};
+ use super::{Grid, BidirectionalIterator};
+ use index::{Point, Line, Column};
#[test]
fn grid_swap_lines_ok() {
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)]);
}
}
+
+ // 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());
+ }
+
}
diff --git a/src/input.rs b/src/input.rs
index e6dc3993..e2b96c60 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -20,16 +20,18 @@
//! determine what to do when a non-modifier key is pressed.
use std::borrow::Cow;
use std::mem;
+use std::time::Instant;
use copypasta::{Clipboard, Load, Buffer};
use glutin::{ElementState, VirtualKeyCode, MouseButton};
use glutin::{Mods, mods};
use glutin::{TouchPhase, MouseScrollDelta};
-use event::{Mouse};
+use config;
+use event::{ClickState, Mouse};
use index::{Line, Column, Side, Point};
-use term::mode::{self, TermMode};
use term::SizeInfo;
+use term::mode::{self, TermMode};
use util::fmt::Red;
/// Processes input from glutin.
@@ -41,6 +43,7 @@ use util::fmt::Red;
pub struct Processor<'a, A: 'a> {
pub key_bindings: &'a [KeyBinding],
pub mouse_bindings: &'a [MouseBinding],
+ pub mouse_config: &'a config::Mouse,
pub ctx: A,
}
@@ -51,7 +54,10 @@ pub trait ActionContext {
fn copy_selection(&self, Buffer);
fn clear_selection(&mut self);
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_coords(&self) -> Option<Point>;
}
/// 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) {
- if self.ctx.terminal_mode().intersects(mode::MOUSE_REPORT_CLICK | mode::MOUSE_MOTION) {
- self.mouse_report(0);
- return;
+ pub fn on_mouse_double_click(&mut self) {
+ if let Some(point) = self.ctx.mouse_coords() {
+ self.ctx.semantic_selection(point);
}
+ }
- 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) {
@@ -422,14 +458,136 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> {
#[cfg(test)]
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;
+ #[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 {
{
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! {
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 },
diff --git a/src/term/mod.rs b/src/term/mod.rs
index 0dc5532c..b0ca2a59 100644
--- a/src/term/mod.rs
+++ b/src/term/mod.rs
@@ -20,8 +20,8 @@ use std::cmp::min;
use std::io;
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
-use grid::{Grid, ClearRegion, ToRange};
-use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive};
+use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange};
+use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side};
use selection::{Span, Selection};
use config::{Config};
@@ -352,6 +352,8 @@ pub struct Term {
/// Saved cursor from alt grid
cursor_save_alt: Cursor,
+
+ semantic_escape_chars: String,
}
/// Terminal size info
@@ -436,11 +438,13 @@ impl Term {
size_info: size,
empty_cell: template,
custom_cursor_colors: config.custom_cursor_colors(),
+ semantic_escape_chars: config.selection().semantic_escape_chars.clone(),
}
}
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]
@@ -448,6 +452,64 @@ impl Term {
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 {
/// Need a generic push() for the Append trait
trait PushChar {
@@ -1283,12 +1345,85 @@ impl ansi::Handler for Term {
mod tests {
extern crate serde_json;
- use super::{Term, limit, SizeInfo};
+ use super::{Cell, Term, limit, SizeInfo};
+ use term::cell;
use grid::Grid;
use index::{Point, Line, Column};
- use term::{Cell};
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
///