commit 814f2629cbcf02b18ff08d394d15835419e563e4
parent a12443955939185bdfaf5f35bb47c3aa8f2a07bd
Author: zeertzjq <zeertzjq@outlook.com>
Date: Thu, 5 Feb 2026 08:19:11 +0800
fix(terminal): handle split composing chars at right edge (#37694)
Problem:
Recombining composing chars in terminal doesn't work at right edge.
Solution:
Check for the case where printing the previous char didn't advance the
cursor. Reset at_phantom when returning to combine_pos.
Diffstat:
3 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c
@@ -342,13 +342,15 @@ static int on_text(const char bytes[], size_t len, void *user)
// See if the cursor has moved since
if (state->pos.row == state->combine_pos.row
- && state->pos.col == state->combine_pos.col + state->combine_width) {
+ && state->pos.col >= state->combine_pos.col
+ && state->pos.col <= state->combine_pos.col + state->combine_width) {
// This is a combining char. that needs to be merged with the previous glyph output
if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) {
// Find where we need to append these combining chars
grapheme_len = state->grapheme_len;
grapheme_state = state->grapheme_state;
state->pos.col = state->combine_pos.col;
+ state->at_phantom = 0;
recombine = true;
} else {
DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
@@ -758,7 +758,7 @@ describe(':terminal buffer', function()
end)
it('handles split UTF-8 sequences #16245', function()
- local screen = Screen.new(50, 7)
+ local screen = Screen.new(50, 10)
fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
screen:expect([[
^å |
@@ -766,7 +766,24 @@ describe(':terminal buffer', function()
1: å̲ |
2: å̲ |
3: å̲ |
- |*2
+ |
+ [Process exited 0] |
+ |*3
+ ]])
+ -- Test with Unicode char at right edge using a 4-wide terminal
+ command('bwipe! | set laststatus=0 | 4vnew')
+ fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true })
+ screen:expect([[
+ ^å │ |
+ ref:│{1:~ }|
+ å̲ │{1:~ }|
+ 1: å̲│{1:~ }|
+ 2: å̲│{1:~ }|
+ 3: å̲│{1:~ }|
+ │{1:~ }|
+ [Pro│{1:~ }|
+ cess│{1:~ }|
+ |
]])
end)
diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua
@@ -992,6 +992,7 @@ describe('vterm', function()
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
+ -- Combining across buffers multiple times
reset(state, nil)
push('e', vt)
expect('putglyph 65 1 0,0')
@@ -1000,6 +1001,32 @@ describe('vterm', function()
push('\xCC\x82', vt)
expect('putglyph 65,301,302 1 0,0')
+ -- Combining across buffers at right edge
+ reset(state, nil)
+ push('\x1b[5;80H', vt)
+ push('e', vt)
+ expect('putglyph 65 1 4,79')
+ push('\xCC\x81', vt)
+ expect('putglyph 65,301 1 4,79')
+ push('\xCC\x82Z', vt)
+ expect('putglyph 65,301,302 1 4,79\nputglyph 5a 1 5,0')
+
+ -- Combining regional indicators
+ reset(state, nil)
+ push('\x1b[5;77H', vt)
+ push('🇦', vt)
+ expect('putglyph 1f1e6 2 4,76')
+ push('🇩', vt)
+ expect('putglyph 1f1e6,1f1e9 2 4,76')
+ push('🇱', vt)
+ expect('putglyph 1f1f1 2 4,78')
+ push('🇮', vt)
+ expect('putglyph 1f1f1,1f1ee 2 4,78')
+ push('🇲', vt)
+ expect('putglyph 1f1f2 2 5,0')
+ push('🇨', vt)
+ expect('putglyph 1f1f2,1f1e8 2 5,0')
+
-- emoji with ZWJ and variant selectors, as one chunk
reset(state, nil)
push('🏳️🌈🏳️⚧️🏴☠️', vt)