diff --git src/plusminus_header_highlighter.rs src/plusminus_header_highlighter.rs index 5bb5056..c354931 100644 --- src/plusminus_header_highlighter.rs +++ src/plusminus_header_highlighter.rs @@ -6,7 +6,7 @@ use crate::lines_highlighter::{LinesHighlighter, Response}; use crate::refiner::to_highlighted_tokens; use crate::string_future::StringFuture; use crate::token_collector::{ - lowlight_timestamp, render, unhighlight_git_prefix, LINE_STYLE_NEW_FILENAME, + align_tabs, lowlight_timestamp, render, unhighlight_git_prefix, LINE_STYLE_NEW_FILENAME, LINE_STYLE_OLD_FILENAME, }; @@ -92,8 +92,11 @@ impl PlusMinusHeaderHighlighter { to_highlighted_tokens(&self.old_name, &self.new_name); lowlight_timestamp(&mut old_tokens); - unhighlight_git_prefix(&mut old_tokens); lowlight_timestamp(&mut new_tokens); + + align_tabs(&mut old_tokens, &mut new_tokens); + + unhighlight_git_prefix(&mut old_tokens); unhighlight_git_prefix(&mut new_tokens); let old_filename = render(&LINE_STYLE_OLD_FILENAME, "--- ", &old_tokens); @@ -108,3 +111,34 @@ impl PlusMinusHeaderHighlighter { return highlighted; } } + +#[cfg(test)] +mod tests { + use crate::ansi::remove_ansi_escape_codes; + + use super::*; + + #[test] + fn test_align_timestamps() { + let mut test_me = + PlusMinusHeaderHighlighter::from_line("--- x.txt\t2023-12-15 15:43:29").unwrap(); + let mut response = test_me + .consume_line( + "+++ /Users/johan/src/riff/README.md\t2024-01-29 14:56:40", + &ThreadPool::new(1), + ) + .unwrap(); + assert_eq!(LineAcceptance::AcceptedDone, response.line_accepted); + assert_eq!(1, response.highlighted.len()); + + let mut highlighted = response.highlighted[0].get().to_string().into_bytes(); + remove_ansi_escape_codes(&mut highlighted); + let plain = String::from_utf8(highlighted).unwrap(); + + assert_eq!( + "--- x.txt 2023-12-15 15:43:29\n\ + +++ /Users/johan/src/riff/README.md 2024-01-29 14:56:40\n", + plain.as_str() + ); + } +} diff --git src/token_collector.rs src/token_collector.rs index b58c50f..e4d4152 100644 --- src/token_collector.rs +++ src/token_collector.rs @@ -1,3 +1,5 @@ +use std::cmp; + use crate::ansi::AnsiStyle; use crate::ansi::Color::Default; use crate::ansi::Color::Green; @@ -311,6 +313,38 @@ pub fn highlight_nonleading_tabs(tokens: &mut [StyledToken]) { } } +pub(crate) fn align_tabs(old: &mut [StyledToken], new: &mut [StyledToken]) { + let old_tab_index_token = old.iter().position(|token| token.token == "\t"); + if old_tab_index_token.is_none() { + return; + } + let old_tab_index_token = old_tab_index_token.unwrap(); + let old_tab_index_char = old + .iter() + .take(old_tab_index_token) + .map(|token| token.token.chars().count()) + .sum::(); + + let new_tab_index_token = new.iter().position(|token| token.token == "\t"); + if new_tab_index_token.is_none() { + return; + } + let new_tab_index_token = new_tab_index_token.unwrap(); + let new_tab_index_char = new + .iter() + .take(new_tab_index_token) + .map(|token| token.token.chars().count()) + .sum::(); + + let old_spaces = + " ".repeat(2 + cmp::max(old_tab_index_char, new_tab_index_char) - old_tab_index_char); + let new_spaces = + " ".repeat(2 + cmp::max(old_tab_index_char, new_tab_index_char) - new_tab_index_char); + + old[old_tab_index_token].token = old_spaces; + new[new_tab_index_token].token = new_spaces; +} + /// Highlight single space between two highlighted tokens pub fn bridge_consecutive_highlighted_tokens(tokens: &mut [StyledToken]) { enum FoundState {