Skip to content

Commit 9f05fff

Browse files
cruesslerStephan Dilly
authored and
Stephan Dilly
committed
Add async wrapper to blame
- Rename `self.path` to `self.file_path`. - Take into account that `draw_scrollbar` subtracts the area’s height before calculating the scrollbar’s position. - Show in title if blame is pending.
1 parent 7072258 commit 9f05fff

File tree

4 files changed

+284
-30
lines changed

4 files changed

+284
-30
lines changed

asyncgit/src/blame.rs

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use crate::{
2+
error::Result,
3+
hash,
4+
sync::{self, BlameAt, FileBlame},
5+
AsyncNotification, CWD,
6+
};
7+
use crossbeam_channel::Sender;
8+
use std::{
9+
hash::Hash,
10+
sync::{
11+
atomic::{AtomicUsize, Ordering},
12+
Arc, Mutex,
13+
},
14+
};
15+
16+
///
17+
#[derive(Hash, Clone, PartialEq)]
18+
pub struct BlameParams {
19+
/// path to the file to blame
20+
pub file_path: String,
21+
}
22+
23+
struct Request<R, A>(R, Option<A>);
24+
25+
#[derive(Default, Clone)]
26+
struct LastResult<P, R> {
27+
params: P,
28+
hash: u64,
29+
result: R,
30+
}
31+
32+
///
33+
pub struct AsyncBlame {
34+
current: Arc<Mutex<Request<u64, FileBlame>>>,
35+
last: Arc<Mutex<Option<LastResult<BlameParams, FileBlame>>>>,
36+
sender: Sender<AsyncNotification>,
37+
pending: Arc<AtomicUsize>,
38+
}
39+
40+
impl AsyncBlame {
41+
///
42+
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
43+
Self {
44+
current: Arc::new(Mutex::new(Request(0, None))),
45+
last: Arc::new(Mutex::new(None)),
46+
sender: sender.clone(),
47+
pending: Arc::new(AtomicUsize::new(0)),
48+
}
49+
}
50+
51+
///
52+
pub fn last(
53+
&mut self,
54+
) -> Result<Option<(BlameParams, FileBlame)>> {
55+
let last = self.last.lock()?;
56+
57+
Ok(last.clone().map(|last_result| {
58+
(last_result.params, last_result.result)
59+
}))
60+
}
61+
62+
///
63+
pub fn refresh(&mut self) -> Result<()> {
64+
if let Ok(Some(param)) = self.get_last_param() {
65+
self.clear_current()?;
66+
self.request(param)?;
67+
}
68+
Ok(())
69+
}
70+
71+
///
72+
pub fn is_pending(&self) -> bool {
73+
self.pending.load(Ordering::Relaxed) > 0
74+
}
75+
76+
///
77+
pub fn request(
78+
&mut self,
79+
params: BlameParams,
80+
) -> Result<Option<FileBlame>> {
81+
log::trace!("request");
82+
83+
let hash = hash(&params);
84+
85+
{
86+
let mut current = self.current.lock()?;
87+
88+
if current.0 == hash {
89+
return Ok(current.1.clone());
90+
}
91+
92+
current.0 = hash;
93+
current.1 = None;
94+
}
95+
96+
let arc_current = Arc::clone(&self.current);
97+
let arc_last = Arc::clone(&self.last);
98+
let sender = self.sender.clone();
99+
let arc_pending = Arc::clone(&self.pending);
100+
101+
self.pending.fetch_add(1, Ordering::Relaxed);
102+
103+
rayon_core::spawn(move || {
104+
let notify = Self::get_blame_helper(
105+
params,
106+
&arc_last,
107+
&arc_current,
108+
hash,
109+
);
110+
111+
let notify = match notify {
112+
Err(err) => {
113+
log::error!("get_blame_helper error: {}", err);
114+
true
115+
}
116+
Ok(notify) => notify,
117+
};
118+
119+
arc_pending.fetch_sub(1, Ordering::Relaxed);
120+
121+
sender
122+
.send(if notify {
123+
AsyncNotification::Blame
124+
} else {
125+
AsyncNotification::FinishUnchanged
126+
})
127+
.expect("error sending blame");
128+
});
129+
130+
Ok(None)
131+
}
132+
133+
fn get_blame_helper(
134+
params: BlameParams,
135+
arc_last: &Arc<
136+
Mutex<Option<LastResult<BlameParams, FileBlame>>>,
137+
>,
138+
arc_current: &Arc<Mutex<Request<u64, FileBlame>>>,
139+
hash: u64,
140+
) -> Result<bool> {
141+
let file_blame = sync::blame::blame_file(
142+
CWD,
143+
&params.file_path,
144+
&BlameAt::Head,
145+
)?;
146+
147+
let mut notify = false;
148+
{
149+
let mut current = arc_current.lock()?;
150+
if current.0 == hash {
151+
current.1 = Some(file_blame.clone());
152+
notify = true;
153+
}
154+
}
155+
156+
{
157+
let mut last = arc_last.lock()?;
158+
*last = Some(LastResult {
159+
result: file_blame,
160+
hash,
161+
params,
162+
});
163+
}
164+
165+
Ok(notify)
166+
}
167+
168+
fn get_last_param(&self) -> Result<Option<BlameParams>> {
169+
Ok(self
170+
.last
171+
.lock()?
172+
.clone()
173+
.map(|last_result| last_result.params))
174+
}
175+
176+
fn clear_current(&mut self) -> Result<()> {
177+
let mut current = self.current.lock()?;
178+
current.0 = 0;
179+
current.1 = None;
180+
Ok(())
181+
}
182+
}

asyncgit/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//TODO: get this in someday since expect still leads us to crashes sometimes
2121
// #![deny(clippy::expect_used)]
2222

23+
mod blame;
2324
pub mod cached;
2425
mod commit_files;
2526
mod diff;
@@ -35,6 +36,7 @@ pub mod sync;
3536
mod tags;
3637

3738
pub use crate::{
39+
blame::{AsyncBlame, BlameParams},
3840
commit_files::AsyncCommitFiles,
3941
diff::{AsyncDiff, DiffParams, DiffType},
4042
fetch::{AsyncFetch, FetchRequest},
@@ -75,6 +77,8 @@ pub enum AsyncNotification {
7577
PushTags,
7678
///
7779
Fetch,
80+
///
81+
Blame,
7882
}
7983

8084
/// current working directory `./`

src/app.rs

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ impl App {
9898
),
9999
blame_file_popup: BlameFileComponent::new(
100100
&queue,
101+
sender,
101102
&strings::blame_title(&key_config),
102103
theme.clone(),
103104
key_config.clone(),
@@ -324,6 +325,7 @@ impl App {
324325
self.status_tab.update_git(ev)?;
325326
self.stashing_tab.update_git(ev)?;
326327
self.revlog.update_git(ev)?;
328+
self.blame_file_popup.update_git(ev)?;
327329
self.inspect_commit_popup.update_git(ev)?;
328330
self.push_popup.update_git(ev)?;
329331
self.push_tags_popup.update_git(ev)?;
@@ -346,6 +348,7 @@ impl App {
346348
self.status_tab.anything_pending()
347349
|| self.revlog.any_work_pending()
348350
|| self.stashing_tab.anything_pending()
351+
|| self.blame_file_popup.any_work_pending()
349352
|| self.inspect_commit_popup.any_work_pending()
350353
|| self.input.is_state_changing()
351354
|| self.push_popup.any_work_pending()

0 commit comments

Comments
 (0)