implement asynchronous read commands
This commit changes previous reading command implementation to an asynchronous implementation. By the nature of this change, this commit touches many places in the ui and evaluator. Aim is to fix the following problems: - There is no race condition anymore when reading commands and other commands update the ui at the same time. - Autocompletion and keymenu is now drawn in the main draw event. This should fix some ui glitches when a new menu is smaller than the previous one. - Window resize event when reading a command is now properly handled. - Readline actions are now regular commands. This should make it possible to change the default keybindings for these actions in the future. Mentioned in #36.
This commit is contained in:
parent
f66a4a4a2e
commit
5f87cb2542
140
app.go
140
app.go
@ -8,9 +8,6 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
@ -55,144 +52,11 @@ func waitKey() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function is used to read expressions on the client side. Prompting
|
||||
// commands (e.g. "read") are recognized and evaluated while being read here.
|
||||
// Digits are interpreted as command counts but this is only done for digits
|
||||
// preceding any non-digit characters (e.g. "42y2k" as 42 times "y2k").
|
||||
func (app *App) readExpr() chan MultiExpr {
|
||||
ch := make(chan MultiExpr)
|
||||
|
||||
renew := &CallExpr{"renew", nil}
|
||||
count := 1
|
||||
|
||||
var acc []rune
|
||||
var cnt []rune
|
||||
|
||||
go func() {
|
||||
for {
|
||||
switch ev := app.ui.pollEvent(); ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Ch != 0 {
|
||||
switch {
|
||||
case ev.Ch == '<':
|
||||
acc = append(acc, '<', 'l', 't', '>')
|
||||
case ev.Ch == '>':
|
||||
acc = append(acc, '<', 'g', 't', '>')
|
||||
case unicode.IsDigit(ev.Ch) && len(acc) == 0:
|
||||
cnt = append(cnt, ev.Ch)
|
||||
default:
|
||||
acc = append(acc, ev.Ch)
|
||||
}
|
||||
} else {
|
||||
val := gKeyVal[ev.Key]
|
||||
if string(val) == "<esc>" {
|
||||
ch <- MultiExpr{renew, 1}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
acc = append(acc, val...)
|
||||
}
|
||||
|
||||
binds, ok := findBinds(gOpts.keys, string(acc))
|
||||
|
||||
switch len(binds) {
|
||||
case 0:
|
||||
app.ui.message = fmt.Sprintf("unknown mapping: %s", string(acc))
|
||||
ch <- MultiExpr{renew, 1}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
case 1:
|
||||
if ok {
|
||||
if len(cnt) > 0 {
|
||||
c, err := strconv.Atoi(string(cnt))
|
||||
if err != nil {
|
||||
log.Printf("converting command count: %s", err)
|
||||
}
|
||||
count = c
|
||||
} else {
|
||||
count = 1
|
||||
}
|
||||
expr := gOpts.keys[string(acc)]
|
||||
switch expr.(type) {
|
||||
case *CallExpr:
|
||||
switch expr.(*CallExpr).name {
|
||||
case "read",
|
||||
"read-shell",
|
||||
"read-shell-wait",
|
||||
"read-shell-async",
|
||||
"search",
|
||||
"search-back",
|
||||
"push":
|
||||
expr.eval(app, nil)
|
||||
app.ui.loadFile(app.nav)
|
||||
app.ui.draw(app.nav)
|
||||
default:
|
||||
ch <- MultiExpr{expr, count}
|
||||
}
|
||||
default:
|
||||
ch <- MultiExpr{expr, count}
|
||||
}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
if len(acc) > 0 {
|
||||
app.ui.listBinds(binds)
|
||||
}
|
||||
default:
|
||||
if ok {
|
||||
// TODO: use a delay
|
||||
if len(cnt) > 0 {
|
||||
c, err := strconv.Atoi(string(cnt))
|
||||
if err != nil {
|
||||
log.Printf("converting command count: %s", err)
|
||||
}
|
||||
count = c
|
||||
} else {
|
||||
count = 1
|
||||
}
|
||||
expr := gOpts.keys[string(acc)]
|
||||
switch expr.(type) {
|
||||
case *CallExpr:
|
||||
switch expr.(*CallExpr).name {
|
||||
case "read",
|
||||
"read-shell",
|
||||
"read-shell-wait",
|
||||
"read-shell-async",
|
||||
"search",
|
||||
"search-back",
|
||||
"push":
|
||||
expr.eval(app, nil)
|
||||
app.ui.loadFile(app.nav)
|
||||
app.ui.draw(app.nav)
|
||||
default:
|
||||
ch <- MultiExpr{expr, count}
|
||||
}
|
||||
default:
|
||||
ch <- MultiExpr{expr, count}
|
||||
}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
if len(acc) > 0 {
|
||||
app.ui.listBinds(binds)
|
||||
}
|
||||
}
|
||||
case termbox.EventResize:
|
||||
ch <- MultiExpr{renew, 1}
|
||||
default:
|
||||
// TODO: handle other events
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// This is the main event loop of the application. There are two channels to
|
||||
// read expressions from client and server. Reading and evaluation are done on
|
||||
// different goroutines except for prompting commands (e.g. "read").
|
||||
// separate goroutines.
|
||||
func (app *App) handleInp() {
|
||||
clientChan := app.readExpr()
|
||||
clientChan := app.ui.readExpr()
|
||||
|
||||
var serverChan chan Expr
|
||||
|
||||
|
166
eval.go
166
eval.go
@ -242,56 +242,17 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
app.ui.loadFile(app.nav)
|
||||
app.ui.loadFileInfo(app.nav)
|
||||
case "read":
|
||||
s := app.ui.prompt(app.nav, ":")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("command: %s", s)
|
||||
p := newParser(strings.NewReader(s))
|
||||
for p.parse() {
|
||||
p.expr.eval(app, nil)
|
||||
}
|
||||
if p.err != nil {
|
||||
app.ui.message = p.err.Error()
|
||||
log.Print(p.err)
|
||||
}
|
||||
app.ui.cmdpref = ":"
|
||||
case "read-shell":
|
||||
s := app.ui.prompt(app.nav, "$")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("shell: %s", s)
|
||||
app.runShell(s, nil, false, false)
|
||||
app.ui.cmdpref = "$"
|
||||
case "read-shell-wait":
|
||||
s := app.ui.prompt(app.nav, "!")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("shell-wait: %s", s)
|
||||
app.runShell(s, nil, true, false)
|
||||
app.ui.cmdpref = "!"
|
||||
case "read-shell-async":
|
||||
s := app.ui.prompt(app.nav, "&")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("shell-async: %s", s)
|
||||
app.runShell(s, nil, false, true)
|
||||
app.ui.cmdpref = "&"
|
||||
case "search":
|
||||
s := app.ui.prompt(app.nav, "/")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("search: %s", s)
|
||||
app.ui.message = "sorry, search is not implemented yet!"
|
||||
// TODO: implement
|
||||
app.ui.cmdpref = "/"
|
||||
case "search-back":
|
||||
s := app.ui.prompt(app.nav, "?")
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
log.Printf("search-back: %s", s)
|
||||
app.ui.message = "sorry, search-back is not implemented yet!"
|
||||
// TODO: implement
|
||||
app.ui.cmdpref = "?"
|
||||
case "toggle":
|
||||
app.nav.toggle()
|
||||
case "invert":
|
||||
@ -359,7 +320,118 @@ func (e *CallExpr) eval(app *App, args []string) {
|
||||
app.ui.loadFileInfo(app.nav)
|
||||
case "push":
|
||||
if len(e.args) > 0 {
|
||||
app.ui.keysbuf = append(app.ui.keysbuf, splitKeys(e.args[0])...)
|
||||
log.Println("pushing keys", e.args[0])
|
||||
for _, key := range splitKeys(e.args[0]) {
|
||||
app.ui.keychan <- key
|
||||
}
|
||||
}
|
||||
case "cmd-insert":
|
||||
if len(e.args) > 0 {
|
||||
app.ui.cmdlacc = append(app.ui.cmdlacc, []rune(e.args[0])...)
|
||||
}
|
||||
case "cmd-escape":
|
||||
app.ui.menubuf = nil
|
||||
app.ui.cmdbuf = nil
|
||||
app.ui.cmdlacc = nil
|
||||
app.ui.cmdracc = nil
|
||||
app.ui.cmdpref = ""
|
||||
case "cmd-comp":
|
||||
var matches []string
|
||||
if app.ui.cmdpref == ":" {
|
||||
matches, app.ui.cmdlacc = compCmd(app.ui.cmdlacc)
|
||||
} else {
|
||||
matches, app.ui.cmdlacc = compShell(app.ui.cmdlacc)
|
||||
}
|
||||
app.ui.draw(app.nav)
|
||||
if len(matches) > 1 {
|
||||
app.ui.menubuf = listMatches(matches)
|
||||
} else {
|
||||
app.ui.menubuf = nil
|
||||
}
|
||||
case "cmd-enter":
|
||||
s := string(append(app.ui.cmdlacc, app.ui.cmdracc...))
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
app.ui.menubuf = nil
|
||||
app.ui.cmdbuf = nil
|
||||
app.ui.cmdlacc = nil
|
||||
app.ui.cmdracc = nil
|
||||
switch app.ui.cmdpref {
|
||||
case ":":
|
||||
log.Printf("command: %s", s)
|
||||
p := newParser(strings.NewReader(s))
|
||||
for p.parse() {
|
||||
p.expr.eval(app, nil)
|
||||
}
|
||||
if p.err != nil {
|
||||
app.ui.message = p.err.Error()
|
||||
log.Print(p.err)
|
||||
}
|
||||
case "$":
|
||||
log.Printf("shell: %s", s)
|
||||
app.runShell(s, nil, false, false)
|
||||
case "!":
|
||||
log.Printf("shell-wait: %s", s)
|
||||
app.runShell(s, nil, true, false)
|
||||
case "&":
|
||||
log.Printf("shell-async: %s", s)
|
||||
app.runShell(s, nil, false, true)
|
||||
case "/":
|
||||
log.Printf("search: %s", s)
|
||||
app.ui.message = "sorry, search is not implemented yet!"
|
||||
// TODO: implement
|
||||
case "?":
|
||||
log.Printf("search-back: %s", s)
|
||||
app.ui.message = "sorry, search-back is not implemented yet!"
|
||||
// TODO: implement
|
||||
default:
|
||||
log.Printf("entering unknown execution prefix: %q", app.ui.cmdpref)
|
||||
}
|
||||
app.ui.cmdpref = ""
|
||||
case "cmd-delete-back":
|
||||
if len(app.ui.cmdlacc) > 0 {
|
||||
app.ui.cmdlacc = app.ui.cmdlacc[:len(app.ui.cmdlacc)-1]
|
||||
}
|
||||
case "cmd-delete":
|
||||
if len(app.ui.cmdracc) > 0 {
|
||||
app.ui.cmdracc = app.ui.cmdracc[1:]
|
||||
}
|
||||
case "cmd-left":
|
||||
if len(app.ui.cmdlacc) > 0 {
|
||||
app.ui.cmdracc = append([]rune{app.ui.cmdlacc[len(app.ui.cmdlacc)-1]}, app.ui.cmdracc...)
|
||||
app.ui.cmdlacc = app.ui.cmdlacc[:len(app.ui.cmdlacc)-1]
|
||||
}
|
||||
case "cmd-right":
|
||||
if len(app.ui.cmdracc) > 0 {
|
||||
app.ui.cmdlacc = append(app.ui.cmdlacc, app.ui.cmdracc[0])
|
||||
app.ui.cmdracc = app.ui.cmdracc[1:]
|
||||
}
|
||||
case "cmd-beg":
|
||||
app.ui.cmdracc = append(app.ui.cmdlacc, app.ui.cmdracc...)
|
||||
app.ui.cmdlacc = nil
|
||||
case "cmd-end":
|
||||
app.ui.cmdlacc = append(app.ui.cmdlacc, app.ui.cmdracc...)
|
||||
app.ui.cmdracc = nil
|
||||
case "cmd-delete-end":
|
||||
if len(app.ui.cmdracc) > 0 {
|
||||
app.ui.cmdbuf = app.ui.cmdracc
|
||||
app.ui.cmdracc = nil
|
||||
}
|
||||
case "cmd-delete-beg":
|
||||
if len(app.ui.cmdlacc) > 0 {
|
||||
app.ui.cmdbuf = app.ui.cmdlacc
|
||||
app.ui.cmdlacc = nil
|
||||
}
|
||||
case "cmd-delete-word":
|
||||
ind := strings.LastIndex(strings.TrimRight(string(app.ui.cmdlacc), " "), " ") + 1
|
||||
app.ui.cmdbuf = app.ui.cmdlacc[ind:]
|
||||
app.ui.cmdlacc = app.ui.cmdlacc[:ind]
|
||||
case "cmd-put":
|
||||
app.ui.cmdlacc = append(app.ui.cmdlacc, app.ui.cmdbuf...)
|
||||
case "cmd-transpose":
|
||||
if len(app.ui.cmdlacc) > 1 {
|
||||
app.ui.cmdlacc[len(app.ui.cmdlacc)-1], app.ui.cmdlacc[len(app.ui.cmdlacc)-2] = app.ui.cmdlacc[len(app.ui.cmdlacc)-2], app.ui.cmdlacc[len(app.ui.cmdlacc)-1]
|
||||
}
|
||||
default:
|
||||
cmd, ok := gOpts.cmds[e.name]
|
||||
@ -391,7 +463,7 @@ func (e *ExecExpr) eval(app *App, args []string) {
|
||||
log.Printf("search-back: %s -- %s", e, args)
|
||||
// TODO: implement
|
||||
default:
|
||||
log.Printf("unknown execution prefix: %q", e.pref)
|
||||
log.Printf("evaluating unknown execution prefix: %q", e.pref)
|
||||
}
|
||||
}
|
||||
|
||||
|
334
ui.go
334
ui.go
@ -13,6 +13,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
@ -340,7 +341,13 @@ type UI struct {
|
||||
message string
|
||||
regprev []string
|
||||
dirprev *Dir
|
||||
keysbuf []string
|
||||
keychan chan string
|
||||
evschan chan termbox.Event
|
||||
cmdpref string
|
||||
cmdlacc []rune
|
||||
cmdracc []rune
|
||||
cmdbuf []rune
|
||||
menubuf *bytes.Buffer
|
||||
}
|
||||
|
||||
func getWidths(wtot int) []int {
|
||||
@ -376,11 +383,22 @@ func newUI() *UI {
|
||||
wacc += widths[i]
|
||||
}
|
||||
|
||||
key := make(chan string, 1000)
|
||||
evs := make(chan termbox.Event)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
evs <- termbox.PollEvent()
|
||||
}
|
||||
}()
|
||||
|
||||
return &UI{
|
||||
wins: wins,
|
||||
pwdwin: newWin(wtot, 1, 0, 0),
|
||||
msgwin: newWin(wtot, 1, 0, htot-1),
|
||||
menuwin: newWin(wtot, 1, 0, htot-2),
|
||||
keychan: key,
|
||||
evschan: evs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,26 +496,11 @@ func (ui *UI) loadFile(nav *Nav) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) clearMsg() {
|
||||
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
||||
win := ui.msgwin
|
||||
win.printl(0, 0, fg, bg, "")
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func (ui *UI) draw(nav *Nav) {
|
||||
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
||||
|
||||
termbox.Clear(fg, bg)
|
||||
|
||||
// leave the cursor at the beginning of the current file for screen readers
|
||||
var length, woff, doff int
|
||||
defer func() {
|
||||
fmt.Printf("[%d;%dH", ui.wins[woff+length-1].y+nav.dirs[doff+length-1].pos+1, ui.wins[woff+length-1].x+1)
|
||||
}()
|
||||
|
||||
defer termbox.Flush()
|
||||
|
||||
dir := nav.currDir()
|
||||
|
||||
path := strings.Replace(dir.path, envHome, "~", -1)
|
||||
@ -507,20 +510,28 @@ func (ui *UI) draw(nav *Nav) {
|
||||
ui.pwdwin.printf(len(envUser)+len(envHost)+1, 0, fg, bg, ":")
|
||||
ui.pwdwin.printf(len(envUser)+len(envHost)+2, 0, termbox.AttrBold|termbox.ColorBlue, bg, "%s", path)
|
||||
|
||||
length = min(len(ui.wins), len(nav.dirs))
|
||||
woff = len(ui.wins) - length
|
||||
length := min(len(ui.wins), len(nav.dirs))
|
||||
woff := len(ui.wins) - length
|
||||
|
||||
if gOpts.preview {
|
||||
length = min(len(ui.wins)-1, len(nav.dirs))
|
||||
woff = len(ui.wins) - 1 - length
|
||||
}
|
||||
|
||||
doff = len(nav.dirs) - length
|
||||
doff := len(nav.dirs) - length
|
||||
for i := 0; i < length; i++ {
|
||||
ui.wins[woff+i].printd(nav.dirs[doff+i], nav.marks, nav.saves)
|
||||
}
|
||||
|
||||
defer ui.msgwin.print(0, 0, fg, bg, ui.message)
|
||||
if ui.cmdpref != "" {
|
||||
ui.msgwin.printl(0, 0, fg, bg, ui.cmdpref)
|
||||
ui.msgwin.print(len(ui.cmdpref), 0, fg, bg, string(ui.cmdlacc))
|
||||
ui.msgwin.print(len(ui.cmdpref)+runeSliceWidth(ui.cmdlacc), 0, fg, bg, string(ui.cmdracc))
|
||||
termbox.SetCursor(ui.msgwin.x+len(ui.cmdpref)+runeSliceWidth(ui.cmdlacc), ui.msgwin.y)
|
||||
} else {
|
||||
ui.msgwin.print(0, 0, fg, bg, ui.message)
|
||||
termbox.HideCursor()
|
||||
}
|
||||
|
||||
if gOpts.preview {
|
||||
f, err := nav.currFile()
|
||||
@ -536,6 +547,28 @@ func (ui *UI) draw(nav *Nav) {
|
||||
preview.printr(ui.regprev)
|
||||
}
|
||||
}
|
||||
|
||||
if ui.menubuf != nil {
|
||||
lines := strings.Split(ui.menubuf.String(), "\n")
|
||||
|
||||
lines = lines[:len(lines)-1]
|
||||
|
||||
ui.menuwin.h = len(lines) - 1
|
||||
ui.menuwin.y = ui.wins[0].h - ui.menuwin.h
|
||||
|
||||
ui.menuwin.printl(0, 0, termbox.AttrBold, termbox.AttrBold, lines[0])
|
||||
for i, line := range lines[1:] {
|
||||
ui.menuwin.printl(0, i+1, termbox.ColorDefault, termbox.ColorDefault, "")
|
||||
ui.menuwin.print(0, i+1, termbox.ColorDefault, termbox.ColorDefault, line)
|
||||
}
|
||||
}
|
||||
|
||||
termbox.Flush()
|
||||
|
||||
if ui.cmdpref == "" {
|
||||
// leave the cursor at the beginning of the current file for screen readers
|
||||
fmt.Printf("[%d;%dH", ui.wins[woff+length-1].y+nav.dirs[doff+length-1].pos+1, ui.wins[woff+length-1].x+1)
|
||||
}
|
||||
}
|
||||
|
||||
func findBinds(keys map[string]Expr, prefix string) (binds map[string]Expr, ok bool) {
|
||||
@ -551,33 +584,55 @@ func findBinds(keys map[string]Expr, prefix string) (binds map[string]Expr, ok b
|
||||
return
|
||||
}
|
||||
|
||||
func listBinds(binds map[string]Expr) *bytes.Buffer {
|
||||
t := new(tabwriter.Writer)
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
var keys []string
|
||||
for k := range binds {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
|
||||
fmt.Fprintln(t, "keys\tcommand")
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(t, "%s\t%v\n", k, binds[k])
|
||||
}
|
||||
t.Flush()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (ui *UI) pollEvent() termbox.Event {
|
||||
if len(ui.keysbuf) > 0 {
|
||||
select {
|
||||
case key := <-ui.keychan:
|
||||
ev := termbox.Event{Type: termbox.EventKey}
|
||||
keys := ui.keysbuf[0]
|
||||
if len(keys) == 1 {
|
||||
ev.Ch, _ = utf8.DecodeRuneInString(keys)
|
||||
|
||||
if len(key) == 1 {
|
||||
ev.Ch, _ = utf8.DecodeRuneInString(key)
|
||||
} else {
|
||||
switch keys {
|
||||
switch key {
|
||||
case "<lt>":
|
||||
ev.Ch = '<'
|
||||
case "<gt>":
|
||||
ev.Ch = '>'
|
||||
default:
|
||||
if val, ok := gValKey[keys]; ok {
|
||||
if val, ok := gValKey[key]; ok {
|
||||
ev.Key = val
|
||||
} else {
|
||||
ev.Key = termbox.KeyEsc
|
||||
msg := fmt.Sprintf("unknown key: %s", keys)
|
||||
msg := fmt.Sprintf("unknown key: %s", key)
|
||||
ui.message = msg
|
||||
log.Print(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.keysbuf = ui.keysbuf[1:]
|
||||
|
||||
return ev
|
||||
case ev := <-ui.evschan:
|
||||
return ev
|
||||
}
|
||||
return termbox.PollEvent()
|
||||
}
|
||||
|
||||
type MultiExpr struct {
|
||||
@ -585,104 +640,152 @@ type MultiExpr struct {
|
||||
count int
|
||||
}
|
||||
|
||||
func (ui *UI) prompt(nav *Nav, pref string) string {
|
||||
fg, bg := termbox.ColorDefault, termbox.ColorDefault
|
||||
// This function is used to read expressions on the client side. Digits are
|
||||
// interpreted as command counts but this is only done for digits preceding any
|
||||
// non-digit characters (e.g. "42y2k" as 42 times "y2k").
|
||||
func (ui *UI) readExpr() chan MultiExpr {
|
||||
ch := make(chan MultiExpr)
|
||||
|
||||
win := ui.msgwin
|
||||
renew := &CallExpr{"renew", nil}
|
||||
count := 1
|
||||
|
||||
win.printl(0, 0, fg, bg, pref)
|
||||
termbox.SetCursor(win.x+len(pref), win.y)
|
||||
defer termbox.HideCursor()
|
||||
termbox.Flush()
|
||||
|
||||
var lacc []rune
|
||||
var racc []rune
|
||||
|
||||
var buf []rune
|
||||
var acc []rune
|
||||
var cnt []rune
|
||||
|
||||
go func() {
|
||||
for {
|
||||
switch ev := ui.pollEvent(); ev.Type {
|
||||
ev := ui.pollEvent()
|
||||
|
||||
if ui.cmdpref != "" {
|
||||
switch ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Ch != 0 {
|
||||
lacc = append(lacc, ev.Ch)
|
||||
ch <- MultiExpr{&CallExpr{"cmd-insert", []string{string(ev.Ch)}}, 1}
|
||||
} else {
|
||||
// TODO: rest of the keys
|
||||
switch ev.Key {
|
||||
case termbox.KeyEsc:
|
||||
return ""
|
||||
ch <- MultiExpr{&CallExpr{"cmd-escape", nil}, 1}
|
||||
case termbox.KeySpace:
|
||||
lacc = append(lacc, ' ')
|
||||
ch <- MultiExpr{&CallExpr{"cmd-insert", []string{" "}}, 1}
|
||||
case termbox.KeyTab:
|
||||
var matches []string
|
||||
if pref == ":" {
|
||||
matches, lacc = compCmd(lacc)
|
||||
} else {
|
||||
matches, lacc = compShell(lacc)
|
||||
}
|
||||
ui.draw(nav)
|
||||
if len(matches) > 1 {
|
||||
ui.listMatches(matches)
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-comp", nil}, 1}
|
||||
case termbox.KeyEnter, termbox.KeyCtrlJ:
|
||||
win.printl(0, 0, fg, bg, "")
|
||||
termbox.Flush()
|
||||
return string(append(lacc, racc...))
|
||||
ch <- MultiExpr{&CallExpr{"cmd-enter", nil}, 1}
|
||||
case termbox.KeyBackspace, termbox.KeyBackspace2:
|
||||
if len(lacc) > 0 {
|
||||
lacc = lacc[:len(lacc)-1]
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-delete-back", nil}, 1}
|
||||
case termbox.KeyDelete, termbox.KeyCtrlD:
|
||||
if len(racc) > 0 {
|
||||
racc = racc[1:]
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-delete", nil}, 1}
|
||||
case termbox.KeyArrowLeft, termbox.KeyCtrlB:
|
||||
if len(lacc) > 0 {
|
||||
racc = append([]rune{lacc[len(lacc)-1]}, racc...)
|
||||
lacc = lacc[:len(lacc)-1]
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-left", nil}, 1}
|
||||
case termbox.KeyArrowRight, termbox.KeyCtrlF:
|
||||
if len(racc) > 0 {
|
||||
lacc = append(lacc, racc[0])
|
||||
racc = racc[1:]
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-right", nil}, 1}
|
||||
case termbox.KeyHome, termbox.KeyCtrlA:
|
||||
racc = append(lacc, racc...)
|
||||
lacc = nil
|
||||
ch <- MultiExpr{&CallExpr{"cmd-beg", nil}, 1}
|
||||
case termbox.KeyEnd, termbox.KeyCtrlE:
|
||||
lacc = append(lacc, racc...)
|
||||
racc = nil
|
||||
ch <- MultiExpr{&CallExpr{"cmd-end", nil}, 1}
|
||||
case termbox.KeyCtrlK:
|
||||
if len(racc) > 0 {
|
||||
buf = racc
|
||||
racc = nil
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-delete-end", nil}, 1}
|
||||
case termbox.KeyCtrlU:
|
||||
if len(lacc) > 0 {
|
||||
buf = lacc
|
||||
lacc = nil
|
||||
}
|
||||
ch <- MultiExpr{&CallExpr{"cmd-delete-beg", nil}, 1}
|
||||
case termbox.KeyCtrlW:
|
||||
ind := strings.LastIndex(strings.TrimRight(string(lacc), " "), " ") + 1
|
||||
buf = lacc[ind:]
|
||||
lacc = lacc[:ind]
|
||||
ch <- MultiExpr{&CallExpr{"cmd-delete-word", nil}, 1}
|
||||
case termbox.KeyCtrlY:
|
||||
lacc = append(lacc, buf...)
|
||||
ch <- MultiExpr{&CallExpr{"cmd-put", nil}, 1}
|
||||
case termbox.KeyCtrlT:
|
||||
if len(lacc) > 1 {
|
||||
lacc[len(lacc)-1], lacc[len(lacc)-2] = lacc[len(lacc)-2], lacc[len(lacc)-1]
|
||||
ch <- MultiExpr{&CallExpr{"cmd-transpose", nil}, 1}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
win.printl(0, 0, fg, bg, pref)
|
||||
win.print(len(pref), 0, fg, bg, string(lacc))
|
||||
win.print(len(pref)+runeSliceWidth(lacc), 0, fg, bg, string(racc))
|
||||
termbox.SetCursor(win.x+len(pref)+runeSliceWidth(lacc), win.y)
|
||||
termbox.Flush()
|
||||
switch ev.Type {
|
||||
case termbox.EventKey:
|
||||
if ev.Ch != 0 {
|
||||
switch {
|
||||
case ev.Ch == '<':
|
||||
acc = append(acc, '<', 'l', 't', '>')
|
||||
case ev.Ch == '>':
|
||||
acc = append(acc, '<', 'g', 't', '>')
|
||||
case unicode.IsDigit(ev.Ch) && len(acc) == 0:
|
||||
cnt = append(cnt, ev.Ch)
|
||||
default:
|
||||
acc = append(acc, ev.Ch)
|
||||
}
|
||||
} else {
|
||||
val := gKeyVal[ev.Key]
|
||||
if string(val) == "<esc>" {
|
||||
ch <- MultiExpr{renew, 1}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
acc = append(acc, val...)
|
||||
}
|
||||
|
||||
binds, ok := findBinds(gOpts.keys, string(acc))
|
||||
|
||||
switch len(binds) {
|
||||
case 0:
|
||||
ui.message = fmt.Sprintf("unknown mapping: %s", string(acc))
|
||||
ch <- MultiExpr{renew, 1}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
case 1:
|
||||
if ok {
|
||||
if len(cnt) > 0 {
|
||||
c, err := strconv.Atoi(string(cnt))
|
||||
if err != nil {
|
||||
log.Printf("converting command count: %s", err)
|
||||
}
|
||||
count = c
|
||||
} else {
|
||||
count = 1
|
||||
}
|
||||
expr := gOpts.keys[string(acc)]
|
||||
ch <- MultiExpr{expr, count}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
if len(acc) > 0 {
|
||||
ui.menubuf = listBinds(binds)
|
||||
ch <- MultiExpr{renew, 1}
|
||||
} else if ui.menubuf != nil {
|
||||
ui.menubuf = nil
|
||||
}
|
||||
default:
|
||||
if ok {
|
||||
// TODO: use a delay
|
||||
if len(cnt) > 0 {
|
||||
c, err := strconv.Atoi(string(cnt))
|
||||
if err != nil {
|
||||
log.Printf("converting command count: %s", err)
|
||||
}
|
||||
count = c
|
||||
} else {
|
||||
count = 1
|
||||
}
|
||||
expr := gOpts.keys[string(acc)]
|
||||
ch <- MultiExpr{expr, count}
|
||||
acc = nil
|
||||
cnt = nil
|
||||
}
|
||||
if len(acc) > 0 {
|
||||
ui.menubuf = listBinds(binds)
|
||||
ch <- MultiExpr{renew, 1}
|
||||
} else {
|
||||
ui.menubuf = nil
|
||||
}
|
||||
}
|
||||
case termbox.EventResize:
|
||||
ch <- MultiExpr{renew, 1}
|
||||
default:
|
||||
// TODO: handle other events
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (ui *UI) pause() {
|
||||
@ -701,44 +804,7 @@ func (ui *UI) sync() {
|
||||
}
|
||||
}
|
||||
|
||||
func (ui *UI) showMenu(b *bytes.Buffer) {
|
||||
lines := strings.Split(b.String(), "\n")
|
||||
|
||||
lines = lines[:len(lines)-1]
|
||||
|
||||
ui.menuwin.h = len(lines) - 1
|
||||
ui.menuwin.y = ui.wins[0].h - ui.menuwin.h
|
||||
|
||||
ui.menuwin.printl(0, 0, termbox.AttrBold, termbox.AttrBold, lines[0])
|
||||
for i, line := range lines[1:] {
|
||||
ui.menuwin.printl(0, i+1, termbox.ColorDefault, termbox.ColorDefault, "")
|
||||
ui.menuwin.print(0, i+1, termbox.ColorDefault, termbox.ColorDefault, line)
|
||||
}
|
||||
|
||||
termbox.Flush()
|
||||
}
|
||||
|
||||
func (ui *UI) listBinds(binds map[string]Expr) {
|
||||
t := new(tabwriter.Writer)
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
var keys []string
|
||||
for k := range binds {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
t.Init(b, 0, gOpts.tabstop, 2, '\t', 0)
|
||||
fmt.Fprintln(t, "keys\tcommand")
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(t, "%s\t%v\n", k, binds[k])
|
||||
}
|
||||
t.Flush()
|
||||
|
||||
ui.showMenu(b)
|
||||
}
|
||||
|
||||
func (ui *UI) listMatches(matches []string) {
|
||||
func listMatches(matches []string) *bytes.Buffer {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
wtot, _ := termbox.Size()
|
||||
@ -759,5 +825,5 @@ func (ui *UI) listMatches(matches []string) {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
|
||||
ui.showMenu(b)
|
||||
return b
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user