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  }