-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
As part of the upcoming dependency upgrade, there will be changes to the supported Unicode version. This change will cause some emojis (grapheme clusters) to report different string widths than previously. This will impact the layout especially when there are borders. The terminal package will have the role of handling compatibility issues between the terminal and packages used to determine string width. The first functionality added to this package is to allow the overriding of the width of specific grapheme clusters (a selection using variant select 16). This will provide compatibility for any terminal that does not currently support Unicode 14 or above. For overriding of grapheme cluster width, a fork of `uniseg` was necessary to allow for an replacement map. While the width calculcation changes are applied when the UI is configured, no packages are currently using the `uniseg` functions so none of the overrides apply. Once the dependency upgrade is completed, this will provide the same results as the previous string width functions.
- Loading branch information
1 parent
1b726c8
commit 32f6bad
Showing
5 changed files
with
221 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package terminal | ||
|
||
import ( | ||
"github.com/mikelorant/committed/internal/config" | ||
|
||
"github.com/rivo/uniseg" | ||
) | ||
|
||
type graphemes struct { | ||
codepoints []rune | ||
width int | ||
} | ||
|
||
func Set(c config.Compatibility) { | ||
uniseg.GraphemeClusterWidthOverrides = overrideGraphemeClusterWidth(c) | ||
} | ||
|
||
func Clear() { | ||
uniseg.GraphemeClusterWidthOverrides = nil | ||
} | ||
|
||
func overrideGraphemeClusterWidth(c config.Compatibility) map[string]int { | ||
gs := make([]graphemes, 0) | ||
|
||
switch c { | ||
case config.CompatibilityTtyd: | ||
case config.CompatibilityKitty: | ||
default: | ||
gs = append(gs, overrideVS16()...) | ||
} | ||
|
||
return overrides(gs) | ||
} | ||
|
||
// Grapheme clusters using variant selector 16 | ||
// had their widths changed as part of Unicode 14. | ||
// Unicode < 14 = 1. | ||
// Unicode >= 14 = 2. | ||
// Required for: | ||
// - macOS Terminal (2.12.7) | ||
// - iTerm2 (3.4.23) | ||
// - VSCode (1.87.0) | ||
// - Alacritty (0.13.1) | ||
// - WezTerm (20240203) | ||
func overrideVS16() []graphemes { | ||
return []graphemes{ | ||
{codepoints: []rune{0x203c, 0xfe0f}, width: 1}, // ‼️ | ||
{codepoints: []rune{0x21a9, 0xfe0f}, width: 1}, // ↩️ | ||
{codepoints: []rune{0x2601, 0xfe0f}, width: 1}, // ☁️ | ||
{codepoints: []rune{0x267b, 0xfe0f}, width: 1}, // ♻️ | ||
{codepoints: []rune{0x2697, 0xfe0f}, width: 1}, // ⚗️ | ||
{codepoints: []rune{0x2699, 0xfe0f}, width: 1}, // ⚙️ | ||
{codepoints: []rune{0x26b0, 0xfe0f}, width: 1}, // ⚰️ | ||
{codepoints: []rune{0x270f, 0xfe0f}, width: 1}, // ✏️ | ||
{codepoints: []rune{0x2b06, 0xfe0f}, width: 1}, // ⬆️ | ||
{codepoints: []rune{0x2b07, 0xfe0f}, width: 1}, // ⬇️ | ||
} | ||
} | ||
|
||
func overrides(gs []graphemes) map[string]int { | ||
overrides := make(map[string]int, len(gs)) | ||
for _, g := range gs { | ||
key := string(g.codepoints) | ||
overrides[key] = g.width | ||
} | ||
|
||
return overrides | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package terminal_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/mikelorant/committed/internal/config" | ||
"github.com/mikelorant/committed/internal/terminal" | ||
|
||
"github.com/rivo/uniseg" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSet(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
compat config.Compatibility | ||
value string | ||
width int | ||
}{ | ||
{ | ||
name: "default empty", | ||
width: 0, | ||
}, | ||
{ | ||
name: "default simple", | ||
value: "test", | ||
width: 4, | ||
}, | ||
{ | ||
name: "default single emoji without override", | ||
value: "❤️", | ||
width: 2, | ||
}, | ||
{ | ||
name: "default single emoji with override", | ||
value: "⬆️", | ||
width: 1, | ||
}, | ||
{ | ||
name: "default multiple emojis without override", | ||
value: "❤️❤️", | ||
width: 4, | ||
}, | ||
{ | ||
name: "default multiple emojis with override", | ||
value: "⬆️⬆️", | ||
width: 2, | ||
}, | ||
{ | ||
name: "default mixed emojis", | ||
value: "⬆️❤️", | ||
width: 3, | ||
}, | ||
{ | ||
name: "default multiple mixed emojis", | ||
value: "⬆️❤️❤️⬆️", | ||
width: 6, | ||
}, | ||
{ | ||
name: "ttyd empty", | ||
compat: config.CompatibilityTtyd, | ||
value: "", | ||
width: 0, | ||
}, | ||
{ | ||
name: "ttyd simple", | ||
compat: config.CompatibilityTtyd, | ||
value: "test", | ||
width: 4, | ||
}, | ||
{ | ||
name: "ttyd multiple emojis", | ||
compat: config.CompatibilityTtyd, | ||
value: "⬆️❤️", | ||
width: 4, | ||
}, | ||
{ | ||
name: "kitty empty", | ||
compat: config.CompatibilityKitty, | ||
value: "", | ||
width: 0, | ||
}, | ||
{ | ||
name: "ttyd simple", | ||
compat: config.CompatibilityKitty, | ||
value: "test", | ||
width: 4, | ||
}, | ||
{ | ||
name: "ttyd multiple emojis", | ||
compat: config.CompatibilityKitty, | ||
value: "⬆️❤️", | ||
width: 4, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
terminal.Set(tt.compat) | ||
|
||
width := uniseg.StringWidth(tt.value) | ||
assert.Equal(t, tt.width, width) | ||
|
||
terminal.Clear() | ||
}) | ||
} | ||
} | ||
|
||
func TestClear(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
value string | ||
width int | ||
orWidth int | ||
}{ | ||
{ | ||
name: "default without override", | ||
value: "❤️", | ||
width: 2, | ||
orWidth: 2, | ||
}, | ||
{ | ||
name: "default with override", | ||
value: "⬆️", | ||
width: 2, | ||
orWidth: 1, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
width := uniseg.StringWidth(tt.value) | ||
assert.Equal(t, tt.width, width) | ||
|
||
terminal.Set(config.CompatibilityDefault) | ||
|
||
width = uniseg.StringWidth(tt.value) | ||
assert.Equal(t, tt.orWidth, width) | ||
|
||
terminal.Clear() | ||
|
||
width = uniseg.StringWidth(tt.value) | ||
assert.Equal(t, tt.width, width) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters