diff --git a/.travis.yml b/.travis.yml index 04c834a..345d805 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,28 @@ language: go sudo: false + +matrix: + include: + - go: 1.11.x + os: linux + - go: 1.12.x + os: linux + - go: 1.11.x + os: linux + env: CROSS_COMPILE=true + - go: 1.12.x + os: linux + env: CROSS_COMPILE=true + - go: 1.11.x + os: osx + - go: 1.12.x + os: osx + install: - - go get ./... -go: - - 1.4 - - tip + - if [ "$TRAVIS_OS_NAME" = "linux" -a "$CROSS_COMPILE" = "true" ]; then go get github.com/mattn/go-isatty ; fi + - go get -t -v ./... + +script: + - go build + - go test + - if [ "$TRAVIS_OS_NAME" = "linux" -a "$CROSS_COMPILE" = "true" ]; then env GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -v; fi diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..80083d8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/gosuri/uilive + +go 1.10 diff --git a/terminal_size.go b/terminal_size.go index 3ae0132..ebef466 100644 --- a/terminal_size.go +++ b/terminal_size.go @@ -1,3 +1,5 @@ +// +build !windows + package uilive import ( @@ -10,8 +12,6 @@ import ( type windowSize struct { rows uint16 cols uint16 - xPixels uint16 - yPixels uint16 } var out *os.File @@ -22,13 +22,13 @@ func getTermSize() (int, int) { if runtime.GOOS == "openbsd" { out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { - os.Exit(1) + return 0, 0 } } else { out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0) if err != nil { - os.Exit(1) + return 0, 0 } } _, _, _ = syscall.Syscall(syscall.SYS_IOCTL, diff --git a/terminal_size_windows.go b/terminal_size_windows.go new file mode 100644 index 0000000..94b7ed0 --- /dev/null +++ b/terminal_size_windows.go @@ -0,0 +1,24 @@ +// +build windows + +package uilive + +import ( + "os" + "unsafe" +) + +func getTermSize() (int, int) { + out, err := os.Open("CONOUT$") + if err != nil { + return 0, 0 + } + defer out.Close() + + var csbi consoleScreenBufferInfo + ret, _, _ := procGetConsoleScreenBufferInfo.Call(out.Fd(), uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return 0, 0 + } + + return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1) +} diff --git a/writer.go b/writer.go index c7728d9..d76542a 100644 --- a/writer.go +++ b/writer.go @@ -20,7 +20,7 @@ var overFlowHandled bool var termWidth int // Out is the default output writer for the Writer -var Out = os.Stdout +var Out = io.Writer(os.Stdout) // ErrClosedPipe is the error returned when trying to writer is not listening var ErrClosedPipe = errors.New("uilive: read/write on closed pipe") @@ -107,7 +107,7 @@ func (w *Writer) Flush() error { func (w *Writer) Start() { if w.ticker == nil { w.ticker = time.NewTicker(w.RefreshInterval) - w.tdone = make(chan bool, 1) + w.tdone = make(chan bool) } go w.Listen() @@ -116,7 +116,8 @@ func (w *Writer) Start() { // Stop stops the listener that updates the terminal func (w *Writer) Stop() { w.Flush() - close(w.tdone) + w.tdone <- true + <-w.tdone } // Listen listens for updates to the writer's buffer and flushes to the out provided. It blocks the runtime. @@ -125,13 +126,14 @@ func (w *Writer) Listen() { select { case <-w.ticker.C: if w.ticker != nil { - w.Flush() + _ = w.Flush() } case <-w.tdone: w.mtx.Lock() w.ticker.Stop() w.ticker = nil w.mtx.Unlock() + close(w.tdone) return } } diff --git a/writer_test.go b/writer_test.go index 5ed200f..793136b 100644 --- a/writer_test.go +++ b/writer_test.go @@ -12,13 +12,24 @@ func TestWriter(t *testing.T) { w.Out = b w.Start() for i := 0; i < 2; i++ { - fmt.Fprintln(w, "foo") + _, _ = fmt.Fprintln(w, "foo") } w.Stop() - fmt.Fprintln(b, "bar") + _, _ = fmt.Fprintln(b, "bar") want := "foo\nfoo\nbar\n" if b.String() != want { t.Fatalf("want %q, got %q", want, b.String()) } } + +func TestStartCalledTwice(t *testing.T) { + w := New() + b := &bytes.Buffer{} + w.Out = b + + w.Start() + w.Stop() + w.Start() + w.Stop() +} diff --git a/writer_windows.go b/writer_windows.go index c7a0342..8dafb40 100644 --- a/writer_windows.go +++ b/writer_windows.go @@ -4,8 +4,10 @@ package uilive import ( "fmt" + "strings" "syscall" "unsafe" + "github.com/mattn/go-isatty" ) var kernel32 = syscall.NewLazyDLL("kernel32.dll") @@ -14,11 +16,10 @@ var ( procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") ) // clear the line and move the cursor up -var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC) +var clear = fmt.Sprintf("%c[%dA%c[2K\r", ESC, 0, ESC) type short int16 type dword uint32 @@ -55,12 +56,12 @@ func (w *Writer) clearLines() { } fd := f.Fd() var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(&csbi))) + _, _, _ = procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(&csbi))) for i := 0; i < w.lineCount; i++ { // move the cursor up csbi.cursorPosition.y-- - procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&csbi.cursorPosition)))) + _, _, _ = procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&csbi.cursorPosition)))) // clear the line cursor := coord{ x: csbi.window.left, @@ -68,6 +69,6 @@ func (w *Writer) clearLines() { } var count, w dword count = dword(csbi.size.x) - procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) + _, _, _ = procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) } }