From 898d8d94c858774b02668c1490068cf086a1f9f0 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Mon, 7 Nov 2016 02:15:34 +0900 Subject: [PATCH] Fix issues in tcell renderer and Windows build - Fix display of CJK wide characters - Fix horizontal offset of header lines - Add support for keys with ALT modifier, shift-tab, page-up and down - Fix util.ExecCommand to properly parse command-line arguments - Fix redraw on resize - Implement Pause/Resume for execute action - Remove runtime check of GOOS - Change exit status to 2 when tcell failed to start - TBD: Travis CI build for tcell renderer - Pending. tcell cannot reliably ingest keys from tmux send-keys --- .travis.yml | 8 +++- src/Makefile | 2 +- src/reader.go | 3 +- src/terminal.go | 7 ++++ src/tui/ncurses.go | 4 ++ src/tui/tcell.go | 85 ++++++++++++++++++++++++++++------------ src/util/util_unix.go | 5 +++ src/util/util_windows.go | 13 +++++- test/test_go.rb | 2 +- 9 files changed, 96 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 597c1f6..a1a6497 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ language: ruby -rvm: -- 2.2.0 +matrix: + include: + - env: TAGS= + rvm: 2.2.0 +# - env: TAGS=tcell +# rvm: 2.2.0 install: - sudo apt-get update diff --git a/src/Makefile b/src/Makefile index edc77fa..15f4c73 100644 --- a/src/Makefile +++ b/src/Makefile @@ -52,7 +52,7 @@ android-build: $(SRCDIR) rm -f $(RELEASEARM7) test: deps - SHELL=/bin/sh go test -v ./... + SHELL=/bin/sh go test -v -tags "$(TAGS)" ./... install: $(BINDIR)/fzf diff --git a/src/reader.go b/src/reader.go index 15a0f44..7e8e2e0 100644 --- a/src/reader.go +++ b/src/reader.go @@ -4,7 +4,6 @@ import ( "bufio" "io" "os" - "runtime" "github.com/junegunn/fzf/src/util" ) @@ -44,7 +43,7 @@ func (r *Reader) feed(src io.Reader) { if len(bytea) > 0 { if err == nil { // get rid of carriage return if under Windows: - if runtime.GOOS == "windows" && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') { + if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') { bytea = bytea[:byteaLen-2] } else { bytea = bytea[:byteaLen-1] diff --git a/src/terminal.go b/src/terminal.go index 39c7823..6d1a1ab 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -7,6 +7,7 @@ import ( "os/signal" "regexp" "sort" + "strconv" "strings" "sync" "syscall" @@ -919,6 +920,9 @@ func keyMatch(key int, event tui.Event) bool { } func quoteEntry(entry string) string { + if util.IsWindows() { + return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1)) + } return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'" } @@ -982,6 +986,9 @@ func (t *Terminal) executeCommand(template string, items []*Item) { cmd.Stderr = os.Stderr tui.Pause() cmd.Run() + if tui.Resume() { + t.printAll() + } t.refresh() } diff --git a/src/tui/ncurses.go b/src/tui/ncurses.go index 3f52f3f..d55c1c8 100644 --- a/src/tui/ncurses.go +++ b/src/tui/ncurses.go @@ -138,6 +138,10 @@ func Pause() { C.endwin() } +func Resume() bool { + return false +} + func Close() { C.endwin() C.delscreen(_screen) diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 591b366..0bb06e9 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -13,6 +13,8 @@ import ( "github.com/gdamore/tcell" "github.com/gdamore/tcell/encoding" + + "github.com/junegunn/go-runewidth" ) type ColorPair [2]Color @@ -108,21 +110,32 @@ func (a Attr) Merge(b Attr) Attr { var ( _screen tcell.Screen + _mouse bool ) +func initScreen() { + s, e := tcell.NewScreen() + if e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(2) + } + if e = s.Init(); e != nil { + fmt.Fprintf(os.Stderr, "%v\n", e) + os.Exit(2) + } + if _mouse { + s.EnableMouse() + } else { + s.DisableMouse() + } + _screen = s +} + func Init(theme *ColorTheme, black bool, mouse bool) { encoding.Register() - s, e := tcell.NewScreen() - if e != nil { - fmt.Fprintf(os.Stderr, "%v\n", e) - os.Exit(1) - } - if e = s.Init(); e != nil { - fmt.Fprintf(os.Stderr, "%v\n", e) - os.Exit(1) - } - _screen = s + _mouse = mouse + initScreen() _color = theme != nil if _color { @@ -139,12 +152,6 @@ func Init(theme *ColorTheme, black bool, mouse bool) { ColSelected = ColorPair{theme.Selected, theme.DarkBg} ColHeader = ColorPair{theme.Header, theme.Bg} ColBorder = ColorPair{theme.Border, theme.Bg} - - if mouse { - _screen.EnableMouse() - } else { - _screen.DisableMouse() - } } func MaxX() int { @@ -162,6 +169,7 @@ func (w *Window) win() *WindowTcell { } func Clear() { + _screen.Sync() _screen.Clear() } @@ -211,6 +219,7 @@ func GetChar() Event { // process keyboard: case *tcell.EventKey: + alt := (ev.Modifiers() & tcell.ModAlt) > 0 switch ev.Key() { case tcell.KeyCtrlA: return Event{CtrlA, 0, nil} @@ -233,6 +242,9 @@ func GetChar() Event { case tcell.KeyCtrlL: return Event{CtrlL, 0, nil} case tcell.KeyCtrlM: + if alt { + return Event{AltEnter, 0, nil} + } return Event{CtrlM, 0, nil} case tcell.KeyCtrlN: return Event{CtrlN, 0, nil} @@ -261,6 +273,9 @@ func GetChar() Event { case tcell.KeyCtrlZ: return Event{CtrlZ, 0, nil} case tcell.KeyBackspace, tcell.KeyBackspace2: + if alt { + return Event{AltBS, 0, nil} + } return Event{BSpace, 0, nil} case tcell.KeyUp: @@ -278,13 +293,15 @@ func GetChar() Event { return Event{Del, 0, nil} case tcell.KeyEnd: return Event{End, 0, nil} - /*case tcell.KeyPgUp: + case tcell.KeyPgUp: return Event{PgUp, 0, nil} - case tcell.KeyPgdn: - return Event{PgDn, 0, nil}*/ + case tcell.KeyPgDn: + return Event{PgDn, 0, nil} case tcell.KeyTab: return Event{Tab, 0, nil} + case tcell.KeyBacktab: + return Event{BTab, 0, nil} case tcell.KeyF1: return Event{F1, 0, nil} @@ -313,7 +330,19 @@ func GetChar() Event { // ev.Ch doesn't work for some reason for space: case tcell.KeyRune: - return Event{Rune, ev.Rune(), nil} + r := ev.Rune() + if alt { + switch r { + case ' ': + return Event{AltSpace, 0, nil} + case '/': + return Event{AltSlash, 0, nil} + } + if r >= 'a' && r <= 'z' { + return Event{AltA + int(r) - 'a', 0, nil} + } + } + return Event{Rune, r, nil} case tcell.KeyEsc: return Event{ESC, 0, nil} @@ -325,7 +354,12 @@ func GetChar() Event { } func Pause() { - // TODO + _screen.Fini() +} + +func Resume() bool { + initScreen() + return true } func Close() { @@ -391,11 +425,10 @@ func (w *Window) Move(y int, x int) { func (w *Window) MoveAndClear(y int, x int) { w.Move(y, x) - r, _ := utf8.DecodeRuneInString(" ") for i := w.win().LastX; i < w.Width; i++ { - _screen.SetContent(i+w.Left, w.win().LastY+w.Top, r, nil, ColDefault.style()) + _screen.SetContent(i+w.Left, w.win().LastY+w.Top, rune(' '), nil, ColDefault.style()) } - w.win().LastX = 0 + w.win().LastX = x } func (w *Window) Print(text string) { @@ -439,7 +472,7 @@ func (w *Window) PrintString(text string, pair ColorPair, a Attr) { if xPos < (w.Left+w.Width) && yPos < (w.Top+w.Height) { _screen.SetContent(xPos, yPos, r, nil, style) } - lx++ + lx += runewidth.RuneWidth(r) } } w.win().LastX += lx @@ -482,7 +515,7 @@ func (w *Window) FillString(text string, pair ColorPair, a Attr) bool { } _screen.SetContent(xPos, yPos, r, nil, style) - lx++ + lx += runewidth.RuneWidth(r) } } w.win().LastX += lx diff --git a/src/util/util_unix.go b/src/util/util_unix.go index dcc5cb5..29e0d30 100644 --- a/src/util/util_unix.go +++ b/src/util/util_unix.go @@ -15,3 +15,8 @@ func ExecCommand(command string) *exec.Cmd { } return exec.Command(shell, "-c", command) } + +// IsWindows returns true on Windows +func IsWindows() bool { + return false +} diff --git a/src/util/util_windows.go b/src/util/util_windows.go index a660f39..3aa8660 100644 --- a/src/util/util_windows.go +++ b/src/util/util_windows.go @@ -5,6 +5,8 @@ package util import ( "os" "os/exec" + + "github.com/junegunn/go-shellwords" ) // ExecCommand executes the given command with $SHELL @@ -13,5 +15,14 @@ func ExecCommand(command string) *exec.Cmd { if len(shell) == 0 { shell = "cmd" } - return exec.Command(shell, "/c", command) + args, _ := shellwords.Parse(command) + allArgs := make([]string, len(args)+1) + allArgs[0] = "/c" + copy(allArgs[1:], args) + return exec.Command(shell, allArgs...) +} + +// IsWindows returns true on Windows +func IsWindows() bool { + return true } diff --git a/test/test_go.rb b/test/test_go.rb index f34d8b3..3fdfddd 100644 --- a/test/test_go.rb +++ b/test/test_go.rb @@ -1058,7 +1058,7 @@ class TestGoFZF < TestBase def test_invalid_term lines = `TERM=xxx #{FZF}` assert_equal 2, $?.exitstatus - assert lines.include?('Invalid $TERM: xxx') + assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found') end def test_invalid_option