Skip to content

Commit 49f4005

Browse files
committed
unicode width updates
- 0xFE0F (VS16) needs to move the cursor and clear the overlapping cell when it's combined with a previous cell, which adds some complexity if the next print was going to need to wrap - tmux uses a hardcoded list of codepoints that are always forced wide since they are typically rendered as wide even if the unicode standard or wcwidth() typically considers them narrow. add that table here as well so tmux and mosh can stay in sync for these codepoints - unassigned codepoints should default to a width of 1 instead of not being printed at all, which allows unknown codepoints to still possibly be rendered
1 parent 6ceed3f commit 49f4005

File tree

2 files changed

+67
-22
lines changed

2 files changed

+67
-22
lines changed

src/terminal/terminal.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,26 @@ void Emulator::print( const Parser::Print* act )
134134
}
135135
if ( !combining_cell->full() ) {
136136
combining_cell->append( ch );
137-
if ( ch == 0xFE0F ) {
137+
// VS16 causes the previous codepoint to be rendered as its emoji representation
138+
// which could cause it to change from 1 to 2 characters wide
139+
if ( ch == 0xFE0F && !combining_cell->get_wide() ) {
140+
// have to move this emoji to the next line
141+
if ( fb.ds.auto_wrap_mode && fb.ds.next_print_will_wrap ) {
142+
fb.get_mutable_row( -1 )->set_wrap( false );
143+
fb.ds.move_col( 0 );
144+
fb.move_rows_autoscroll( 1 );
145+
*fb.get_mutable_cell() = *combining_cell;
146+
fb.reset_cell( combining_cell );
147+
fb.ds.move_col( 1, true, true );
148+
combining_cell = fb.get_combining_cell();
149+
}
138150
combining_cell->set_wide( true );
151+
if ( fb.ds.insert_mode ) {
152+
fb.insert_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() );
153+
} else if ( fb.ds.get_cursor_col() < fb.ds.get_width() ) {
154+
fb.reset_cell( fb.get_mutable_cell() );
155+
}
156+
fb.ds.move_col( 1, true, true );
139157
}
140158
}
141159
} break;

src/util/char_utils.h

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,60 @@
22
#define CHAR_UTILS_HPP
33

44
#include "widechar_width.h"
5+
#include <algorithm>
6+
#include <array>
57

68
static int mosh_wcwidth( uint32_t c )
79
{
8-
int width = widechar_wcwidth( c );
9-
if ( width >= 0 ) {
10-
return width;
11-
}
10+
int result = [c]() {
11+
int width = widechar_wcwidth( c );
12+
if ( width >= 0 ) {
13+
return width;
14+
}
15+
16+
/* https://github.com/ridiculousfish/widecharwidth/tree/master#c-usage */
17+
switch ( width ) {
18+
case widechar_nonprint:
19+
return -1;
20+
case widechar_combining:
21+
return 0;
22+
case widechar_ambiguous:
23+
return 1;
24+
case widechar_private_use:
25+
return 1;
26+
case widechar_unassigned:
27+
return 1;
28+
case widechar_non_character:
29+
return -1;
30+
case widechar_widened_in_9:
31+
return 2;
32+
default:
33+
return -1;
34+
}
35+
}();
36+
37+
static constexpr auto force_wide = std::array {
38+
0x0261D, 0x026F9, 0x0270A, 0x0270B, 0x0270C, 0x0270D, 0x1F1E6, 0x1F1E7, 0x1F1E8, 0x1F1E9, 0x1F1EA, 0x1F1EB,
39+
0x1F1EC, 0x1F1ED, 0x1F1EE, 0x1F1EF, 0x1F1F0, 0x1F1F1, 0x1F1F2, 0x1F1F3, 0x1F1F4, 0x1F1F5, 0x1F1F6, 0x1F1F7,
40+
0x1F1F8, 0x1F1F9, 0x1F1FA, 0x1F1FB, 0x1F1FC, 0x1F1FD, 0x1F1FE, 0x1F1FF, 0x1F385, 0x1F3C2, 0x1F3C3, 0x1F3C4,
41+
0x1F3C7, 0x1F3CA, 0x1F3CB, 0x1F3CC, 0x1F3FB, 0x1F3FC, 0x1F3FD, 0x1F3FE, 0x1F3FF, 0x1F442, 0x1F443, 0x1F446,
42+
0x1F447, 0x1F448, 0x1F449, 0x1F44A, 0x1F44B, 0x1F44C, 0x1F44D, 0x1F44E, 0x1F44F, 0x1F450, 0x1F466, 0x1F467,
43+
0x1F468, 0x1F469, 0x1F46B, 0x1F46C, 0x1F46D, 0x1F46E, 0x1F470, 0x1F471, 0x1F472, 0x1F473, 0x1F474, 0x1F475,
44+
0x1F476, 0x1F477, 0x1F478, 0x1F47C, 0x1F481, 0x1F482, 0x1F483, 0x1F485, 0x1F486, 0x1F487, 0x1F48F, 0x1F491,
45+
0x1F4AA, 0x1F574, 0x1F575, 0x1F57A, 0x1F590, 0x1F595, 0x1F596, 0x1F645, 0x1F646, 0x1F647, 0x1F64B, 0x1F64C,
46+
0x1F64D, 0x1F64E, 0x1F64F, 0x1F6A3, 0x1F6B4, 0x1F6B5, 0x1F6B6, 0x1F6C0, 0x1F6CC, 0x1F90C, 0x1F90F, 0x1F918,
47+
0x1F919, 0x1F91A, 0x1F91B, 0x1F91C, 0x1F91D, 0x1F91E, 0x1F91F, 0x1F926, 0x1F930, 0x1F931, 0x1F932, 0x1F933,
48+
0x1F934, 0x1F935, 0x1F936, 0x1F937, 0x1F938, 0x1F939, 0x1F93D, 0x1F93E, 0x1F977, 0x1F9B5, 0x1F9B6, 0x1F9B8,
49+
0x1F9B9, 0x1F9BB, 0x1F9CD, 0x1F9CE, 0x1F9CF, 0x1F9D1, 0x1F9D2, 0x1F9D3, 0x1F9D4, 0x1F9D5, 0x1F9D6, 0x1F9D7,
50+
0x1F9D8, 0x1F9D9, 0x1F9DA, 0x1F9DB, 0x1F9DC, 0x1F9DD, 0x1FAC3, 0x1FAC4, 0x1FAC5, 0x1FAF0, 0x1FAF1, 0x1FAF2,
51+
0x1FAF3, 0x1FAF4, 0x1FAF5, 0x1FAF6, 0x1FAF7, 0x1FAF8 };
1252

13-
/* https://github.com/ridiculousfish/widecharwidth/tree/master#c-usage */
14-
switch ( width ) {
15-
case widechar_nonprint:
16-
return -1;
17-
case widechar_combining:
18-
return 0;
19-
case widechar_ambiguous:
20-
return 1;
21-
case widechar_private_use:
22-
return 1;
23-
case widechar_unassigned:
24-
return -1;
25-
case widechar_non_character:
26-
return -1;
27-
case widechar_widened_in_9:
53+
if ( result == 1 ) {
54+
if ( std::binary_search( force_wide.begin(), force_wide.end(), c ) ) {
2855
return 2;
29-
default:
30-
return -1;
56+
}
3157
}
58+
return result;
3259
}
3360

3461
#endif

0 commit comments

Comments
 (0)