feat: show current-line git blame by default

This commit is contained in:
Valdemar Lorenzen
2026-10-13 12:21:03 +02:00
parent 5382307fcc
commit 7a3c27c30e
12 changed files with 1231 additions and 7 deletions
+163
View File
@@ -1834,6 +1834,36 @@ fn draw_document(
document_caret = Some(position);
}
}
if focused
&& document_caret.is_some()
&& let Some(label) = editor.line_blame()
&& let Some((screen_row, &(_, continuation, segment))) = document
.visible
.iter()
.enumerate()
.rev()
.find(|(_, (row, _, _))| *row == cursor.row)
{
let line = &editor.lines()[cursor.row];
let end = display_column(line, usize::MAX);
let start = end.saturating_sub(segment.start).saturating_add(2);
let label_width = UnicodeWidthStr::width(label);
if end >= segment.start
&& end <= segment.end
&& (!wrapped || continuation + 1 == document.wrap.segments(cursor.row, line).len())
&& start.saturating_add(label_width) <= width
{
frame.render_widget(
Paragraph::new(label).style(Style::default().fg(Color::DarkGray)),
Rect::new(
viewport.x + start as u16,
viewport.y + screen_row as u16,
label_width as u16,
1,
),
);
}
}
if !focused {
return;
}
@@ -7394,6 +7424,139 @@ end
assert!(titles.contains("2 [No Name]"), "{titles}");
}
#[test]
fn inline_blame_sanitizes_metadata_preserves_text_and_hides_stale_labels() {
let directory = Directory::new();
let mut editor = Editor::open(
Some(directory.file("source.txt", "界 e\u{301}\nsecond")),
vec![],
)
.unwrap();
let source = editor.blame_source().unwrap();
assert!(editor.set_line_blame(
source,
crate::blame::LineBlame {
commit: "0123456789abcdef".into(),
author: "A\tuthor".into(),
summary: "Summary\n\u{1b}done".into(),
uncommitted: false,
}
));
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 8)).unwrap();
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let screen = screen_text(&terminal);
assert!(
screen.contains("界") && screen.contains("e\u{301}"),
"{screen}"
);
assert!(
screen.contains("A�uthor · Summary��done (01234567)"),
"{screen}"
);
let caret = terminal.get_cursor_position().unwrap();
let cell = terminal.backend().buffer()[(caret.x, caret.y)].clone();
editor.handle(Input::Down);
assert!(editor.line_blame().is_none());
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert!(!screen_text(&terminal).contains("Summary"));
editor.handle(Input::Up);
assert_eq!(
editor.line_blame(),
Some("A�uthor · Summary��done (01234567)")
);
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert!(screen_text(&terminal).contains("A�uthor · Summary��done (01234567)"));
assert_eq!(terminal.get_cursor_position().unwrap(), caret);
assert_eq!(terminal.backend().buffer()[(caret.x, caret.y)], cell);
}
#[test]
fn inline_blame_uses_last_wrap_continuation_and_never_overflows_narrow_panes() {
let directory = Directory::new();
let mut editor =
Editor::open(Some(directory.file("source.txt", &"x".repeat(60))), vec![]).unwrap();
editor.execute("word_wrap.toggle");
let source = editor.blame_source().unwrap();
editor.set_line_blame(
source,
crate::blame::LineBlame {
commit: String::new(),
author: String::new(),
summary: String::new(),
uncommitted: true,
},
);
let mut views = BufferViewports::default();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(50, 8)).unwrap();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let caret = terminal.get_cursor_position().unwrap();
assert!(!pane_screen(&terminal, Rect::new(0, caret.y, 50, 1)).contains("Uncommitted"));
assert!(pane_screen(&terminal, Rect::new(0, caret.y + 1, 50, 1)).contains("Uncommitted"));
let cell = terminal.backend().buffer()[(16, caret.y + 1)].clone();
assert_eq!(cell.symbol(), "U");
assert_eq!(cell.fg, Color::DarkGray);
let mut narrow = Terminal::new(ratatui::backend::TestBackend::new(12, 8)).unwrap();
narrow.draw(|frame| views.draw(frame, &mut editor)).unwrap();
assert!(!screen_text(&narrow).contains("Uncommitted"));
assert!(screen_text(&narrow).contains("xxx"));
editor.execute("git_blame.toggle");
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert!(!screen_text(&terminal).contains("Uncommitted"));
let mut historical = readonly_diff_editor(deletion_file());
assert!(historical.blame_source().is_none());
historical.execute("tab.new");
assert!(historical.diff().is_none());
assert!(historical.blame_source().is_none());
}
#[test]
fn inline_blame_disappears_when_wheel_scroll_hides_the_primary_caret() {
let directory = Directory::new();
let text = (0..40)
.map(|row| format!("row {row}"))
.collect::<Vec<_>>()
.join("\n");
let mut session =
EditorSession::open(Some(directory.file("source.txt", &text)), vec![], None).unwrap();
let source = session.editor().blame_source().unwrap();
session.editor_mut().set_line_blame(
source,
crate::blame::LineBlame {
commit: String::new(),
author: String::new(),
summary: String::new(),
uncommitted: true,
},
);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 8)).unwrap();
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
assert!(screen_text(&terminal).contains("Uncommitted"));
let area = views.document.geometry.unwrap().1;
assert!(views.scroll(
&mut session,
document_wheel(MouseEventKind::ScrollDown, area.x + 2, area.y + 2)
));
terminal
.draw(|frame| views.draw_session(frame, &mut session))
.unwrap();
assert!(!screen_text(&terminal).contains("Uncommitted"));
assert_eq!(session.editor().cursor().row, 0);
}
#[test]
fn default_native_battery_and_custom_override_redraw_through_clear() {
let directory = Directory::new();