Skip to content

Commit 8e1ac88

Browse files
committed
Copy selection to clipboard in diff view
1 parent 14fab80 commit 8e1ac88

File tree

6 files changed

+160
-20
lines changed

6 files changed

+160
-20
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ jobs:
3131
profile: minimal
3232
components: clippy
3333

34+
- name: Install dependencies for clipboard access
35+
if: matrix.os == 'ubuntu-latest'
36+
run: |
37+
sudo apt-get -qq install libxcb-shape0-dev libxcb-xfixes0-dev
38+
3439
- name: Build Debug
3540
run: |
3641
rustc --version
@@ -58,6 +63,10 @@ jobs:
5863
profile: minimal
5964
target: x86_64-unknown-linux-musl
6065

66+
- name: Install dependencies for clipboard access
67+
run: |
68+
sudo apt-get -qq install libxcb-shape0-dev libxcb-xfixes0-dev
69+
6170
- name: Setup MUSL
6271
run: |
6372
sudo apt-get -qq install musl-tools

Cargo.lock

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ serde = "1.0"
4040
anyhow = "1.0.32"
4141
unicode-width = "0.1"
4242
textwrap = "0.12"
43+
clipboard = "0.5"
4344

4445
[target.'cfg(not(windows))'.dependencies]
4546
pprof = { version = "0.3", features = ["flamegraph"], optional = true }

src/components/diff.rs

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{
2-
CommandBlocking, DrawableComponent, ExtendType, ScrollType,
2+
CommandBlocking, Direction, DrawableComponent, ScrollType,
33
};
44
use crate::{
55
components::{CommandInfo, Component},
@@ -10,6 +10,7 @@ use crate::{
1010
};
1111
use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD};
1212
use bytesize::ByteSize;
13+
use clipboard::{ClipboardContext, ClipboardProvider};
1314
use crossterm::event::Event;
1415
use std::{borrow::Cow, cell::Cell, cmp, path::Path};
1516
use tui::{
@@ -49,11 +50,30 @@ impl Selection {
4950
}
5051
}
5152

53+
fn modify(&mut self, direction: Direction, max: usize) {
54+
let start = self.get_start();
55+
let old_end = self.get_end();
56+
57+
*self = match direction {
58+
Direction::Up => {
59+
Self::Multiple(start, old_end.saturating_sub(1))
60+
}
61+
62+
Direction::Down => {
63+
Self::Multiple(start, cmp::min(old_end + 1, max))
64+
}
65+
};
66+
}
67+
5268
fn contains(&self, index: usize) -> bool {
5369
match self {
5470
Self::Single(start) => index == *start,
5571
Self::Multiple(start, end) => {
56-
*start <= index && index <= *end
72+
if start <= end {
73+
*start <= index && index <= *end
74+
} else {
75+
*end <= index && index <= *start
76+
}
5777
}
5878
}
5979
}
@@ -177,26 +197,45 @@ impl DiffComponent {
177197
Ok(())
178198
}
179199

180-
fn extend_selection(
200+
fn modify_selection(
181201
&mut self,
182-
extend_type: ExtendType,
202+
direction: Direction,
183203
) -> Result<()> {
184204
if let Some(diff) = &self.diff {
185205
let max = diff.lines.saturating_sub(1) as usize;
186-
let start = self.selection.get_start();
187-
let old_end = self.selection.get_end();
188206

189-
self.selection = match extend_type {
190-
ExtendType::Up => Selection::Multiple(
191-
start,
192-
cmp::max(start, old_end.saturating_sub(1)),
193-
),
207+
self.selection.modify(direction, max);
208+
}
194209

195-
ExtendType::Down => Selection::Multiple(
196-
start,
197-
cmp::min(old_end + 1, max),
198-
),
199-
};
210+
Ok(())
211+
}
212+
213+
fn copy_selection(&self) -> Result<()> {
214+
if let Some(diff) = &self.diff {
215+
let lines_to_copy: Vec<&str> = diff
216+
.hunks
217+
.iter()
218+
.flat_map(|hunk| hunk.lines.iter())
219+
.enumerate()
220+
.filter_map(|(i, line)| {
221+
if self.selection.contains(i) {
222+
Some(
223+
line.content
224+
.trim_matches(|c| {
225+
c == '\n' || c == '\r'
226+
})
227+
.as_ref(),
228+
)
229+
} else {
230+
None
231+
}
232+
})
233+
.collect();
234+
235+
let mut ctx: ClipboardContext = ClipboardProvider::new()
236+
.expect("failed to get access to clipboard");
237+
ctx.set_contents(lines_to_copy.join("\n"))
238+
.expect("failed to set clipboard contents");
200239
}
201240

202241
Ok(())
@@ -489,7 +528,7 @@ impl DrawableComponent for DiffComponent {
489528
self.scroll_top.set(calc_scroll_top(
490529
self.scroll_top.get(),
491530
self.current_size.get().1 as usize,
492-
self.selection.get_start(),
531+
self.selection.get_end(),
493532
));
494533

495534
let title =
@@ -570,11 +609,11 @@ impl Component for DiffComponent {
570609
Ok(true)
571610
}
572611
keys::SHIFT_DOWN => {
573-
self.extend_selection(ExtendType::Down)?;
612+
self.modify_selection(Direction::Down)?;
574613
Ok(true)
575614
}
576615
keys::SHIFT_UP => {
577-
self.extend_selection(ExtendType::Up)?;
616+
self.modify_selection(Direction::Up)?;
578617
Ok(true)
579618
}
580619
keys::END => {
@@ -618,6 +657,10 @@ impl Component for DiffComponent {
618657
}
619658
Ok(true)
620659
}
660+
keys::COPY => {
661+
self.copy_selection()?;
662+
Ok(true)
663+
}
621664
_ => Ok(false),
622665
};
623666
}

src/components/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ pub enum ScrollType {
106106
}
107107

108108
#[derive(Copy, Clone)]
109-
pub enum ExtendType {
109+
pub enum Direction {
110110
Up,
111111
Down,
112112
}

src/keys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub const SHIFT_UP: KeyEvent =
5050
pub const SHIFT_DOWN: KeyEvent =
5151
with_mod(KeyCode::Down, KeyModifiers::SHIFT);
5252
pub const ENTER: KeyEvent = no_mod(KeyCode::Enter);
53+
pub const COPY: KeyEvent = no_mod(KeyCode::Char('y'));
5354
pub const EDIT_FILE: KeyEvent = no_mod(KeyCode::Char('e'));
5455
pub const STATUS_STAGE_FILE: KeyEvent = no_mod(KeyCode::Enter);
5556
pub const STATUS_STAGE_ALL: KeyEvent = no_mod(KeyCode::Char('a'));

0 commit comments

Comments
 (0)