Skip to content

Commit d070c6e

Browse files
committed
Add support for VS-16 changing the width of the base character but don't enable it.
Turning this on breaks vim because it doesn't handle VS-16, and then draws over the right-hand cell, erasing the character. See issue 9185
1 parent 05ad9b5 commit d070c6e

File tree

9 files changed

+200
-9
lines changed

9 files changed

+200
-9
lines changed

sources/NSCharacterSet+iTerm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
+ (instancetype)spacingCombiningMarksForUnicodeVersion:(int)version;
2525

26+
+ (instancetype)emojiAcceptingVS16;
27+
2628
+ (instancetype)codePointsWithOwnCell;
2729

2830
+ (NSCharacterSet *)urlCharacterSet;

sources/NSCharacterSet+iTerm.m

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,4 +2186,132 @@ + (NSCharacterSet *)urlCharacterSet {
21862186
return urlChars;
21872187
}
21882188

2189+
+ (instancetype)emojiAcceptingVS16 {
2190+
static dispatch_once_t onceToken;
2191+
static NSMutableCharacterSet *emoji;
2192+
dispatch_once(&onceToken, ^{
2193+
emoji = [[NSMutableCharacterSet alloc] init];
2194+
[emoji addCharactersInRange:NSMakeRange(0xa9, 1)];
2195+
[emoji addCharactersInRange:NSMakeRange(0xae, 1)];
2196+
[emoji addCharactersInRange:NSMakeRange(0x203c, 1)];
2197+
[emoji addCharactersInRange:NSMakeRange(0x2049, 1)];
2198+
[emoji addCharactersInRange:NSMakeRange(0x2122, 1)];
2199+
[emoji addCharactersInRange:NSMakeRange(0x2139, 1)];
2200+
[emoji addCharactersInRange:NSMakeRange(0x2194, 6)];
2201+
[emoji addCharactersInRange:NSMakeRange(0x21a9, 2)];
2202+
[emoji addCharactersInRange:NSMakeRange(0x2328, 1)];
2203+
[emoji addCharactersInRange:NSMakeRange(0x23cf, 1)];
2204+
[emoji addCharactersInRange:NSMakeRange(0x23ed, 3)];
2205+
[emoji addCharactersInRange:NSMakeRange(0x23f1, 2)];
2206+
[emoji addCharactersInRange:NSMakeRange(0x23f8, 3)];
2207+
[emoji addCharactersInRange:NSMakeRange(0x24c2, 1)];
2208+
[emoji addCharactersInRange:NSMakeRange(0x25aa, 2)];
2209+
[emoji addCharactersInRange:NSMakeRange(0x25b6, 1)];
2210+
[emoji addCharactersInRange:NSMakeRange(0x25c0, 1)];
2211+
[emoji addCharactersInRange:NSMakeRange(0x25fb, 2)];
2212+
[emoji addCharactersInRange:NSMakeRange(0x2600, 5)];
2213+
[emoji addCharactersInRange:NSMakeRange(0x260e, 1)];
2214+
[emoji addCharactersInRange:NSMakeRange(0x2611, 1)];
2215+
[emoji addCharactersInRange:NSMakeRange(0x2618, 1)];
2216+
[emoji addCharactersInRange:NSMakeRange(0x261d, 1)];
2217+
[emoji addCharactersInRange:NSMakeRange(0x2620, 1)];
2218+
[emoji addCharactersInRange:NSMakeRange(0x2622, 2)];
2219+
[emoji addCharactersInRange:NSMakeRange(0x2626, 1)];
2220+
[emoji addCharactersInRange:NSMakeRange(0x262a, 1)];
2221+
[emoji addCharactersInRange:NSMakeRange(0x262e, 2)];
2222+
[emoji addCharactersInRange:NSMakeRange(0x2638, 3)];
2223+
[emoji addCharactersInRange:NSMakeRange(0x2640, 1)];
2224+
[emoji addCharactersInRange:NSMakeRange(0x2642, 1)];
2225+
[emoji addCharactersInRange:NSMakeRange(0x265f, 2)];
2226+
[emoji addCharactersInRange:NSMakeRange(0x2663, 1)];
2227+
[emoji addCharactersInRange:NSMakeRange(0x2665, 2)];
2228+
[emoji addCharactersInRange:NSMakeRange(0x2668, 1)];
2229+
[emoji addCharactersInRange:NSMakeRange(0x267b, 1)];
2230+
[emoji addCharactersInRange:NSMakeRange(0x267e, 1)];
2231+
[emoji addCharactersInRange:NSMakeRange(0x2692, 1)];
2232+
[emoji addCharactersInRange:NSMakeRange(0x2694, 4)];
2233+
[emoji addCharactersInRange:NSMakeRange(0x2699, 1)];
2234+
[emoji addCharactersInRange:NSMakeRange(0x269b, 2)];
2235+
[emoji addCharactersInRange:NSMakeRange(0x26a0, 1)];
2236+
[emoji addCharactersInRange:NSMakeRange(0x26a7, 1)];
2237+
[emoji addCharactersInRange:NSMakeRange(0x26b0, 2)];
2238+
[emoji addCharactersInRange:NSMakeRange(0x26c8, 1)];
2239+
[emoji addCharactersInRange:NSMakeRange(0x26cf, 1)];
2240+
[emoji addCharactersInRange:NSMakeRange(0x26d1, 1)];
2241+
[emoji addCharactersInRange:NSMakeRange(0x26d3, 1)];
2242+
[emoji addCharactersInRange:NSMakeRange(0x26e9, 1)];
2243+
[emoji addCharactersInRange:NSMakeRange(0x26f0, 2)];
2244+
[emoji addCharactersInRange:NSMakeRange(0x26f4, 1)];
2245+
[emoji addCharactersInRange:NSMakeRange(0x26f7, 3)];
2246+
[emoji addCharactersInRange:NSMakeRange(0x2702, 1)];
2247+
[emoji addCharactersInRange:NSMakeRange(0x2708, 2)];
2248+
[emoji addCharactersInRange:NSMakeRange(0x270c, 2)];
2249+
[emoji addCharactersInRange:NSMakeRange(0x270f, 1)];
2250+
[emoji addCharactersInRange:NSMakeRange(0x2712, 1)];
2251+
[emoji addCharactersInRange:NSMakeRange(0x2714, 1)];
2252+
[emoji addCharactersInRange:NSMakeRange(0x2716, 1)];
2253+
[emoji addCharactersInRange:NSMakeRange(0x271d, 1)];
2254+
[emoji addCharactersInRange:NSMakeRange(0x2721, 1)];
2255+
[emoji addCharactersInRange:NSMakeRange(0x2733, 2)];
2256+
[emoji addCharactersInRange:NSMakeRange(0x2744, 1)];
2257+
[emoji addCharactersInRange:NSMakeRange(0x2747, 1)];
2258+
[emoji addCharactersInRange:NSMakeRange(0x2763, 2)];
2259+
[emoji addCharactersInRange:NSMakeRange(0x27a1, 1)];
2260+
[emoji addCharactersInRange:NSMakeRange(0x2934, 2)];
2261+
[emoji addCharactersInRange:NSMakeRange(0x2b05, 3)];
2262+
[emoji addCharactersInRange:NSMakeRange(0x3030, 1)];
2263+
[emoji addCharactersInRange:NSMakeRange(0x303d, 1)];
2264+
[emoji addCharactersInRange:NSMakeRange(0x3297, 1)];
2265+
[emoji addCharactersInRange:NSMakeRange(0x3299, 1)];
2266+
[emoji addCharactersInRange:NSMakeRange(0x1f170, 2)];
2267+
[emoji addCharactersInRange:NSMakeRange(0x1f17e, 2)];
2268+
[emoji addCharactersInRange:NSMakeRange(0x1f202, 1)];
2269+
[emoji addCharactersInRange:NSMakeRange(0x1f237, 1)];
2270+
[emoji addCharactersInRange:NSMakeRange(0x1f321, 1)];
2271+
[emoji addCharactersInRange:NSMakeRange(0x1f324, 9)];
2272+
[emoji addCharactersInRange:NSMakeRange(0x1f336, 1)];
2273+
[emoji addCharactersInRange:NSMakeRange(0x1f37d, 1)];
2274+
[emoji addCharactersInRange:NSMakeRange(0x1f396, 2)];
2275+
[emoji addCharactersInRange:NSMakeRange(0x1f399, 3)];
2276+
[emoji addCharactersInRange:NSMakeRange(0x1f39e, 2)];
2277+
[emoji addCharactersInRange:NSMakeRange(0x1f3cb, 4)];
2278+
[emoji addCharactersInRange:NSMakeRange(0x1f3d4, 12)];
2279+
[emoji addCharactersInRange:NSMakeRange(0x1f3f3, 1)];
2280+
[emoji addCharactersInRange:NSMakeRange(0x1f3f5, 1)];
2281+
[emoji addCharactersInRange:NSMakeRange(0x1f3f7, 1)];
2282+
[emoji addCharactersInRange:NSMakeRange(0x1f43f, 1)];
2283+
[emoji addCharactersInRange:NSMakeRange(0x1f441, 1)];
2284+
[emoji addCharactersInRange:NSMakeRange(0x1f4fd, 1)];
2285+
[emoji addCharactersInRange:NSMakeRange(0x1f549, 2)];
2286+
[emoji addCharactersInRange:NSMakeRange(0x1f56f, 2)];
2287+
[emoji addCharactersInRange:NSMakeRange(0x1f573, 7)];
2288+
[emoji addCharactersInRange:NSMakeRange(0x1f587, 1)];
2289+
[emoji addCharactersInRange:NSMakeRange(0x1f58a, 4)];
2290+
[emoji addCharactersInRange:NSMakeRange(0x1f590, 1)];
2291+
[emoji addCharactersInRange:NSMakeRange(0x1f5a5, 1)];
2292+
[emoji addCharactersInRange:NSMakeRange(0x1f5a8, 1)];
2293+
[emoji addCharactersInRange:NSMakeRange(0x1f5b1, 2)];
2294+
[emoji addCharactersInRange:NSMakeRange(0x1f5bc, 1)];
2295+
[emoji addCharactersInRange:NSMakeRange(0x1f5c2, 3)];
2296+
[emoji addCharactersInRange:NSMakeRange(0x1f5d1, 3)];
2297+
[emoji addCharactersInRange:NSMakeRange(0x1f5dc, 3)];
2298+
[emoji addCharactersInRange:NSMakeRange(0x1f5e1, 1)];
2299+
[emoji addCharactersInRange:NSMakeRange(0x1f5e3, 1)];
2300+
[emoji addCharactersInRange:NSMakeRange(0x1f5e8, 1)];
2301+
[emoji addCharactersInRange:NSMakeRange(0x1f5ef, 1)];
2302+
[emoji addCharactersInRange:NSMakeRange(0x1f5f3, 1)];
2303+
[emoji addCharactersInRange:NSMakeRange(0x1f5fa, 1)];
2304+
[emoji addCharactersInRange:NSMakeRange(0x1f6cb, 1)];
2305+
[emoji addCharactersInRange:NSMakeRange(0x1f6cd, 3)];
2306+
[emoji addCharactersInRange:NSMakeRange(0x1f6e0, 6)];
2307+
[emoji addCharactersInRange:NSMakeRange(0x1f6e9, 1)];
2308+
[emoji addCharactersInRange:NSMakeRange(0x1f6f0, 1)];
2309+
[emoji addCharactersInRange:NSMakeRange(0x1f6f3, 1)];
2310+
[emoji addCharactersInRange:NSMakeRange(0x23, 1)];
2311+
[emoji addCharactersInRange:NSMakeRange(0x2a, 1)];
2312+
[emoji addCharactersInRange:NSMakeRange(0x30, 10)];
2313+
});
2314+
return emoji;
2315+
}
2316+
21892317
@end

sources/ScreenChar.m

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,9 @@ void StringToScreenChars(NSString *s,
636636
}
637637
}
638638
if (composedOrNonBmpChar) {
639+
const NSUInteger composedLength = composedOrNonBmpChar.length;
639640
// Ensure the string is not longer than what we support.
640-
if (composedOrNonBmpChar.length > kMaxParts) {
641+
if (composedLength > kMaxParts) {
641642
composedOrNonBmpChar = [composedOrNonBmpChar substringToIndex:kMaxParts];
642643

643644
// Ensure a high surrogate isn't left dangling at the end.
@@ -646,13 +647,25 @@ void StringToScreenChars(NSString *s,
646647
}
647648
}
648649
SetComplexCharInScreenChar(buf + j, composedOrNonBmpChar, normalization, spacingCombiningMark);
650+
NSInteger next = 1;
649651
UTF32Char baseChar = [composedOrNonBmpChar characterAtIndex:0];
650-
if (IsHighSurrogate(baseChar) && composedOrNonBmpChar.length > 1) {
652+
if (IsHighSurrogate(baseChar) && composedLength > 1) {
651653
baseChar = DecodeSurrogatePair(baseChar, [composedOrNonBmpChar characterAtIndex:1]);
654+
next += 1;
652655
}
653656
isDoubleWidth = [NSString isDoubleWidthCharacter:baseChar
654657
ambiguousIsDoubleWidth:ambiguousIsDoubleWidth
655658
unicodeVersion:unicodeVersion];
659+
if (!isDoubleWidth && composedLength > next) {
660+
const unichar peek = [composedOrNonBmpChar characterAtIndex:next];
661+
if (peek == 0xfe0f) {
662+
// VS16
663+
if ([[NSCharacterSet emojiAcceptingVS16] characterIsMember:baseChar] &&
664+
[iTermAdvancedSettingsModel vs16Supported]) {
665+
isDoubleWidth = YES;
666+
}
667+
}
668+
}
656669
}
657670

658671
// Append a DWC_RIGHT if the base character is double-width.

sources/VT100Screen.m

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ - (void)appendStringAtCursor:(NSString *)string {
13351335
movedBackOverDoubleWidth:&predecessorIsDoubleWidth];
13361336
NSString *augmentedString = string;
13371337
NSString *predecessorString = pred.x >= 0 ? [currentGrid_ stringForCharacterAt:pred] : nil;
1338-
BOOL augmented = predecessorString != nil;
1338+
const BOOL augmented = predecessorString != nil;
13391339
if (augmented) {
13401340
augmentedString = [predecessorString stringByAppendingString:string];
13411341
} else {
@@ -5335,12 +5335,6 @@ - (void)doPrint
53355335
collectInputForPrinting_ = NO;
53365336
}
53375337

5338-
- (BOOL)isDoubleWidthCharacter:(unichar)c {
5339-
return [NSString isDoubleWidthCharacter:c
5340-
ambiguousIsDoubleWidth:[delegate_ screenShouldTreatAmbiguousCharsAsDoubleWidth]
5341-
unicodeVersion:[delegate_ screenUnicodeVersion]];
5342-
}
5343-
53445338
- (void)popScrollbackLines:(int)linesPushed
53455339
{
53465340
// Undo the appending of the screen to scrollback

sources/iTermAdvancedSettingsModel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ extern NSString *const iTermAdvancedSettingsDidChange;
338338
+ (BOOL)useSystemCursorWhenPossible;
339339
+ (BOOL)useUnevenTabs;
340340
+ (BOOL)openProfilesInNewWindow;
341+
+ (BOOL)vs16Supported;
341342
+ (BOOL)workAroundMultiDisplayOSBug;
342343
+ (BOOL)workAroundNumericKeypadBug;
343344
+ (int)xtermVersion;

sources/iTermAdvancedSettingsModel.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ + (void)enumerateDictionaries:(void (^)(NSDictionary *))block {
606606
DEFINE_BOOL(allowTabbarInTitlebarAccessoryBigSur, NO, SECTION_EXPERIMENTAL @"Make the tab bar a titlebar accessory view in Big Sur?");
607607
DEFINE_BOOL(storeStateInSqlite, YES, SECTION_EXPERIMENTAL @"Store window restoration state in SQLite");
608608
DEFINE_BOOL(useNewContentFormat, YES, SECTION_EXPERIMENTAL @"Save unlimited amount of window contents.\nThis is going to be slow unless you enable SQLite-based window restoration too.");
609+
DEFINE_BOOL(vs16Supported, NO, SECTION_EXPERIMENTAL @"Support variation selector 16 making emoji fullwidth?");
609610

610611
#pragma mark - Scripting
611612
#define SECTION_SCRIPTING @"Scripting: "

tests/shamrock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
☘x
2+
☘️xabcdef
3+

tests/vs16

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
☘️

tools/emoji.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python3
2+
import itertools
3+
4+
def get_ranges(i):
5+
def difference(pair):
6+
x, y = pair
7+
return y - x
8+
for a, b in itertools.groupby(enumerate(i), difference):
9+
b = list(b)
10+
yield b[0][1], b[-1][1]
11+
12+
def parse(s):
13+
parts = s.split("..")
14+
if len(parts) == 1:
15+
return (int(parts[0], 16), 1)
16+
low = int(parts[0], 16)
17+
high = int(parts[1], 16)
18+
return (low, high - low + 1)
19+
20+
def output(label, variable, values):
21+
print("// " + label)
22+
nums = []
23+
for v in values:
24+
start, count = parse(v)
25+
for i in range(count):
26+
nums.append(start + i)
27+
for r in get_ranges(nums):
28+
start = r[0]
29+
count = r[1] - r[0] + 1
30+
print(" [%s addCharactersInRange:NSMakeRange(%s, %d)];" % (variable, hex(start), count))
31+
print("")
32+
33+
f = open("emoji-sequences.txt", "r")
34+
ranges = []
35+
for line in f:
36+
if line.startswith("#"):
37+
continue
38+
parts = line.split(";")
39+
if len(parts) < 1:
40+
continue
41+
sequences = parts[0].split(" ")
42+
if len(sequences) < 2:
43+
continue
44+
if sequences[1] == "FE0F":
45+
ranges.append(sequences[0])
46+
47+
48+
output("Emoji", "emoji", ranges)

0 commit comments

Comments
 (0)