use crate::*;
pub static GRID_DEFAULT_CELL: grid_cell = grid_cell::new(
utf8_data::new([b' '], 0, 1, 1),
grid_attr::empty(),
grid_flag::empty(),
8,
8,
8,
0,
);
pub static GRID_PADDING_CELL: grid_cell = grid_cell::new(
utf8_data::new([b'!'], 0, 0, 0),
grid_attr::empty(),
grid_flag::PADDING,
8,
8,
8,
0,
);
pub static GRID_CLEARED_CELL: grid_cell = grid_cell::new(
utf8_data::new([b' '], 0, 1, 1),
grid_attr::empty(),
grid_flag::CLEARED,
8,
8,
8,
0,
);
pub static GRID_CLEARED_ENTRY: grid_cell_entry = grid_cell_entry {
union_: grid_cell_entry_union {
data: grid_cell_entry_data {
attr: 0,
fg: 8,
bg: 8,
data: b' ',
},
},
flags: grid_flag::CLEARED,
};
unsafe fn grid_store_cell(gce: *mut grid_cell_entry, gc: *const grid_cell, c: u8) {
unsafe {
(*gce).flags = (*gc).flags & !grid_flag::CLEARED;
(*gce).union_.data.fg = ((*gc).fg & 0xff) as u8;
if (*gc).fg & COLOUR_FLAG_256 != 0 {
(*gce).flags |= grid_flag::FG256;
}
(*gce).union_.data.bg = ((*gc).bg & 0xff) as u8;
if (*gc).bg & COLOUR_FLAG_256 != 0 {
(*gce).flags |= grid_flag::BG256;
}
(*gce).union_.data.attr = (*gc).attr.bits() as u8;
(*gce).union_.data.data = c;
}
}
pub unsafe fn grid_set_tab(gc: *mut grid_cell, width: c_uint) {
unsafe {
(*gc).data.data = [0; UTF8_SIZE];
(*gc).flags |= grid_flag::TAB;
(*gc).flags &= !grid_flag::PADDING;
(*gc).data.width = width as u8;
(*gc).data.size = width as u8;
(*gc).data.have = width as u8;
for i in 0..width as usize {
(*gc).data.data[i] = b' ';
}
}
}
unsafe fn grid_need_extended_cell(gce: *const grid_cell_entry, gc: *const grid_cell) -> bool {
unsafe {
if (*gce).flags.contains(grid_flag::EXTENDED) {
return true;
}
if (*gc).attr.bits() > 0xff {
return true;
}
if (*gc).data.size != 1 || (*gc).data.width != 1 {
return true;
}
if ((*gc).fg & COLOUR_FLAG_RGB != 0) || ((*gc).bg & COLOUR_FLAG_RGB != 0) {
return true;
}
if (*gc).us != 8 {
return true;
}
if (*gc).link != 0 {
return true;
}
if (*gc).flags.contains(grid_flag::TAB) {
return true;
}
false
}
}
unsafe fn grid_get_extended_cell(
gl: *mut grid_line,
gce: *mut grid_cell_entry,
flags: grid_flag,
) {
unsafe {
let at = (*gl).extdsize + 1;
(*gl).extddata = xreallocarray_((*gl).extddata, at as usize).as_ptr();
(*gl).extdsize = at;
(*gce).union_.offset = at - 1;
(*gce).flags = flags | grid_flag::EXTENDED;
}
}
unsafe fn grid_extended_cell(
gl: *mut grid_line,
gce: *mut grid_cell_entry,
gc: *const grid_cell,
) -> *mut grid_extd_entry {
unsafe {
let flags = (*gc).flags & !grid_flag::CLEARED;
if !(*gce).flags.contains(grid_flag::EXTENDED) {
grid_get_extended_cell(gl, gce, flags);
} else if (*gce).union_.offset >= (*gl).extdsize {
fatalx("offset too big");
}
(*gl).flags |= grid_line_flag::EXTENDED;
let uc = if (*gc).flags.contains(grid_flag::TAB) {
(*gc).data.width as utf8_char
} else {
let mut uc = MaybeUninit::<utf8_char>::uninit();
utf8_from_data(&raw const (*gc).data, uc.as_mut_ptr());
uc.assume_init()
};
let gee = &mut *(*gl).extddata.offset((*gce).union_.offset as isize);
gee.data = uc;
gee.attr = (*gc).attr.bits();
gee.flags = flags.bits();
gee.fg = (*gc).fg;
gee.bg = (*gc).bg;
gee.us = (*gc).us;
gee.link = (*gc).link;
gee
}
}
unsafe fn grid_compact_line(gl: *mut grid_line) {
unsafe {
let mut new_extdsize = 0u32;
if (*gl).extdsize == 0 {
return;
}
for px in 0..(*gl).cellsize {
let gce = &raw mut *(*gl).celldata.add(px as usize);
if (*gce).flags.contains(grid_flag::EXTENDED) {
new_extdsize += 1;
}
}
if new_extdsize == 0 {
free_((*gl).extddata);
(*gl).extddata = null_mut();
(*gl).extdsize = 0;
return;
}
let new_extddata: *mut grid_extd_entry =
xreallocarray_(null_mut(), new_extdsize as usize).as_ptr();
let mut idx = 0;
for px in 0..(*gl).cellsize {
let gce = (*gl).celldata.add(px as usize);
if (*gce).flags.contains(grid_flag::EXTENDED) {
let gee = (*gl).extddata.add((*gce).union_.offset as usize);
std::ptr::copy_nonoverlapping(
gee as *const grid_extd_entry,
new_extddata.add(idx as usize),
1,
);
(*gce).union_.offset = idx;
idx += 1;
}
}
free_((*gl).extddata);
(*gl).extddata = new_extddata;
(*gl).extdsize = new_extdsize;
}
}
pub unsafe fn grid_get_line(gd: *mut grid, line: c_uint) -> *mut grid_line {
unsafe { (*gd).linedata.add(line as usize) }
}
pub unsafe fn grid_adjust_lines(gd: *mut grid, lines: c_uint) {
unsafe {
(*gd).linedata = xreallocarray_((*gd).linedata, lines as usize).as_ptr();
}
}
unsafe fn grid_clear_cell(gd: *mut grid, px: c_uint, py: c_uint, bg: c_uint) {
unsafe {
let gl = (*gd).linedata.add(py as usize);
let gce = (*gl).celldata.add(px as usize);
std::ptr::copy_nonoverlapping(&raw const GRID_CLEARED_ENTRY, gce, 1);
if bg != 8 {
if (bg & COLOUR_FLAG_RGB as u32) != 0 {
grid_get_extended_cell(gl, gce, (*gce).flags);
let gee = grid_extended_cell(gl, gce, &raw const GRID_CLEARED_CELL);
(*gee).bg = bg as i32;
} else {
if (bg & COLOUR_FLAG_256 as u32) != 0 {
(*gce).flags |= grid_flag::BG256;
}
(*gce).union_.data.bg = bg as c_uchar;
}
}
}
}
unsafe fn grid_check_y(gd: *mut grid, from: *const u8, py: c_uint) -> c_int {
unsafe {
if py >= (*gd).hsize as c_uint + (*gd).sy as c_uint {
log_debug!("{}: y out of range: {}", _s(from), py);
return -1;
}
}
0
}
pub unsafe fn grid_cells_look_equal(gc1: *const grid_cell, gc2: *const grid_cell) -> c_int {
unsafe {
if (*gc1).fg != (*gc2).fg || (*gc1).bg != (*gc2).bg {
return 0;
}
if (*gc1).attr != (*gc2).attr {
return 0;
}
if ((*gc1).flags & !grid_flag::CLEARED) != ((*gc2).flags & !grid_flag::CLEARED) {
return 0;
}
if (*gc1).link != (*gc2).link {
return 0;
}
1
}
}
pub unsafe fn grid_cells_equal(gc1: *const grid_cell, gc2: *const grid_cell) -> bool {
unsafe {
if grid_cells_look_equal(gc1, gc2) == 0 {
return false;
}
if (*gc1).data.width != (*gc2).data.width {
return false;
}
if (*gc1).data.size != (*gc2).data.size {
return false;
}
libc::memcmp(
(*gc1).data.data.as_ptr().cast(),
(*gc2).data.data.as_ptr().cast(),
(*gc1).data.size as usize,
) == 0
}
}
unsafe fn grid_free_line(gd: *mut grid, py: c_uint) {
unsafe {
free_((*(*gd).linedata.add(py as usize)).celldata);
(*(*gd).linedata.add(py as usize)).celldata = null_mut();
free_((*(*gd).linedata.add(py as usize)).extddata);
(*(*gd).linedata.add(py as usize)).extddata = null_mut();
}
}
unsafe fn grid_free_lines(gd: *mut grid, py: c_uint, ny: c_uint) {
unsafe {
for yy in py..(py + ny) {
grid_free_line(gd, yy);
}
}
}
pub fn grid_create(sx: u32, sy: u32, hlimit: u32) -> *mut grid {
Box::leak(Box::new(grid {
sx,
sy,
flags: if hlimit != 0 { GRID_HISTORY } else { 0 },
hscrolled: 0,
hsize: 0,
hlimit,
linedata: if sy != 0 {
xcalloc_::<grid_line>(sy as usize).as_ptr()
} else {
null_mut()
},
}))
}
pub unsafe fn grid_destroy(gd: *mut grid) {
unsafe {
grid_free_lines(gd, 0, (*gd).hsize + (*gd).sy);
free_((*gd).linedata);
free_(gd);
}
}
pub unsafe fn grid_compare(ga: *mut grid, gb: *mut grid) -> c_int {
unsafe {
if (*ga).sx != (*gb).sx || (*ga).sy != (*gb).sy {
return 1;
}
for yy in 0..(*ga).sy {
let gla = &mut (*(*ga).linedata.add(yy as usize));
let glb = &mut (*(*gb).linedata.add(yy as usize));
if gla.cellsize != glb.cellsize {
return 1;
}
for xx in 0..gla.cellsize {
let mut gca = grid_cell::new(
utf8_data::new([0; 4], 0, 0, 0),
grid_attr::empty(),
grid_flag::empty(),
0,
0,
0,
0,
);
let mut gcb = grid_cell::new(
utf8_data::new([0; 4], 0, 0, 0),
grid_attr::empty(),
grid_flag::empty(),
0,
0,
0,
0,
);
grid_get_cell(ga, xx, yy, &mut gca);
grid_get_cell(gb, xx, yy, &mut gcb);
if !grid_cells_equal(&gca, &gcb) {
return 1;
}
}
}
0
}
}
unsafe fn grid_trim_history(gd: *mut grid, ny: c_uint) {
unsafe {
grid_free_lines(gd, 0, ny);
libc::memmove(
(*gd).linedata as *mut c_void,
(*gd).linedata.add(ny as usize) as *const c_void,
((*gd).hsize + (*gd).sy - ny) as usize * size_of::<grid_line>(),
);
}
}
pub unsafe fn grid_collect_history(gd: *mut grid) {
unsafe {
if (*gd).hsize == 0 || (*gd).hsize < (*gd).hlimit {
return;
}
let mut ny = (*gd).hlimit / 10;
if ny < 1 {
ny = 1;
}
if ny > (*gd).hsize {
ny = (*gd).hsize;
}
grid_trim_history(gd, ny);
(*gd).hsize -= ny;
if (*gd).hscrolled > (*gd).hsize {
(*gd).hscrolled = (*gd).hsize;
}
}
}
pub unsafe fn grid_remove_history(gd: *mut grid, ny: c_uint) {
unsafe {
if ny > (*gd).hsize {
return;
}
for yy in 0..ny {
grid_free_line(gd, (*gd).hsize + (*gd).sy - 1 - yy);
}
(*gd).hsize -= ny;
}
}
pub unsafe fn grid_scroll_history(gd: *mut grid, bg: c_uint) {
unsafe {
let yy = (*gd).hsize + (*gd).sy;
(*gd).linedata = xreallocarray_((*gd).linedata, (yy + 1) as usize).as_ptr();
grid_empty_line(gd, yy, bg);
(*gd).hscrolled += 1;
grid_compact_line(&mut (*(*gd).linedata.add((*gd).hsize as usize)));
(*(*gd).linedata.add((*gd).hsize as usize)).time = CURRENT_TIME;
(*gd).hsize += 1;
}
}
pub unsafe fn grid_clear_history(gd: *mut grid) {
unsafe {
grid_trim_history(gd, (*gd).hsize);
(*gd).hscrolled = 0;
(*gd).hsize = 0;
(*gd).linedata = xreallocarray_((*gd).linedata, (*gd).sy as usize).as_ptr();
}
}
pub unsafe fn grid_scroll_history_region(
gd: *mut grid,
mut upper: c_uint,
mut lower: c_uint,
bg: c_uint,
) {
unsafe {
let yy = (*gd).hsize + (*gd).sy;
(*gd).linedata = xreallocarray_((*gd).linedata, (yy + 1) as usize).as_ptr();
let gl_history = (*gd).linedata.add((*gd).hsize as usize);
std::ptr::copy(gl_history, gl_history.add(1), (*gd).sy as usize);
upper += 1;
let gl_upper = (*gd).linedata.add(upper as usize);
lower += 1;
std::ptr::copy_nonoverlapping(gl_upper, gl_history, 1);
(*gl_history).time = CURRENT_TIME;
std::ptr::copy(gl_upper.add(1), gl_upper, (lower - upper) as usize);
grid_empty_line(gd, lower, bg);
(*gd).hscrolled += 1;
(*gd).hsize += 1;
}
}
unsafe fn grid_expand_line(gd: *mut grid, py: c_uint, mut sx: c_uint, bg: c_uint) {
unsafe {
let gl = (*gd).linedata.add(py as usize);
if sx <= (*gl).cellsize {
return;
}
if sx < (*gd).sx / 4 {
sx = (*gd).sx / 4;
} else if sx < (*gd).sx / 2 {
sx = (*gd).sx / 2;
} else if (*gd).sx > sx {
sx = (*gd).sx;
}
(*gl).celldata = xreallocarray_((*gl).celldata, sx as usize).as_ptr();
for xx in (*gl).cellsize..sx {
grid_clear_cell(gd, xx, py, bg);
}
(*gl).cellsize = sx;
}
}
pub unsafe fn grid_empty_line(gd: *mut grid, py: c_uint, bg: c_uint) {
unsafe {
(*gd).linedata.add(py as usize).write(zeroed());
if !COLOUR_DEFAULT(bg as i32) {
grid_expand_line(gd, py, (*gd).sx, bg);
}
}
}
pub unsafe fn grid_peek_line(gd: *mut grid, py: c_uint) -> *mut grid_line {
unsafe {
if grid_check_y(gd, c!("grid_peek_line"), py) != 0 {
return null_mut();
}
(*gd).linedata.add(py as usize)
}
}
unsafe fn grid_get_cell1(gl: *mut grid_line, px: c_uint, gc: *mut grid_cell) {
unsafe {
let gce = (*gl).celldata.add(px as usize);
if (*gce).flags.contains(grid_flag::EXTENDED) {
if (*gce).union_.offset >= (*gl).extdsize {
std::ptr::copy(&GRID_DEFAULT_CELL, gc, 1);
} else {
let gee = (*gl).extddata.add((*gce).union_.offset as usize);
(*gc).flags = grid_flag::from_bits((*gee).flags).unwrap();
(*gc).attr = grid_attr::from_bits((*gee).attr).expect("invalid grid_attr");
(*gc).fg = (*gee).fg;
(*gc).bg = (*gee).bg;
(*gc).us = (*gee).us;
(*gc).link = (*gee).link;
if (*gc).flags.contains(grid_flag::TAB) {
grid_set_tab(gc, (*gee).data);
} else {
(*gc).data = utf8_to_data((*gee).data);
}
}
return;
}
(*gc).flags = (*gce).flags & !(grid_flag::FG256 | grid_flag::BG256);
(*gc).attr = grid_attr::from_bits((*gce).union_.data.attr as u16).unwrap();
(*gc).fg = (*gce).union_.data.fg as i32;
if (*gce).flags.contains(grid_flag::FG256) {
(*gc).fg |= COLOUR_FLAG_256;
}
(*gc).bg = (*gce).union_.data.bg as i32;
if (*gce).flags.contains(grid_flag::BG256) {
(*gc).bg |= COLOUR_FLAG_256;
}
(*gc).us = 8;
utf8_set(&mut (*gc).data, (*gce).union_.data.data);
(*gc).link = 0;
}
}
pub unsafe fn grid_get_cell(gd: *mut grid, px: c_uint, py: c_uint, gc: *mut grid_cell) {
unsafe {
if grid_check_y(gd, c!("grid_get_cell"), py) != 0
|| px >= (*(*gd).linedata.add(py as usize)).cellsize
{
std::ptr::copy(&raw const GRID_DEFAULT_CELL, gc, 1);
} else {
grid_get_cell1((*gd).linedata.add(py as usize), px, gc);
}
}
}
pub unsafe fn grid_set_cell(gd: *mut grid, px: c_uint, py: c_uint, gc: *const grid_cell) {
unsafe {
if grid_check_y(gd, c!("grid_set_cell"), py) != 0 {
return;
}
grid_expand_line(gd, py, px + 1, 8);
let gl = &mut (*(*gd).linedata.add(py as usize));
if px + 1 > gl.cellused {
gl.cellused = px + 1;
}
let gce = gl.celldata.add(px as usize);
if grid_need_extended_cell(gce, gc) {
grid_extended_cell(gl, gce, gc);
} else {
grid_store_cell(gce, gc, (*gc).data.data[0]);
}
}
}
pub unsafe fn grid_set_padding(gd: *mut grid, px: c_uint, py: c_uint) {
unsafe {
grid_set_cell(gd, px, py, &GRID_PADDING_CELL);
}
}
pub unsafe fn grid_set_cells(
gd: *mut grid,
px: u32,
py: u32,
gc: *const grid_cell,
s: *const u8,
slen: usize,
) {
unsafe {
if grid_check_y(gd, c!("grid_set_cells"), py) != 0 {
return;
}
grid_expand_line(gd, py, px + slen as c_uint, 8);
let gl = (*gd).linedata.add(py as usize);
if px + slen as c_uint > (*gl).cellused {
(*gl).cellused = px + slen as c_uint;
}
for i in 0..slen {
let gce = (*gl).celldata.add((px + i as c_uint) as usize);
if grid_need_extended_cell(gce, gc) {
let gee = grid_extended_cell(gl, gce, gc);
(*gee).data = utf8_build_one(*s.add(i));
} else {
grid_store_cell(gce, gc, *s.add(i));
}
}
}
}
pub unsafe fn grid_clear(
gd: *mut grid,
px: c_uint,
py: c_uint,
nx: c_uint,
ny: c_uint,
bg: c_uint,
) {
unsafe {
if nx == 0 || ny == 0 {
return;
}
if px == 0 && nx == (*gd).sx {
grid_clear_lines(gd, py, ny, bg);
return;
}
if grid_check_y(gd, c!("grid_clear"), py) != 0 {
return;
}
if grid_check_y(gd, c!("grid_clear"), py + ny - 1) != 0 {
return;
}
for yy in py..py + ny {
let gl = (*gd).linedata.add(yy as usize);
let mut sx = (*gd).sx;
if sx > (*gl).cellsize {
sx = (*gl).cellsize;
}
let mut ox = nx;
if COLOUR_DEFAULT(bg as i32) {
if px > sx {
continue;
}
if px + nx > sx {
ox = sx - px;
}
}
grid_expand_line(gd, yy, px + ox, 8); for xx in px..px + ox {
grid_clear_cell(gd, xx, yy, bg);
}
}
}
}
pub unsafe fn grid_clear_lines(gd: *mut grid, py: c_uint, ny: c_uint, bg: c_uint) {
unsafe {
if ny == 0 {
return;
}
if grid_check_y(gd, c!("grid_clear_lines"), py) != 0 {
return;
}
if grid_check_y(gd, c!("grid_clear_lines"), py + ny - 1) != 0 {
return;
}
for yy in py..py + ny {
grid_free_line(gd, yy);
grid_empty_line(gd, yy, bg);
}
if py != 0 {
(*(*gd).linedata.add(py as usize - 1)).flags &= !grid_line_flag::WRAPPED;
}
}
}
pub unsafe fn grid_move_lines(gd: *mut grid, dy: c_uint, py: c_uint, ny: c_uint, bg: c_uint) {
unsafe {
if ny == 0 || py == dy {
return;
}
if grid_check_y(gd, c!("grid_move_lines"), py) != 0 {
return;
}
if grid_check_y(gd, c!("grid_move_lines"), py + ny - 1) != 0 {
return;
}
if grid_check_y(gd, c!("grid_move_lines"), dy) != 0 {
return;
}
if grid_check_y(gd, c!("grid_move_lines"), dy + ny - 1) != 0 {
return;
}
for yy in dy..dy + ny {
if yy >= py && yy < py + ny {
continue;
}
grid_free_line(gd, yy);
}
if dy != 0 {
(*(*gd).linedata.add(dy as usize - 1)).flags &= !grid_line_flag::WRAPPED;
}
let src = (*gd).linedata.add(py as usize);
let dst = (*gd).linedata.add(dy as usize);
std::ptr::copy(src, dst, ny as usize);
for yy in py..py + ny {
if yy < dy || yy >= dy + ny {
grid_empty_line(gd, yy, bg);
}
}
if py != 0 && (py < dy || py >= dy + ny) {
(*(*gd).linedata.add(py as usize - 1)).flags &= !grid_line_flag::WRAPPED;
}
}
}
pub unsafe fn grid_move_cells(
gd: *mut grid,
dx: c_uint,
px: c_uint,
py: c_uint,
nx: c_uint,
bg: c_uint,
) {
unsafe {
if nx == 0 || px == dx {
return;
}
if grid_check_y(gd, c!("grid_move_cells"), py) != 0 {
return;
}
let gl = (*gd).linedata.add(py as usize);
grid_expand_line(gd, py, px + nx, 8);
grid_expand_line(gd, py, dx + nx, 8);
let src = (*gl).celldata.add(px as usize);
let dst = (*gl).celldata.add(dx as usize);
std::ptr::copy(src, dst, nx as usize);
if dx + nx > (*gl).cellused {
(*gl).cellused = dx + nx;
}
for xx in px..px + nx {
if xx >= dx && xx < dx + nx {
continue;
}
grid_clear_cell(gd, xx, py, bg);
}
}
}
unsafe fn grid_string_cells_fg(gc: *const grid_cell, values: *mut c_int) -> usize {
unsafe {
let mut n: usize = 0;
if (*gc).fg & COLOUR_FLAG_256 != 0 {
*values.add(n) = 38;
n += 1;
*values.add(n) = 5;
n += 1;
*values.add(n) = ((*gc).fg & 0xff) as c_int;
n += 1;
} else if (*gc).fg & COLOUR_FLAG_RGB != 0 {
*values.add(n) = 38;
n += 1;
*values.add(n) = 2;
n += 1;
let (r, g, b) = colour_split_rgb((*gc).fg);
*values.add(n) = r as c_int;
n += 1;
*values.add(n) = g as c_int;
n += 1;
*values.add(n) = b as c_int;
n += 1;
} else {
match (*gc).fg {
0..=7 => {
*values.add(n) = (*gc).fg + 30;
n += 1;
}
8 => {
*values.add(n) = 39;
n += 1;
}
90..=97 => {
*values.add(n) = (*gc).fg;
n += 1;
}
_ => {}
}
}
n
}
}
unsafe fn grid_string_cells_bg(gc: *const grid_cell, values: *mut c_int) -> usize {
unsafe {
let mut n: usize = 0;
if (*gc).bg & COLOUR_FLAG_256 != 0 {
*values.add(n) = 48;
n += 1;
*values.add(n) = 5;
n += 1;
*values.add(n) = ((*gc).bg & 0xff) as c_int;
n += 1;
} else if (*gc).bg & COLOUR_FLAG_RGB != 0 {
*values.add(n) = 48;
n += 1;
*values.add(n) = 2;
n += 1;
let (r, g, b) = colour_split_rgb((*gc).bg);
*values.add(n) = r as c_int;
n += 1;
*values.add(n) = g as c_int;
n += 1;
*values.add(n) = b as c_int;
n += 1;
} else {
match (*gc).bg {
0..=7 => {
*values.add(n) = (*gc).bg + 40;
n += 1;
}
8 => {
*values.add(n) = 49;
n += 1;
}
90..=97 => {
*values.add(n) = (*gc).bg + 10;
n += 1;
}
_ => {}
}
}
n
}
}
unsafe fn grid_string_cells_us(gc: *const grid_cell, values: *mut c_int) -> usize {
unsafe {
let mut n: usize = 0;
if (*gc).us & COLOUR_FLAG_256 != 0 {
*values.add(n) = 58;
n += 1;
*values.add(n) = 5;
n += 1;
*values.add(n) = ((*gc).us & 0xff) as c_int;
n += 1;
} else if (*gc).us & COLOUR_FLAG_RGB != 0 {
*values.add(n) = 58;
n += 1;
*values.add(n) = 2;
n += 1;
let (r, g, b) = colour_split_rgb((*gc).us);
*values.add(n) = r as c_int;
n += 1;
*values.add(n) = g as c_int;
n += 1;
*values.add(n) = b as c_int;
n += 1;
}
n
}
}
unsafe fn grid_string_cells_add_code(
buf: *mut u8,
len: usize,
n: c_uint,
s: *mut c_int,
newc: *mut c_int,
oldc: *mut c_int,
nnewc: usize,
noldc: usize,
flags: grid_string_flags,
) {
unsafe {
let mut tmp: [u8; 64] = [0; 64];
let reset = n != 0 && *s == 0;
if nnewc == 0 {
return; }
if !reset
&& nnewc == noldc
&& libc::memcmp(
newc as *const c_void,
oldc as *const c_void,
nnewc * std::mem::size_of::<c_int>(),
) == 0
{
return; }
if reset && (*newc == 49 || *newc == 39) {
return; }
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\033["), len);
} else {
strlcat(buf, c!("\x1b["), len);
}
for i in 0..nnewc {
if i + 1 < nnewc {
_ = xsnprintf_!(tmp.as_mut_ptr(), tmp.len(), "{};", *newc.add(i));
} else {
_ = xsnprintf_!(tmp.as_mut_ptr(), tmp.len(), "{}", *newc.add(i));
}
strlcat(buf, tmp.as_ptr(), len);
}
strlcat(buf, c!("m"), len);
}
}
unsafe fn grid_string_cells_add_hyperlink(
buf: *mut u8,
len: usize,
id: *const u8,
uri: *const u8,
flags: grid_string_flags,
) -> bool {
unsafe {
if strlen(uri) + strlen(id) + 17 >= len {
return false;
}
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\033]8;"), len);
} else {
strlcat(buf, c!("\x1b]8;"), len);
}
if *id != 0 {
let tmp = format_nul!("id={};", _s(id));
strlcat(buf, tmp, len);
free_(tmp);
} else {
strlcat(buf, c!(";"), len);
}
strlcat(buf, uri, len);
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\033\\\\"), len);
} else {
strlcat(buf, c!("\x1b\\"), len);
}
true
}
}
unsafe fn grid_string_cells_code(
lastgc: *const grid_cell,
gc: *const grid_cell,
buf: *mut u8,
len: usize,
flags: grid_string_flags,
sc: *mut screen,
) -> bool {
unsafe {
let mut oldc: [c_int; 64] = [0; 64];
let mut newc: [c_int; 64] = [0; 64];
let mut s: [c_int; 128] = [0; 128];
let mut noldc: usize;
let mut nnewc: usize;
let mut n: u32 = 0;
let attr = (*gc).attr;
let mut lastattr = (*lastgc).attr;
let mut tmp: [u8; 64] = [0; 64];
let mut uri: *const u8 = null();
let mut id: *const u8 = null();
let mut has_link = false;
static ATTRS: [(grid_attr, c_uint); 13] = [
(grid_attr::GRID_ATTR_BRIGHT, 1),
(grid_attr::GRID_ATTR_DIM, 2),
(grid_attr::GRID_ATTR_ITALICS, 3),
(grid_attr::GRID_ATTR_UNDERSCORE, 4),
(grid_attr::GRID_ATTR_BLINK, 5),
(grid_attr::GRID_ATTR_REVERSE, 7),
(grid_attr::GRID_ATTR_HIDDEN, 8),
(grid_attr::GRID_ATTR_STRIKETHROUGH, 9),
(grid_attr::GRID_ATTR_UNDERSCORE_2, 42),
(grid_attr::GRID_ATTR_UNDERSCORE_3, 43),
(grid_attr::GRID_ATTR_UNDERSCORE_4, 44),
(grid_attr::GRID_ATTR_UNDERSCORE_5, 45),
(grid_attr::GRID_ATTR_OVERLINE, 53),
];
for &(mask, _) in &ATTRS {
if !attr.intersects(mask) && lastattr.intersects(mask)
|| ((*lastgc).us != 8 && (*gc).us == 8)
{
s[n as usize] = 0;
n += 1;
lastattr &= grid_attr::GRID_ATTR_CHARSET;
break;
}
}
for &(mask, code) in &ATTRS {
if attr.intersects(mask) && !lastattr.intersects(mask) {
s[n as usize] = code as c_int;
n += 1;
}
}
*buf = 0;
if n > 0 {
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\033["), len);
} else {
strlcat(buf, c!("\x1b["), len);
}
for i in 0..n {
if s[i as usize] < 10 {
_ = xsnprintf_!(tmp.as_mut_ptr(), tmp.len(), "{}", s[i as usize],);
} else {
_ = xsnprintf_!(
tmp.as_mut_ptr(),
tmp.len(),
"{}:{}",
s[i as usize] / 10,
s[i as usize] % 10,
);
}
strlcat(buf, tmp.as_ptr(), len);
if i + 1 < n {
strlcat(buf, c!(";"), len);
}
}
strlcat(buf, c!("m"), len);
}
nnewc = grid_string_cells_fg(gc, newc.as_mut_ptr());
noldc = grid_string_cells_fg(lastgc, oldc.as_mut_ptr());
grid_string_cells_add_code(
buf,
len,
n,
s.as_mut_ptr(),
newc.as_mut_ptr(),
oldc.as_mut_ptr(),
nnewc,
noldc,
flags,
);
nnewc = grid_string_cells_bg(gc, newc.as_mut_ptr());
noldc = grid_string_cells_bg(lastgc, oldc.as_mut_ptr());
grid_string_cells_add_code(
buf,
len,
n,
s.as_mut_ptr(),
newc.as_mut_ptr(),
oldc.as_mut_ptr(),
nnewc,
noldc,
flags,
);
nnewc = grid_string_cells_us(gc, newc.as_mut_ptr());
noldc = grid_string_cells_us(lastgc, oldc.as_mut_ptr());
grid_string_cells_add_code(
buf,
len,
n,
s.as_mut_ptr(),
newc.as_mut_ptr(),
oldc.as_mut_ptr(),
nnewc,
noldc,
flags,
);
if attr.intersects(grid_attr::GRID_ATTR_CHARSET)
&& !lastattr.intersects(grid_attr::GRID_ATTR_CHARSET)
{
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\016"), len); } else {
strlcat(buf, c!("\x0e"), len); }
}
if !attr.intersects(grid_attr::GRID_ATTR_CHARSET)
&& lastattr.intersects(grid_attr::GRID_ATTR_CHARSET)
{
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES) {
strlcat(buf, c!("\\017"), len); } else {
strlcat(buf, c!("\x0f"), len); }
}
if !sc.is_null() && !(*sc).hyperlinks.is_null() && (*lastgc).link != (*gc).link {
if hyperlinks_get(
(*sc).hyperlinks,
(*gc).link,
&raw mut uri,
&raw mut id,
null_mut(),
) {
has_link = grid_string_cells_add_hyperlink(buf, len, id, uri, flags);
} else if has_link {
grid_string_cells_add_hyperlink(buf, len, c!(""), c!(""), flags);
has_link = false;
}
}
has_link
}
}
pub unsafe fn grid_string_cells(
gd: *mut grid,
px: c_uint,
py: c_uint,
nx: c_uint,
lastgc: *mut *mut grid_cell,
flags: grid_string_flags,
s: *mut screen,
) -> *mut u8 {
static mut LASTGC1: grid_cell = unsafe { zeroed() };
unsafe {
let mut gc: grid_cell = zeroed();
let mut data: *const u8;
let mut code: [u8; 8192] = [0; 8192];
let mut len: usize = 128;
let mut off: usize = 0;
let mut size: usize = 0;
let mut codelen: usize;
let mut has_link: bool = false;
if !lastgc.is_null() && (*lastgc).is_null() {
std::ptr::copy(&GRID_DEFAULT_CELL, &raw mut LASTGC1, 1);
*lastgc = &raw mut LASTGC1;
}
let mut buf: *mut u8 = xmalloc(len).as_ptr() as *mut u8;
let gl = grid_peek_line(gd, py);
if gl.is_null() {
*buf = b'\0';
return buf;
}
let end = if flags.intersects(grid_string_flags::GRID_STRING_EMPTY_CELLS) {
(*gl).cellsize
} else {
(*gl).cellused
};
for xx in px..px + nx {
if xx >= end {
break;
}
grid_get_cell(gd, xx, py, &mut gc);
if gc.flags.intersects(grid_flag::PADDING) {
continue;
}
if flags.intersects(grid_string_flags::GRID_STRING_WITH_SEQUENCES) {
has_link = grid_string_cells_code(
*lastgc,
&gc,
code.as_mut_ptr(),
code.len(),
flags,
s
);
codelen = strlen(code.as_ptr());
std::ptr::copy(&gc, *lastgc, 1);
} else {
codelen = 0;
}
if gc.flags.contains(grid_flag::TAB) {
data = c!("\t");
size = 1;
} else {
data = &raw const gc.data.data as *const u8;
size = gc.data.size as usize;
if flags.intersects(grid_string_flags::GRID_STRING_ESCAPE_SEQUENCES)
&& size == 1
&& *data == b'\\'
{
data = c!("\\\\");
size = 2;
}
}
while len < off + size + codelen + 1 {
buf = xreallocarray(buf.cast(), 2, len).as_ptr() as *mut u8;
len *= 2;
}
if codelen != 0 {
std::ptr::copy(code.as_ptr(), buf.add(off), codelen);
off += codelen;
}
std::ptr::copy(data, buf.add(off), size);
off += size;
}
if has_link {
grid_string_cells_add_hyperlink(code.as_mut_ptr(), code.len(), c!(""), c!(""), flags);
codelen = strlen(code.as_ptr());
while len < off + size + codelen + 1 {
buf = xreallocarray(buf.cast(), 2, len).as_ptr() as *mut u8;
len *= 2;
}
std::ptr::copy(code.as_ptr(), buf.add(off), codelen);
off += codelen;
}
if flags.intersects(grid_string_flags::GRID_STRING_TRIM_SPACES) {
while off > 0 && *buf.add(off - 1) == b' ' {
off -= 1;
}
}
*buf.add(off) = 0;
buf
}
}
pub unsafe fn grid_duplicate_lines(
dst: *mut grid,
mut dy: c_uint,
src: *mut grid,
mut sy: c_uint,
mut ny: c_uint,
) {
unsafe {
if dy + ny > (*dst).hsize + (*dst).sy {
ny = (*dst).hsize + (*dst).sy - dy;
}
if sy + ny > (*src).hsize + (*src).sy {
ny = (*src).hsize + (*src).sy - sy;
}
grid_free_lines(dst, dy, ny);
for _ in 0..ny {
let srcl = (*src).linedata.add(sy as usize);
let dstl = (*dst).linedata.add(dy as usize);
std::ptr::copy_nonoverlapping(srcl, dstl, 1);
if (*srcl).cellsize != 0 {
(*dstl).celldata =
xreallocarray_::<grid_cell_entry>(null_mut(), (*srcl).cellsize as usize)
.as_ptr();
std::ptr::copy_nonoverlapping(
(*srcl).celldata,
(*dstl).celldata,
(*srcl).cellsize as usize,
);
} else {
(*dstl).celldata = null_mut();
}
if (*srcl).extdsize != 0 {
(*dstl).extdsize = (*srcl).extdsize;
(*dstl).extddata =
xreallocarray_::<grid_extd_entry>(null_mut(), (*dstl).extdsize as usize)
.as_ptr();
std::ptr::copy_nonoverlapping(
(*srcl).extddata,
(*dstl).extddata,
(*dstl).extdsize as usize,
);
} else {
(*dstl).extddata = null_mut();
}
sy += 1;
dy += 1;
}
}
}
unsafe fn grid_reflow_dead(gl: *mut grid_line) {
unsafe {
std::ptr::write_bytes(gl as *mut u8, 0, std::mem::size_of::<grid_line>());
(*gl).flags = grid_line_flag::DEAD;
}
}
unsafe fn grid_reflow_add(gd: *mut grid, n: c_uint) -> *mut grid_line {
unsafe {
let sy = (*gd).sy + n;
(*gd).linedata = xreallocarray_((*gd).linedata, sy as usize).as_ptr();
let gl = (*gd).linedata.add((*gd).sy as usize);
std::ptr::write_bytes(
gl as *mut u8,
0,
(n as usize) * std::mem::size_of::<grid_line>(),
);
(*gd).sy = sy;
gl
}
}
unsafe fn grid_reflow_move(gd: *mut grid, from: *mut grid_line) -> *mut grid_line {
unsafe {
let to = grid_reflow_add(gd, 1);
std::ptr::copy_nonoverlapping(from, to, 1);
grid_reflow_dead(from);
to
}
}
unsafe fn grid_reflow_join(
target: *mut grid,
gd: *mut grid,
sx: c_uint,
yy: c_uint,
mut width: c_uint,
already: c_int,
) {
unsafe {
let mut from: *mut grid_line = null_mut();
let mut gc = zeroed();
let mut lines = 0;
let mut wrapped = true;
let mut want = 0;
let (to, gl) = if already == 0 {
let to = (*target).sy;
let gl = grid_reflow_move(target, (*gd).linedata.add(yy as usize));
(to, gl)
} else {
let to = (*target).sy - 1;
let gl = (*target).linedata.add(to as usize);
(to, gl)
};
let mut at = (*gl).cellused;
loop {
if yy + 1 + lines == (*gd).hsize + (*gd).sy {
break;
}
let line = yy + 1 + lines;
if !(*(*gd).linedata.add(line as usize))
.flags
.intersects(grid_line_flag::WRAPPED)
{
wrapped = false;
}
if (*(*gd).linedata.add(line as usize)).cellused == 0 {
if !wrapped {
break;
}
lines += 1;
continue;
}
grid_get_cell1((*gd).linedata.add(line as usize), 0, &mut gc);
if width + gc.data.width as u32 > sx {
break;
}
width += gc.data.width as u32;
grid_set_cell(target, at, to, &gc);
at += 1;
from = (*gd).linedata.add(line as usize);
want = 1;
while want < (*from).cellused {
grid_get_cell1(from, want, &mut gc);
if width + gc.data.width as u32 > sx {
break;
}
width += gc.data.width as u32;
grid_set_cell(target, at, to, &gc);
at += 1;
want += 1;
}
lines += 1;
if !wrapped || want != (*from).cellused || width == sx {
break;
}
}
if lines == 0 || from.is_null() {
return;
}
let left = (*from).cellused - want;
if left != 0 {
grid_move_cells(gd, 0, want, yy + lines, left, 8);
(*from).cellsize = left;
(*from).cellused = left;
lines -= 1;
} else if !wrapped {
(*gl).flags &= !grid_line_flag::WRAPPED;
}
for i in (yy + 1)..(yy + 1 + lines) {
free((*(*gd).linedata.add(i as usize)).celldata.cast());
free((*(*gd).linedata.add(i as usize)).extddata.cast());
grid_reflow_dead((*gd).linedata.add(i as usize));
}
if (*gd).hscrolled > to + lines {
(*gd).hscrolled -= lines;
} else if (*gd).hscrolled > to {
(*gd).hscrolled = to;
}
}
}
unsafe fn grid_reflow_split(target: *mut grid, gd: *mut grid, sx: u32, yy: u32, at: u32) {
unsafe {
let gl = (*gd).linedata.add(yy as usize);
let mut gc = zeroed();
let used = (*gl).cellused;
let flags = (*gl).flags;
let lines = if !(*gl).flags.intersects(grid_line_flag::EXTENDED) {
1 + ((*gl).cellused - 1) / sx
} else {
let mut lines = 2;
let mut width = 0;
for i in at..used {
grid_get_cell1(gl, i, &mut gc);
if width + gc.data.width as u32 > sx {
lines += 1;
width = 0;
}
width += gc.data.width as u32;
}
lines
};
let mut line = (*target).sy + 1;
let first = grid_reflow_add(target, lines);
let mut width = 0;
let mut xx = 0;
for i in at..used {
grid_get_cell1(gl, i, &raw mut gc);
if width + gc.data.width as u32 > sx {
(*(*target).linedata.add(line as usize)).flags |= grid_line_flag::WRAPPED;
line += 1;
width = 0;
xx = 0;
}
width += gc.data.width as u32;
grid_set_cell(target, xx, line, &gc);
xx += 1;
}
if flags.intersects(grid_line_flag::WRAPPED) {
(*(*target).linedata.add(line as usize)).flags |= grid_line_flag::WRAPPED;
}
(*gl).cellsize = at;
(*gl).cellused = at;
(*gl).flags |= grid_line_flag::WRAPPED;
std::ptr::copy_nonoverlapping(gl as *const grid_line, first, 1);
grid_reflow_dead(gl);
if yy <= (*gd).hscrolled {
(*gd).hscrolled += lines - 1;
}
if width < sx && flags.intersects(grid_line_flag::WRAPPED) {
grid_reflow_join(target, gd, sx, yy, width, 1);
}
}
}
pub unsafe fn grid_reflow(gd: *mut grid, sx: u32) {
unsafe {
let target = grid_create((*gd).sx, 0, 0);
for yy in 0..((*gd).hsize + (*gd).sy) {
let gl = (*gd).linedata.add(yy as usize);
if (*gl).flags.intersects(grid_line_flag::DEAD) {
continue;
}
let mut at = 0;
let mut width = 0;
let mut gc = zeroed();
if !(*gl).flags.intersects(grid_line_flag::EXTENDED) {
width = (*gl).cellused;
if width > sx {
at = sx;
} else {
at = width;
}
} else {
for i in 0..(*gl).cellused {
grid_get_cell1(gl, i, &mut gc);
if at == 0 && width + gc.data.width as u32 > sx {
at = i;
}
width += gc.data.width as u32;
}
}
if width == sx {
grid_reflow_move(target, gl);
continue;
}
if width > sx {
grid_reflow_split(target, gd, sx, yy, at);
continue;
}
if (*gl).flags.intersects(grid_line_flag::WRAPPED) {
grid_reflow_join(target, gd, sx, yy, width, 0);
} else {
grid_reflow_move(target, gl);
}
}
if (*target).sy < (*gd).sy {
grid_reflow_add(target, (*gd).sy - (*target).sy);
}
(*gd).hsize = (*target).sy - (*gd).sy;
if (*gd).hscrolled > (*gd).hsize {
(*gd).hscrolled = (*gd).hsize;
}
free((*gd).linedata.cast());
(*gd).linedata = (*target).linedata;
free(target.cast());
}
}
pub unsafe fn grid_wrap_position(gd: *mut grid, px: u32, py: u32, wx: *mut u32, wy: *mut u32) {
unsafe {
let mut ax = 0;
let mut ay = 0;
for yy in 0..py {
if (*(*gd).linedata.add(yy as usize))
.flags
.intersects(grid_line_flag::WRAPPED)
{
ax += (*(*gd).linedata.add(yy as usize)).cellused;
} else {
ax = 0;
ay += 1;
}
}
if px >= (*(*gd).linedata.add(py as usize)).cellused {
ax = u32::MAX;
} else {
ax += px;
}
*wx = ax;
*wy = ay;
}
}
pub unsafe fn grid_unwrap_position(
gd: *mut grid,
px: *mut u32,
py: *mut u32,
mut wx: u32,
wy: u32,
) {
unsafe {
let mut ay = 0;
let mut yy = 0;
while yy < (*gd).hsize + (*gd).sy - 1 {
if ay == wy {
break;
}
if !(*(*gd).linedata.add(yy as usize))
.flags
.intersects(grid_line_flag::WRAPPED)
{
ay += 1;
}
yy += 1;
}
if wx == u32::MAX {
while (*(*gd).linedata.add(yy as usize))
.flags
.intersects(grid_line_flag::WRAPPED)
{
yy += 1;
}
wx = (*(*gd).linedata.add(yy as usize)).cellused;
} else {
while (*(*gd).linedata.add(yy as usize))
.flags
.intersects(grid_line_flag::WRAPPED)
{
if wx < (*(*gd).linedata.add(yy as usize)).cellused {
break;
}
wx -= (*(*gd).linedata.add(yy as usize)).cellused;
yy += 1;
}
}
*px = wx;
*py = yy;
}
}
pub unsafe fn grid_line_length(gd: *mut grid, py: u32) -> u32 {
unsafe {
let mut gc = zeroed();
let mut px = (*grid_get_line(gd, py)).cellsize;
if px > (*gd).sx {
px = (*gd).sx;
}
while px > 0 {
grid_get_cell(gd, px - 1, py, &mut gc);
if (gc.flags.intersects(grid_flag::PADDING))
|| gc.data.size != 1
|| gc.data.data[0] != b' '
{
break;
}
px -= 1;
}
px
}
}
pub unsafe fn grid_in_set(gd: *mut grid, px: c_uint, py: c_uint, set: *const u8) -> u32 {
unsafe {
let mut gc: grid_cell = zeroed();
let mut tmp_gc: grid_cell = zeroed();
grid_get_cell(gd, px, py, &raw mut gc);
if !strchr(set, b'\t' as i32).is_null() {
if gc.flags.contains(grid_flag::TAB) {
return gc.data.width as u32;
}
if gc.flags.contains(grid_flag::PADDING) {
let mut pxx = px;
loop {
pxx -= 1;
grid_get_cell(gd, pxx, py, &raw mut tmp_gc);
if !(pxx > 0 && tmp_gc.flags.contains(grid_flag::PADDING)) {
break;
}
}
if tmp_gc.flags.contains(grid_flag::TAB) {
return tmp_gc.data.width as u32 - (px - pxx);
}
}
}
if gc.flags.contains(grid_flag::PADDING) {
return 0;
}
utf8_cstrhas(set, &raw const gc.data) as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_cell(ch: u8, fg: i32, bg: i32) -> grid_cell {
grid_cell::new(
utf8_data::new([ch], 0, 1, 1),
grid_attr::empty(),
grid_flag::empty(),
fg,
bg,
8,
0,
)
}
unsafe fn set_line3(gd: *mut grid, py: u32, s: &[u8]) {
unsafe {
for (i, &ch) in s.iter().enumerate() {
let gc = make_cell(ch, 8, 8);
grid_set_cell(gd, i as u32, py, &gc);
}
}
}
#[test]
fn test_grid_create_and_destroy() {
let gd = grid_create(80, 24, 100);
unsafe {
assert_eq!((*gd).sx, 80);
assert_eq!((*gd).sy, 24);
assert_eq!((*gd).hsize, 0);
grid_destroy(gd);
}
}
#[test]
fn test_grid_set_get_cell_roundtrip() {
let gd = grid_create(80, 24, 0);
unsafe {
let gc = make_cell(b'A', 2, 5);
grid_set_cell(gd, 3, 4, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 3, 4, &mut out);
assert_eq!(out.fg, 2);
assert_eq!(out.bg, 5);
assert_eq!(out.data.size, 1);
assert_eq!(out.data.width, 1);
assert_eq!(out.data.data[0], b'A');
assert!(grid_cells_equal(&gc, &out));
grid_destroy(gd);
}
}
#[test]
fn test_grid_get_cell_out_of_range_is_default() {
let gd = grid_create(80, 24, 0);
unsafe {
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert!(grid_cells_equal(&GRID_DEFAULT_CELL, &out));
grid_destroy(gd);
}
}
#[test]
fn test_grid_cells_equal_and_look_equal() {
unsafe {
let a = make_cell(b'X', 1, 2);
let b = make_cell(b'X', 1, 2);
assert_eq!(grid_cells_look_equal(&a, &b), 1);
assert!(grid_cells_equal(&a, &b));
let c = make_cell(b'Y', 1, 2);
assert_eq!(grid_cells_look_equal(&a, &c), 1);
assert!(!grid_cells_equal(&a, &c));
let d = make_cell(b'X', 7, 2);
assert_eq!(grid_cells_look_equal(&a, &d), 0);
assert!(!grid_cells_equal(&a, &d));
let e = make_cell(b'X', 1, 7);
assert_eq!(grid_cells_look_equal(&a, &e), 0);
assert!(!grid_cells_equal(&a, &e));
let mut f = make_cell(b'X', 1, 2);
f.attr = grid_attr::GRID_ATTR_BRIGHT;
assert_eq!(grid_cells_look_equal(&a, &f), 0);
assert!(!grid_cells_equal(&a, &f));
}
}
#[test]
fn test_grid_compare_identical_grids() {
unsafe {
let ga = grid_create(10, 3, 0);
let gb = grid_create(10, 3, 0);
for y in 0..3u32 {
for x in 0..5u32 {
let gc = make_cell(b'a' + x as u8, (x % 8) as i32, (y % 8) as i32);
grid_set_cell(ga, x, y, &gc);
grid_set_cell(gb, x, y, &gc);
}
}
assert_eq!(grid_compare(ga, gb), 0);
let diff = make_cell(b'Z', 4, 4);
grid_set_cell(gb, 0, 0, &diff);
assert_eq!(grid_compare(ga, gb), 1);
grid_destroy(ga);
grid_destroy(gb);
}
}
#[test]
fn test_grid_line_length_trims_trailing_blanks() {
unsafe {
let gd = grid_create(80, 24, 0);
assert_eq!(grid_line_length(gd, 0), 0);
for (i, ch) in [b'a', b'b', b'c'].into_iter().enumerate() {
let gc = make_cell(ch, 8, 8);
grid_set_cell(gd, i as u32, 0, &gc);
}
assert_eq!(grid_line_length(gd, 0), 3);
let space = make_cell(b' ', 8, 8);
grid_set_cell(gd, 3, 0, &space);
grid_set_cell(gd, 4, 0, &space);
assert_eq!(grid_line_length(gd, 0), 3);
grid_destroy(gd);
}
}
#[test]
fn test_grid_line_length_all_blanks_is_zero() {
unsafe {
let gd = grid_create(80, 24, 0);
let space = make_cell(b' ', 8, 8);
for x in 0..5u32 {
grid_set_cell(gd, x, 1, &space);
}
assert_eq!(grid_line_length(gd, 1), 0);
let gc = make_cell(b'X', 8, 8);
grid_set_cell(gd, 4, 1, &gc);
assert_eq!(grid_line_length(gd, 1), 5);
grid_destroy(gd);
}
}
unsafe fn cell_char(gd: *mut grid, px: u32, py: u32) -> u8 {
unsafe {
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, px, py, &mut out);
out.data.data[0]
}
}
#[test]
fn test_grid_get_cell_y_out_of_range() {
let gd = grid_create(10, 3, 0);
unsafe {
let mut out = make_cell(b'X', 1, 2);
grid_get_cell(gd, 0, 99, &mut out);
assert!(grid_cells_equal(&GRID_DEFAULT_CELL, &out));
grid_destroy(gd);
}
}
#[test]
fn test_grid_peek_line_range() {
let gd = grid_create(10, 3, 0);
unsafe {
assert!(grid_peek_line(gd, 99).is_null());
assert!(!grid_peek_line(gd, 0).is_null());
grid_destroy(gd);
}
}
#[test]
fn test_grid_set_cell_updates_cellused() {
let gd = grid_create(80, 2, 0);
unsafe {
let gc = make_cell(b'x', 8, 8);
grid_set_cell(gd, 5, 0, &gc);
assert_eq!((*grid_get_line(gd, 0)).cellused, 6);
grid_set_cell(gd, 2, 0, &gc);
assert_eq!((*grid_get_line(gd, 0)).cellused, 6);
grid_destroy(gd);
}
}
#[test]
fn test_grid_set_cells_run_and_cellused() {
let gd = grid_create(80, 2, 0);
unsafe {
let gc = make_cell(b' ', 3, 4);
grid_set_cells(gd, 2, 0, &gc, c!("abcd"), 4);
assert_eq!(cell_char(gd, 2, 0), b'a');
assert_eq!(cell_char(gd, 5, 0), b'd');
assert_eq!((*grid_get_line(gd, 0)).cellused, 6);
grid_destroy(gd);
}
}
#[test]
fn test_grid_set_padding_flags() {
let gd = grid_create(20, 2, 0);
unsafe {
grid_set_padding(gd, 3, 0);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 3, 0, &mut out);
assert!(out.flags.intersects(grid_flag::PADDING));
assert_eq!(out.data.width, 0);
grid_destroy(gd);
}
}
#[test]
fn test_grid_scroll_history() {
let gd = grid_create(80, 3, 100);
unsafe {
let gc = make_cell(b'T', 1, 2);
grid_set_cell(gd, 0, 0, &gc); grid_scroll_history(gd, 8);
assert_eq!((*gd).hsize, 1);
assert_eq!((*gd).hscrolled, 1);
assert_eq!(cell_char(gd, 0, 0), b'T');
grid_destroy(gd);
}
}
#[test]
fn test_grid_collect_history() {
let gd = grid_create(80, 1, 10);
unsafe {
for _ in 0..10u32 {
grid_scroll_history(gd, 8);
}
assert_eq!((*gd).hsize, 10);
for y in 0..10u32 {
let gc = make_cell(b'0' + y as u8, 8, 8);
grid_set_cell(gd, 0, y, &gc);
}
grid_collect_history(gd);
assert_eq!((*gd).hsize, 9);
assert_eq!(cell_char(gd, 0, 0), b'1');
grid_destroy(gd);
}
}
#[test]
fn test_grid_remove_history() {
let gd = grid_create(80, 1, 100);
unsafe {
for _ in 0..4 {
grid_scroll_history(gd, 8);
}
assert_eq!((*gd).hsize, 4);
grid_remove_history(gd, 3);
assert_eq!((*gd).hsize, 1);
grid_remove_history(gd, 99);
assert_eq!((*gd).hsize, 1);
grid_destroy(gd);
}
}
#[test]
fn test_grid_clear_history() {
let gd = grid_create(80, 2, 100);
unsafe {
for _ in 0..5 {
grid_scroll_history(gd, 8);
}
assert!((*gd).hsize > 0);
grid_clear_history(gd);
assert_eq!((*gd).hsize, 0);
assert_eq!((*gd).hscrolled, 0);
grid_destroy(gd);
}
}
#[test]
fn test_grid_clear_partial_width_bg() {
let gd = grid_create(80, 3, 0);
unsafe {
for x in 0..8u32 {
let gc = make_cell(b'X', 1, 2);
grid_set_cell(gd, x, 0, &gc);
}
grid_clear(gd, 2, 0, 3, 1, 4);
for x in 2..5u32 {
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, x, 0, &mut out);
assert_eq!(out.data.data[0], b' ');
assert_eq!(out.bg, 4);
assert!(out.flags.intersects(grid_flag::CLEARED));
}
assert_eq!(cell_char(gd, 1, 0), b'X');
assert_eq!(cell_char(gd, 5, 0), b'X');
grid_destroy(gd);
}
}
#[test]
fn test_grid_clear_full_width_empties_line() {
let gd = grid_create(6, 2, 0);
unsafe {
for x in 0..6u32 {
let gc = make_cell(b'X', 1, 2);
grid_set_cell(gd, x, 0, &gc);
}
grid_clear(gd, 0, 0, 6, 1, 8);
for x in 0..6u32 {
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, x, 0, &mut out);
assert!(grid_cells_equal(&GRID_DEFAULT_CELL, &out));
}
grid_destroy(gd);
}
}
#[test]
fn test_grid_clear_lines_resets_wrap_above() {
let gd = grid_create(10, 4, 0);
unsafe {
(*grid_get_line(gd, 0)).flags |= grid_line_flag::WRAPPED;
grid_clear_lines(gd, 1, 1, 8);
assert!(!(*grid_get_line(gd, 0)).flags.intersects(grid_line_flag::WRAPPED));
grid_destroy(gd);
}
}
#[test]
fn test_grid_move_cells() {
let gd = grid_create(80, 1, 0);
unsafe {
for (i, ch) in b"ABCDEF".iter().enumerate() {
let gc = make_cell(*ch, 1, 2);
grid_set_cell(gd, i as u32, 0, &gc);
}
grid_move_cells(gd, 0, 2, 0, 3, 8);
assert_eq!(cell_char(gd, 0, 0), b'C');
assert_eq!(cell_char(gd, 1, 0), b'D');
assert_eq!(cell_char(gd, 2, 0), b'E');
assert_eq!(cell_char(gd, 3, 0), b' ');
assert_eq!(cell_char(gd, 4, 0), b' ');
assert_eq!(cell_char(gd, 5, 0), b'F');
grid_destroy(gd);
}
}
#[test]
fn test_grid_move_lines() {
let gd = grid_create(80, 6, 0);
unsafe {
for y in 0..6u32 {
let gc = make_cell(b'0' + y as u8, 1, 2);
grid_set_cell(gd, 0, y, &gc);
}
grid_move_lines(gd, 0, 3, 2, 8);
assert_eq!(cell_char(gd, 0, 0), b'3');
assert_eq!(cell_char(gd, 0, 1), b'4');
assert_eq!(cell_char(gd, 0, 3), b' ');
assert_eq!(cell_char(gd, 0, 4), b' ');
grid_destroy(gd);
}
}
#[test]
fn test_grid_duplicate_lines_independent() {
let src = grid_create(20, 3, 0);
let dst = grid_create(20, 3, 0);
unsafe {
for (i, ch) in b"hi".iter().enumerate() {
let gc = make_cell(*ch, 1, 2);
grid_set_cell(src, i as u32, 0, &gc);
}
grid_duplicate_lines(dst, 0, src, 0, 1);
assert_eq!(cell_char(dst, 0, 0), b'h');
assert_eq!(cell_char(dst, 1, 0), b'i');
let z = make_cell(b'Z', 1, 2);
grid_set_cell(src, 0, 0, &z);
assert_eq!(cell_char(dst, 0, 0), b'h');
assert_eq!(cell_char(src, 0, 0), b'Z');
grid_destroy(src);
grid_destroy(dst);
}
}
#[test]
fn test_grid_empty_line_nondefault_bg_expands() {
let gd = grid_create(8, 2, 0);
unsafe {
grid_empty_line(gd, 0, 4); assert_eq!((*grid_get_line(gd, 0)).cellsize, 8);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.bg, 4);
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_rgb_fg_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b'r', 8, 8);
gc.fg = COLOUR_FLAG_RGB | 0x11_22_33;
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.fg, COLOUR_FLAG_RGB | 0x11_22_33);
assert!(grid_cells_equal(&gc, &out));
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_us_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b'u', 1, 2);
gc.us = 5;
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.us, 5);
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_link_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b'l', 1, 2);
gc.link = 7;
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.link, 7);
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_wide_attr_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b's', 1, 2);
gc.attr = grid_attr::GRID_ATTR_STRIKETHROUGH | grid_attr::GRID_ATTR_BRIGHT;
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert!(out.attr.intersects(grid_attr::GRID_ATTR_STRIKETHROUGH));
assert!(out.attr.intersects(grid_attr::GRID_ATTR_BRIGHT));
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_wide_char_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b' ', 1, 2);
gc.data = utf8_data::new([0xE4, 0xB8, 0xAD], 3, 3, 2);
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.data.size, 3);
assert_eq!(out.data.width, 2);
assert_eq!(&out.data.data[..3], &[0xE4, 0xB8, 0xAD]);
grid_destroy(gd);
}
}
#[test]
fn test_grid_wrap_unwrap_roundtrip() {
let gd = grid_create(3, 4, 0);
unsafe {
set_line3(gd, 0, b"abc");
set_line3(gd, 1, b"de");
(*grid_get_line(gd, 0)).flags |= grid_line_flag::WRAPPED;
let mut wx = 0u32;
let mut wy = 0u32;
grid_wrap_position(gd, 1, 1, &mut wx, &mut wy);
assert_eq!(wx, 4);
assert_eq!(wy, 0);
let mut px = 0u32;
let mut py = 0u32;
grid_unwrap_position(gd, &mut px, &mut py, wx, wy);
assert_eq!((px, py), (1, 1));
grid_destroy(gd);
}
}
#[test]
fn test_grid_wrap_position_past_end() {
let gd = grid_create(10, 2, 0);
unsafe {
set_line3(gd, 0, b"ab");
let mut wx = 0u32;
let mut wy = 0u32;
grid_wrap_position(gd, 5, 0, &mut wx, &mut wy);
assert_eq!(wx, u32::MAX);
grid_destroy(gd);
}
}
#[test]
fn test_grid_reflow_splits_wide_line() {
let gd = grid_create(6, 1, 0);
unsafe {
for (i, ch) in b"abcdef".iter().enumerate() {
let gc = make_cell(*ch, 8, 8);
grid_set_cell(gd, i as u32, 0, &gc);
}
grid_reflow(gd, 3);
assert_eq!((*gd).hsize, 1); assert_eq!(cell_char(gd, 0, 0), b'a');
assert_eq!(cell_char(gd, 2, 0), b'c');
assert!((*grid_get_line(gd, 0)).flags.intersects(grid_line_flag::WRAPPED));
assert_eq!(cell_char(gd, 0, 1), b'd');
assert_eq!(cell_char(gd, 2, 1), b'f');
grid_destroy(gd);
}
}
#[test]
fn test_grid_reflow_joins_wrapped_lines() {
let gd = grid_create(3, 2, 0);
unsafe {
set_line3(gd, 0, b"abc");
set_line3(gd, 1, b"def");
(*grid_get_line(gd, 0)).flags |= grid_line_flag::WRAPPED;
grid_reflow(gd, 6);
for (i, ch) in b"abcdef".iter().enumerate() {
assert_eq!(cell_char(gd, i as u32, 0), *ch, "col {i}");
}
grid_destroy(gd);
}
}
#[test]
fn test_grid_string_cells_trim_spaces() {
let gd = grid_create(20, 2, 0);
unsafe {
grid_set_cells(gd, 0, 0, &make_cell(b' ', 8, 8), c!("hi "), 5);
let out = grid_string_cells(
gd,
0,
0,
20,
null_mut(),
grid_string_flags::GRID_STRING_TRIM_SPACES,
null_mut(),
);
let s = std::ffi::CStr::from_ptr(out.cast());
assert_eq!(s.to_bytes(), b"hi");
free_(out);
grid_destroy(gd);
}
}
#[test]
fn test_grid_string_cells_plain() {
let gd = grid_create(20, 2, 0);
unsafe {
grid_set_cells(gd, 0, 0, &make_cell(b' ', 8, 8), c!("cell"), 4);
let out = grid_string_cells(
gd,
0,
0,
20,
null_mut(),
grid_string_flags::empty(),
null_mut(),
);
let s = std::ffi::CStr::from_ptr(out.cast());
assert_eq!(s.to_bytes(), b"cell");
free_(out);
grid_destroy(gd);
}
}
#[test]
fn test_grid_string_cells_out_of_range_py_empty() {
let gd = grid_create(20, 3, 0);
unsafe {
let out = grid_string_cells(
gd,
0,
99,
20,
null_mut(),
grid_string_flags::empty(),
null_mut(),
);
let s = std::ffi::CStr::from_ptr(out.cast());
assert_eq!(s.to_bytes(), b"");
free_(out);
grid_destroy(gd);
}
}
#[test]
fn test_grid_string_cells_empty_cells_reads_to_cellsize() {
let gd = grid_create(8, 2, 0);
unsafe {
grid_empty_line(gd, 0, 4);
assert_eq!((*grid_get_line(gd, 0)).cellsize, 8);
assert_eq!((*grid_get_line(gd, 0)).cellused, 0);
let plain = grid_string_cells(
gd,
0,
0,
8,
null_mut(),
grid_string_flags::empty(),
null_mut(),
);
assert_eq!(std::ffi::CStr::from_ptr(plain.cast()).to_bytes(), b"");
free_(plain);
let full = grid_string_cells(
gd,
0,
0,
8,
null_mut(),
grid_string_flags::GRID_STRING_EMPTY_CELLS,
null_mut(),
);
assert_eq!(std::ffi::CStr::from_ptr(full.cast()).to_bytes(), b" ");
free_(full);
grid_destroy(gd);
}
}
#[test]
fn test_grid_reflow_wrapped_then_empty_no_crash() {
let gd = grid_create(3, 3, 0);
unsafe {
set_line3(gd, 0, b"abc");
(*grid_get_line(gd, 0)).flags |= grid_line_flag::WRAPPED;
(*grid_get_line(gd, 1)).flags |= grid_line_flag::WRAPPED;
grid_reflow(gd, 6);
assert_eq!(cell_char(gd, 0, 0), b'a');
assert_eq!(cell_char(gd, 2, 0), b'c');
grid_destroy(gd);
}
}
#[test]
fn test_extended_cell_rgb_bg_roundtrip() {
let gd = grid_create(20, 2, 0);
unsafe {
let mut gc = make_cell(b'b', 8, 8);
gc.bg = COLOUR_FLAG_RGB | 0x44_55_66;
grid_set_cell(gd, 0, 0, &gc);
let mut out = zeroed::<grid_cell>();
grid_get_cell(gd, 0, 0, &mut out);
assert_eq!(out.bg, COLOUR_FLAG_RGB | 0x44_55_66);
assert!(grid_cells_equal(&gc, &out));
grid_destroy(gd);
}
}
#[test]
fn test_grid_cells_look_equal_link_differs() {
unsafe {
let mut a = make_cell(b'k', 1, 2);
let mut b = make_cell(b'k', 1, 2);
a.link = 3;
b.link = 4;
assert_eq!(grid_cells_look_equal(&a, &b), 0);
assert!(!grid_cells_equal(&a, &b));
b.link = 3;
assert_eq!(grid_cells_look_equal(&a, &b), 1);
assert!(grid_cells_equal(&a, &b));
}
}
#[test]
fn test_grid_compare_different_sizes() {
unsafe {
let a = grid_create(10, 3, 0);
let wide = grid_create(11, 3, 0);
let tall = grid_create(10, 4, 0);
assert_eq!(grid_compare(a, wide), 1);
assert_eq!(grid_compare(a, tall), 1);
grid_destroy(a);
grid_destroy(wide);
grid_destroy(tall);
}
}
#[test]
fn test_grid_move_cells_noop_guards() {
let gd = grid_create(80, 1, 0);
unsafe {
for (i, ch) in b"ABCDEF".iter().enumerate() {
let gc = make_cell(*ch, 1, 2);
grid_set_cell(gd, i as u32, 0, &gc);
}
grid_move_cells(gd, 0, 2, 0, 0, 8);
grid_move_cells(gd, 3, 3, 0, 2, 8);
for (i, ch) in b"ABCDEF".iter().enumerate() {
assert_eq!(cell_char(gd, i as u32, 0), *ch, "col {i}");
}
grid_destroy(gd);
}
}
#[test]
fn test_grid_reflow_exact_width_moves_unchanged() {
let gd = grid_create(10, 1, 0);
unsafe {
for (i, ch) in b"abcdef".iter().enumerate() {
let gc = make_cell(*ch, 8, 8);
grid_set_cell(gd, i as u32, 0, &gc);
}
grid_reflow(gd, 6);
assert_eq!((*gd).hsize, 0); for (i, ch) in b"abcdef".iter().enumerate() {
assert_eq!(cell_char(gd, i as u32, 0), *ch, "col {i}");
}
grid_destroy(gd);
}
}
}