refactor(grid): change schar_T representation to be more compact

Previously, a screen cell would occupy 28+4=32 bytes per cell
as we always made space for up to MAX_MCO+1 codepoints in a cell.

As an example, even a pretty modest 50*80 screen would consume

50*80*2*32 = 256000, i e a quarter megabyte

With the factor of two due to the TUI side buffer, and even more when
using msg_grid and/or ext_multigrid.

This instead stores a 4-byte union of either:
- a valid UTF-8 sequence up to 4 bytes
- an escape char which is invalid UTF-8 (0xFF) plus a 24-bit index to a
  glyph cache

This avoids allocating space for huge composed glyphs _upfront_, while
still keeping rendering such glyphs reasonably fast (1 hash table lookup
+ one plain index lookup). If the same large glyphs are using repeatedly
on the screen, this is still a net reduction of memory/cache
consumption. The only case which really gets worse is if you blast
the screen full with crazy emojis and zalgo text and even this case
only leads to 4 extra bytes per char.

When only <= 4-byte glyphs are used, plus the 4-byte attribute code,
i e 8 bytes in total there is a factor of four reduction of memory use.
Memory which will be quite hot in cache as the screen buffer is scanned
over in win_line() buffer text drawing

A slight complication is that the representation depends on host byte
order. I've tested this manually by compling and running this
in qemu-s390x and it works fine. We might add a qemu based solution
to CI at some point.
This commit is contained in:
bfredl 2023-09-13 13:39:18 +02:00
parent 46402c16c0
commit 8da986ea87
25 changed files with 439 additions and 171 deletions

View File

@ -79,6 +79,9 @@ function vim.api.nvim__id_float(flt) end
--- @return any[]
function vim.api.nvim__inspect_cell(grid, row, col) end
--- @private
function vim.api.nvim__invalidate_glyph_cache() end
--- @private
--- @return any[]
function vim.api.nvim__runtime_inspect() end

View File

@ -833,8 +833,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
if (i == ncells - 1 || attrs[i] != attrs[i + 1]
|| strcmp(chunk[i], chunk[i + 1]) != 0) {
if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
@ -859,7 +858,9 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
mpack_str(buf, chunk[i]);
char sc_buf[MAX_SCHAR_SIZE];
schar_get(sc_buf, chunk[i]);
mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
@ -869,7 +870,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
was_space = strequal(chunk[i], " ");
was_space = chunk[i] == schar_from_ascii(' ');
}
}
// If the last chunk was all spaces, add a clearing chunk even if there are
@ -893,8 +894,10 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
remote_ui_put(ui, chunk[i]);
if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
char sc_buf[MAX_SCHAR_SIZE];
schar_get(sc_buf, chunk[i]);
remote_ui_put(ui, sc_buf);
if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}

View File

@ -1947,7 +1947,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
ADD_C(ret, CSTR_AS_OBJ((char *)g->chars[off]));
char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
schar_get(sc_buf, g->chars[off]);
ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@ -1963,6 +1965,11 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
void nvim__invalidate_glyph_cache(void)
{
schar_cache_clear_force();
}
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{

View File

@ -16,7 +16,7 @@
#include "nvim/drawscreen.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
#include "nvim/grid_defs.h"
#include "nvim/grid.h"
#include "nvim/highlight_group.h"
#include "nvim/macros.h"
#include "nvim/mbyte.h"
@ -348,7 +348,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
for (size_t i = 0; i < 8; i++) {
Array tuple = ARRAY_DICT_INIT;
String s = cstrn_to_string(config->border_chars[i], sizeof(schar_T));
String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
@ -520,7 +520,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
schar_T chars[8];
char chars[8][MAX_SCHAR_SIZE];
bool shadow_color;
} defaults[] = {
{ "double", { "", "", "", "", "", "", "", "" }, false },
@ -531,7 +531,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false },
};
schar_T *chars = fconfig->border_chars;
char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars;
int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true;

View File

@ -954,7 +954,7 @@ typedef struct {
WinStyle style;
bool border;
bool shadow;
schar_T border_chars[8];
char border_chars[8][MAX_SCHAR_SIZE];
int border_hl_ids[8];
int border_attr[8];
bool title;

View File

@ -106,6 +106,8 @@ typedef struct {
int c_extra; ///< extra chars, all the same
int c_final; ///< final char, mandatory if set
int n_closing; ///< number of chars in fdc which will be closing
bool extra_for_extmark; ///< n_extra set for inline virtual text
// saved "extra" items for when draw_state becomes WL_LINE (again)
@ -221,11 +223,11 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b
if (*p == TAB) {
cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
for (int c = 0; c < cells; c++) {
schar_from_ascii(dest[c], ' ');
dest[c] = schar_from_ascii(' ');
}
goto done;
} else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) {
schar_from_ascii(dest[0], *p);
dest[0] = schar_from_ascii(*p);
s->prev_c = u8c;
} else {
if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
@ -252,10 +254,10 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b
} else {
s->prev_c = u8c;
}
schar_from_cc(dest[0], u8c, u8cc);
dest[0] = schar_from_cc(u8c, u8cc);
}
if (cells > 1) {
dest[1][0] = 0;
dest[1] = 0;
}
done:
s->p += c_len;
@ -348,17 +350,17 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
max_col - col, false, vcol);
// If we failed to emit a char, we still need to put a space and advance.
if (cells < 1) {
schar_from_ascii(linebuf_char[col], ' ');
linebuf_char[col] = schar_from_ascii(' ');
cells = 1;
}
for (int c = 0; c < cells; c++) {
linebuf_attr[col++] = attr;
}
if (col < max_col && linebuf_char[col][0] == 0) {
if (col < max_col && linebuf_char[col] == 0) {
// If the left half of a double-width char is overwritten,
// change the right half to a space so that grid redraws properly,
// but don't advance the current column.
schar_from_ascii(linebuf_char[col], ' ');
linebuf_char[col] = schar_from_ascii(' ');
}
vcol += cells;
}
@ -384,7 +386,8 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
// Allocate a buffer, "wlv->extra[]" may already be in use.
xfree(wlv->p_extra_free);
wlv->p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum);
wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, wp, wlv->foldinfo, wlv->lnum,
&wlv->n_closing);
wlv->p_extra_free[wlv->n_extra] = NUL;
wlv->p_extra = wlv->p_extra_free;
wlv->c_extra = NUL;
@ -405,7 +408,7 @@ static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
///
/// Assume monocell characters
/// @return number of chars added to \param p
size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing)
{
int i = 0;
int level;
@ -447,16 +450,23 @@ size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
}
}
int n_closing_val = i;
if (closed) {
if (symbol != 0) {
// rollback previous write
char_counter -= (size_t)len;
memset(&p[char_counter], ' ', (size_t)len);
n_closing_val--;
}
len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
char_counter += (size_t)len;
}
if (n_closing) {
*n_closing = n_closing_val;
}
return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
}
@ -2737,7 +2747,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.col += n;
} else {
// Add a blank character to highlight.
schar_from_ascii(linebuf_char[wlv.off], ' ');
linebuf_char[wlv.off] = schar_from_ascii(' ');
}
if (area_attr == 0 && !has_fold) {
// Use attributes from match with highest priority among
@ -2839,7 +2849,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
int col_stride = wp->w_p_rl ? -1 : 1;
while (wp->w_p_rl ? wlv.col >= 0 : wlv.col < grid->cols) {
schar_from_ascii(linebuf_char[wlv.off], ' ');
linebuf_char[wlv.off] = schar_from_ascii(' ');
linebuf_vcol[wlv.off] = MAXCOL;
wlv.col += col_stride;
if (draw_color_col) {
@ -2873,7 +2883,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
// logical line
int n = wp->w_p_rl ? -1 : 1;
while (wlv.col >= 0 && wlv.col < grid->cols) {
schar_from_ascii(linebuf_char[wlv.off], ' ');
linebuf_char[wlv.off] = schar_from_ascii(' ');
linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol];
linebuf_vcol[wlv.off] = wlv.vcol;
wlv.off += n;
@ -2973,9 +2983,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
wlv.col--;
}
if (mb_utf8) {
schar_from_cc(linebuf_char[wlv.off], mb_c, u8cc);
linebuf_char[wlv.off] = schar_from_cc(mb_c, u8cc);
} else {
schar_from_ascii(linebuf_char[wlv.off], (char)c);
linebuf_char[wlv.off] = schar_from_ascii((char)c);
}
if (multi_attr) {
linebuf_attr[wlv.off] = multi_attr;
@ -2986,12 +2996,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_onl
linebuf_vcol[wlv.off] = wlv.vcol;
if (wlv.draw_state == WL_FOLD) {
linebuf_vcol[wlv.off] = -2;
if (wlv.n_closing > 0) {
linebuf_vcol[wlv.off] = -3;
wlv.n_closing--;
}
}
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
wlv.off++;
wlv.col++;
// UTF-8: Put a 0 in the second screen char.
linebuf_char[wlv.off][0] = 0;
linebuf_char[wlv.off] = 0;
linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1];
if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
wlv.vcol++;

View File

@ -444,6 +444,15 @@ int update_screen(void)
display_tick++; // let syntax code know we're in a next round of
// display updating
// glyph cache full, very rare
if (schar_cache_clear_if_full()) {
// must use CLEAR, as the contents of screen buffers cannot be
// compared to their previous state here.
// TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs)
// we need to revalidate these here as well!
type = MAX(type, UPD_CLEAR);
}
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
if (msg_did_scroll) {
@ -736,7 +745,10 @@ static void win_redr_border(win_T *wp)
ScreenGrid *grid = &wp->w_grid_alloc;
schar_T *chars = wp->w_float_config.border_chars;
schar_T chars[8];
for (int i = 0; i < 8; i++) {
chars[i] = schar_from_str(wp->w_float_config.border_chars[i]);
}
int *attrs = wp->w_float_config.border_attr;
int *adj = wp->w_border_adj;
@ -770,7 +782,7 @@ static void win_redr_border(win_T *wp)
grid_puts_line_flush(false);
}
if (adj[1]) {
int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
grid_puts_line_start(grid, i + adj[0]);
grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
grid_puts_line_flush(false);
@ -784,7 +796,7 @@ static void win_redr_border(win_T *wp)
}
for (int i = 0; i < icol; i++) {
int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
}

View File

@ -17,6 +17,7 @@
#include "nvim/arabic.h"
#include "nvim/buffer_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@ -36,6 +37,15 @@
// Per-cell attributes
static size_t linebuf_size = 0;
// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string.
// Then it instead stores an index into glyph_cache.keys[] which is a flat char array.
// The hash part is used by schar_from_buf() to quickly lookup glyphs which already
// has been interned. schar_get() should used to convert a schar_T value
// back to a string buffer.
//
// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL).
static Set(glyph) glyph_cache = SET_INIT;
/// Determine if dedicated window grid should be used or the default_grid
///
/// If UI did not request multigrid support, draw all windows on the
@ -56,25 +66,119 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off)
}
/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell.
int schar_from_cc(char *p, int c, int u8cc[MAX_MCO])
schar_T schar_from_cc(int c, int u8cc[MAX_MCO])
{
int len = utf_char2bytes(c, p);
char buf[MAX_SCHAR_SIZE];
int len = utf_char2bytes(c, buf);
for (int i = 0; i < MAX_MCO; i++) {
if (u8cc[i] == 0) {
break;
}
len += utf_char2bytes(u8cc[i], p + len);
len += utf_char2bytes(u8cc[i], buf + len);
}
p[len] = 0;
return len;
buf[len] = 0;
return schar_from_buf(buf, (size_t)len);
}
schar_T schar_from_str(char *str)
{
if (str == NULL) {
return 0;
}
return schar_from_buf(str, strlen(str));
}
/// @param buf need not be NUL terminated, but may not contain embedded NULs.
///
/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte)
schar_T schar_from_buf(const char *buf, size_t len)
{
assert(len < MAX_SCHAR_SIZE);
if (len <= 4) {
schar_T sc = 0;
memcpy((char *)&sc, buf, len);
return sc;
} else {
String str = { .data = (char *)buf, .size = len };
MHPutStatus status;
uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status);
assert(idx < 0xFFFFFF);
#ifdef ORDER_BIG_ENDIAN
return idx + ((uint32_t)0xFF << 24);
#else
return 0xFF + (idx << 8);
#endif
}
}
/// Check if cache is full, and if it is, clear it.
///
/// This should normally only be called in update_screen()
///
/// @return true if cache was clered, and all your screen buffers now are hosed
/// and you need to use UPD_CLEAR
bool schar_cache_clear_if_full(void)
{
// note: critical max is really (1<<24)-1. This gives us some marginal
// until next time update_screen() is called
if (glyph_cache.h.n_keys > (1<<21)) {
set_clear(glyph, &glyph_cache);
return true;
}
return false;
}
/// For testing. The condition in schar_cache_clear_force is hard to
/// reach, so this function can be used to force a cache clear in a test.
void schar_cache_clear_force(void)
{
set_clear(glyph, &glyph_cache);
must_redraw = UPD_CLEAR;
}
bool schar_high(schar_T sc)
{
#ifdef ORDER_BIG_ENDIAN
return ((sc & 0xFF000000) == 0xFF000000);
#else
return ((sc & 0xFF) == 0xFF);
#endif
}
void schar_get(char *buf_out, schar_T sc)
{
if (schar_high(sc)) {
#ifdef ORDER_BIG_ENDIAN
uint32_t idx = sc & (0x00FFFFFF);
#else
uint32_t idx = sc >> 8;
#endif
if (idx >= glyph_cache.h.n_keys) {
abort();
}
xstrlcpy(buf_out, &glyph_cache.keys[idx], 32);
} else {
memcpy(buf_out, (char *)&sc, 4);
buf_out[4] = NUL;
}
}
/// @return ascii char or NUL if not ascii
char schar_get_ascii(schar_T sc)
{
#ifdef ORDER_BIG_ENDIAN
return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL;
#else
return (sc < 0x80) ? (char)sc : NUL;
#endif
}
/// clear a line in the grid starting at "off" until "width" characters
/// are cleared.
void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
{
for (int col = 0; col < width; col++) {
schar_from_ascii(grid->chars[off + (size_t)col], ' ');
grid->chars[off + (size_t)col] = schar_from_ascii(' ');
}
int fill = valid ? 0 : -1;
(void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
@ -93,7 +197,7 @@ bool grid_invalid_row(ScreenGrid *grid, int row)
static int line_off2cells(schar_T *line, size_t off, size_t max_off)
{
return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1;
return (off + 1 < max_off && line[off + 1] == 0) ? 2 : 1;
}
/// Return number of display cells for char at grid->chars[off].
@ -124,7 +228,7 @@ int grid_fix_col(ScreenGrid *grid, int col, int row)
col += coloff;
if (grid->chars != NULL && col > 0
&& grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) {
&& grid->chars[grid->line_offset[row] + (size_t)col] == 0) {
return col - 1 - coloff;
}
return col - coloff;
@ -155,7 +259,7 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp)
if (attrp != NULL) {
*attrp = grid->attrs[off];
}
schar_copy(bytes, grid->chars[off]);
schar_get(bytes, grid->chars[off]);
}
/// put string '*text' on the window grid at position 'row' and 'col', with
@ -185,12 +289,12 @@ void grid_puts_line_start(ScreenGrid *grid, int row)
put_dirty_grid = grid;
}
void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr)
void grid_put_schar(ScreenGrid *grid, int row, int col, schar_T schar, int attr)
{
assert(put_dirty_row == row);
size_t off = grid->line_offset[row] + (size_t)col;
if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) {
schar_copy(grid->chars[off], schar);
if (grid->attrs[off] != attr || grid->chars[off] != schar || rdb_flags & RDB_NODELTA) {
grid->chars[off] = schar;
grid->attrs[off] = attr;
put_dirty_first = MIN(put_dirty_first, col);
@ -293,10 +397,12 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int
}
schar_T buf;
schar_from_cc(buf, u8c, u8cc);
// TODO(bfredl): why not just keep the original byte sequence. arabshape is
// an edge case, treat it as such..
buf = schar_from_cc(u8c, u8cc);
int need_redraw = schar_cmp(grid->chars[off], buf)
|| (mbyte_cells == 2 && grid->chars[off + 1][0] != 0)
int need_redraw = grid->chars[off] != buf
|| (mbyte_cells == 2 && grid->chars[off + 1] != 0)
|| grid->attrs[off] != attr
|| exmode_active
|| rdb_flags & RDB_NODELTA;
@ -320,15 +426,15 @@ int grid_puts_len(ScreenGrid *grid, const char *text, int textlen, int row, int
// When at the start of the text and overwriting the right half of a
// two-cell character in the same grid, truncate that into a '>'.
if (ptr == text && col > 0 && grid->chars[off][0] == 0) {
schar_from_ascii(grid->chars[off - 1], '>');
if (ptr == text && col > 0 && grid->chars[off] == 0) {
grid->chars[off - 1] = schar_from_ascii('>');
}
schar_copy(grid->chars[off], buf);
grid->chars[off] = buf;
grid->attrs[off] = attr;
grid->vcols[off] = -1;
if (mbyte_cells == 2) {
grid->chars[off + 1][0] = 0;
grid->chars[off + 1] = 0;
grid->attrs[off + 1] = attr;
grid->vcols[off + 1] = -1;
}
@ -429,12 +535,12 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
int dirty_last = 0;
int col = start_col;
schar_from_char(sc, c1);
sc = schar_from_char(c1);
size_t lineoff = grid->line_offset[row];
for (col = start_col; col < end_col; col++) {
size_t off = lineoff + (size_t)col;
if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
schar_copy(grid->chars[off], sc);
if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
grid->chars[off] = sc;
grid->attrs[off] = attr;
if (dirty_first == INT_MAX) {
dirty_first = col;
@ -443,7 +549,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
}
grid->vcols[off] = -1;
if (col == start_col) {
schar_from_char(sc, c2);
sc = schar_from_char(c2);
}
}
if (dirty_last > dirty_first) {
@ -483,11 +589,10 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols)
{
return (cols > 0
&& ((schar_cmp(linebuf_char[off_from], grid->chars[off_to])
&& ((linebuf_char[off_from] != grid->chars[off_to]
|| linebuf_attr[off_from] != grid->attrs[off_to]
|| (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1
&& schar_cmp(linebuf_char[off_from + 1],
grid->chars[off_to + 1])))
&& linebuf_char[off_from + 1] != grid->chars[off_to + 1]))
|| rdb_flags & RDB_NODELTA));
}
@ -544,7 +649,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (wp->w_p_nu && wp->w_p_rnu) {
// do not overwrite the line number, change "123 text" to
// "123<<<xt".
while (skip < max_off_from && ascii_isdigit(*linebuf_char[off])) {
while (skip < max_off_from && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
off++;
skip++;
}
@ -554,9 +659,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (line_off2cells(linebuf_char, off, max_off_from) > 1) {
// When the first half of a double-width character is
// overwritten, change the second half to a space.
schar_from_ascii(linebuf_char[off + 1], ' ');
linebuf_char[off + 1] = schar_from_ascii(' ');
}
schar_from_ascii(linebuf_char[off], '<');
linebuf_char[off] = schar_from_ascii('<');
linebuf_attr[off] = HL_ATTR(HLF_AT);
off++;
}
@ -565,8 +670,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (rlflag) {
// Clear rest first, because it's left of the text.
if (clear_width > 0) {
while (col <= endcol && grid->chars[off_to][0] == ' '
&& grid->chars[off_to][1] == NUL
while (col <= endcol && grid->chars[off_to] == schar_from_ascii(' ')
&& grid->attrs[off_to] == bg_attr) {
off_to++;
col++;
@ -619,9 +723,9 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
clear_next = true;
}
schar_copy(grid->chars[off_to], linebuf_char[off_from]);
grid->chars[off_to] = linebuf_char[off_from];
if (char_cells == 2) {
schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]);
grid->chars[off_to + 1] = linebuf_char[off_from + 1];
}
grid->attrs[off_to] = linebuf_attr[off_from];
@ -645,7 +749,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
if (clear_next) {
// Clear the second half of a double-wide character of which the left
// half was overwritten with a single-wide character.
schar_from_ascii(grid->chars[off_to], ' ');
grid->chars[off_to] = schar_from_ascii(' ');
end_dirty++;
}
@ -654,12 +758,10 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
// blank out the rest of the line
// TODO(bfredl): we could cache winline widths
while (col < clear_width) {
if (grid->chars[off_to][0] != ' '
|| grid->chars[off_to][1] != NUL
if (grid->chars[off_to] != schar_from_ascii(' ')
|| grid->attrs[off_to] != bg_attr
|| rdb_flags & RDB_NODELTA) {
grid->chars[off_to][0] = ' ';
grid->chars[off_to][1] = NUL;
grid->chars[off_to] = schar_from_ascii(' ');
grid->attrs[off_to] = bg_attr;
if (start_dirty == -1) {
start_dirty = col;

View File

@ -33,30 +33,25 @@ EXTERN colnr_T *linebuf_vcol INIT(= NULL);
// screen grid.
/// Put a ASCII character in a screen cell.
static inline void schar_from_ascii(char *p, const char c)
{
p[0] = c;
p[1] = 0;
}
///
/// If `x` is a compile time constant, schar_from_ascii(x) will also be.
/// But the specific value varies per plattform.
#ifdef ORDER_BIG_ENDIAN
# define schar_from_ascii(x) ((schar_T)((x) << 24))
#else
# define schar_from_ascii(x) ((schar_T)(x))
#endif
/// Put a unicode character in a screen cell.
static inline int schar_from_char(char *p, int c)
static inline schar_T schar_from_char(int c)
{
int len = utf_char2bytes(c, p);
p[len] = NUL;
return len;
}
/// compare the contents of two screen cells.
static inline int schar_cmp(char *sc1, char *sc2)
{
return strncmp(sc1, sc2, sizeof(schar_T));
}
/// copy the contents of screen cell `sc2` into cell `sc1`
static inline void schar_copy(char *sc1, char *sc2)
{
xstrlcpy(sc1, sc2, sizeof(schar_T));
schar_T sc = 0;
if (c >= 0x200000) {
// TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences
c = 0xFFFD;
}
utf_char2bytes(c, (char *)&sc);
return sc;
}
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -9,9 +9,13 @@
#include "nvim/types.h"
#define MAX_MCO 6 // fixed value for 'maxcombine'
// Includes final NUL. at least 4*(MAX_MCO+1)+1
#define MAX_SCHAR_SIZE 32
// The characters and attributes drawn on grids.
typedef char schar_T[(MAX_MCO + 1) * 4 + 1];
// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianess)
// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4)
typedef uint32_t schar_T;
typedef int sattr_T;
enum {
@ -41,6 +45,9 @@ enum {
///
/// vcols[] contains the virtual columns in the line. -1 means not available
/// (below last line), MAXCOL means after the end of the line.
/// -2 or -3 means in fold column and a mouse click should:
/// -2: open a fold
/// -3: close a fold
///
/// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start
/// of line 'n'. These offsets are in general not linear, as full screen scrolling

View File

@ -86,7 +86,7 @@ static int get_attr_entry(HlEntry entry)
}
retry: {}
MhPutStatus status;
MHPutStatus status;
uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status);
if (status == kMHExisting) {
return (int)k;

View File

@ -47,25 +47,6 @@ static inline uint32_t hash_cstr_t(const char *s)
#define equal_cstr_t strequal
// when used as a key, String doesn't need to be NUL terminated,
// and can also contain embedded NUL:s as part of the data.
static inline uint32_t hash_String(String s)
{
uint32_t h = 0;
for (size_t i = 0; i < s.size; i++) {
h = (h << 5) - h + (uint8_t)s.data[i];
}
return h;
}
static inline bool equal_String(String a, String b)
{
if (a.size != b.size) {
return false;
}
return memcmp(a.data, b.data, a.size) == 0;
}
static inline uint32_t hash_HlEntry(HlEntry ae)
{
const uint8_t *data = (const uint8_t *)&ae;

View File

@ -18,6 +18,25 @@
typedef const char *cstr_t;
typedef void *ptr_t;
// when used as a key, String doesn't need to be NUL terminated,
// and can also contain embedded NUL:s as part of the data.
static inline uint32_t hash_String(String s)
{
uint32_t h = 0;
for (size_t i = 0; i < s.size; i++) {
h = (h << 5) - h + (uint8_t)s.data[i];
}
return h;
}
static inline bool equal_String(String a, String b)
{
if (a.size != b.size) {
return false;
}
return memcmp(a.data, b.data, a.size) == 0;
}
#define Set(type) Set_##type
#define Map(T, U) Map_##T##U
#define PMap(T) Map(T, ptr_t)
@ -57,7 +76,7 @@ typedef enum {
kMHExisting = 0,
kMHNewKeyDidFit,
kMHNewKeyRealloc,
} MhPutStatus;
} MHPutStatus;
void mh_clear(MapHash *h);
void mh_realloc(MapHash *h, uint32_t n_min_buckets);
@ -65,20 +84,22 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets);
// layer 1: key type specific defs
// This is all need for sets.
#define KEY_DECLS(T) \
#define MH_DECLS(T, K, K_query) \
typedef struct { \
MapHash h; \
T *keys; \
K *keys; \
} Set(T); \
\
uint32_t mh_find_bucket_##T(Set(T) *set, T key, bool put); \
uint32_t mh_get_##T(Set(T) *set, T key); \
uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \
uint32_t mh_get_##T(Set(T) *set, K_query key); \
void mh_rehash_##T(Set(T) *set); \
uint32_t mh_put_##T(Set(T) *set, T key, MhPutStatus *new); \
uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \
#define KEY_DECLS(T) \
MH_DECLS(T, T, T) \
uint32_t mh_delete_##T(Set(T) *set, T *key); \
\
static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \
MhPutStatus status; \
MHPutStatus status; \
uint32_t k = mh_put_##T(set, key, &status); \
if (key_alloc) { \
*key_alloc = &set->keys[k]; \
@ -120,6 +141,7 @@ void mh_realloc(MapHash *h, uint32_t n_min_buckets);
#define quasiquote(x, y) x##y
MH_DECLS(glyph, char, String)
KEY_DECLS(int)
KEY_DECLS(cstr_t)
KEY_DECLS(ptr_t)

102
src/nvim/map_glyph_cache.c Normal file
View File

@ -0,0 +1,102 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// Specialized version of Set() where interned strings is stored in a compact,
// NUL-separated char array.
// `String key` lookup keys don't need to be NULL terminated, but they
// must not contain embedded NUL:s. When reading a key from set->keys, they
// are always NUL terminated, though. Thus, it is enough to store an index into
// this array, and use strlen(), to retrive an interned key.
#include "nvim/api/private/helpers.h"
#include "nvim/map.h"
uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put)
{
MapHash *h = &set->h;
uint32_t step = 0;
uint32_t mask = h->n_buckets - 1;
uint32_t k = hash_String(key);
uint32_t i = k & mask;
uint32_t last = i;
uint32_t site = put ? last : MH_TOMBSTONE;
while (!mh_is_empty(h, i)) {
if (mh_is_del(h, i)) {
if (site == last) {
site = i;
}
} else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) {
return i;
}
i = (i + (++step)) & mask;
if (i == last) {
abort();
}
}
if (site == last) {
site = i;
}
return site;
}
/// @return index into set->keys if found, MH_TOMBSTONE otherwise
uint32_t mh_get_glyph(Set(glyph) *set, String key)
{
if (set->h.n_buckets == 0) {
return MH_TOMBSTONE;
}
uint32_t idx = mh_find_bucket_glyph(set, key, false);
return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
}
void mh_rehash_glyph(Set(glyph) *set)
{
// assume the format of set->keys, i e NUL terminated strings
for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) {
uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true);
// there must be tombstones when we do a rehash
if (!mh_is_empty((&set->h), idx)) {
abort();
}
set->h.hash[idx] = k + 1;
}
set->h.n_occupied = set->h.size = set->h.n_keys;
}
uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new)
{
MapHash *h = &set->h;
// Might rehash ahead of time if "key" already existed. But it was
// going to happen soon anyway.
if (h->n_occupied >= h->upper_bound) {
mh_realloc(h, h->n_buckets + 1);
mh_rehash_glyph(set);
}
uint32_t idx = mh_find_bucket_glyph(set, key, true);
if (mh_is_either(h, idx)) {
h->size++;
h->n_occupied++;
uint32_t size = (uint32_t)key.size + 1; // NUL takes space
uint32_t pos = h->n_keys;
h->n_keys += size;
if (h->n_keys > h->keys_capacity) {
h->keys_capacity = MAX(h->keys_capacity * 2, 64);
set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char));
*new = kMHNewKeyRealloc;
} else {
*new = kMHNewKeyDidFit;
}
memcpy(&set->keys[pos], key.data, key.size);
set->keys[pos + key.size] = NUL;
h->hash[idx] = pos + 1;
return pos;
} else {
*new = kMHExisting;
uint32_t pos = h->hash[idx] - 1;
assert(equal_String(cstr_as_string(&set->keys[pos]), key));
return pos;
}
}

View File

@ -80,7 +80,7 @@ void KEY_NAME(mh_rehash_)(SET_TYPE *set)
/// if new item, indicates if keys[] was resized.
///
/// @return keys index
uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MhPutStatus *new)
uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new)
{
MapHash *h = &set->h;
// Might rehash ahead of time if "key" already existed. But it was

View File

@ -28,7 +28,7 @@ VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc
VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc,
bool *new_item)
{
MhPutStatus status;
MHPutStatus status;
uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status);
if (status != kMHExisting) {
if (status == kMHNewKeyRealloc) {

View File

@ -1849,7 +1849,6 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
int mouse_char = ' ';
int max_row = Rows;
int max_col = Columns;
bool multigrid = ui_has(kUIMultigrid);
@ -1864,7 +1863,6 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
if (wp && mouse_row >= 0 && mouse_row < max_row
&& mouse_col >= 0 && mouse_col < max_col) {
ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
int fdc = win_fdccol_count(wp);
int use_row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
int use_col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
@ -1901,22 +1899,12 @@ static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
// concealed characters.
*vcolp = col_from_screen;
}
// Remember the character under the mouse, might be one of foldclose or
// foldopen fillchars in the fold column.
mouse_char = utf_ptr2char((char *)gp->chars[off]);
}
// Check for position outside of the fold column.
if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
mouse_char = ' ';
}
}
if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
if (col_from_screen == -2) {
*flagsp |= MOUSE_FOLD_OPEN;
} else if (mouse_char != ' ') {
} else if (col_from_screen == -3) {
*flagsp |= MOUSE_FOLD_CLOSE;
}
}

View File

@ -9,6 +9,7 @@
#include "mpack/conv.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/grid.h"
#include "nvim/macros.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel_defs.h"
@ -497,13 +498,13 @@ redo:
if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
g->clear_width = repeat;
} else {
schar_T sc = schar_from_buf(cellbuf, cellsize);
for (int r = 0; r < repeat; r++) {
if (g->coloff >= (int)grid_line_buf_size) {
p->state = -1;
return false;
}
memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize);
grid_line_buf_char[g->coloff][cellsize] = NUL;
grid_line_buf_char[g->coloff] = sc;
grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
}

View File

@ -1663,7 +1663,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
char *p = NULL;
if (fold) {
size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM));
size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo,
(linenr_T)get_vim_var_nr(VV_LNUM), NULL);
stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1);
p = out_p;
p[n] = NUL;

View File

@ -24,6 +24,7 @@
#include "nvim/event/signal.h"
#include "nvim/event/stream.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
@ -675,15 +676,15 @@ static void final_column_wrap(TUIData *tui)
/// It is undocumented, but in the majority of terminals and terminal emulators
/// printing at the right margin does not cause an automatic wrap until the
/// next character is printed, holding the cursor in place until then.
static void print_cell(TUIData *tui, UCell *ptr)
static void print_cell(TUIData *tui, char *buf, sattr_T attr)
{
UGrid *grid = &tui->grid;
if (!tui->immediate_wrap_after_last_column) {
// Printing the next character finally advances the cursor.
final_column_wrap(tui);
}
update_attrs(tui, ptr->attr);
out(tui, ptr->data, strlen(ptr->data));
update_attrs(tui, attr);
out(tui, buf, strlen(buf));
grid->col++;
if (tui->immediate_wrap_after_last_column) {
// Printing at the right margin immediately advances the cursor.
@ -703,8 +704,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next)
return false;
}
}
if (strlen(cell->data) > 1) {
return false;
if (schar_get_ascii(cell->data) == 0) {
return false; // not ascii
}
cell++;
}
@ -831,14 +832,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
{
UGrid *grid = &tui->grid;
if (grid->row == -1 && cell->data[0] == NUL) {
if (grid->row == -1 && cell->data == NUL) {
// If cursor needs to repositioned and there is nothing to print, don't move cursor.
return;
}
cursor_goto(tui, row, col);
bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data));
char buf[MAX_SCHAR_SIZE];
schar_get(buf, cell->data);
bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf));
if (is_ambiwidth && is_doublewidth) {
// Clear the two screen cells.
// If the character is single-width in the host terminal it won't change the second cell.
@ -847,7 +850,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
cursor_goto(tui, row, col);
}
print_cell(tui, cell);
print_cell(tui, buf, cell->attr);
if (is_ambiwidth) {
// Force repositioning cursor after printing an ambiguous-width character.
@ -976,6 +979,8 @@ void tui_grid_clear(TUIData *tui, Integer g)
{
UGrid *grid = &tui->grid;
ugrid_clear(grid);
// safe to clear cache at this point
schar_cache_clear_if_full();
kv_size(tui->invalid_regions) = 0;
clear_region(tui, 0, tui->height, 0, tui->width, 0);
}
@ -1273,7 +1278,7 @@ void tui_flush(TUIData *tui)
int clear_col;
for (clear_col = r.right; clear_col > 0; clear_col--) {
UCell *cell = &grid->cells[row][clear_col - 1];
if (!(cell->data[0] == ' ' && cell->data[1] == NUL
if (!(cell->data == schar_from_ascii(' ')
&& cell->attr == clear_attr)) {
break;
}
@ -1281,7 +1286,7 @@ void tui_flush(TUIData *tui)
UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
print_cell_at_pos(tui, row, curcol, cell,
curcol < clear_col - 1 && (cell + 1)->data[0] == NUL);
curcol < clear_col - 1 && (cell + 1)->data == NUL);
});
if (clear_col < r.right) {
clear_region(tui, row, row + 1, clear_col, r.right, clear_attr);
@ -1399,7 +1404,10 @@ void tui_screenshot(TUIData *tui, String path)
for (int i = 0; i < grid->height; i++) {
cursor_goto(tui, i, 0);
for (int j = 0; j < grid->width; j++) {
print_cell(tui, &grid->cells[i][j]);
UCell cell = grid->cells[i][j];
char buf[MAX_SCHAR_SIZE];
schar_get(buf, cell.data);
print_cell(tui, buf, cell.attr);
}
}
flush_buf(tui);
@ -1446,13 +1454,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
{
UGrid *grid = &tui->grid;
for (Integer c = startcol; c < endcol; c++) {
memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T));
grid->cells[linerow][c].data = chunk[c - startcol];
assert((size_t)attrs[c - startcol] < kv_size(tui->attrs));
grid->cells[linerow][c].attr = attrs[c - startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
print_cell_at_pos(tui, (int)linerow, curcol, cell,
curcol < endcol - 1 && (cell + 1)->data[0] == NUL);
curcol < endcol - 1 && (cell + 1)->data == NUL);
});
if (clearcol > endcol) {
@ -1469,7 +1477,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
if (endcol != grid->width) {
// Print the last char of the row, if we haven't already done so.
int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1;
print_cell_at_pos(tui, (int)linerow, grid->width - size,
&grid->cells[linerow][grid->width - size], size == 2);
}

View File

@ -46,4 +46,10 @@ typedef enum {
typedef struct Decoration Decoration;
#ifndef ORDER_BIG_ENDIAN
# if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
# define ORDER_BIG_ENDIAN
# endif
#endif
#endif // NVIM_TYPES_H

View File

@ -4,6 +4,7 @@
#include <assert.h>
#include <string.h>
#include "nvim/grid.h"
#include "nvim/memory.h"
#include "nvim/ugrid.h"
@ -79,8 +80,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat
{
for (int row = top; row <= bot; row++) {
UGRID_FOREACH_CELL(grid, row, left, right + 1, {
cell->data[0] = ' ';
cell->data[1] = 0;
cell->data = schar_from_ascii(' ');
cell->attr = attr;
});
}

View File

@ -11,10 +11,8 @@ struct ugrid;
typedef struct ucell UCell;
typedef struct ugrid UGrid;
#define CELLBYTES (sizeof(schar_T))
struct ucell {
char data[CELLBYTES + 1];
schar_T data;
sattr_T attr;
};

View File

@ -55,7 +55,7 @@ static int msg_current_row = INT_MAX;
static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
static schar_T msg_sep_char = { ' ', NUL };
static schar_T msg_sep_char = schar_from_ascii(' ');
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
@ -354,7 +354,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
grid = &msg_grid;
sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
for (int i = col; i < until; i++) {
memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf));
linebuf[i - startcol] = msg_sep_char;
attrbuf[i - startcol] = msg_sep_attr;
}
} else {
@ -363,9 +363,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf));
memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf));
if (grid->comp_col + grid->cols > until
&& grid->chars[off + n][0] == NUL) {
linebuf[until - 1 - startcol][0] = ' ';
linebuf[until - 1 - startcol][1] = '\0';
&& grid->chars[off + n] == NUL) {
linebuf[until - 1 - startcol] = schar_from_ascii(' ');
if (col == startcol && n == 1) {
skipstart = 0;
}
@ -378,10 +377,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
for (int i = col - (int)startcol; i < until - startcol; i += width) {
width = 1;
// negative space
bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) {
bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL;
if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) {
width = 2;
thru &= strequal((char *)linebuf[i + 1], " ");
thru &= linebuf[i + 1] == schar_from_ascii(' ');
}
attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
if (width == 2) {
@ -396,19 +395,18 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// Tricky: if overlap caused a doublewidth char to get cut-off, must
// replace the visible half with a space.
if (linebuf[col - startcol][0] == NUL) {
linebuf[col - startcol][0] = ' ';
linebuf[col - startcol][1] = NUL;
if (linebuf[col - startcol] == NUL) {
linebuf[col - startcol] = schar_from_ascii(' ');
if (col == endcol - 1) {
skipend = 0;
}
} else if (col == startcol && n > 1 && linebuf[1][0] == NUL) {
} else if (col == startcol && n > 1 && linebuf[1] == NUL) {
skipstart = 0;
}
col = until;
}
if (linebuf[endcol - startcol - 1][0] == NUL) {
if (linebuf[endcol - startcol - 1] == NUL) {
skipend = 0;
}
@ -568,7 +566,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
if (scrolled && row > 0) {
msg_sep_row = (int)row - 1;
if (sep_char.data) {
xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
msg_sep_char = schar_from_buf(sep_char.data, sep_char.size);
}
} else {
msg_sep_row = -1;

View File

@ -160,6 +160,22 @@ describe("multibyte rendering", function()
{1:~ }|
|
]]}
-- nvim will reset the zalgo text^W^W glyph cache if it gets too full.
-- this should be exceedingly rare, but fake it to make sure it works
meths._invalidate_glyph_cache()
screen:expect{grid=[[
^L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ |
ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ |
U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ |
u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ |
D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ |
ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ |
š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ |
o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚ |
{1:~ }|
|
]], reset=true}
end)
end)