gotest.tools/gotestsum@v1.11.0/internal/dotwriter/writer_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 package dotwriter 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "strings" 11 "syscall" 12 "unsafe" 13 14 "golang.org/x/sys/windows" 15 ) 16 17 var kernel32 = syscall.NewLazyDLL("kernel32.dll") 18 19 var ( 20 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") 21 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") 22 ) 23 24 // clear the line and move the cursor up 25 var clear = fmt.Sprintf("%c[%dA%c[2K\r", ESC, 0, ESC) 26 27 type dword uint32 28 29 type coord struct { 30 x int16 31 y int16 32 } 33 34 type fdWriter interface { 35 io.Writer 36 Fd() uintptr 37 } 38 39 // Flush implementation on windows is not ideal; we clear the entire screen before writing, which can result in flashing output 40 // Windows likely can adopt the same approach as posix if someone invests some effort 41 func (w *Writer) Flush() error { 42 if w.buf.Len() == 0 { 43 return nil 44 } 45 w.clearLines(w.lineCount) 46 w.lineCount = bytes.Count(w.buf.Bytes(), []byte{'\n'}) 47 _, err := w.out.Write(w.buf.Bytes()) 48 w.buf.Reset() 49 return err 50 } 51 52 func (w *Writer) clearLines(count int) { 53 f, ok := w.out.(fdWriter) 54 if ok && !isConsole(f.Fd()) { 55 ok = false 56 } 57 if !ok { 58 _, _ = fmt.Fprint(w.out, strings.Repeat(clear, count)) 59 return 60 } 61 fd := f.Fd() 62 63 var csbi windows.ConsoleScreenBufferInfo 64 if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &csbi); err != nil { 65 return 66 } 67 68 for i := 0; i < count; i++ { 69 // move the cursor up 70 csbi.CursorPosition.Y-- 71 _, _, _ = procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&csbi.CursorPosition)))) 72 // clear the line 73 cursor := coord{ 74 x: csbi.Window.Left, 75 y: csbi.Window.Top + csbi.CursorPosition.Y, 76 } 77 var count, w dword 78 count = dword(csbi.Size.X) 79 _, _, _ = procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) 80 } 81 } 82 83 func isConsole(fd uintptr) bool { 84 var mode uint32 85 err := windows.GetConsoleMode(windows.Handle(fd), &mode) 86 return err == nil 87 }