Refactor keymap

Created Action/ActionFunc to wrap common operations
refs #121
This commit is contained in:
Daisuke Maki 2014-07-02 21:31:49 +09:00
parent c7963db0d4
commit ca5cbb6fb1
3 changed files with 392 additions and 354 deletions

382
action.go Normal file
View file

@ -0,0 +1,382 @@
package peco
import (
"unicode"
"github.com/nsf/termbox-go"
)
var handlers map[string]Action
var defaultKeyBinding map[termbox.Key]Action
type Action interface {
Execute(*Input, termbox.Event)
}
type ActionFunc func(*Input, termbox.Event)
func (a ActionFunc) Execute(i *Input, e termbox.Event) {
a(i, e)
}
func (a ActionFunc) register(name string, defaultKeys ...termbox.Key) {
handlers["peco."+name] = a
for _, k := range defaultKeys {
defaultKeyBinding[k] = a
}
}
func init() {
handlers = map[string]Action{}
defaultKeyBinding = map[termbox.Key]Action{}
ActionFunc(doBeginningOfLine).register("BeginningOfLine", termbox.KeyCtrlA)
ActionFunc(doBackwardChar).register("BackwardChar", termbox.KeyCtrlB)
ActionFunc(doBackwardWord).register("BackwardWord")
ActionFunc(doCancel).register("Cancel", termbox.KeyCtrlC, termbox.KeyEsc)
ActionFunc(doDeleteAll).register("DeleteAll")
ActionFunc(doDeleteBackwardChar).register(
"DeleteBackwardChar",
termbox.KeyBackspace,
termbox.KeyBackspace2,
)
ActionFunc(doDeleteBackwardWord).register(
"DeleteBackwardWord",
termbox.KeyCtrlW,
)
ActionFunc(doDeleteForwardChar).register("DeletedForwardChar", termbox.KeyCtrlD)
ActionFunc(doDeleteForwardWord).register("DeleteForwardWord")
ActionFunc(doEndOfFile).register("EndOfFile")
ActionFunc(doEndOfLine).register("EndOfLine", termbox.KeyCtrlE)
ActionFunc(doFinish).register("Finish", termbox.KeyEnter)
ActionFunc(doForwardChar).register("ForwardChar", termbox.KeyCtrlF)
ActionFunc(doForwardWord).register("ForwardWord")
ActionFunc(doKillEndOfLine).register("KillEndOfLine", termbox.KeyCtrlK)
ActionFunc(doKillBeginningOfLine).register("KillBeginningOfLine", termbox.KeyCtrlU)
ActionFunc(doRotateMatcher).register("RotateMatcher", termbox.KeyCtrlR)
ActionFunc(doSelectNext).register(
"SelectNext",
termbox.KeyArrowDown,
termbox.KeyCtrlN,
)
ActionFunc(doSelectNextPage).register(
"SelectNextPage",
termbox.KeyArrowRight,
)
ActionFunc(doSelectPrevious).register(
"SelectPrevious",
termbox.KeyArrowUp,
termbox.KeyCtrlP,
)
ActionFunc(doSelectPreviousPage).register(
"SelectPreviousPage",
termbox.KeyArrowLeft,
)
ActionFunc(doToggleSelection).register("ToggleSelection")
ActionFunc(doToggleSelectionAndSelectNext).register(
"ToggleSelectionAndSelectNext",
termbox.KeyCtrlSpace,
)
}
func doRotateMatcher(i *Input, ev termbox.Event) {
i.Ctx.CurrentMatcher++
if i.Ctx.CurrentMatcher >= len(i.Ctx.Matchers) {
i.Ctx.CurrentMatcher = 0
}
if i.ExecQuery() {
return
}
i.DrawMatches(nil)
}
func doToggleSelection(i *Input, _ termbox.Event) {
if i.selection.Has(i.currentLine) {
i.selection.Remove(i.currentLine)
return
}
i.selection.Add(i.currentLine)
}
func doFinish(i *Input, _ termbox.Event) {
// Must end with all the selected lines.
i.selection.Add(i.currentLine)
i.result = []Match{}
for _, lineno := range i.selection {
if lineno <= len(i.current) {
i.result = append(i.result, i.current[lineno-1])
}
}
i.ExitWith(0)
}
func doCancel(i *Input, ev termbox.Event) {
// peco.Cancel -> end program, exit with failure
i.ExitWith(1)
}
func doSelectPrevious(i *Input, ev termbox.Event) {
i.PagingCh() <- ToPrevLine
i.DrawMatches(nil)
}
func doSelectNext(i *Input, ev termbox.Event) {
i.PagingCh() <- ToNextLine
i.DrawMatches(nil)
}
func doSelectPreviousPage(i *Input, ev termbox.Event) {
i.PagingCh() <- ToPrevPage
i.DrawMatches(nil)
}
func doSelectNextPage(i *Input, ev termbox.Event) {
i.PagingCh() <- ToNextPage
i.DrawMatches(nil)
}
func doToggleSelectionAndSelectNext(i *Input, ev termbox.Event) {
doToggleSelection(i, ev)
doSelectNext(i, ev)
}
func doDeleteBackwardWord(i *Input, _ termbox.Event) {
if i.caretPos == 0 {
return
}
for pos := i.caretPos - 1; pos >= 0; pos-- {
if pos == 0 {
i.query = i.query[i.caretPos:]
break
}
if unicode.IsSpace(i.query[pos]) {
buf := make([]rune, len(i.query)-(i.caretPos-pos))
copy(buf, i.query[:pos])
copy(buf[pos:], i.query[i.caretPos:])
i.query = buf
i.caretPos = pos
break
}
}
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func doForwardWord(i *Input, _ termbox.Event) {
if i.caretPos >= len(i.query) {
return
}
foundSpace := false
for pos := i.caretPos; pos < len(i.query); pos++ {
r := i.query[pos]
if foundSpace {
if !unicode.IsSpace(r) {
i.caretPos = pos
i.DrawMatches(nil)
return
}
} else {
if unicode.IsSpace(r) {
foundSpace = true
}
}
}
// not found. just move to the end of the buffer
i.caretPos = len(i.query)
i.DrawMatches(nil)
}
func doBackwardWord(i *Input, _ termbox.Event) {
if i.caretPos == 0 {
return
}
if i.caretPos >= len(i.query) {
i.caretPos--
}
// if we start from a whitespace-ish position, we should
// rewind to the end of the previous word, and then do the
// search all over again
SEARCH_PREV_WORD:
if unicode.IsSpace(i.query[i.caretPos]) {
for pos := i.caretPos; pos > 0; pos-- {
if !unicode.IsSpace(i.query[pos]) {
i.caretPos = pos
break
}
}
}
// if we start from the first character of a word, we
// should attempt to move back and search for the previous word
if i.caretPos > 0 && unicode.IsSpace(i.query[i.caretPos-1]) {
i.caretPos--
goto SEARCH_PREV_WORD
}
// Now look for a space
for pos := i.caretPos; pos > 0; pos-- {
if unicode.IsSpace(i.query[pos]) {
i.caretPos = pos + 1
i.DrawMatches(nil)
return
}
}
// not found. just move to the beginning of the buffer
i.caretPos = 0
i.DrawMatches(nil)
}
func doForwardChar(i *Input, _ termbox.Event) {
if i.caretPos >= len(i.query) {
return
}
i.caretPos++
i.DrawMatches(nil)
}
func doBackwardChar(i *Input, _ termbox.Event) {
if i.caretPos <= 0 {
return
}
i.caretPos--
i.DrawMatches(nil)
}
func doDeleteForwardWord(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
for pos := i.caretPos; pos < len(i.query); pos++ {
if pos == len(i.query)-1 {
i.query = i.query[:i.caretPos]
break
}
if unicode.IsSpace(i.query[pos]) {
buf := make([]rune, len(i.query)-(pos-i.caretPos))
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos:], i.query[pos:])
i.query = buf
break
}
}
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func doBeginningOfLine(i *Input, _ termbox.Event) {
i.caretPos = 0
i.DrawMatches(nil)
}
func doEndOfLine(i *Input, _ termbox.Event) {
i.caretPos = len(i.query)
i.DrawMatches(nil)
}
func doEndOfFile(i *Input, ev termbox.Event) {
if len(i.query) > 0 {
doDeleteForwardChar(i, ev)
} else {
doCancel(i, ev)
}
}
func doKillBeginningOfLine(i *Input, _ termbox.Event) {
i.query = i.query[i.caretPos:]
i.caretPos = 0
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func doKillEndOfLine(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
i.query = i.query[0:i.caretPos]
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func doDeleteAll(i *Input, _ termbox.Event) {
i.query = make([]rune, 0)
i.current = nil
i.DrawMatches(nil)
}
func doDeleteForwardChar(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
buf := make([]rune, len(i.query)-1)
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos:], i.query[i.caretPos+1:])
i.query = buf
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func doDeleteBackwardChar(i *Input, ev termbox.Event) {
if len(i.query) <= 0 {
return
}
switch i.caretPos {
case 0:
// No op
return
case len(i.query):
i.query = i.query[:len(i.query)-1]
default:
buf := make([]rune, len(i.query)-1)
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos-1:], i.query[i.caretPos:])
i.query = buf
}
i.caretPos--
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}

View file

@ -83,7 +83,7 @@ func (i *Input) Loop() {
func (i *Input) handleKeyEvent(ev termbox.Event) {
if h := i.config.Keymap.Handler(ev); h != nil {
h(i, ev)
h.Execute(i, ev)
return
}
}

362
keymap.go
View file

@ -5,7 +5,6 @@ import (
"fmt"
"os"
"strings"
"unicode"
"github.com/nsf/termbox-go"
)
@ -17,14 +16,12 @@ const (
ModMax
)
type KeymapHandler func(*Input, termbox.Event)
// Keymap contains keys which are modifiers (like Alt+X), and points to
// RawKeymap
type Keymap [ModMax]RawKeymap
// RawKeymap contains the actual mapping from termbox.Key to KeymapHandler
type RawKeymap map[termbox.Key]KeymapHandler
// RawKeymap contains the actual mapping from termbox.Key to Action
type RawKeymap map[termbox.Key]Action
type KeymapStringKey string
@ -139,304 +136,6 @@ func handleAcceptChar(i *Input, ev termbox.Event) {
}
}
// peco.Finish -> end program, exit with success
func handleFinish(i *Input, _ termbox.Event) {
// Must end with all the selected lines.
i.selection.Add(i.currentLine)
i.result = []Match{}
for _, lineno := range i.selection {
if lineno <= len(i.current) {
i.result = append(i.result, i.current[lineno-1])
}
}
i.ExitWith(0)
}
func handleToggleSelection(i *Input, _ termbox.Event) {
if i.selection.Has(i.currentLine) {
i.selection.Remove(i.currentLine)
return
}
i.selection.Add(i.currentLine)
}
func handleToggleSelectionAndSelectNext(i *Input, ev termbox.Event) {
handleToggleSelection(i, ev)
handleSelectNext(i, ev)
}
// peco.Cancel -> end program, exit with failure
func handleCancel(i *Input, ev termbox.Event) {
i.ExitWith(1)
}
func handleSelectPrevious(i *Input, ev termbox.Event) {
i.PagingCh() <- ToPrevLine
i.DrawMatches(nil)
}
func handleSelectNext(i *Input, ev termbox.Event) {
i.PagingCh() <- ToNextLine
i.DrawMatches(nil)
}
func handleSelectPreviousPage(i *Input, ev termbox.Event) {
i.PagingCh() <- ToPrevPage
i.DrawMatches(nil)
}
func handleSelectNextPage(i *Input, ev termbox.Event) {
i.PagingCh() <- ToNextPage
i.DrawMatches(nil)
}
func handleRotateMatcher(i *Input, ev termbox.Event) {
i.Ctx.CurrentMatcher++
if i.Ctx.CurrentMatcher >= len(i.Ctx.Matchers) {
i.Ctx.CurrentMatcher = 0
}
if i.ExecQuery() {
return
}
i.DrawMatches(nil)
}
func handleForwardWord(i *Input, _ termbox.Event) {
if i.caretPos >= len(i.query) {
return
}
foundSpace := false
for pos := i.caretPos; pos < len(i.query); pos++ {
r := i.query[pos]
if foundSpace {
if !unicode.IsSpace(r) {
i.caretPos = pos
i.DrawMatches(nil)
return
}
} else {
if unicode.IsSpace(r) {
foundSpace = true
}
}
}
// not found. just move to the end of the buffer
i.caretPos = len(i.query)
i.DrawMatches(nil)
}
func handleBackwardWord(i *Input, _ termbox.Event) {
if i.caretPos == 0 {
return
}
if i.caretPos >= len(i.query) {
i.caretPos--
}
// if we start from a whitespace-ish position, we should
// rewind to the end of the previous word, and then do the
// search all over again
SEARCH_PREV_WORD:
if unicode.IsSpace(i.query[i.caretPos]) {
for pos := i.caretPos; pos > 0; pos-- {
if !unicode.IsSpace(i.query[pos]) {
i.caretPos = pos
break
}
}
}
// if we start from the first character of a word, we
// should attempt to move back and search for the previous word
if i.caretPos > 0 && unicode.IsSpace(i.query[i.caretPos-1]) {
i.caretPos--
goto SEARCH_PREV_WORD
}
// Now look for a space
for pos := i.caretPos; pos > 0; pos-- {
if unicode.IsSpace(i.query[pos]) {
i.caretPos = pos + 1
i.DrawMatches(nil)
return
}
}
// not found. just move to the beginning of the buffer
i.caretPos = 0
i.DrawMatches(nil)
}
func handleForwardChar(i *Input, _ termbox.Event) {
if i.caretPos >= len(i.query) {
return
}
i.caretPos++
i.DrawMatches(nil)
}
func handleBackwardChar(i *Input, _ termbox.Event) {
if i.caretPos <= 0 {
return
}
i.caretPos--
i.DrawMatches(nil)
}
func handleBeginningOfLine(i *Input, _ termbox.Event) {
i.caretPos = 0
i.DrawMatches(nil)
}
func handleEndOfLine(i *Input, _ termbox.Event) {
i.caretPos = len(i.query)
i.DrawMatches(nil)
}
func handleEndOfFile(i *Input, ev termbox.Event) {
if len(i.query) > 0 {
handleDeleteForwardChar(i, ev)
} else {
handleCancel(i, ev)
}
}
func handleKillBeginOfLine(i *Input, _ termbox.Event) {
i.query = i.query[i.caretPos:]
i.caretPos = 0
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func handleKillEndOfLine(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
i.query = i.query[0:i.caretPos]
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func handleDeleteAll(i *Input, _ termbox.Event) {
i.query = make([]rune, 0)
i.current = nil
i.DrawMatches(nil)
}
func handleDeleteForwardChar(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
buf := make([]rune, len(i.query)-1)
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos:], i.query[i.caretPos+1:])
i.query = buf
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func handleDeleteBackwardChar(i *Input, ev termbox.Event) {
if len(i.query) <= 0 {
return
}
switch i.caretPos {
case 0:
// No op
return
case len(i.query):
i.query = i.query[:len(i.query)-1]
default:
buf := make([]rune, len(i.query)-1)
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos-1:], i.query[i.caretPos:])
i.query = buf
}
i.caretPos--
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func handleDeleteForwardWord(i *Input, _ termbox.Event) {
if len(i.query) <= i.caretPos {
return
}
for pos := i.caretPos; pos < len(i.query); pos++ {
if pos == len(i.query)-1 {
i.query = i.query[:i.caretPos]
break
}
if unicode.IsSpace(i.query[pos]) {
buf := make([]rune, len(i.query)-(pos-i.caretPos))
copy(buf, i.query[:i.caretPos])
copy(buf[i.caretPos:], i.query[pos:])
i.query = buf
break
}
}
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func handleDeleteBackwardWord(i *Input, _ termbox.Event) {
if i.caretPos == 0 {
return
}
for pos := i.caretPos - 1; pos >= 0; pos-- {
if pos == 0 {
i.query = i.query[i.caretPos:]
break
}
if unicode.IsSpace(i.query[pos]) {
buf := make([]rune, len(i.query)-(i.caretPos-pos))
copy(buf, i.query[:pos])
copy(buf[pos:], i.query[i.caretPos:])
i.query = buf
i.caretPos = pos
break
}
}
if i.ExecQuery() {
return
}
i.current = nil
i.DrawMatches(nil)
}
func (ksk KeymapStringKey) ToKey() (k termbox.Key, modifier int, err error) {
modifier = ModNone
key := string(ksk)
@ -457,61 +156,18 @@ func (ksk KeymapStringKey) ToKey() (k termbox.Key, modifier int, err error) {
return
}
var handlers = map[string]KeymapHandler{
"peco.KillEndOfLine": handleKillEndOfLine,
"peco.DeleteAll": handleDeleteAll,
"peco.BeginningOfLine": handleBeginningOfLine,
"peco.EndOfLine": handleEndOfLine,
"peco.EndOfFile": handleEndOfFile,
"peco.ForwardChar": handleForwardChar,
"peco.BackwardChar": handleBackwardChar,
"peco.ForwardWord": handleForwardWord,
"peco.BackwardWord": handleBackwardWord,
"peco.DeleteForwardChar": handleDeleteForwardChar,
"peco.DeleteBackwardChar": handleDeleteBackwardChar,
"peco.DeleteForwardWord": handleDeleteForwardWord,
"peco.DeleteBackwardWord": handleDeleteBackwardWord,
"peco.SelectPreviousPage": handleSelectPreviousPage,
"peco.SelectNextPage": handleSelectNextPage,
"peco.SelectPrevious": handleSelectPrevious,
"peco.SelectNext": handleSelectNext,
"peco.ToggleSelection": handleToggleSelection,
"peco.ToggleSelectionAndSelectNext": handleToggleSelectionAndSelectNext,
"peco.RotateMatcher": handleRotateMatcher,
"peco.Finish": handleFinish,
"peco.Cancel": handleCancel,
}
func NewKeymap() Keymap {
def := RawKeymap{}
for k, v := range defaultKeyBinding {
def[k] = v
}
return Keymap{
{
termbox.KeyEsc: handleCancel,
termbox.KeyCtrlC: handleCancel,
termbox.KeyEnter: handleFinish,
termbox.KeyArrowUp: handleSelectPrevious,
termbox.KeyCtrlP: handleSelectPrevious,
termbox.KeyArrowDown: handleSelectNext,
termbox.KeyCtrlN: handleSelectNext,
termbox.KeyArrowLeft: handleSelectPreviousPage,
termbox.KeyArrowRight: handleSelectNextPage,
termbox.KeyCtrlD: handleDeleteForwardChar,
termbox.KeyBackspace: handleDeleteBackwardChar,
termbox.KeyBackspace2: handleDeleteBackwardChar,
termbox.KeyCtrlW: handleDeleteBackwardWord,
termbox.KeyCtrlA: handleBeginningOfLine,
termbox.KeyCtrlE: handleEndOfLine,
termbox.KeyCtrlF: handleForwardChar,
termbox.KeyCtrlB: handleBackwardChar,
termbox.KeyCtrlK: handleKillEndOfLine,
termbox.KeyCtrlU: handleKillBeginOfLine,
termbox.KeyCtrlR: handleRotateMatcher,
termbox.KeyCtrlSpace: handleToggleSelectionAndSelectNext,
},
def,
{},
}
}
func (km Keymap) Handler(ev termbox.Event) KeymapHandler {
func (km Keymap) Handler(ev termbox.Event) Action {
modifier := ModNone
if (ev.Mod & termbox.ModAlt) != 0 {
modifier = ModAlt
@ -542,7 +198,7 @@ func (km Keymap) Handler(ev termbox.Event) KeymapHandler {
panic("Can't get here")
}
return handleAcceptChar
return ActionFunc(handleAcceptChar)
}
func (km Keymap) UnmarshalJSON(buf []byte) error {