| 1 | //go:build windows |
| 2 | |
| 3 | package cli |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "sync" |
| 8 | "unicode/utf16" |
| 9 | "unsafe" |
| 10 | |
| 11 | "golang.org/x/sys/windows" |
| 12 | ) |
| 13 | |
| 14 | const ( |
| 15 | consoleTextmodeBuffer = 1 |
| 16 | codePageUSASCII = 20127 |
| 17 | ) |
| 18 | |
| 19 | var ( |
| 20 | kernel32DLL = windows.NewLazySystemDLL("kernel32.dll") |
| 21 | procCreateConsoleScreenBuffer = kernel32DLL.NewProc("CreateConsoleScreenBuffer") |
| 22 | procWideCharToMultiByte = kernel32DLL.NewProc("WideCharToMultiByte") |
| 23 | ) |
| 24 | |
| 25 | // measureBuffer is a never-activated screen buffer of the process's console: |
| 26 | // it shares the window's font, so writing a rune into it and reading the cursor |
| 27 | // back gives the width the console draws without touching the visible screen. |
| 28 | var measureBuffer = sync.OnceValue(func() windows.Handle { |
| 29 | h, _, _ := procCreateConsoleScreenBuffer.Call( |
| 30 | uintptr(windows.GENERIC_READ|windows.GENERIC_WRITE), |
| 31 | uintptr(windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE), |
| 32 | 0, consoleTextmodeBuffer, 0) |
| 33 | if h == 0 || windows.Handle(h) == windows.InvalidHandle { |
| 34 | return windows.InvalidHandle |
| 35 | } |
| 36 | return windows.Handle(h) |
| 37 | }) |
| 38 | |
| 39 | // newConsoleGlyphFit returns nil unless out is a console the process can |
| 40 | // measure. A pseudoconsole measures ambiguous-width runes as one cell, so under |
| 41 | // Windows Terminal or an SSH session nothing is replaced. |
| 42 | func newConsoleGlyphFit(out *os.File) *glyphFit { |
| 43 | var mode uint32 |
| 44 | if windows.GetConsoleMode(windows.Handle(out.Fd()), &mode) != nil { |
| 45 | return nil |
| 46 | } |
| 47 | h := measureBuffer() |
| 48 | if h == windows.InvalidHandle { |
| 49 | return nil |
| 50 | } |
| 51 | return newGlyphFit(func(r rune) int { return consoleCells(h, r) }, asciiBestFit) |
| 52 | } |
| 53 | |
| 54 | func consoleCells(h windows.Handle, r rune) int { |
| 55 | if windows.SetConsoleCursorPosition(h, windows.Coord{}) != nil { |
| 56 | return 0 |
| 57 | } |
| 58 | u := utf16.Encode([]rune{r}) |
| 59 | var written uint32 |
| 60 | if windows.WriteConsole(h, &u[0], uint32(len(u)), &written, nil) != nil { |
| 61 | return 0 |
| 62 | } |
| 63 | var info windows.ConsoleScreenBufferInfo |
| 64 | if windows.GetConsoleScreenBufferInfo(h, &info) != nil || info.CursorPosition.Y != 0 { |
| 65 | return 0 |
| 66 | } |
| 67 | return int(info.CursorPosition.X) |
| 68 | } |
| 69 | |
| 70 | // asciiBestFit asks the OS for its US-ASCII best-fit of r, whose unmapped |
| 71 | // answer is the code page's default character. |
| 72 | func asciiBestFit(r rune) rune { |
| 73 | u := utf16.Encode([]rune{r}) |
| 74 | var out [2]byte |
| 75 | n, _, _ := procWideCharToMultiByte.Call(codePageUSASCII, 0, |
| 76 | uintptr(unsafe.Pointer(&u[0])), uintptr(len(u)), |
| 77 | uintptr(unsafe.Pointer(&out[0])), uintptr(len(out)), 0, 0) |
| 78 | if n != 1 || out[0] < 0x20 || out[0] > 0x7e { |
| 79 | return 0 |
| 80 | } |
| 81 | return rune(out[0]) |
| 82 | } |
| 83 |