From 836b428d066daa25142f5c4a72f4b554101309f3 Mon Sep 17 00:00:00 2001 From: John Costa Date: Sat, 10 Oct 2026 15:47:47 -0700 Subject: [PATCH] fix: render tabs in commit messages as spaces Closes #3036 --- CHANGELOG.md | 1 + .../commit_details/compare_details.rs | 5 ++++- src/components/commit_details/details.rs | 17 +++++++++++++++-- src/components/revision_files.rs | 6 +++++- src/components/utils/logitems.rs | 3 ++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ca0eaac2..7aa2c96a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * crash when opening submodule ([#2895](https://github.com/gitui-org/gitui/issues/2895)) * when staging the last file in a directory, the first item after the directory is no longer skipped [[@Tillerino](https://github.com/Tillerino)] ([#2748](https://github.com/gitui-org/gitui/issues/2748)) * index-out-of-bounds panic when unstaging lines near the end of a diff ([#2953](https://github.com/gitui-org/gitui/issues/2953)) +* tabs in commit messages are rendered as spaces instead of being dropped or garbling the commit list and details view [[@costajohnt](https://github.com/costajohnt)] ([#3036](https://github.com/gitui-org/gitui/issues/3036)) ## [0.28.1] - 2026-03-21 diff --git a/src/components/commit_details/compare_details.rs b/src/components/commit_details/compare_details.rs index 47e7a96823..96852d0cca 100644 --- a/src/components/commit_details/compare_details.rs +++ b/src/components/commit_details/compare_details.rs @@ -9,6 +9,7 @@ use crate::{ CommandBlocking, CommandInfo, Component, DrawableComponent, EventState, }, + string_utils::tabs_to_spaces, strings::{self}, ui::style::SharedTheme, }; @@ -88,7 +89,9 @@ impl CompareDetailsComponent { Cow::from( data.message .as_ref() - .map(|msg| msg.subject.clone()) + .map(|msg| { + tabs_to_spaces(msg.subject.clone()) + }) .unwrap_or_default(), ), self.theme.text(true, false), diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 42825900a0..84740a8926 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -8,6 +8,7 @@ use crate::{ EventState, ScrollType, }, keys::{key_match, SharedKeyConfig}, + string_utils::tabs_to_spaces, strings::{self, order}, ui::style::SharedTheme, }; @@ -81,15 +82,17 @@ impl DetailsComponent { width: usize, ) -> WrappedCommitMessage<'_> { let width = width.max(1); - let wrapped_title = bwrap::wrap!(&message.subject, width) + let subject = tabs_to_spaces(message.subject.clone()); + let wrapped_title = bwrap::wrap!(&subject, width) .lines() .map(String::from) .map(Cow::from) .collect(); if let Some(ref body) = message.body { + let body = tabs_to_spaces(body.clone()); let wrapped_message: Vec> = - bwrap::wrap!(body, width) + bwrap::wrap!(&body, width) .lines() .map(String::from) .map(Cow::from) @@ -486,6 +489,16 @@ mod tests { ] ); } + + #[test] + fn test_textwrap_tabs() { + let message = CommitMessage::from("subj\tx\n\nPR:\t123"); + + assert_eq!( + get_wrapped_lines(&message, 50), + vec!["subj x", "", "PR: 123"] + ); + } } #[cfg(test)] diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 1e15ec086f..abfbeacbff 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -8,6 +8,7 @@ use crate::{ keys::{key_match, SharedKeyConfig}, popups::{BlameFileOpen, FileRevOpen}, queue::{InternalEvent, Queue, StackablePopupOpen}, + string_utils::tabs_to_spaces, strings::{self, order, symbol}, try_or_popup, ui::{self, common_nav, style::SharedTheme}, @@ -357,7 +358,10 @@ impl RevisionFilesComponent { let _ignore = write!(title, " {{{}}}", c.id.get_short_string()); - c.message.lines().next() + c.message + .lines() + .next() + .map(|m| tabs_to_spaces(m.to_string())) }); if let Some(message) = message { diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 9e0706226e..e829c66134 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -1,3 +1,4 @@ +use crate::string_utils::tabs_to_spaces; use asyncgit::sync::{CommitId, CommitInfo}; use chrono::{DateTime, Duration, Local, Utc}; use indexmap::IndexSet; @@ -41,7 +42,7 @@ impl From for LogEntry { }; let author = c.author; - let msg = c.message; + let msg = tabs_to_spaces(c.message); // Replace markdown emojis with Unicode equivalent #[cfg(feature = "ghemoji")]