mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-25 14:05:41 -05:00
Implement copying selection for macOS
Still need automatic loading into selection copy buffer for linux.
This commit is contained in:
parent
d28a734473
commit
ae470bf68b
7 changed files with 172 additions and 8 deletions
|
@ -116,6 +116,7 @@ colors:
|
||||||
# bytes. Possible values of `action` include `Paste` and `PasteSelection`.
|
# bytes. Possible values of `action` include `Paste` and `PasteSelection`.
|
||||||
key_bindings:
|
key_bindings:
|
||||||
- { key: V, mods: Command, action: Paste }
|
- { key: V, mods: Command, action: Paste }
|
||||||
|
- { key: C, mods: Command, action: Copy }
|
||||||
- { key: Home, chars: "\x1b[H", mode: ~AppCursor }
|
- { key: Home, chars: "\x1b[H", mode: ~AppCursor }
|
||||||
- { key: Home, chars: "\x1b[1~", mode: AppCursor }
|
- { key: Home, chars: "\x1b[1~", mode: AppCursor }
|
||||||
- { key: End, chars: "\x1b[F", mode: ~AppCursor }
|
- { key: End, chars: "\x1b[F", mode: ~AppCursor }
|
||||||
|
|
|
@ -280,6 +280,7 @@ impl de::Deserialize for ActionWrapper {
|
||||||
{
|
{
|
||||||
Ok(ActionWrapper(match value {
|
Ok(ActionWrapper(match value {
|
||||||
"Paste" => Action::Paste,
|
"Paste" => Action::Paste,
|
||||||
|
"Copy" => Action::Copy,
|
||||||
"PasteSelection" => Action::PasteSelection,
|
"PasteSelection" => Action::PasteSelection,
|
||||||
_ => return Err(E::invalid_value("invalid value for Action")),
|
_ => return Err(E::invalid_value("invalid value for Action")),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -148,7 +148,6 @@ impl<N: Notify> Processor<N> {
|
||||||
},
|
},
|
||||||
glutin::Event::KeyboardInput(state, _code, key, mods, string) => {
|
glutin::Event::KeyboardInput(state, _code, key, mods, string) => {
|
||||||
processor.process_key(state, key, mods, string);
|
processor.process_key(state, key, mods, string);
|
||||||
processor.ctx.selection.clear();
|
|
||||||
},
|
},
|
||||||
glutin::Event::MouseInput(state, button) => {
|
glutin::Event::MouseInput(state, button) => {
|
||||||
processor.mouse_input(state, button);
|
processor.mouse_input(state, button);
|
||||||
|
|
11
src/input.rs
11
src/input.rs
|
@ -148,8 +148,14 @@ impl Action {
|
||||||
ctx.notifier.notify(s.clone().into_bytes())
|
ctx.notifier.notify(s.clone().into_bytes())
|
||||||
},
|
},
|
||||||
Action::Copy => {
|
Action::Copy => {
|
||||||
// so... need access to terminal state. and the selection.
|
if let Some(selection) = ctx.selection.span() {
|
||||||
unimplemented!();
|
let buf = ctx.terminal.string_from_selection(&selection);
|
||||||
|
|
||||||
|
Clipboard::new()
|
||||||
|
.expect("get clipboard")
|
||||||
|
.store_primary(buf)
|
||||||
|
.expect("copy into clipboard");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Action::Paste |
|
Action::Paste |
|
||||||
Action::PasteSelection => {
|
Action::PasteSelection => {
|
||||||
|
@ -328,6 +334,7 @@ impl<'a, N: Notify + 'a> Processor<'a, N> {
|
||||||
// Didn't process a binding; print the provided character
|
// Didn't process a binding; print the provided character
|
||||||
if let Some(string) = string {
|
if let Some(string) = string {
|
||||||
self.ctx.notifier.notify(string.into_bytes());
|
self.ctx.notifier.notify(string.into_bytes());
|
||||||
|
self.ctx.selection.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use index::{Location, Column, Side, Linear};
|
use index::{Location, Column, Side, Linear, Line};
|
||||||
use grid::ToRange;
|
use grid::ToRange;
|
||||||
|
|
||||||
/// The area selected
|
/// The area selected
|
||||||
|
@ -94,7 +94,7 @@ impl Selection {
|
||||||
|
|
||||||
pub fn span(&self) -> Option<Span> {
|
pub fn span(&self) -> Option<Span> {
|
||||||
match *self {
|
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 {
|
let (front, tail, front_side, tail_side) = if *start > *end {
|
||||||
// Selected upward; start/end are swapped
|
// Selected upward; start/end are swapped
|
||||||
(end, start, end_side, start_side)
|
(end, start, end_side, start_side)
|
||||||
|
@ -180,6 +180,43 @@ pub struct Span {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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]
|
#[inline]
|
||||||
fn exclude_start(start: Linear) -> Linear {
|
fn exclude_start(start: Linear) -> Linear {
|
||||||
start + 1
|
start + 1
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use ansi::{NamedColor, Color};
|
use ansi::{NamedColor, Color};
|
||||||
|
use grid;
|
||||||
|
use index::Column;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -35,6 +35,27 @@ pub struct Cell {
|
||||||
pub flags: Flags,
|
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 {
|
impl Cell {
|
||||||
pub fn bold(&self) -> bool {
|
pub fn bold(&self) -> bool {
|
||||||
self.flags.contains(BOLD)
|
self.flags.contains(BOLD)
|
||||||
|
@ -67,3 +88,21 @@ impl Cell {
|
||||||
mem::swap(&mut self.fg, &mut self.bg);
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,10 +21,11 @@ use std::io;
|
||||||
use ansi::{self, Color, NamedColor, Attr, Handler};
|
use ansi::{self, Color, NamedColor, Attr, Handler};
|
||||||
use grid::{Grid, ClearRegion, ToRange};
|
use grid::{Grid, ClearRegion, ToRange};
|
||||||
use index::{self, Cursor, Column, Line, Linear};
|
use index::{self, Cursor, Column, Line, Linear};
|
||||||
use selection::Selection;
|
use selection::{Span, Selection};
|
||||||
|
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub use self::cell::Cell;
|
pub use self::cell::Cell;
|
||||||
|
use self::cell::LineLength;
|
||||||
|
|
||||||
/// Iterator that yields cells needing render
|
/// 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
|
/// Convert the given pixel values to a grid coordinate
|
||||||
///
|
///
|
||||||
/// The mouse coordinates are expected to be relative to the top left. The
|
/// The mouse coordinates are expected to be relative to the top left. The
|
||||||
|
|
Loading…
Reference in a new issue