feat: show current-line git blame by default
This commit is contained in:
+116
-3
@@ -12,8 +12,9 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
blame::BlameWorker,
|
||||
diff::{Focus, Operation as DiffOperation, Outcome as DiffOutcome},
|
||||
editor::{Cursor, Editor, Effect, Input, Invocation, Mode},
|
||||
editor::{BlameSource, Cursor, Editor, Effect, Input, Invocation, Mode},
|
||||
lsp::Lsp,
|
||||
plugin::{
|
||||
DocumentEvent, DocumentEventKind, DocumentSnapshot, PluginEvent, PluginUpdate,
|
||||
@@ -173,6 +174,9 @@ pub struct EditorSession {
|
||||
pending_completion: bool,
|
||||
hover_owner: Option<String>,
|
||||
diff_workers: BTreeMap<usize, DiffWorker>,
|
||||
blame_worker: Option<BlameWorker>,
|
||||
blame_source: Option<BlameSource>,
|
||||
blame_request_id: u64,
|
||||
battery: Option<crate::battery::Battery>,
|
||||
battery_enabled: bool,
|
||||
last_file_refresh: Instant,
|
||||
@@ -225,6 +229,7 @@ impl EditorSession {
|
||||
config.clipboard_mode
|
||||
));
|
||||
}
|
||||
editor.set_git_blame(config.git_blame);
|
||||
editor.set_diff_context_options(config.diff_context_lines, config.diff_expand_lines);
|
||||
editor.set_diff_display_options(config.diff_view_mode, config.diff_split_orientation);
|
||||
if let Err(error) =
|
||||
@@ -274,6 +279,9 @@ impl EditorSession {
|
||||
pending_completion: false,
|
||||
hover_owner: None,
|
||||
diff_workers: BTreeMap::new(),
|
||||
blame_worker: None,
|
||||
blame_source: None,
|
||||
blame_request_id: 0,
|
||||
battery: None,
|
||||
battery_enabled: false,
|
||||
last_file_refresh: Instant::now(),
|
||||
@@ -512,6 +520,7 @@ impl EditorSession {
|
||||
}
|
||||
|
||||
fn synchronize_documents(&mut self) -> bool {
|
||||
let blame_changed = self.poll_blame();
|
||||
// Leaving a request's source invalidates it permanently, even if history returns there.
|
||||
if let Some(requests) = self.requests.get_mut("core")
|
||||
&& requests.latest.is_some_and(|(_, source)| {
|
||||
@@ -536,7 +545,7 @@ impl EditorSession {
|
||||
}
|
||||
if !self.lsp.is_enabled() && !self.plugins.values().any(RubyPlugin::is_async) {
|
||||
self.documents.clear();
|
||||
return self.refresh_lsp_names();
|
||||
return self.refresh_lsp_names() | blame_changed;
|
||||
}
|
||||
let mut events = Vec::new();
|
||||
if !self.editor.should_quit() {
|
||||
@@ -612,7 +621,7 @@ impl EditorSession {
|
||||
});
|
||||
}
|
||||
let mut failures = Vec::new();
|
||||
let mut changed = false;
|
||||
let mut changed = blame_changed;
|
||||
for event in &events {
|
||||
if let Err(error) = self.lsp.notify_document(event) {
|
||||
self.editor.set_message(format!("LSP: {error}"));
|
||||
@@ -1013,6 +1022,56 @@ impl EditorSession {
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
fn poll_blame(&mut self) -> bool {
|
||||
let mut changed = false;
|
||||
if !self
|
||||
.blame_source
|
||||
.as_ref()
|
||||
.is_some_and(|source| self.editor.blame_source_is_current(source))
|
||||
{
|
||||
self.blame_source = self.editor.blame_source();
|
||||
self.blame_request_id = self.blame_request_id.wrapping_add(1);
|
||||
changed |= self.editor.clear_line_blame();
|
||||
if let Some(source) = &self.blame_source {
|
||||
if self.blame_worker.is_none() {
|
||||
self.blame_worker = BlameWorker::start().ok();
|
||||
}
|
||||
if let Some(worker) = &mut self.blame_worker
|
||||
&& worker
|
||||
.request(
|
||||
self.blame_request_id,
|
||||
source.path.clone(),
|
||||
self.editor.lines().join("\n"),
|
||||
source.row,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
self.blame_worker = None;
|
||||
}
|
||||
} else {
|
||||
self.blame_worker = None;
|
||||
}
|
||||
}
|
||||
while let Some((id, result)) = self.blame_worker.as_mut().and_then(BlameWorker::poll) {
|
||||
changed |= self.apply_blame_result(id, result);
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn apply_blame_result(
|
||||
&mut self,
|
||||
id: u64,
|
||||
result: Result<Option<crate::blame::LineBlame>, String>,
|
||||
) -> bool {
|
||||
if id == self.blame_request_id
|
||||
&& let Some(source) = &self.blame_source
|
||||
&& let Ok(Some(blame)) = result
|
||||
{
|
||||
return self.editor.set_line_blame(source.clone(), blame);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn poll_battery(&mut self) -> bool {
|
||||
let enabled = self.editor.label("battery").is_empty()
|
||||
&& [
|
||||
@@ -1128,6 +1187,60 @@ mod tests {
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn blame_sources_are_deduplicated_and_stale_results_cannot_reappear() {
|
||||
let fixture = Fixture::new();
|
||||
let path = fixture.file("source.txt", "first\nsecond");
|
||||
let mut session = EditorSession::open(Some(path.clone()), vec![], None).unwrap();
|
||||
assert!(session.editor.git_blame());
|
||||
let first = session.blame_request_id;
|
||||
session.poll_blame();
|
||||
assert_eq!(session.blame_request_id, first);
|
||||
let blame = || {
|
||||
Ok(Some(crate::blame::LineBlame {
|
||||
commit: "0123456789".into(),
|
||||
author: "Author".into(),
|
||||
summary: "Original".into(),
|
||||
uncommitted: false,
|
||||
}))
|
||||
};
|
||||
assert!(session.apply_blame_result(first, blame()));
|
||||
assert_eq!(
|
||||
session.editor.line_blame(),
|
||||
Some("Author · Original (01234567)")
|
||||
);
|
||||
session.handle(Input::Down);
|
||||
assert!(session.editor.line_blame().is_none());
|
||||
session.handle(Input::Up);
|
||||
assert!(!session.apply_blame_result(first, blame()));
|
||||
let source = session.editor.blame_source().unwrap();
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('x'));
|
||||
assert!(
|
||||
!session
|
||||
.editor
|
||||
.set_line_blame(source, blame().unwrap().unwrap())
|
||||
);
|
||||
session.handle(Input::Escape);
|
||||
session.execute("git_blame.toggle");
|
||||
assert!(!session.editor.git_blame());
|
||||
assert!(session.blame_worker.is_none());
|
||||
assert!(session.blame_source.is_none());
|
||||
assert!(session.editor.line_blame().is_none());
|
||||
assert!(!session.apply_blame_result(session.blame_request_id, blame()));
|
||||
session.poll_blame();
|
||||
assert!(session.blame_worker.is_none());
|
||||
let config = fixture.file(
|
||||
"config.rb",
|
||||
"Vamp.configure { |config| config.git_blame = false }",
|
||||
);
|
||||
let mut disabled = EditorSession::open(Some(path), vec![], Some(&config)).unwrap();
|
||||
assert!(!disabled.editor.git_blame());
|
||||
assert!(disabled.blame_worker.is_none());
|
||||
disabled.poll_blame();
|
||||
assert!(disabled.blame_worker.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_navigation_resolves_utf16_and_validates_full_ranges_atomically() {
|
||||
use crate::plugin::Utf16Range;
|
||||
|
||||
Reference in New Issue
Block a user