mirror of
https://github.com/neovim/neovim.git
synced 2026-08-28 18:11:47 +00:00
Merge pull request #31635 from bfredl/vtermpirates
feat(terminal): support grapheme clusters, including emoji
This commit is contained in:
@@ -400,15 +400,28 @@ describe(':terminal buffer', function()
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('truncates number of composing characters to 5', function()
|
||||
it('truncates the size of grapheme clusters', function()
|
||||
local chan = api.nvim_open_term(0, {})
|
||||
local composing = ('a̳'):sub(2)
|
||||
api.nvim_chan_send(chan, 'a' .. composing:rep(8))
|
||||
api.nvim_chan_send(chan, 'a' .. composing:rep(20))
|
||||
retry(nil, nil, function()
|
||||
eq('a' .. composing:rep(5), api.nvim_get_current_line())
|
||||
eq('a' .. composing:rep(14), api.nvim_get_current_line())
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles extended grapheme clusters', function()
|
||||
local screen = Screen.new(50, 7)
|
||||
feed 'i'
|
||||
local chan = api.nvim_open_term(0, {})
|
||||
api.nvim_chan_send(chan, '🏴☠️ yarrr')
|
||||
screen:expect([[
|
||||
🏴☠️ yarrr^ |
|
||||
|*5
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
eq('🏴☠️ yarrr', api.nvim_get_current_line())
|
||||
end)
|
||||
|
||||
it('handles split UTF-8 sequences #16245', function()
|
||||
local screen = Screen.new(50, 7)
|
||||
fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include "nvim/grid.h"
|
||||
#include "nvim/mbyte.h"
|
||||
|
||||
#include "vterm_test.h"
|
||||
|
||||
@@ -202,6 +204,26 @@ int selection_query(VTermSelectionMask mask, void *user)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_schar(FILE *f, schar_T schar) {
|
||||
char buf[MAX_SCHAR_SIZE];
|
||||
schar_get(buf, schar);
|
||||
StrCharInfo ci = utf_ptr2StrCharInfo(buf);
|
||||
bool did = false;
|
||||
while (*ci.ptr != 0) {
|
||||
if (did) {
|
||||
fprintf(f, ",");
|
||||
}
|
||||
|
||||
if (ci.chr.len == 1 && ci.chr.value >= 0x80) {
|
||||
fprintf(f, "??%x", ci.chr.value);
|
||||
} else {
|
||||
fprintf(f, "%x", ci.chr.value);
|
||||
}
|
||||
did = true;
|
||||
ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len);
|
||||
}
|
||||
}
|
||||
|
||||
bool want_state_putglyph;
|
||||
int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
|
||||
{
|
||||
@@ -211,9 +233,7 @@ int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
|
||||
|
||||
FILE *f = fopen(VTERM_TEST_FILE, "a");
|
||||
fprintf(f, "putglyph ");
|
||||
for (int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
|
||||
fprintf(f, i ? ",%x" : "%x", info->chars[i]);
|
||||
}
|
||||
print_schar(f, info->schar);
|
||||
fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
|
||||
if (info->protected_cell) {
|
||||
fprintf(f, " prot");
|
||||
@@ -443,14 +463,15 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
|
||||
}
|
||||
|
||||
int eol = cols;
|
||||
while (eol && !cells[eol - 1].chars[0]) {
|
||||
while (eol && !cells[eol-1].schar) {
|
||||
eol--;
|
||||
}
|
||||
|
||||
FILE *f = fopen(VTERM_TEST_FILE, "a");
|
||||
fprintf(f, "sb_pushline %d =", cols);
|
||||
for (int c = 0; c < eol; c++) {
|
||||
fprintf(f, " %02X", cells[c].chars[0]);
|
||||
fprintf(f, " ");
|
||||
print_schar(f, cells[c].schar);
|
||||
}
|
||||
fprintf(f, "\n");
|
||||
|
||||
@@ -467,10 +488,10 @@ int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
|
||||
|
||||
// All lines of scrollback contain "ABCDE"
|
||||
for (int col = 0; col < cols; col++) {
|
||||
if (col < 5) {
|
||||
cells[col].chars[0] = (uint32_t)('A' + col);
|
||||
if(col < 5) {
|
||||
cells[col].schar = schar_from_ascii((uint32_t)('A' + col));
|
||||
} else {
|
||||
cells[col].chars[0] = 0;
|
||||
cells[col].schar = 0;
|
||||
}
|
||||
|
||||
cells[col].width = 1;
|
||||
|
||||
@@ -17,7 +17,6 @@ local bit = require('bit')
|
||||
--- @field VTERM_KEY_NONE integer
|
||||
--- @field VTERM_KEY_TAB integer
|
||||
--- @field VTERM_KEY_UP integer
|
||||
--- @field VTERM_MAX_CHARS_PER_CELL integer
|
||||
--- @field VTERM_MOD_ALT integer
|
||||
--- @field VTERM_MOD_CTRL integer
|
||||
--- @field VTERM_MOD_SHIFT integer
|
||||
@@ -80,6 +79,8 @@ local bit = require('bit')
|
||||
--- @field vterm_state_set_selection_callbacks function
|
||||
--- @field vterm_state_set_unrecognised_fallbacks function
|
||||
local vterm = t.cimport(
|
||||
'./src/nvim/mbyte.h',
|
||||
'./src/nvim/grid.h',
|
||||
'./src/vterm/vterm.h',
|
||||
'./src/vterm/vterm_internal.h',
|
||||
'./test/unit/fixtures/vterm_test.h'
|
||||
@@ -302,16 +303,12 @@ local function screen_chars(start_row, start_col, end_row, end_col, expected, sc
|
||||
rect['end_row'] = end_row
|
||||
rect['end_col'] = end_col
|
||||
|
||||
local len = vterm.vterm_screen_get_chars(screen, nil, 0, rect)
|
||||
local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
|
||||
|
||||
local chars = t.ffi.new('uint32_t[?]', len)
|
||||
vterm.vterm_screen_get_chars(screen, chars, len, rect)
|
||||
|
||||
local actual = ''
|
||||
for i = 0, tonumber(len) - 1 do
|
||||
actual = actual .. string.char(chars[i])
|
||||
end
|
||||
local text = t.ffi.new('unsigned char[?]', len)
|
||||
vterm.vterm_screen_get_text(screen, text, len, rect)
|
||||
|
||||
local actual = t.ffi.string(text, len)
|
||||
t.eq(expected, actual)
|
||||
end
|
||||
|
||||
@@ -349,7 +346,7 @@ local function screen_row(row, expected, screen, end_col)
|
||||
local text = t.ffi.new('unsigned char[?]', len)
|
||||
vterm.vterm_screen_get_text(screen, text, len, rect)
|
||||
|
||||
t.eq(expected, t.ffi.string(text))
|
||||
t.eq(expected, t.ffi.string(text, len))
|
||||
end
|
||||
|
||||
local function screen_cell(row, col, expected, screen)
|
||||
@@ -360,14 +357,20 @@ local function screen_cell(row, col, expected, screen)
|
||||
local cell = t.ffi.new('VTermScreenCell')
|
||||
vterm.vterm_screen_get_cell(screen, pos, cell)
|
||||
|
||||
local buf = t.ffi.new('unsigned char[32]')
|
||||
vterm.schar_get(buf, cell.schar)
|
||||
|
||||
local actual = '{'
|
||||
for i = 0, vterm.VTERM_MAX_CHARS_PER_CELL - 1 do
|
||||
if cell['chars'][i] ~= 0 then
|
||||
if i > 0 then
|
||||
actual = actual .. ','
|
||||
end
|
||||
actual = string.format('%s%02x', actual, cell['chars'][i])
|
||||
local i = 0
|
||||
while buf[i] > 0 do
|
||||
local char = vterm.utf_ptr2char(buf + i)
|
||||
local charlen = vterm.utf_ptr2len(buf + i)
|
||||
if i > 0 then
|
||||
actual = actual .. ','
|
||||
end
|
||||
local invalid = char >= 128 and charlen == 1
|
||||
actual = string.format('%s%s%02x', actual, invalid and '?' or '', char)
|
||||
i = i + charlen
|
||||
end
|
||||
actual = string.format('%s} width=%d attrs={', actual, cell['width'])
|
||||
actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
|
||||
@@ -962,8 +965,8 @@ describe('vterm', function()
|
||||
|
||||
-- Spare combining chars get truncated
|
||||
reset(state, nil)
|
||||
push('e' .. string.rep('\xCC\x81', 10), vt)
|
||||
expect('putglyph 65,301,301,301,301,301 1 0,0') -- and nothing more
|
||||
push('e' .. string.rep('\xCC\x81', 20), vt)
|
||||
expect('putglyph 65,301,301,301,301,301,301,301,301,301,301,301,301,301,301 1 0,0') -- and nothing more
|
||||
|
||||
reset(state, nil)
|
||||
push('e', vt)
|
||||
@@ -973,6 +976,34 @@ describe('vterm', function()
|
||||
push('\xCC\x82', vt)
|
||||
expect('putglyph 65,301,302 1 0,0')
|
||||
|
||||
-- emoji with ZWJ and variant selectors, as one chunk
|
||||
reset(state, nil)
|
||||
push('🏳️🌈🏳️⚧️🏴☠️', vt)
|
||||
expect([[putglyph 1f3f3,fe0f,200d,1f308 2 0,0
|
||||
putglyph 1f3f3,fe0f,200d,26a7,fe0f 2 0,2
|
||||
putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
|
||||
|
||||
-- emoji, one code point at a time
|
||||
reset(state, nil)
|
||||
push('🏳', vt)
|
||||
expect('putglyph 1f3f3 2 0,0')
|
||||
push('\xef\xb8\x8f', vt)
|
||||
expect('putglyph 1f3f3,fe0f 2 0,0')
|
||||
push('\xe2\x80\x8d', vt)
|
||||
expect('putglyph 1f3f3,fe0f,200d 2 0,0')
|
||||
push('🌈', vt)
|
||||
expect('putglyph 1f3f3,fe0f,200d,1f308 2 0,0')
|
||||
|
||||
-- modifier can change width
|
||||
push('❤', vt)
|
||||
expect('putglyph 2764 1 0,2')
|
||||
push('\xef\xb8\x8f', vt)
|
||||
expect('putglyph 2764,fe0f 2 0,2')
|
||||
|
||||
-- also works batched
|
||||
push('❤️', vt)
|
||||
expect('putglyph 2764,fe0f 2 0,4')
|
||||
|
||||
-- DECSCA protected
|
||||
reset(state, nil)
|
||||
push('A\x1b[1"qB\x1b[2"qC', vt)
|
||||
@@ -3046,7 +3077,7 @@ describe('vterm', function()
|
||||
screen_cell(
|
||||
0,
|
||||
0,
|
||||
'{65,301,302,303,304,305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
|
||||
'{65,301,302,303,304,305,306,307,308,309,30a} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
|
||||
screen
|
||||
)
|
||||
|
||||
@@ -3063,7 +3094,7 @@ describe('vterm', function()
|
||||
screen_cell(
|
||||
0,
|
||||
0,
|
||||
'{65,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
|
||||
'{65,301,301,301,301,301,301,301,301,301,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)',
|
||||
screen
|
||||
)
|
||||
|
||||
@@ -3072,6 +3103,16 @@ describe('vterm', function()
|
||||
push('\x1b[80G\xEF\xBC\x90', vt)
|
||||
screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
|
||||
screen_cell(1, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
|
||||
|
||||
-- Outputting emoji with ZWJ and variant selectors
|
||||
reset(nil, screen)
|
||||
push('🏳️🌈🏳️⚧️🏴☠️', vt)
|
||||
|
||||
-- stylua: ignore start
|
||||
screen_cell(0, 0, '{1f3f3,fe0f,200d,1f308} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
|
||||
screen_cell(0, 2, '{1f3f3,fe0f,200d,26a7,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
|
||||
screen_cell(0, 4, '{1f3f4,200d,2620,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen)
|
||||
-- stylua: ignore end
|
||||
end)
|
||||
|
||||
pending('62screen_damage', function() end)
|
||||
@@ -3125,7 +3166,7 @@ describe('vterm', function()
|
||||
screen = wantscreen(vt, { b = true })
|
||||
resize(20, 80, vt)
|
||||
expect(
|
||||
'sb_pushline 80 = 54 6F 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
|
||||
'sb_pushline 80 = 54 6f 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 ='
|
||||
)
|
||||
-- TODO(dundargoc): fix or remove
|
||||
-- screen_row( 0 , "",screen)
|
||||
|
||||
Reference in New Issue
Block a user