mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-03 04:34:21 -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`.
|
||||
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 }
|
||||
|
|
|
@ -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")),
|
||||
}))
|
||||
|
|
|
@ -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);
|
||||
|
|
11
src/input.rs
11
src/input.rs
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue