From a095fe277b14ec1df19ff436a31399d627130b3b Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Tue, 24 Feb 2026 15:49:08 +0300 Subject: [PATCH] fix: always reserve space for selection marker to prevent text shift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render " " (2 spaces) for unselected messages instead of nothing, so text stays aligned when navigating with the ▶ selection indicator. Co-Authored-By: Claude Opus 4.6 --- src/ui/components/message_bubble.rs | 52 +++++++++---------- .../input_field__input_reply_mode.snap | 2 +- tests/snapshots/messages__album_incoming.snap | 6 +-- tests/snapshots/messages__album_selected.snap | 2 +- ...messages__album_with_regular_messages.snap | 8 +-- .../messages__date_separator_old_date.snap | 2 +- tests/snapshots/messages__edited_message.snap | 2 +- .../messages__forwarded_message.snap | 2 +- .../messages__long_message_wrap.snap | 6 +-- .../messages__markdown_bold_italic_code.snap | 2 +- .../messages__markdown_link_mention.snap | 2 +- .../snapshots/messages__markdown_spoiler.snap | 2 +- .../messages__media_placeholder.snap | 2 +- .../messages__multiple_reactions.snap | 2 +- tests/snapshots/messages__reply_message.snap | 2 +- .../snapshots/messages__selected_message.snap | 2 +- .../snapshots/messages__sender_grouping.snap | 6 +-- .../messages__single_incoming_message.snap | 2 +- .../snapshots/messages__single_reaction.snap | 2 +- .../modals__emoji_picker_default.snap | 2 +- .../modals__emoji_picker_with_selection.snap | 2 +- tests/snapshots/modals__pinned_message.snap | 2 +- 22 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/ui/components/message_bubble.rs b/src/ui/components/message_bubble.rs index db26cf5..3769914 100644 --- a/src/ui/components/message_bubble.rs +++ b/src/ui/components/message_bubble.rs @@ -221,9 +221,9 @@ pub fn render_message_bubble( let mut lines = Vec::new(); let is_selected = selected_msg_id == Some(msg.id()); - // Маркер выбора - let selection_marker = if is_selected { "▶ " } else { "" }; - let marker_len = selection_marker.chars().count(); + // Маркер выбора (всегда резервируем место для ▶, чтобы текст не сдвигался) + let selection_marker = if is_selected { "▶ " } else { " " }; + let marker_len = 2; // Цвет сообщения let msg_color = if is_selected { @@ -306,16 +306,16 @@ pub fn render_message_bubble( let full_len = line_len + time_mark_len + marker_len; let padding = content_width.saturating_sub(full_len + 1); let mut line_spans = vec![Span::raw(" ".repeat(padding))]; - if is_selected && i == 0 { - // Одна строка — маркер на ней + if i == 0 { + // Первая (или единственная) строка — маркер line_spans.push(Span::styled( selection_marker, Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), )); - } else if is_selected { - // Последняя строка multi-line — пробелы вместо маркера + } else { + // Остальные строки multi-line — пробелы вместо маркера line_spans.push(Span::raw(" ".repeat(marker_len))); } line_spans.extend(formatted_spans); @@ -327,14 +327,14 @@ pub fn render_message_bubble( } else { let padding = content_width.saturating_sub(line_len + marker_len + 1); let mut line_spans = vec![Span::raw(" ".repeat(padding))]; - if i == 0 && is_selected { + if i == 0 { line_spans.push(Span::styled( selection_marker, Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), )); - } else if is_selected { + } else { // Средние строки multi-line — пробелы вместо маркера line_spans.push(Span::raw(" ".repeat(marker_len))); } @@ -364,14 +364,12 @@ pub fn render_message_bubble( if i == 0 { let mut line_spans = vec![]; - if is_selected { - line_spans.push(Span::styled( - selection_marker, - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - )); - } + line_spans.push(Span::styled( + selection_marker, + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + )); line_spans .push(Span::styled(format!(" {}", time_str), Style::default().fg(Color::Gray))); line_spans.push(Span::raw(" ")); @@ -548,8 +546,8 @@ pub fn render_album_bubble( let is_selected = messages.iter().any(|m| selected_msg_id == Some(m.id())); let is_outgoing = messages.first().is_some_and(|m| m.is_outgoing()); - // Selection marker - let selection_marker = if is_selected { "▶ " } else { "" }; + // Selection marker (всегда резервируем место) + let selection_marker = if is_selected { "▶ " } else { " " }; // Фильтруем фото let photos: Vec<&MessageInfo> = messages.iter().filter(|m| m.has_photo()).collect(); @@ -567,15 +565,13 @@ pub fn render_album_bubble( let cols = photo_count.min(ALBUM_GRID_MAX_COLS); let rows = photo_count.div_ceil(cols); - // Добавляем маркер выбора на первую строку - if is_selected { - lines.push(Line::from(vec![Span::styled( - selection_marker, - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - )])); - } + // Добавляем маркер выбора на первую строку (всегда — для постоянного отступа) + lines.push(Line::from(vec![Span::styled( + selection_marker, + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + )])); let grid_start_line = lines.len(); diff --git a/tests/snapshots/input_field__input_reply_mode.snap b/tests/snapshots/input_field__input_reply_mode.snap index 6d4855f..acaaa08 100644 --- a/tests/snapshots/input_field__input_reply_mode.snap +++ b/tests/snapshots/input_field__input_reply_mode.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Mom ──────────────── │ -│ (14:33) What do you think about this? │ +│ (14:33) What do you think about this? │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__album_incoming.snap b/tests/snapshots/messages__album_incoming.snap index 4e9c3b5..cae2ce1 100644 --- a/tests/snapshots/messages__album_incoming.snap +++ b/tests/snapshots/messages__album_incoming.snap @@ -9,9 +9,9 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Alice ──────────────── │ -│ (14:33) 📷 [Фото] │ -│ (14:33) Caption for album │ -│ (14:33) 📷 [Фото] │ +│ (14:33) 📷 [Фото] │ +│ (14:33) Caption for album │ +│ (14:33) 📷 [Фото] │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__album_selected.snap b/tests/snapshots/messages__album_selected.snap index c8dd19e..5244392 100644 --- a/tests/snapshots/messages__album_selected.snap +++ b/tests/snapshots/messages__album_selected.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Alice ──────────────── │ -│ (14:33) 📷 [Фото] │ +│ (14:33) 📷 [Фото] │ │▶ (14:33) 📷 [Фото] │ │ │ │ │ diff --git a/tests/snapshots/messages__album_with_regular_messages.snap b/tests/snapshots/messages__album_with_regular_messages.snap index 264475b..736f998 100644 --- a/tests/snapshots/messages__album_with_regular_messages.snap +++ b/tests/snapshots/messages__album_with_regular_messages.snap @@ -9,10 +9,10 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Alice ──────────────── │ -│ (14:33) Regular message before │ -│ (14:33) 📷 [Фото] │ -│ (14:33) Album caption │ -│ (14:33) Regular message after │ +│ (14:33) Regular message before │ +│ (14:33) 📷 [Фото] │ +│ (14:33) Album caption │ +│ (14:33) Regular message after │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__date_separator_old_date.snap b/tests/snapshots/messages__date_separator_old_date.snap index 7236593..4200d35 100644 --- a/tests/snapshots/messages__date_separator_old_date.snap +++ b/tests/snapshots/messages__date_separator_old_date.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Message from the past │ +│ (14:33) Message from the past │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__edited_message.snap b/tests/snapshots/messages__edited_message.snap index c98497c..84af6a3 100644 --- a/tests/snapshots/messages__edited_message.snap +++ b/tests/snapshots/messages__edited_message.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33 ✎) Edited text │ +│ (14:33 ✎) Edited text │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__forwarded_message.snap b/tests/snapshots/messages__forwarded_message.snap index 918bc8f..636d936 100644 --- a/tests/snapshots/messages__forwarded_message.snap +++ b/tests/snapshots/messages__forwarded_message.snap @@ -10,7 +10,7 @@ expression: output │ │ │User ──────────────── │ │↪ Переслано от Alice │ -│ (14:33) Forwarded content │ +│ (14:33) Forwarded content │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__long_message_wrap.snap b/tests/snapshots/messages__long_message_wrap.snap index 2beffe5..e56c2b9 100644 --- a/tests/snapshots/messages__long_message_wrap.snap +++ b/tests/snapshots/messages__long_message_wrap.snap @@ -9,9 +9,9 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) This is a very long message that should wrap across multiple lines │ -│ when rendered in the terminal UI. Let's make it even longer to │ -│ ensure we test the wrapping behavior properly. │ +│ (14:33) This is a very long message that should wrap across multiple lines │ +│ when rendered in the terminal UI. Let's make it even longer to │ +│ ensure we test the wrapping behavior properly. │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__markdown_bold_italic_code.snap b/tests/snapshots/messages__markdown_bold_italic_code.snap index cfe7134..99a2df2 100644 --- a/tests/snapshots/messages__markdown_bold_italic_code.snap +++ b/tests/snapshots/messages__markdown_bold_italic_code.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) **bold** *italic* `code` │ +│ (14:33) **bold** *italic* `code` │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__markdown_link_mention.snap b/tests/snapshots/messages__markdown_link_mention.snap index aacbe63..5e328b8 100644 --- a/tests/snapshots/messages__markdown_link_mention.snap +++ b/tests/snapshots/messages__markdown_link_mention.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Check [this](https://example.com) and @username │ +│ (14:33) Check [this](https://example.com) and @username │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__markdown_spoiler.snap b/tests/snapshots/messages__markdown_spoiler.snap index 9458598..7966c3b 100644 --- a/tests/snapshots/messages__markdown_spoiler.snap +++ b/tests/snapshots/messages__markdown_spoiler.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Spoiler: ||hidden text|| │ +│ (14:33) Spoiler: ||hidden text|| │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__media_placeholder.snap b/tests/snapshots/messages__media_placeholder.snap index 210f9fd..8fa7e87 100644 --- a/tests/snapshots/messages__media_placeholder.snap +++ b/tests/snapshots/messages__media_placeholder.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) [Фото] │ +│ (14:33) [Фото] │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__multiple_reactions.snap b/tests/snapshots/messages__multiple_reactions.snap index a8a8808..5f93505 100644 --- a/tests/snapshots/messages__multiple_reactions.snap +++ b/tests/snapshots/messages__multiple_reactions.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Popular message │ +│ (14:33) Popular message │ │[👍 ] 5 👎 3 │ │ │ │ │ diff --git a/tests/snapshots/messages__reply_message.snap b/tests/snapshots/messages__reply_message.snap index c0e65e8..f73b934 100644 --- a/tests/snapshots/messages__reply_message.snap +++ b/tests/snapshots/messages__reply_message.snap @@ -10,7 +10,7 @@ expression: output │ │ │User ──────────────── │ │┌ Mom: Original message text │ -│ (14:33) This is a reply │ +│ (14:33) This is a reply │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__selected_message.snap b/tests/snapshots/messages__selected_message.snap index 581d331..5766841 100644 --- a/tests/snapshots/messages__selected_message.snap +++ b/tests/snapshots/messages__selected_message.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Selected message │ +│ (14:33) Selected message │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__sender_grouping.snap b/tests/snapshots/messages__sender_grouping.snap index c2d894e..995bf10 100644 --- a/tests/snapshots/messages__sender_grouping.snap +++ b/tests/snapshots/messages__sender_grouping.snap @@ -9,11 +9,11 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Alice ──────────────── │ -│ (14:33) First message │ -│ (14:33) Second message │ +│ (14:33) First message │ +│ (14:33) Second message │ │ │ │Bob ──────────────── │ -│ (14:33) Third message │ +│ (14:33) Third message │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__single_incoming_message.snap b/tests/snapshots/messages__single_incoming_message.snap index 9d23183..d04005c 100644 --- a/tests/snapshots/messages__single_incoming_message.snap +++ b/tests/snapshots/messages__single_incoming_message.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │Mom ──────────────── │ -│ (14:33) Hello there! │ +│ (14:33) Hello there! │ │ │ │ │ │ │ diff --git a/tests/snapshots/messages__single_reaction.snap b/tests/snapshots/messages__single_reaction.snap index 4c185b6..8db49e4 100644 --- a/tests/snapshots/messages__single_reaction.snap +++ b/tests/snapshots/messages__single_reaction.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Great! │ +│ (14:33) Great! │ │[👍 ] │ │ │ │ │ diff --git a/tests/snapshots/modals__emoji_picker_default.snap b/tests/snapshots/modals__emoji_picker_default.snap index 0a9e3de..4896adf 100644 --- a/tests/snapshots/modals__emoji_picker_default.snap +++ b/tests/snapshots/modals__emoji_picker_default.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) React to this │ +│ (14:33) React to this │ │ │ │ ┌ Выбери реакцию ────────────────────────────────┐ │ │ │ │ │ diff --git a/tests/snapshots/modals__emoji_picker_with_selection.snap b/tests/snapshots/modals__emoji_picker_with_selection.snap index 0a9e3de..4896adf 100644 --- a/tests/snapshots/modals__emoji_picker_with_selection.snap +++ b/tests/snapshots/modals__emoji_picker_with_selection.snap @@ -9,7 +9,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) React to this │ +│ (14:33) React to this │ │ │ │ ┌ Выбери реакцию ────────────────────────────────┐ │ │ │ │ │ diff --git a/tests/snapshots/modals__pinned_message.snap b/tests/snapshots/modals__pinned_message.snap index 6c5b1aa..1a8c437 100644 --- a/tests/snapshots/modals__pinned_message.snap +++ b/tests/snapshots/modals__pinned_message.snap @@ -10,7 +10,7 @@ expression: output │ ──────── 02.01.2022 ──────── │ │ │ │User ──────────────── │ -│ (14:33) Regular message │ +│ (14:33) Regular message │ │ │ │ │ │ │ -- 2.49.1