ginger/gim/terminal/terminal.go

109 lines
2.4 KiB
Go
Raw Normal View History

// Package terminal implements functionality related to interacting with a
// terminal. Using this package takes the place of using stdout directly
package terminal
import (
"bytes"
"fmt"
"io"
"os"
"syscall"
"unsafe"
"github.com/mediocregopher/ginger/gim/geo"
)
// Terminal provides an interface to a terminal which allows for "drawing"
// rather than just writing. Note that all operations on a Terminal aren't
// actually drawn to the screen until Flush is called.
//
// The coordinate system described by Terminal looks like this:
//
// 0,0 ------------------> x
// |
// |
// |
// |
// |
// |
// |
// |
// v
// y
//
type Terminal struct {
buf *bytes.Buffer
// When initialized this will be set to os.Stdout, but can be set to
// anything
Out io.Writer
}
// New initializes and returns a usable Terminal
func New() *Terminal {
return &Terminal{
buf: new(bytes.Buffer),
Out: os.Stdout,
}
}
// WindowSize returns the size of the terminal window (width/height)
// TODO this doesn't support winblows
func (t *Terminal) WindowSize() geo.XY {
var sz struct {
rows uint16
cols uint16
xpixels uint16
ypixels uint16
}
_, _, err := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(syscall.Stdin),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(&sz)),
)
if err != 0 {
panic(err.Error())
}
return geo.XY{int(sz.cols), int(sz.rows)}
}
// SetPos sets the terminal's actual cursor position to the given coordinates.
func (t *Terminal) SetPos(to geo.XY) {
// actual terminal uses 1,1 as top-left, because 1-indexing is a great idea
fmt.Fprintf(t.buf, "\033[%d;%dH", to[1]+1, to[0]+1)
}
// HideCursor causes the cursor to not actually be shown
func (t *Terminal) HideCursor() {
fmt.Fprintf(t.buf, "\033[?25l")
}
// ShowCursor causes the cursor to be shown, if it was previously hidden
func (t *Terminal) ShowCursor() {
fmt.Fprintf(t.buf, "\033[?25h")
}
// Clear completely clears all drawn characters on the screen and returns the
// cursor to the origin. This implicitly calls Draw.
func (t *Terminal) Clear() {
t.buf.Reset()
fmt.Fprintf(t.buf, "\033[2J")
t.Draw()
}
// WriteBuffer writes the contents to the Buffer to the Terminal's buffer,
// starting at the given coordinate.
func (t *Terminal) WriteBuffer(at geo.XY, b *Buffer) {
t.SetPos(at)
t.buf.WriteString(b.String())
}
// Draw writes all buffered changes to the screen
func (t *Terminal) Draw() {
if _, err := io.Copy(t.Out, t.buf); err != nil {
panic(err)
}
t.buf.Reset()
}