From 3a4d32b85b67f4bc34f45b263464d9ddfd2d07ae Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Apr 2025 10:45:39 +0530 Subject: [PATCH] Get basic full string width tests running --- embeds.go | 15 +++- tools/cli/wcswidth_kitten.go | 118 ++++++++++++++++++++++++++++++ tools/tui/loop/key-encoding.go | 2 +- tools/wcswidth/char-props_test.go | 13 +--- 4 files changed, 137 insertions(+), 11 deletions(-) diff --git a/embeds.go b/embeds.go index eb7e08ec4..d9e36a75f 100644 --- a/embeds.go +++ b/embeds.go @@ -2,10 +2,23 @@ package kitty import ( _ "embed" + "encoding/json" "fmt" ) var _ = fmt.Print //go:embed kitty_tests/GraphemeBreakTest.json -var GraphemeBreakTestData []byte +var grapheme_break_test_data []byte + +type GraphemeBreakTest struct { + Data []string `json:"data"` + Comment string `json:"comment"` +} + +func LoadGraphemeBreakTests() (ans []GraphemeBreakTest, err error) { + if err := json.Unmarshal(grapheme_break_test_data, &ans); err != nil { + return nil, fmt.Errorf("Failed to parse GraphemeBreakTest JSON with error: %s", err) + } + return +} diff --git a/tools/cli/wcswidth_kitten.go b/tools/cli/wcswidth_kitten.go index 09a6c9417..243dda08c 100644 --- a/tools/cli/wcswidth_kitten.go +++ b/tools/cli/wcswidth_kitten.go @@ -1,12 +1,130 @@ package cli import ( + "bytes" "fmt" + "strconv" + "strings" + + "github.com/google/go-cmp/cmp" + + "kitty" + "kitty/tools/tui/loop" + "kitty/tools/utils" + "kitty/tools/wcswidth" ) var _ = fmt.Print +type test_struct struct { + description string + expected_cursor_positions []int + actual_cursor_positions []int + payload string +} + +const cursor_position_report = "\x1b[6n" + +func run_tests(tests []*test_struct) (err error) { + lp, err := loop.New(loop.NoAlternateScreen) + if err != nil { + return err + } + buf := strings.Builder{} + buf.WriteString(loop.PENDING_UPDATE.EscapeCodeToSet()) + for _, t := range tests { + buf.WriteString(t.payload) + buf.WriteString("\r\x1b[K") + if buf.Len() > 512*1024 { + buf.WriteString(loop.PENDING_UPDATE.EscapeCodeToReset()) + buf.WriteString(loop.PENDING_UPDATE.EscapeCodeToSet()) + } + } + buf.WriteString(loop.PENDING_UPDATE.EscapeCodeToReset()) + buf.WriteString("\x1b[c") + + lp.OnInitialize = func() (string, error) { + lp.SetCursorVisible(false) + lp.Printf("Running %d tests, please wait...\n", len(tests)) + lp.QueueWriteString(buf.String()) + return "", err + } + lp.OnFinalize = func() string { + lp.SetCursorVisible(true) + return "" + } + current_test_idx := 0 + lp.OnEscapeCode = func(typ loop.EscapeCodeType, data []byte) error { + if typ == loop.CSI { + switch data[len(data)-1] { + case 'c': + lp.Quit(0) + case 'R': + if idx := bytes.IndexByte(data, ';'); idx > -1 { + if xpos, e := strconv.Atoi(utils.UnsafeBytesToString(data[idx+1 : len(data)-1])); e == nil { + t := tests[current_test_idx] + if len(t.actual_cursor_positions) >= len(t.expected_cursor_positions) && current_test_idx+1 < len(tests) { + current_test_idx += 1 + t = tests[current_test_idx] + } + t.actual_cursor_positions = append(t.actual_cursor_positions, xpos-1) + } + } + } + } + return nil + } + if err = lp.Run(); err != nil { + return err + } + return show_results(tests) +} + +func show_results(tests []*test_struct) (err error) { + num_failures := 0 + for _, t := range tests { + if diff := cmp.Diff(t.expected_cursor_positions, t.actual_cursor_positions); diff != "" { + fmt.Println("\x1b[31mTest failed\x1b[39m:", t.description) + fmt.Println(diff) + num_failures++ + } + } + if num_failures > 0 { + err = fmt.Errorf("%d out of %d tests failed.", num_failures, len(tests)) + } else { + fmt.Printf("All %d tests passed!\n", len(tests)) + } + return +} + +func has_control_chars(text string) bool { + for _, ch := range text { + if ch < ' ' { + return true + } + } + return false +} + func main() (rc int, err error) { + var tests []*test_struct + if gb_tests, err := kitty.LoadGraphemeBreakTests(); err == nil { + for i, t := range gb_tests { + desc := fmt.Sprintf("Unicode GraphemeBreakTest: #%d (%s)", i, t.Comment) + text := strings.Join(t.Data, "") + if has_control_chars(text) { + continue + } + payload := " " + text + cursor_position_report + test := test_struct{description: desc, payload: payload, expected_cursor_positions: []int{1 + wcswidth.Stringwidth(text)}} + tests = append(tests, &test) + } + if err = run_tests(tests); err != nil { + return 1, err + } + } else { + return 1, err + } return } diff --git a/tools/tui/loop/key-encoding.go b/tools/tui/loop/key-encoding.go index 44f1ac8f9..9a0bf9bb4 100644 --- a/tools/tui/loop/key-encoding.go +++ b/tools/tui/loop/key-encoding.go @@ -138,7 +138,7 @@ func KeyEventFromCSI(csi string) *KeyEvent { } orig_csi := csi last_char := csi[len(csi)-1:] - if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) { + if !strings.Contains("u~ABCDEHFPQS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) { return nil } csi = csi[:len(csi)-1] diff --git a/tools/wcswidth/char-props_test.go b/tools/wcswidth/char-props_test.go index bc3d3bbca..5103465b5 100644 --- a/tools/wcswidth/char-props_test.go +++ b/tools/wcswidth/char-props_test.go @@ -1,12 +1,12 @@ package wcswidth import ( - "encoding/json" "fmt" "strings" "testing" _ "embed" + "github.com/google/go-cmp/cmp" "kitty" @@ -14,11 +14,6 @@ import ( var _ = fmt.Print -type GraphemeBreakTest struct { - Data []string `json:"data"` - Comment string `json:"comment"` -} - func TestSplitIntoGraphemes(t *testing.T) { var m = map[string][]string{ " \u0308 ": {" \u0308", " "}, @@ -29,9 +24,9 @@ func TestSplitIntoGraphemes(t *testing.T) { t.Fatalf("Failed to split %#v into graphemes: %s", text, diff) } } - tests := []GraphemeBreakTest{} - if err := json.Unmarshal(kitty.GraphemeBreakTestData, &tests); err != nil { - t.Fatalf("Failed to parse GraphemeBreakTest JSON with error: %s", err) + tests, err := kitty.LoadGraphemeBreakTests() + if err != nil { + t.Fatal(err) } for i, x := range tests { text := strings.Join(x.Data, "")