mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #25214 from bfredl/glyphcache
refactor(grid): change schar_T representation to be more compact
This commit is contained in:
commit
1db45a9c1f
3
runtime/lua/vim/_meta/api.lua
generated
3
runtime/lua/vim/_meta/api.lua
generated
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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"
|
||||
@ -350,7 +350,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);
|
||||
@ -522,7 +522,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 },
|
||||
@ -533,7 +533,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;
|
||||
|
@ -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;
|
||||
|
@ -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++;
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
|
178
src/nvim/grid.c
178
src/nvim/grid.c
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
102
src/nvim/map_glyph_cache.c
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user