From f8debc04876bc24eaa8d04731e30fed13d379cf8 Mon Sep 17 00:00:00 2001 From: Daniil Tatianin <99danilt@gmail.com> Date: Wed, 23 Sep 2026 22:33:58 +0300 Subject: [PATCH] feature: configurable description guide length for commit messages Add a `Description Guide Length` preference next to the existing subject guide length. The commit message editor now: - shows a `DESCRIPTION n/limit` counter (longest body line) with the same warning color and icon used for the subject - draws the vertical column guide at the configured length instead of a hardcoded 80 columns Since the working copy commit box, the reword editor in interactive rebase, the commit template editor and the standalone editor mode all share `CommitMessageToolBox`, the guide applies everywhere a message is written. The default stays at 80 so existing users see no change. Signed-off-by: Daniil Tatianin <99danilt@gmail.com> --- src/Resources/Locales/en_US.axaml | 2 + src/Resources/Locales/zh_CN.axaml | 2 + src/Resources/Locales/zh_TW.axaml | 2 + src/ViewModels/Preferences.cs | 7 +++ src/Views/CommitMessageToolBox.axaml | 18 +++++++ src/Views/CommitMessageToolBox.axaml.cs | 72 ++++++++++++++++++++++++- src/Views/Preferences.axaml | 39 ++++++++++---- 7 files changed, 131 insertions(+), 11 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 4dc15a4c36..2ac3000d4e 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -204,6 +204,7 @@ SHA Signer: Open in Browser + DESCRIPTION Enter commit message. Please use an empty-line to separate subject and description! SUBJECT Compare @@ -696,6 +697,7 @@ GENERAL Check for updates on startup Date Format + Description Guide Length Enable compact folders in changes tree Language History Commits diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index a7f35949ba..28f8eb8ae5 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -208,6 +208,7 @@ 提交指纹 签名者 : 浏览器中查看 + 描述 请输入提交的信息。注意:主题与具体描述中间需要空白行分隔! 主题 比较 @@ -700,6 +701,7 @@ 通用配置 启动时检测软件更新 日期时间格式 + DESCRIPTION字数检测 在变更列表树中启用紧凑文件夹模式 显示语言 最大历史提交数 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 85612abe12..ac0d36a326 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -198,6 +198,7 @@ 提交編號 簽署人: 在瀏覽器中檢視 + 描述 請輸入提交訊息,標題與詳細描述之間請使用單行空白區隔。 標題 比較 @@ -690,6 +691,7 @@ 一般設定 啟動時檢查軟體更新 日期時間格式 + 提交描述字數偵測 在樹狀變更目錄中啟用密集資料夾模式 顯示語言 最大歷史提交數 diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 4803ec4296..c1fa46e0d9 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -141,6 +141,12 @@ public int SubjectGuideLength set => SetProperty(ref _subjectGuideLength, value); } + public int DescriptionGuideLength + { + get => _descriptionGuideLength; + set => SetProperty(ref _descriptionGuideLength, value); + } + public int DateTimeFormat { get => Models.DateTimeFormat.ActiveIndex; @@ -820,6 +826,7 @@ private bool RemoveInvalidRepositoriesRecursive(List collection) private int _maxHistoryCommits = 20000; private int _subjectGuideLength = 50; + private int _descriptionGuideLength = 80; private bool _useFixedTabWidth = true; private bool _useAutoHideScrollBars = true; private bool _useGitHubStyleAvatar = true; diff --git a/src/Views/CommitMessageToolBox.axaml b/src/Views/CommitMessageToolBox.axaml index 8f65e5372c..5ca9490dac 100644 --- a/src/Views/CommitMessageToolBox.axaml +++ b/src/Views/CommitMessageToolBox.axaml @@ -21,6 +21,7 @@ Text="{Binding #ThisControl.CommitMessage, Mode=TwoWay}" Watermark="{DynamicResource Text.CommitMessageTextBox.Placeholder}" SubjectGuideLength="{Binding Source={x:Static vm:Preferences.Instance}, Path=SubjectGuideLength, Mode=OneWay}" + DescriptionGuideLength="{Binding Source={x:Static vm:Preferences.Instance}, Path=DescriptionGuideLength, Mode=OneWay}" GuideLineBrush="{DynamicResource Brush.Border2}" FontFamily="{DynamicResource Fonts.Monospace}"> @@ -138,6 +139,23 @@ Text="{Binding #Editor.SubjectGuideLength, Mode=OneWay}" VerticalAlignment="Center"/> + + + + + diff --git a/src/Views/CommitMessageToolBox.axaml.cs b/src/Views/CommitMessageToolBox.axaml.cs index 373e737261..ba447e9761 100644 --- a/src/Views/CommitMessageToolBox.axaml.cs +++ b/src/Views/CommitMessageToolBox.axaml.cs @@ -81,6 +81,40 @@ public bool WarnSubjectLength set => SetAndRaise(WarnSubjectLengthProperty, ref _warnSubjectLen, value); } + public static readonly DirectProperty DescriptionLengthProperty = + AvaloniaProperty.RegisterDirect( + nameof(DescriptionLength), + static o => o.DescriptionLength); + + public int DescriptionLength + { + get => _descriptionLen; + set => SetAndRaise(DescriptionLengthProperty, ref _descriptionLen, value); + } + + public static readonly DirectProperty DescriptionGuideLengthProperty = + AvaloniaProperty.RegisterDirect( + nameof(DescriptionGuideLength), + static o => o.DescriptionGuideLength, + static (o, v) => o.DescriptionGuideLength = v); + + public int DescriptionGuideLength + { + get => _descriptionGuideLen; + set => SetAndRaise(DescriptionGuideLengthProperty, ref _descriptionGuideLen, value); + } + + public static readonly DirectProperty WarnDescriptionLengthProperty = + AvaloniaProperty.RegisterDirect( + nameof(WarnDescriptionLength), + static o => o.WarnDescriptionLength); + + public bool WarnDescriptionLength + { + get => _warnDescriptionLen; + set => SetAndRaise(WarnDescriptionLengthProperty, ref _warnDescriptionLen, value); + } + public static readonly DirectProperty> SuggestionsProperty = AvaloniaProperty.RegisterDirect>( nameof(Suggestions), @@ -173,7 +207,7 @@ public override void Render(DrawingContext context) new Typeface(font), ViewModels.Preferences.Instance.DefaultFontSize, Brushes.White); - var columnGuideX = columnTest.WidthIncludingTrailingWhitespace * 80 + 5.5; + var columnGuideX = columnTest.WidthIncludingTrailingWhitespace * _descriptionGuideLen + 5.5; context.DrawLine(pen, new Point(columnGuideX, Math.Max(0, y)), new Point(columnGuideX, Bounds.Height)); } @@ -209,10 +243,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang { _subjectEndCharIdx = -1; SubjectLength = 0; + DescriptionLength = 0; return; } var subjectLen = 0; + var descriptionLen = 0; var lastNonLineBreakCharIdx = 0; var lastLineStart = 0; for (var i = 0; i < text.Length; i++) @@ -226,7 +262,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang if (string.IsNullOrWhiteSpace(line)) { if (subjectLen > 0) + { + descriptionLen = GetMaxLineLength(text, lastLineStart); break; + } continue; } @@ -253,12 +292,18 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang } SubjectLength = subjectLen; + DescriptionLength = descriptionLen; _subjectEndCharIdx = lastNonLineBreakCharIdx; } else if (change.Property == SubjectLengthProperty || change.Property == SubjectGuideLengthProperty) { WarnSubjectLength = _subjectLen > _subjectGuideLen; } + else if (change.Property == DescriptionLengthProperty || change.Property == DescriptionGuideLengthProperty) + { + WarnDescriptionLength = _descriptionLen > _descriptionGuideLen; + InvalidateVisual(); + } else if (change.Property == SubjectEndYProperty) { InvalidateVisual(); @@ -377,6 +422,28 @@ protected override void OnKeyDown(KeyEventArgs e) base.OnKeyDown(e); } + private static int GetMaxLineLength(string text, int start) + { + var maxLen = 0; + var lineStart = start; + for (var i = start; i <= text.Length; i++) + { + if (i == text.Length || text[i] == '\n') + { + var lineLen = i - lineStart; + while (lineLen > 0 && char.IsWhiteSpace(text[lineStart + lineLen - 1])) + lineLen--; + + if (lineLen > maxLen) + maxLen = lineLen; + + lineStart = i + 1; + } + } + + return maxLen; + } + private void OnLayoutUpdated(object sender, EventArgs e) { if (_textPresenter == null || !IsEffectivelyVisible) @@ -447,6 +514,9 @@ private void OnLayoutUpdated(object sender, EventArgs e) private int _subjectEndCharIdx = -1; private double _subjectEndY = 0; private bool _warnSubjectLen = false; + private int _descriptionLen = 0; + private int _descriptionGuideLen = 80; + private bool _warnDescriptionLen = false; private int _suggestionMatchStartIdx = -1; private List _suggestions = null; private int _selectedSuggestionIdx = 0; diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml index 55c2b17276..6960d93be7 100644 --- a/src/Views/Preferences.axaml +++ b/src/Views/Preferences.axaml @@ -47,7 +47,7 @@ - + + + + + + + + - - - - - - - -