Compare commits

..

2 commits

Author SHA1 Message Date
8f5a8f8251 Add measured widths 2022-06-07 21:26:10 +02:00
bcf6193cd9 Measure widths of various unicode characters 2022-05-31 15:05:40 +02:00
46 changed files with 10741 additions and 4138 deletions

View file

@ -1,8 +0,0 @@
{
"files.insertFinalNewline": true,
"rust-analyzer.cargo.features": "all",
"rust-analyzer.imports.granularity.enforce": true,
"rust-analyzer.imports.granularity.group": "module",
"rust-analyzer.imports.group.enable": true,
"evenBetterToml.formatter.columnWidth": 100,
}

View file

@ -1,70 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Procedure when bumping the version number:
1. Update dependencies in a separate commit
2. Set version number in `Cargo.toml`
3. Add new section in this changelog
4. Commit with message `Bump version to X.Y.Z`
5. Create tag named `vX.Y.Z`
6. Push `master` and the new tag
## Unreleased
## v0.3.4 - 2025-03-8
### Added
- `Frame::set_bell` to print a bell character when the frame is displayed
- `widgets::bell`
## v0.3.3 - 2025-02-28
### Fixed
- Rendering glitches in unicode-based with estimation
## v0.3.2 - 2025-02-23
### Added
- Unicode-based grapheme width estimation method
## v0.3.1 - 2025-02-21
### Fixed
- Rendering glitches, mainly related to emoji
## v0.3.0 - 2024-11-06
### Added
- `Terminal::mark_dirty`
### Changed
- **(breaking)** Updated dependencies
## v0.2.3 - 2024-04-25
### Fixed
- Width measurements of ASCII control characters
- Toss messing up the terminal state
## v0.2.2 - 2024-01-14
### Fixed
- Crash when drawing `widgets::Predrawn` with width 0
## v0.2.1 - 2024-01-05
### Added
- `Frame::set_title`
- `WidgetExt::title`
- `widgets::title`
## v0.2.0 - 2023-08-31
### Changed
- **(breaking)** Updated dependencies
## v0.1.0 - 2023-05-14
Initial release

View file

@ -1,11 +1,13 @@
[package]
name = "toss"
version = "0.3.4"
version = "0.1.0"
edition = "2021"
[dependencies]
async-trait = "0.1.83"
crossterm = "0.28.1"
unicode-linebreak = "0.1.5"
unicode-segmentation = "1.12.0"
unicode-width = "0.2.0"
crossterm = "0.23.2"
unicode-linebreak = "0.1.2"
unicode-segmentation = "1.9.0"
unicode-width = "0.1.9"
[dev-dependencies]
unicode-blocks = "0.1.4"

View file

@ -1,30 +1,39 @@
use crossterm::event::Event;
use crossterm::style::Stylize;
use toss::{Frame, Pos, Style, Terminal};
use crossterm::style::{ContentStyle, Stylize};
use toss::frame::{Frame, Pos};
use toss::terminal::{Redraw, Terminal};
fn draw(f: &mut Frame) {
f.write(Pos::new(0, 0), ("Hello world!", Style::new().green()));
f.write(
Pos::new(0, 0),
"Hello world!",
ContentStyle::default().green(),
);
f.write(
Pos::new(0, 1),
("Press any key to exit", Style::new().on_dark_blue()),
"Press any key to exit",
ContentStyle::default().on_dark_blue(),
);
f.show_cursor(Pos::new(16, 0));
}
fn render_frame(term: &mut Terminal) {
let mut dirty = true;
while dirty {
loop {
// Must be called before rendering, otherwise the terminal has out-of-date
// size information and will present garbage.
term.autoresize().unwrap();
draw(term.frame());
term.present().unwrap();
dirty = term.measure_widths().unwrap();
if term.present().unwrap() == Redraw::NotRequired {
break;
}
}
}
fn main() {
// Automatically enters alternate screen and enables raw mode
let mut term = Terminal::new().unwrap();
term.set_measuring(true);
loop {
// Render and display a frame. A full frame is displayed on the terminal

View file

@ -1,47 +0,0 @@
use std::io;
use crossterm::event::Event;
use crossterm::style::Stylize;
use toss::widgets::{BorderLook, Text};
use toss::{Style, Styled, Terminal, Widget, WidgetExt};
fn widget() -> impl Widget<io::Error> {
let styled = Styled::new("Hello world!", Style::new().dark_green())
.then_plain("\n")
.then("Press any key to exit", Style::new().on_dark_blue());
Text::new(styled)
.padding()
.with_horizontal(1)
.border()
.with_look(BorderLook::LINE_DOUBLE)
.with_style(Style::new().dark_red())
.background()
.with_style(Style::new().on_yellow().opaque())
.float()
.with_all(0.5)
}
fn render_frame(term: &mut Terminal) {
let mut dirty = true;
while dirty {
term.present_widget(widget()).unwrap();
dirty = term.measure_widths().unwrap();
}
}
fn main() {
// Automatically enters alternate screen and enables raw mode
let mut term = Terminal::new().unwrap();
term.set_measuring(true);
loop {
// Render and display a frame. A full frame is displayed on the terminal
// once this function exits.
render_frame(&mut term);
// Exit if the user presses any buttons
if !matches!(crossterm::event::read().unwrap(), Event::Resize(_, _)) {
break;
}
}
}

375
examples/measure_widths.rs Normal file
View file

@ -0,0 +1,375 @@
use std::io;
use crossterm::cursor::MoveTo;
use crossterm::execute;
use crossterm::style::Print;
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};
use unicode_blocks::UnicodeBlock;
use unicode_width::UnicodeWidthChar;
use unicode_blocks as ub;
fn measure_width(c: char) {
if let Some(predicted_width) = c.width() {
execute!(
io::stdout(),
Clear(ClearType::CurrentLine),
MoveTo(0, 1),
Print(c),
)
.unwrap();
let actual_width: usize = crossterm::cursor::position().unwrap().0.into();
if predicted_width != actual_width {
eprintln!(
"{}: actual {actual_width}, expected {predicted_width}",
c as u32
);
}
}
}
fn measure_widths(block: UnicodeBlock) {
execute!(
io::stdout(),
Clear(ClearType::All),
MoveTo(0, 0),
Print(block.name()),
MoveTo(0, 1),
)
.unwrap();
for c in block.start()..=block.end() {
if let Some(c) = char::from_u32(c) {
measure_width(c);
} else {
eprintln!("{c}: error");
}
}
}
fn main() {
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen).unwrap();
measure_widths(ub::ADLAM);
measure_widths(ub::AEGEAN_NUMBERS);
measure_widths(ub::AHOM);
measure_widths(ub::ALCHEMICAL_SYMBOLS);
measure_widths(ub::ALPHABETIC_PRESENTATION_FORMS);
measure_widths(ub::ANATOLIAN_HIEROGLYPHS);
measure_widths(ub::ANCIENT_GREEK_MUSICAL_NOTATION);
measure_widths(ub::ANCIENT_GREEK_NUMBERS);
measure_widths(ub::ANCIENT_SYMBOLS);
measure_widths(ub::ARABIC);
measure_widths(ub::ARABIC_EXTENDED_A);
measure_widths(ub::ARABIC_EXTENDED_B);
measure_widths(ub::ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS);
measure_widths(ub::ARABIC_PRESENTATION_FORMS_A);
measure_widths(ub::ARABIC_PRESENTATION_FORMS_B);
measure_widths(ub::ARABIC_SUPPLEMENT);
measure_widths(ub::ARMENIAN);
measure_widths(ub::ARROWS);
measure_widths(ub::AVESTAN);
measure_widths(ub::BALINESE);
measure_widths(ub::BAMUM);
measure_widths(ub::BAMUM_SUPPLEMENT);
measure_widths(ub::BASIC_LATIN);
measure_widths(ub::BASSA_VAH);
measure_widths(ub::BATAK);
measure_widths(ub::BENGALI);
measure_widths(ub::BHAIKSUKI);
measure_widths(ub::BLOCK_ELEMENTS);
measure_widths(ub::BOPOMOFO);
measure_widths(ub::BOPOMOFO_EXTENDED);
measure_widths(ub::BOX_DRAWING);
measure_widths(ub::BRAHMI);
measure_widths(ub::BRAILLE_PATTERNS);
measure_widths(ub::BUGINESE);
measure_widths(ub::BUHID);
measure_widths(ub::BYZANTINE_MUSICAL_SYMBOLS);
measure_widths(ub::CARIAN);
measure_widths(ub::CAUCASIAN_ALBANIAN);
measure_widths(ub::CHAKMA);
measure_widths(ub::CHAM);
measure_widths(ub::CHEROKEE);
measure_widths(ub::CHEROKEE_SUPPLEMENT);
measure_widths(ub::CHESS_SYMBOLS);
measure_widths(ub::CHORASMIAN);
measure_widths(ub::CJK_COMPATIBILITY);
measure_widths(ub::CJK_COMPATIBILITY_FORMS);
measure_widths(ub::CJK_COMPATIBILITY_IDEOGRAPHS);
measure_widths(ub::CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT);
measure_widths(ub::CJK_RADICALS_SUPPLEMENT);
measure_widths(ub::CJK_STROKES);
measure_widths(ub::CJK_SYMBOLS_AND_PUNCTUATION);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F);
measure_widths(ub::CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G);
measure_widths(ub::COMBINING_DIACRITICAL_MARKS);
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_EXTENDED);
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS);
measure_widths(ub::COMBINING_DIACRITICAL_MARKS_SUPPLEMENT);
measure_widths(ub::COMBINING_HALF_MARKS);
measure_widths(ub::COMMON_INDIC_NUMBER_FORMS);
measure_widths(ub::CONTROL_PICTURES);
measure_widths(ub::COPTIC);
measure_widths(ub::COPTIC_EPACT_NUMBERS);
measure_widths(ub::COUNTING_ROD_NUMERALS);
measure_widths(ub::CUNEIFORM);
measure_widths(ub::CUNEIFORM_NUMBERS_AND_PUNCTUATION);
measure_widths(ub::CURRENCY_SYMBOLS);
measure_widths(ub::CYPRIOT_SYLLABARY);
measure_widths(ub::CYPRO_MINOAN);
measure_widths(ub::CYRILLIC);
measure_widths(ub::CYRILLIC_EXTENDED_A);
measure_widths(ub::CYRILLIC_EXTENDED_B);
measure_widths(ub::CYRILLIC_EXTENDED_C);
measure_widths(ub::CYRILLIC_SUPPLEMENT);
measure_widths(ub::DESERET);
measure_widths(ub::DEVANAGARI);
measure_widths(ub::DEVANAGARI_EXTENDED);
measure_widths(ub::DINGBATS);
measure_widths(ub::DIVES_AKURU);
measure_widths(ub::DOGRA);
measure_widths(ub::DOMINO_TILES);
measure_widths(ub::DUPLOYAN);
measure_widths(ub::EARLY_DYNASTIC_CUNEIFORM);
measure_widths(ub::EGYPTIAN_HIEROGLYPHS);
measure_widths(ub::EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS);
measure_widths(ub::ELBASAN);
measure_widths(ub::ELYMAIC);
measure_widths(ub::EMOTICONS);
measure_widths(ub::ENCLOSED_ALPHANUMERICS);
measure_widths(ub::ENCLOSED_ALPHANUMERIC_SUPPLEMENT);
measure_widths(ub::ENCLOSED_CJK_LETTERS_AND_MONTHS);
measure_widths(ub::ENCLOSED_IDEOGRAPHIC_SUPPLEMENT);
measure_widths(ub::ETHIOPIC);
measure_widths(ub::ETHIOPIC_EXTENDED);
measure_widths(ub::ETHIOPIC_EXTENDED_A);
measure_widths(ub::ETHIOPIC_EXTENDED_B);
measure_widths(ub::ETHIOPIC_SUPPLEMENT);
measure_widths(ub::GENERAL_PUNCTUATION);
measure_widths(ub::GEOMETRIC_SHAPES);
measure_widths(ub::GEOMETRIC_SHAPES_EXTENDED);
measure_widths(ub::GEORGIAN);
measure_widths(ub::GEORGIAN_EXTENDED);
measure_widths(ub::GEORGIAN_SUPPLEMENT);
measure_widths(ub::GLAGOLITIC);
measure_widths(ub::GLAGOLITIC_SUPPLEMENT);
measure_widths(ub::GOTHIC);
measure_widths(ub::GRANTHA);
measure_widths(ub::GREEK_AND_COPTIC);
measure_widths(ub::GREEK_EXTENDED);
measure_widths(ub::GUJARATI);
measure_widths(ub::GUNJALA_GONDI);
measure_widths(ub::GURMUKHI);
measure_widths(ub::HALFWIDTH_AND_FULLWIDTH_FORMS);
measure_widths(ub::HANGUL_COMPATIBILITY_JAMO);
measure_widths(ub::HANGUL_JAMO);
measure_widths(ub::HANGUL_JAMO_EXTENDED_A);
measure_widths(ub::HANGUL_JAMO_EXTENDED_B);
measure_widths(ub::HANGUL_SYLLABLES);
measure_widths(ub::HANIFI_ROHINGYA);
measure_widths(ub::HANUNOO);
measure_widths(ub::HATRAN);
measure_widths(ub::HEBREW);
// measure_widths(ub::HIGH_PRIVATE_USE_SURROGATES);
// measure_widths(ub::HIGH_SURROGATES);
measure_widths(ub::HIRAGANA);
measure_widths(ub::IDEOGRAPHIC_DESCRIPTION_CHARACTERS);
measure_widths(ub::IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION);
measure_widths(ub::IMPERIAL_ARAMAIC);
measure_widths(ub::INDIC_SIYAQ_NUMBERS);
measure_widths(ub::INSCRIPTIONAL_PAHLAVI);
measure_widths(ub::INSCRIPTIONAL_PARTHIAN);
measure_widths(ub::IPA_EXTENSIONS);
measure_widths(ub::JAVANESE);
measure_widths(ub::KAITHI);
measure_widths(ub::KANA_EXTENDED_A);
measure_widths(ub::KANA_EXTENDED_B);
measure_widths(ub::KANA_SUPPLEMENT);
measure_widths(ub::KANBUN);
measure_widths(ub::KANGXI_RADICALS);
measure_widths(ub::KANNADA);
measure_widths(ub::KATAKANA);
measure_widths(ub::KATAKANA_PHONETIC_EXTENSIONS);
measure_widths(ub::KAYAH_LI);
measure_widths(ub::KHAROSHTHI);
measure_widths(ub::KHITAN_SMALL_SCRIPT);
measure_widths(ub::KHMER);
measure_widths(ub::KHMER_SYMBOLS);
measure_widths(ub::KHOJKI);
measure_widths(ub::KHUDAWADI);
measure_widths(ub::LAO);
measure_widths(ub::LATIN_1_SUPPLEMENT);
measure_widths(ub::LATIN_EXTENDED_A);
measure_widths(ub::LATIN_EXTENDED_ADDITIONAL);
measure_widths(ub::LATIN_EXTENDED_B);
measure_widths(ub::LATIN_EXTENDED_C);
measure_widths(ub::LATIN_EXTENDED_D);
measure_widths(ub::LATIN_EXTENDED_E);
measure_widths(ub::LATIN_EXTENDED_F);
measure_widths(ub::LATIN_EXTENDED_G);
measure_widths(ub::LEPCHA);
measure_widths(ub::LETTERLIKE_SYMBOLS);
measure_widths(ub::LIMBU);
measure_widths(ub::LINEAR_A);
measure_widths(ub::LINEAR_B_IDEOGRAMS);
measure_widths(ub::LINEAR_B_SYLLABARY);
measure_widths(ub::LISU);
measure_widths(ub::LISU_SUPPLEMENT);
// measure_widths(ub::LOW_SURROGATES);
measure_widths(ub::LYCIAN);
measure_widths(ub::LYDIAN);
measure_widths(ub::MAHAJANI);
measure_widths(ub::MAHJONG_TILES);
measure_widths(ub::MAKASAR);
measure_widths(ub::MALAYALAM);
measure_widths(ub::MANDAIC);
measure_widths(ub::MANICHAEAN);
measure_widths(ub::MARCHEN);
measure_widths(ub::MASARAM_GONDI);
measure_widths(ub::MATHEMATICAL_ALPHANUMERIC_SYMBOLS);
measure_widths(ub::MATHEMATICAL_OPERATORS);
measure_widths(ub::MAYAN_NUMERALS);
measure_widths(ub::MEDEFAIDRIN);
measure_widths(ub::MEETEI_MAYEK);
measure_widths(ub::MEETEI_MAYEK_EXTENSIONS);
measure_widths(ub::MENDE_KIKAKUI);
measure_widths(ub::MEROITIC_CURSIVE);
measure_widths(ub::MEROITIC_HIEROGLYPHS);
measure_widths(ub::MIAO);
measure_widths(ub::MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A);
measure_widths(ub::MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B);
measure_widths(ub::MISCELLANEOUS_SYMBOLS);
measure_widths(ub::MISCELLANEOUS_SYMBOLS_AND_ARROWS);
measure_widths(ub::MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS);
measure_widths(ub::MISCELLANEOUS_TECHNICAL);
measure_widths(ub::MODI);
measure_widths(ub::MODIFIER_TONE_LETTERS);
measure_widths(ub::MONGOLIAN);
measure_widths(ub::MONGOLIAN_SUPPLEMENT);
measure_widths(ub::MRO);
measure_widths(ub::MULTANI);
measure_widths(ub::MUSICAL_SYMBOLS);
measure_widths(ub::MYANMAR);
measure_widths(ub::MYANMAR_EXTENDED_A);
measure_widths(ub::MYANMAR_EXTENDED_B);
measure_widths(ub::NABATAEAN);
measure_widths(ub::NANDINAGARI);
measure_widths(ub::NEWA);
measure_widths(ub::NEW_TAI_LUE);
measure_widths(ub::NKO);
measure_widths(ub::NUMBER_FORMS);
measure_widths(ub::NUSHU);
measure_widths(ub::NYIAKENG_PUACHUE_HMONG);
measure_widths(ub::OGHAM);
measure_widths(ub::OLD_HUNGARIAN);
measure_widths(ub::OLD_ITALIC);
measure_widths(ub::OLD_NORTH_ARABIAN);
measure_widths(ub::OLD_PERMIC);
measure_widths(ub::OLD_PERSIAN);
measure_widths(ub::OLD_SOGDIAN);
measure_widths(ub::OLD_SOUTH_ARABIAN);
measure_widths(ub::OLD_TURKIC);
measure_widths(ub::OLD_UYGHUR);
measure_widths(ub::OL_CHIKI);
measure_widths(ub::OPTICAL_CHARACTER_RECOGNITION);
measure_widths(ub::ORIYA);
measure_widths(ub::ORNAMENTAL_DINGBATS);
measure_widths(ub::OSAGE);
measure_widths(ub::OSMANYA);
measure_widths(ub::OTTOMAN_SIYAQ_NUMBERS);
measure_widths(ub::PAHAWH_HMONG);
measure_widths(ub::PALMYRENE);
measure_widths(ub::PAU_CIN_HAU);
measure_widths(ub::PHAGS_PA);
measure_widths(ub::PHAISTOS_DISC);
measure_widths(ub::PHOENICIAN);
measure_widths(ub::PHONETIC_EXTENSIONS);
measure_widths(ub::PHONETIC_EXTENSIONS_SUPPLEMENT);
measure_widths(ub::PLAYING_CARDS);
measure_widths(ub::PRIVATE_USE_AREA);
measure_widths(ub::PSALTER_PAHLAVI);
measure_widths(ub::REJANG);
measure_widths(ub::RUMI_NUMERAL_SYMBOLS);
measure_widths(ub::RUNIC);
measure_widths(ub::SAMARITAN);
measure_widths(ub::SAURASHTRA);
measure_widths(ub::SHARADA);
measure_widths(ub::SHAVIAN);
measure_widths(ub::SHORTHAND_FORMAT_CONTROLS);
measure_widths(ub::SIDDHAM);
measure_widths(ub::SINHALA);
measure_widths(ub::SINHALA_ARCHAIC_NUMBERS);
measure_widths(ub::SMALL_FORM_VARIANTS);
measure_widths(ub::SMALL_KANA_EXTENSION);
measure_widths(ub::SOGDIAN);
measure_widths(ub::SORA_SOMPENG);
measure_widths(ub::SOYOMBO);
measure_widths(ub::SPACING_MODIFIER_LETTERS);
measure_widths(ub::SPECIALS);
measure_widths(ub::SUNDANESE);
measure_widths(ub::SUNDANESE_SUPPLEMENT);
measure_widths(ub::SUPERSCRIPTS_AND_SUBSCRIPTS);
measure_widths(ub::SUPPLEMENTAL_ARROWS_A);
measure_widths(ub::SUPPLEMENTAL_ARROWS_B);
measure_widths(ub::SUPPLEMENTAL_ARROWS_C);
measure_widths(ub::SUPPLEMENTAL_MATHEMATICAL_OPERATORS);
measure_widths(ub::SUPPLEMENTAL_PUNCTUATION);
measure_widths(ub::SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHS);
measure_widths(ub::SUPPLEMENTARY_PRIVATE_USE_AREA_A);
measure_widths(ub::SUPPLEMENTARY_PRIVATE_USE_AREA_B);
measure_widths(ub::SUTTON_SIGNWRITING);
measure_widths(ub::SYLOTI_NAGRI);
measure_widths(ub::SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A);
measure_widths(ub::SYMBOLS_FOR_LEGACY_COMPUTING);
measure_widths(ub::SYRIAC);
measure_widths(ub::SYRIAC_SUPPLEMENT);
measure_widths(ub::TAGALOG);
measure_widths(ub::TAGBANWA);
measure_widths(ub::TAGS);
measure_widths(ub::TAI_LE);
measure_widths(ub::TAI_THAM);
measure_widths(ub::TAI_VIET);
measure_widths(ub::TAI_XUAN_JING_SYMBOLS);
measure_widths(ub::TAKRI);
measure_widths(ub::TAMIL);
measure_widths(ub::TAMIL_SUPPLEMENT);
measure_widths(ub::TANGSA);
measure_widths(ub::TANGUT);
measure_widths(ub::TANGUT_COMPONENTS);
measure_widths(ub::TANGUT_SUPPLEMENT);
measure_widths(ub::TELUGU);
measure_widths(ub::THAANA);
measure_widths(ub::THAI);
measure_widths(ub::TIBETAN);
measure_widths(ub::TIFINAGH);
measure_widths(ub::TIRHUTA);
measure_widths(ub::TOTO);
measure_widths(ub::TRANSPORT_AND_MAP_SYMBOLS);
measure_widths(ub::UGARITIC);
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS);
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED);
measure_widths(ub::UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A);
measure_widths(ub::VAI);
measure_widths(ub::VARIATION_SELECTORS);
measure_widths(ub::VARIATION_SELECTORS_SUPPLEMENT);
measure_widths(ub::VEDIC_EXTENSIONS);
measure_widths(ub::VERTICAL_FORMS);
measure_widths(ub::VITHKUQI);
measure_widths(ub::WANCHO);
measure_widths(ub::WARANG_CITI);
measure_widths(ub::YEZIDI);
measure_widths(ub::YIJING_HEXAGRAM_SYMBOLS);
measure_widths(ub::YI_RADICALS);
measure_widths(ub::YI_SYLLABLES);
measure_widths(ub::ZANABAZAR_SQUARE);
measure_widths(ub::ZNAMENNY_MUSICAL_NOTATION);
execute!(stdout, LeaveAlternateScreen).unwrap();
}

View file

@ -1,67 +1,79 @@
use crossterm::event::Event;
use crossterm::style::Stylize;
use toss::{Frame, Pos, Style, Terminal};
use crossterm::style::{ContentStyle, Stylize};
use toss::frame::{Frame, Pos};
use toss::terminal::{Redraw, Terminal};
fn draw(f: &mut Frame) {
f.write(
Pos::new(0, 0),
"Writing over wide graphemes removes the entire overwritten grapheme.",
ContentStyle::default(),
);
let under = Style::new().white().on_dark_blue();
let over = Style::new().black().on_dark_yellow();
let under = ContentStyle::default().white().on_dark_blue();
let over = ContentStyle::default().black().on_dark_yellow();
for i in 0..6 {
let delta = i - 2;
f.write(Pos::new(2 + i * 7, 2), ("a😀", under));
f.write(Pos::new(2 + i * 7, 3), ("a😀", under));
f.write(Pos::new(2 + i * 7, 4), ("a😀", under));
f.write(Pos::new(2 + i * 7 + delta, 3), ("b", over));
f.write(Pos::new(2 + i * 7 + delta, 4), ("😈", over));
f.write(Pos::new(2 + i * 7, 2), "a😀", under);
f.write(Pos::new(2 + i * 7, 3), "a😀", under);
f.write(Pos::new(2 + i * 7, 4), "a😀", under);
f.write(Pos::new(2 + i * 7 + delta, 3), "b", over);
f.write(Pos::new(2 + i * 7 + delta, 4), "😈", over);
}
f.write(
Pos::new(0, 6),
"Wide graphemes at the edges of the screen apply their style, but are not",
ContentStyle::default(),
);
f.write(
Pos::new(0, 7),
"actually rendered.",
ContentStyle::default(),
);
f.write(Pos::new(0, 7), "actually rendered.");
let x1 = -1;
let x2 = f.size().width as i32 / 2 - 3;
let x3 = f.size().width as i32 - 5;
f.write(Pos::new(x1, 9), ("123456", under));
f.write(Pos::new(x1, 10), ("😀😀😀", under));
f.write(Pos::new(x2, 9), ("123456", under));
f.write(Pos::new(x2, 10), ("😀😀😀", under));
f.write(Pos::new(x3, 9), ("123456", under));
f.write(Pos::new(x3, 10), ("😀😀😀", under));
f.write(Pos::new(x1, 9), "123456", under);
f.write(Pos::new(x1, 10), "😀😀😀", under);
f.write(Pos::new(x2, 9), "123456", under);
f.write(Pos::new(x2, 10), "😀😀😀", under);
f.write(Pos::new(x3, 9), "123456", under);
f.write(Pos::new(x3, 10), "😀😀😀", under);
let scientist = "👩‍🔬";
f.write(
Pos::new(0, 12),
"Most terminals ignore the zero width joiner and display this female",
ContentStyle::default(),
);
f.write(
Pos::new(0, 13),
"scientist emoji as a woman and a microscope: 👩‍🔬",
ContentStyle::default(),
);
for i in 0..(f.widthdb().width(scientist) + 4) {
f.write(Pos::new(2, 15 + i as i32), (scientist, under));
f.write(Pos::new(i as i32, 15 + i as i32), ("x", over));
for i in 0..(f.width(scientist) + 4) {
f.write(Pos::new(2, 15 + i as i32), scientist, under);
f.write(Pos::new(i as i32, 15 + i as i32), "x", over);
}
}
fn render_frame(term: &mut Terminal) {
let mut dirty = true;
while dirty {
loop {
// Must be called before rendering, otherwise the terminal has out-of-date
// size information and will present garbage.
term.autoresize().unwrap();
draw(term.frame());
term.present().unwrap();
dirty = term.measure_widths().unwrap();
if term.present().unwrap() == Redraw::NotRequired {
break;
}
}
}
fn main() {
// Automatically enters alternate screen and enables raw mode
let mut term = Terminal::new().unwrap();
term.set_measuring(true);
loop {
// Render and display a frame. A full frame is displayed on the terminal

View file

@ -1,5 +1,7 @@
use crossterm::event::Event;
use toss::{Frame, Pos, Styled, Terminal};
use crossterm::style::ContentStyle;
use toss::frame::{Frame, Pos};
use toss::terminal::{Redraw, Terminal};
fn draw(f: &mut Frame) {
let text = concat!(
@ -12,46 +14,37 @@ fn draw(f: &mut Frame) {
"This\u{00a0}sentence\u{00a0}is\u{00a0}separated\u{00a0}by\u{00a0}non-\u{2060}breaking\u{00a0}spaces.\n",
"\n",
"It can also properly handle wide graphemes (like emoji 🤔), ",
"including ones usually displayed incorrectly by terminal emulators, like 👩‍🔬 (a female scientist emoji).\n",
"\n",
"Finally, tabs are supported as well. ",
"The following text is rendered with a tab width of 4:\n",
"\tx\n",
"1\tx\n",
"12\tx\n",
"123\tx\n",
"1234\tx\n",
"12345\tx\n",
"123456\tx\n",
"1234567\tx\n",
"12345678\tx\n",
"123456789\tx\n",
"including ones usually displayed incorrectly by terminal emulators, like 👩‍🔬 (a female scientist emoji).",
);
let width = f.size().width.into();
let breaks = f.widthdb().wrap(text, width);
let lines = Styled::new_plain(text).split_at_indices(&breaks);
for (i, mut line) in lines.into_iter().enumerate() {
line.trim_end();
f.write(Pos::new(0, i as i32), line);
let breaks = f.wrap(text, f.size().width.into());
let lines = toss::split_at_indices(text, &breaks);
for (i, line) in lines.iter().enumerate() {
f.write(
Pos::new(0, i as i32),
line.trim_end(),
ContentStyle::default(),
);
}
}
fn render_frame(term: &mut Terminal) {
let mut dirty = true;
while dirty {
loop {
// Must be called before rendering, otherwise the terminal has out-of-date
// size information and will present garbage.
term.autoresize().unwrap();
draw(term.frame());
term.present().unwrap();
dirty = term.measure_widths().unwrap();
if term.present().unwrap() == Redraw::NotRequired {
break;
}
}
}
fn main() {
// Automatically enters alternate screen and enables raw mode
let mut term = Terminal::new().unwrap();
term.set_measuring(true);
term.set_tab_width(4);
loop {
// Render and display a frame. A full frame is displayed on the terminal

View file

@ -1,8 +1,34 @@
use std::ops::Range;
use crossterm::style::ContentStyle;
use unicode_segmentation::UnicodeSegmentation;
use crate::{Pos, Size, Style, Styled, WidthDb};
use crate::widthdb::WidthDB;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Size {
pub width: u16,
pub height: u16,
}
impl Size {
pub const ZERO: Self = Self {
width: 0,
height: 0,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pos {
pub x: i32,
pub y: i32,
}
impl Pos {
pub const ZERO: Self = Self { x: 0, y: 0 };
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cell {
@ -23,93 +49,13 @@ impl Default for Cell {
}
}
#[derive(Debug, Clone, Copy)]
struct StackFrame {
pub pos: Pos,
pub size: Size,
pub drawable_area: Option<(Pos, Size)>,
}
impl StackFrame {
fn intersect_areas(
a_start: Pos,
a_size: Size,
b_start: Pos,
b_size: Size,
) -> Option<(Pos, Size)> {
// The first row/column that is not part of the area any more
let a_end = a_start + a_size;
let b_end = b_start + b_size;
let x_start = a_start.x.max(b_start.x);
let x_end = a_end.x.min(b_end.x);
let y_start = a_start.y.max(b_start.y);
let y_end = a_end.y.min(b_end.y);
if x_start < x_end && y_start < y_end {
let start = Pos::new(x_start, y_start);
let size = Size::new((x_end - x_start) as u16, (y_end - y_start) as u16);
Some((start, size))
} else {
None
}
}
fn then(&self, pos: Pos, size: Size) -> Self {
let pos = self.local_to_global(pos);
let drawable_area = self
.drawable_area
.and_then(|(da_pos, da_size)| Self::intersect_areas(da_pos, da_size, pos, size));
Self {
pos,
size,
drawable_area,
}
}
fn local_to_global(&self, local_pos: Pos) -> Pos {
local_pos + self.pos
}
fn global_to_local(&self, global_pos: Pos) -> Pos {
global_pos - self.pos
}
/// Ranges along the x and y axis where drawing is allowed, in global
/// coordinates.
fn legal_ranges(&self) -> Option<(Range<i32>, Range<i32>)> {
if let Some((pos, size)) = self.drawable_area {
let xrange = pos.x..pos.x + size.width as i32;
let yrange = pos.y..pos.y + size.height as i32;
Some((xrange, yrange))
} else {
None
}
}
}
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default)]
pub struct Buffer {
size: Size,
data: Vec<Cell>,
cursor: Option<Pos>,
/// A stack of rectangular drawing areas.
///
/// When rendering to the buffer with a nonempty stack, it behaves as if it
/// was the size of the topmost stack element, and characters are translated
/// by the position of the topmost stack element. No characters can be
/// placed outside the area described by the topmost stack element.
stack: Vec<StackFrame>,
}
impl Buffer {
/// Index in `data` of the cell at the given position. The position must
/// be inside the buffer.
///
/// Ignores the stack.
fn index(&self, x: u16, y: u16) -> usize {
assert!(x < self.size.width);
assert!(y < self.size.height);
@ -121,65 +67,30 @@ impl Buffer {
y * width + x
}
/// A reference to the cell at the given position. The position must be
/// inside the buffer.
///
/// Ignores the stack.
pub fn at(&self, x: u16, y: u16) -> &Cell {
pub(crate) fn at(&self, x: u16, y: u16) -> &Cell {
assert!(x < self.size.width);
assert!(y < self.size.height);
let i = self.index(x, y);
&self.data[i]
}
/// A mutable reference to the cell at the given position. The position must
/// be inside the buffer.
///
/// Ignores the stack.
fn at_mut(&mut self, x: u16, y: u16) -> &mut Cell {
assert!(x < self.size.width);
assert!(y < self.size.height);
let i = self.index(x, y);
&mut self.data[i]
}
fn current_frame(&self) -> StackFrame {
self.stack.last().copied().unwrap_or(StackFrame {
pos: Pos::ZERO,
size: self.size,
drawable_area: Some((Pos::ZERO, self.size)),
})
}
pub fn push(&mut self, pos: Pos, size: Size) {
self.stack.push(self.current_frame().then(pos, size));
}
pub fn pop(&mut self) {
self.stack.pop();
}
/// Size of the current drawable area, respecting the stack.
pub fn size(&self) -> Size {
self.current_frame().size
}
pub fn cursor(&self) -> Option<Pos> {
self.cursor.map(|p| self.current_frame().global_to_local(p))
}
pub fn set_cursor(&mut self, pos: Option<Pos>) {
self.cursor = pos.map(|p| self.current_frame().local_to_global(p));
self.size
}
/// Resize the buffer and reset its contents.
///
/// The buffer's contents are reset even if the buffer is already the
/// correct size. The stack is reset as well.
/// correct size.
pub fn resize(&mut self, size: Size) {
if size == self.size {
if size == self.size() {
self.data.fill_with(Cell::default);
} else {
let width: usize = size.width.into();
@ -190,86 +101,58 @@ impl Buffer {
self.data.clear();
self.data.resize_with(len, Cell::default);
}
self.cursor = None;
self.stack.clear();
}
/// Reset the contents and stack of the buffer.
/// Reset the contents of the buffer.
///
/// `buf.reset()` is equivalent to `buf.resize(buf.size())`.
pub fn reset(&mut self) {
self.resize(self.size);
self.data.fill_with(Cell::default);
}
/// Remove the grapheme at the specified coordinates from the buffer.
///
/// Removes the entire grapheme, not just the cell at the coordinates.
/// Preserves the style of the affected cells. Preserves the cursor. Works
/// even if the coordinates don't point to the beginning of the grapheme.
///
/// Ignores the stack.
/// Preserves the style of the affected cells. Works even if the coordinates
/// don't point to the beginning of the grapheme.
fn erase(&mut self, x: u16, y: u16) {
let cell = self.at(x, y);
let width: u16 = cell.width.into();
let offset: u16 = cell.offset.into();
for x in (x - offset)..(x - offset + width) {
let cell = self.at_mut(x, y);
let style = cell.style;
*cell = Cell::default();
cell.style = style;
}
}
/// Write styled text to the buffer, respecting the width of individual
/// graphemes.
///
/// The initial x position is considered the first column for tab width
/// calculations.
pub fn write(&mut self, widthdb: &mut WidthDb, pos: Pos, styled: &Styled) {
let frame = self.current_frame();
let (xrange, yrange) = match frame.legal_ranges() {
Some(ranges) => ranges,
None => return, // No drawable area
};
let pos = frame.local_to_global(pos);
if !yrange.contains(&pos.y) {
return; // Outside of drawable area
pub fn write(
&mut self,
widthdb: &mut WidthDB,
mut pos: Pos,
content: &str,
style: ContentStyle,
) {
// If we're not even visible, there's nothing to do
if pos.y < 0 || pos.y >= self.size.height as i32 {
return;
}
let y = pos.y as u16;
let mut col: usize = 0;
for (_, style, grapheme) in styled.styled_grapheme_indices() {
let x = pos.x + col as i32;
let width = widthdb.grapheme_width(grapheme, col);
col += width as usize;
if grapheme == "\t" {
for dx in 0..width {
self.write_grapheme(&xrange, x + dx as i32, y, 1, " ", style);
}
} else if width > 0 {
self.write_grapheme(&xrange, x, y, width, grapheme, style);
for grapheme in content.graphemes(true) {
let width = widthdb.grapheme_width(grapheme);
if width > 0 {
self.write_grapheme(pos.x, y, width, grapheme, style);
}
pos.x += width as i32;
}
}
/// Write a single grapheme to the buffer, respecting its width.
///
/// Assumes that `pos.y` is in range.
fn write_grapheme(
&mut self,
xrange: &Range<i32>,
x: i32,
y: u16,
width: u8,
grapheme: &str,
style: Style,
) {
let min_x = xrange.start;
let max_x = xrange.end - 1; // Last possible cell
fn write_grapheme(&mut self, x: i32, y: u16, width: u8, grapheme: &str, style: ContentStyle) {
let min_x = 0;
let max_x = self.size.width as i32 - 1; // Last possible cell
let start_x = x;
let end_x = x + width as i32 - 1; // Coordinate of last cell
@ -280,13 +163,12 @@ impl Buffer {
if start_x >= min_x && end_x <= max_x {
// Fully visible, write actual grapheme
let base_style = self.at(start_x as u16, y).style;
for offset in 0..width {
let x = start_x as u16 + offset as u16;
self.erase(x, y);
*self.at_mut(x, y) = Cell {
content: grapheme.to_string().into_boxed_str(),
style: style.cover(base_style),
style,
width,
offset,
};
@ -296,21 +178,13 @@ impl Buffer {
let start_x = start_x.max(0) as u16;
let end_x = end_x.min(max_x) as u16;
for x in start_x..=end_x {
let base_style = self.at(x, y).style;
self.erase(x, y);
*self.at_mut(x, y) = Cell {
style: style.cover(base_style),
style,
..Default::default()
};
}
}
if let Some(pos) = self.cursor {
if pos.y == y as i32 && start_x <= pos.x && pos.x <= end_x {
// The cursor lies within the bounds of the current grapheme and
self.cursor = None;
}
}
}
pub fn cells(&self) -> Cells<'_> {
@ -332,9 +206,6 @@ impl<'a> Iterator for Cells<'a> {
type Item = (u16, u16, &'a Cell);
fn next(&mut self) -> Option<Self::Item> {
if self.x >= self.buffer.size.width {
return None;
}
if self.y >= self.buffer.size.height {
return None;
}

View file

@ -1,153 +0,0 @@
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
/// Size in screen cells.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Size {
pub width: u16,
pub height: u16,
}
impl Size {
pub const ZERO: Self = Self::new(0, 0);
pub const fn new(width: u16, height: u16) -> Self {
Self { width, height }
}
/// Add two [`Size`]s using [`u16::saturating_add`].
pub const fn saturating_add(self, rhs: Self) -> Self {
Self::new(
self.width.saturating_add(rhs.width),
self.height.saturating_add(rhs.height),
)
}
/// Subtract two [`Size`]s using [`u16::saturating_sub`].
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self::new(
self.width.saturating_sub(rhs.width),
self.height.saturating_sub(rhs.height),
)
}
}
impl Add for Size {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.width + rhs.width, self.height + rhs.height)
}
}
impl AddAssign for Size {
fn add_assign(&mut self, rhs: Self) {
self.width += rhs.width;
self.height += rhs.height;
}
}
impl Sub for Size {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.width - rhs.width, self.height - rhs.height)
}
}
impl SubAssign for Size {
fn sub_assign(&mut self, rhs: Self) {
self.width -= rhs.width;
self.height -= rhs.height;
}
}
/// Position in screen cell coordinates.
///
/// The x axis points to the right. The y axis points down.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pos {
pub x: i32,
pub y: i32,
}
impl Pos {
pub const ZERO: Self = Self::new(0, 0);
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl From<Size> for Pos {
fn from(s: Size) -> Self {
Self::new(s.width.into(), s.height.into())
}
}
impl Add for Pos {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl Add<Size> for Pos {
type Output = Self;
fn add(self, rhs: Size) -> Self {
Self::new(self.x + rhs.width as i32, self.y + rhs.height as i32)
}
}
impl AddAssign for Pos {
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
}
}
impl AddAssign<Size> for Pos {
fn add_assign(&mut self, rhs: Size) {
self.x += rhs.width as i32;
self.y += rhs.height as i32;
}
}
impl Sub for Pos {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl Sub<Size> for Pos {
type Output = Self;
fn sub(self, rhs: Size) -> Self {
Self::new(self.x - rhs.width as i32, self.y - rhs.height as i32)
}
}
impl SubAssign for Pos {
fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}
impl SubAssign<Size> for Pos {
fn sub_assign(&mut self, rhs: Size) {
self.x -= rhs.width as i32;
self.y -= rhs.height as i32;
}
}
impl Neg for Pos {
type Output = Self;
fn neg(self) -> Self {
Self::new(-self.x, -self.y)
}
}

View file

@ -1,40 +1,35 @@
//! Rendering the next frame.
use crossterm::style::ContentStyle;
use crate::buffer::Buffer;
use crate::{Pos, Size, Styled, WidthDb};
pub use crate::buffer::{Pos, Size};
use crate::widthdb::WidthDB;
use crate::wrap;
#[derive(Debug, Default)]
pub struct Frame {
pub(crate) widthdb: WidthDb,
pub(crate) widthdb: WidthDB,
pub(crate) buffer: Buffer,
pub(crate) title: Option<String>,
pub(crate) bell: bool,
cursor: Option<Pos>,
}
impl Frame {
pub fn push(&mut self, pos: Pos, size: Size) {
self.buffer.push(pos, size);
}
pub fn pop(&mut self) {
self.buffer.pop();
}
pub fn size(&self) -> Size {
self.buffer.size()
}
pub fn reset(&mut self) {
self.buffer.reset();
self.title = None;
self.cursor = None;
}
pub fn cursor(&self) -> Option<Pos> {
self.buffer.cursor()
self.cursor
}
pub fn set_cursor(&mut self, pos: Option<Pos>) {
self.buffer.set_cursor(pos);
self.cursor = pos;
}
pub fn show_cursor(&mut self, pos: Pos) {
@ -45,19 +40,27 @@ impl Frame {
self.set_cursor(None);
}
pub fn set_title(&mut self, title: Option<String>) {
self.title = title;
/// Determine the width of a grapheme.
///
/// If the width has not been measured yet, it is estimated using the
/// Unicode Standard Annex #11.
pub fn grapheme_width(&mut self, grapheme: &str) -> u8 {
self.widthdb.grapheme_width(grapheme)
}
pub fn set_bell(&mut self, bell: bool) {
self.bell = bell;
/// Determine the width of a string based on its graphemes.
///
/// If the width of a grapheme has not been measured yet, it is estimated
/// using the Unicode Standard Annex #11.
pub fn width(&mut self, s: &str) -> usize {
self.widthdb.width(s)
}
pub fn widthdb(&mut self) -> &mut WidthDb {
&mut self.widthdb
pub fn wrap(&mut self, text: &str, width: usize) -> Vec<usize> {
wrap::wrap(text, width, &mut self.widthdb)
}
pub fn write<S: Into<Styled>>(&mut self, pos: Pos, styled: S) {
self.buffer.write(&mut self.widthdb, pos, &styled.into());
pub fn write(&mut self, pos: Pos, content: &str, style: ContentStyle) {
self.buffer.write(&mut self.widthdb, pos, content, style);
}
}

View file

@ -1,29 +1,7 @@
#![forbid(unsafe_code)]
// Rustc lint groups
#![warn(future_incompatible)]
#![warn(rust_2018_idioms)]
#![warn(unused)]
// Rustc lints
#![warn(noop_method_call)]
#![warn(single_use_lifetimes)]
// Clippy lints
#![warn(clippy::use_self)]
mod buffer;
mod coords;
mod frame;
mod style;
mod styled;
mod terminal;
mod widget;
pub mod widgets;
pub mod frame;
pub mod terminal;
mod widthdb;
mod wrap;
pub use coords::*;
pub use frame::*;
pub use style::*;
pub use styled::*;
pub use terminal::*;
pub use widget::*;
pub use widthdb::*;
pub use wrap::split_at_indices;

View file

@ -1,60 +0,0 @@
use crossterm::style::{ContentStyle, Stylize};
fn merge_cs(base: ContentStyle, cover: ContentStyle) -> ContentStyle {
ContentStyle {
foreground_color: cover.foreground_color.or(base.foreground_color),
background_color: cover.background_color.or(base.background_color),
underline_color: cover.underline_color.or(base.underline_color),
attributes: cover.attributes,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Style {
pub content_style: ContentStyle,
pub opaque: bool,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
pub fn transparent(mut self) -> Self {
self.opaque = false;
self
}
pub fn opaque(mut self) -> Self {
self.opaque = true;
self
}
pub fn cover(self, base: ContentStyle) -> ContentStyle {
if self.opaque {
return self.content_style;
}
merge_cs(base, self.content_style)
}
}
impl AsRef<ContentStyle> for Style {
fn as_ref(&self) -> &ContentStyle {
&self.content_style
}
}
impl AsMut<ContentStyle> for Style {
fn as_mut(&mut self) -> &mut ContentStyle {
&mut self.content_style
}
}
impl Stylize for Style {
type Styled = Self;
fn stylize(self) -> Self::Styled {
self
}
}

View file

@ -1,195 +0,0 @@
use std::iter::Peekable;
use std::slice;
use unicode_segmentation::{GraphemeIndices, Graphemes, UnicodeSegmentation};
use crate::Style;
#[derive(Debug, Default, Clone)]
pub struct Styled {
text: String,
/// List of `(style, until)` tuples. The style should be applied to all
/// chars in the range `prev_until..until`.
styles: Vec<(Style, usize)>,
}
impl Styled {
pub fn new<S: AsRef<str>>(text: S, style: Style) -> Self {
Self::default().then(text, style)
}
pub fn new_plain<S: AsRef<str>>(text: S) -> Self {
Self::default().then_plain(text)
}
pub fn then<S: AsRef<str>>(mut self, text: S, style: Style) -> Self {
let text = text.as_ref();
if !text.is_empty() {
self.text.push_str(text);
self.styles.push((style, self.text.len()));
}
self
}
pub fn then_plain<S: AsRef<str>>(self, text: S) -> Self {
self.then(text, Style::new())
}
pub fn and_then(mut self, mut other: Self) -> Self {
let delta = self.text.len();
for (_, until) in &mut other.styles {
*until += delta;
}
self.text.push_str(&other.text);
self.styles.extend(other.styles);
self
}
pub fn text(&self) -> &str {
&self.text
}
pub fn split_at(self, mid: usize) -> (Self, Self) {
let (left_text, right_text) = self.text.split_at(mid);
let mut left_styles = vec![];
let mut right_styles = vec![];
let mut from = 0;
for (style, until) in self.styles {
if from < mid {
left_styles.push((style, until.min(mid)));
}
if mid < until {
right_styles.push((style, until.saturating_sub(mid)));
}
from = until;
}
let left = Self {
text: left_text.to_string(),
styles: left_styles,
};
let right = Self {
text: right_text.to_string(),
styles: right_styles,
};
(left, right)
}
pub fn split_at_indices(self, indices: &[usize]) -> Vec<Self> {
let mut lines = vec![];
let mut rest = self;
let mut offset = 0;
for i in indices {
let (left, right) = rest.split_at(i - offset);
lines.push(left);
rest = right;
offset = *i;
}
lines.push(rest);
lines
}
pub fn trim_end(&mut self) {
self.text = self.text.trim_end().to_string();
let text_len = self.text.len();
let mut styles_len = 0;
for (_, until) in &mut self.styles {
styles_len += 1;
if *until >= text_len {
*until = text_len;
break;
}
}
while self.styles.len() > styles_len {
self.styles.pop();
}
}
}
//////////////////////////////
// Iterating over graphemes //
//////////////////////////////
pub struct StyledGraphemeIndices<'a> {
text: GraphemeIndices<'a>,
styles: Peekable<slice::Iter<'a, (Style, usize)>>,
}
impl<'a> Iterator for StyledGraphemeIndices<'a> {
type Item = (usize, Style, &'a str);
fn next(&mut self) -> Option<Self::Item> {
let (gi, grapheme) = self.text.next()?;
let (mut style, mut until) = **self.styles.peek().expect("styles cover entire text");
while gi >= until {
self.styles.next();
(style, until) = **self.styles.peek().expect("styles cover entire text");
}
Some((gi, style, grapheme))
}
}
impl Styled {
pub fn graphemes(&self) -> Graphemes<'_> {
self.text.graphemes(true)
}
pub fn grapheme_indices(&self) -> GraphemeIndices<'_> {
self.text.grapheme_indices(true)
}
pub fn styled_grapheme_indices(&self) -> StyledGraphemeIndices<'_> {
StyledGraphemeIndices {
text: self.grapheme_indices(),
styles: self.styles.iter().peekable(),
}
}
}
//////////////////////////
// Converting to Styled //
//////////////////////////
impl From<&str> for Styled {
fn from(text: &str) -> Self {
Self::new_plain(text)
}
}
impl From<String> for Styled {
fn from(text: String) -> Self {
Self::new_plain(text)
}
}
impl<S: AsRef<str>> From<(S,)> for Styled {
fn from((text,): (S,)) -> Self {
Self::new_plain(text)
}
}
impl<S: AsRef<str>> From<(S, Style)> for Styled {
fn from((text, style): (S, Style)) -> Self {
Self::new(text, style)
}
}
impl<S: AsRef<str>> From<&[(S, Style)]> for Styled {
fn from(segments: &[(S, Style)]) -> Self {
let mut result = Self::default();
for (text, style) in segments {
result = result.then(text, *style);
}
result
}
}

View file

@ -1,28 +1,22 @@
//! Displaying frames on a terminal.
use std::io::{self, Write};
use std::mem;
use std::io::Write;
use std::{io, mem};
use crossterm::cursor::{Hide, MoveTo, Show};
use crossterm::event::{
DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags,
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
};
use crossterm::style::{Print, PrintStyledContent, StyledContent};
use crossterm::terminal::{
BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate, EnterAlternateScreen,
LeaveAlternateScreen, SetTitle,
};
use crossterm::style::{PrintStyledContent, StyledContent};
use crossterm::terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::{ExecutableCommand, QueueableCommand};
use crate::buffer::Buffer;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb, WidthEstimationMethod};
use crate::buffer::{Buffer, Size};
use crate::frame::Frame;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Redraw {
Required,
NotRequired,
}
/// Wrapper that manages terminal output.
///
/// This struct (usually) wraps around stdout and handles showing things on the
/// terminal. It cleans up after itself when droppped, so it shouldn't leave the
/// terminal in a weird state even if your program crashes.
pub struct Terminal {
/// Render target.
out: Box<dyn Write>,
@ -37,17 +31,17 @@ pub struct Terminal {
impl Drop for Terminal {
fn drop(&mut self) {
let _ = self.suspend();
let _ = crossterm::terminal::disable_raw_mode();
let _ = self.out.execute(LeaveAlternateScreen);
let _ = self.out.execute(Show);
}
}
impl Terminal {
/// Create a new [`Terminal`] that wraps stdout.
pub fn new() -> io::Result<Self> {
Self::with_target(Box::new(io::stdout()))
}
/// Create a new terminal wrapping a custom output.
pub fn with_target(out: Box<dyn Write>) -> io::Result<Self> {
let mut result = Self {
out,
@ -55,136 +49,13 @@ impl Terminal {
prev_frame_buffer: Buffer::default(),
full_redraw: true,
};
result.unsuspend()?;
Ok(result)
}
/// Temporarily restore the terminal state to normal.
///
/// This is useful when running external programs the user should interact
/// with directly, for example a text editor.
///
/// Call [`Self::unsuspend`] to return the terminal state before drawing and
/// presenting the next frame.
pub fn suspend(&mut self) -> io::Result<()> {
crossterm::terminal::disable_raw_mode()?;
#[cfg(not(windows))]
{
self.out.execute(PopKeyboardEnhancementFlags)?;
self.out.execute(DisableBracketedPaste)?;
}
self.out.execute(LeaveAlternateScreen)?;
self.out.execute(Show)?;
Ok(())
}
/// Restore the terminal state after calling [`Self::suspend`].
///
/// After calling this function, a new frame needs to be drawn and presented
/// by the application. The previous screen contents are **not** restored.
pub fn unsuspend(&mut self) -> io::Result<()> {
crossterm::terminal::enable_raw_mode()?;
self.out.execute(EnterAlternateScreen)?;
#[cfg(not(windows))]
{
self.out.execute(EnableBracketedPaste)?;
self.out.execute(PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES,
))?;
}
self.full_redraw = true;
Ok(())
}
/// Set the tab width in columns.
///
/// For more details, see [`Self::tab_width`].
pub fn set_tab_width(&mut self, tab_width: u8) {
self.frame.widthdb.tab_width = tab_width;
}
/// The tab width in columns.
///
/// For accurate width calculations and consistency across terminals, tabs
/// are not printed to the terminal directly, but instead converted into
/// spaces.
pub fn tab_width(&self) -> u8 {
self.frame.widthdb.tab_width
}
/// Set the grapheme width estimation method.
///
/// For more details, see [`WidthEstimationMethod`].
pub fn set_width_estimation_method(&mut self, method: WidthEstimationMethod) {
self.frame.widthdb.estimate = method;
}
/// The grapheme width estimation method.
///
/// For more details, see [`WidthEstimationMethod`].
pub fn width_estimation_method(&mut self) -> WidthEstimationMethod {
self.frame.widthdb.estimate
}
/// Enable or disable grapheme width measurements.
///
/// For more details, see [`Self::measuring`].
pub fn set_measuring(&mut self, active: bool) {
self.frame.widthdb.measure = active;
}
/// Whether grapheme widths should be measured or estimated.
///
/// Handling of wide characters is inconsistent from terminal emulator to
/// terminal emulator, and may even depend on the font the user is using.
///
/// When enabled, any newly encountered graphemes are measured whenever
/// [`Self::measure_widths`] is called. This is done by clearing the screen,
/// printing the grapheme and measuring the resulting cursor position.
/// Because of this, the screen will flicker occasionally. However, grapheme
/// widths will always be accurate independent of the terminal
/// configuration.
///
/// When disabled, the width of graphemes is estimated using the Unicode
/// Standard Annex #11. This usually works fine, but may break on some emoji
/// or other less commonly used character sequences.
pub fn measuring(&self) -> bool {
self.frame.widthdb.measure
}
/// Whether any unmeasured graphemes were seen since the last call to
/// [`Self::measure_widths`].
///
/// Returns `true` whenever [`Self::measure_widths`] would return `true`.
pub fn measuring_required(&self) -> bool {
self.frame.widthdb.measuring_required()
}
/// Measure widths of all unmeasured graphemes.
///
/// If width measurements are disabled, this function does nothing. For more
/// info, see [`Self::measuring`].
///
/// Returns `true` if any new graphemes were measured and the screen must be
/// redrawn. Keep in mind that after redrawing the screen, graphemes may
/// have become visible that have not yet been measured. You should keep
/// re-measuring and re-drawing until this function returns `false`.
pub fn measure_widths(&mut self) -> io::Result<bool> {
if self.frame.widthdb.measuring_required() {
self.full_redraw = true;
self.frame.widthdb.measure_widths(&mut self.out)?;
Ok(true)
} else {
Ok(false)
}
result.out.execute(EnterAlternateScreen)?;
Ok(result)
}
/// Resize the frame and other internal buffers if the terminal size has
/// changed.
///
/// Should be called before drawing a frame and presenting it with
/// [`Self::present`]. It is not necessary to call this when using
/// [`Self::present_widget`] or [`Self::present_async_widget`].
pub fn autoresize(&mut self) -> io::Result<()> {
let (width, height) = crossterm::terminal::size()?;
let size = Size { width, height };
@ -197,86 +68,42 @@ impl Terminal {
Ok(())
}
/// The current frame.
pub fn frame(&mut self) -> &mut Frame {
&mut self.frame
}
/// A database of grapheme widths.
pub fn widthdb(&mut self) -> &mut WidthDb {
&mut self.frame.widthdb
}
/// Mark the terminal as dirty, forcing a full redraw whenever any variant
/// of [`Self::present`] is called.
pub fn mark_dirty(&mut self) {
self.full_redraw = true;
}
/// Display the current frame on the screen and prepare the next frame.
///
/// Before drawing and presenting a frame, [`Self::measure_widths`] and
/// [`Self::autoresize`] should be called.
/// Returns `true` if an immediate redraw is required.
///
/// After calling this function, the frame returned by [`Self::frame`] will
/// be empty again and have no cursor position.
pub fn present(&mut self) -> io::Result<()> {
self.out.queue(BeginSynchronizedUpdate)?;
let result = self.draw_to_screen();
self.out.queue(EndSynchronizedUpdate)?;
result?;
pub fn present(&mut self) -> io::Result<Redraw> {
if self.frame.widthdb.measuring_required() {
self.frame.widthdb.measure_widths(&mut self.out)?;
// Since we messed up the screen by measuring widths, we'll need to
// do a full redraw the next time around.
self.full_redraw = true;
// Throwing away the current frame because its content were rendered
// with unconfirmed width data. Also, this function guarantees that
// after it is called, the frame is empty.
self.frame.reset();
return Ok(Redraw::Required);
}
self.out.flush()?;
mem::swap(&mut self.prev_frame_buffer, &mut self.frame.buffer);
self.frame.reset();
Ok(())
}
/// Display a [`Widget`] on the screen.
///
/// Before creating and presenting a widget, [`Self::measure_widths`] should
/// be called. There is no need to call [`Self::autoresize`].
pub fn present_widget<E, W>(&mut self, widget: W) -> Result<(), E>
where
E: From<io::Error>,
W: Widget<E>,
{
self.autoresize()?;
widget.draw(self.frame())?;
self.present()?;
Ok(())
}
/// Display an [`AsyncWidget`] on the screen.
///
/// Before creating and presenting a widget, [`Self::measure_widths`] should
/// be called. There is no need to call [`Self::autoresize`].
pub async fn present_async_widget<E, W>(&mut self, widget: W) -> Result<(), E>
where
E: From<io::Error>,
W: AsyncWidget<E>,
{
self.autoresize()?;
widget.draw(self.frame()).await?;
self.present()?;
Ok(())
}
fn draw_to_screen(&mut self) -> io::Result<()> {
if self.full_redraw {
self.out.queue(Clear(ClearType::All))?;
io::stdout().queue(Clear(ClearType::All))?;
self.prev_frame_buffer.reset(); // Because the screen is now empty
self.full_redraw = false;
}
self.draw_differences()?;
self.update_cursor()?;
self.update_title()?;
self.ring_bell()?;
self.out.flush()?;
Ok(())
mem::swap(&mut self.prev_frame_buffer, &mut self.frame.buffer);
self.frame.reset();
Ok(Redraw::NotRequired)
}
fn draw_differences(&mut self) -> io::Result<()> {
@ -309,19 +136,4 @@ impl Terminal {
self.out.queue(Hide)?;
Ok(())
}
fn update_title(&mut self) -> io::Result<()> {
if let Some(title) = &self.frame.title {
self.out.queue(SetTitle(title.clone()))?;
}
Ok(())
}
fn ring_bell(&mut self) -> io::Result<()> {
if self.frame.bell {
self.out.queue(Print('\x07'))?;
}
self.frame.bell = false;
Ok(())
}
}

View file

@ -1,127 +0,0 @@
use async_trait::async_trait;
use crate::widgets::{
Background, Border, Boxed, BoxedAsync, BoxedSendSync, Desync, Either2, Either3, Float,
JoinSegment, Layer2, Padding, Resize, Title,
};
use crate::{Frame, Size, WidthDb};
// TODO Feature-gate these traits
pub trait Widget<E> {
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E>;
fn draw(self, frame: &mut Frame) -> Result<(), E>;
}
#[async_trait]
pub trait AsyncWidget<E> {
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E>;
async fn draw(self, frame: &mut Frame) -> Result<(), E>;
}
pub trait WidgetExt: Sized {
fn background(self) -> Background<Self> {
Background::new(self)
}
fn border(self) -> Border<Self> {
Border::new(self)
}
fn boxed<'a, E>(self) -> Boxed<'a, E>
where
Self: Widget<E> + 'a,
{
Boxed::new(self)
}
fn boxed_send_sync<'a, E>(self) -> BoxedSendSync<'a, E>
where
Self: Widget<E> + Send + Sync + 'a,
{
BoxedSendSync::new(self)
}
fn boxed_async<'a, E>(self) -> BoxedAsync<'a, E>
where
Self: AsyncWidget<E> + Send + Sync + 'a,
{
BoxedAsync::new(self)
}
fn desync(self) -> Desync<Self> {
Desync(self)
}
fn first2<W2>(self) -> Either2<Self, W2> {
Either2::First(self)
}
fn second2<W1>(self) -> Either2<W1, Self> {
Either2::Second(self)
}
fn first3<W2, W3>(self) -> Either3<Self, W2, W3> {
Either3::First(self)
}
fn second3<W1, W3>(self) -> Either3<W1, Self, W3> {
Either3::Second(self)
}
fn third3<W1, W2>(self) -> Either3<W1, W2, Self> {
Either3::Third(self)
}
fn float(self) -> Float<Self> {
Float::new(self)
}
fn segment(self) -> JoinSegment<Self> {
JoinSegment::new(self)
}
fn below<W>(self, above: W) -> Layer2<Self, W> {
Layer2::new(self, above)
}
fn above<W>(self, below: W) -> Layer2<W, Self> {
Layer2::new(below, self)
}
fn padding(self) -> Padding<Self> {
Padding::new(self)
}
fn resize(self) -> Resize<Self> {
Resize::new(self)
}
fn title<S: ToString>(self, title: S) -> Title<Self> {
Title::new(self, title)
}
}
// It would be nice if this could be restricted to types implementing Widget.
// However, Widget (and AsyncWidget) have the E type parameter, which WidgetExt
// doesn't have. We sadly can't have unconstrained type parameters like that in
// impl blocks.
//
// If WidgetExt had a type parameter E, we'd need to specify that parameter
// everywhere we use the trait. This is less ergonomic than just constructing
// the types manually.
//
// Blanket-implementing this trait is not great, but usually works fine.
impl<W> WidgetExt for W {}

View file

@ -1,35 +0,0 @@
pub mod background;
pub mod bell;
pub mod border;
pub mod boxed;
pub mod cursor;
pub mod desync;
pub mod editor;
pub mod either;
pub mod empty;
pub mod float;
pub mod join;
pub mod layer;
pub mod padding;
pub mod predrawn;
pub mod resize;
pub mod text;
pub mod title;
pub use background::*;
pub use bell::*;
pub use border::*;
pub use boxed::*;
pub use cursor::*;
pub use desync::*;
pub use editor::*;
pub use either::*;
pub use empty::*;
pub use float::*;
pub use join::*;
pub use layer::*;
pub use padding::*;
pub use predrawn::*;
pub use resize::*;
pub use text::*;
pub use title::*;

View file

@ -1,71 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Style, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct Background<I> {
pub inner: I,
pub style: Style,
}
impl<I> Background<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
style: Style::new().opaque(),
}
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
fn fill(&self, frame: &mut Frame) {
let size = frame.size();
for dy in 0..size.height {
for dx in 0..size.width {
frame.write(Pos::new(dx.into(), dy.into()), (" ", self.style));
}
}
}
}
impl<E, I> Widget<E> for Background<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.fill(frame);
self.inner.draw(frame)
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Background<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height).await
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.fill(frame);
self.inner.draw(frame).await
}
}

View file

@ -1,55 +0,0 @@
use crate::{Frame, Size, Widget, WidthDb};
///////////
// State //
///////////
#[derive(Debug, Default, Clone)]
pub struct BellState {
// Whether the bell should be rung the next time the widget is displayed.
pub ring: bool,
}
impl BellState {
pub fn new() -> Self {
Self::default()
}
pub fn widget(&mut self) -> Bell<'_> {
Bell { state: self }
}
}
////////////
// Widget //
////////////
#[derive(Debug)]
pub struct Bell<'a> {
state: &'a mut BellState,
}
impl Bell<'_> {
pub fn state(&mut self) -> &mut BellState {
self.state
}
}
impl<E> Widget<E> for Bell<'_> {
fn size(
&self,
_widthdb: &mut WidthDb,
_max_width: Option<u16>,
_max_height: Option<u16>,
) -> Result<Size, E> {
Ok(Size::ZERO)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
if self.state.ring {
frame.set_bell(true);
self.state.ring = false
}
Ok(())
}
}

View file

@ -1,201 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Style, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct BorderLook {
pub top_left: &'static str,
pub top_right: &'static str,
pub bottom_left: &'static str,
pub bottom_right: &'static str,
pub top: &'static str,
pub bottom: &'static str,
pub left: &'static str,
pub right: &'static str,
}
impl BorderLook {
/// ```text
/// +-------+
/// | Hello |
/// +-------+
/// ```
pub const ASCII: Self = Self {
top_left: "+",
top_right: "+",
bottom_left: "+",
bottom_right: "+",
top: "-",
bottom: "-",
left: "|",
right: "|",
};
/// ```text
/// ┌───────┐
/// │ Hello │
/// └───────┘
/// ```
pub const LINE: Self = Self {
top_left: "",
top_right: "",
bottom_left: "",
bottom_right: "",
top: "",
bottom: "",
left: "",
right: "",
};
/// ```text
/// ┏━━━━━━━┓
/// ┃ Hello ┃
/// ┗━━━━━━━┛
/// ```
pub const LINE_HEAVY: Self = Self {
top_left: "",
top_right: "",
bottom_left: "",
bottom_right: "",
top: "",
bottom: "",
left: "",
right: "",
};
/// ```text
/// ╔═══════╗
/// ║ Hello ║
/// ╚═══════╝
/// ```
pub const LINE_DOUBLE: Self = Self {
top_left: "",
top_right: "",
bottom_left: "",
bottom_right: "",
top: "",
bottom: "",
left: "",
right: "",
};
}
impl Default for BorderLook {
fn default() -> Self {
Self::LINE
}
}
#[derive(Debug, Clone, Copy)]
pub struct Border<I> {
pub inner: I,
pub look: BorderLook,
pub style: Style,
}
impl<I> Border<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
look: BorderLook::default(),
style: Style::default(),
}
}
pub fn with_look(mut self, look: BorderLook) -> Self {
self.look = look;
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
fn draw_border(&self, frame: &mut Frame) {
let size = frame.size();
let right = size.width.saturating_sub(1).into();
let bottom = size.height.saturating_sub(1).into();
for y in 1..bottom {
frame.write(Pos::new(right, y), (self.look.right, self.style));
frame.write(Pos::new(0, y), (self.look.left, self.style));
}
for x in 1..right {
frame.write(Pos::new(x, bottom), (self.look.bottom, self.style));
frame.write(Pos::new(x, 0), (self.look.top, self.style));
}
frame.write(
Pos::new(right, bottom),
(self.look.bottom_right, self.style),
);
frame.write(Pos::new(0, bottom), (self.look.bottom_left, self.style));
frame.write(Pos::new(right, 0), (self.look.top_right, self.style));
frame.write(Pos::new(0, 0), (self.look.top_left, self.style));
}
fn push_inner(&self, frame: &mut Frame) {
let mut size = frame.size();
size.width = size.width.saturating_sub(2);
size.height = size.height.saturating_sub(2);
frame.push(Pos::new(1, 1), size);
}
}
impl<E, I> Widget<E> for Border<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let max_width = max_width.map(|w| w.saturating_sub(2));
let max_height = max_height.map(|h| h.saturating_sub(2));
let size = self.inner.size(widthdb, max_width, max_height)?;
Ok(size + Size::new(2, 2))
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.draw_border(frame);
self.push_inner(frame);
self.inner.draw(frame)?;
frame.pop();
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Border<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let max_width = max_width.map(|w| w.saturating_sub(2));
let max_height = max_height.map(|h| h.saturating_sub(2));
let size = self.inner.size(widthdb, max_width, max_height).await?;
Ok(size + Size::new(2, 2))
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.draw_border(frame);
self.push_inner(frame);
self.inner.draw(frame).await?;
frame.pop();
Ok(())
}
}

View file

@ -1,142 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
pub struct Boxed<'a, E>(Box<dyn WidgetWrapper<E> + 'a>);
impl<'a, E> Boxed<'a, E> {
pub fn new<I>(inner: I) -> Self
where
I: Widget<E> + 'a,
{
Self(Box::new(inner))
}
}
impl<E> Widget<E> for Boxed<'_, E> {
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.0.wrap_size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.0.wrap_draw(frame)
}
}
pub struct BoxedSendSync<'a, E>(Box<dyn WidgetWrapper<E> + Send + Sync + 'a>);
impl<'a, E> BoxedSendSync<'a, E> {
pub fn new<I>(inner: I) -> Self
where
I: Widget<E> + Send + Sync + 'a,
{
Self(Box::new(inner))
}
}
impl<E> Widget<E> for BoxedSendSync<'_, E> {
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.0.wrap_size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.0.wrap_draw(frame)
}
}
pub struct BoxedAsync<'a, E>(Box<dyn AsyncWidgetWrapper<E> + Send + Sync + 'a>);
impl<'a, E> BoxedAsync<'a, E> {
pub fn new<I>(inner: I) -> Self
where
I: AsyncWidget<E> + Send + Sync + 'a,
{
Self(Box::new(inner))
}
}
#[async_trait]
impl<E> AsyncWidget<E> for BoxedAsync<'_, E> {
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.0.wrap_size(widthdb, max_width, max_height).await
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.0.wrap_draw(frame).await
}
}
trait WidgetWrapper<E> {
fn wrap_size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E>;
fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E>;
}
impl<E, W> WidgetWrapper<E> for W
where
W: Widget<E>,
{
fn wrap_size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.size(widthdb, max_width, max_height)
}
fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E> {
(*self).draw(frame)
}
}
#[async_trait]
trait AsyncWidgetWrapper<E> {
async fn wrap_size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E>;
async fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E>;
}
#[async_trait]
impl<E, W> AsyncWidgetWrapper<E> for W
where
W: AsyncWidget<E> + Send + Sync,
{
async fn wrap_size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.size(widthdb, max_width, max_height).await
}
async fn wrap_draw(self: Box<Self>, frame: &mut Frame) -> Result<(), E> {
(*self).draw(frame).await
}
}

View file

@ -1,68 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct Cursor<I> {
pub inner: I,
pub position: Pos,
}
impl<I> Cursor<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
position: Pos::ZERO,
}
}
pub fn with_position(mut self, position: Pos) -> Self {
self.position = position;
self
}
pub fn with_position_xy(self, x: i32, y: i32) -> Self {
self.with_position(Pos::new(x, y))
}
}
impl<E, I> Widget<E> for Cursor<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame)?;
frame.show_cursor(self.position);
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Cursor<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height).await
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame).await?;
frame.show_cursor(self.position);
Ok(())
}
}

View file

@ -1,42 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Widget};
pub struct Desync<I>(pub I);
impl<E, I> Widget<E> for Desync<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut crate::WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<crate::Size, E> {
self.0.size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut crate::Frame) -> Result<(), E> {
self.0.draw(frame)
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Desync<I>
where
I: Widget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut crate::WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<crate::Size, E> {
self.0.size(widthdb, max_width, max_height)
}
async fn draw(self, frame: &mut crate::Frame) -> Result<(), E> {
self.0.draw(frame)
}
}

View file

@ -1,485 +0,0 @@
use std::iter;
use crossterm::style::Stylize;
use unicode_segmentation::UnicodeSegmentation;
use crate::{Frame, Pos, Size, Style, Styled, Widget, WidthDb};
/// Like [`WidthDb::wrap`] but includes a final break index if the text ends
/// with a newline.
fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec<usize> {
let mut breaks = widthdb.wrap(text, width);
if text.ends_with('\n') {
breaks.push(text.len())
}
breaks
}
///////////
// State //
///////////
#[derive(Debug, Clone)]
pub struct EditorState {
text: String,
/// Index of the cursor in the text.
///
/// Must point to a valid grapheme boundary.
cursor_idx: usize,
/// Column of the cursor on the screen just after it was last moved
/// horizontally.
cursor_col: usize,
/// Position of the cursor when the editor was last rendered.
last_cursor_pos: Pos,
}
impl EditorState {
pub fn new() -> Self {
Self::with_initial_text(String::new())
}
pub fn with_initial_text(text: String) -> Self {
Self {
cursor_idx: text.len(),
cursor_col: 0,
last_cursor_pos: Pos::ZERO,
text,
}
}
///////////////////////////////
// Grapheme helper functions //
///////////////////////////////
fn grapheme_boundaries(&self) -> Vec<usize> {
self.text
.grapheme_indices(true)
.map(|(i, _)| i)
.chain(iter::once(self.text.len()))
.collect()
}
/// Ensure the cursor index lies on a grapheme boundary. If it doesn't, it
/// is moved to the next grapheme boundary.
///
/// Can handle arbitrary cursor index.
fn move_cursor_to_grapheme_boundary(&mut self) {
for i in self.grapheme_boundaries() {
#[allow(clippy::comparison_chain)]
if i == self.cursor_idx {
// We're at a valid grapheme boundary already
return;
} else if i > self.cursor_idx {
// There was no valid grapheme boundary at our cursor index, so
// we'll take the next one we can get.
self.cursor_idx = i;
return;
}
}
// The cursor was out of bounds, so move it to the last valid index.
self.cursor_idx = self.text.len();
}
///////////////////////////////
// Line/col helper functions //
///////////////////////////////
/// Like [`Self::grapheme_boundaries`] but for lines.
///
/// Note that the last line can have a length of 0 if the text ends with a
/// newline.
fn line_boundaries(&self) -> Vec<usize> {
let newlines = self
.text
.char_indices()
.filter(|(_, c)| *c == '\n')
.map(|(i, _)| i + 1); // utf-8 encodes '\n' as a single byte
iter::once(0)
.chain(newlines)
.chain(iter::once(self.text.len()))
.collect()
}
/// Find the cursor's current line.
///
/// Returns `(line_nr, start_idx, end_idx)`.
fn cursor_line(&self, boundaries: &[usize]) -> (usize, usize, usize) {
let mut result = (0, 0, 0);
for (i, (start, end)) in boundaries.iter().zip(boundaries.iter().skip(1)).enumerate() {
if self.cursor_idx >= *start {
result = (i, *start, *end);
} else {
break;
}
}
result
}
fn cursor_col(&self, widthdb: &mut WidthDb, line_start: usize) -> usize {
widthdb.width(&self.text[line_start..self.cursor_idx])
}
fn line(&self, line: usize) -> (usize, usize) {
let boundaries = self.line_boundaries();
boundaries
.iter()
.copied()
.zip(boundaries.iter().copied().skip(1))
.nth(line)
.expect("line exists")
}
fn move_cursor_to_line_col(&mut self, widthdb: &mut WidthDb, line: usize, col: usize) {
let (start, end) = self.line(line);
let line = &self.text[start..end];
let mut width = 0;
for (gi, g) in line.grapheme_indices(true) {
self.cursor_idx = start + gi;
if col > width {
width += widthdb.grapheme_width(g, width) as usize;
} else {
return;
}
}
if !line.ends_with('\n') {
self.cursor_idx = end;
}
}
fn record_cursor_col(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.line_boundaries();
let (_, start, _) = self.cursor_line(&boundaries);
self.cursor_col = self.cursor_col(widthdb, start);
}
/////////////
// Editing //
/////////////
pub fn text(&self) -> &str {
&self.text
}
pub fn set_text(&mut self, widthdb: &mut WidthDb, text: String) {
self.text = text;
self.move_cursor_to_grapheme_boundary();
self.record_cursor_col(widthdb);
}
pub fn clear(&mut self) {
self.text = String::new();
self.cursor_idx = 0;
self.cursor_col = 0;
}
/// Insert a character at the current cursor position and move the cursor
/// accordingly.
pub fn insert_char(&mut self, widthdb: &mut WidthDb, ch: char) {
self.text.insert(self.cursor_idx, ch);
self.cursor_idx += ch.len_utf8();
self.record_cursor_col(widthdb);
}
/// Insert a string at the current cursor position and move the cursor
/// accordingly.
pub fn insert_str(&mut self, widthdb: &mut WidthDb, str: &str) {
self.text.insert_str(self.cursor_idx, str);
self.cursor_idx += str.len();
self.record_cursor_col(widthdb);
}
/// Delete the grapheme before the cursor position.
pub fn backspace(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.grapheme_boundaries();
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
if *end == self.cursor_idx {
self.text.replace_range(start..end, "");
self.cursor_idx = *start;
self.record_cursor_col(widthdb);
break;
}
}
}
/// Delete the grapheme after the cursor position.
pub fn delete(&mut self) {
let boundaries = self.grapheme_boundaries();
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
if *start == self.cursor_idx {
self.text.replace_range(start..end, "");
break;
}
}
}
/////////////////////
// Cursor movement //
/////////////////////
pub fn move_cursor_left(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.grapheme_boundaries();
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
if *end == self.cursor_idx {
self.cursor_idx = *start;
self.record_cursor_col(widthdb);
break;
}
}
}
pub fn move_cursor_right(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.grapheme_boundaries();
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
if *start == self.cursor_idx {
self.cursor_idx = *end;
self.record_cursor_col(widthdb);
break;
}
}
}
pub fn move_cursor_left_a_word(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.grapheme_boundaries();
let mut encountered_word = false;
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)).rev() {
if *end == self.cursor_idx {
let g = &self.text[*start..*end];
let whitespace = g.chars().all(|c| c.is_whitespace());
if encountered_word && whitespace {
break;
} else if !whitespace {
encountered_word = true;
}
self.cursor_idx = *start;
}
}
self.record_cursor_col(widthdb);
}
pub fn move_cursor_right_a_word(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.grapheme_boundaries();
let mut encountered_word = false;
for (start, end) in boundaries.iter().zip(boundaries.iter().skip(1)) {
if *start == self.cursor_idx {
let g = &self.text[*start..*end];
let whitespace = g.chars().all(|c| c.is_whitespace());
if encountered_word && whitespace {
break;
} else if !whitespace {
encountered_word = true;
}
self.cursor_idx = *end;
}
}
self.record_cursor_col(widthdb);
}
pub fn move_cursor_to_start_of_line(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.line_boundaries();
let (line, _, _) = self.cursor_line(&boundaries);
self.move_cursor_to_line_col(widthdb, line, 0);
self.record_cursor_col(widthdb);
}
pub fn move_cursor_to_end_of_line(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.line_boundaries();
let (line, _, _) = self.cursor_line(&boundaries);
self.move_cursor_to_line_col(widthdb, line, usize::MAX);
self.record_cursor_col(widthdb);
}
pub fn move_cursor_up(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.line_boundaries();
let (line, _, _) = self.cursor_line(&boundaries);
if line > 0 {
self.move_cursor_to_line_col(widthdb, line - 1, self.cursor_col);
}
}
pub fn move_cursor_down(&mut self, widthdb: &mut WidthDb) {
let boundaries = self.line_boundaries();
// There's always at least one line, and always at least two line
// boundaries at 0 and self.text.len().
let amount_of_lines = boundaries.len() - 1;
let (line, _, _) = self.cursor_line(&boundaries);
if line + 1 < amount_of_lines {
self.move_cursor_to_line_col(widthdb, line + 1, self.cursor_col);
}
}
pub fn last_cursor_pos(&self) -> Pos {
self.last_cursor_pos
}
pub fn widget(&mut self) -> Editor<'_> {
Editor {
highlighted: Styled::new_plain(&self.text),
hidden: None,
focus: true,
state: self,
}
}
}
impl Default for EditorState {
fn default() -> Self {
Self::new()
}
}
////////////
// Widget //
////////////
#[derive(Debug)]
pub struct Editor<'a> {
state: &'a mut EditorState,
highlighted: Styled,
pub hidden: Option<Styled>,
pub focus: bool,
}
impl Editor<'_> {
pub fn state(&mut self) -> &mut EditorState {
self.state
}
pub fn text(&self) -> &Styled {
&self.highlighted
}
pub fn highlight<F>(&mut self, highlight: F)
where
F: FnOnce(&str) -> Styled,
{
self.highlighted = highlight(&self.state.text);
assert_eq!(self.state.text, self.highlighted.text());
}
pub fn with_highlight<F>(mut self, highlight: F) -> Self
where
F: FnOnce(&str) -> Styled,
{
self.highlight(highlight);
self
}
pub fn with_visible(mut self) -> Self {
self.hidden = None;
self
}
pub fn with_hidden<S: Into<Styled>>(mut self, placeholder: S) -> Self {
self.hidden = Some(placeholder.into());
self
}
pub fn with_hidden_default_placeholder(self) -> Self {
self.with_hidden(("<hidden>", Style::new().grey().italic()))
}
pub fn with_focus(mut self, active: bool) -> Self {
self.focus = active;
self
}
fn wrapped_cursor(cursor_idx: usize, break_indices: &[usize]) -> (usize, usize) {
let mut row = 0;
let mut line_idx = cursor_idx;
for break_idx in break_indices {
if cursor_idx < *break_idx {
break;
} else {
row += 1;
line_idx = cursor_idx - break_idx;
}
}
(row, line_idx)
}
fn indices(&self, widthdb: &mut WidthDb, max_width: Option<u16>) -> Vec<usize> {
let max_width = max_width
// One extra column for cursor
.map(|w| w.saturating_sub(1) as usize)
.unwrap_or(usize::MAX);
let text = self.hidden.as_ref().unwrap_or(&self.highlighted);
wrap(widthdb, text.text(), max_width)
}
fn rows(&self, indices: &[usize]) -> Vec<Styled> {
let text = match self.hidden.as_ref() {
Some(hidden) if !self.highlighted.text().is_empty() => hidden,
_ => &self.highlighted,
};
text.clone().split_at_indices(indices)
}
fn cursor(&self, widthdb: &mut WidthDb, width: u16, indices: &[usize], rows: &[Styled]) -> Pos {
if self.hidden.is_some() {
return Pos::new(0, 0);
}
let (cursor_row, cursor_line_idx) = Self::wrapped_cursor(self.state.cursor_idx, indices);
let cursor_col = widthdb.width(rows[cursor_row].text().split_at(cursor_line_idx).0);
// Ensure the cursor is always visible
let cursor_col = cursor_col.min(width.saturating_sub(1).into());
let cursor_row: i32 = cursor_row.try_into().unwrap_or(i32::MAX);
let cursor_col: i32 = cursor_col.try_into().unwrap_or(i32::MAX);
Pos::new(cursor_col, cursor_row)
}
}
impl<E> Widget<E> for Editor<'_> {
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
_max_height: Option<u16>,
) -> Result<Size, E> {
let indices = self.indices(widthdb, max_width);
let rows = self.rows(&indices);
let width = rows
.iter()
.map(|row| widthdb.width(row.text()))
.max()
.unwrap_or(0)
// One extra column for cursor
.saturating_add(1);
let height = rows.len();
let width: u16 = width.try_into().unwrap_or(u16::MAX);
let height: u16 = height.try_into().unwrap_or(u16::MAX);
Ok(Size::new(width, height))
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
let size = frame.size();
let indices = self.indices(frame.widthdb(), Some(size.width));
let rows = self.rows(&indices);
let cursor = self.cursor(frame.widthdb(), size.width, &indices, &rows);
for (i, row) in rows.into_iter().enumerate() {
frame.write(Pos::new(0, i as i32), row);
}
if self.focus {
frame.set_cursor(Some(cursor));
}
self.state.last_cursor_pos = cursor;
Ok(())
}
}

View file

@ -1,118 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
macro_rules! mk_either {
(
pub enum $name:ident {
$( $constr:ident($ty:ident), )+
}
) => {
#[derive(Debug, Clone, Copy)]
pub enum $name< $( $ty ),+ > {
$( $constr($ty), )+
}
impl<E, $( $ty ),+> Widget<E> for $name< $( $ty ),+ >
where
$( $ty: Widget<E>, )+
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
match self {
$( Self::$constr(w) => w.size(widthdb, max_width, max_height), )+
}
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
match self {
$( Self::$constr(w) => w.draw(frame), )+
}
}
}
#[async_trait]
impl<E, $( $ty ),+> AsyncWidget<E> for $name< $( $ty ),+ >
where
$( $ty: AsyncWidget<E> + Send + Sync, )+
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
match self {
$( Self::$constr(w) => w.size(widthdb, max_width, max_height).await, )+
}
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
match self {
$( Self::$constr(w) => w.draw(frame).await, )+
}
}
}
};
}
mk_either! {
pub enum Either2 {
First(I1),
Second(I2),
}
}
mk_either! {
pub enum Either3 {
First(I1),
Second(I2),
Third(I3),
}
}
mk_either! {
pub enum Either4 {
First(I1),
Second(I2),
Third(I3),
Fourth(I4),
}
}
mk_either! {
pub enum Either5 {
First(I1),
Second(I2),
Third(I3),
Fourth(I4),
Fifth(I5),
}
}
mk_either! {
pub enum Either6 {
First(I1),
Second(I2),
Third(I3),
Fourth(I4),
Fifth(I5),
Sixth(I6),
}
}
mk_either! {
pub enum Either7 {
First(I1),
Second(I2),
Third(I3),
Fourth(I4),
Fifth(I5),
Sixth(I6),
Seventh(I7),
}
}

View file

@ -1,42 +0,0 @@
use crate::{Frame, Size, Widget, WidthDb};
#[derive(Debug, Default, Clone, Copy)]
pub struct Empty {
pub size: Size,
}
impl Empty {
pub fn new() -> Self {
Self { size: Size::ZERO }
}
pub fn with_width(mut self, width: u16) -> Self {
self.size.width = width;
self
}
pub fn with_height(mut self, height: u16) -> Self {
self.size.height = height;
self
}
pub fn with_size(mut self, size: Size) -> Self {
self.size = size;
self
}
}
impl<E> Widget<E> for Empty {
fn size(
&self,
_widthdb: &mut WidthDb,
_max_width: Option<u16>,
_max_height: Option<u16>,
) -> Result<Size, E> {
Ok(self.size)
}
fn draw(self, _frame: &mut Frame) -> Result<(), E> {
Ok(())
}
}

View file

@ -1,166 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct Float<I> {
pub inner: I,
horizontal: Option<f32>,
vertical: Option<f32>,
}
impl<I> Float<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
horizontal: None,
vertical: None,
}
}
pub fn horizontal(&self) -> Option<f32> {
self.horizontal
}
pub fn set_horizontal(&mut self, position: Option<f32>) {
if let Some(position) = position {
assert!((0.0..=1.0).contains(&position));
}
self.horizontal = position;
}
pub fn vertical(&self) -> Option<f32> {
self.vertical
}
pub fn set_vertical(&mut self, position: Option<f32>) {
if let Some(position) = position {
assert!((0.0..=1.0).contains(&position));
}
self.vertical = position;
}
pub fn with_horizontal(mut self, position: f32) -> Self {
self.set_horizontal(Some(position));
self
}
pub fn with_vertical(mut self, position: f32) -> Self {
self.set_vertical(Some(position));
self
}
pub fn with_all(self, position: f32) -> Self {
self.with_horizontal(position).with_vertical(position)
}
pub fn with_left(self) -> Self {
self.with_horizontal(0.0)
}
pub fn with_right(self) -> Self {
self.with_horizontal(1.0)
}
pub fn with_top(self) -> Self {
self.with_vertical(0.0)
}
pub fn with_bottom(self) -> Self {
self.with_vertical(1.0)
}
pub fn with_center_h(self) -> Self {
self.with_horizontal(0.5)
}
pub fn with_center_v(self) -> Self {
self.with_vertical(0.5)
}
pub fn with_center(self) -> Self {
self.with_all(0.5)
}
fn push_inner(&self, frame: &mut Frame, size: Size, mut inner_size: Size) {
let mut inner_pos = Pos::ZERO;
if let Some(horizontal) = self.horizontal {
let available = size.width.saturating_sub(inner_size.width) as f32;
// Biased towards the left if horizontal lands exactly on the
// boundary between two cells
inner_pos.x = (horizontal * available).floor().min(available) as i32;
inner_size.width = inner_size.width.min(size.width);
} else {
inner_size.width = size.width;
}
if let Some(vertical) = self.vertical {
let available = size.height.saturating_sub(inner_size.height) as f32;
// Biased towards the top if vertical lands exactly on the boundary
// between two cells
inner_pos.y = (vertical * available).floor().min(available) as i32;
inner_size.height = inner_size.height.min(size.height);
} else {
inner_size.height = size.height;
}
frame.push(inner_pos, inner_size);
}
}
impl<E, I> Widget<E> for Float<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
let size = frame.size();
let inner_size = self
.inner
.size(frame.widthdb(), Some(size.width), Some(size.height))?;
self.push_inner(frame, size, inner_size);
self.inner.draw(frame)?;
frame.pop();
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Float<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height).await
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
let size = frame.size();
let inner_size = self
.inner
.size(frame.widthdb(), Some(size.width), Some(size.height))
.await?;
self.push_inner(frame, size, inner_size);
self.inner.draw(frame).await?;
frame.pop();
Ok(())
}
}

View file

@ -1,721 +0,0 @@
use std::cmp::Ordering;
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
// The following algorithm has three goals, listed in order of importance:
//
// 1. Use the available space
// 2. Avoid shrinking segments where possible
// 3. Match the given weights as closely as possible
//
// Its input is a list of weighted segments where each segment wants to use a
// certain amount of space. The weights signify how the available space would be
// assigned if goal 2 was irrelevant.
//
// First, the algorithm must decide whether it must grow or shrink segments.
// Because goal 2 has a higher priority than goal 3, it never makes sense to
// shrink a segment in order to make another larger. In both cases, a segment's
// actual size is compared to its allotment, i. e. what size it should be based
// on its weight.
//
// Growth
// ======
//
// If segments must be grown, an important observation can be made: If all
// segments are smaller than their allotment, then each segment can be assigned
// its allotment without violating goal 2, thereby fulfilling goal 3.
//
// Another important observation can be made: If a segment is at least as large
// as its allotment, it must never be grown as that would violate goal 3.
//
// Based on these two observations, the growth algorithm first repeatedly
// removes all segments that are at least as large as their allotment. It then
// resizes the remaining segments to their allotments.
//
// Shrinkage
// =========
//
// If segments must be shrunk, an important observation can be made: If all
// segments are larger than their allotment, then each segment can be assigned
// its allotment, thereby fulfilling goal 3. Since goal 1 is more important than
// goal 2, we know that some elements must be shrunk.
//
// Another important observation can be made: If a segment is at least as small
// as its allotment, it must never be shrunk as that would violate goal 3.
//
// Based on these two observations, the shrinkage algorithm first repeatedly
// removes all segments that are at least as small as their allotment. It then
// resizes the remaining segments to their allotments.
#[derive(Debug)]
struct Segment {
major: u16,
minor: u16,
weight: f32,
growing: bool,
shrinking: bool,
}
impl Segment {
fn new<I>(major_minor: (u16, u16), segment: &JoinSegment<I>) -> Self {
Self {
major: major_minor.0,
minor: major_minor.1,
weight: segment.weight,
growing: segment.growing,
shrinking: segment.shrinking,
}
}
}
fn total_size(segments: &[&mut Segment]) -> u16 {
let mut total = 0_u16;
for segment in segments {
total = total.saturating_add(segment.major);
}
total
}
fn total_weight(segments: &[&mut Segment]) -> f32 {
segments.iter().map(|s| s.weight).sum()
}
fn balance(segments: &mut [Segment], available: u16) {
let segments = segments.iter_mut().collect::<Vec<_>>();
match total_size(&segments).cmp(&available) {
Ordering::Less => grow(segments, available),
Ordering::Greater => shrink(segments, available),
Ordering::Equal => {}
}
}
fn grow(mut segments: Vec<&mut Segment>, mut available: u16) {
assert!(available >= total_size(&segments));
// Only grow segments that can be grown.
segments.retain(|s| {
if s.growing {
return true;
}
available = available.saturating_sub(s.major);
false
});
// Repeatedly remove all segments that do not need to grow, i. e. that are
// at least as large as their allotment.
loop {
let mut total_weight = total_weight(&segments);
// If there are no segments with a weight > 0, space is distributed
// evenly among all remaining segments.
if total_weight <= 0.0 {
for segment in &mut segments {
segment.weight = 1.0;
}
total_weight = segments.len() as f32;
}
let mut removed = 0;
segments.retain(|s| {
let allotment = s.weight / total_weight * available as f32;
if (s.major as f32) < allotment {
return true; // May need to grow
}
removed += s.major;
false
});
available -= removed;
if removed == 0 {
break; // All remaining segments are smaller than their allotments
}
}
let total_weight = segments.iter().map(|s| s.weight).sum::<f32>();
if total_weight <= 0.0 {
return; // No more segments left
}
// Size each remaining segment according to its allotment.
let mut used = 0;
for segment in &mut segments {
let allotment = segment.weight / total_weight * available as f32;
segment.major = allotment.floor() as u16;
used += segment.major;
}
// Distribute remaining unused space from left to right.
//
// The rounding error on each segment is at most 1, so we only need to loop
// over the segments once.
let remaining = available - used;
assert!(remaining as usize <= segments.len());
for segment in segments.into_iter().take(remaining.into()) {
segment.major += 1;
}
}
fn shrink(mut segments: Vec<&mut Segment>, mut available: u16) {
assert!(available <= total_size(&segments));
// Only shrink segments that can be shrunk.
segments.retain(|s| {
if s.shrinking {
return true;
}
available = available.saturating_sub(s.major);
false
});
// Repeatedly remove all segments that do not need to shrink, i. e. that are
// at least as small as their allotment.
loop {
let mut total_weight = total_weight(&segments);
// If there are no segments with a weight > 0, space is distributed
// evenly among all remaining segments.
if total_weight <= 0.0 {
for segment in &mut segments {
segment.weight = 1.0;
}
total_weight = segments.len() as f32;
}
let mut removed = 0;
segments.retain(|s| {
let allotment = s.weight / total_weight * available as f32;
if (s.major as f32) > allotment {
return true; // May need to shrink
}
// The segment size subtracted from `available` is always smaller
// than or equal to its allotment. Since `available` is the sum of
// all allotments, it can never go below 0.
assert!(s.major <= available);
removed += s.major;
false
});
available -= removed;
if removed == 0 {
break; // All segments want more than their weight allows.
}
}
let total_weight = segments.iter().map(|s| s.weight).sum::<f32>();
if total_weight <= 0.0 {
return; // No more segments left
}
// Size each remaining segment according to its allotment.
let mut used = 0;
for segment in &mut segments {
let allotment = segment.weight / total_weight * available as f32;
segment.major = allotment.floor() as u16;
used += segment.major;
}
// Distribute remaining unused space from left to right.
//
// The rounding error on each segment is at most 1, so we only need to loop
// over the segments once.
let remaining = available - used;
assert!(remaining as usize <= segments.len());
for segment in segments.into_iter().take(remaining.into()) {
segment.major += 1;
}
}
#[derive(Debug, Clone, Copy)]
pub struct JoinSegment<I> {
pub inner: I,
weight: f32,
pub growing: bool,
pub shrinking: bool,
}
impl<I> JoinSegment<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
weight: 1.0,
growing: true,
shrinking: true,
}
}
pub fn weight(&self) -> f32 {
self.weight
}
pub fn set_weight(&mut self, weight: f32) {
assert!(weight >= 0.0);
self.weight = weight;
}
pub fn with_weight(mut self, weight: f32) -> Self {
self.set_weight(weight);
self
}
pub fn with_growing(mut self, enabled: bool) -> Self {
self.growing = enabled;
self
}
pub fn with_shrinking(mut self, enabled: bool) -> Self {
self.shrinking = enabled;
self
}
pub fn with_fixed(self, fixed: bool) -> Self {
self.with_growing(!fixed).with_shrinking(!fixed)
}
}
fn to_mm<T>(horizontal: bool, w: T, h: T) -> (T, T) {
if horizontal {
(w, h)
} else {
(h, w)
}
}
fn from_mm<T>(horizontal: bool, major: T, minor: T) -> (T, T) {
if horizontal {
(major, minor)
} else {
(minor, major)
}
}
fn size<E, I: Widget<E>>(
horizontal: bool,
widthdb: &mut WidthDb,
segment: &JoinSegment<I>,
major: Option<u16>,
minor: Option<u16>,
) -> Result<(u16, u16), E> {
if horizontal {
let size = segment.inner.size(widthdb, major, minor)?;
Ok((size.width, size.height))
} else {
let size = segment.inner.size(widthdb, minor, major)?;
Ok((size.height, size.width))
}
}
fn size_with_balanced<E, I: Widget<E>>(
horizontal: bool,
widthdb: &mut WidthDb,
segment: &JoinSegment<I>,
balanced: &Segment,
minor: Option<u16>,
) -> Result<(u16, u16), E> {
size(horizontal, widthdb, segment, Some(balanced.major), minor)
}
async fn size_async<E, I: AsyncWidget<E>>(
horizontal: bool,
widthdb: &mut WidthDb,
segment: &JoinSegment<I>,
major: Option<u16>,
minor: Option<u16>,
) -> Result<(u16, u16), E> {
if horizontal {
let size = segment.inner.size(widthdb, major, minor).await?;
Ok((size.width, size.height))
} else {
let size = segment.inner.size(widthdb, minor, major).await?;
Ok((size.height, size.width))
}
}
async fn size_async_with_balanced<E, I: AsyncWidget<E>>(
horizontal: bool,
widthdb: &mut WidthDb,
segment: &JoinSegment<I>,
balanced: &Segment,
minor: Option<u16>,
) -> Result<(u16, u16), E> {
size_async(horizontal, widthdb, segment, Some(balanced.major), minor).await
}
fn sum_major_max_minor(segments: &[Segment]) -> (u16, u16) {
let mut major = 0_u16;
let mut minor = 0_u16;
for segment in segments {
major = major.saturating_add(segment.major);
minor = minor.max(segment.minor);
}
(major, minor)
}
#[derive(Debug, Clone)]
pub struct Join<I> {
horizontal: bool,
segments: Vec<JoinSegment<I>>,
}
impl<I> Join<I> {
pub fn horizontal(segments: Vec<JoinSegment<I>>) -> Self {
Self {
horizontal: true,
segments,
}
}
pub fn vertical(segments: Vec<JoinSegment<I>>) -> Self {
Self {
horizontal: false,
segments,
}
}
}
impl<E, I> Widget<E> for Join<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
let mut segments = Vec::with_capacity(self.segments.len());
for segment in &self.segments {
let major_minor = size(self.horizontal, widthdb, segment, None, max_minor)?;
segments.push(Segment::new(major_minor, segment));
}
if let Some(available) = max_major {
balance(&mut segments, available);
let mut new_segments = Vec::with_capacity(self.segments.len());
for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) {
let major_minor =
size_with_balanced(self.horizontal, widthdb, segment, &balanced, max_minor)?;
new_segments.push(Segment::new(major_minor, segment));
}
segments = new_segments;
}
let (major, minor) = sum_major_max_minor(&segments);
let (width, height) = from_mm(self.horizontal, major, minor);
Ok(Size::new(width, height))
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
let frame_size = frame.size();
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
let widthdb = frame.widthdb();
let mut segments = Vec::with_capacity(self.segments.len());
for segment in &self.segments {
let major_minor = size(self.horizontal, widthdb, segment, None, Some(max_minor))?;
segments.push(Segment::new(major_minor, segment));
}
balance(&mut segments, max_major);
let mut major = 0_i32;
for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) {
let (x, y) = from_mm(self.horizontal, major, 0);
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
frame.push(Pos::new(x, y), Size::new(w, h));
segment.inner.draw(frame)?;
frame.pop();
major += balanced.major as i32;
}
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Join<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
let mut segments = Vec::with_capacity(self.segments.len());
for segment in &self.segments {
let major_minor =
size_async(self.horizontal, widthdb, segment, None, max_minor).await?;
segments.push(Segment::new(major_minor, segment));
}
if let Some(available) = max_major {
balance(&mut segments, available);
let mut new_segments = Vec::with_capacity(self.segments.len());
for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) {
let major_minor = size_async_with_balanced(
self.horizontal,
widthdb,
segment,
&balanced,
max_minor,
)
.await?;
new_segments.push(Segment::new(major_minor, segment));
}
segments = new_segments;
}
let (major, minor) = sum_major_max_minor(&segments);
let (width, height) = from_mm(self.horizontal, major, minor);
Ok(Size::new(width, height))
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
let frame_size = frame.size();
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
let widthdb = frame.widthdb();
let mut segments = Vec::with_capacity(self.segments.len());
for segment in &self.segments {
let major_minor =
size_async(self.horizontal, widthdb, segment, None, Some(max_minor)).await?;
segments.push(Segment::new(major_minor, segment));
}
balance(&mut segments, max_major);
let mut major = 0_i32;
for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) {
let (x, y) = from_mm(self.horizontal, major, 0);
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
frame.push(Pos::new(x, y), Size::new(w, h));
segment.inner.draw(frame).await?;
frame.pop();
major += balanced.major as i32;
}
Ok(())
}
}
macro_rules! mk_join {
(
pub struct $name:ident {
$( pub $arg:ident: $type:ident [$n:expr], )+
}
) => {
#[derive(Debug, Clone, Copy)]
pub struct $name< $($type),+ >{
horizontal: bool,
$( pub $arg: JoinSegment<$type>, )+
}
impl< $($type),+ > $name< $($type),+ >{
pub fn horizontal( $($arg: JoinSegment<$type>),+ ) -> Self {
Self { horizontal: true, $( $arg, )+ }
}
pub fn vertical( $($arg: JoinSegment<$type>),+ ) -> Self {
Self { horizontal: false, $( $arg, )+ }
}
}
impl<E, $($type),+ > Widget<E> for $name< $($type),+ >
where
$( $type: Widget<E>, )+
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
let mut segments = [ $(
Segment::new(
size(self.horizontal, widthdb, &self.$arg, None, max_minor)?,
&self.$arg,
),
)+ ];
if let Some(available) = max_major {
balance(&mut segments, available);
let new_segments = [ $(
Segment::new(
size_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor)?,
&self.$arg,
),
)+ ];
segments = new_segments;
}
let (major, minor) = sum_major_max_minor(&segments);
let (width, height) = from_mm(self.horizontal, major, minor);
Ok(Size::new(width, height))
}
#[allow(unused_assignments)]
fn draw(self, frame: &mut Frame) -> Result<(), E> {
let frame_size = frame.size();
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
let widthdb = frame.widthdb();
let mut segments = [ $(
Segment::new(
size(self.horizontal, widthdb, &self.$arg, None, Some(max_minor))?,
&self.$arg,
),
)+ ];
balance(&mut segments, max_major);
let mut major = 0_i32;
$( {
let balanced = &segments[$n];
let (x, y) = from_mm(self.horizontal, major, 0);
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
frame.push(Pos::new(x, y), Size::new(w, h));
self.$arg.inner.draw(frame)?;
frame.pop();
major += balanced.major as i32;
} )*
Ok(())
}
}
#[async_trait]
impl<E, $($type),+ > AsyncWidget<E> for $name< $($type),+ >
where
E: Send,
$( $type: AsyncWidget<E> + Send + Sync, )+
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height);
let mut segments = [ $(
Segment::new(
size_async(self.horizontal, widthdb, &self.$arg, None, max_minor).await?,
&self.$arg,
),
)+ ];
if let Some(available) = max_major {
balance(&mut segments, available);
let new_segments = [ $(
Segment::new(
size_async_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor).await?,
&self.$arg,
),
)+ ];
segments = new_segments;
}
let (major, minor) = sum_major_max_minor(&segments);
let (width, height) = from_mm(self.horizontal, major, minor);
Ok(Size::new(width, height))
}
#[allow(unused_assignments)]
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
let frame_size = frame.size();
let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height);
let widthdb = frame.widthdb();
let mut segments = [ $(
Segment::new(
size_async(self.horizontal, widthdb, &self.$arg, None, Some(max_minor)).await?,
&self.$arg,
),
)+ ];
balance(&mut segments, max_major);
let mut major = 0_i32;
$( {
let balanced = &segments[$n];
let (x, y) = from_mm(self.horizontal, major, 0);
let (w, h) = from_mm(self.horizontal, balanced.major, max_minor);
frame.push(Pos::new(x, y), Size::new(w, h));
self.$arg.inner.draw(frame).await?;
frame.pop();
major += balanced.major as i32;
} )*
Ok(())
}
}
};
}
mk_join! {
pub struct Join2 {
pub first: I1 [0],
pub second: I2 [1],
}
}
mk_join! {
pub struct Join3 {
pub first: I1 [0],
pub second: I2 [1],
pub third: I3 [2],
}
}
mk_join! {
pub struct Join4 {
pub first: I1 [0],
pub second: I2 [1],
pub third: I3 [2],
pub fourth: I4 [3],
}
}
mk_join! {
pub struct Join5 {
pub first: I1 [0],
pub second: I2 [1],
pub third: I3 [2],
pub fourth: I4 [3],
pub fifth: I5 [4],
}
}
mk_join! {
pub struct Join6 {
pub first: I1 [0],
pub second: I2 [1],
pub third: I3 [2],
pub fourth: I4 [3],
pub fifth: I5 [4],
pub sixth: I6 [5],
}
}
mk_join! {
pub struct Join7 {
pub first: I1 [0],
pub second: I2 [1],
pub third: I3 [2],
pub fourth: I4 [3],
pub fifth: I5 [4],
pub sixth: I6 [5],
pub seventh: I7 [6],
}
}

View file

@ -1,201 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
#[derive(Debug, Clone)]
pub struct Layer<I> {
layers: Vec<I>,
}
impl<I> Layer<I> {
pub fn new(layers: Vec<I>) -> Self {
Self { layers }
}
}
impl<E, I> Widget<E> for Layer<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let mut size = Size::ZERO;
for layer in &self.layers {
let lsize = layer.size(widthdb, max_width, max_height)?;
size.width = size.width.max(lsize.width);
size.height = size.height.max(lsize.height);
}
Ok(size)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
for layer in self.layers {
layer.draw(frame)?;
}
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Layer<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let mut size = Size::ZERO;
for layer in &self.layers {
let lsize = layer.size(widthdb, max_width, max_height).await?;
size.width = size.width.max(lsize.width);
size.height = size.height.max(lsize.height);
}
Ok(size)
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
for layer in self.layers {
layer.draw(frame).await?;
}
Ok(())
}
}
macro_rules! mk_layer {
(
pub struct $name:ident {
$( pub $arg:ident: $type:ident, )+
}
) => {
#[derive(Debug, Clone, Copy)]
pub struct $name< $($type),+ >{
$( pub $arg: $type, )+
}
impl< $($type),+ > $name< $($type),+ >{
pub fn new( $($arg: $type),+ ) -> Self {
Self { $( $arg, )+ }
}
}
impl<E, $($type),+ > Widget<E> for $name< $($type),+ >
where
$( $type: Widget<E>, )+
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let mut size = Size::ZERO;
$({
let lsize = self.$arg.size(widthdb, max_width, max_height)?;
size.width = size.width.max(lsize.width);
size.height = size.height.max(lsize.height);
})+
Ok(size)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
$( self.$arg.draw(frame)?; )+
Ok(())
}
}
#[async_trait]
impl<E, $($type),+ > AsyncWidget<E> for $name< $($type),+ >
where
E: Send,
$( $type: AsyncWidget<E> + Send + Sync, )+
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let mut size = Size::ZERO;
$({
let lsize = self.$arg.size(widthdb, max_width, max_height).await?;
size.width = size.width.max(lsize.width);
size.height = size.height.max(lsize.height);
})+
Ok(size)
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
$( self.$arg.draw(frame).await?; )+
Ok(())
}
}
};
}
mk_layer!(
pub struct Layer2 {
pub first: I1,
pub second: I2,
}
);
mk_layer!(
pub struct Layer3 {
pub first: I1,
pub second: I2,
pub third: I3,
}
);
mk_layer!(
pub struct Layer4 {
pub first: I1,
pub second: I2,
pub third: I3,
pub fourth: I4,
}
);
mk_layer!(
pub struct Layer5 {
pub first: I1,
pub second: I2,
pub third: I3,
pub fourth: I4,
pub fifth: I5,
}
);
mk_layer!(
pub struct Layer6 {
pub first: I1,
pub second: I2,
pub third: I3,
pub fourth: I4,
pub fifth: I5,
pub sixth: I6,
}
);
mk_layer!(
pub struct Layer7 {
pub first: I1,
pub second: I2,
pub third: I3,
pub fourth: I4,
pub fifth: I5,
pub sixth: I6,
pub seventh: I7,
}
);

View file

@ -1,133 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct Padding<I> {
pub inner: I,
pub left: u16,
pub right: u16,
pub top: u16,
pub bottom: u16,
pub stretch: bool,
}
impl<I> Padding<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
left: 0,
right: 0,
top: 0,
bottom: 0,
stretch: false,
}
}
pub fn with_left(mut self, amount: u16) -> Self {
self.left = amount;
self
}
pub fn with_right(mut self, amount: u16) -> Self {
self.right = amount;
self
}
pub fn with_top(mut self, amount: u16) -> Self {
self.top = amount;
self
}
pub fn with_bottom(mut self, amount: u16) -> Self {
self.bottom = amount;
self
}
pub fn with_horizontal(self, amount: u16) -> Self {
self.with_left(amount).with_right(amount)
}
pub fn with_vertical(self, amount: u16) -> Self {
self.with_top(amount).with_bottom(amount)
}
pub fn with_all(self, amount: u16) -> Self {
self.with_horizontal(amount).with_vertical(amount)
}
pub fn with_stretch(mut self, stretch: bool) -> Self {
self.stretch = stretch;
self
}
fn pad_size(&self) -> Size {
Size::new(self.left + self.right, self.top + self.bottom)
}
fn push_inner(&self, frame: &mut Frame) {
let size = frame.size();
let pad_size = self.pad_size();
let inner_size = size.saturating_sub(pad_size);
frame.push(Pos::new(self.left.into(), self.top.into()), inner_size);
}
}
impl<E, I> Widget<E> for Padding<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let pad_size = self.pad_size();
let max_width = max_width.map(|w| w.saturating_sub(pad_size.width));
let max_height = max_height.map(|h| h.saturating_sub(pad_size.height));
let size = self.inner.size(widthdb, max_width, max_height)?;
Ok(size + pad_size)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
if self.stretch {
self.inner.draw(frame)?;
} else {
self.push_inner(frame);
self.inner.draw(frame)?;
frame.pop();
}
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Padding<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let pad_size = self.pad_size();
let max_width = max_width.map(|w| w.saturating_sub(pad_size.width));
let max_height = max_height.map(|h| h.saturating_sub(pad_size.height));
let size = self.inner.size(widthdb, max_width, max_height).await?;
Ok(size + pad_size)
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
if self.stretch {
self.inner.draw(frame).await?;
} else {
self.push_inner(frame);
self.inner.draw(frame).await?;
frame.pop();
}
Ok(())
}
}

View file

@ -1,74 +0,0 @@
use std::mem;
use crate::buffer::Buffer;
use crate::{AsyncWidget, Frame, Pos, Size, Style, Styled, Widget, WidthDb};
#[derive(Debug, Clone)]
pub struct Predrawn {
buffer: Buffer,
}
impl Predrawn {
pub fn new<E, W: Widget<E>>(inner: W, widthdb: &mut WidthDb) -> Result<Self, E> {
let mut tmp_frame = Frame::default();
let size = inner.size(widthdb, None, None)?;
tmp_frame.buffer.resize(size);
mem::swap(widthdb, &mut tmp_frame.widthdb);
inner.draw(&mut tmp_frame)?;
mem::swap(widthdb, &mut tmp_frame.widthdb);
let buffer = tmp_frame.buffer;
Ok(Self { buffer })
}
pub async fn new_async<E, W: AsyncWidget<E>>(
inner: W,
widthdb: &mut WidthDb,
) -> Result<Self, E> {
let mut tmp_frame = Frame::default();
let size = inner.size(widthdb, None, None).await?;
tmp_frame.buffer.resize(size);
mem::swap(widthdb, &mut tmp_frame.widthdb);
inner.draw(&mut tmp_frame).await?;
mem::swap(widthdb, &mut tmp_frame.widthdb);
let buffer = tmp_frame.buffer;
Ok(Self { buffer })
}
pub fn size(&self) -> Size {
self.buffer.size()
}
}
impl<E> Widget<E> for Predrawn {
fn size(
&self,
_widthdb: &mut WidthDb,
_max_width: Option<u16>,
_max_height: Option<u16>,
) -> Result<Size, E> {
Ok(self.buffer.size())
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
for (x, y, cell) in self.buffer.cells() {
let pos = Pos::new(x.into(), y.into());
let style = Style {
content_style: cell.style,
opaque: true,
};
frame.write(pos, Styled::new(&cell.content, style));
}
if let Some(cursor) = self.buffer.cursor() {
frame.set_cursor(Some(cursor));
}
Ok(())
}
}

View file

@ -1,120 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
#[derive(Debug, Clone, Copy)]
pub struct Resize<I> {
pub inner: I,
pub min_width: Option<u16>,
pub min_height: Option<u16>,
pub max_width: Option<u16>,
pub max_height: Option<u16>,
}
impl<I> Resize<I> {
pub fn new(inner: I) -> Self {
Self {
inner,
min_width: None,
min_height: None,
max_width: None,
max_height: None,
}
}
pub fn with_min_width(mut self, width: u16) -> Self {
self.min_width = Some(width);
self
}
pub fn with_min_height(mut self, height: u16) -> Self {
self.min_height = Some(height);
self
}
pub fn with_max_width(mut self, width: u16) -> Self {
self.max_width = Some(width);
self
}
pub fn with_max_height(mut self, height: u16) -> Self {
self.max_height = Some(height);
self
}
fn presize(
&self,
mut width: Option<u16>,
mut height: Option<u16>,
) -> (Option<u16>, Option<u16>) {
if let Some(mw) = self.max_width {
width = Some(width.unwrap_or(mw).min(mw));
}
if let Some(mh) = self.max_height {
height = Some(height.unwrap_or(mh).max(mh));
}
(width, height)
}
fn resize(&self, size: Size) -> Size {
let mut width = size.width;
let mut height = size.height;
if let Some(min_width) = self.min_width {
width = width.max(min_width);
}
if let Some(min_height) = self.min_height {
height = height.max(min_height);
}
if let Some(max_width) = self.max_width {
width = width.min(max_width);
}
if let Some(max_height) = self.max_height {
height = height.min(max_height);
}
Size::new(width, height)
}
}
impl<E, I> Widget<E> for Resize<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_width, max_height) = self.presize(max_width, max_height);
let size = self.inner.size(widthdb, max_width, max_height)?;
Ok(self.resize(size))
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame)
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Resize<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
let (max_width, max_height) = self.presize(max_width, max_height);
let size = self.inner.size(widthdb, max_width, max_height).await?;
Ok(self.resize(size))
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame).await
}
}

View file

@ -1,68 +0,0 @@
use crate::{Frame, Pos, Size, Styled, Widget, WidthDb};
#[derive(Debug, Clone)]
pub struct Text {
pub styled: Styled,
pub wrap: bool,
}
impl Text {
pub fn new<S: Into<Styled>>(styled: S) -> Self {
Self {
styled: styled.into(),
wrap: true,
}
}
pub fn with_wrap(mut self, active: bool) -> Self {
self.wrap = active;
self
}
fn wrapped(&self, widthdb: &mut WidthDb, max_width: Option<u16>) -> Vec<Styled> {
let max_width = max_width
.filter(|_| self.wrap)
.map(|w| w as usize)
.unwrap_or(usize::MAX);
let indices = widthdb.wrap(self.styled.text(), max_width);
self.styled.clone().split_at_indices(&indices)
}
}
impl<E> Widget<E> for Text {
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
_max_height: Option<u16>,
) -> Result<Size, E> {
let lines = self.wrapped(widthdb, max_width);
let min_width = lines
.iter()
.map(|l| widthdb.width(l.text().trim_end()))
.max()
.unwrap_or(0);
let min_height = lines.len();
let min_width: u16 = min_width.try_into().unwrap_or(u16::MAX);
let min_height: u16 = min_height.try_into().unwrap_or(u16::MAX);
Ok(Size::new(min_width, min_height))
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
let size = frame.size();
for (i, line) in self
.wrapped(frame.widthdb(), Some(size.width))
.into_iter()
.enumerate()
{
let i: i32 = i.try_into().unwrap_or(i32::MAX);
frame.write(Pos::new(0, i), line);
}
Ok(())
}
}

View file

@ -1,59 +0,0 @@
use async_trait::async_trait;
use crate::{AsyncWidget, Frame, Size, Widget, WidthDb};
#[derive(Debug, Clone)]
pub struct Title<I> {
pub inner: I,
pub title: String,
}
impl<I> Title<I> {
pub fn new<S: ToString>(inner: I, title: S) -> Self {
Self {
inner,
title: title.to_string(),
}
}
}
impl<E, I> Widget<E> for Title<I>
where
I: Widget<E>,
{
fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height)
}
fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame)?;
frame.set_title(Some(self.title));
Ok(())
}
}
#[async_trait]
impl<E, I> AsyncWidget<E> for Title<I>
where
I: AsyncWidget<E> + Send + Sync,
{
async fn size(
&self,
widthdb: &mut WidthDb,
max_width: Option<u16>,
max_height: Option<u16>,
) -> Result<Size, E> {
self.inner.size(widthdb, max_width, max_height).await
}
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
self.inner.draw(frame).await?;
frame.set_title(Some(self.title));
Ok(())
}
}

View file

@ -6,129 +6,51 @@ use crossterm::style::Print;
use crossterm::terminal::{Clear, ClearType};
use crossterm::QueueableCommand;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::wrap;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum WidthEstimationMethod {
/// Estimate the width of a grapheme using legacy methods.
///
/// Different terminal emulators all use different approaches to determine
/// grapheme widths, so this method will never be able to give a fully
/// correct solution. For that, the only possible approach is measuring the
/// actual grapheme width.
#[default]
Legacy,
/// Estimate the width of a grapheme using the unicode standard in a
/// best-effort manner.
Unicode,
}
use unicode_width::UnicodeWidthStr;
/// Measures and stores the with (in terminal coordinates) of graphemes.
#[derive(Debug)]
pub struct WidthDb {
pub(crate) estimate: WidthEstimationMethod,
pub(crate) measure: bool,
pub(crate) tab_width: u8,
#[derive(Debug, Default)]
pub struct WidthDB {
known: HashMap<String, u8>,
requested: HashSet<String>,
}
impl Default for WidthDb {
fn default() -> Self {
Self {
estimate: WidthEstimationMethod::default(),
measure: false,
tab_width: 8,
known: Default::default(),
requested: Default::default(),
}
}
}
impl WidthDb {
/// Determine the width of a tab character starting at the specified column.
fn tab_width_at_column(&self, col: usize) -> u8 {
self.tab_width - (col % self.tab_width as usize) as u8
}
impl WidthDB {
/// Determine the width of a grapheme.
///
/// If the grapheme is a tab, the column is used to determine its width.
///
/// If the width has not been measured yet or measurements are turned off,
/// it is estimated using the Unicode Standard Annex #11.
pub fn grapheme_width(&mut self, grapheme: &str, col: usize) -> u8 {
/// If the width has not been measured yet, it is estimated using the
/// Unicode Standard Annex #11.
pub fn grapheme_width(&mut self, grapheme: &str) -> u8 {
assert_eq!(Some(grapheme), grapheme.graphemes(true).next());
if grapheme == "\t" {
return self.tab_width_at_column(col);
}
if self.measure {
if let Some(width) = self.known.get(grapheme) {
return *width;
}
if let Some(width) = self.known.get(grapheme) {
*width
} else {
self.requested.insert(grapheme.to_string());
}
match self.estimate {
// A character-wise width calculation is a simple and obvious
// approach to compute character widths. The idea is that dumb
// terminal emulators tend to do something roughly like this, and
// smart terminal emulators try to emulate dumb ones for
// compatibility. In practice, this approach seems to be fairly
// robust.
WidthEstimationMethod::Legacy => grapheme
.chars()
.filter(|c| !c.is_ascii_control())
.flat_map(|c| c.width())
.sum::<usize>()
.try_into()
.unwrap_or(u8::MAX),
// The unicode width crate considers control chars to have a width
// of 1 even though they usually have a width of 0 when displayed.
WidthEstimationMethod::Unicode => grapheme
.split(|c: char| c.is_ascii_control())
.map(|s| s.width())
.sum::<usize>()
.try_into()
.unwrap_or(u8::MAX),
grapheme.width() as u8
}
}
/// Determine the width of a string based on its graphemes.
///
/// If a grapheme is a tab, its column is used to determine its width.
///
/// If the width of a grapheme has not been measured yet or measurements are
/// turned off, it is estimated using the Unicode Standard Annex #11.
/// If the width of a grapheme has not been measured yet, it is estimated
/// using the Unicode Standard Annex #11.
pub fn width(&mut self, s: &str) -> usize {
let mut total: usize = 0;
for grapheme in s.graphemes(true) {
total += self.grapheme_width(grapheme, total) as usize;
total += if let Some(width) = self.known.get(grapheme) {
(*width).into()
} else {
self.requested.insert(grapheme.to_string());
grapheme.width()
};
}
total
}
/// Perform primitive word wrapping with the specified maximum width.
///
/// Returns the byte offsets at which the string should be split into lines.
/// An offset of 1 would mean the first line contains only a single byte.
/// These offsets lie on grapheme boundaries.
///
/// This function does not support bidirectional script. It assumes the
/// entire text has the same direction.
pub fn wrap(&mut self, text: &str, width: usize) -> Vec<usize> {
wrap::wrap(self, text, width)
}
/// Whether any new graphemes have been seen since the last time
/// [`Self::measure_widths`] was called.
pub(crate) fn measuring_required(&self) -> bool {
self.measure && !self.requested.is_empty()
pub fn measuring_required(&self) -> bool {
!self.requested.is_empty()
}
/// Measure the width of all new graphemes that have been seen since the
@ -137,20 +59,8 @@ impl WidthDb {
/// This function measures the actual width of graphemes by writing them to
/// the terminal. After it finishes, the terminal's contents should be
/// assumed to be garbage and a full redraw should be performed.
pub(crate) fn measure_widths(&mut self, out: &mut impl Write) -> io::Result<()> {
if !self.measure {
return Ok(());
}
pub fn measure_widths(&mut self, out: &mut impl Write) -> io::Result<()> {
for grapheme in self.requested.drain() {
if grapheme.chars().any(|c| c.is_ascii_control()) {
// ASCII control characters like the escape character or the
// bell character tend to be interpreted specially by terminals.
// This may break width measurements. To avoid this, we just
// assign each control character a with of 0.
self.known.insert(grapheme, 0);
continue;
}
out.queue(Clear(ClearType::All))?
.queue(MoveTo(0, 0))?
.queue(Print(&grapheme))?;

View file

@ -3,21 +3,21 @@
use unicode_linebreak::BreakOpportunity;
use unicode_segmentation::UnicodeSegmentation;
use crate::WidthDb;
use crate::widthdb::WidthDB;
pub fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec<usize> {
// TODO Handle tabs separately?
// TODO Convert into an iterator?
pub fn wrap(text: &str, width: usize, widthdb: &mut WidthDB) -> Vec<usize> {
let mut breaks = vec![];
let mut break_options = unicode_linebreak::linebreaks(text).peekable();
// The last valid break point encountered and its width
let mut valid_break = None;
let mut valid_break_width = 0;
// Starting index and width of the line at the current grapheme (with and
// without trailing whitespace)
let mut current_start = 0;
// Width of the line at the current grapheme
let mut current_width = 0;
let mut current_width_trimmed = 0;
for (gi, g) in text.grapheme_indices(true) {
// Advance break options
@ -36,56 +36,60 @@ pub fn wrap(widthdb: &mut WidthDb, text: &str, width: usize) -> Vec<usize> {
BreakOpportunity::Mandatory => {
breaks.push(bi);
valid_break = None;
current_start = bi;
valid_break_width = 0;
current_width = 0;
current_width_trimmed = 0;
}
BreakOpportunity::Allowed => {
valid_break = Some(bi);
valid_break_width = current_width;
}
}
}
// Calculate widths after current grapheme
let g_is_whitespace = g.chars().all(|c| c.is_whitespace());
let g_width = widthdb.grapheme_width(g, current_width) as usize;
current_width += g_width;
if !g_is_whitespace {
current_width_trimmed = current_width;
}
// Wrap at last break point if necessary
if current_width_trimmed > width {
if let Some(bi) = valid_break {
let new_line = &text[bi..gi + g.len()];
let grapheme_width: usize = widthdb.grapheme_width(g).into();
if current_width + grapheme_width > width {
if current_width == 0 {
// The grapheme is wider than the maximum width, so we'll allow
// it, thereby forcing the following grapheme to break no matter
// what (either because of a mandatory or allowed break, or via
// a forced break).
} else if let Some(bi) = valid_break {
// We can't fit the grapheme onto the current line, so we'll
// just break at the last valid break point.
breaks.push(bi);
current_width -= valid_break_width;
valid_break = None;
current_start = bi;
current_width = widthdb.width(new_line);
current_width_trimmed = widthdb.width(new_line.trim_end());
}
}
// Perform a forced break if still necessary
if current_width_trimmed > width {
if current_start == gi {
// The grapheme is the only thing on the current line and it is
// wider than the maximum width, so we'll allow it, thereby
// forcing the following grapheme to break no matter what
// (either because of a mandatory or allowed break, or via a
// forced break).
valid_break_width = 0;
} else {
// Forced break in the middle of a normally non-breakable chunk
// because there are no valid break points.
// Forced break in the midde of a normally non-breakable chunk
// because there have been no valid break points yet.
breaks.push(gi);
valid_break = None;
current_start = gi;
current_width = widthdb.grapheme_width(g, 0).into();
current_width_trimmed = if g_is_whitespace { 0 } else { current_width };
valid_break_width = 0;
current_width = 0;
}
}
current_width += grapheme_width;
}
breaks
}
pub fn split_at_indices<'a>(s: &'a str, indices: &[usize]) -> Vec<&'a str> {
let mut slices = vec![];
let mut rest = s;
let mut offset = 0;
for i in indices {
let (left, right) = rest.split_at(i - offset);
slices.push(left);
rest = right;
offset = *i;
}
slices.push(rest);
slices
}

2
widths-alacritty Normal file
View file

@ -0,0 +1,2 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`

5118
widths-cool-retro-term Normal file

File diff suppressed because it is too large Load diff

82
widths-gnome-terminal Normal file
View file

@ -0,0 +1,82 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
55216: actual 0, expected 1
55217: actual 0, expected 1
55218: actual 0, expected 1
55219: actual 0, expected 1
55220: actual 0, expected 1
55221: actual 0, expected 1
55222: actual 0, expected 1
55223: actual 0, expected 1
55224: actual 0, expected 1
55225: actual 0, expected 1
55226: actual 0, expected 1
55227: actual 0, expected 1
55228: actual 0, expected 1
55229: actual 0, expected 1
55230: actual 0, expected 1
55231: actual 0, expected 1
55232: actual 0, expected 1
55233: actual 0, expected 1
55234: actual 0, expected 1
55235: actual 0, expected 1
55236: actual 0, expected 1
55237: actual 0, expected 1
55238: actual 0, expected 1
55239: actual 0, expected 1
55240: actual 0, expected 1
55241: actual 0, expected 1
55242: actual 0, expected 1
55243: actual 0, expected 1
55244: actual 0, expected 1
55245: actual 0, expected 1
55246: actual 0, expected 1
55247: actual 0, expected 1
55248: actual 0, expected 1
55249: actual 0, expected 1
55250: actual 0, expected 1
55251: actual 0, expected 1
55252: actual 0, expected 1
55253: actual 0, expected 1
55254: actual 0, expected 1
55255: actual 0, expected 1
55256: actual 0, expected 1
55257: actual 0, expected 1
55258: actual 0, expected 1
55259: actual 0, expected 1
55260: actual 0, expected 1
55261: actual 0, expected 1
55262: actual 0, expected 1
55263: actual 0, expected 1
55264: actual 0, expected 1
55265: actual 0, expected 1
55266: actual 0, expected 1
55267: actual 0, expected 1
55268: actual 0, expected 1
55269: actual 0, expected 1
55270: actual 0, expected 1
55271: actual 0, expected 1
55272: actual 0, expected 1
55273: actual 0, expected 1
55274: actual 0, expected 1
55275: actual 0, expected 1
55276: actual 0, expected 1
55277: actual 0, expected 1
55278: actual 0, expected 1
55279: actual 0, expected 1
55280: actual 0, expected 1
55281: actual 0, expected 1
55282: actual 0, expected 1
55283: actual 0, expected 1
55284: actual 0, expected 1
55285: actual 0, expected 1
55286: actual 0, expected 1
55287: actual 0, expected 1
55288: actual 0, expected 1
55289: actual 0, expected 1
55290: actual 0, expected 1
55291: actual 0, expected 1
55292: actual 0, expected 1
55293: actual 0, expected 1
55294: actual 0, expected 1
55295: actual 0, expected 1

2896
widths-hyper Normal file

File diff suppressed because it is too large Load diff

726
widths-kitty Normal file
View file

@ -0,0 +1,726 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
71456: actual 0, expected 1
71457: actual 0, expected 1
71462: actual 0, expected 1
64976: actual 0, expected 1
64977: actual 0, expected 1
64978: actual 0, expected 1
64979: actual 0, expected 1
64980: actual 0, expected 1
64981: actual 0, expected 1
64982: actual 0, expected 1
64983: actual 0, expected 1
64984: actual 0, expected 1
64985: actual 0, expected 1
64986: actual 0, expected 1
64987: actual 0, expected 1
64988: actual 0, expected 1
64989: actual 0, expected 1
64990: actual 0, expected 1
64991: actual 0, expected 1
64992: actual 0, expected 1
64993: actual 0, expected 1
64994: actual 0, expected 1
64995: actual 0, expected 1
64996: actual 0, expected 1
64997: actual 0, expected 1
64998: actual 0, expected 1
64999: actual 0, expected 1
65000: actual 0, expected 1
65001: actual 0, expected 1
65002: actual 0, expected 1
65003: actual 0, expected 1
65004: actual 0, expected 1
65005: actual 0, expected 1
65006: actual 0, expected 1
65007: actual 0, expected 1
6916: actual 0, expected 1
6965: actual 0, expected 1
6971: actual 0, expected 1
6973: actual 0, expected 1
6974: actual 0, expected 1
6975: actual 0, expected 1
6976: actual 0, expected 1
6977: actual 0, expected 1
6979: actual 0, expected 1
6980: actual 0, expected 1
7143: actual 0, expected 1
7146: actual 0, expected 1
7147: actual 0, expected 1
7148: actual 0, expected 1
7150: actual 0, expected 1
7154: actual 0, expected 1
7155: actual 0, expected 1
2434: actual 0, expected 1
2435: actual 0, expected 1
2494: actual 0, expected 1
2495: actual 0, expected 1
2496: actual 0, expected 1
2503: actual 0, expected 1
2504: actual 0, expected 1
2507: actual 0, expected 1
2508: actual 0, expected 1
2519: actual 0, expected 1
72751: actual 0, expected 1
72766: actual 0, expected 1
69632: actual 0, expected 1
69634: actual 0, expected 1
6681: actual 0, expected 1
6682: actual 0, expected 1
69932: actual 0, expected 1
69957: actual 0, expected 1
69958: actual 0, expected 1
43567: actual 0, expected 1
43568: actual 0, expected 1
43571: actual 0, expected 1
43572: actual 0, expected 1
43597: actual 0, expected 1
12334: actual 0, expected 2
12335: actual 0, expected 2
12336: actual 1, expected 2
12349: actual 1, expected 2
2307: actual 0, expected 1
2363: actual 0, expected 1
2366: actual 0, expected 1
2367: actual 0, expected 1
2368: actual 0, expected 1
2377: actual 0, expected 1
2378: actual 0, expected 1
2379: actual 0, expected 1
2380: actual 0, expected 1
2382: actual 0, expected 1
2383: actual 0, expected 1
71984: actual 0, expected 1
71985: actual 0, expected 1
71986: actual 0, expected 1
71987: actual 0, expected 1
71988: actual 0, expected 1
71989: actual 0, expected 1
71991: actual 0, expected 1
71992: actual 0, expected 1
71997: actual 0, expected 1
72000: actual 0, expected 1
72002: actual 0, expected 1
71724: actual 0, expected 1
71725: actual 0, expected 1
71726: actual 0, expected 1
71736: actual 0, expected 1
127462: actual 2, expected 1
127463: actual 2, expected 1
127464: actual 2, expected 1
127465: actual 2, expected 1
127466: actual 2, expected 1
127467: actual 2, expected 1
127468: actual 2, expected 1
127469: actual 2, expected 1
127470: actual 2, expected 1
127471: actual 2, expected 1
127472: actual 2, expected 1
127473: actual 2, expected 1
127474: actual 2, expected 1
127475: actual 2, expected 1
127476: actual 2, expected 1
127477: actual 2, expected 1
127478: actual 2, expected 1
127479: actual 2, expected 1
127480: actual 2, expected 1
127481: actual 2, expected 1
127482: actual 2, expected 1
127483: actual 2, expected 1
127484: actual 2, expected 1
127485: actual 2, expected 1
127486: actual 2, expected 1
127487: actual 2, expected 1
12951: actual 1, expected 2
12953: actual 1, expected 2
127490: actual 1, expected 2
127543: actual 1, expected 2
8293: actual 0, expected 1
70402: actual 0, expected 1
70403: actual 0, expected 1
70462: actual 0, expected 1
70463: actual 0, expected 1
70465: actual 0, expected 1
70466: actual 0, expected 1
70467: actual 0, expected 1
70468: actual 0, expected 1
70471: actual 0, expected 1
70472: actual 0, expected 1
70475: actual 0, expected 1
70476: actual 0, expected 1
70477: actual 0, expected 1
70487: actual 0, expected 1
70498: actual 0, expected 1
70499: actual 0, expected 1
2691: actual 0, expected 1
2750: actual 0, expected 1
2751: actual 0, expected 1
2752: actual 0, expected 1
2761: actual 0, expected 1
2763: actual 0, expected 1
2764: actual 0, expected 1
73098: actual 0, expected 1
73099: actual 0, expected 1
73100: actual 0, expected 1
73101: actual 0, expected 1
73102: actual 0, expected 1
73107: actual 0, expected 1
73108: actual 0, expected 1
73110: actual 0, expected 1
2563: actual 0, expected 1
2622: actual 0, expected 1
2623: actual 0, expected 1
2624: actual 0, expected 1
65440: actual 0, expected 1
12644: actual 0, expected 2
4447: actual 0, expected 2
4449: actual 1, expected 0
4450: actual 1, expected 0
4451: actual 1, expected 0
4452: actual 1, expected 0
4453: actual 1, expected 0
4454: actual 1, expected 0
4455: actual 1, expected 0
4456: actual 1, expected 0
4457: actual 1, expected 0
4458: actual 1, expected 0
4459: actual 1, expected 0
4460: actual 1, expected 0
4461: actual 1, expected 0
4462: actual 1, expected 0
4463: actual 1, expected 0
4464: actual 1, expected 0
4465: actual 1, expected 0
4466: actual 1, expected 0
4467: actual 1, expected 0
4468: actual 1, expected 0
4469: actual 1, expected 0
4470: actual 1, expected 0
4471: actual 1, expected 0
4472: actual 1, expected 0
4473: actual 1, expected 0
4474: actual 1, expected 0
4475: actual 1, expected 0
4476: actual 1, expected 0
4477: actual 1, expected 0
4478: actual 1, expected 0
4479: actual 1, expected 0
4480: actual 1, expected 0
4481: actual 1, expected 0
4482: actual 1, expected 0
4483: actual 1, expected 0
4484: actual 1, expected 0
4485: actual 1, expected 0
4486: actual 1, expected 0
4487: actual 1, expected 0
4488: actual 1, expected 0
4489: actual 1, expected 0
4490: actual 1, expected 0
4491: actual 1, expected 0
4492: actual 1, expected 0
4493: actual 1, expected 0
4494: actual 1, expected 0
4495: actual 1, expected 0
4496: actual 1, expected 0
4497: actual 1, expected 0
4498: actual 1, expected 0
4499: actual 1, expected 0
4500: actual 1, expected 0
4501: actual 1, expected 0
4502: actual 1, expected 0
4503: actual 1, expected 0
4504: actual 1, expected 0
4505: actual 1, expected 0
4506: actual 1, expected 0
4507: actual 1, expected 0
4508: actual 1, expected 0
4509: actual 1, expected 0
4510: actual 1, expected 0
4511: actual 1, expected 0
4512: actual 1, expected 0
4513: actual 1, expected 0
4514: actual 1, expected 0
4515: actual 1, expected 0
4516: actual 1, expected 0
4517: actual 1, expected 0
4518: actual 1, expected 0
4519: actual 1, expected 0
4520: actual 1, expected 0
4521: actual 1, expected 0
4522: actual 1, expected 0
4523: actual 1, expected 0
4524: actual 1, expected 0
4525: actual 1, expected 0
4526: actual 1, expected 0
4527: actual 1, expected 0
4528: actual 1, expected 0
4529: actual 1, expected 0
4530: actual 1, expected 0
4531: actual 1, expected 0
4532: actual 1, expected 0
4533: actual 1, expected 0
4534: actual 1, expected 0
4535: actual 1, expected 0
4536: actual 1, expected 0
4537: actual 1, expected 0
4538: actual 1, expected 0
4539: actual 1, expected 0
4540: actual 1, expected 0
4541: actual 1, expected 0
4542: actual 1, expected 0
4543: actual 1, expected 0
4544: actual 1, expected 0
4545: actual 1, expected 0
4546: actual 1, expected 0
4547: actual 1, expected 0
4548: actual 1, expected 0
4549: actual 1, expected 0
4550: actual 1, expected 0
4551: actual 1, expected 0
4552: actual 1, expected 0
4553: actual 1, expected 0
4554: actual 1, expected 0
4555: actual 1, expected 0
4556: actual 1, expected 0
4557: actual 1, expected 0
4558: actual 1, expected 0
4559: actual 1, expected 0
4560: actual 1, expected 0
4561: actual 1, expected 0
4562: actual 1, expected 0
4563: actual 1, expected 0
4564: actual 1, expected 0
4565: actual 1, expected 0
4566: actual 1, expected 0
4567: actual 1, expected 0
4568: actual 1, expected 0
4569: actual 1, expected 0
4570: actual 1, expected 0
4571: actual 1, expected 0
4572: actual 1, expected 0
4573: actual 1, expected 0
4574: actual 1, expected 0
4575: actual 1, expected 0
4576: actual 1, expected 0
4577: actual 1, expected 0
4578: actual 1, expected 0
4579: actual 1, expected 0
4580: actual 1, expected 0
4581: actual 1, expected 0
4582: actual 1, expected 0
4583: actual 1, expected 0
4584: actual 1, expected 0
4585: actual 1, expected 0
4586: actual 1, expected 0
4587: actual 1, expected 0
4588: actual 1, expected 0
4589: actual 1, expected 0
4590: actual 1, expected 0
4591: actual 1, expected 0
4592: actual 1, expected 0
4593: actual 1, expected 0
4594: actual 1, expected 0
4595: actual 1, expected 0
4596: actual 1, expected 0
4597: actual 1, expected 0
4598: actual 1, expected 0
4599: actual 1, expected 0
4600: actual 1, expected 0
4601: actual 1, expected 0
4602: actual 1, expected 0
4603: actual 1, expected 0
4604: actual 1, expected 0
4605: actual 1, expected 0
4606: actual 1, expected 0
4607: actual 1, expected 0
5940: actual 0, expected 1
94192: actual 0, expected 2
94193: actual 0, expected 2
43395: actual 0, expected 1
43444: actual 0, expected 1
43445: actual 0, expected 1
43450: actual 0, expected 1
43451: actual 0, expected 1
43454: actual 0, expected 1
43455: actual 0, expected 1
43456: actual 0, expected 1
69762: actual 0, expected 1
69808: actual 0, expected 1
69809: actual 0, expected 1
69810: actual 0, expected 1
69815: actual 0, expected 1
69816: actual 0, expected 1
3202: actual 0, expected 1
3203: actual 0, expected 1
3262: actual 0, expected 1
3264: actual 0, expected 1
3265: actual 0, expected 1
3266: actual 0, expected 1
3267: actual 0, expected 1
3268: actual 0, expected 1
3271: actual 0, expected 1
3272: actual 0, expected 1
3274: actual 0, expected 1
3275: actual 0, expected 1
3285: actual 0, expected 1
3286: actual 0, expected 1
6070: actual 0, expected 1
6078: actual 0, expected 1
6079: actual 0, expected 1
6080: actual 0, expected 1
6081: actual 0, expected 1
6082: actual 0, expected 1
6083: actual 0, expected 1
6084: actual 0, expected 1
6085: actual 0, expected 1
6087: actual 0, expected 1
6088: actual 0, expected 1
70188: actual 0, expected 1
70189: actual 0, expected 1
70190: actual 0, expected 1
70194: actual 0, expected 1
70195: actual 0, expected 1
70197: actual 0, expected 1
70368: actual 0, expected 1
70369: actual 0, expected 1
70370: actual 0, expected 1
173: actual 0, expected 1
7204: actual 0, expected 1
7205: actual 0, expected 1
7206: actual 0, expected 1
7207: actual 0, expected 1
7208: actual 0, expected 1
7209: actual 0, expected 1
7210: actual 0, expected 1
7211: actual 0, expected 1
7220: actual 0, expected 1
7221: actual 0, expected 1
6435: actual 0, expected 1
6436: actual 0, expected 1
6437: actual 0, expected 1
6438: actual 0, expected 1
6441: actual 0, expected 1
6442: actual 0, expected 1
6443: actual 0, expected 1
6448: actual 0, expected 1
6449: actual 0, expected 1
6451: actual 0, expected 1
6452: actual 0, expected 1
6453: actual 0, expected 1
6454: actual 0, expected 1
6455: actual 0, expected 1
6456: actual 0, expected 1
73461: actual 0, expected 1
73462: actual 0, expected 1
3330: actual 0, expected 1
3331: actual 0, expected 1
3390: actual 0, expected 1
3391: actual 0, expected 1
3392: actual 0, expected 1
3398: actual 0, expected 1
3399: actual 0, expected 1
3400: actual 0, expected 1
3402: actual 0, expected 1
3403: actual 0, expected 1
3404: actual 0, expected 1
3415: actual 0, expected 1
72873: actual 0, expected 1
72881: actual 0, expected 1
72884: actual 0, expected 1
44003: actual 0, expected 1
44004: actual 0, expected 1
44006: actual 0, expected 1
44007: actual 0, expected 1
44009: actual 0, expected 1
44010: actual 0, expected 1
44012: actual 0, expected 1
43755: actual 0, expected 1
43758: actual 0, expected 1
43759: actual 0, expected 1
43765: actual 0, expected 1
94033: actual 0, expected 1
94034: actual 0, expected 1
94035: actual 0, expected 1
94036: actual 0, expected 1
94037: actual 0, expected 1
94038: actual 0, expected 1
94039: actual 0, expected 1
94040: actual 0, expected 1
94041: actual 0, expected 1
94042: actual 0, expected 1
94043: actual 0, expected 1
94044: actual 0, expected 1
94045: actual 0, expected 1
94046: actual 0, expected 1
94047: actual 0, expected 1
94048: actual 0, expected 1
94049: actual 0, expected 1
94050: actual 0, expected 1
94051: actual 0, expected 1
94052: actual 0, expected 1
94053: actual 0, expected 1
94054: actual 0, expected 1
94055: actual 0, expected 1
94056: actual 0, expected 1
94057: actual 0, expected 1
94058: actual 0, expected 1
94059: actual 0, expected 1
94060: actual 0, expected 1
94061: actual 0, expected 1
94062: actual 0, expected 1
94063: actual 0, expected 1
94064: actual 0, expected 1
94065: actual 0, expected 1
94066: actual 0, expected 1
94067: actual 0, expected 1
94068: actual 0, expected 1
94069: actual 0, expected 1
94070: actual 0, expected 1
94071: actual 0, expected 1
94072: actual 0, expected 1
94073: actual 0, expected 1
94074: actual 0, expected 1
94075: actual 0, expected 1
94076: actual 0, expected 1
94077: actual 0, expected 1
94078: actual 0, expected 1
94079: actual 0, expected 1
94080: actual 0, expected 1
94081: actual 0, expected 1
94082: actual 0, expected 1
94083: actual 0, expected 1
94084: actual 0, expected 1
94085: actual 0, expected 1
94086: actual 0, expected 1
94087: actual 0, expected 1
127995: actual 0, expected 2
127996: actual 0, expected 2
127997: actual 0, expected 2
127998: actual 0, expected 2
127999: actual 0, expected 2
71216: actual 0, expected 1
71217: actual 0, expected 1
71218: actual 0, expected 1
71227: actual 0, expected 1
71228: actual 0, expected 1
71230: actual 0, expected 1
119141: actual 0, expected 1
119142: actual 0, expected 1
119149: actual 0, expected 1
119150: actual 0, expected 1
119151: actual 0, expected 1
119152: actual 0, expected 1
119153: actual 0, expected 1
119154: actual 0, expected 1
4139: actual 0, expected 1
4140: actual 0, expected 1
4145: actual 0, expected 1
4152: actual 0, expected 1
4155: actual 0, expected 1
4156: actual 0, expected 1
4182: actual 0, expected 1
4183: actual 0, expected 1
4194: actual 0, expected 1
4195: actual 0, expected 1
4196: actual 0, expected 1
4199: actual 0, expected 1
4200: actual 0, expected 1
4201: actual 0, expected 1
4202: actual 0, expected 1
4203: actual 0, expected 1
4204: actual 0, expected 1
4205: actual 0, expected 1
4227: actual 0, expected 1
4228: actual 0, expected 1
4231: actual 0, expected 1
4232: actual 0, expected 1
4233: actual 0, expected 1
4234: actual 0, expected 1
4235: actual 0, expected 1
4236: actual 0, expected 1
4239: actual 0, expected 1
4250: actual 0, expected 1
4251: actual 0, expected 1
4252: actual 0, expected 1
43643: actual 0, expected 1
43645: actual 0, expected 1
72145: actual 0, expected 1
72146: actual 0, expected 1
72147: actual 0, expected 1
72156: actual 0, expected 1
72157: actual 0, expected 1
72158: actual 0, expected 1
72159: actual 0, expected 1
72164: actual 0, expected 1
70709: actual 0, expected 1
70710: actual 0, expected 1
70711: actual 0, expected 1
70720: actual 0, expected 1
70721: actual 0, expected 1
70725: actual 0, expected 1
2818: actual 0, expected 1
2819: actual 0, expected 1
2878: actual 0, expected 1
2880: actual 0, expected 1
2887: actual 0, expected 1
2888: actual 0, expected 1
2891: actual 0, expected 1
2892: actual 0, expected 1
2903: actual 0, expected 1
43346: actual 0, expected 1
43347: actual 0, expected 1
43136: actual 0, expected 1
43137: actual 0, expected 1
43188: actual 0, expected 1
43189: actual 0, expected 1
43190: actual 0, expected 1
43191: actual 0, expected 1
43192: actual 0, expected 1
43193: actual 0, expected 1
43194: actual 0, expected 1
43195: actual 0, expected 1
43196: actual 0, expected 1
43197: actual 0, expected 1
43198: actual 0, expected 1
43199: actual 0, expected 1
43200: actual 0, expected 1
43201: actual 0, expected 1
43202: actual 0, expected 1
43203: actual 0, expected 1
70018: actual 0, expected 1
70067: actual 0, expected 1
70068: actual 0, expected 1
70069: actual 0, expected 1
70079: actual 0, expected 1
70080: actual 0, expected 1
70094: actual 0, expected 1
71087: actual 0, expected 1
71088: actual 0, expected 1
71089: actual 0, expected 1
71096: actual 0, expected 1
71097: actual 0, expected 1
71098: actual 0, expected 1
71099: actual 0, expected 1
71102: actual 0, expected 1
3458: actual 0, expected 1
3459: actual 0, expected 1
3535: actual 0, expected 1
3536: actual 0, expected 1
3537: actual 0, expected 1
3544: actual 0, expected 1
3545: actual 0, expected 1
3546: actual 0, expected 1
3547: actual 0, expected 1
3548: actual 0, expected 1
3549: actual 0, expected 1
3550: actual 0, expected 1
3551: actual 0, expected 1
3570: actual 0, expected 1
3571: actual 0, expected 1
72279: actual 0, expected 1
72280: actual 0, expected 1
72343: actual 0, expected 1
65520: actual 0, expected 1
65521: actual 0, expected 1
65522: actual 0, expected 1
65523: actual 0, expected 1
65524: actual 0, expected 1
65525: actual 0, expected 1
65526: actual 0, expected 1
65527: actual 0, expected 1
65528: actual 0, expected 1
65534: actual 0, expected 1
65535: actual 0, expected 1
7042: actual 0, expected 1
7073: actual 0, expected 1
7078: actual 0, expected 1
7079: actual 0, expected 1
7082: actual 0, expected 1
1048574: actual 0, expected 1
1048575: actual 0, expected 1
1114110: actual 0, expected 1
1114111: actual 0, expected 1
43043: actual 0, expected 1
43044: actual 0, expected 1
43047: actual 0, expected 1
5909: actual 0, expected 1
917504: actual 0, expected 1
917506: actual 0, expected 1
917507: actual 0, expected 1
917508: actual 0, expected 1
917509: actual 0, expected 1
917510: actual 0, expected 1
917511: actual 0, expected 1
917512: actual 0, expected 1
917513: actual 0, expected 1
917514: actual 0, expected 1
917515: actual 0, expected 1
917516: actual 0, expected 1
917517: actual 0, expected 1
917518: actual 0, expected 1
917519: actual 0, expected 1
917520: actual 0, expected 1
917521: actual 0, expected 1
917522: actual 0, expected 1
917523: actual 0, expected 1
917524: actual 0, expected 1
917525: actual 0, expected 1
917526: actual 0, expected 1
917527: actual 0, expected 1
917528: actual 0, expected 1
917529: actual 0, expected 1
917530: actual 0, expected 1
917531: actual 0, expected 1
917532: actual 0, expected 1
917533: actual 0, expected 1
917534: actual 0, expected 1
917535: actual 0, expected 1
6741: actual 0, expected 1
6743: actual 0, expected 1
6753: actual 0, expected 1
6755: actual 0, expected 1
6756: actual 0, expected 1
6765: actual 0, expected 1
6766: actual 0, expected 1
6767: actual 0, expected 1
6768: actual 0, expected 1
6769: actual 0, expected 1
6770: actual 0, expected 1
71340: actual 0, expected 1
71342: actual 0, expected 1
71343: actual 0, expected 1
71350: actual 0, expected 1
3006: actual 0, expected 1
3007: actual 0, expected 1
3009: actual 0, expected 1
3010: actual 0, expected 1
3014: actual 0, expected 1
3015: actual 0, expected 1
3016: actual 0, expected 1
3018: actual 0, expected 1
3019: actual 0, expected 1
3020: actual 0, expected 1
3031: actual 0, expected 1
3073: actual 0, expected 1
3074: actual 0, expected 1
3075: actual 0, expected 1
3137: actual 0, expected 1
3138: actual 0, expected 1
3139: actual 0, expected 1
3140: actual 0, expected 1
3902: actual 0, expected 1
3903: actual 0, expected 1
3967: actual 0, expected 1
70832: actual 0, expected 1
70833: actual 0, expected 1
70834: actual 0, expected 1
70841: actual 0, expected 1
70843: actual 0, expected 1
70844: actual 0, expected 1
70845: actual 0, expected 1
70846: actual 0, expected 1
70849: actual 0, expected 1
7393: actual 0, expected 1
7415: actual 0, expected 1
72249: actual 0, expected 1

260
widths-konsole Normal file
View file

@ -0,0 +1,260 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
127462: actual 2, expected 1
127463: actual 2, expected 1
127464: actual 2, expected 1
127465: actual 2, expected 1
127466: actual 2, expected 1
127467: actual 2, expected 1
127468: actual 2, expected 1
127469: actual 2, expected 1
127470: actual 2, expected 1
127471: actual 2, expected 1
127472: actual 2, expected 1
127473: actual 2, expected 1
127474: actual 2, expected 1
127475: actual 2, expected 1
127476: actual 2, expected 1
127477: actual 2, expected 1
127478: actual 2, expected 1
127479: actual 2, expected 1
127480: actual 2, expected 1
127481: actual 2, expected 1
127482: actual 2, expected 1
127483: actual 2, expected 1
127484: actual 2, expected 1
127485: actual 2, expected 1
127486: actual 2, expected 1
127487: actual 2, expected 1
4448: actual 2, expected 0
4449: actual 2, expected 0
4450: actual 2, expected 0
4451: actual 2, expected 0
4452: actual 2, expected 0
4453: actual 2, expected 0
4454: actual 2, expected 0
4455: actual 2, expected 0
4456: actual 2, expected 0
4457: actual 2, expected 0
4458: actual 2, expected 0
4459: actual 2, expected 0
4460: actual 2, expected 0
4461: actual 2, expected 0
4462: actual 2, expected 0
4463: actual 2, expected 0
4464: actual 2, expected 0
4465: actual 2, expected 0
4466: actual 2, expected 0
4467: actual 2, expected 0
4468: actual 2, expected 0
4469: actual 2, expected 0
4470: actual 2, expected 0
4471: actual 2, expected 0
4472: actual 2, expected 0
4473: actual 2, expected 0
4474: actual 2, expected 0
4475: actual 2, expected 0
4476: actual 2, expected 0
4477: actual 2, expected 0
4478: actual 2, expected 0
4479: actual 2, expected 0
4480: actual 2, expected 0
4481: actual 2, expected 0
4482: actual 2, expected 0
4483: actual 2, expected 0
4484: actual 2, expected 0
4485: actual 2, expected 0
4486: actual 2, expected 0
4487: actual 2, expected 0
4488: actual 2, expected 0
4489: actual 2, expected 0
4490: actual 2, expected 0
4491: actual 2, expected 0
4492: actual 2, expected 0
4493: actual 2, expected 0
4494: actual 2, expected 0
4495: actual 2, expected 0
4496: actual 2, expected 0
4497: actual 2, expected 0
4498: actual 2, expected 0
4499: actual 2, expected 0
4500: actual 2, expected 0
4501: actual 2, expected 0
4502: actual 2, expected 0
4503: actual 2, expected 0
4504: actual 2, expected 0
4505: actual 2, expected 0
4506: actual 2, expected 0
4507: actual 2, expected 0
4508: actual 2, expected 0
4509: actual 2, expected 0
4510: actual 2, expected 0
4511: actual 2, expected 0
4512: actual 2, expected 0
4513: actual 2, expected 0
4514: actual 2, expected 0
4515: actual 2, expected 0
4516: actual 2, expected 0
4517: actual 2, expected 0
4518: actual 2, expected 0
4519: actual 2, expected 0
4520: actual 2, expected 0
4521: actual 2, expected 0
4522: actual 2, expected 0
4523: actual 2, expected 0
4524: actual 2, expected 0
4525: actual 2, expected 0
4526: actual 2, expected 0
4527: actual 2, expected 0
4528: actual 2, expected 0
4529: actual 2, expected 0
4530: actual 2, expected 0
4531: actual 2, expected 0
4532: actual 2, expected 0
4533: actual 2, expected 0
4534: actual 2, expected 0
4535: actual 2, expected 0
4536: actual 2, expected 0
4537: actual 2, expected 0
4538: actual 2, expected 0
4539: actual 2, expected 0
4540: actual 2, expected 0
4541: actual 2, expected 0
4542: actual 2, expected 0
4543: actual 2, expected 0
4544: actual 2, expected 0
4545: actual 2, expected 0
4546: actual 2, expected 0
4547: actual 2, expected 0
4548: actual 2, expected 0
4549: actual 2, expected 0
4550: actual 2, expected 0
4551: actual 2, expected 0
4552: actual 2, expected 0
4553: actual 2, expected 0
4554: actual 2, expected 0
4555: actual 2, expected 0
4556: actual 2, expected 0
4557: actual 2, expected 0
4558: actual 2, expected 0
4559: actual 2, expected 0
4560: actual 2, expected 0
4561: actual 2, expected 0
4562: actual 2, expected 0
4563: actual 2, expected 0
4564: actual 2, expected 0
4565: actual 2, expected 0
4566: actual 2, expected 0
4567: actual 2, expected 0
4568: actual 2, expected 0
4569: actual 2, expected 0
4570: actual 2, expected 0
4571: actual 2, expected 0
4572: actual 2, expected 0
4573: actual 2, expected 0
4574: actual 2, expected 0
4575: actual 2, expected 0
4576: actual 2, expected 0
4577: actual 2, expected 0
4578: actual 2, expected 0
4579: actual 2, expected 0
4580: actual 2, expected 0
4581: actual 2, expected 0
4582: actual 2, expected 0
4583: actual 2, expected 0
4584: actual 2, expected 0
4585: actual 2, expected 0
4586: actual 2, expected 0
4587: actual 2, expected 0
4588: actual 2, expected 0
4589: actual 2, expected 0
4590: actual 2, expected 0
4591: actual 2, expected 0
4592: actual 2, expected 0
4593: actual 2, expected 0
4594: actual 2, expected 0
4595: actual 2, expected 0
4596: actual 2, expected 0
4597: actual 2, expected 0
4598: actual 2, expected 0
4599: actual 2, expected 0
4600: actual 2, expected 0
4601: actual 2, expected 0
4602: actual 2, expected 0
4603: actual 2, expected 0
4604: actual 2, expected 0
4605: actual 2, expected 0
4606: actual 2, expected 0
4607: actual 2, expected 0
55216: actual 2, expected 1
55217: actual 2, expected 1
55218: actual 2, expected 1
55219: actual 2, expected 1
55220: actual 2, expected 1
55221: actual 2, expected 1
55222: actual 2, expected 1
55223: actual 2, expected 1
55224: actual 2, expected 1
55225: actual 2, expected 1
55226: actual 2, expected 1
55227: actual 2, expected 1
55228: actual 2, expected 1
55229: actual 2, expected 1
55230: actual 2, expected 1
55231: actual 2, expected 1
55232: actual 2, expected 1
55233: actual 2, expected 1
55234: actual 2, expected 1
55235: actual 2, expected 1
55236: actual 2, expected 1
55237: actual 2, expected 1
55238: actual 2, expected 1
55243: actual 2, expected 1
55244: actual 2, expected 1
55245: actual 2, expected 1
55246: actual 2, expected 1
55247: actual 2, expected 1
55248: actual 2, expected 1
55249: actual 2, expected 1
55250: actual 2, expected 1
55251: actual 2, expected 1
55252: actual 2, expected 1
55253: actual 2, expected 1
55254: actual 2, expected 1
55255: actual 2, expected 1
55256: actual 2, expected 1
55257: actual 2, expected 1
55258: actual 2, expected 1
55259: actual 2, expected 1
55260: actual 2, expected 1
55261: actual 2, expected 1
55262: actual 2, expected 1
55263: actual 2, expected 1
55264: actual 2, expected 1
55265: actual 2, expected 1
55266: actual 2, expected 1
55267: actual 2, expected 1
55268: actual 2, expected 1
55269: actual 2, expected 1
55270: actual 2, expected 1
55271: actual 2, expected 1
55272: actual 2, expected 1
55273: actual 2, expected 1
55274: actual 2, expected 1
55275: actual 2, expected 1
55276: actual 2, expected 1
55277: actual 2, expected 1
55278: actual 2, expected 1
55279: actual 2, expected 1
55280: actual 2, expected 1
55281: actual 2, expected 1
55282: actual 2, expected 1
55283: actual 2, expected 1
55284: actual 2, expected 1
55285: actual 2, expected 1
55286: actual 2, expected 1
55287: actual 2, expected 1
55288: actual 2, expected 1
55289: actual 2, expected 1
55290: actual 2, expected 1
55291: actual 2, expected 1

420
widths-st Normal file
View file

@ -0,0 +1,420 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
1536: actual 1, expected 0
1537: actual 1, expected 0
1538: actual 1, expected 0
1539: actual 1, expected 0
1540: actual 1, expected 0
1541: actual 1, expected 0
1757: actual 1, expected 0
2250: actual 1, expected 0
2251: actual 1, expected 0
2252: actual 1, expected 0
2253: actual 1, expected 0
2254: actual 1, expected 0
2255: actual 1, expected 0
2256: actual 1, expected 0
2257: actual 1, expected 0
2258: actual 1, expected 0
2274: actual 1, expected 0
2192: actual 1, expected 0
2193: actual 1, expected 0
2200: actual 1, expected 0
2201: actual 1, expected 0
2202: actual 1, expected 0
2203: actual 1, expected 0
2204: actual 1, expected 0
2205: actual 1, expected 0
2206: actual 1, expected 0
2207: actual 1, expected 0
69744: actual 1, expected 0
69747: actual 1, expected 0
69748: actual 1, expected 0
64110: actual 1, expected 2
64111: actual 1, expected 2
64218: actual 1, expected 2
64219: actual 1, expected 2
64220: actual 1, expected 2
64221: actual 1, expected 2
64222: actual 1, expected 2
64223: actual 1, expected 2
64224: actual 1, expected 2
64225: actual 1, expected 2
64226: actual 1, expected 2
64227: actual 1, expected 2
64228: actual 1, expected 2
64229: actual 1, expected 2
64230: actual 1, expected 2
64231: actual 1, expected 2
64232: actual 1, expected 2
64233: actual 1, expected 2
64234: actual 1, expected 2
64235: actual 1, expected 2
64236: actual 1, expected 2
64237: actual 1, expected 2
64238: actual 1, expected 2
64239: actual 1, expected 2
64240: actual 1, expected 2
64241: actual 1, expected 2
64242: actual 1, expected 2
64243: actual 1, expected 2
64244: actual 1, expected 2
64245: actual 1, expected 2
64246: actual 1, expected 2
64247: actual 1, expected 2
64248: actual 1, expected 2
64249: actual 1, expected 2
64250: actual 1, expected 2
64251: actual 1, expected 2
64252: actual 1, expected 2
64253: actual 1, expected 2
64254: actual 1, expected 2
64255: actual 1, expected 2
195102: actual 1, expected 2
195103: actual 1, expected 2
40957: actual 1, expected 2
40958: actual 1, expected 2
40959: actual 1, expected 2
173790: actual 1, expected 2
173791: actual 1, expected 2
177973: actual 1, expected 2
177974: actual 1, expected 2
177975: actual 1, expected 2
177976: actual 1, expected 2
177977: actual 1, expected 2
177978: actual 1, expected 2
177979: actual 1, expected 2
177980: actual 1, expected 2
177981: actual 1, expected 2
177982: actual 1, expected 2
177983: actual 1, expected 2
178206: actual 1, expected 2
178207: actual 1, expected 2
183970: actual 1, expected 2
183971: actual 1, expected 2
183972: actual 1, expected 2
183973: actual 1, expected 2
183974: actual 1, expected 2
183975: actual 1, expected 2
183976: actual 1, expected 2
183977: actual 1, expected 2
183978: actual 1, expected 2
183979: actual 1, expected 2
183980: actual 1, expected 2
183981: actual 1, expected 2
183982: actual 1, expected 2
183983: actual 1, expected 2
191457: actual 1, expected 2
191458: actual 1, expected 2
191459: actual 1, expected 2
191460: actual 1, expected 2
191461: actual 1, expected 2
191462: actual 1, expected 2
191463: actual 1, expected 2
191464: actual 1, expected 2
191465: actual 1, expected 2
191466: actual 1, expected 2
191467: actual 1, expected 2
191468: actual 1, expected 2
191469: actual 1, expected 2
191470: actual 1, expected 2
191471: actual 1, expected 2
201547: actual 1, expected 2
201548: actual 1, expected 2
201549: actual 1, expected 2
201550: actual 1, expected 2
201551: actual 1, expected 2
6849: actual 1, expected 0
6850: actual 1, expected 0
6851: actual 1, expected 0
6852: actual 1, expected 0
6853: actual 1, expected 0
6854: actual 1, expected 0
6855: actual 1, expected 0
6856: actual 1, expected 0
6857: actual 1, expected 0
6858: actual 1, expected 0
6859: actual 1, expected 0
6860: actual 1, expected 0
6861: actual 1, expected 0
6862: actual 1, expected 0
7674: actual 1, expected 0
12872: actual 2, expected 1
12873: actual 2, expected 1
12874: actual 2, expected 1
12875: actual 2, expected 1
12876: actual 2, expected 1
12877: actual 2, expected 1
12878: actual 2, expected 1
12879: actual 2, expected 1
129008: actual 1, expected 2
55216: actual 0, expected 1
55217: actual 0, expected 1
55218: actual 0, expected 1
55219: actual 0, expected 1
55220: actual 0, expected 1
55221: actual 0, expected 1
55222: actual 0, expected 1
55223: actual 0, expected 1
55224: actual 0, expected 1
55225: actual 0, expected 1
55226: actual 0, expected 1
55227: actual 0, expected 1
55228: actual 0, expected 1
55229: actual 0, expected 1
55230: actual 0, expected 1
55231: actual 0, expected 1
55232: actual 0, expected 1
55233: actual 0, expected 1
55234: actual 0, expected 1
55235: actual 0, expected 1
55236: actual 0, expected 1
55237: actual 0, expected 1
55238: actual 0, expected 1
55243: actual 0, expected 1
55244: actual 0, expected 1
55245: actual 0, expected 1
55246: actual 0, expected 1
55247: actual 0, expected 1
55248: actual 0, expected 1
55249: actual 0, expected 1
55250: actual 0, expected 1
55251: actual 0, expected 1
55252: actual 0, expected 1
55253: actual 0, expected 1
55254: actual 0, expected 1
55255: actual 0, expected 1
55256: actual 0, expected 1
55257: actual 0, expected 1
55258: actual 0, expected 1
55259: actual 0, expected 1
55260: actual 0, expected 1
55261: actual 0, expected 1
55262: actual 0, expected 1
55263: actual 0, expected 1
55264: actual 0, expected 1
55265: actual 0, expected 1
55266: actual 0, expected 1
55267: actual 0, expected 1
55268: actual 0, expected 1
55269: actual 0, expected 1
55270: actual 0, expected 1
55271: actual 0, expected 1
55272: actual 0, expected 1
55273: actual 0, expected 1
55274: actual 0, expected 1
55275: actual 0, expected 1
55276: actual 0, expected 1
55277: actual 0, expected 1
55278: actual 0, expected 1
55279: actual 0, expected 1
55280: actual 0, expected 1
55281: actual 0, expected 1
55282: actual 0, expected 1
55283: actual 0, expected 1
55284: actual 0, expected 1
55285: actual 0, expected 1
55286: actual 0, expected 1
55287: actual 0, expected 1
55288: actual 0, expected 1
55289: actual 0, expected 1
55290: actual 0, expected 1
55291: actual 0, expected 1
5940: actual 0, expected 1
69821: actual 1, expected 0
69826: actual 1, expected 0
69837: actual 1, expected 0
110879: actual 1, expected 2
110880: actual 1, expected 2
110881: actual 1, expected 2
110882: actual 1, expected 2
110576: actual 1, expected 2
110577: actual 1, expected 2
110578: actual 1, expected 2
110579: actual 1, expected 2
110581: actual 1, expected 2
110582: actual 1, expected 2
110583: actual 1, expected 2
110584: actual 1, expected 2
110585: actual 1, expected 2
110586: actual 1, expected 2
110587: actual 1, expected 2
110589: actual 1, expected 2
110590: actual 1, expected 2
6159: actual 1, expected 0
69506: actual 1, expected 0
69507: actual 1, expected 0
69508: actual 1, expected 0
69509: actual 1, expected 0
129401: actual 1, expected 2
129484: actual 1, expected 2
129659: actual 1, expected 2
129660: actual 1, expected 2
129705: actual 1, expected 2
129706: actual 1, expected 2
129707: actual 1, expected 2
129708: actual 1, expected 2
129719: actual 1, expected 2
129720: actual 1, expected 2
129721: actual 1, expected 2
129722: actual 1, expected 2
129731: actual 1, expected 2
129732: actual 1, expected 2
129733: actual 1, expected 2
129751: actual 1, expected 2
129752: actual 1, expected 2
129753: actual 1, expected 2
129760: actual 1, expected 2
129761: actual 1, expected 2
129762: actual 1, expected 2
129763: actual 1, expected 2
129764: actual 1, expected 2
129765: actual 1, expected 2
129766: actual 1, expected 2
129767: actual 1, expected 2
129776: actual 1, expected 2
129777: actual 1, expected 2
129778: actual 1, expected 2
129779: actual 1, expected 2
129780: actual 1, expected 2
129781: actual 1, expected 2
129782: actual 1, expected 2
1807: actual 1, expected 0
3132: actual 1, expected 0
123566: actual 1, expected 0
128733: actual 1, expected 2
128734: actual 1, expected 2
128735: actual 1, expected 2
19904: actual 2, expected 1
19905: actual 2, expected 1
19906: actual 2, expected 1
19907: actual 2, expected 1
19908: actual 2, expected 1
19909: actual 2, expected 1
19910: actual 2, expected 1
19911: actual 2, expected 1
19912: actual 2, expected 1
19913: actual 2, expected 1
19914: actual 2, expected 1
19915: actual 2, expected 1
19916: actual 2, expected 1
19917: actual 2, expected 1
19918: actual 2, expected 1
19919: actual 2, expected 1
19920: actual 2, expected 1
19921: actual 2, expected 1
19922: actual 2, expected 1
19923: actual 2, expected 1
19924: actual 2, expected 1
19925: actual 2, expected 1
19926: actual 2, expected 1
19927: actual 2, expected 1
19928: actual 2, expected 1
19929: actual 2, expected 1
19930: actual 2, expected 1
19931: actual 2, expected 1
19932: actual 2, expected 1
19933: actual 2, expected 1
19934: actual 2, expected 1
19935: actual 2, expected 1
19936: actual 2, expected 1
19937: actual 2, expected 1
19938: actual 2, expected 1
19939: actual 2, expected 1
19940: actual 2, expected 1
19941: actual 2, expected 1
19942: actual 2, expected 1
19943: actual 2, expected 1
19944: actual 2, expected 1
19945: actual 2, expected 1
19946: actual 2, expected 1
19947: actual 2, expected 1
19948: actual 2, expected 1
19949: actual 2, expected 1
19950: actual 2, expected 1
19951: actual 2, expected 1
19952: actual 2, expected 1
19953: actual 2, expected 1
19954: actual 2, expected 1
19955: actual 2, expected 1
19956: actual 2, expected 1
19957: actual 2, expected 1
19958: actual 2, expected 1
19959: actual 2, expected 1
19960: actual 2, expected 1
19961: actual 2, expected 1
19962: actual 2, expected 1
19963: actual 2, expected 1
19964: actual 2, expected 1
19965: actual 2, expected 1
19966: actual 2, expected 1
19967: actual 2, expected 1
118528: actual 1, expected 0
118529: actual 1, expected 0
118530: actual 1, expected 0
118531: actual 1, expected 0
118532: actual 1, expected 0
118533: actual 1, expected 0
118534: actual 1, expected 0
118535: actual 1, expected 0
118536: actual 1, expected 0
118537: actual 1, expected 0
118538: actual 1, expected 0
118539: actual 1, expected 0
118540: actual 1, expected 0
118541: actual 1, expected 0
118542: actual 1, expected 0
118543: actual 1, expected 0
118544: actual 1, expected 0
118545: actual 1, expected 0
118546: actual 1, expected 0
118547: actual 1, expected 0
118548: actual 1, expected 0
118549: actual 1, expected 0
118550: actual 1, expected 0
118551: actual 1, expected 0
118552: actual 1, expected 0
118553: actual 1, expected 0
118554: actual 1, expected 0
118555: actual 1, expected 0
118556: actual 1, expected 0
118557: actual 1, expected 0
118558: actual 1, expected 0
118559: actual 1, expected 0
118560: actual 1, expected 0
118561: actual 1, expected 0
118562: actual 1, expected 0
118563: actual 1, expected 0
118564: actual 1, expected 0
118565: actual 1, expected 0
118566: actual 1, expected 0
118567: actual 1, expected 0
118568: actual 1, expected 0
118569: actual 1, expected 0
118570: actual 1, expected 0
118571: actual 1, expected 0
118572: actual 1, expected 0
118573: actual 1, expected 0
118576: actual 1, expected 0
118577: actual 1, expected 0
118578: actual 1, expected 0
118579: actual 1, expected 0
118580: actual 1, expected 0
118581: actual 1, expected 0
118582: actual 1, expected 0
118583: actual 1, expected 0
118584: actual 1, expected 0
118585: actual 1, expected 0
118586: actual 1, expected 0
118587: actual 1, expected 0
118588: actual 1, expected 0
118589: actual 1, expected 0
118590: actual 1, expected 0
118591: actual 1, expected 0
118592: actual 1, expected 0
118593: actual 1, expected 0
118594: actual 1, expected 0
118595: actual 1, expected 0
118596: actual 1, expected 0
118597: actual 1, expected 0
118598: actual 1, expected 0

82
widths-terminator Normal file
View file

@ -0,0 +1,82 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
55216: actual 0, expected 1
55217: actual 0, expected 1
55218: actual 0, expected 1
55219: actual 0, expected 1
55220: actual 0, expected 1
55221: actual 0, expected 1
55222: actual 0, expected 1
55223: actual 0, expected 1
55224: actual 0, expected 1
55225: actual 0, expected 1
55226: actual 0, expected 1
55227: actual 0, expected 1
55228: actual 0, expected 1
55229: actual 0, expected 1
55230: actual 0, expected 1
55231: actual 0, expected 1
55232: actual 0, expected 1
55233: actual 0, expected 1
55234: actual 0, expected 1
55235: actual 0, expected 1
55236: actual 0, expected 1
55237: actual 0, expected 1
55238: actual 0, expected 1
55239: actual 0, expected 1
55240: actual 0, expected 1
55241: actual 0, expected 1
55242: actual 0, expected 1
55243: actual 0, expected 1
55244: actual 0, expected 1
55245: actual 0, expected 1
55246: actual 0, expected 1
55247: actual 0, expected 1
55248: actual 0, expected 1
55249: actual 0, expected 1
55250: actual 0, expected 1
55251: actual 0, expected 1
55252: actual 0, expected 1
55253: actual 0, expected 1
55254: actual 0, expected 1
55255: actual 0, expected 1
55256: actual 0, expected 1
55257: actual 0, expected 1
55258: actual 0, expected 1
55259: actual 0, expected 1
55260: actual 0, expected 1
55261: actual 0, expected 1
55262: actual 0, expected 1
55263: actual 0, expected 1
55264: actual 0, expected 1
55265: actual 0, expected 1
55266: actual 0, expected 1
55267: actual 0, expected 1
55268: actual 0, expected 1
55269: actual 0, expected 1
55270: actual 0, expected 1
55271: actual 0, expected 1
55272: actual 0, expected 1
55273: actual 0, expected 1
55274: actual 0, expected 1
55275: actual 0, expected 1
55276: actual 0, expected 1
55277: actual 0, expected 1
55278: actual 0, expected 1
55279: actual 0, expected 1
55280: actual 0, expected 1
55281: actual 0, expected 1
55282: actual 0, expected 1
55283: actual 0, expected 1
55284: actual 0, expected 1
55285: actual 0, expected 1
55286: actual 0, expected 1
55287: actual 0, expected 1
55288: actual 0, expected 1
55289: actual 0, expected 1
55290: actual 0, expected 1
55291: actual 0, expected 1
55292: actual 0, expected 1
55293: actual 0, expected 1
55294: actual 0, expected 1
55295: actual 0, expected 1

82
widths-termite Normal file
View file

@ -0,0 +1,82 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
55216: actual 0, expected 1
55217: actual 0, expected 1
55218: actual 0, expected 1
55219: actual 0, expected 1
55220: actual 0, expected 1
55221: actual 0, expected 1
55222: actual 0, expected 1
55223: actual 0, expected 1
55224: actual 0, expected 1
55225: actual 0, expected 1
55226: actual 0, expected 1
55227: actual 0, expected 1
55228: actual 0, expected 1
55229: actual 0, expected 1
55230: actual 0, expected 1
55231: actual 0, expected 1
55232: actual 0, expected 1
55233: actual 0, expected 1
55234: actual 0, expected 1
55235: actual 0, expected 1
55236: actual 0, expected 1
55237: actual 0, expected 1
55238: actual 0, expected 1
55239: actual 0, expected 1
55240: actual 0, expected 1
55241: actual 0, expected 1
55242: actual 0, expected 1
55243: actual 0, expected 1
55244: actual 0, expected 1
55245: actual 0, expected 1
55246: actual 0, expected 1
55247: actual 0, expected 1
55248: actual 0, expected 1
55249: actual 0, expected 1
55250: actual 0, expected 1
55251: actual 0, expected 1
55252: actual 0, expected 1
55253: actual 0, expected 1
55254: actual 0, expected 1
55255: actual 0, expected 1
55256: actual 0, expected 1
55257: actual 0, expected 1
55258: actual 0, expected 1
55259: actual 0, expected 1
55260: actual 0, expected 1
55261: actual 0, expected 1
55262: actual 0, expected 1
55263: actual 0, expected 1
55264: actual 0, expected 1
55265: actual 0, expected 1
55266: actual 0, expected 1
55267: actual 0, expected 1
55268: actual 0, expected 1
55269: actual 0, expected 1
55270: actual 0, expected 1
55271: actual 0, expected 1
55272: actual 0, expected 1
55273: actual 0, expected 1
55274: actual 0, expected 1
55275: actual 0, expected 1
55276: actual 0, expected 1
55277: actual 0, expected 1
55278: actual 0, expected 1
55279: actual 0, expected 1
55280: actual 0, expected 1
55281: actual 0, expected 1
55282: actual 0, expected 1
55283: actual 0, expected 1
55284: actual 0, expected 1
55285: actual 0, expected 1
55286: actual 0, expected 1
55287: actual 0, expected 1
55288: actual 0, expected 1
55289: actual 0, expected 1
55290: actual 0, expected 1
55291: actual 0, expected 1
55292: actual 0, expected 1
55293: actual 0, expected 1
55294: actual 0, expected 1
55295: actual 0, expected 1

427
widths-xterm Normal file
View file

@ -0,0 +1,427 @@
Finished release [optimized] target(s) in 0.02s
Running `target/release/examples/measure_widths`
1536: actual 1, expected 0
1537: actual 1, expected 0
1538: actual 1, expected 0
1539: actual 1, expected 0
1540: actual 1, expected 0
1541: actual 1, expected 0
1757: actual 1, expected 0
2250: actual 1, expected 0
2251: actual 1, expected 0
2252: actual 1, expected 0
2253: actual 1, expected 0
2254: actual 1, expected 0
2255: actual 1, expected 0
2256: actual 1, expected 0
2257: actual 1, expected 0
2258: actual 1, expected 0
2274: actual 1, expected 0
2192: actual 1, expected 0
2193: actual 1, expected 0
2200: actual 1, expected 0
2201: actual 1, expected 0
2202: actual 1, expected 0
2203: actual 1, expected 0
2204: actual 1, expected 0
2205: actual 1, expected 0
2206: actual 1, expected 0
2207: actual 1, expected 0
69744: actual 1, expected 0
69747: actual 1, expected 0
69748: actual 1, expected 0
64110: actual 1, expected 2
64111: actual 1, expected 2
64218: actual 1, expected 2
64219: actual 1, expected 2
64220: actual 1, expected 2
64221: actual 1, expected 2
64222: actual 1, expected 2
64223: actual 1, expected 2
64224: actual 1, expected 2
64225: actual 1, expected 2
64226: actual 1, expected 2
64227: actual 1, expected 2
64228: actual 1, expected 2
64229: actual 1, expected 2
64230: actual 1, expected 2
64231: actual 1, expected 2
64232: actual 1, expected 2
64233: actual 1, expected 2
64234: actual 1, expected 2
64235: actual 1, expected 2
64236: actual 1, expected 2
64237: actual 1, expected 2
64238: actual 1, expected 2
64239: actual 1, expected 2
64240: actual 1, expected 2
64241: actual 1, expected 2
64242: actual 1, expected 2
64243: actual 1, expected 2
64244: actual 1, expected 2
64245: actual 1, expected 2
64246: actual 1, expected 2
64247: actual 1, expected 2
64248: actual 1, expected 2
64249: actual 1, expected 2
64250: actual 1, expected 2
64251: actual 1, expected 2
64252: actual 1, expected 2
64253: actual 1, expected 2
64254: actual 1, expected 2
64255: actual 1, expected 2
195102: actual 1, expected 2
195103: actual 1, expected 2
40957: actual 1, expected 2
40958: actual 1, expected 2
40959: actual 1, expected 2
173790: actual 1, expected 2
173791: actual 1, expected 2
177973: actual 1, expected 2
177974: actual 1, expected 2
177975: actual 1, expected 2
177976: actual 1, expected 2
177977: actual 1, expected 2
177978: actual 1, expected 2
177979: actual 1, expected 2
177980: actual 1, expected 2
177981: actual 1, expected 2
177982: actual 1, expected 2
177983: actual 1, expected 2
178206: actual 1, expected 2
178207: actual 1, expected 2
183970: actual 1, expected 2
183971: actual 1, expected 2
183972: actual 1, expected 2
183973: actual 1, expected 2
183974: actual 1, expected 2
183975: actual 1, expected 2
183976: actual 1, expected 2
183977: actual 1, expected 2
183978: actual 1, expected 2
183979: actual 1, expected 2
183980: actual 1, expected 2
183981: actual 1, expected 2
183982: actual 1, expected 2
183983: actual 1, expected 2
191457: actual 1, expected 2
191458: actual 1, expected 2
191459: actual 1, expected 2
191460: actual 1, expected 2
191461: actual 1, expected 2
191462: actual 1, expected 2
191463: actual 1, expected 2
191464: actual 1, expected 2
191465: actual 1, expected 2
191466: actual 1, expected 2
191467: actual 1, expected 2
191468: actual 1, expected 2
191469: actual 1, expected 2
191470: actual 1, expected 2
191471: actual 1, expected 2
201547: actual 1, expected 2
201548: actual 1, expected 2
201549: actual 1, expected 2
201550: actual 1, expected 2
201551: actual 1, expected 2
6849: actual 1, expected 0
6850: actual 1, expected 0
6851: actual 1, expected 0
6852: actual 1, expected 0
6853: actual 1, expected 0
6854: actual 1, expected 0
6855: actual 1, expected 0
6856: actual 1, expected 0
6857: actual 1, expected 0
6858: actual 1, expected 0
6859: actual 1, expected 0
6860: actual 1, expected 0
6861: actual 1, expected 0
6862: actual 1, expected 0
7674: actual 1, expected 0
12872: actual 2, expected 1
12873: actual 2, expected 1
12874: actual 2, expected 1
12875: actual 2, expected 1
12876: actual 2, expected 1
12877: actual 2, expected 1
12878: actual 2, expected 1
12879: actual 2, expected 1
8206: actual 1, expected 0
8207: actual 1, expected 0
8234: actual 1, expected 0
8235: actual 1, expected 0
8236: actual 1, expected 0
8237: actual 1, expected 0
8238: actual 1, expected 0
129008: actual 1, expected 2
55216: actual 0, expected 1
55217: actual 0, expected 1
55218: actual 0, expected 1
55219: actual 0, expected 1
55220: actual 0, expected 1
55221: actual 0, expected 1
55222: actual 0, expected 1
55223: actual 0, expected 1
55224: actual 0, expected 1
55225: actual 0, expected 1
55226: actual 0, expected 1
55227: actual 0, expected 1
55228: actual 0, expected 1
55229: actual 0, expected 1
55230: actual 0, expected 1
55231: actual 0, expected 1
55232: actual 0, expected 1
55233: actual 0, expected 1
55234: actual 0, expected 1
55235: actual 0, expected 1
55236: actual 0, expected 1
55237: actual 0, expected 1
55238: actual 0, expected 1
55243: actual 0, expected 1
55244: actual 0, expected 1
55245: actual 0, expected 1
55246: actual 0, expected 1
55247: actual 0, expected 1
55248: actual 0, expected 1
55249: actual 0, expected 1
55250: actual 0, expected 1
55251: actual 0, expected 1
55252: actual 0, expected 1
55253: actual 0, expected 1
55254: actual 0, expected 1
55255: actual 0, expected 1
55256: actual 0, expected 1
55257: actual 0, expected 1
55258: actual 0, expected 1
55259: actual 0, expected 1
55260: actual 0, expected 1
55261: actual 0, expected 1
55262: actual 0, expected 1
55263: actual 0, expected 1
55264: actual 0, expected 1
55265: actual 0, expected 1
55266: actual 0, expected 1
55267: actual 0, expected 1
55268: actual 0, expected 1
55269: actual 0, expected 1
55270: actual 0, expected 1
55271: actual 0, expected 1
55272: actual 0, expected 1
55273: actual 0, expected 1
55274: actual 0, expected 1
55275: actual 0, expected 1
55276: actual 0, expected 1
55277: actual 0, expected 1
55278: actual 0, expected 1
55279: actual 0, expected 1
55280: actual 0, expected 1
55281: actual 0, expected 1
55282: actual 0, expected 1
55283: actual 0, expected 1
55284: actual 0, expected 1
55285: actual 0, expected 1
55286: actual 0, expected 1
55287: actual 0, expected 1
55288: actual 0, expected 1
55289: actual 0, expected 1
55290: actual 0, expected 1
55291: actual 0, expected 1
5940: actual 0, expected 1
69821: actual 1, expected 0
69826: actual 1, expected 0
69837: actual 1, expected 0
110879: actual 1, expected 2
110880: actual 1, expected 2
110881: actual 1, expected 2
110882: actual 1, expected 2
110576: actual 1, expected 2
110577: actual 1, expected 2
110578: actual 1, expected 2
110579: actual 1, expected 2
110581: actual 1, expected 2
110582: actual 1, expected 2
110583: actual 1, expected 2
110584: actual 1, expected 2
110585: actual 1, expected 2
110586: actual 1, expected 2
110587: actual 1, expected 2
110589: actual 1, expected 2
110590: actual 1, expected 2
6159: actual 1, expected 0
69506: actual 1, expected 0
69507: actual 1, expected 0
69508: actual 1, expected 0
69509: actual 1, expected 0
129401: actual 1, expected 2
129484: actual 1, expected 2
129659: actual 1, expected 2
129660: actual 1, expected 2
129705: actual 1, expected 2
129706: actual 1, expected 2
129707: actual 1, expected 2
129708: actual 1, expected 2
129719: actual 1, expected 2
129720: actual 1, expected 2
129721: actual 1, expected 2
129722: actual 1, expected 2
129731: actual 1, expected 2
129732: actual 1, expected 2
129733: actual 1, expected 2
129751: actual 1, expected 2
129752: actual 1, expected 2
129753: actual 1, expected 2
129760: actual 1, expected 2
129761: actual 1, expected 2
129762: actual 1, expected 2
129763: actual 1, expected 2
129764: actual 1, expected 2
129765: actual 1, expected 2
129766: actual 1, expected 2
129767: actual 1, expected 2
129776: actual 1, expected 2
129777: actual 1, expected 2
129778: actual 1, expected 2
129779: actual 1, expected 2
129780: actual 1, expected 2
129781: actual 1, expected 2
129782: actual 1, expected 2
1807: actual 1, expected 0
3132: actual 1, expected 0
123566: actual 1, expected 0
128733: actual 1, expected 2
128734: actual 1, expected 2
128735: actual 1, expected 2
19904: actual 2, expected 1
19905: actual 2, expected 1
19906: actual 2, expected 1
19907: actual 2, expected 1
19908: actual 2, expected 1
19909: actual 2, expected 1
19910: actual 2, expected 1
19911: actual 2, expected 1
19912: actual 2, expected 1
19913: actual 2, expected 1
19914: actual 2, expected 1
19915: actual 2, expected 1
19916: actual 2, expected 1
19917: actual 2, expected 1
19918: actual 2, expected 1
19919: actual 2, expected 1
19920: actual 2, expected 1
19921: actual 2, expected 1
19922: actual 2, expected 1
19923: actual 2, expected 1
19924: actual 2, expected 1
19925: actual 2, expected 1
19926: actual 2, expected 1
19927: actual 2, expected 1
19928: actual 2, expected 1
19929: actual 2, expected 1
19930: actual 2, expected 1
19931: actual 2, expected 1
19932: actual 2, expected 1
19933: actual 2, expected 1
19934: actual 2, expected 1
19935: actual 2, expected 1
19936: actual 2, expected 1
19937: actual 2, expected 1
19938: actual 2, expected 1
19939: actual 2, expected 1
19940: actual 2, expected 1
19941: actual 2, expected 1
19942: actual 2, expected 1
19943: actual 2, expected 1
19944: actual 2, expected 1
19945: actual 2, expected 1
19946: actual 2, expected 1
19947: actual 2, expected 1
19948: actual 2, expected 1
19949: actual 2, expected 1
19950: actual 2, expected 1
19951: actual 2, expected 1
19952: actual 2, expected 1
19953: actual 2, expected 1
19954: actual 2, expected 1
19955: actual 2, expected 1
19956: actual 2, expected 1
19957: actual 2, expected 1
19958: actual 2, expected 1
19959: actual 2, expected 1
19960: actual 2, expected 1
19961: actual 2, expected 1
19962: actual 2, expected 1
19963: actual 2, expected 1
19964: actual 2, expected 1
19965: actual 2, expected 1
19966: actual 2, expected 1
19967: actual 2, expected 1
118528: actual 1, expected 0
118529: actual 1, expected 0
118530: actual 1, expected 0
118531: actual 1, expected 0
118532: actual 1, expected 0
118533: actual 1, expected 0
118534: actual 1, expected 0
118535: actual 1, expected 0
118536: actual 1, expected 0
118537: actual 1, expected 0
118538: actual 1, expected 0
118539: actual 1, expected 0
118540: actual 1, expected 0
118541: actual 1, expected 0
118542: actual 1, expected 0
118543: actual 1, expected 0
118544: actual 1, expected 0
118545: actual 1, expected 0
118546: actual 1, expected 0
118547: actual 1, expected 0
118548: actual 1, expected 0
118549: actual 1, expected 0
118550: actual 1, expected 0
118551: actual 1, expected 0
118552: actual 1, expected 0
118553: actual 1, expected 0
118554: actual 1, expected 0
118555: actual 1, expected 0
118556: actual 1, expected 0
118557: actual 1, expected 0
118558: actual 1, expected 0
118559: actual 1, expected 0
118560: actual 1, expected 0
118561: actual 1, expected 0
118562: actual 1, expected 0
118563: actual 1, expected 0
118564: actual 1, expected 0
118565: actual 1, expected 0
118566: actual 1, expected 0
118567: actual 1, expected 0
118568: actual 1, expected 0
118569: actual 1, expected 0
118570: actual 1, expected 0
118571: actual 1, expected 0
118572: actual 1, expected 0
118573: actual 1, expected 0
118576: actual 1, expected 0
118577: actual 1, expected 0
118578: actual 1, expected 0
118579: actual 1, expected 0
118580: actual 1, expected 0
118581: actual 1, expected 0
118582: actual 1, expected 0
118583: actual 1, expected 0
118584: actual 1, expected 0
118585: actual 1, expected 0
118586: actual 1, expected 0
118587: actual 1, expected 0
118588: actual 1, expected 0
118589: actual 1, expected 0
118590: actual 1, expected 0
118591: actual 1, expected 0
118592: actual 1, expected 0
118593: actual 1, expected 0
118594: actual 1, expected 0
118595: actual 1, expected 0
118596: actual 1, expected 0
118597: actual 1, expected 0
118598: actual 1, expected 0