mirror of
https://github.com/junegunn/fzf
synced 2024-11-16 12:12:48 +00:00
Delete ncurses implementation
This commit is contained in:
parent
655dfb8328
commit
e3e7b3360c
@ -1,505 +0,0 @@
|
|||||||
// +build ncurses
|
|
||||||
// +build !windows
|
|
||||||
// +build !tcell
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
FILE* c_tty() {
|
|
||||||
return fopen("/dev/tty", "r");
|
|
||||||
}
|
|
||||||
|
|
||||||
SCREEN* c_newterm(FILE* tty) {
|
|
||||||
return newterm(NULL, stderr, tty);
|
|
||||||
}
|
|
||||||
|
|
||||||
int c_getcurx(WINDOW* win) {
|
|
||||||
return getcurx(win);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Attr C.uint
|
|
||||||
|
|
||||||
type CursesWindow struct {
|
|
||||||
impl *C.WINDOW
|
|
||||||
top int
|
|
||||||
left int
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Top() int {
|
|
||||||
return w.top
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Left() int {
|
|
||||||
return w.left
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Width() int {
|
|
||||||
return w.width
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Height() int {
|
|
||||||
return w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Refresh() {
|
|
||||||
C.wnoutrefresh(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) FinishFill() {
|
|
||||||
// NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bold Attr = C.A_BOLD
|
|
||||||
Dim = C.A_DIM
|
|
||||||
Blink = C.A_BLINK
|
|
||||||
Reverse = C.A_REVERSE
|
|
||||||
Underline = C.A_UNDERLINE
|
|
||||||
)
|
|
||||||
|
|
||||||
var Italic Attr = C.A_VERTICAL << 1 // FIXME
|
|
||||||
|
|
||||||
const (
|
|
||||||
AttrRegular Attr = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_screen *C.SCREEN
|
|
||||||
_colorMap map[int]int16
|
|
||||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_colorMap = make(map[int]int16)
|
|
||||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
|
||||||
Italic = C.A_NORMAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
|
||||||
return Dark256
|
|
||||||
}
|
|
||||||
return Default16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
|
||||||
C.setlocale(C.LC_ALL, C.CString(""))
|
|
||||||
tty := C.c_tty()
|
|
||||||
if tty == nil {
|
|
||||||
errorExit("Failed to open /dev/tty")
|
|
||||||
}
|
|
||||||
_screen = C.c_newterm(tty)
|
|
||||||
if _screen == nil {
|
|
||||||
errorExit("Invalid $TERM: " + os.Getenv("TERM"))
|
|
||||||
}
|
|
||||||
C.set_term(_screen)
|
|
||||||
if r.mouse {
|
|
||||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
|
||||||
C.mouseinterval(0)
|
|
||||||
}
|
|
||||||
C.noecho()
|
|
||||||
C.raw() // stty dsusp undef
|
|
||||||
C.nonl()
|
|
||||||
C.keypad(C.stdscr, true)
|
|
||||||
|
|
||||||
delay := 50
|
|
||||||
delayEnv := os.Getenv("ESCDELAY")
|
|
||||||
if len(delayEnv) > 0 {
|
|
||||||
num, err := strconv.Atoi(delayEnv)
|
|
||||||
if err == nil && num >= 0 {
|
|
||||||
delay = num
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.set_escdelay(C.int(delay))
|
|
||||||
|
|
||||||
if r.theme != nil {
|
|
||||||
C.start_color()
|
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
initPairs(r.theme)
|
|
||||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
_colorFn = attrColored
|
|
||||||
} else {
|
|
||||||
initTheme(r.theme, nil, r.forceBlack)
|
|
||||||
_colorFn = attrMono
|
|
||||||
}
|
|
||||||
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
ch := C.getch()
|
|
||||||
if ch != C.ERR {
|
|
||||||
C.ungetch(ch)
|
|
||||||
}
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme) {
|
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
|
||||||
for _, pair := range []ColorPair{
|
|
||||||
ColNormal,
|
|
||||||
ColPrompt,
|
|
||||||
ColMatch,
|
|
||||||
ColCurrent,
|
|
||||||
ColCurrentMatch,
|
|
||||||
ColSpinner,
|
|
||||||
ColInfo,
|
|
||||||
ColCursor,
|
|
||||||
ColSelected,
|
|
||||||
ColHeader,
|
|
||||||
ColBorder} {
|
|
||||||
C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause(bool) {
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume(bool) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
|
||||||
C.endwin()
|
|
||||||
C.delscreen(_screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
|
||||||
if r.theme != nil {
|
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
}
|
|
||||||
// FIXME Does not implement BorderHorizontal
|
|
||||||
if borderStyle != BorderNone {
|
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
|
||||||
C.wcolor_set(win, pair, nil)
|
|
||||||
C.wattron(win, attr)
|
|
||||||
C.box(win, 0, 0)
|
|
||||||
C.wattroff(win, attr)
|
|
||||||
C.wcolor_set(win, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CursesWindow{
|
|
||||||
impl: win,
|
|
||||||
top: top,
|
|
||||||
left: left,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrColored(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return C.short(color.index()), C.int(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrMono(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return 0, C.int(attrFor(color, a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
|
||||||
return int(C.COLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxY() int {
|
|
||||||
return int(C.LINES)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Close() {
|
|
||||||
C.delwin(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Enclose(y int, x int) bool {
|
|
||||||
return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Move(y int, x int) {
|
|
||||||
C.wmove(w.impl, C.int(y), C.int(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) MoveAndClear(y int, x int) {
|
|
||||||
w.Move(y, x)
|
|
||||||
C.wclrtoeol(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Print(text string) {
|
|
||||||
C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
|
|
||||||
p, a := _colorFn(color, attr)
|
|
||||||
C.wcolor_set(w.impl, p, nil)
|
|
||||||
C.wattron(w.impl, a)
|
|
||||||
w.Print(text)
|
|
||||||
C.wattroff(w.impl, a)
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
|
||||||
C.clear()
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
|
||||||
C.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Erase() {
|
|
||||||
C.werase(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) X() int {
|
|
||||||
return int(C.c_getcurx(w.impl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Fill(str string) FillReturn {
|
|
||||||
if C.waddstr(w.impl, C.CString(str)) == C.OK {
|
|
||||||
return FillContinue
|
|
||||||
}
|
|
||||||
return FillSuspend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
|
|
||||||
index := ColorPair{fg, bg, -1}.index()
|
|
||||||
C.wcolor_set(w.impl, C.short(index), nil)
|
|
||||||
C.wattron(w.impl, C.int(attr))
|
|
||||||
ret := w.Fill(str)
|
|
||||||
C.wattroff(w.impl, C.int(attr))
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|
||||||
for _, w := range windows {
|
|
||||||
w.Refresh()
|
|
||||||
}
|
|
||||||
C.doupdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) index() int16 {
|
|
||||||
if p.id >= 0 {
|
|
||||||
return p.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ncurses does not support 24-bit colors
|
|
||||||
if p.is24() {
|
|
||||||
return ColDefault.index()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := p.key()
|
|
||||||
if found, prs := _colorMap[key]; prs {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int16(len(_colorMap)) + ColUser.id
|
|
||||||
C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
|
|
||||||
_colorMap[key] = id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func consume(expects ...rune) bool {
|
|
||||||
for _, r := range expects {
|
|
||||||
if int(C.getch()) != int(r) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func escSequence() Event {
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
defer func() {
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}()
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
return Event{ESC, 0, nil}
|
|
||||||
case CtrlM:
|
|
||||||
return Event{CtrlAltM, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case 127, C.KEY_BACKSPACE:
|
|
||||||
return Event{AltBS, 0, nil}
|
|
||||||
case '[':
|
|
||||||
// Bracketed paste mode (printf "\e[?2004h")
|
|
||||||
// \e[200~ TEXT \e[201~
|
|
||||||
if consume('2', '0', '0', '~') {
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c >= 'a' && c <= 'z' {
|
|
||||||
return Event{AltA + int(c) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c >= '0' && c <= '9' {
|
|
||||||
return Event{Alt0 + int(c) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't care. Ignore the rest.
|
|
||||||
for ; c != C.ERR; c = C.getch() {
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
// Unexpected error from blocking read
|
|
||||||
r.Close()
|
|
||||||
errorExit("Failed to read /dev/tty")
|
|
||||||
case C.KEY_UP:
|
|
||||||
return Event{Up, 0, nil}
|
|
||||||
case C.KEY_DOWN:
|
|
||||||
return Event{Down, 0, nil}
|
|
||||||
case C.KEY_LEFT:
|
|
||||||
return Event{Left, 0, nil}
|
|
||||||
case C.KEY_RIGHT:
|
|
||||||
return Event{Right, 0, nil}
|
|
||||||
case C.KEY_HOME:
|
|
||||||
return Event{Home, 0, nil}
|
|
||||||
case C.KEY_END:
|
|
||||||
return Event{End, 0, nil}
|
|
||||||
case C.KEY_BACKSPACE:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case C.KEY_F0 + 1:
|
|
||||||
return Event{F1, 0, nil}
|
|
||||||
case C.KEY_F0 + 2:
|
|
||||||
return Event{F2, 0, nil}
|
|
||||||
case C.KEY_F0 + 3:
|
|
||||||
return Event{F3, 0, nil}
|
|
||||||
case C.KEY_F0 + 4:
|
|
||||||
return Event{F4, 0, nil}
|
|
||||||
case C.KEY_F0 + 5:
|
|
||||||
return Event{F5, 0, nil}
|
|
||||||
case C.KEY_F0 + 6:
|
|
||||||
return Event{F6, 0, nil}
|
|
||||||
case C.KEY_F0 + 7:
|
|
||||||
return Event{F7, 0, nil}
|
|
||||||
case C.KEY_F0 + 8:
|
|
||||||
return Event{F8, 0, nil}
|
|
||||||
case C.KEY_F0 + 9:
|
|
||||||
return Event{F9, 0, nil}
|
|
||||||
case C.KEY_F0 + 10:
|
|
||||||
return Event{F10, 0, nil}
|
|
||||||
case C.KEY_F0 + 11:
|
|
||||||
return Event{F11, 0, nil}
|
|
||||||
case C.KEY_F0 + 12:
|
|
||||||
return Event{F12, 0, nil}
|
|
||||||
case C.KEY_DC:
|
|
||||||
return Event{Del, 0, nil}
|
|
||||||
case C.KEY_PPAGE:
|
|
||||||
return Event{PgUp, 0, nil}
|
|
||||||
case C.KEY_NPAGE:
|
|
||||||
return Event{PgDn, 0, nil}
|
|
||||||
case C.KEY_BTAB:
|
|
||||||
return Event{BTab, 0, nil}
|
|
||||||
case C.KEY_ENTER:
|
|
||||||
return Event{CtrlM, 0, nil}
|
|
||||||
case C.KEY_SLEFT:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case C.KEY_SRIGHT:
|
|
||||||
return Event{SRight, 0, nil}
|
|
||||||
case C.KEY_MOUSE:
|
|
||||||
var me C.MEVENT
|
|
||||||
if C.getmouse(&me) != C.ERR {
|
|
||||||
mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0
|
|
||||||
x := int(me.x)
|
|
||||||
y := int(me.y)
|
|
||||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
|
||||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
|
||||||
now := time.Now()
|
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, y)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{y}
|
|
||||||
r.prevDownTime = now
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
|
||||||
double := false
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
|
||||||
} else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON4_PRESSED) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
case C.KEY_RESIZE:
|
|
||||||
return Event{Resize, 0, nil}
|
|
||||||
case ESC:
|
|
||||||
return escSequence()
|
|
||||||
case 127:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case 0:
|
|
||||||
return Event{CtrlSpace, 0, nil}
|
|
||||||
}
|
|
||||||
// CTRL-A ~ CTRL-Z
|
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
|
||||||
return Event{int(c), 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-byte character
|
|
||||||
buffer := []byte{byte(c)}
|
|
||||||
for {
|
|
||||||
r, _ := utf8.DecodeRune(buffer)
|
|
||||||
if r != utf8.RuneError {
|
|
||||||
return Event{Rune, r, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := C.getch()
|
|
||||||
if c == C.ERR {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c >= C.KEY_CODE_YES {
|
|
||||||
C.ungetch(c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer = append(buffer, byte(c))
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user