This commit is contained in:
Cheney 2015-10-02 10:37:21 +08:00
parent 79d1bf27b4
commit 879224ddc9
6 changed files with 139 additions and 79 deletions

View File

@ -28,6 +28,12 @@ You can read the source code in [example/main.go](https://github.com/chzyer/read
* More funny examples
* Support dumb/eterm-color terminal in emacs
# Features
* Support emacs/vi mode, almost all basic features GNU-Readline support
* zsh-style backward/forward history search
* zsh-style completion
* Readline auto refresh when others write to Stdout while editing(it needs specify the Stdout/Stderr provided by *readline.Instance to others).
# Usage
* Simplest example

View File

@ -20,6 +20,10 @@ bye
}
var completer = readline.NewPrefixCompleter(
readline.PcItem("mode",
readline.PcItem("vi"),
readline.PcItem("emacs"),
),
readline.PcItem("login"),
readline.PcItem("say",
readline.PcItem("hello"),
@ -40,7 +44,6 @@ func main() {
Prompt: "\033[31m»\033[0m ",
HistoryFile: "/tmp/readline.tmp",
AutoComplete: completer,
// VimMode: true,
})
if err != nil {
panic(err)
@ -53,8 +56,23 @@ func main() {
if err != nil {
break
}
line = strings.TrimSpace(line)
switch {
case strings.HasPrefix(line, "mode "):
switch line[5:] {
case "vi":
l.SetVimMode(true)
case "emacs":
l.SetVimMode(false)
default:
println("invalid mode:", line[5:])
}
case line == "mode":
if l.IsVimMode() {
println("current mode: vim")
} else {
println("current mode: emacs")
}
case line == "login":
pswd, err := l.ReadPassword("please enter your password: ")
if err != nil {

View File

@ -95,9 +95,8 @@ func (o *Operation) ioloop() {
}
if o.IsEnableVimMode() {
var ok bool
r, ok = o.HandleVim(r, o.t.ReadRune)
if ok {
r = o.HandleVim(r, o.t.ReadRune)
if r == 0 {
continue
}
}

View File

@ -64,6 +64,10 @@ func (i *Instance) SetVimMode(on bool) {
i.o.SetVimMode(on)
}
func (i *Instance) IsVimMode() bool {
return i.o.IsEnableVimMode()
}
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
return i.o.Password(prompt)
}

View File

@ -5,6 +5,11 @@ import (
"io"
)
type runeBufferBck struct {
buf []rune
idx int
}
type RuneBuffer struct {
buf []rune
idx int
@ -12,6 +17,22 @@ type RuneBuffer struct {
w io.Writer
cleanInScreen bool
bck *runeBufferBck
}
func (r *RuneBuffer) Backup() {
r.bck = &runeBufferBck{r.buf, r.idx}
}
func (r *RuneBuffer) Restore() {
r.Refresh(func() {
if r.bck == nil {
return
}
r.buf = r.bck.buf
r.idx = r.bck.idx
})
}
func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
@ -98,6 +119,13 @@ func (r *RuneBuffer) MoveForward() {
})
}
func (r *RuneBuffer) Erase() {
r.Refresh(func() {
r.idx = 0
r.buf = r.buf[:0]
})
}
func (r *RuneBuffer) Delete() (success bool) {
r.Refresh(func() {
if r.idx == len(r.buf) {

153
vim.go
View File

@ -30,92 +30,97 @@ func (o *opVim) SetVimMode(on bool) {
}
func (o *opVim) ExitVimMode() {
o.vimMode = VIM_NORMAL
o.vimMode = VIM_INSERT
}
func (o *opVim) IsEnableVimMode() bool {
return o.cfg.VimMode
}
func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune, handle bool) {
func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) {
rb := o.op.buf
handled = true
switch r {
case CharEnter, CharInterrupt:
case 'h':
t = CharBackward
case 'j':
t = CharNext
case 'k':
t = CharPrev
case 'l':
t = CharForward
case '0', '^':
rb.MoveToLineStart()
case '$':
rb.MoveToLineEnd()
case 'b':
rb.MoveToPrevWord()
case 'w':
rb.MoveToNextWord()
case 'f', 'F', 't', 'T':
next := readNext()
prevChar := r == 't' || r == 'T'
reverse := r == 'F' || r == 'T'
switch next {
case CharEsc:
default:
rb.MoveTo(next, prevChar, reverse)
}
default:
return r, false
}
return t, true
}
func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) {
rb := o.op.buf
handled := true
{
switch r {
case 'h':
t = CharBackward
case 'j':
t = CharNext
case 'k':
t = CharPrev
case 'l':
t = CharForward
default:
handled = false
}
if handled {
return t, false
}
}
{ // to insert
handled = true
switch r {
case 'i':
case 'I':
rb.MoveToLineStart()
case 'a':
rb.MoveForward()
case 'A':
rb.MoveToLineEnd()
case 's':
rb.Delete()
default:
handled = false
}
if handled {
o.EnterVimInsertMode()
return r, true
}
}
{ // movement
handled = true
switch r {
case '0', '^':
rb.MoveToLineStart()
case '$':
rb.MoveToLineEnd()
case 'b':
rb.MoveToPrevWord()
handled = true
switch r {
case 'i':
case 'I':
rb.MoveToLineStart()
case 'a':
rb.MoveForward()
case 'A':
rb.MoveToLineEnd()
case 's':
rb.Delete()
case 'S':
rb.Erase()
case 'c':
next := readNext()
switch next {
case 'c':
rb.Erase()
case 'w':
rb.MoveToNextWord()
case 'f', 'F', 't', 'T':
next := readNext()
prevChar := r == 't' || r == 'T'
reverse := r == 'F' || r == 'T'
switch next {
case CharEsc:
default:
if rb.MoveTo(next, prevChar, reverse) {
return r, true
}
}
default:
handled = false
}
if handled {
return r, true
rb.DeleteWord()
}
default:
return r, false
}
o.EnterVimInsertMode()
return
}
func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) {
switch r {
case CharEnter, CharInterrupt:
o.ExitVimMode()
return r
}
if r, handled := o.handleVimNormalMovement(r, readNext); handled {
return r
}
if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled {
return r
}
// invalid operation
o.op.t.Bell()
return r, true
return 0
}
func (o *opVim) EnterVimInsertMode() {
@ -126,19 +131,19 @@ func (o *opVim) ExitVimInsertMode() {
o.vimMode = VIM_NORMAL
}
func (o *opVim) HandleVim(r rune, readNext func() rune) (rune, bool) {
func (o *opVim) HandleVim(r rune, readNext func() rune) rune {
if o.vimMode == VIM_NORMAL {
return o.HandleVimNormal(r, readNext)
}
if r == CharEsc {
o.ExitVimInsertMode()
return r, true
return 0
}
switch o.vimMode {
case VIM_INSERT:
return r, false
return r
case VIM_VISUAL:
}
return r, false
return r
}