Ellipsize (#467)

* feat: add Ellipsize enum

* chore: API changes needed for ellipsize

Decided not to change "layout()" function for now to avoid breaking the
interface. For now.

* chore: shape ellipsis

* feat: Ellipsize::Start

Since it can only have 1 line, it's easier to implement.

* DROPME: temporarily change rich-text for testing

* test(ellipsize): Testing Ellipsize::Start

Long text in small buffer should produce ellipsis glyphs

* fix: do not need font_system anymore

We moved ellipsis shaping elsewhere so no need to pass font_system to
layout function (which also was recreating a new one in the tests every
time making them take forever).

* feat: Ellipsize::End

* improv(ellipsize): use a single ellipsis shape

* improv: Ellipsie::End && Wrap::None

There is no need to layout the whole line if it's not going to fit.

* fix: mixed bidi text when Ellipsize::End && Wrap::None

* chore: clean up and simplify when line.RTL==span.RTL

* fix(ellipsize): last word is not (word_count -1) if iter().rev()

* refactor(layout): extract the layout algorithm to make it more readable

* improv(ellipsize): Ellipsize::Start && Wrap::None

we iterate in reverse and only layout what's going to be visible

* Revert: delete the previous approach of post processing ellipsis

* doc: explain the interaction between Ellipsize and Wrap

* chore: lower the scope

* feat: Ellipsize the last line of a paragraph

For now only the number of lines is supported

* fix: clear ellipsized field on visual lines

This was causing ellipsis to show on random lines

* chore: remove old tests

will add better tests soon

* chore: clean up changes from previous attempt

* fix: consider the ellipsis width when doing alignment

* feat(ellipsize): add `Height` limit to `Ellipsize`

* fix: ellipsize the start of the last line correctly

* fix: ellipsize at the start of mixed bidi lines

* feat: Ellipsize::Middle

* fix: consider ellipsize::middle when calculating alignment correction

* refactor: improve readability

* refactor: deduplicate "fit_glyphs"

* refactor: combine backward and forward layout into one (wip)

* fix: Backward works in the unified layout_spans

* chore: clean up

* fix: Ellipsize::Middle

* fix: handle large words in bidi boundaries

* chore: clean up and some refactoring

* fix: ellipsis is now the same level as the surrounding text

* fix: try to fit more when ellipsizing::middle

* improv: ellipsis now have the same level as the neighbors

This makes ellipsized RTL text inside a LTR line more readable.

before:

Hello سلام...خوبی؟
Hello خولی؟...سلام

* fix: some extra words were being rendered in Ellipsize::Middle

This was causing the last word (if it's not the same level as the rest)
to be rendered outside the buffer.

* test: a few test cases for ellipsize

* fix: assign the correct byte range to ellipsis

this should fix the panic when selecting or clicking on or near the
ellipsis in the editor.
This commit is contained in:
Hojjat Abdollahi 2026-02-19 09:11:22 -07:00 committed by GitHub
parent 4fd11f0e5e
commit 4819bc30fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1568 additions and 313 deletions

View file

@ -11,8 +11,8 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color,
Cursor, FontSystem, Hinting, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter,
Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding,
LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
};
/// A line of visible text for rendering
@ -214,6 +214,7 @@ pub struct Buffer {
/// True if a redraw is requires. Set to false after processing
redraw: bool,
wrap: Wrap,
ellipsize: Ellipsize,
monospace_width: Option<f32>,
tab_width: u16,
hinting: Hinting,
@ -229,6 +230,7 @@ impl Clone for Buffer {
scroll: self.scroll,
redraw: self.redraw,
wrap: self.wrap,
ellipsize: self.ellipsize,
monospace_width: self.monospace_width,
tab_width: self.tab_width,
hinting: self.hinting,
@ -258,6 +260,7 @@ impl Buffer {
scroll: Scroll::default(),
redraw: false,
wrap: Wrap::WordOrGlyph,
ellipsize: Ellipsize::None,
monospace_width: None,
tab_width: 8,
hinting: Hinting::default(),
@ -298,6 +301,7 @@ impl Buffer {
self.metrics.font_size,
self.width_opt,
self.wrap,
self.ellipsize,
self.monospace_width,
self.tab_width,
self.hinting,
@ -542,6 +546,7 @@ impl Buffer {
self.metrics.font_size,
self.width_opt,
self.wrap,
self.ellipsize,
self.monospace_width,
self.tab_width,
self.hinting,
@ -590,6 +595,20 @@ impl Buffer {
}
}
/// Get the current [`Ellipsize`]
pub const fn ellipsize(&self) -> Ellipsize {
self.ellipsize
}
/// Set the current [`Ellipsize`]
pub fn set_ellipsize(&mut self, font_system: &mut FontSystem, ellipsize: Ellipsize) {
if ellipsize != self.ellipsize {
self.ellipsize = ellipsize;
self.relayout(font_system);
self.shape_until_scroll(font_system, false);
}
}
/// Get the current `monospace_width`
pub const fn monospace_width(&self) -> Option<f32> {
self.monospace_width
@ -1406,6 +1425,11 @@ impl BorrowedWithFontSystem<'_, Buffer> {
self.inner.set_wrap(self.font_system, wrap);
}
/// Set the current [`Ellipsize`]
pub fn set_ellipsize(&mut self, ellipsize: Ellipsize) {
self.inner.set_ellipsize(self.font_system, ellipsize);
}
/// Set the current buffer dimensions
pub fn set_size(&mut self, width_opt: Option<f32>, height_opt: Option<f32>) {
self.inner.set_size(self.font_system, width_opt, height_opt);

View file

@ -1,10 +1,12 @@
#![allow(clippy::too_many_arguments)]
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::mem;
use crate::{
Align, Attrs, AttrsList, Cached, FontSystem, Hinting, LayoutLine, LineEnding, ShapeLine,
Shaping, Wrap,
Align, Attrs, AttrsList, Cached, Ellipsize, FontSystem, Hinting, LayoutLine, LineEnding,
ShapeLine, Shaping, Wrap,
};
/// A line (or paragraph) of text that is shaped and laid out
@ -242,6 +244,7 @@ impl BufferLine {
font_size: f32,
width_opt: Option<f32>,
wrap: Wrap,
ellipsize: Ellipsize,
match_mono_width: Option<f32>,
tab_width: u16,
hinting: Hinting,
@ -258,6 +261,7 @@ impl BufferLine {
font_size,
width_opt,
wrap,
ellipsize,
align,
&mut layout,
match_mono_width,

View file

@ -152,6 +152,29 @@ impl Display for Align {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Ellipsize {
/// No Ellipsizing
#[default]
None,
/// Ellipsizes the start of the last visual line that fits within the `EllipsizeHeightLimit`
Start(EllipsizeHeightLimit),
/// Ellipsizes the middle of the last visual line that fits within the `EllipsizeHeightLimit`.
Middle(EllipsizeHeightLimit),
/// Ellipsizes the end of the last visual line that fits within the `EllipsizeHeightLimit`.
End(EllipsizeHeightLimit),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EllipsizeHeightLimit {
/// Number of lines to show before ellipsizing the rest. Only works if `Wrap` is NOT set to
/// `Wrap::None`. Otherwise, it will be ignored and the behavior will be the same as `Lines(1)`
Lines(usize),
/// Ellipsizes the last line that fits within the given height limit. If `Wrap` is set to
/// `Wrap::None`, the behavior will be the same as `Lines(1)`
Height(f32),
}
/// Metrics hinting strategy
#[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
pub enum Hinting {

File diff suppressed because it is too large Load diff