This commit is contained in:
Mikhail Kilin
2026-02-13 19:52:53 +03:00
parent 6d08300daa
commit 6639dc876c
38 changed files with 961 additions and 123 deletions

View File

@@ -24,19 +24,40 @@ struct WrappedLine {
start_offset: usize,
}
/// Разбивает текст на строки с учётом максимальной ширины
/// Разбивает текст на строки с учётом максимальной ширины и `\n`
fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
let mut all_lines = Vec::new();
let mut char_offset = 0;
for segment in text.split('\n') {
let wrapped = wrap_paragraph(segment, max_width, char_offset);
all_lines.extend(wrapped);
char_offset += segment.chars().count() + 1; // +1 за '\n'
}
if all_lines.is_empty() {
all_lines.push(WrappedLine {
text: String::new(),
start_offset: 0,
});
}
all_lines
}
/// Разбивает один абзац (без `\n`) на строки по ширине
fn wrap_paragraph(text: &str, max_width: usize, base_offset: usize) -> Vec<WrappedLine> {
if max_width == 0 {
return vec![WrappedLine {
text: text.to_string(),
start_offset: 0,
start_offset: base_offset,
}];
}
let mut result = Vec::new();
let mut current_line = String::new();
let mut current_width = 0;
let mut line_start_offset = 0;
let mut line_start_offset = base_offset;
let chars: Vec<char> = text.chars().collect();
let mut word_start = 0;
@@ -51,7 +72,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
if current_width == 0 {
current_line = word;
current_width = word_width;
line_start_offset = word_start;
line_start_offset = base_offset + word_start;
} else if current_width + 1 + word_width <= max_width {
current_line.push(' ');
current_line.push_str(&word);
@@ -63,7 +84,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
});
current_line = word;
current_width = word_width;
line_start_offset = word_start;
line_start_offset = base_offset + word_start;
}
in_word = false;
}
@@ -79,7 +100,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
if current_width == 0 {
current_line = word;
line_start_offset = word_start;
line_start_offset = base_offset + word_start;
} else if current_width + 1 + word_width <= max_width {
current_line.push(' ');
current_line.push_str(&word);
@@ -89,7 +110,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
start_offset: line_start_offset,
});
current_line = word;
line_start_offset = word_start;
line_start_offset = base_offset + word_start;
}
}
@@ -103,7 +124,7 @@ fn wrap_text_with_offsets(text: &str, max_width: usize) -> Vec<WrappedLine> {
if result.is_empty() {
result.push(WrappedLine {
text: String::new(),
start_offset: 0,
start_offset: base_offset,
});
}
@@ -288,11 +309,15 @@ 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 {
if is_selected && i == 0 {
// Одна строка — маркер на ней
line_spans.push(Span::styled(
selection_marker,
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
));
} else if is_selected {
// Последняя строка multi-line — пробелы вместо маркера
line_spans.push(Span::raw(" ".repeat(marker_len)));
}
line_spans.extend(formatted_spans);
line_spans.push(Span::styled(format!(" {}", time_mark), Style::default().fg(Color::Gray)));
@@ -305,6 +330,9 @@ pub fn render_message_bubble(
selection_marker,
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
));
} else if is_selected {
// Средние строки multi-line — пробелы вместо маркера
line_spans.push(Span::raw(" ".repeat(marker_len)));
}
line_spans.extend(formatted_spans);
lines.push(Line::from(line_spans));