From 879224ddc999a58d955e1d5a6aa72c83405f7fab Mon Sep 17 00:00:00 2001 From: Cheney Date: Fri, 2 Oct 2015 10:37:21 +0800 Subject: [PATCH] refactor --- README.md | 6 ++ example/main.go | 22 ++++++- operation.go | 5 +- readline.go | 4 ++ runebuf.go | 28 +++++++++ vim.go | 153 +++++++++++++++++++++++++----------------------- 6 files changed, 139 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 4f92e2e..0d5dd8d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/main.go b/example/main.go index eda3be9..e550e95 100644 --- a/example/main.go +++ b/example/main.go @@ -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 { diff --git a/operation.go b/operation.go index 739d2f6..bd2247c 100644 --- a/operation.go +++ b/operation.go @@ -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 } } diff --git a/readline.go b/readline.go index 04f339c..e91d6ae 100644 --- a/readline.go +++ b/readline.go @@ -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) } diff --git a/runebuf.go b/runebuf.go index 5cfa9f7..4b0f4aa 100644 --- a/runebuf.go +++ b/runebuf.go @@ -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) { diff --git a/vim.go b/vim.go index c64fbaa..61f28e0 100644 --- a/vim.go +++ b/vim.go @@ -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 }