use std::num::NonZeroU16;
use ratatui::{
buffer::{Buffer, CellDiffOption, CellWidth},
layout::{Position, Rect},
};
const PLACEHOLDER: char = '\u{10EEEE}';
pub fn anchor(buf: &mut Buffer, area: Rect) {
let area = area.intersection(buf.area);
for y in area.top()..area.bottom() {
let mut marks = (buf[(area.x, y)].symbol().chars())
.skip_while(|&c| c != PLACEHOLDER)
.skip(1);
let (Some(row), Some(_), Some(id)) = (marks.next(), marks.next(), marks.next()) else {
continue;
};
for (x, &column) in (area.x + 1..area.right()).zip(&DIACRITICS[1..]) {
buf[(x, y)].set_symbol(&String::from_iter([PLACEHOLDER, row, column, id]));
}
}
}
pub struct Transmit {
at: Position,
escape: String,
}
pub fn transmit(buf: &Buffer, area: Rect) -> Option<Transmit> {
let at = area.as_position();
let symbol = buf.cell(at)?.symbol();
let escape = &symbol[..symbol.find(PLACEHOLDER)?];
(!escape.is_empty()).then(|| Transmit {
at,
escape: escape.to_owned(),
})
}
pub fn keep(buf: &mut Buffer, transmits: Vec<Transmit>) {
for Transmit { at, escape } in transmits {
let Some(cell) = buf.cell_mut(at) else {
continue;
};
if cell.symbol().contains(&escape) {
continue;
}
let width = NonZeroU16::new(cell.cell_width()).unwrap_or(NonZeroU16::MIN);
let symbol = escape + cell.symbol();
cell.set_symbol(&symbol)
.set_diff_option(CellDiffOption::ForcedWidth(width));
}
}
#[rustfmt::skip]
const DIACRITICS: [char; 297] = [
'\u{0305}', '\u{030D}', '\u{030E}', '\u{0310}', '\u{0312}', '\u{033D}', '\u{033E}', '\u{033F}',
'\u{0346}', '\u{034A}', '\u{034B}', '\u{034C}', '\u{0350}', '\u{0351}', '\u{0352}', '\u{0357}',
'\u{035B}', '\u{0363}', '\u{0364}', '\u{0365}', '\u{0366}', '\u{0367}', '\u{0368}', '\u{0369}',
'\u{036A}', '\u{036B}', '\u{036C}', '\u{036D}', '\u{036E}', '\u{036F}', '\u{0483}', '\u{0484}',
'\u{0485}', '\u{0486}', '\u{0487}', '\u{0592}', '\u{0593}', '\u{0594}', '\u{0595}', '\u{0597}',
'\u{0598}', '\u{0599}', '\u{059C}', '\u{059D}', '\u{059E}', '\u{059F}', '\u{05A0}', '\u{05A1}',
'\u{05A8}', '\u{05A9}', '\u{05AB}', '\u{05AC}', '\u{05AF}', '\u{05C4}', '\u{0610}', '\u{0611}',
'\u{0612}', '\u{0613}', '\u{0614}', '\u{0615}', '\u{0616}', '\u{0617}', '\u{0657}', '\u{0658}',
'\u{0659}', '\u{065A}', '\u{065B}', '\u{065D}', '\u{065E}', '\u{06D6}', '\u{06D7}', '\u{06D8}',
'\u{06D9}', '\u{06DA}', '\u{06DB}', '\u{06DC}', '\u{06DF}', '\u{06E0}', '\u{06E1}', '\u{06E2}',
'\u{06E4}', '\u{06E7}', '\u{06E8}', '\u{06EB}', '\u{06EC}', '\u{0730}', '\u{0732}', '\u{0733}',
'\u{0735}', '\u{0736}', '\u{073A}', '\u{073D}', '\u{073F}', '\u{0740}', '\u{0741}', '\u{0743}',
'\u{0745}', '\u{0747}', '\u{0749}', '\u{074A}', '\u{07EB}', '\u{07EC}', '\u{07ED}', '\u{07EE}',
'\u{07EF}', '\u{07F0}', '\u{07F1}', '\u{07F3}', '\u{0816}', '\u{0817}', '\u{0818}', '\u{0819}',
'\u{081B}', '\u{081C}', '\u{081D}', '\u{081E}', '\u{081F}', '\u{0820}', '\u{0821}', '\u{0822}',
'\u{0823}', '\u{0825}', '\u{0826}', '\u{0827}', '\u{0829}', '\u{082A}', '\u{082B}', '\u{082C}',
'\u{082D}', '\u{0951}', '\u{0953}', '\u{0954}', '\u{0F82}', '\u{0F83}', '\u{0F86}', '\u{0F87}',
'\u{135D}', '\u{135E}', '\u{135F}', '\u{17DD}', '\u{193A}', '\u{1A17}', '\u{1A75}', '\u{1A76}',
'\u{1A77}', '\u{1A78}', '\u{1A79}', '\u{1A7A}', '\u{1A7B}', '\u{1A7C}', '\u{1B6B}', '\u{1B6D}',
'\u{1B6E}', '\u{1B6F}', '\u{1B70}', '\u{1B71}', '\u{1B72}', '\u{1B73}', '\u{1CD0}', '\u{1CD1}',
'\u{1CD2}', '\u{1CDA}', '\u{1CDB}', '\u{1CE0}', '\u{1DC0}', '\u{1DC1}', '\u{1DC3}', '\u{1DC4}',
'\u{1DC5}', '\u{1DC6}', '\u{1DC7}', '\u{1DC8}', '\u{1DC9}', '\u{1DCB}', '\u{1DCC}', '\u{1DD1}',
'\u{1DD2}', '\u{1DD3}', '\u{1DD4}', '\u{1DD5}', '\u{1DD6}', '\u{1DD7}', '\u{1DD8}', '\u{1DD9}',
'\u{1DDA}', '\u{1DDB}', '\u{1DDC}', '\u{1DDD}', '\u{1DDE}', '\u{1DDF}', '\u{1DE0}', '\u{1DE1}',
'\u{1DE2}', '\u{1DE3}', '\u{1DE4}', '\u{1DE5}', '\u{1DE6}', '\u{1DFE}', '\u{20D0}', '\u{20D1}',
'\u{20D4}', '\u{20D5}', '\u{20D6}', '\u{20D7}', '\u{20DB}', '\u{20DC}', '\u{20E1}', '\u{20E7}',
'\u{20E9}', '\u{20F0}', '\u{2CEF}', '\u{2CF0}', '\u{2CF1}', '\u{2DE0}', '\u{2DE1}', '\u{2DE2}',
'\u{2DE3}', '\u{2DE4}', '\u{2DE5}', '\u{2DE6}', '\u{2DE7}', '\u{2DE8}', '\u{2DE9}', '\u{2DEA}',
'\u{2DEB}', '\u{2DEC}', '\u{2DED}', '\u{2DEE}', '\u{2DEF}', '\u{2DF0}', '\u{2DF1}', '\u{2DF2}',
'\u{2DF3}', '\u{2DF4}', '\u{2DF5}', '\u{2DF6}', '\u{2DF7}', '\u{2DF8}', '\u{2DF9}', '\u{2DFA}',
'\u{2DFB}', '\u{2DFC}', '\u{2DFD}', '\u{2DFE}', '\u{2DFF}', '\u{A66F}', '\u{A67C}', '\u{A67D}',
'\u{A6F0}', '\u{A6F1}', '\u{A8E0}', '\u{A8E1}', '\u{A8E2}', '\u{A8E3}', '\u{A8E4}', '\u{A8E5}',
'\u{A8E6}', '\u{A8E7}', '\u{A8E8}', '\u{A8E9}', '\u{A8EA}', '\u{A8EB}', '\u{A8EC}', '\u{A8ED}',
'\u{A8EE}', '\u{A8EF}', '\u{A8F0}', '\u{A8F1}', '\u{AAB0}', '\u{AAB2}', '\u{AAB3}', '\u{AAB7}',
'\u{AAB8}', '\u{AABE}', '\u{AABF}', '\u{AAC1}', '\u{FE20}', '\u{FE21}', '\u{FE22}', '\u{FE23}',
'\u{FE24}', '\u{FE25}', '\u{FE26}', '\u{10A0F}', '\u{10A38}', '\u{1D185}', '\u{1D186}', '\u{1D187}',
'\u{1D188}', '\u{1D189}', '\u{1D1AA}', '\u{1D1AB}', '\u{1D1AC}', '\u{1D1AD}', '\u{1D242}', '\u{1D243}',
'\u{1D244}',
];
#[cfg(test)]
mod tests {
use ratatui::style::Style;
use super::*;
fn placeholder(row: usize, column: usize) -> String {
String::from_iter([
PLACEHOLDER,
DIACRITICS[row],
DIACRITICS[column],
DIACRITICS[0],
])
}
#[test]
fn every_cell_gets_its_row_and_column() {
let mut buf = Buffer::empty(Rect::new(0, 0, 4, 2));
let first = format!("\x1b_Gsend\x1b\\{}", placeholder(1, 0));
buf[(0, 0)].set_symbol(&first);
for x in 1..4 {
buf[(x, 0)].set_symbol(&PLACEHOLDER.to_string());
}
buf[(0, 1)].set_symbol("x");
anchor(&mut buf, Rect::new(0, 0, 4, 2));
assert_eq!(buf[(0, 0)].symbol(), first);
assert_eq!(buf[(3, 0)].symbol(), placeholder(1, 3));
assert_eq!(buf[(2, 1)].symbol(), " ");
}
const SEND: &str = "\x1b_Gsend\x1b\\";
fn first_draw(buf: &mut Buffer) {
buf[(0, 0)]
.set_symbol(&format!("{SEND}{}", placeholder(0, 0)))
.set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::MIN));
for x in 1..4 {
buf[(x, 0)]
.set_symbol(&placeholder(0, x.into()))
.set_diff_option(CellDiffOption::ForcedWidth(NonZeroU16::MIN));
}
}
#[test]
fn a_label_over_the_first_cell_keeps_the_transmit() {
let area = Rect::new(0, 0, 4, 1);
let mut buf = Buffer::empty(area);
first_draw(&mut buf);
let sent = transmit(&buf, area).into_iter().collect();
buf.set_string(0, 0, "ab", Style::new());
keep(&mut buf, sent);
assert_eq!(buf[(0, 0)].symbol(), format!("{SEND}a"));
assert_eq!(buf[(1, 0)].symbol(), "b");
let cells: Vec<(u16, String)> = (Buffer::empty(area).diff(&buf).into_iter())
.map(|(x, _, cell)| (x, cell.symbol().to_owned()))
.collect();
assert_eq!(cells[..2], [(0, format!("{SEND}a")), (1, "b".into())]);
}
#[test]
fn the_width_holds_on_a_cell_without_options() {
let area = Rect::new(0, 0, 4, 1);
let mut buf = Buffer::empty(area);
first_draw(&mut buf);
let sent = transmit(&buf, area).into_iter().collect();
buf[(0, 0)].reset();
buf[(0, 0)].set_symbol("✓");
keep(&mut buf, sent);
assert_eq!(buf[(0, 0)].cell_width(), 1);
}
#[test]
fn later_draws_and_uncovered_cells_stay() {
let area = Rect::new(0, 0, 4, 1);
let mut buf = Buffer::empty(area);
first_draw(&mut buf);
let first = buf[(0, 0)].symbol().to_owned();
let sent = transmit(&buf, area).into_iter().collect();
keep(&mut buf, sent);
assert_eq!(buf[(0, 0)].symbol(), first);
buf[(0, 0)].set_symbol(&placeholder(0, 0));
assert!(transmit(&buf, area).is_none());
}
}