Implement copying selection for macOS

Still need automatic loading into selection copy buffer for linux.
This commit is contained in:
Joe Wilm 2016-12-26 22:52:37 -05:00
parent d28a734473
commit ae470bf68b
7 changed files with 172 additions and 8 deletions

View File

@ -116,6 +116,7 @@ colors:
# bytes. Possible values of `action` include `Paste` and `PasteSelection`.
key_bindings:
- { key: V, mods: Command, action: Paste }
- { key: C, mods: Command, action: Copy }
- { key: Home, chars: "\x1b[H", mode: ~AppCursor }
- { key: Home, chars: "\x1b[1~", mode: AppCursor }
- { key: End, chars: "\x1b[F", mode: ~AppCursor }

View File

@ -280,6 +280,7 @@ impl de::Deserialize for ActionWrapper {
{
Ok(ActionWrapper(match value {
"Paste" => Action::Paste,
"Copy" => Action::Copy,
"PasteSelection" => Action::PasteSelection,
_ => return Err(E::invalid_value("invalid value for Action")),
}))

View File

@ -148,7 +148,6 @@ impl<N: Notify> Processor<N> {
},
glutin::Event::KeyboardInput(state, _code, key, mods, string) => {
processor.process_key(state, key, mods, string);
processor.ctx.selection.clear();
},
glutin::Event::MouseInput(state, button) => {
processor.mouse_input(state, button);

View File

@ -148,8 +148,14 @@ impl Action {
ctx.notifier.notify(s.clone().into_bytes())
},
Action::Copy => {
// so... need access to terminal state. and the selection.
unimplemented!();
if let Some(selection) = ctx.selection.span() {
let buf = ctx.terminal.string_from_selection(&selection);
Clipboard::new()
.expect("get clipboard")
.store_primary(buf)
.expect("copy into clipboard");
}
},
Action::Paste |
Action::PasteSelection => {
@ -328,6 +334,7 @@ impl<'a, N: Notify + 'a> Processor<'a, N> {
// Didn't process a binding; print the provided character
if let Some(string) = string {
self.ctx.notifier.notify(string.into_bytes());
self.ctx.selection.clear();
}
}
}

View File

@ -21,7 +21,7 @@
use std::mem;
use std::ops::RangeInclusive;
use index::{Location, Column, Side, Linear};
use index::{Location, Column, Side, Linear, Line};
use grid::ToRange;
/// The area selected
@ -94,7 +94,7 @@ impl Selection {
pub fn span(&self) -> Option<Span> {
match *self {
Selection::Active {ref start, ref end, ref start_side, ref end_side } => {
Selection::Active { ref start, ref end, ref start_side, ref end_side } => {
let (front, tail, front_side, tail_side) = if *start > *end {
// Selected upward; start/end are swapped
(end, start, end_side, start_side)
@ -180,6 +180,43 @@ pub struct Span {
}
impl Span {
pub fn to_locations(&self, cols: Column) -> (Location, Location) {
match self.ty {
SpanType::Inclusive => (self.front, self.tail),
SpanType::Exclusive => {
(Span::wrap_start(self.front, cols), Span::wrap_end(self.tail, cols))
},
SpanType::ExcludeFront => (Span::wrap_start(self.front, cols), self.tail),
SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, cols))
}
}
fn wrap_start(mut start: Location, cols: Column) -> Location {
if start.col == cols - 1 {
Location {
line: start.line + 1,
col: Column(0),
}
} else {
start.col += 1;
start
}
}
fn wrap_end(end: Location, cols: Column) -> Location {
if end.col == Column(0) && end.line != Line(0) {
Location {
line: end.line - 1,
col: cols
}
} else {
Location {
line: end.line,
col: end.col - 1
}
}
}
#[inline]
fn exclude_start(start: Linear) -> Linear {
start + 1

View File

@ -11,11 +11,11 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::mem;
use ansi::{NamedColor, Color};
use grid;
use index::Column;
bitflags! {
#[derive(Serialize, Deserialize)]
@ -35,6 +35,27 @@ pub struct Cell {
pub flags: Flags,
}
/// Get the length of occupied cells in a line
pub trait LineLength {
/// Calculate the occupied line length
fn line_length(&self) -> Column;
}
impl LineLength for grid::Row<Cell> {
fn line_length(&self) -> Column {
let mut length = Column(0);
for (index, cell) in self[..].iter().rev().enumerate() {
if cell.c != ' ' {
length = Column(self.len() - index);
break;
}
}
length
}
}
impl Cell {
pub fn bold(&self) -> bool {
self.flags.contains(BOLD)
@ -67,3 +88,21 @@ impl Cell {
mem::swap(&mut self.fg, &mut self.bg);
}
}
#[cfg(test)]
mod tests {
use super::{Cell, LineLength};
use grid::Row;
use index::Column;
use ansi::Color;
#[test]
fn line_length_works() {
let template = Cell::new(' ', Color::Indexed(0), Color::Indexed(0));
let mut row = Row::new(Column(10), &template);
row[Column(5)].c = 'a';
assert_eq!(row.line_length(), Column(6));
}
}

View File

@ -21,10 +21,11 @@ use std::io;
use ansi::{self, Color, NamedColor, Attr, Handler};
use grid::{Grid, ClearRegion, ToRange};
use index::{self, Cursor, Column, Line, Linear};
use selection::Selection;
use selection::{Span, Selection};
pub mod cell;
pub use self::cell::Cell;
use self::cell::LineLength;
/// Iterator that yields cells needing render
///
@ -309,6 +310,85 @@ impl Term {
}
}
pub fn string_from_selection(&self, span: &Span) -> String {
trait Append<T> {
fn append(&mut self, grid: &Grid<Cell>, line: Line, cols: T);
}
use std::ops::{Range, RangeTo, RangeFrom, RangeFull};
impl Append<Range<Column>> for String {
fn append(&mut self, grid: &Grid<Cell>, line: Line, cols: Range<Column>) {
let line = &grid[line];
let line_length = line.line_length();
let line_end = cmp::min(line_length, cols.end + 1);
for cell in &line[cols.start..line_end] {
self.push(cell.c);
}
}
}
impl Append<RangeTo<Column>> for String {
#[inline]
fn append(&mut self, grid: &Grid<Cell>, line: Line, cols: RangeTo<Column>) {
self.append(grid, line, Column(0)..cols.end);
}
}
impl Append<RangeFrom<Column>> for String {
#[inline]
fn append(&mut self, grid: &Grid<Cell>, line: Line, cols: RangeFrom<Column>) {
self.append(grid, line, cols.start..Column(usize::max_value() - 1));
self.push('\n');
}
}
impl Append<RangeFull> for String {
#[inline]
fn append(&mut self, grid: &Grid<Cell>, line: Line, _: RangeFull) {
self.append(grid, line, Column(0)..Column(usize::max_value() - 1));
self.push('\n');
}
}
let mut res = String::new();
let (start, end) = span.to_locations(self.grid.num_cols());
let line_count = end.line - start.line;
match line_count {
// Selection within single line
Line(0) => {
res.append(&self.grid, start.line, start.col..end.col);
},
// Selection ends on line following start
Line(1) => {
// Starting line
res.append(&self.grid, start.line, start.col..);
// Ending line
res.append(&self.grid, end.line, ..end.col);
},
// Multi line selection
_ => {
// Starting line
res.append(&self.grid, start.line, start.col..);
let middle_range = (start.line + 1)..(end.line);
for line in middle_range {
res.append(&self.grid, line, ..);
}
// Ending line
res.append(&self.grid, end.line, ..end.col);
}
}
res
}
/// Convert the given pixel values to a grid coordinate
///
/// The mouse coordinates are expected to be relative to the top left. The