Skip to content

Commit e371153

Browse files
authored
Report failure to copy to clipboard (#1410)
* (refactor) move copy_commit_hash from revlog into commitlist, and make fewer functions public in commitlist * (refactor) reduce duplication in commit copying code; use already-stored commits instead of looking up items * (clipboard) actually check subprocess exit status, and report failure instead of ignoring it * (commitlist) display popup with copy failure instead of exiting the application on error
1 parent a172b18 commit e371153

File tree

5 files changed

+56
-121
lines changed

5 files changed

+56
-121
lines changed

src/clipboard.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ fn exec_copy_with_args(
1717
.args(args)
1818
.stdin(Stdio::piped())
1919
.stdout(Stdio::null())
20+
.stderr(Stdio::piped())
2021
.spawn()
2122
.map_err(|e| anyhow!("`{:?}`: {}", command, e))?;
2223

@@ -27,11 +28,20 @@ fn exec_copy_with_args(
2728
.write_all(text.as_bytes())
2829
.map_err(|e| anyhow!("`{:?}`: {}", command, e))?;
2930

30-
process
31-
.wait()
31+
let out = process
32+
.wait_with_output()
3233
.map_err(|e| anyhow!("`{:?}`: {}", command, e))?;
3334

34-
Ok(())
35+
if out.status.success() {
36+
Ok(())
37+
} else {
38+
let msg = if out.stderr.is_empty() {
39+
format!("{}", out.status).into()
40+
} else {
41+
String::from_utf8_lossy(&out.stderr)
42+
};
43+
Err(anyhow!("`{command:?}`: {msg}"))
44+
}
3545
}
3646

3747
fn exec_copy(command: &str, text: &str) -> Result<()> {

src/components/commitlist.rs

Lines changed: 36 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
},
77
keys::{key_match, SharedKeyConfig},
88
queue::{InternalEvent, Queue},
9-
strings::{self, copy_fail, copy_success, symbol},
9+
strings::{self, symbol},
1010
ui::style::{SharedTheme, Theme},
1111
ui::{calc_scroll_top, draw_scrollbar},
1212
};
@@ -143,14 +143,6 @@ impl CommitList {
143143
self.marked.clear();
144144
}
145145

146-
///
147-
pub fn marked_indexes(&self) -> Vec<usize> {
148-
let (indexes, _): (Vec<usize>, Vec<_>) =
149-
self.marked.iter().copied().unzip();
150-
151-
indexes
152-
}
153-
154146
///
155147
pub fn marked_commits(&self) -> Vec<CommitId> {
156148
let (_, commits): (Vec<_>, Vec<CommitId>) =
@@ -159,106 +151,48 @@ impl CommitList {
159151
commits
160152
}
161153

162-
fn marked_consecutive(&self) -> bool {
163-
let marked = self.marked_indexes();
164-
165-
for i in 1..marked.len() {
166-
if marked[i - 1] + 1 != marked[i] {
167-
return false;
168-
}
169-
}
170-
171-
true
172-
}
173-
174-
pub fn copy_marked_hashes(&self) -> Result<()> {
175-
if self.marked_consecutive() {
176-
let m = self.marked_indexes();
177-
178-
let first = self.items.iter().nth(m[0]);
179-
180-
let last = self.items.iter().nth(m[m.len() - 1]);
181-
182-
if let (Some(f), Some(l)) = (first, last) {
183-
let yank =
184-
format!("{}^..{}", f.hash_short, l.hash_short);
185-
if let Err(e) = crate::clipboard::copy_string(&yank) {
186-
self.queue.push(InternalEvent::ShowErrorMsg(
187-
copy_fail(&e.to_string()),
188-
));
189-
return Err(e);
190-
}
191-
self.queue.push(InternalEvent::ShowInfoMsg(
192-
copy_success(&yank),
193-
));
194-
};
195-
} else {
196-
let separate = self
197-
.marked_indexes()
154+
pub fn copy_commit_hash(&self) -> Result<()> {
155+
let marked = self.marked.as_slice();
156+
let yank: Option<Cow<str>> = match marked {
157+
[] => self
158+
.items
198159
.iter()
199-
.map(|e| {
200-
self.items
160+
.nth(
161+
self.selection
162+
.saturating_sub(self.items.index_offset()),
163+
)
164+
.map(|e| Cow::Borrowed(e.hash_short.as_ref())),
165+
[(_idx, commit)] => {
166+
Some(commit.get_short_string().into())
167+
}
168+
[first, .., last] => {
169+
let marked_consecutive =
170+
marked.windows(2).all(|w| w[0].0 + 1 == w[1].0);
171+
172+
let yank = if marked_consecutive {
173+
format!(
174+
"{}^..{}",
175+
first.1.get_short_string(),
176+
last.1.get_short_string()
177+
)
178+
} else {
179+
marked
201180
.iter()
202-
.nth(*e)
203-
.map_or_else(String::new, |le| {
204-
le.hash_short.to_string()
181+
.map(|(_idx, commit)| {
182+
commit.get_short_string()
205183
})
206-
})
207-
.join(" ");
208-
209-
if let Err(e) = crate::clipboard::copy_string(&separate) {
210-
self.queue.push(InternalEvent::ShowErrorMsg(
211-
copy_fail(&e.to_string()),
212-
));
213-
return Err(e);
184+
.join(" ")
185+
};
186+
Some(yank.into())
214187
}
188+
};
189+
190+
if let Some(yank) = yank {
191+
crate::clipboard::copy_string(&yank)?;
215192
self.queue.push(InternalEvent::ShowInfoMsg(
216-
copy_success(&separate),
193+
strings::copy_success(&yank),
217194
));
218195
}
219-
220-
Ok(())
221-
}
222-
223-
pub fn copy_entry_hash(&self) -> Result<()> {
224-
match self.marked_count() {
225-
0 => {
226-
if let Some(e) = self.items.iter().nth(
227-
self.selection
228-
.saturating_sub(self.items.index_offset()),
229-
) {
230-
if let Err(e) =
231-
crate::clipboard::copy_string(&e.hash_short)
232-
{
233-
self.queue.push(InternalEvent::ShowErrorMsg(
234-
copy_fail(&e.to_string()),
235-
));
236-
return Err(e);
237-
}
238-
self.queue.push(InternalEvent::ShowInfoMsg(
239-
copy_success(&e.hash_short),
240-
));
241-
}
242-
}
243-
1 => {
244-
if let Some(e) =
245-
self.items.iter().nth(self.marked_indexes()[0])
246-
{
247-
if let Err(e) =
248-
crate::clipboard::copy_string(&e.hash_short)
249-
{
250-
self.queue.push(InternalEvent::ShowErrorMsg(
251-
copy_fail(&e.to_string()),
252-
));
253-
return Err(e);
254-
}
255-
self.queue.push(InternalEvent::ShowInfoMsg(
256-
copy_success(&e.hash_short),
257-
));
258-
}
259-
}
260-
_ => {}
261-
}
262196
Ok(())
263197
}
264198

src/components/utils/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub mod statustree;
1212
/// It will show a popup in that case
1313
#[macro_export]
1414
macro_rules! try_or_popup {
15-
($self:ident, $msg:literal, $e:expr) => {
15+
($self:ident, $msg:expr, $e:expr) => {
1616
if let Err(err) = $e {
1717
::log::error!("{} {}", $msg, err);
1818
$self.queue.push(InternalEvent::ShowErrorMsg(format!(

src/strings.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub static PUSH_TAGS_STATES_DONE: &str = "done";
3030
pub static POPUP_TITLE_SUBMODULES: &str = "Submodules";
3131
pub static POPUP_TITLE_FUZZY_FIND: &str = "Fuzzy Finder";
3232

33-
pub static POPUP_FAIL_COPY: &str = "Failed to copy the Text";
33+
pub static POPUP_FAIL_COPY: &str = "Failed to copy text";
3434
pub static POPUP_SUCCESS_COPY: &str = "Copied Text";
3535

3636
pub mod symbol {
@@ -360,10 +360,6 @@ pub fn copy_success(s: &str) -> String {
360360
format!("{POPUP_SUCCESS_COPY} \"{s}\"")
361361
}
362362

363-
pub fn copy_fail(e: &str) -> String {
364-
format!("{POPUP_FAIL_COPY}: {e}")
365-
}
366-
367363
pub fn ellipsis_trim_start(s: &str, width: usize) -> Cow<str> {
368364
if s.width() <= width {
369365
Cow::Borrowed(s)

src/tabs/revlog.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,6 @@ impl Revlog {
163163
self.list.selected_entry().map(|e| e.id)
164164
}
165165

166-
fn copy_commit_hash(&self) -> Result<()> {
167-
if self.list.marked_count() > 1 {
168-
self.list.copy_marked_hashes()?;
169-
} else {
170-
self.list.copy_entry_hash()?;
171-
}
172-
Ok(())
173-
}
174-
175166
fn selected_commit_tags(
176167
&self,
177168
commit: &Option<CommitId>,
@@ -260,7 +251,11 @@ impl Component for Revlog {
260251
self.update()?;
261252
return Ok(EventState::Consumed);
262253
} else if key_match(k, self.key_config.keys.copy) {
263-
self.copy_commit_hash()?;
254+
try_or_popup!(
255+
self,
256+
strings::POPUP_FAIL_COPY,
257+
self.list.copy_commit_hash()
258+
);
264259
return Ok(EventState::Consumed);
265260
} else if key_match(k, self.key_config.keys.push) {
266261
self.queue.push(InternalEvent::PushTags);

0 commit comments

Comments
 (0)