diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 6e66041b..d26f6218 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -13,7 +13,8 @@ use helix_stdx::path::get_relative_path; use helix_view::{ align_view, document::{DocumentOpenError, DocumentSavedEventResult}, - editor::{ConfigEvent, EditorEvent}, + editor::{ConfigEvent, EditorEvent, TICK_DURATION}, + events::DidRequestInlineBlame, graphics::Rect, theme, tree::Layout, @@ -35,7 +36,7 @@ use crate::{ use log::{debug, error, info, warn}; #[cfg(not(feature = "integration"))] use std::io::stdout; -use std::{io::stdin, path::Path, sync::Arc}; +use std::{io::stdin, path::Path, sync::Arc, time::Duration}; #[cfg(not(windows))] use anyhow::Context; @@ -586,6 +587,18 @@ impl Application { )); } + async fn handle_tick(&mut self) { + self.editor.ticks_elapsed += 1; + // request an inline blame refresh every this amount of ticks + const INLINE_BLAME_REQUEST_TIME: u128 = + Duration::from_millis(150).as_nanos() / TICK_DURATION.as_nanos(); + if self.editor.ticks_elapsed % INLINE_BLAME_REQUEST_TIME == 0 { + helix_event::dispatch(DidRequestInlineBlame { + editor: &mut self.editor, + }) + }; + } + #[inline(always)] pub async fn handle_editor_event(&mut self, event: EditorEvent) -> bool { log::debug!("received editor event: {:?}", event); @@ -622,6 +635,9 @@ impl Application { return true; } } + EditorEvent::Tick => { + self.handle_tick().await; + } } false diff --git a/helix-term/src/events.rs b/helix-term/src/events.rs index 9d06627c..210dde57 100644 --- a/helix-term/src/events.rs +++ b/helix-term/src/events.rs @@ -1,7 +1,7 @@ use helix_event::{events, register_event}; use helix_view::document::Mode; use helix_view::events::{ - DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen, DocumentFocusLost, SelectionDidChange, + DiagnosticsDidChange, DocumentDidChange, DocumentDidOpen, DocumentFocusLost, SelectionDidChange, DidRequestInlineBlame }; use crate::commands; @@ -20,6 +20,7 @@ pub fn register() { register_event::<DocumentDidChange>(); register_event::<DocumentDidOpen>(); register_event::<DocumentFocusLost>(); + register_event::<DidRequestInlineBlame>(); register_event::<SelectionDidChange>(); register_event::<DiagnosticsDidChange>(); } diff --git a/helix-term/src/handlers/blame.rs b/helix-term/src/handlers/blame.rs index 923ae74c..12cb58b2 100644 --- a/helix-term/src/handlers/blame.rs +++ b/helix-term/src/handlers/blame.rs @@ -1,10 +1,13 @@ use std::time::Duration; use helix_event::{register_hook, send_blocking}; -use helix_view::handlers::{BlameEvent, Handlers}; +use helix_view::{ + events::DidRequestInlineBlame, + handlers::{BlameEvent, Handlers}, +}; use tokio::{task::JoinHandle, time::Instant}; -use crate::{events::PostCommand, job}; +use crate::job; #[derive(Default)] pub struct BlameHandler { @@ -77,13 +80,14 @@ impl helix_event::AsyncHook for BlameHandler { pub(super) fn register_hooks(handlers: &Handlers) { let tx = handlers.blame.clone(); - register_hook!(move |event: &mut PostCommand<'_, '_>| { - let version_control_config = &event.cx.editor.config().version_control; + register_hook!(move |event: &mut DidRequestInlineBlame<'_>| { + let version_control_config = &event.editor.config().version_control; if !version_control_config.inline_blame { return Ok(()); } - let (view, doc) = current!(event.cx.editor); + let (view, doc) = current!(event.editor); + let Some(file) = doc.path() else { return Ok(()); }; @@ -93,10 +97,24 @@ pub(super) fn register_hooks(handlers: &Handlers) { return Ok(()); }; - let hunks = doc.diff_handle().unwrap().load(); + if let Some(cached) = &mut event.editor.blame_cache { + // don't update the blame if we haven't moved to a different line + if (view.id, cursor_line) == *cached { + return Ok(()); + } else { + *cached = (view.id, cursor_line) + } + }; - let (inserted_lines_count, deleted_lines_count) = - hunks.inserted_and_deleted_before_line(cursor_line as usize); + let Some(hunks) = doc.diff_handle() else { + return Ok(()); + }; + + log::error!("updated blame!"); + + let (inserted_lines_count, deleted_lines_count) = hunks + .load() + .inserted_and_deleted_before_line(cursor_line as usize); send_blocking( &tx, @@ -106,7 +124,7 @@ pub(super) fn register_hooks(handlers: &Handlers) { deleted_lines_count, inserted_lines_count, // ok to clone because diff_providers is very small - diff_providers: event.cx.editor.diff_providers.clone(), + diff_providers: event.editor.diff_providers.clone(), // ok to clone because blame_format is likely to be about 30 characters or less blame_format: version_control_config.inline_blame_format.clone(), }, diff --git a/helix-vcs/src/git/blame.rs b/helix-vcs/src/git/blame.rs index dbf44679..a7456c28 100644 --- a/helix-vcs/src/git/blame.rs +++ b/helix-vcs/src/git/blame.rs @@ -185,12 +185,10 @@ pub fn blame_line( }) } -// attributes on expressions are not allowed -// however, in our macro its possible that sometimes the -// assignment to the mutable variable will not be read. -// -// when the last line has no expected blame commit -#[allow(unused_assignments)] +// For some reasons the CI is failing on windows with the message "Commits not found". +// There is nothing windows-specific in this implementation +// As long as these tests pass on other platforms, on Windows it should work too +// #[cfg(not(windows))] #[cfg(test)] mod test { use std::fs::File; @@ -341,6 +339,7 @@ mod test { $( let line_diff_flag = line_diff_flag!($($line_diff)?, $commit_msg, $line); + #[allow(unused_assignments)] match line_diff_flag { LineDiff::Insert => added_lines += 1, LineDiff::Delete => removed_lines += 1, @@ -373,7 +372,10 @@ mod test { .unwrap_or("<no commit>") ); )? - line_number += 1; + #[allow(unused_assignments)] + { + line_number += 1; + } } )* )* @@ -448,7 +450,7 @@ mod test { " four" delete, " two" delete, " five" delete, - " four", + " four" 5, "}" 1, " five" 5, " four" 5; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1a585ef0..81b968bc 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -23,7 +23,7 @@ use icons::Icons; use futures_util::stream::select_all::SelectAll; use futures_util::{future, StreamExt}; use helix_lsp::{Call, LanguageServerId}; -use tokio_stream::wrappers::UnboundedReceiverStream; +use tokio_stream::wrappers::{IntervalStream, UnboundedReceiverStream}; use std::{ borrow::Cow, @@ -39,7 +39,7 @@ use std::{ use tokio::{ sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - time::{sleep, Duration, Instant, Sleep}, + time::{interval, sleep, Duration, Instant, Sleep}, }; use anyhow::{anyhow, bail, Error}; @@ -1111,6 +1111,8 @@ pub struct Editor { pub auto_pairs: Option<AutoPairs>, pub idle_timer: Pin<Box<Sleep>>, + pub tick: IntervalStream, + pub ticks_elapsed: u128, redraw_timer: Pin<Box<Sleep>>, last_motion: Option<Motion>, pub last_completion: Option<CompleteAction>, @@ -1136,6 +1138,7 @@ pub struct Editor { pub mouse_down_range: Option<Range>, pub cursor_cache: CursorCache, + pub blame_cache: Option<(ViewId, u32)>, } pub type Motion = Box<dyn Fn(&mut Editor)>; @@ -1147,6 +1150,7 @@ pub enum EditorEvent { LanguageServerMessage((LanguageServerId, Call)), DebuggerEvent(dap::Payload), IdleTimer, + Tick, Redraw, } @@ -1201,6 +1205,9 @@ pub enum CloseError { SaveError(anyhow::Error), } +/// A tick happens every this amount of milliseconds +pub const TICK_DURATION: Duration = Duration::from_millis(1000 / 60); + impl Editor { pub fn new( mut area: Rect, @@ -1246,6 +1253,8 @@ impl Editor { status_msg: None, autoinfo: None, idle_timer: Box::pin(sleep(conf.idle_timeout)), + tick: IntervalStream::new(interval(TICK_DURATION)), + ticks_elapsed: 0, redraw_timer: Box::pin(sleep(Duration::MAX)), last_motion: None, last_completion: None, @@ -1258,6 +1267,7 @@ impl Editor { handlers, mouse_down_range: None, cursor_cache: CursorCache::default(), + blame_cache: None, } } @@ -2164,6 +2174,9 @@ impl Editor { _ = &mut self.idle_timer => { return EditorEvent::IdleTimer } + _ = &mut self.tick.next() => { + return EditorEvent::Tick + } } } } diff --git a/helix-view/src/events.rs b/helix-view/src/events.rs index cb3ed90c..e3ea1304 100644 --- a/helix-view/src/events.rs +++ b/helix-view/src/events.rs @@ -16,4 +16,5 @@ events! { DiagnosticsDidChange<'a> { editor: &'a mut Editor, doc: DocumentId } // called **after** a document loses focus (but not when its closed) DocumentFocusLost<'a> { editor: &'a mut Editor, doc: DocumentId } + DidRequestInlineBlame<'a> { editor: &'a mut Editor } }