From b12d22aa35fea1f9ab66e6392c5d069ea9b11656 Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Wed, 8 Apr 2026 11:21:13 -0400 Subject: [PATCH] feat(blame): add inline blame display mode option Replace the unimplemented ShowOnHoverOnly boolean with a three-way InlineBlameDisplayMode setting (Always, CurrentLine, Hover) so users can control when inline blame annotations appear. --- .../Editor/BlameAdornment/BlameAdornment.cs | 68 +++++++++++++++++++ .../Options/GeneralOptions.cs | 29 ++++++-- .../Options/GeneralOptionsPage.cs | 9 +++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/CodingWithCalvin.GitRanger/Editor/BlameAdornment/BlameAdornment.cs b/src/CodingWithCalvin.GitRanger/Editor/BlameAdornment/BlameAdornment.cs index 4478d9c..3359e74 100644 --- a/src/CodingWithCalvin.GitRanger/Editor/BlameAdornment/BlameAdornment.cs +++ b/src/CodingWithCalvin.GitRanger/Editor/BlameAdornment/BlameAdornment.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; using CodingWithCalvin.GitRanger.Core.Models; using CodingWithCalvin.GitRanger.Options; @@ -35,6 +36,7 @@ internal sealed class BlameAdornment private IReadOnlyList _blameData = Array.Empty(); private string? _currentFilePath; private bool _isLoading; + private int _activeLineNumber = -1; /// /// Creates a new blame adornment for the given text view. @@ -60,6 +62,9 @@ public BlameAdornment( // Subscribe to events _view.LayoutChanged += OnLayoutChanged; _view.Closed += OnViewClosed; + _view.Caret.PositionChanged += OnCaretPositionChanged; + _view.VisualElement.MouseMove += OnMouseMove; + _view.VisualElement.MouseLeave += OnMouseLeave; _blameService.BlameLoaded += OnBlameLoaded; GeneralOptions.Saved += OnOptionsSaved; @@ -71,10 +76,63 @@ private void OnViewClosed(object sender, EventArgs e) { _view.LayoutChanged -= OnLayoutChanged; _view.Closed -= OnViewClosed; + _view.Caret.PositionChanged -= OnCaretPositionChanged; + _view.VisualElement.MouseMove -= OnMouseMove; + _view.VisualElement.MouseLeave -= OnMouseLeave; _blameService.BlameLoaded -= OnBlameLoaded; GeneralOptions.Saved -= OnOptionsSaved; } + private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) + { + var options = GeneralOptions.Instance; + if (options == null || !options.EnableInlineBlame || options.InlineBlameDisplayMode != InlineBlameMode.CurrentLine) + return; + + var caretLineNumber = _view.TextSnapshot.GetLineNumberFromPosition( + _view.Caret.Position.BufferPosition.Position) + 1; + + if (caretLineNumber == _activeLineNumber) + return; + + _activeLineNumber = caretLineNumber; + ClearAdornments(); + UpdateAdornments(); + } + + private void OnMouseMove(object sender, MouseEventArgs e) + { + var options = GeneralOptions.Instance; + if (options == null || !options.EnableInlineBlame || options.InlineBlameDisplayMode != InlineBlameMode.Hover) + return; + + var position = e.GetPosition(_view.VisualElement); + var viewportPoint = new Point(position.X + _view.ViewportLeft, position.Y + _view.ViewportTop); + + var hoveredLine = _view.TextViewLines.GetTextViewLineContainingYCoordinate(viewportPoint.Y); + if (hoveredLine == null) + return; + + var lineNumber = _view.TextSnapshot.GetLineNumberFromPosition(hoveredLine.Start.Position) + 1; + + if (lineNumber == _activeLineNumber) + return; + + _activeLineNumber = lineNumber; + ClearAdornments(); + UpdateAdornments(); + } + + private void OnMouseLeave(object sender, MouseEventArgs e) + { + var options = GeneralOptions.Instance; + if (options == null || !options.EnableInlineBlame || options.InlineBlameDisplayMode != InlineBlameMode.Hover) + return; + + _activeLineNumber = -1; + ClearAdornments(); + } + private void OnOptionsSaved(GeneralOptions options) { _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => @@ -173,6 +231,12 @@ private void UpdateAdornments() if (_isLoading || _blameData.Count == 0) return; + if (options.InlineBlameDisplayMode == InlineBlameMode.CurrentLine) + { + _activeLineNumber = _view.TextSnapshot.GetLineNumberFromPosition( + _view.Caret.Position.BufferPosition.Position) + 1; + } + var viewportTop = _view.ViewportTop; var viewportBottom = _view.ViewportBottom; @@ -182,6 +246,10 @@ private void UpdateAdornments() continue; var lineNumber = _view.TextSnapshot.GetLineNumberFromPosition(line.Start.Position) + 1; + + if (options.InlineBlameDisplayMode != InlineBlameMode.Always && lineNumber != _activeLineNumber) + continue; + var blameInfo = _blameData.FirstOrDefault(b => b.LineNumber == lineNumber); if (blameInfo == null) continue; diff --git a/src/CodingWithCalvin.GitRanger/Options/GeneralOptions.cs b/src/CodingWithCalvin.GitRanger/Options/GeneralOptions.cs index c542486..ec8683c 100644 --- a/src/CodingWithCalvin.GitRanger/Options/GeneralOptions.cs +++ b/src/CodingWithCalvin.GitRanger/Options/GeneralOptions.cs @@ -85,10 +85,10 @@ public class GeneralOptions : BaseOptionModel public bool CompactMode { get; set; } = false; [Category("Display")] - [DisplayName("Show On Hover Only")] - [Description("Only show detailed blame information on mouse hover.")] - [DefaultValue(false)] - public bool ShowOnHoverOnly { get; set; } = false; + [DisplayName("Inline Blame Display Mode")] + [Description("Controls when inline blame is shown: Always (all lines), CurrentLine (caret line only), or Hover (mouse hover only).")] + [DefaultValue(InlineBlameMode.Always)] + public InlineBlameMode InlineBlameDisplayMode { get; set; } = InlineBlameMode.Always; // Gutter Settings [Category("Gutter")] @@ -136,6 +136,27 @@ public class GeneralOptions : BaseOptionModel public LogLevel LogLevel { get; set; } = LogLevel.Error; } + /// + /// Controls when inline blame annotations are displayed. + /// + public enum InlineBlameMode + { + /// + /// Show inline blame on all visible lines. + /// + Always, + + /// + /// Show inline blame only on the current caret line. + /// + CurrentLine, + + /// + /// Show inline blame only when the mouse hovers over a line. + /// + Hover + } + /// /// Color mode for blame annotations. /// diff --git a/src/CodingWithCalvin.GitRanger/Options/GeneralOptionsPage.cs b/src/CodingWithCalvin.GitRanger/Options/GeneralOptionsPage.cs index 955621a..b21b454 100644 --- a/src/CodingWithCalvin.GitRanger/Options/GeneralOptionsPage.cs +++ b/src/CodingWithCalvin.GitRanger/Options/GeneralOptionsPage.cs @@ -126,6 +126,15 @@ public bool CompactMode set => _options.CompactMode = value; } + [Category("Display")] + [DisplayName("Inline Blame Display Mode")] + [Description("Controls when inline blame is shown: Always (all lines), CurrentLine (caret line only), or Hover (mouse hover only).")] + public InlineBlameMode InlineBlameDisplayMode + { + get => _options.InlineBlameDisplayMode; + set => _options.InlineBlameDisplayMode = value; + } + // Gutter Settings [Category("Gutter")] [DisplayName("Gutter Width")]