Prepare for termbox/windows build

`TAGS=termbox make` (or `go build -tags termbox`)
This commit is contained in:
Junegunn Choi 2016-10-24 09:44:56 +09:00
parent 2cff00dce2
commit 0c573b3dff
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
21 changed files with 924 additions and 659 deletions

View File

@ -43,7 +43,7 @@ $(SRCDIR):
ln -s $(ROOTDIR) $(SRCDIR) ln -s $(ROOTDIR) $(SRCDIR)
deps: $(SRCDIR) $(SOURCES) deps: $(SRCDIR) $(SOURCES)
cd $(SRCDIR) && go get cd $(SRCDIR) && go get -tags "$(TAGS)"
android-build: $(SRCDIR) android-build: $(SRCDIR)
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get

View File

@ -83,9 +83,11 @@ Third-party libraries used
- [ncurses][ncurses] - [ncurses][ncurses]
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth) - [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org/2013) - Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords) - [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org/2014) - Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
- Licensed under [MIT](http://mattn.mit-license.org)
License License
------- -------

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
) )
type ansiOffset struct { type ansiOffset struct {
@ -16,9 +16,9 @@ type ansiOffset struct {
} }
type ansiState struct { type ansiState struct {
fg int fg tui.Color
bg int bg tui.Color
attr curses.Attr attr tui.Attr
} }
func (s *ansiState) colored() bool { func (s *ansiState) colored() bool {
@ -134,26 +134,26 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
case 49: case 49:
state.bg = -1 state.bg = -1
case 1: case 1:
state.attr = curses.Bold state.attr = tui.Bold
case 2: case 2:
state.attr = curses.Dim state.attr = tui.Dim
case 4: case 4:
state.attr = curses.Underline state.attr = tui.Underline
case 5: case 5:
state.attr = curses.Blink state.attr = tui.Blink
case 7: case 7:
state.attr = curses.Reverse state.attr = tui.Reverse
case 0: case 0:
init() init()
default: default:
if num >= 30 && num <= 37 { if num >= 30 && num <= 37 {
state.fg = num - 30 state.fg = tui.Color(num - 30)
} else if num >= 40 && num <= 47 { } else if num >= 40 && num <= 47 {
state.bg = num - 40 state.bg = tui.Color(num - 40)
} else if num >= 90 && num <= 97 { } else if num >= 90 && num <= 97 {
state.fg = num - 90 + 8 state.fg = tui.Color(num - 90 + 8)
} else if num >= 100 && num <= 107 { } else if num >= 100 && num <= 107 {
state.bg = num - 100 + 8 state.bg = tui.Color(num - 100 + 8)
} }
} }
case 1: case 1:
@ -164,7 +164,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
state256 = 0 state256 = 0
} }
case 2: case 2:
*ptr = num *ptr = tui.Color(num)
state256 = 0 state256 = 0
} }
} }

View File

@ -4,14 +4,14 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
) )
func TestExtractColor(t *testing.T) { func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) { assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
var attr curses.Attr var attr tui.Attr
if bold { if bold {
attr = curses.Bold attr = tui.Bold
} }
if offset.offset[0] != b || offset.offset[1] != e || if offset.offset[0] != b || offset.offset[1] != e ||
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr { offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {

View File

@ -15,7 +15,6 @@ const (
coordinatorDelayStep time.Duration = 10 * time.Millisecond coordinatorDelayStep time.Duration = 10 * time.Millisecond
// Reader // Reader
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
readerBufferSize = 64 * 1024 readerBufferSize = 64 * 1024
// Terminal // Terminal

8
src/constants_unix.go Normal file
View File

@ -0,0 +1,8 @@
// +build !windows
package fzf
const (
// Reader
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
)

8
src/constants_windows.go Normal file
View File

@ -0,0 +1,8 @@
// +build windows
package fzf
const (
// Reader
defaultCommand = `dir /s/b`
)

View File

@ -9,7 +9,7 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/go-shellwords" "github.com/junegunn/go-shellwords"
) )
@ -142,7 +142,7 @@ type Options struct {
Multi bool Multi bool
Ansi bool Ansi bool
Mouse bool Mouse bool
Theme *curses.ColorTheme Theme *tui.ColorTheme
Black bool Black bool
Reverse bool Reverse bool
Cycle bool Cycle bool
@ -187,7 +187,7 @@ func defaultOptions() *Options {
Multi: false, Multi: false,
Ansi: false, Ansi: false,
Mouse: true, Mouse: true,
Theme: curses.EmptyTheme(), Theme: tui.EmptyTheme(),
Black: false, Black: false,
Reverse: false, Reverse: false,
Cycle: false, Cycle: false,
@ -358,60 +358,60 @@ func parseKeyChords(str string, message string) map[int]string {
chord := 0 chord := 0
switch lkey { switch lkey {
case "up": case "up":
chord = curses.Up chord = tui.Up
case "down": case "down":
chord = curses.Down chord = tui.Down
case "left": case "left":
chord = curses.Left chord = tui.Left
case "right": case "right":
chord = curses.Right chord = tui.Right
case "enter", "return": case "enter", "return":
chord = curses.CtrlM chord = tui.CtrlM
case "space": case "space":
chord = curses.AltZ + int(' ') chord = tui.AltZ + int(' ')
case "bspace", "bs": case "bspace", "bs":
chord = curses.BSpace chord = tui.BSpace
case "alt-enter", "alt-return": case "alt-enter", "alt-return":
chord = curses.AltEnter chord = tui.AltEnter
case "alt-space": case "alt-space":
chord = curses.AltSpace chord = tui.AltSpace
case "alt-/": case "alt-/":
chord = curses.AltSlash chord = tui.AltSlash
case "alt-bs", "alt-bspace": case "alt-bs", "alt-bspace":
chord = curses.AltBS chord = tui.AltBS
case "tab": case "tab":
chord = curses.Tab chord = tui.Tab
case "btab", "shift-tab": case "btab", "shift-tab":
chord = curses.BTab chord = tui.BTab
case "esc": case "esc":
chord = curses.ESC chord = tui.ESC
case "del": case "del":
chord = curses.Del chord = tui.Del
case "home": case "home":
chord = curses.Home chord = tui.Home
case "end": case "end":
chord = curses.End chord = tui.End
case "pgup", "page-up": case "pgup", "page-up":
chord = curses.PgUp chord = tui.PgUp
case "pgdn", "page-down": case "pgdn", "page-down":
chord = curses.PgDn chord = tui.PgDn
case "shift-left": case "shift-left":
chord = curses.SLeft chord = tui.SLeft
case "shift-right": case "shift-right":
chord = curses.SRight chord = tui.SRight
case "double-click": case "double-click":
chord = curses.DoubleClick chord = tui.DoubleClick
case "f10": case "f10":
chord = curses.F10 chord = tui.F10
default: default:
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) { if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chord = curses.CtrlA + int(lkey[5]) - 'a' chord = tui.CtrlA + int(lkey[5]) - 'a'
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) { } else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
chord = curses.AltA + int(lkey[4]) - 'a' chord = tui.AltA + int(lkey[4]) - 'a'
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' { } else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
chord = curses.F1 + int(key[1]) - '1' chord = tui.F1 + int(key[1]) - '1'
} else if utf8.RuneCountInString(key) == 1 { } else if utf8.RuneCountInString(key) == 1 {
chord = curses.AltZ + int([]rune(key)[0]) chord = tui.AltZ + int([]rune(key)[0])
} else { } else {
errorExit("unsupported key: " + key) errorExit("unsupported key: " + key)
} }
@ -458,7 +458,7 @@ func parseTiebreak(str string) []criterion {
return criteria return criteria
} }
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme { func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
if theme != nil { if theme != nil {
dupe := *theme dupe := *theme
return &dupe return &dupe
@ -466,16 +466,16 @@ func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
return nil return nil
} }
func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme { func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme := dupeTheme(defaultTheme) theme := dupeTheme(defaultTheme)
for _, str := range strings.Split(strings.ToLower(str), ",") { for _, str := range strings.Split(strings.ToLower(str), ",") {
switch str { switch str {
case "dark": case "dark":
theme = dupeTheme(curses.Dark256) theme = dupeTheme(tui.Dark256)
case "light": case "light":
theme = dupeTheme(curses.Light256) theme = dupeTheme(tui.Light256)
case "16": case "16":
theme = dupeTheme(curses.Default16) theme = dupeTheme(tui.Default16)
case "bw", "no": case "bw", "no":
theme = nil theme = nil
default: default:
@ -495,7 +495,7 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
if err != nil || ansi32 < -1 || ansi32 > 255 { if err != nil || ansi32 < -1 || ansi32 > 255 {
fail() fail()
} }
ansi := int16(ansi32) ansi := tui.Color(ansi32)
switch pair[0] { switch pair[0] {
case "fg": case "fg":
theme.Fg = ansi theme.Fg = ansi
@ -572,9 +572,9 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
} }
var key int var key int
if len(pair[0]) == 1 && pair[0][0] == escapedColon { if len(pair[0]) == 1 && pair[0][0] == escapedColon {
key = ':' + curses.AltZ key = ':' + tui.AltZ
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma { } else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
key = ',' + curses.AltZ key = ',' + tui.AltZ
} else { } else {
keys := parseKeyChords(pair[0], "key name required") keys := parseKeyChords(pair[0], "key name required")
key = firstKey(keys) key = firstKey(keys)
@ -868,7 +868,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--color": case "--color":
spec := optionalNextString(allArgs, &i) spec := optionalNextString(allArgs, &i)
if len(spec) == 0 { if len(spec) == 0 {
opts.Theme = curses.EmptyTheme() opts.Theme = tui.EmptyTheme()
} else { } else {
opts.Theme = parseTheme(opts.Theme, spec) opts.Theme = parseTheme(opts.Theme, spec)
} }
@ -905,7 +905,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "+c", "--no-color": case "+c", "--no-color":
opts.Theme = nil opts.Theme = nil
case "+2", "--no-256": case "+2", "--no-256":
opts.Theme = curses.Default16 opts.Theme = tui.Default16
case "--black": case "--black":
opts.Black = true opts.Black = true
case "--no-black": case "--no-black":
@ -1071,11 +1071,11 @@ func parseOptions(opts *Options, allArgs []string) {
func postProcessOptions(opts *Options) { func postProcessOptions(opts *Options) {
// Default actions for CTRL-N / CTRL-P when --history is set // Default actions for CTRL-N / CTRL-P when --history is set
if opts.History != nil { if opts.History != nil {
if _, prs := opts.Keymap[curses.CtrlP]; !prs { if _, prs := opts.Keymap[tui.CtrlP]; !prs {
opts.Keymap[curses.CtrlP] = actPreviousHistory opts.Keymap[tui.CtrlP] = actPreviousHistory
} }
if _, prs := opts.Keymap[curses.CtrlN]; !prs { if _, prs := opts.Keymap[tui.CtrlN]; !prs {
opts.Keymap[curses.CtrlN] = actNextHistory opts.Keymap[tui.CtrlN] = actNextHistory
} }
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@ -133,48 +133,48 @@ func TestParseKeys(t *testing.T) {
if len(pairs) != 11 { if len(pairs) != 11 {
t.Error(11) t.Error(11)
} }
check(curses.CtrlZ, "ctrl-z") check(tui.CtrlZ, "ctrl-z")
check(curses.AltZ, "alt-z") check(tui.AltZ, "alt-z")
check(curses.F2, "f2") check(tui.F2, "f2")
check(curses.AltZ+'@', "@") check(tui.AltZ+'@', "@")
check(curses.AltA, "Alt-a") check(tui.AltA, "Alt-a")
check(curses.AltZ+'!', "!") check(tui.AltZ+'!', "!")
check(curses.CtrlA+'g'-'a', "ctrl-G") check(tui.CtrlA+'g'-'a', "ctrl-G")
check(curses.AltZ+'J', "J") check(tui.AltZ+'J', "J")
check(curses.AltZ+'g', "g") check(tui.AltZ+'g', "g")
check(curses.AltEnter, "ALT-enter") check(tui.AltEnter, "ALT-enter")
check(curses.AltSpace, "alt-SPACE") check(tui.AltSpace, "alt-SPACE")
// Synonyms // Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "") pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 { if len(pairs) != 9 {
t.Error(9) t.Error(9)
} }
check(curses.CtrlM, "Return") check(tui.CtrlM, "Return")
check(curses.AltZ+' ', "space") check(tui.AltZ+' ', "space")
check(curses.Tab, "tab") check(tui.Tab, "tab")
check(curses.BTab, "btab") check(tui.BTab, "btab")
check(curses.ESC, "esc") check(tui.ESC, "esc")
check(curses.Up, "up") check(tui.Up, "up")
check(curses.Down, "down") check(tui.Down, "down")
check(curses.Left, "left") check(tui.Left, "left")
check(curses.Right, "right") check(tui.Right, "right")
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "") pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
if len(pairs) != 11 { if len(pairs) != 11 {
t.Error(11) t.Error(11)
} }
check(curses.Tab, "Ctrl-I") check(tui.Tab, "Ctrl-I")
check(curses.PgUp, "page-up") check(tui.PgUp, "page-up")
check(curses.PgDn, "Page-Down") check(tui.PgDn, "Page-Down")
check(curses.Home, "Home") check(tui.Home, "Home")
check(curses.End, "End") check(tui.End, "End")
check(curses.AltBS, "Alt-BSpace") check(tui.AltBS, "Alt-BSpace")
check(curses.SLeft, "shift-left") check(tui.SLeft, "shift-left")
check(curses.SRight, "shift-right") check(tui.SRight, "shift-right")
check(curses.BTab, "shift-tab") check(tui.BTab, "shift-tab")
check(curses.CtrlM, "Enter") check(tui.CtrlM, "Enter")
check(curses.BSpace, "bspace") check(tui.BSpace, "bspace")
} }
func TestParseKeysWithComma(t *testing.T) { func TestParseKeysWithComma(t *testing.T) {
@ -191,36 +191,36 @@ func TestParseKeysWithComma(t *testing.T) {
pairs := parseKeyChords(",", "") pairs := parseKeyChords(",", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords(",,a,b", "") pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a") check(pairs, tui.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b") check(pairs, tui.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,b,,", "") pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a") check(pairs, tui.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b") check(pairs, tui.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,,,b", "") pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a") check(pairs, tui.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b") check(pairs, tui.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,,,b,c", "") pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4) checkN(len(pairs), 4)
check(pairs, curses.AltZ+'a', "a") check(pairs, tui.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b") check(pairs, tui.AltZ+'b', "b")
check(pairs, curses.AltZ+'c', "c") check(pairs, tui.AltZ+'c', "c")
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords(",,,", "") pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, curses.AltZ+',', ",") check(pairs, tui.AltZ+',', ",")
} }
func TestBind(t *testing.T) { func TestBind(t *testing.T) {
@ -236,41 +236,41 @@ func TestBind(t *testing.T) {
} }
keymap := defaultKeymap() keymap := defaultKeymap()
execmap := make(map[int]string) execmap := make(map[int]string)
check(actBeginningOfLine, keymap[curses.CtrlA]) check(actBeginningOfLine, keymap[tui.CtrlA])
parseKeymap(keymap, execmap, parseKeymap(keymap, execmap,
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+ "ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+ "f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+ "alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)") ",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
check(actKillLine, keymap[curses.CtrlA]) check(actKillLine, keymap[tui.CtrlA])
check(actToggleSort, keymap[curses.CtrlB]) check(actToggleSort, keymap[tui.CtrlB])
check(actPageUp, keymap[curses.AltZ+'c']) check(actPageUp, keymap[tui.AltZ+'c'])
check(actAbort, keymap[curses.AltZ+',']) check(actAbort, keymap[tui.AltZ+','])
check(actAccept, keymap[curses.AltZ+':']) check(actAccept, keymap[tui.AltZ+':'])
check(actPageDown, keymap[curses.AltZ]) check(actPageDown, keymap[tui.AltZ])
check(actExecute, keymap[curses.F1]) check(actExecute, keymap[tui.F1])
check(actExecute, keymap[curses.F2]) check(actExecute, keymap[tui.F2])
check(actExecute, keymap[curses.F3]) check(actExecute, keymap[tui.F3])
check(actExecute, keymap[curses.F4]) check(actExecute, keymap[tui.F4])
checkString("ls {}", execmap[curses.F1]) checkString("ls {}", execmap[tui.F1])
checkString("echo {}, {}, {}", execmap[curses.F2]) checkString("echo {}, {}, {}", execmap[tui.F2])
checkString("echo '({})'", execmap[curses.F3]) checkString("echo '({})'", execmap[tui.F3])
checkString("less {}", execmap[curses.F4]) checkString("less {}", execmap[tui.F4])
checkString("echo (,),[,],/,:,;,%,{}", execmap[curses.AltA]) checkString("echo (,),[,],/,:,;,%,{}", execmap[tui.AltA])
checkString("echo (,),[,],/,:,@,%,{}", execmap[curses.AltB]) checkString("echo (,),[,],/,:,@,%,{}", execmap[tui.AltB])
checkString("\nfoobar,Y:execute(baz)", execmap[curses.AltZ+'X']) checkString("\nfoobar,Y:execute(baz)", execmap[tui.AltZ+'X'])
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} { for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char)) parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
checkString("foobar", execmap[curses.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])]) checkString("foobar", execmap[tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
} }
parseKeymap(keymap, execmap, "f1:abort") parseKeymap(keymap, execmap, "f1:abort")
check(actAbort, keymap[curses.F1]) check(actAbort, keymap[tui.F1])
} }
func TestColorSpec(t *testing.T) { func TestColorSpec(t *testing.T) {
theme := curses.Dark256 theme := tui.Dark256
dark := parseTheme(theme, "dark") dark := parseTheme(theme, "dark")
if *dark != *theme { if *dark != *theme {
t.Errorf("colors should be equivalent") t.Errorf("colors should be equivalent")
@ -283,7 +283,7 @@ func TestColorSpec(t *testing.T) {
if *light == *theme { if *light == *theme {
t.Errorf("should not be equivalent") t.Errorf("should not be equivalent")
} }
if *light != *curses.Light256 { if *light != *tui.Light256 {
t.Errorf("colors should be equivalent") t.Errorf("colors should be equivalent")
} }
if light == theme { if light == theme {
@ -294,23 +294,23 @@ func TestColorSpec(t *testing.T) {
if customized.Fg != 231 || customized.Bg != 232 { if customized.Fg != 231 || customized.Bg != 232 {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
if *curses.Dark256 == *customized { if *tui.Dark256 == *customized {
t.Errorf("colors should not be equivalent") t.Errorf("colors should not be equivalent")
} }
customized.Fg = curses.Dark256.Fg customized.Fg = tui.Dark256.Fg
customized.Bg = curses.Dark256.Bg customized.Bg = tui.Dark256.Bg
if *curses.Dark256 != *customized { if *tui.Dark256 != *customized {
t.Errorf("colors should now be equivalent: %v, %v", curses.Dark256, customized) t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
} }
customized = parseTheme(theme, "fg:231,dark,bg:232") customized = parseTheme(theme, "fg:231,dark,bg:232")
if customized.Fg != curses.Dark256.Fg || customized.Bg == curses.Dark256.Bg { if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
} }
func TestParseNilTheme(t *testing.T) { func TestParseNilTheme(t *testing.T) {
var theme *curses.ColorTheme var theme *tui.ColorTheme
newTheme := parseTheme(theme, "prompt:12") newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil { if newTheme != nil {
t.Errorf("color is disabled. keep it that way.") t.Errorf("color is disabled. keep it that way.")
@ -330,21 +330,21 @@ func TestDefaultCtrlNP(t *testing.T) {
t.Error() t.Error()
} }
} }
check([]string{}, curses.CtrlN, actDown) check([]string{}, tui.CtrlN, actDown)
check([]string{}, curses.CtrlP, actUp) check([]string{}, tui.CtrlP, actUp)
check([]string{"--bind=ctrl-n:accept"}, curses.CtrlN, actAccept) check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, curses.CtrlP, actAccept) check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
hist := "--history=/tmp/fzf-history" hist := "--history=/tmp/fzf-history"
check([]string{hist}, curses.CtrlN, actNextHistory) check([]string{hist}, tui.CtrlN, actNextHistory)
check([]string{hist}, curses.CtrlP, actPreviousHistory) check([]string{hist}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlN, actAccept) check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlP, actPreviousHistory) check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlN, actNextHistory) check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlP, actAccept) check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
} }
func optsFor(words ...string) *Options { func optsFor(words ...string) *Options {

View File

@ -5,7 +5,7 @@ import (
"sort" "sort"
"unicode" "unicode"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@ -14,8 +14,8 @@ type Offset [2]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
color int color tui.ColorPair
attr curses.Attr attr tui.Attr
index int32 index int32
} }
@ -92,7 +92,7 @@ func minRank() rank {
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, theme *curses.ColorTheme, color int, attr curses.Attr, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI code, or --color=no // No ANSI code, or --color=no
@ -149,23 +149,23 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *curses.ColorThe
fg := ansi.color.fg fg := ansi.color.fg
if fg == -1 { if fg == -1 {
if current { if current {
fg = int(theme.Current) fg = theme.Current
} else { } else {
fg = int(theme.Fg) fg = theme.Fg
} }
} }
bg := ansi.color.bg bg := ansi.color.bg
if bg == -1 { if bg == -1 {
if current { if current {
bg = int(theme.DarkBg) bg = theme.DarkBg
} else { } else {
bg = int(theme.Bg) bg = theme.Bg
} }
} }
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: curses.PairFor(fg, bg), color: tui.PairFor(fg, bg),
attr: ansi.color.attr | attr}) attr: ansi.color.attr.Merge(attr)})
} }
} }
} }

View File

@ -1,3 +1,5 @@
// +build !termbox
package fzf package fzf
import ( import (
@ -5,7 +7,7 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@ -98,26 +100,26 @@ func TestColorOffset(t *testing.T) {
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}}, ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, curses.Bold}}, ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}}, ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, curses.Bold}}}}} ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}] // [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
colors := item.colorOffsets(offsets, curses.Dark256, 99, 0, true) colors := item.colorOffsets(offsets, tui.Dark256, 99, 0, true)
assert := func(idx int, b int32, e int32, c int, bold bool) { assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
var attr curses.Attr var attr tui.Attr
if bold { if bold {
attr = curses.Bold attr = tui.Bold
} }
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr { if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
t.Error(o) t.Error(o)
} }
} }
assert(0, 0, 5, curses.ColUser, false) assert(0, 0, 5, tui.ColUser, false)
assert(1, 5, 15, 99, false) assert(1, 5, 15, 99, false)
assert(2, 15, 20, curses.ColUser, false) assert(2, 15, 20, tui.ColUser, false)
assert(3, 22, 25, curses.ColUser+1, true) assert(3, 22, 25, tui.ColUser+1, true)
assert(4, 25, 35, 99, false) assert(4, 25, 35, 99, false)
assert(5, 35, 40, curses.ColUser+2, true) assert(5, 35, 40, tui.ColUser+2, true)
} }

View File

@ -12,7 +12,7 @@ import (
"syscall" "syscall"
"time" "time"
C "github.com/junegunn/fzf/src/curses" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/junegunn/go-runewidth" "github.com/junegunn/go-runewidth"
@ -69,9 +69,9 @@ type Terminal struct {
header0 []string header0 []string
ansi bool ansi bool
margin [4]sizeSpec margin [4]sizeSpec
window *C.Window window *tui.Window
bwindow *C.Window bwindow *tui.Window
pwindow *C.Window pwindow *tui.Window
count int count int
progress int progress int
reading bool reading bool
@ -90,7 +90,7 @@ type Terminal struct {
suppress bool suppress bool
startChan chan bool startChan chan bool
slab *util.Slab slab *util.Slab
theme *C.ColorTheme theme *tui.ColorTheme
} }
type selectedItem struct { type selectedItem struct {
@ -187,51 +187,51 @@ const (
func defaultKeymap() map[int]actionType { func defaultKeymap() map[int]actionType {
keymap := make(map[int]actionType) keymap := make(map[int]actionType)
keymap[C.Invalid] = actInvalid keymap[tui.Invalid] = actInvalid
keymap[C.CtrlA] = actBeginningOfLine keymap[tui.CtrlA] = actBeginningOfLine
keymap[C.CtrlB] = actBackwardChar keymap[tui.CtrlB] = actBackwardChar
keymap[C.CtrlC] = actAbort keymap[tui.CtrlC] = actAbort
keymap[C.CtrlG] = actAbort keymap[tui.CtrlG] = actAbort
keymap[C.CtrlQ] = actAbort keymap[tui.CtrlQ] = actAbort
keymap[C.ESC] = actAbort keymap[tui.ESC] = actAbort
keymap[C.CtrlD] = actDeleteCharEOF keymap[tui.CtrlD] = actDeleteCharEOF
keymap[C.CtrlE] = actEndOfLine keymap[tui.CtrlE] = actEndOfLine
keymap[C.CtrlF] = actForwardChar keymap[tui.CtrlF] = actForwardChar
keymap[C.CtrlH] = actBackwardDeleteChar keymap[tui.CtrlH] = actBackwardDeleteChar
keymap[C.BSpace] = actBackwardDeleteChar keymap[tui.BSpace] = actBackwardDeleteChar
keymap[C.Tab] = actToggleDown keymap[tui.Tab] = actToggleDown
keymap[C.BTab] = actToggleUp keymap[tui.BTab] = actToggleUp
keymap[C.CtrlJ] = actDown keymap[tui.CtrlJ] = actDown
keymap[C.CtrlK] = actUp keymap[tui.CtrlK] = actUp
keymap[C.CtrlL] = actClearScreen keymap[tui.CtrlL] = actClearScreen
keymap[C.CtrlM] = actAccept keymap[tui.CtrlM] = actAccept
keymap[C.CtrlN] = actDown keymap[tui.CtrlN] = actDown
keymap[C.CtrlP] = actUp keymap[tui.CtrlP] = actUp
keymap[C.CtrlU] = actUnixLineDiscard keymap[tui.CtrlU] = actUnixLineDiscard
keymap[C.CtrlW] = actUnixWordRubout keymap[tui.CtrlW] = actUnixWordRubout
keymap[C.CtrlY] = actYank keymap[tui.CtrlY] = actYank
keymap[C.AltB] = actBackwardWord keymap[tui.AltB] = actBackwardWord
keymap[C.SLeft] = actBackwardWord keymap[tui.SLeft] = actBackwardWord
keymap[C.AltF] = actForwardWord keymap[tui.AltF] = actForwardWord
keymap[C.SRight] = actForwardWord keymap[tui.SRight] = actForwardWord
keymap[C.AltD] = actKillWord keymap[tui.AltD] = actKillWord
keymap[C.AltBS] = actBackwardKillWord keymap[tui.AltBS] = actBackwardKillWord
keymap[C.Up] = actUp keymap[tui.Up] = actUp
keymap[C.Down] = actDown keymap[tui.Down] = actDown
keymap[C.Left] = actBackwardChar keymap[tui.Left] = actBackwardChar
keymap[C.Right] = actForwardChar keymap[tui.Right] = actForwardChar
keymap[C.Home] = actBeginningOfLine keymap[tui.Home] = actBeginningOfLine
keymap[C.End] = actEndOfLine keymap[tui.End] = actEndOfLine
keymap[C.Del] = actDeleteChar keymap[tui.Del] = actDeleteChar
keymap[C.PgUp] = actPageUp keymap[tui.PgUp] = actPageUp
keymap[C.PgDn] = actPageDown keymap[tui.PgDn] = actPageDown
keymap[C.Rune] = actRune keymap[tui.Rune] = actRune
keymap[C.Mouse] = actMouse keymap[tui.Mouse] = actMouse
keymap[C.DoubleClick] = actAccept keymap[tui.DoubleClick] = actAccept
return keymap return keymap
} }
@ -299,7 +299,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan bool, 1),
initFunc: func() { initFunc: func() {
C.Init(opts.Theme, opts.Black, opts.Mouse) tui.Init(opts.Theme, opts.Black, opts.Mouse)
}} }}
} }
@ -429,8 +429,8 @@ func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
} }
func (t *Terminal) resizeWindows() { func (t *Terminal) resizeWindows() {
screenWidth := C.MaxX() screenWidth := tui.MaxX()
screenHeight := C.MaxY() screenHeight := tui.MaxY()
marginInt := [4]int{} marginInt := [4]int{}
for idx, sizeSpec := range t.margin { for idx, sizeSpec := range t.margin {
if sizeSpec.percent { if sizeSpec.percent {
@ -479,33 +479,33 @@ func (t *Terminal) resizeWindows() {
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
createPreviewWindow := func(y int, x int, w int, h int) { createPreviewWindow := func(y int, x int, w int, h int) {
t.bwindow = C.NewWindow(y, x, w, h, true) t.bwindow = tui.NewWindow(y, x, w, h, true)
t.pwindow = C.NewWindow(y+1, x+2, w-4, h-2, false) t.pwindow = tui.NewWindow(y+1, x+2, w-4, h-2, false)
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = C.NewWindow( t.window = tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false) marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = C.NewWindow( t.window = tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false) marginInt[0], marginInt[3], width, height-pheight, false)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = C.NewWindow( t.window = tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false) marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = C.NewWindow( t.window = tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false) marginInt[0], marginInt[3], width-pwidth, height, false)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
t.window = C.NewWindow( t.window = tui.NewWindow(
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
@ -531,24 +531,24 @@ func (t *Terminal) placeCursor() {
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
t.move(0, 0, true) t.move(0, 0, true)
t.window.CPrint(C.ColPrompt, C.Bold, t.prompt) t.window.CPrint(tui.ColPrompt, tui.Bold, t.prompt)
t.window.CPrint(C.ColNormal, C.Bold, string(t.input)) t.window.CPrint(tui.ColNormal, tui.Bold, string(t.input))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
if t.inlineInfo { if t.inlineInfo {
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true) t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
if t.reading { if t.reading {
t.window.CPrint(C.ColSpinner, C.Bold, " < ") t.window.CPrint(tui.ColSpinner, tui.Bold, " < ")
} else { } else {
t.window.CPrint(C.ColPrompt, C.Bold, " < ") t.window.CPrint(tui.ColPrompt, tui.Bold, " < ")
} }
} else { } else {
t.move(1, 0, true) t.move(1, 0, true)
if t.reading { if t.reading {
duration := int64(spinnerDuration) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
t.window.CPrint(C.ColSpinner, C.Bold, _spinner[idx]) t.window.CPrint(tui.ColSpinner, tui.Bold, _spinner[idx])
} }
t.move(1, 2, false) t.move(1, 2, false)
} }
@ -567,7 +567,7 @@ func (t *Terminal) printInfo() {
if t.progress > 0 && t.progress < 100 { if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress) output += fmt.Sprintf(" (%d%%)", t.progress)
} }
t.window.CPrint(C.ColInfo, 0, output) t.window.CPrint(tui.ColInfo, 0, output)
} }
func (t *Terminal) printHeader() { func (t *Terminal) printHeader() {
@ -591,7 +591,8 @@ func (t *Terminal) printHeader() {
colors: colors} colors: colors}
t.move(line, 2, true) t.move(line, 2, true)
t.printHighlighted(&Result{item: item}, 0, C.ColHeader, 0, false) t.printHighlighted(&Result{item: item},
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false)
} }
} }
@ -625,21 +626,21 @@ func (t *Terminal) printItem(result *Result, i int, current bool) {
} else if current { } else if current {
label = ">" label = ">"
} }
t.window.CPrint(C.ColCursor, C.Bold, label) t.window.CPrint(tui.ColCursor, tui.Bold, label)
if current { if current {
if selected { if selected {
t.window.CPrint(C.ColSelected, C.Bold, ">") t.window.CPrint(tui.ColSelected, tui.Bold, ">")
} else { } else {
t.window.CPrint(C.ColCurrent, C.Bold, " ") t.window.CPrint(tui.ColCurrent, tui.Bold, " ")
} }
t.printHighlighted(result, C.Bold, C.ColCurrent, C.ColCurrentMatch, true) t.printHighlighted(result, tui.Bold, tui.ColCurrent, tui.ColCurrentMatch, true)
} else { } else {
if selected { if selected {
t.window.CPrint(C.ColSelected, C.Bold, ">") t.window.CPrint(tui.ColSelected, tui.Bold, ">")
} else { } else {
t.window.Print(" ") t.window.Print(" ")
} }
t.printHighlighted(result, 0, C.ColNormal, C.ColMatch, false) t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false)
} }
} }
@ -695,7 +696,7 @@ func overflow(runes []rune, max int) bool {
return false return false
} }
func (t *Terminal) printHighlighted(result *Result, attr C.Attr, col1 int, col2 int, current bool) { func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool) {
item := result.item item := result.item
// Overflow // Overflow
@ -827,7 +828,7 @@ func (t *Terminal) printPreview() {
if t.previewer.offset > 0 { if t.previewer.offset > 0 {
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines) offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
t.pwindow.Move(0, t.pwindow.Width-len(offset)) t.pwindow.Move(0, t.pwindow.Width-len(offset))
t.pwindow.CPrint(C.ColInfo, C.Reverse, offset) t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
} }
} }
@ -858,11 +859,10 @@ func (t *Terminal) printAll() {
func (t *Terminal) refresh() { func (t *Terminal) refresh() {
if !t.suppress { if !t.suppress {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
t.bwindow.Refresh() tui.RefreshWindows([]*tui.Window{t.bwindow, t.pwindow, t.window})
t.pwindow.Refresh() } else {
tui.RefreshWindows([]*tui.Window{t.window})
} }
t.window.Refresh()
C.DoUpdate()
} }
} }
@ -912,10 +912,10 @@ func (t *Terminal) rubout(pattern string) {
t.input = append(t.input[:t.cx], after...) t.input = append(t.input[:t.cx], after...)
} }
func keyMatch(key int, event C.Event) bool { func keyMatch(key int, event tui.Event) bool {
return event.Type == key || return event.Type == key ||
event.Type == C.Rune && int(event.Char) == key-C.AltZ || event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
event.Type == C.Mouse && key == C.DoubleClick && event.MouseEvent.Double event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
} }
func quoteEntry(entry string) string { func quoteEntry(entry string) string {
@ -980,7 +980,7 @@ func (t *Terminal) executeCommand(template string, items []*Item) {
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
C.Endwin() tui.Pause()
cmd.Run() cmd.Run()
t.refresh() t.refresh()
} }
@ -1014,7 +1014,7 @@ func (t *Terminal) Loop() {
}() }()
resizeChan := make(chan os.Signal, 1) resizeChan := make(chan os.Signal, 1)
signal.Notify(resizeChan, syscall.SIGWINCH) notifyOnResize(resizeChan) // Non-portable
go func() { go func() {
for { for {
<-resizeChan <-resizeChan
@ -1126,12 +1126,11 @@ func (t *Terminal) Loop() {
case reqRefresh: case reqRefresh:
t.suppress = false t.suppress = false
case reqRedraw: case reqRedraw:
C.Clear() tui.Clear()
C.Endwin() tui.Refresh()
C.Refresh()
t.printAll() t.printAll()
case reqClose: case reqClose:
C.Close() tui.Close()
if t.output() { if t.output() {
exit(exitOk) exit(exitOk)
} }
@ -1144,11 +1143,11 @@ func (t *Terminal) Loop() {
case reqPreviewRefresh: case reqPreviewRefresh:
t.printPreview() t.printPreview()
case reqPrintQuery: case reqPrintQuery:
C.Close() tui.Close()
t.printer(string(t.input)) t.printer(string(t.input))
exit(exitOk) exit(exitOk)
case reqQuit: case reqQuit:
C.Close() tui.Close()
exit(exitInterrupt) exit(exitInterrupt)
} }
} }
@ -1161,7 +1160,7 @@ func (t *Terminal) Loop() {
looping := true looping := true
for looping { for looping {
event := C.GetChar() event := tui.GetChar()
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input
@ -1445,7 +1444,7 @@ func (t *Terminal) Loop() {
// Double-click // Double-click
if my >= min { if my >= min {
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() { if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
return doAction(t.keymap[C.DoubleClick], C.DoubleClick) return doAction(t.keymap[tui.DoubleClick], tui.DoubleClick)
} }
} }
} else if me.Down { } else if me.Down {
@ -1468,8 +1467,8 @@ func (t *Terminal) Loop() {
mapkey := event.Type mapkey := event.Type
if t.jumping == jumpDisabled { if t.jumping == jumpDisabled {
action := t.keymap[mapkey] action := t.keymap[mapkey]
if mapkey == C.Rune { if mapkey == tui.Rune {
mapkey = int(event.Char) + int(C.AltZ) mapkey = int(event.Char) + int(tui.AltZ)
if act, prs := t.keymap[mapkey]; prs { if act, prs := t.keymap[mapkey]; prs {
action = act action = act
} }
@ -1484,7 +1483,7 @@ func (t *Terminal) Loop() {
} }
changed = string(previousInput) != string(t.input) changed = string(previousInput) != string(t.input)
} else { } else {
if mapkey == C.Rune { if mapkey == tui.Rune {
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() { if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
t.cy = idx + t.offset t.cy = idx + t.offset
if t.jumping == jumpAcceptEnabled { if t.jumping == jumpAcceptEnabled {

13
src/terminal_unix.go Normal file
View File

@ -0,0 +1,13 @@
// +build !windows
package fzf
import (
"os"
"os/signal"
"syscall"
)
func notifyOnResize(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGWINCH)
}

11
src/terminal_windows.go Normal file
View File

@ -0,0 +1,11 @@
// +build windows
package fzf
import (
"os"
)
func notifyOnResize(resizeChan chan<- os.Signal) {
// TODO
}

View File

@ -1,4 +1,7 @@
package curses // +build !windows
// +build !termbox
package tui
/* /*
#include <ncurses.h> #include <ncurses.h>
@ -10,7 +13,6 @@ package curses
SCREEN *c_newterm () { SCREEN *c_newterm () {
return newterm(NULL, stderr, stdin); return newterm(NULL, stderr, stdin);
} }
*/ */
import "C" import "C"
@ -23,6 +25,10 @@ import (
"unicode/utf8" "unicode/utf8"
) )
type ColorPair int16
type Attr C.int
type WindowImpl C.WINDOW
const ( const (
Bold = C.A_BOLD Bold = C.A_BOLD
Dim = C.A_DIM Dim = C.A_DIM
@ -31,89 +37,13 @@ const (
Underline = C.A_UNDERLINE Underline = C.A_UNDERLINE
) )
type Attr C.int
// Types of user action
const ( const (
Rune = iota AttrRegular Attr = 0
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'
) )
// Pallete // Pallete
const ( const (
_ = iota ColDefault ColorPair = iota
ColNormal ColNormal
ColPrompt ColPrompt
ColMatch ColMatch
@ -128,193 +58,26 @@ const (
ColUser // Should be the last entry ColUser // Should be the last entry
) )
const (
doubleClickDuration = 500 * time.Millisecond
colDefault = -1
colUndefined = -2
)
type ColorTheme struct {
Fg int16
Bg int16
DarkBg int16
Prompt int16
Match int16
Current int16
CurrentMatch int16
Spinner int16
Info int16
Cursor int16
Selected int16
Header int16
Border int16
}
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 ( var (
_buf []byte
_in *os.File _in *os.File
_color bool
_colorFn func(int, Attr) C.int
_colorMap map[int]int
_prevDownTime time.Time
_clickY []int
_screen *C.SCREEN _screen *C.SCREEN
Default16 *ColorTheme _colorMap map[int]ColorPair
Dark256 *ColorTheme _colorFn func(ColorPair, Attr) C.int
Light256 *ColorTheme
) )
type Window struct {
win *C.WINDOW
Top int
Left int
Width int
Height int
}
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(ColNormal)))
}
if border {
attr := _colorFn(ColBorder, 0)
C.wattron(win, attr)
C.box(win, 0, 0)
C.wattroff(win, attr)
}
return &Window{
win: win,
Top: top,
Left: left,
Width: width,
Height: height,
}
}
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() { func init() {
_prevDownTime = time.Unix(0, 0) _colorMap = make(map[int]ColorPair)
_clickY = []int{}
_colorMap = make(map[int]int)
Default16 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
DarkBg: C.COLOR_BLACK,
Prompt: C.COLOR_BLUE,
Match: C.COLOR_GREEN,
Current: C.COLOR_YELLOW,
CurrentMatch: C.COLOR_GREEN,
Spinner: C.COLOR_GREEN,
Info: C.COLOR_WHITE,
Cursor: C.COLOR_RED,
Selected: C.COLOR_MAGENTA,
Header: C.COLOR_CYAN,
Border: C.COLOR_BLACK}
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 attrColored(pair int, a Attr) C.int { func (a Attr) Merge(b Attr) Attr {
var attr C.int return a | b
if pair > 0 {
attr = C.COLOR_PAIR(C.int(pair))
}
return attr | C.int(a)
} }
func attrMono(pair int, a Attr) C.int { func DefaultTheme() *ColorTheme {
var attr C.int if C.tigetnum(C.CString("colors")) >= 256 {
switch pair { return Dark256
case ColCurrent:
if a&C.A_BOLD == C.A_BOLD {
attr = C.A_REVERSE
} }
case ColMatch: return Default16
attr = C.A_UNDERLINE
case ColCurrentMatch:
attr = C.A_UNDERLINE | C.A_REVERSE
}
if 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 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 Init(theme *ColorTheme, black bool, mouse bool) { func Init(theme *ColorTheme, black bool, mouse bool) {
@ -344,52 +107,19 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
_color = theme != nil _color = theme != nil
if _color { if _color {
C.start_color() C.start_color()
var baseTheme *ColorTheme InitTheme(theme, black)
if C.tigetnum(C.CString("colors")) >= 256 { initPairs(theme)
baseTheme = Dark256 C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal))))
} else {
baseTheme = Default16
}
initPairs(baseTheme, theme, black)
C.bkgd(C.chtype(C.COLOR_PAIR(ColNormal)))
_colorFn = attrColored _colorFn = attrColored
} else { } else {
_colorFn = attrMono _colorFn = attrMono
} }
} }
func override(baseTheme *ColorTheme, theme *ColorTheme) { func initPairs(theme *ColorTheme) {
o := func(a int16, b int16) int16 {
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)
}
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
if black {
theme.Bg = C.COLOR_BLACK
}
// Updates theme
override(baseTheme, theme)
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg)) C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
initPair := func(group C.short, fg int16, bg int16) { initPair := func(group ColorPair, fg Color, bg Color) {
C.init_pair(group, C.short(fg), C.short(bg)) C.init_pair(C.short(group), C.short(fg), C.short(bg))
} }
initPair(ColNormal, theme.Fg, theme.Bg) initPair(ColNormal, theme.Fg, theme.Bg)
initPair(ColPrompt, theme.Prompt, theme.Bg) initPair(ColPrompt, theme.Prompt, theme.Bg)
@ -404,11 +134,161 @@ func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
initPair(ColBorder, theme.Border, theme.Bg) initPair(ColBorder, theme.Border, theme.Bg)
} }
func Pause() {
C.endwin()
}
func Close() { func Close() {
C.endwin() C.endwin()
C.delscreen(_screen) 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 { func GetBytes() []byte {
c := getch(false) c := getch(false)
_buf = append(_buf, byte(c)) _buf = append(_buf, byte(c))
@ -631,84 +511,3 @@ func GetChar() Event {
sz = rsz sz = rsz
return Event{Rune, r, nil} return Event{Rune, r, nil}
} }
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 int, 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()
}
func Endwin() {
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 int, bg int, 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 (w *Window) Refresh() {
C.wnoutrefresh(w.win)
}
func DoUpdate() {
C.doupdate()
}
func PairFor(fg int, bg int) int {
key := (fg << 8) + bg
if found, prs := _colorMap[key]; prs {
return found
}
id := len(_colorMap) + ColUser
C.init_pair(C.short(id), C.short(fg), C.short(bg))
_colorMap[key] = id
return id
}

151
src/tui/termbox.go Normal file
View 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
View 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)
}

View File

@ -1,4 +1,4 @@
package curses package tui
import ( import (
"testing" "testing"

View File

@ -1,13 +1,11 @@
package util package util
// #include <unistd.h>
import "C"
import ( import (
"math" "math"
"os" "os"
"os/exec"
"time" "time"
"github.com/junegunn/go-isatty"
) )
// Max returns the largest integer // Max returns the largest integer
@ -95,14 +93,5 @@ func DurWithin(
// IsTty returns true is stdin is a terminal // IsTty returns true is stdin is a terminal
func IsTty() bool { func IsTty() bool {
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0 return isatty.IsTerminal(os.Stdin.Fd())
}
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
return exec.Command(shell, "-c", command)
} }

17
src/util/util_unix.go Normal file
View File

@ -0,0 +1,17 @@
// +build !windows
package util
import (
"os"
"os/exec"
)
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
return exec.Command(shell, "-c", command)
}

17
src/util/util_windows.go Normal file
View File

@ -0,0 +1,17 @@
// +build windows
package util
import (
"os"
"os/exec"
)
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "cmd"
}
return exec.Command(shell, "/c", command)
}