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 num int 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.Println("All tests passed!") } return } func has_control_chars(text string) bool { for _, ch := range text { if ch < ' ' { return true } } return false } func main(allowed_tests *utils.Set[int]) (rc int, err error) { var tests []*test_struct if gb_tests, err := kitty.LoadGraphemeBreakTests(); err == nil { for _, t := range gb_tests { test_num := len(tests) + 1 desc := fmt.Sprintf("Test: #%d: Unicode GraphemeBreakTest: %s", test_num, t.Comment) text := strings.Join(t.Data, "") if has_control_chars(text) { continue } payload := " " + text + cursor_position_report test := test_struct{num: test_num, description: desc, payload: payload, expected_cursor_positions: []int{1 + wcswidth.Stringwidth(text)}} tests = append(tests, &test) } if allowed_tests.Len() > 0 { temp := make([]*test_struct, 0, len(tests)) for _, t := range tests { if allowed_tests.Has(t.num) { temp = append(temp, t) } } tests = temp } if err = run_tests(tests); err != nil { return 1, err } } else { return 1, err } return } func WcswidthKittenEntryPoint(root *Command) { root.AddSubCommand(&Command{ Name: "__width_test__", Usage: "[test number to run...]", HelpText: "Test the terminal for compliance with the kitty text-sizing specification's splitting of text into cells. You can optionally specify specific test numbers to run.", Hidden: true, OnlyArgsAllowed: true, Run: func(cmd *Command, args []string) (rc int, err error) { allowed_tests := utils.NewSet[int]() for _, arg := range args { if x, err := strconv.Atoi(arg); err == nil { allowed_tests.Add(x) } else { return 1, fmt.Errorf("%s is not a valid test number", arg) } } return main(allowed_tests) }, }) }