Skip to content

Commit 0207160

Browse files
authored
fix(line): line truncation respects alignment (#987)
When rendering a `Line`, the line will be truncated: - on the right for left aligned lines - on the left for right aligned lines - on bot sides for centered lines E.g. "Hello World" will be rendered as "Hello", "World", "lo wo" for left, right, centered lines respectively. Fixes: #932
1 parent 26af650 commit 0207160

File tree

1 file changed

+94
-7
lines changed

1 file changed

+94
-7
lines changed

src/text/line.rs

+94-7
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,39 @@ impl<'a> Line<'a> {
453453
self.spans.iter_mut()
454454
}
455455

456+
/// Returns a line that's truncated corresponding to it's alignment and result width
457+
#[must_use = "method returns the modified value"]
458+
fn truncated(&'a self, result_width: u16) -> Self {
459+
let mut truncated_line = Line::default();
460+
let width = self.width() as u16;
461+
let mut offset = match self.alignment {
462+
Some(Alignment::Center) => (width.saturating_sub(result_width)) / 2,
463+
Some(Alignment::Right) => width.saturating_sub(result_width),
464+
_ => 0,
465+
};
466+
let mut x = 0;
467+
for span in &self.spans {
468+
let span_width = span.width() as u16;
469+
if offset >= span_width {
470+
offset -= span_width;
471+
continue;
472+
}
473+
let mut new_span = span.clone();
474+
let new_span_width = span_width - offset;
475+
if x + new_span_width > result_width {
476+
let span_end = (result_width - x + offset) as usize;
477+
new_span.content = Cow::from(&span.content[offset as usize..span_end]);
478+
truncated_line.spans.push(new_span);
479+
break;
480+
}
481+
482+
new_span.content = Cow::from(&span.content[offset as usize..]);
483+
truncated_line.spans.push(new_span);
484+
x += new_span_width;
485+
offset = 0;
486+
}
487+
truncated_line
488+
}
456489
/// Adds a span to the line.
457490
///
458491
/// `span` can be any type that is convertible into a `Span`. For example, you can pass a
@@ -554,17 +587,23 @@ impl WidgetRef for Line<'_> {
554587
let area = area.intersection(buf.area);
555588
buf.set_style(area, self.style);
556589
let width = self.width() as u16;
557-
let offset = match self.alignment {
558-
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
559-
Some(Alignment::Right) => area.width.saturating_sub(width),
560-
Some(Alignment::Left) | None => 0,
590+
let mut x = area.left();
591+
let line = if width > area.width {
592+
self.truncated(area.width)
593+
} else {
594+
let offset = match self.alignment {
595+
Some(Alignment::Center) => (area.width.saturating_sub(width)) / 2,
596+
Some(Alignment::Right) => area.width.saturating_sub(width),
597+
Some(Alignment::Left) | None => 0,
598+
};
599+
x = x.saturating_add(offset);
600+
self.to_owned()
561601
};
562-
let mut x = area.left().saturating_add(offset);
563-
for span in &self.spans {
602+
for span in &line.spans {
564603
let span_width = span.width() as u16;
565604
let span_area = Rect {
566605
x,
567-
width: span_width.min(area.right() - x),
606+
width: span_width.min(area.right().saturating_sub(x)),
568607
..area
569608
};
570609
span.render(span_area, buf);
@@ -604,6 +643,7 @@ mod tests {
604643
use rstest::{fixture, rstest};
605644

606645
use super::*;
646+
use crate::assert_buffer_eq;
607647

608648
#[fixture]
609649
fn small_buf() -> Buffer {
@@ -847,6 +887,53 @@ mod tests {
847887

848888
assert_eq!(format!("{line_from_styled_span}"), "Hello, world!");
849889
}
890+
#[test]
891+
fn render_truncates_left() {
892+
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
893+
Line::from("Hello world")
894+
.left_aligned()
895+
.render(buf.area, &mut buf);
896+
let expected = Buffer::with_lines(vec!["Hello"]);
897+
assert_buffer_eq!(buf, expected);
898+
}
899+
900+
#[test]
901+
fn render_truncates_right() {
902+
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
903+
Line::from("Hello world")
904+
.right_aligned()
905+
.render(buf.area, &mut buf);
906+
let expected = Buffer::with_lines(vec!["world"]);
907+
assert_buffer_eq!(buf, expected);
908+
}
909+
910+
#[test]
911+
fn render_truncates_center() {
912+
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
913+
Line::from("Hello world")
914+
.centered()
915+
.render(buf.area, &mut buf);
916+
let expected = Buffer::with_lines(vec!["lo wo"]);
917+
assert_buffer_eq!(buf, expected);
918+
}
919+
920+
#[test]
921+
fn truncate_line_with_multiple_spans() {
922+
let line = Line::default().spans(vec!["foo", "bar"]);
923+
assert_eq!(
924+
line.right_aligned().truncated(4).to_string(),
925+
String::from("obar")
926+
);
927+
}
928+
929+
#[test]
930+
fn truncation_ignores_useless_spans() {
931+
let line = Line::default().spans(vec!["foo", "bar"]);
932+
assert_eq!(
933+
line.right_aligned().truncated(3),
934+
Line::default().spans(vec!["bar"])
935+
);
936+
}
850937

851938
#[test]
852939
fn left_aligned() {

0 commit comments

Comments
 (0)