Refactor keybindings

This commit is contained in:
c-bata 2017-08-13 13:09:45 +09:00
parent 6afdbb6a0b
commit 62d47c3acf
5 changed files with 200 additions and 47 deletions

View File

@ -24,12 +24,7 @@ Easy building a multi-platform binary of the command line tools because built wi
![Keyboard shortcuts](./_resources/keyboard-shortcuts.gif)
go-prompt implements the bash compatible keyboard shortcuts.
* `Ctrl+A`: Go to the beginning of the line.
* `Ctrl+E`: Go to the end of the line.
* `Ctrl+K`: Cut the part of the line after the cursor.
* etc...
You can customize keyboard shortcuts. More details are available from 'KeyBoard Shortcuts' section in Developer Guide.
#### Easy to use
@ -61,7 +56,7 @@ func main() {
More practical example is avairable from `_example` directory or [kube-prompt](https://github.com/c-bata/kube-prompt).
#### Flexible customize
#### Flexible customization
![options](./_resources/prompt-options.png)
go-prompt has many color options. All options are listed in [Developer Guide](./example/README.md).
@ -75,6 +70,7 @@ go-prompt has many color options. All options are listed in [Developer Guide](./
* If you want to create projects using go-prompt, you might want to look at the [Getting Started](./example/README.md).
* If you want to contribute go-prompt, you might want to look at the [Developer Guide](./_tools/README.md).
* If you want to know internal API, you might want to look at the [GoDoc](http://godoc.org/github.com/c-bata/go-prompt).
## Author

83
emacs.go Normal file
View File

@ -0,0 +1,83 @@
package prompt
var emacsKeyBindings = []KeyBind {
// Go to the End of the line
{
Key: ControlE,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextAfterCursor())
buf.CursorRight(len(x))
return buf
},
},
// Go to the beginning of the line
{
Key: ControlA,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextBeforeCursor())
buf.CursorLeft(len(x))
return buf
},
},
// Cut the Line after the cursor
{
Key: ControlK,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextAfterCursor())
buf.Delete(len(x))
return buf
},
},
// Cut/delete the Line before the cursor
{
Key: ControlU,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextBeforeCursor())
buf.DeleteBeforeCursor(len(x))
return buf
},
},
// Delete character under the cursor
{
Key: ControlD,
Fn: func(buf *Buffer) *Buffer {
if buf.Text() == "" {
return buf // This means just exit.
}
buf.Delete(1)
return buf
},
},
// Backspace
{
Key: ControlH,
Fn: func(buf *Buffer) *Buffer {
buf.DeleteBeforeCursor(1)
return buf
},
},
// Right allow: Forward one character
{
Key: ControlF,
Fn: func(buf *Buffer) *Buffer {
buf.CursorRight(1)
return buf
},
},
// Left allow: Backward one character
{
Key: ControlB,
Fn: func(buf *Buffer) *Buffer {
buf.CursorLeft(1)
return buf
},
},
// Cut the Word before the cursor.
{
Key: ControlW,
Fn: func(buf *Buffer) *Buffer {
buf.DeleteBeforeCursor(len([]rune(buf.Document().GetWordBeforeCursor())))
return buf
},
},
}

62
key_bind.go Normal file
View File

@ -0,0 +1,62 @@
package prompt
type KeyBindFunc func(*Buffer) *Buffer
type KeyBind struct {
Key Key
Fn KeyBindFunc
}
var commonKeyBindings = []KeyBind {
// Go to the End of the line
{
Key: End,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextAfterCursor())
buf.CursorRight(len(x))
return buf
},
},
// Go to the beginning of the line
{
Key: Home,
Fn: func(buf *Buffer) *Buffer {
x := []rune(buf.Document().TextBeforeCursor())
buf.CursorLeft(len(x))
return buf
},
},
// Delete character under the cursor
{
Key: Delete,
Fn: func(buf *Buffer) *Buffer {
buf.Delete(1)
return buf
},
},
// Backspace
{
Key: Backspace,
Fn: func(buf *Buffer) *Buffer {
buf.DeleteBeforeCursor(1)
return buf
},
},
// Right allow: Forward one character
{
Key: Right,
Fn: func(buf *Buffer) *Buffer {
buf.CursorRight(1)
return buf
},
},
// Left allow: Backward one character
{
Key: Left,
Fn: func(buf *Buffer) *Buffer {
buf.CursorLeft(1)
return buf
},
},
}

View File

@ -145,6 +145,13 @@ func OptionHistory(x []string) option {
}
}
func OptionAddKeyBind(b ...KeyBind) option {
return func(p *Prompt) error {
p.keyBindings = append(p.keyBindings, b...)
return nil
}
}
func New(executor Executor, completer Completer, opts ...option) *Prompt {
pt := &Prompt{
in: &VT100Parser{fd: syscall.Stdin},

View File

@ -18,12 +18,13 @@ type Executor func(string)
type Completer func(string) []Suggest
type Prompt struct {
in ConsoleParser
buf *Buffer
renderer *Render
executor Executor
history *History
completion *CompletionManager
in ConsoleParser
buf *Buffer
renderer *Render
executor Executor
history *History
completion *CompletionManager
keyBindings []KeyBind
}
type Exec struct {
@ -97,7 +98,7 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
key := p.in.GetKey(b)
switch key {
case ControlJ, Enter:
case Enter, ControlJ:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursor()
if w != "" {
@ -114,30 +115,12 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
if exec.input != "" {
p.history.Add(exec.input)
}
case ControlA:
x := []rune(p.buf.Document().TextBeforeCursor())
p.buf.CursorLeft(len(x))
case ControlE:
x := []rune(p.buf.Document().TextAfterCursor())
p.buf.CursorRight(len(x))
case ControlK:
x := []rune(p.buf.Document().TextAfterCursor())
p.buf.Delete(len(x))
case ControlU:
x := []rune(p.buf.Document().TextBeforeCursor())
p.buf.DeleteBeforeCursor(len(x))
case ControlC:
p.renderer.BreakLine(p.buf)
p.buf = NewBuffer()
p.completion.Reset()
p.history.Clear()
case ControlD:
if p.buf.Text() == "" {
shouldExit = true
} else {
p.buf.Delete(1)
}
case Up:
case Up, ControlP:
if !p.completion.Completing() {
if newBuf, changed := p.history.Older(p.buf); changed {
p.buf = newBuf
@ -147,7 +130,7 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
fallthrough
case BackTab:
p.completion.Previous()
case Down:
case Down, ControlN:
if !p.completion.Completing() {
if newBuf, changed := p.history.Newer(p.buf); changed {
p.buf = newBuf
@ -157,20 +140,12 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
fallthrough
case Tab, ControlI:
p.completion.Next()
case Left:
p.buf.CursorLeft(1)
case Right:
p.buf.CursorRight(1)
case Backspace:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursor()
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
p.buf.InsertText(s.Text, false, true)
p.completion.Reset()
case ControlD:
if p.buf.Text() == "" {
shouldExit = true
return
}
p.buf.DeleteBeforeCursor(1)
p.completion.Reset()
case NotDefined:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursor()
@ -182,8 +157,38 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
p.completion.Reset()
p.buf.InsertText(string(b), false, true)
default:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursor()
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
p.buf.InsertText(s.Text, false, true)
}
p.completion.Reset()
}
for i := range commonKeyBindings {
kb := commonKeyBindings[i]
if kb.Key == key {
p.buf = kb.Fn(p.buf)
}
}
// All the above assume that bash is running in the default Emacs setting
for i := range emacsKeyBindings {
kb := emacsKeyBindings[i]
if kb.Key == key {
p.buf = kb.Fn(p.buf)
}
}
// Custom keybindings
for i := range p.keyBindings {
kb := p.keyBindings[i]
if kb.Key == key {
p.buf = kb.Fn(p.buf)
}
}
return
}