Skip to content

Commit

Permalink
Merge branch 'master' into PR-fix-start-panic
Browse files Browse the repository at this point in the history
  • Loading branch information
asahasrabuddhe authored Jan 2, 2020
2 parents 921d4ab + c14265c commit c88f574
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 31 deletions.
29 changes: 25 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test:
@go test -race .

examples:
@go run -race ./example

.PHONY: test examples
11 changes: 6 additions & 5 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ func main() {
// start listening for updates and render
writer.Start()

for _, f := range []string{"Foo.zip", "Bar.iso"} {
for _, f := range [][]string{{"Foo.zip", "Bar.iso"}, {"Baz.tar.gz", "Qux.img"}} {
for i := 0; i <= 50; i++ {
fmt.Fprintf(writer, "Downloading %s.. (%d/%d) GB\n", f, i, 50)
_, _ = fmt.Fprintf(writer, "Downloading %s.. (%d/%d) GB\n", f[0], i, 50)
_, _ = fmt.Fprintf(writer.Newline(), "Downloading %s.. (%d/%d) GB\n", f[1], i, 50)
time.Sleep(time.Millisecond * 25)
}
fmt.Fprintf(writer.Bypass(), "Downloaded %s\n", f)
_, _ = fmt.Fprintf(writer.Bypass(), "Downloaded %s\n", f[0])
_, _ = fmt.Fprintf(writer.Bypass(), "Downloaded %s\n", f[1])
}

fmt.Fprintln(writer, "Finished: Downloaded 100GB")
_, _ = fmt.Fprintln(writer, "Finished: Downloaded 150GB")
writer.Stop() // flush and stop rendering
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gosuri/uilive

go 1.10
37 changes: 37 additions & 0 deletions terminal_size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build !windows

package uilive

import (
"os"
"runtime"
"syscall"
"unsafe"
)

type windowSize struct {
rows uint16
cols uint16
}

var out *os.File
var err error
var sz windowSize

func getTermSize() (int, int) {
if runtime.GOOS == "openbsd" {
out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return 0, 0
}

} else {
out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
if err != nil {
return 0, 0
}
}
_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
out.Fd(), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
return int(sz.cols), int(sz.rows)
}
24 changes: 24 additions & 0 deletions terminal_size_windows.go
Original file line number Diff line number Diff line change
@@ -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)
}
47 changes: 40 additions & 7 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ const ESC = 27
// RefreshInterval is the default refresh interval to update the ui
var RefreshInterval = time.Millisecond

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")
Expand Down Expand Up @@ -47,8 +51,17 @@ type bypass struct {
writer *Writer
}

type newline struct {
writer *Writer
}

// New returns a new Writer with defaults
func New() *Writer {
termWidth, _ = getTermSize()
if termWidth != 0 {
overFlowHandled = true
}

return &Writer{
Out: Out,
RefreshInterval: RefreshInterval,
Expand All @@ -64,16 +77,24 @@ func (w *Writer) Flush() error {
w.mtx.Lock()
defer w.mtx.Unlock()

// do nothing is buffer is empty
// do nothing if buffer is empty
if len(w.buf.Bytes()) == 0 {
return nil
}
w.clearLines()

lines := 0
var currentLine bytes.Buffer
for _, b := range w.buf.Bytes() {
if b == '\n' {
lines++
currentLine.Reset()
} else {
currentLine.Write([]byte{b})
if overFlowHandled && currentLine.Len() > termWidth {
lines++
currentLine.Reset()
}
}
}
w.lineCount = lines
Expand Down Expand Up @@ -105,7 +126,7 @@ func (w *Writer) Listen() {
select {
case <-w.ticker.C:
if w.ticker != nil {
w.Flush()
_ = w.Flush()
}
case <-w.tdone:
w.mtx.Lock()
Expand All @@ -118,23 +139,35 @@ func (w *Writer) Listen() {
}
}

// Write save the contents of b to its buffers. The only errors returned are ones encountered while writing to the underlying buffer.
func (w *Writer) Write(b []byte) (n int, err error) {
// Write save the contents of buf to the writer b. The only errors returned are ones encountered while writing to the underlying buffer.
func (w *Writer) Write(buf []byte) (n int, err error) {
w.mtx.Lock()
defer w.mtx.Unlock()
return w.buf.Write(b)
return w.buf.Write(buf)
}

// Bypass creates an io.Writer which allows non-buffered output to be written to the underlying output
func (w *Writer) Bypass() io.Writer {
return &bypass{writer: w}
}

func (b *bypass) Write(p []byte) (n int, err error) {
func (b *bypass) Write(p []byte) (int, error) {
b.writer.mtx.Lock()
defer b.writer.mtx.Unlock()

b.writer.clearLines()
b.writer.lineCount = 0
return b.writer.Out.Write(p)
}

// Newline creates an io.Writer which allows buffered output to be written to the underlying output. This enable writing
// to multiple lines at once.
func (w *Writer) Newline() io.Writer {
return &newline{writer: w}
}

func (n *newline) Write(p []byte) (int, error) {
n.writer.mtx.Lock()
defer n.writer.mtx.Unlock()
return n.writer.buf.Write(p)
}
9 changes: 5 additions & 4 deletions writer_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ package uilive

import (
"fmt"
"strings"
)

// clear the line and move the cursor up
var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC)

func (w *Writer) clearLines() {
for i := 0; i < w.lineCount; i++ {
fmt.Fprintf(w.Out, "%c[2K", ESC) // clear the line
fmt.Fprintf(w.Out, "%c[%dA", ESC, 1) // move the cursor up
}
_, _ = fmt.Fprint(w.Out, strings.Repeat(clear, w.lineCount))
}
4 changes: 2 additions & 2 deletions writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ 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 {
Expand Down
18 changes: 9 additions & 9 deletions writer_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ package uilive

import (
"fmt"
"github.com/mattn/go-isatty"
"strings"
"syscall"
"unsafe"
"github.com/mattn/go-isatty"
)

var kernel32 = syscall.NewLazyDLL("kernel32.dll")
Expand All @@ -15,9 +16,11 @@ 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\r", ESC, 0, ESC)

type short int16
type dword uint32
type word uint16
Expand Down Expand Up @@ -48,27 +51,24 @@ func (w *Writer) clearLines() {
ok = false
}
if !ok {
for i := 0; i < w.lineCount; i++ {
fmt.Fprintf(w.Out, "%c[%dA", ESC, 0) // move the cursor up
fmt.Fprintf(w.Out, "%c[2K\r", ESC) // clear the line
}
_, _ = fmt.Fprint(w.Out, strings.Repeat(clear, w.lineCount))
return
}
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,
y: csbi.window.top + csbi.cursorPosition.y,
}
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)))
}
}

0 comments on commit c88f574

Please sign in to comment.