From 50bdfbe35c5247bbd285ddd128d2611753887c28 Mon Sep 17 00:00:00 2001
From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Date: Thu, 20 Mar 2025 08:25:21 +0000
Subject: [PATCH 1/3] fix: remove panic

---
 helix-term/src/handlers/blame.rs | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/helix-term/src/handlers/blame.rs b/helix-term/src/handlers/blame.rs
index 923ae74c..c4e53919 100644
--- a/helix-term/src/handlers/blame.rs
+++ b/helix-term/src/handlers/blame.rs
@@ -93,10 +93,13 @@ pub(super) fn register_hooks(handlers: &Handlers) {
             return Ok(());
         };
 
-        let hunks = doc.diff_handle().unwrap().load();
+        let Some(hunks) = doc.diff_handle() else {
+            return Ok(());
+        };
 
-        let (inserted_lines_count, deleted_lines_count) =
-            hunks.inserted_and_deleted_before_line(cursor_line as usize);
+        let (inserted_lines_count, deleted_lines_count) = hunks
+            .load()
+            .inserted_and_deleted_before_line(cursor_line as usize);
 
         send_blocking(
             &tx,

From 72edf8409db21e7eea8aeb20bb1c1d0abdc286b1 Mon Sep 17 00:00:00 2001
From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Date: Thu, 20 Mar 2025 10:04:17 +0000
Subject: [PATCH 2/3] perf: update inline git blame every 150 milliseconds
 instead of on each command

---
 helix-term/src/application.rs    | 20 ++++++++++++++++++--
 helix-term/src/events.rs         |  4 +++-
 helix-term/src/handlers/blame.rs | 27 +++++++++++++++++++++------
 helix-view/src/editor.rs         | 17 +++++++++++++++--
 helix-view/src/events.rs         |  1 +
 5 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 1da2a700..a6367e76 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -10,7 +10,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,
@@ -32,7 +33,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;
@@ -590,6 +591,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);
@@ -626,6 +639,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 15d81152..14e9274b 100644
--- a/helix-term/src/events.rs
+++ b/helix-term/src/events.rs
@@ -1,7 +1,8 @@
 use helix_event::{events, register_event};
 use helix_view::document::Mode;
 use helix_view::events::{
-    DiagnosticsDidChange, DocumentDidChange, DocumentFocusLost, SelectionDidChange,
+    DiagnosticsDidChange, DidRequestInlineBlame, DocumentDidChange, DocumentFocusLost,
+    SelectionDidChange,
 };
 
 use crate::commands;
@@ -19,6 +20,7 @@ pub fn register() {
     register_event::<PostCommand>();
     register_event::<DocumentDidChange>();
     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 c4e53919..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,21 @@ pub(super) fn register_hooks(handlers: &Handlers) {
             return Ok(());
         };
 
+        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 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);
@@ -109,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-view/src/editor.rs b/helix-view/src/editor.rs
index e9a347f8..45475326 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -21,7 +21,7 @@ use helix_vcs::DiffProviderRegistry;
 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,
@@ -37,7 +37,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};
@@ -1103,6 +1103,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>,
@@ -1128,6 +1130,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)>;
@@ -1139,6 +1142,7 @@ pub enum EditorEvent {
     LanguageServerMessage((LanguageServerId, Call)),
     DebuggerEvent(dap::Payload),
     IdleTimer,
+    Tick,
     Redraw,
 }
 
@@ -1193,6 +1197,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,
@@ -1238,6 +1245,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,
@@ -1250,6 +1259,7 @@ impl Editor {
             handlers,
             mouse_down_range: None,
             cursor_cache: CursorCache::default(),
+            blame_cache: None,
         }
     }
 
@@ -2149,6 +2159,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 eb97268c..2c6b5195 100644
--- a/helix-view/src/events.rs
+++ b/helix-view/src/events.rs
@@ -15,4 +15,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 }
 }

From 2f7c131067d470a719cea6adc729a1bb22265bce Mon Sep 17 00:00:00 2001
From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com>
Date: Thu, 20 Mar 2025 10:14:43 +0000
Subject: [PATCH 3/3] test: add attributes on blocks

---
 helix-vcs/src/git/blame.rs | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

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;