mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-18 08:13:40 -05:00
Prepare for termbox/windows build
`TAGS=termbox make` (or `go build -tags termbox`)
This commit is contained in:
513
src/tui/ncurses.go
Normal file
513
src/tui/ncurses.go
Normal file
@@ -0,0 +1,513 @@
|
||||
// +build !windows
|
||||
// +build !termbox
|
||||
|
||||
package tui
|
||||
|
||||
/*
|
||||
#include <ncurses.h>
|
||||
#include <locale.h>
|
||||
#cgo !static LDFLAGS: -lncurses
|
||||
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
|
||||
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
|
||||
|
||||
SCREEN *c_newterm () {
|
||||
return newterm(NULL, stderr, stdin);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ColorPair int16
|
||||
type Attr C.int
|
||||
type WindowImpl C.WINDOW
|
||||
|
||||
const (
|
||||
Bold = C.A_BOLD
|
||||
Dim = C.A_DIM
|
||||
Blink = C.A_BLINK
|
||||
Reverse = C.A_REVERSE
|
||||
Underline = C.A_UNDERLINE
|
||||
)
|
||||
|
||||
const (
|
||||
AttrRegular Attr = 0
|
||||
)
|
||||
|
||||
// Pallete
|
||||
const (
|
||||
ColDefault ColorPair = iota
|
||||
ColNormal
|
||||
ColPrompt
|
||||
ColMatch
|
||||
ColCurrent
|
||||
ColCurrentMatch
|
||||
ColSpinner
|
||||
ColInfo
|
||||
ColCursor
|
||||
ColSelected
|
||||
ColHeader
|
||||
ColBorder
|
||||
ColUser // Should be the last entry
|
||||
)
|
||||
|
||||
var (
|
||||
_in *os.File
|
||||
_screen *C.SCREEN
|
||||
_colorMap map[int]ColorPair
|
||||
_colorFn func(ColorPair, Attr) C.int
|
||||
)
|
||||
|
||||
func init() {
|
||||
_colorMap = make(map[int]ColorPair)
|
||||
}
|
||||
|
||||
func (a Attr) Merge(b Attr) Attr {
|
||||
return a | b
|
||||
}
|
||||
|
||||
func DefaultTheme() *ColorTheme {
|
||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
||||
return Dark256
|
||||
}
|
||||
return Default16
|
||||
}
|
||||
|
||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
{
|
||||
in, err := os.OpenFile("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
panic("Failed to open /dev/tty")
|
||||
}
|
||||
_in = in
|
||||
// Break STDIN
|
||||
// syscall.Dup2(int(in.Fd()), int(os.Stdin.Fd()))
|
||||
}
|
||||
|
||||
C.setlocale(C.LC_ALL, C.CString(""))
|
||||
_screen = C.c_newterm()
|
||||
if _screen == nil {
|
||||
fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
|
||||
os.Exit(2)
|
||||
}
|
||||
C.set_term(_screen)
|
||||
if mouse {
|
||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
||||
}
|
||||
C.noecho()
|
||||
C.raw() // stty dsusp undef
|
||||
|
||||
_color = theme != nil
|
||||
if _color {
|
||||
C.start_color()
|
||||
InitTheme(theme, black)
|
||||
initPairs(theme)
|
||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
||||
_colorFn = attrColored
|
||||
} else {
|
||||
_colorFn = attrMono
|
||||
}
|
||||
}
|
||||
|
||||
func initPairs(theme *ColorTheme) {
|
||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
||||
initPair := func(group ColorPair, fg Color, bg Color) {
|
||||
C.init_pair(C.short(group), C.short(fg), C.short(bg))
|
||||
}
|
||||
initPair(ColNormal, theme.Fg, theme.Bg)
|
||||
initPair(ColPrompt, theme.Prompt, theme.Bg)
|
||||
initPair(ColMatch, theme.Match, theme.Bg)
|
||||
initPair(ColCurrent, theme.Current, theme.DarkBg)
|
||||
initPair(ColCurrentMatch, theme.CurrentMatch, theme.DarkBg)
|
||||
initPair(ColSpinner, theme.Spinner, theme.Bg)
|
||||
initPair(ColInfo, theme.Info, theme.Bg)
|
||||
initPair(ColCursor, theme.Cursor, theme.DarkBg)
|
||||
initPair(ColSelected, theme.Selected, theme.DarkBg)
|
||||
initPair(ColHeader, theme.Header, theme.Bg)
|
||||
initPair(ColBorder, theme.Border, theme.Bg)
|
||||
}
|
||||
|
||||
func Pause() {
|
||||
C.endwin()
|
||||
}
|
||||
|
||||
func Close() {
|
||||
C.endwin()
|
||||
C.delscreen(_screen)
|
||||
}
|
||||
|
||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||
if _color {
|
||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
|
||||
}
|
||||
if border {
|
||||
attr := _colorFn(ColBorder, 0)
|
||||
C.wattron(win, attr)
|
||||
C.box(win, 0, 0)
|
||||
C.wattroff(win, attr)
|
||||
}
|
||||
|
||||
return &Window{
|
||||
impl: (*WindowImpl)(win),
|
||||
Top: top,
|
||||
Left: left,
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func attrColored(pair ColorPair, a Attr) C.int {
|
||||
var attr C.int
|
||||
if pair > 0 {
|
||||
attr = C.COLOR_PAIR(C.int(pair))
|
||||
}
|
||||
return attr | C.int(a)
|
||||
}
|
||||
|
||||
func attrMono(pair ColorPair, a Attr) C.int {
|
||||
var attr C.int
|
||||
switch pair {
|
||||
case ColCurrent:
|
||||
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||
attr = C.A_REVERSE
|
||||
}
|
||||
case ColMatch:
|
||||
attr = C.A_UNDERLINE
|
||||
case ColCurrentMatch:
|
||||
attr = C.A_UNDERLINE | C.A_REVERSE
|
||||
}
|
||||
if C.int(a)&C.A_BOLD == C.A_BOLD {
|
||||
attr = attr | C.A_BOLD
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
func MaxX() int {
|
||||
return int(C.COLS)
|
||||
}
|
||||
|
||||
func MaxY() int {
|
||||
return int(C.LINES)
|
||||
}
|
||||
|
||||
func (w *Window) win() *C.WINDOW {
|
||||
return (*C.WINDOW)(w.impl)
|
||||
}
|
||||
|
||||
func (w *Window) Close() {
|
||||
C.delwin(w.win())
|
||||
}
|
||||
|
||||
func (w *Window) Enclose(y int, x int) bool {
|
||||
return bool(C.wenclose(w.win(), C.int(y), C.int(x)))
|
||||
}
|
||||
|
||||
func (w *Window) Move(y int, x int) {
|
||||
C.wmove(w.win(), C.int(y), C.int(x))
|
||||
}
|
||||
|
||||
func (w *Window) MoveAndClear(y int, x int) {
|
||||
w.Move(y, x)
|
||||
C.wclrtoeol(w.win())
|
||||
}
|
||||
|
||||
func (w *Window) Print(text string) {
|
||||
C.waddstr(w.win(), C.CString(strings.Map(func(r rune) rune {
|
||||
if r < 32 {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, text)))
|
||||
}
|
||||
|
||||
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
||||
attr := _colorFn(pair, a)
|
||||
C.wattron(w.win(), attr)
|
||||
w.Print(text)
|
||||
C.wattroff(w.win(), attr)
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
C.clear()
|
||||
C.endwin()
|
||||
}
|
||||
|
||||
func Refresh() {
|
||||
C.refresh()
|
||||
}
|
||||
|
||||
func (w *Window) Erase() {
|
||||
C.werase(w.win())
|
||||
}
|
||||
|
||||
func (w *Window) Fill(str string) bool {
|
||||
return C.waddstr(w.win(), C.CString(str)) == C.OK
|
||||
}
|
||||
|
||||
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
||||
attr := _colorFn(PairFor(fg, bg), a)
|
||||
C.wattron(w.win(), attr)
|
||||
ret := w.Fill(str)
|
||||
C.wattroff(w.win(), attr)
|
||||
return ret
|
||||
}
|
||||
|
||||
func RefreshWindows(windows []*Window) {
|
||||
for _, w := range windows {
|
||||
C.wnoutrefresh(w.win())
|
||||
}
|
||||
C.doupdate()
|
||||
}
|
||||
|
||||
func PairFor(fg Color, bg Color) ColorPair {
|
||||
key := (int(fg) << 8) + int(bg)
|
||||
if found, prs := _colorMap[key]; prs {
|
||||
return found
|
||||
}
|
||||
|
||||
id := ColorPair(len(_colorMap) + int(ColUser))
|
||||
C.init_pair(C.short(id), C.short(fg), C.short(bg))
|
||||
_colorMap[key] = id
|
||||
return id
|
||||
}
|
||||
|
||||
func getch(nonblock bool) int {
|
||||
b := make([]byte, 1)
|
||||
syscall.SetNonblock(int(_in.Fd()), nonblock)
|
||||
_, err := _in.Read(b)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int(b[0])
|
||||
}
|
||||
|
||||
func GetBytes() []byte {
|
||||
c := getch(false)
|
||||
_buf = append(_buf, byte(c))
|
||||
|
||||
for {
|
||||
c = getch(true)
|
||||
if c == -1 {
|
||||
break
|
||||
}
|
||||
_buf = append(_buf, byte(c))
|
||||
}
|
||||
|
||||
return _buf
|
||||
}
|
||||
|
||||
// 27 (91 79) 77 type x y
|
||||
func mouseSequence(sz *int) Event {
|
||||
if len(_buf) < 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch _buf[3] {
|
||||
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||
mod := _buf[3] >= 36
|
||||
down := _buf[3]%2 == 0
|
||||
x := int(_buf[4] - 33)
|
||||
y := int(_buf[5] - 33)
|
||||
double := false
|
||||
if down {
|
||||
now := time.Now()
|
||||
if now.Sub(_prevDownTime) < doubleClickDuration {
|
||||
_clickY = append(_clickY, y)
|
||||
} else {
|
||||
_clickY = []int{y}
|
||||
}
|
||||
_prevDownTime = now
|
||||
} else {
|
||||
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
|
||||
time.Now().Sub(_prevDownTime) < doubleClickDuration {
|
||||
double = true
|
||||
}
|
||||
}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||
mod := _buf[3] >= 100
|
||||
s := 1 - int(_buf[3]%2)*2
|
||||
x := int(_buf[4] - 33)
|
||||
y := int(_buf[5] - 33)
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func escSequence(sz *int) Event {
|
||||
if len(_buf) < 2 {
|
||||
return Event{ESC, 0, nil}
|
||||
}
|
||||
*sz = 2
|
||||
switch _buf[1] {
|
||||
case 13:
|
||||
return Event{AltEnter, 0, nil}
|
||||
case 32:
|
||||
return Event{AltSpace, 0, nil}
|
||||
case 47:
|
||||
return Event{AltSlash, 0, nil}
|
||||
case 98:
|
||||
return Event{AltB, 0, nil}
|
||||
case 100:
|
||||
return Event{AltD, 0, nil}
|
||||
case 102:
|
||||
return Event{AltF, 0, nil}
|
||||
case 127:
|
||||
return Event{AltBS, 0, nil}
|
||||
case 91, 79:
|
||||
if len(_buf) < 3 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 3
|
||||
switch _buf[2] {
|
||||
case 68:
|
||||
return Event{Left, 0, nil}
|
||||
case 67:
|
||||
return Event{Right, 0, nil}
|
||||
case 66:
|
||||
return Event{Down, 0, nil}
|
||||
case 65:
|
||||
return Event{Up, 0, nil}
|
||||
case 90:
|
||||
return Event{BTab, 0, nil}
|
||||
case 72:
|
||||
return Event{Home, 0, nil}
|
||||
case 70:
|
||||
return Event{End, 0, nil}
|
||||
case 77:
|
||||
return mouseSequence(sz)
|
||||
case 80:
|
||||
return Event{F1, 0, nil}
|
||||
case 81:
|
||||
return Event{F2, 0, nil}
|
||||
case 82:
|
||||
return Event{F3, 0, nil}
|
||||
case 83:
|
||||
return Event{F4, 0, nil}
|
||||
case 49, 50, 51, 52, 53, 54:
|
||||
if len(_buf) < 4 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 4
|
||||
switch _buf[2] {
|
||||
case 50:
|
||||
if len(_buf) == 5 && _buf[4] == 126 {
|
||||
*sz = 5
|
||||
switch _buf[3] {
|
||||
case 48:
|
||||
return Event{F9, 0, nil}
|
||||
case 49:
|
||||
return Event{F10, 0, nil}
|
||||
}
|
||||
}
|
||||
// Bracketed paste mode \e[200~ / \e[201
|
||||
if _buf[3] == 48 && (_buf[4] == 48 || _buf[4] == 49) && _buf[5] == 126 {
|
||||
*sz = 6
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
return Event{Invalid, 0, nil} // INS
|
||||
case 51:
|
||||
return Event{Del, 0, nil}
|
||||
case 52:
|
||||
return Event{End, 0, nil}
|
||||
case 53:
|
||||
return Event{PgUp, 0, nil}
|
||||
case 54:
|
||||
return Event{PgDn, 0, nil}
|
||||
case 49:
|
||||
switch _buf[3] {
|
||||
case 126:
|
||||
return Event{Home, 0, nil}
|
||||
case 53, 55, 56, 57:
|
||||
if len(_buf) == 5 && _buf[4] == 126 {
|
||||
*sz = 5
|
||||
switch _buf[3] {
|
||||
case 53:
|
||||
return Event{F5, 0, nil}
|
||||
case 55:
|
||||
return Event{F6, 0, nil}
|
||||
case 56:
|
||||
return Event{F7, 0, nil}
|
||||
case 57:
|
||||
return Event{F8, 0, nil}
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case 59:
|
||||
if len(_buf) != 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch _buf[4] {
|
||||
case 50:
|
||||
switch _buf[5] {
|
||||
case 68:
|
||||
return Event{Home, 0, nil}
|
||||
case 67:
|
||||
return Event{End, 0, nil}
|
||||
}
|
||||
case 53:
|
||||
switch _buf[5] {
|
||||
case 68:
|
||||
return Event{SLeft, 0, nil}
|
||||
case 67:
|
||||
return Event{SRight, 0, nil}
|
||||
}
|
||||
} // _buf[4]
|
||||
} // _buf[3]
|
||||
} // _buf[2]
|
||||
} // _buf[2]
|
||||
} // _buf[1]
|
||||
if _buf[1] >= 'a' && _buf[1] <= 'z' {
|
||||
return Event{AltA + int(_buf[1]) - 'a', 0, nil}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func GetChar() Event {
|
||||
if len(_buf) == 0 {
|
||||
_buf = GetBytes()
|
||||
}
|
||||
if len(_buf) == 0 {
|
||||
panic("Empty _buffer")
|
||||
}
|
||||
|
||||
sz := 1
|
||||
defer func() {
|
||||
_buf = _buf[sz:]
|
||||
}()
|
||||
|
||||
switch _buf[0] {
|
||||
case CtrlC:
|
||||
return Event{CtrlC, 0, nil}
|
||||
case CtrlG:
|
||||
return Event{CtrlG, 0, nil}
|
||||
case CtrlQ:
|
||||
return Event{CtrlQ, 0, nil}
|
||||
case 127:
|
||||
return Event{BSpace, 0, nil}
|
||||
case ESC:
|
||||
return escSequence(&sz)
|
||||
}
|
||||
|
||||
// CTRL-A ~ CTRL-Z
|
||||
if _buf[0] <= CtrlZ {
|
||||
return Event{int(_buf[0]), 0, nil}
|
||||
}
|
||||
r, rsz := utf8.DecodeRune(_buf)
|
||||
if r == utf8.RuneError {
|
||||
return Event{ESC, 0, nil}
|
||||
}
|
||||
sz = rsz
|
||||
return Event{Rune, r, nil}
|
||||
}
|
||||
151
src/tui/termbox.go
Normal file
151
src/tui/termbox.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// +build termbox windows
|
||||
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type ColorPair [2]Color
|
||||
type Attr uint16
|
||||
type WindowImpl int // FIXME
|
||||
|
||||
const (
|
||||
// TODO
|
||||
_ = iota
|
||||
Bold
|
||||
Dim
|
||||
Blink
|
||||
Reverse
|
||||
Underline
|
||||
)
|
||||
|
||||
const (
|
||||
AttrRegular Attr = 0
|
||||
)
|
||||
|
||||
var (
|
||||
ColDefault = ColorPair{colDefault, colDefault}
|
||||
ColNormal ColorPair
|
||||
ColPrompt ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCurrent ColorPair
|
||||
ColCurrentMatch ColorPair
|
||||
ColSpinner ColorPair
|
||||
ColInfo ColorPair
|
||||
ColCursor ColorPair
|
||||
ColSelected ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
ColUser ColorPair
|
||||
)
|
||||
|
||||
func DefaultTheme() *ColorTheme {
|
||||
if termbox.SetOutputMode(termbox.OutputCurrent) == termbox.Output256 {
|
||||
return Dark256
|
||||
}
|
||||
return Default16
|
||||
}
|
||||
|
||||
func PairFor(fg Color, bg Color) ColorPair {
|
||||
return [2]Color{fg, bg}
|
||||
}
|
||||
|
||||
func (a Attr) Merge(b Attr) Attr {
|
||||
return a | b
|
||||
}
|
||||
|
||||
func Init(theme *ColorTheme, black bool, mouse bool) {
|
||||
ColNormal = ColorPair{theme.Fg, theme.Bg}
|
||||
ColPrompt = ColorPair{theme.Prompt, theme.Bg}
|
||||
ColMatch = ColorPair{theme.Match, theme.Bg}
|
||||
ColCurrent = ColorPair{theme.Current, theme.DarkBg}
|
||||
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg}
|
||||
ColSpinner = ColorPair{theme.Spinner, theme.Bg}
|
||||
ColInfo = ColorPair{theme.Info, theme.Bg}
|
||||
ColCursor = ColorPair{theme.Cursor, theme.DarkBg}
|
||||
ColSelected = ColorPair{theme.Selected, theme.DarkBg}
|
||||
ColHeader = ColorPair{theme.Header, theme.Bg}
|
||||
ColBorder = ColorPair{theme.Border, theme.Bg}
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
func MaxX() int {
|
||||
// TODO
|
||||
return 80
|
||||
}
|
||||
|
||||
func MaxY() int {
|
||||
// TODO
|
||||
return 24
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func Refresh() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func GetChar() Event {
|
||||
// TODO
|
||||
return Event{}
|
||||
}
|
||||
|
||||
func Pause() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func Close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func RefreshWindows(windows []*Window) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||
// TODO
|
||||
return &Window{}
|
||||
}
|
||||
|
||||
func (w *Window) Close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) Erase() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) Enclose(y int, x int) bool {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Window) Move(y int, x int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) MoveAndClear(y int, x int) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) Print(text string) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) CPrint(pair ColorPair, a Attr, text string) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (w *Window) Fill(str string) bool {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Window) CFill(str string, fg Color, bg Color, a Attr) bool {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
250
src/tui/tui.go
Normal file
250
src/tui/tui.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Types of user action
|
||||
const (
|
||||
Rune = iota
|
||||
|
||||
CtrlA
|
||||
CtrlB
|
||||
CtrlC
|
||||
CtrlD
|
||||
CtrlE
|
||||
CtrlF
|
||||
CtrlG
|
||||
CtrlH
|
||||
Tab
|
||||
CtrlJ
|
||||
CtrlK
|
||||
CtrlL
|
||||
CtrlM
|
||||
CtrlN
|
||||
CtrlO
|
||||
CtrlP
|
||||
CtrlQ
|
||||
CtrlR
|
||||
CtrlS
|
||||
CtrlT
|
||||
CtrlU
|
||||
CtrlV
|
||||
CtrlW
|
||||
CtrlX
|
||||
CtrlY
|
||||
CtrlZ
|
||||
ESC
|
||||
|
||||
Invalid
|
||||
Mouse
|
||||
DoubleClick
|
||||
|
||||
BTab
|
||||
BSpace
|
||||
|
||||
Del
|
||||
PgUp
|
||||
PgDn
|
||||
|
||||
Up
|
||||
Down
|
||||
Left
|
||||
Right
|
||||
Home
|
||||
End
|
||||
|
||||
SLeft
|
||||
SRight
|
||||
|
||||
F1
|
||||
F2
|
||||
F3
|
||||
F4
|
||||
F5
|
||||
F6
|
||||
F7
|
||||
F8
|
||||
F9
|
||||
F10
|
||||
|
||||
AltEnter
|
||||
AltSpace
|
||||
AltSlash
|
||||
AltBS
|
||||
AltA
|
||||
AltB
|
||||
AltC
|
||||
AltD
|
||||
AltE
|
||||
AltF
|
||||
|
||||
AltZ = AltA + 'z' - 'a'
|
||||
)
|
||||
|
||||
const (
|
||||
doubleClickDuration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
type Color int16
|
||||
|
||||
const (
|
||||
colUndefined Color = -2
|
||||
colDefault = -1
|
||||
)
|
||||
|
||||
const (
|
||||
colBlack Color = iota
|
||||
colRed
|
||||
colGreen
|
||||
colYellow
|
||||
colBlue
|
||||
colMagenta
|
||||
colCyan
|
||||
colWhite
|
||||
)
|
||||
|
||||
type ColorTheme struct {
|
||||
Fg Color
|
||||
Bg Color
|
||||
DarkBg Color
|
||||
Prompt Color
|
||||
Match Color
|
||||
Current Color
|
||||
CurrentMatch Color
|
||||
Spinner Color
|
||||
Info Color
|
||||
Cursor Color
|
||||
Selected Color
|
||||
Header Color
|
||||
Border Color
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type int
|
||||
Char rune
|
||||
MouseEvent *MouseEvent
|
||||
}
|
||||
|
||||
type MouseEvent struct {
|
||||
Y int
|
||||
X int
|
||||
S int
|
||||
Down bool
|
||||
Double bool
|
||||
Mod bool
|
||||
}
|
||||
|
||||
var (
|
||||
_buf []byte
|
||||
_color bool
|
||||
_prevDownTime time.Time
|
||||
_clickY []int
|
||||
Default16 *ColorTheme
|
||||
Dark256 *ColorTheme
|
||||
Light256 *ColorTheme
|
||||
)
|
||||
|
||||
type Window struct {
|
||||
impl *WindowImpl
|
||||
Top int
|
||||
Left int
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
Prompt: colUndefined,
|
||||
Match: colUndefined,
|
||||
Current: colUndefined,
|
||||
CurrentMatch: colUndefined,
|
||||
Spinner: colUndefined,
|
||||
Info: colUndefined,
|
||||
Cursor: colUndefined,
|
||||
Selected: colUndefined,
|
||||
Header: colUndefined,
|
||||
Border: colUndefined}
|
||||
}
|
||||
|
||||
func init() {
|
||||
_prevDownTime = time.Unix(0, 0)
|
||||
_clickY = []int{}
|
||||
Default16 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: colBlack,
|
||||
Prompt: colBlue,
|
||||
Match: colGreen,
|
||||
Current: colYellow,
|
||||
CurrentMatch: colGreen,
|
||||
Spinner: colGreen,
|
||||
Info: colWhite,
|
||||
Cursor: colRed,
|
||||
Selected: colMagenta,
|
||||
Header: colCyan,
|
||||
Border: colBlack}
|
||||
Dark256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 236,
|
||||
Prompt: 110,
|
||||
Match: 108,
|
||||
Current: 254,
|
||||
CurrentMatch: 151,
|
||||
Spinner: 148,
|
||||
Info: 144,
|
||||
Cursor: 161,
|
||||
Selected: 168,
|
||||
Header: 109,
|
||||
Border: 59}
|
||||
Light256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 251,
|
||||
Prompt: 25,
|
||||
Match: 66,
|
||||
Current: 237,
|
||||
CurrentMatch: 23,
|
||||
Spinner: 65,
|
||||
Info: 101,
|
||||
Cursor: 161,
|
||||
Selected: 168,
|
||||
Header: 31,
|
||||
Border: 145}
|
||||
}
|
||||
|
||||
func InitTheme(theme *ColorTheme, black bool) {
|
||||
_color = theme != nil
|
||||
if !_color {
|
||||
return
|
||||
}
|
||||
|
||||
baseTheme := DefaultTheme()
|
||||
if black {
|
||||
theme.Bg = colBlack
|
||||
}
|
||||
|
||||
o := func(a Color, b Color) Color {
|
||||
if b == colUndefined {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||
theme.Match = o(baseTheme.Match, theme.Match)
|
||||
theme.Current = o(baseTheme.Current, theme.Current)
|
||||
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||
theme.Info = o(baseTheme.Info, theme.Info)
|
||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
||||
theme.Header = o(baseTheme.Header, theme.Header)
|
||||
theme.Border = o(baseTheme.Border, theme.Border)
|
||||
}
|
||||
14
src/tui/tui_test.go
Normal file
14
src/tui/tui_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPairFor(t *testing.T) {
|
||||
if PairFor(30, 50) != PairFor(30, 50) {
|
||||
t.Fail()
|
||||
}
|
||||
if PairFor(-1, 10) != PairFor(-1, 10) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user