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
This commit is contained in:
Junegunn Choi 2016-11-07 02:15:34 +09:00
parent 26895da969
commit 898d8d94c8
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
9 changed files with 96 additions and 33 deletions

View File

@ -1,6 +1,10 @@
language: ruby language: ruby
rvm: matrix:
- 2.2.0 include:
- env: TAGS=
rvm: 2.2.0
# - env: TAGS=tcell
# rvm: 2.2.0
install: install:
- sudo apt-get update - sudo apt-get update

View File

@ -52,7 +52,7 @@ android-build: $(SRCDIR)
rm -f $(RELEASEARM7) rm -f $(RELEASEARM7)
test: deps test: deps
SHELL=/bin/sh go test -v ./... SHELL=/bin/sh go test -v -tags "$(TAGS)" ./...
install: $(BINDIR)/fzf install: $(BINDIR)/fzf

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"io" "io"
"os" "os"
"runtime"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@ -44,7 +43,7 @@ func (r *Reader) feed(src io.Reader) {
if len(bytea) > 0 { if len(bytea) > 0 {
if err == nil { if err == nil {
// get rid of carriage return if under Windows: // 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] bytea = bytea[:byteaLen-2]
} else { } else {
bytea = bytea[:byteaLen-1] bytea = bytea[:byteaLen-1]

View File

@ -7,6 +7,7 @@ import (
"os/signal" "os/signal"
"regexp" "regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -919,6 +920,9 @@ func keyMatch(key int, event tui.Event) bool {
} }
func quoteEntry(entry string) string { func quoteEntry(entry string) string {
if util.IsWindows() {
return strconv.Quote(strings.Replace(entry, "\"", "\\\"", -1))
}
return "'" + 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 cmd.Stderr = os.Stderr
tui.Pause() tui.Pause()
cmd.Run() cmd.Run()
if tui.Resume() {
t.printAll()
}
t.refresh() t.refresh()
} }

View File

@ -138,6 +138,10 @@ func Pause() {
C.endwin() C.endwin()
} }
func Resume() bool {
return false
}
func Close() { func Close() {
C.endwin() C.endwin()
C.delscreen(_screen) C.delscreen(_screen)

View File

@ -13,6 +13,8 @@ import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding" "github.com/gdamore/tcell/encoding"
"github.com/junegunn/go-runewidth"
) )
type ColorPair [2]Color type ColorPair [2]Color
@ -108,21 +110,32 @@ func (a Attr) Merge(b Attr) Attr {
var ( var (
_screen tcell.Screen _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) { func Init(theme *ColorTheme, black bool, mouse bool) {
encoding.Register() encoding.Register()
s, e := tcell.NewScreen() _mouse = mouse
if e != nil { initScreen()
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
_color = theme != nil _color = theme != nil
if _color { if _color {
@ -139,12 +152,6 @@ func Init(theme *ColorTheme, black bool, mouse bool) {
ColSelected = ColorPair{theme.Selected, theme.DarkBg} ColSelected = ColorPair{theme.Selected, theme.DarkBg}
ColHeader = ColorPair{theme.Header, theme.Bg} ColHeader = ColorPair{theme.Header, theme.Bg}
ColBorder = ColorPair{theme.Border, theme.Bg} ColBorder = ColorPair{theme.Border, theme.Bg}
if mouse {
_screen.EnableMouse()
} else {
_screen.DisableMouse()
}
} }
func MaxX() int { func MaxX() int {
@ -162,6 +169,7 @@ func (w *Window) win() *WindowTcell {
} }
func Clear() { func Clear() {
_screen.Sync()
_screen.Clear() _screen.Clear()
} }
@ -211,6 +219,7 @@ func GetChar() Event {
// process keyboard: // process keyboard:
case *tcell.EventKey: case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0
switch ev.Key() { switch ev.Key() {
case tcell.KeyCtrlA: case tcell.KeyCtrlA:
return Event{CtrlA, 0, nil} return Event{CtrlA, 0, nil}
@ -233,6 +242,9 @@ func GetChar() Event {
case tcell.KeyCtrlL: case tcell.KeyCtrlL:
return Event{CtrlL, 0, nil} return Event{CtrlL, 0, nil}
case tcell.KeyCtrlM: case tcell.KeyCtrlM:
if alt {
return Event{AltEnter, 0, nil}
}
return Event{CtrlM, 0, nil} return Event{CtrlM, 0, nil}
case tcell.KeyCtrlN: case tcell.KeyCtrlN:
return Event{CtrlN, 0, nil} return Event{CtrlN, 0, nil}
@ -261,6 +273,9 @@ func GetChar() Event {
case tcell.KeyCtrlZ: case tcell.KeyCtrlZ:
return Event{CtrlZ, 0, nil} return Event{CtrlZ, 0, nil}
case tcell.KeyBackspace, tcell.KeyBackspace2: case tcell.KeyBackspace, tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
}
return Event{BSpace, 0, nil} return Event{BSpace, 0, nil}
case tcell.KeyUp: case tcell.KeyUp:
@ -278,13 +293,15 @@ func GetChar() Event {
return Event{Del, 0, nil} return Event{Del, 0, nil}
case tcell.KeyEnd: case tcell.KeyEnd:
return Event{End, 0, nil} return Event{End, 0, nil}
/*case tcell.KeyPgUp: case tcell.KeyPgUp:
return Event{PgUp, 0, nil} return Event{PgUp, 0, nil}
case tcell.KeyPgdn: case tcell.KeyPgDn:
return Event{PgDn, 0, nil}*/ return Event{PgDn, 0, nil}
case tcell.KeyTab: case tcell.KeyTab:
return Event{Tab, 0, nil} return Event{Tab, 0, nil}
case tcell.KeyBacktab:
return Event{BTab, 0, nil}
case tcell.KeyF1: case tcell.KeyF1:
return Event{F1, 0, nil} return Event{F1, 0, nil}
@ -313,7 +330,19 @@ func GetChar() Event {
// ev.Ch doesn't work for some reason for space: // ev.Ch doesn't work for some reason for space:
case tcell.KeyRune: 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: case tcell.KeyEsc:
return Event{ESC, 0, nil} return Event{ESC, 0, nil}
@ -325,7 +354,12 @@ func GetChar() Event {
} }
func Pause() { func Pause() {
// TODO _screen.Fini()
}
func Resume() bool {
initScreen()
return true
} }
func Close() { func Close() {
@ -391,11 +425,10 @@ func (w *Window) Move(y int, x int) {
func (w *Window) MoveAndClear(y int, x int) { func (w *Window) MoveAndClear(y int, x int) {
w.Move(y, x) w.Move(y, x)
r, _ := utf8.DecodeRuneInString(" ")
for i := w.win().LastX; i < w.Width; i++ { 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) { 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) { if xPos < (w.Left+w.Width) && yPos < (w.Top+w.Height) {
_screen.SetContent(xPos, yPos, r, nil, style) _screen.SetContent(xPos, yPos, r, nil, style)
} }
lx++ lx += runewidth.RuneWidth(r)
} }
} }
w.win().LastX += lx 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) _screen.SetContent(xPos, yPos, r, nil, style)
lx++ lx += runewidth.RuneWidth(r)
} }
} }
w.win().LastX += lx w.win().LastX += lx

View File

@ -15,3 +15,8 @@ func ExecCommand(command string) *exec.Cmd {
} }
return exec.Command(shell, "-c", command) return exec.Command(shell, "-c", command)
} }
// IsWindows returns true on Windows
func IsWindows() bool {
return false
}

View File

@ -5,6 +5,8 @@ package util
import ( import (
"os" "os"
"os/exec" "os/exec"
"github.com/junegunn/go-shellwords"
) )
// ExecCommand executes the given command with $SHELL // ExecCommand executes the given command with $SHELL
@ -13,5 +15,14 @@ func ExecCommand(command string) *exec.Cmd {
if len(shell) == 0 { if len(shell) == 0 {
shell = "cmd" 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
} }

View File

@ -1058,7 +1058,7 @@ class TestGoFZF < TestBase
def test_invalid_term def test_invalid_term
lines = `TERM=xxx #{FZF}` lines = `TERM=xxx #{FZF}`
assert_equal 2, $?.exitstatus assert_equal 2, $?.exitstatus
assert lines.include?('Invalid $TERM: xxx') assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found')
end end
def test_invalid_option def test_invalid_option