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] 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::(); register_event::(); register_event::(); + register_event::(); register_event::(); register_event::(); } 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, pub idle_timer: Pin>, + pub tick: IntervalStream, + pub ticks_elapsed: u128, redraw_timer: Pin>, last_motion: Option, pub last_completion: Option, @@ -1128,6 +1130,7 @@ pub struct Editor { pub mouse_down_range: Option, pub cursor_cache: CursorCache, + pub blame_cache: Option<(ViewId, u32)>, } pub type Motion = Box; @@ -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 } }