forked from mirror/readline
Merge pull request #7 from chzyer/feature/add_vim_mode
add simple vim mode
This commit is contained in:
commit
38e5213cc8
|
@ -24,10 +24,15 @@ You can read the source code in [example/main.go](https://github.com/chzyer/read
|
||||||
|
|
||||||
# Todo
|
# Todo
|
||||||
|
|
||||||
* Vim mode
|
|
||||||
* More funny examples
|
* More funny examples
|
||||||
* Support dumb/eterm-color terminal in emacs
|
* Support dumb/eterm-color terminal in emacs
|
||||||
|
|
||||||
|
# Features
|
||||||
|
* Support emacs/vi mode, almost all basic features that GNU-Readline is supported
|
||||||
|
* 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
|
# Usage
|
||||||
|
|
||||||
* Simplest example
|
* Simplest example
|
||||||
|
|
6
char.go
6
char.go
|
@ -7,7 +7,7 @@ const (
|
||||||
CharDelete = 4
|
CharDelete = 4
|
||||||
CharLineEnd = 5
|
CharLineEnd = 5
|
||||||
CharForward = 6
|
CharForward = 6
|
||||||
CharCancel = 7
|
CharBell = 7
|
||||||
CharCtrlH = 8
|
CharCtrlH = 8
|
||||||
CharTab = 9
|
CharTab = 9
|
||||||
CharCtrlJ = 10
|
CharCtrlJ = 10
|
||||||
|
@ -26,8 +26,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MetaPrev rune = -iota - 1
|
MetaBackward rune = -iota - 1
|
||||||
MetaNext
|
MetaForward
|
||||||
MetaDelete
|
MetaDelete
|
||||||
MetaBackspace
|
MetaBackspace
|
||||||
MetaTranspose
|
MetaTranspose
|
||||||
|
|
|
@ -127,7 +127,7 @@ func (o *opCompleter) HandleCompleteSelect(r rune) bool {
|
||||||
next = false
|
next = false
|
||||||
case CharTab, CharForward:
|
case CharTab, CharForward:
|
||||||
o.doSelect()
|
o.doSelect()
|
||||||
case CharCancel, CharInterrupt:
|
case CharBell, CharInterrupt:
|
||||||
o.ExitCompleteMode(true)
|
o.ExitCompleteMode(true)
|
||||||
next = false
|
next = false
|
||||||
case CharNext:
|
case CharNext:
|
||||||
|
|
|
@ -20,6 +20,10 @@ bye
|
||||||
}
|
}
|
||||||
|
|
||||||
var completer = readline.NewPrefixCompleter(
|
var completer = readline.NewPrefixCompleter(
|
||||||
|
readline.PcItem("mode",
|
||||||
|
readline.PcItem("vi"),
|
||||||
|
readline.PcItem("emacs"),
|
||||||
|
),
|
||||||
readline.PcItem("login"),
|
readline.PcItem("login"),
|
||||||
readline.PcItem("say",
|
readline.PcItem("say",
|
||||||
readline.PcItem("hello"),
|
readline.PcItem("hello"),
|
||||||
|
@ -52,8 +56,23 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
switch {
|
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":
|
case line == "login":
|
||||||
pswd, err := l.ReadPassword("please enter your password: ")
|
pswd, err := l.ReadPassword("please enter your password: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
26
operation.go
26
operation.go
|
@ -18,6 +18,7 @@ type Operation struct {
|
||||||
*opHistory
|
*opHistory
|
||||||
*opSearch
|
*opSearch
|
||||||
*opCompleter
|
*opCompleter
|
||||||
|
*opVim
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrapWriter struct {
|
type wrapWriter struct {
|
||||||
|
@ -56,6 +57,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
opHistory: newOpHistory(cfg.HistoryFile),
|
opHistory: newOpHistory(cfg.HistoryFile),
|
||||||
}
|
}
|
||||||
|
op.opVim = newVimMode(op)
|
||||||
op.w = op.buf.w
|
op.w = op.buf.w
|
||||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op)
|
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||||
|
@ -87,13 +89,20 @@ func (o *Operation) ioloop() {
|
||||||
case CharInterrupt:
|
case CharInterrupt:
|
||||||
o.t.KickRead()
|
o.t.KickRead()
|
||||||
fallthrough
|
fallthrough
|
||||||
case CharCancel:
|
case CharBell:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.IsEnableVimMode() {
|
||||||
|
r = o.HandleVim(r, o.t.ReadRune)
|
||||||
|
if r == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r {
|
switch r {
|
||||||
case CharCancel:
|
case CharBell:
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.ExitSearchMode(true)
|
o.ExitSearchMode(true)
|
||||||
o.buf.Refresh(nil)
|
o.buf.Refresh(nil)
|
||||||
|
@ -119,11 +128,11 @@ func (o *Operation) ioloop() {
|
||||||
case CharKill:
|
case CharKill:
|
||||||
o.buf.Kill()
|
o.buf.Kill()
|
||||||
keepInCompleteMode = true
|
keepInCompleteMode = true
|
||||||
case MetaNext:
|
case MetaForward:
|
||||||
o.buf.MoveToNextWord()
|
o.buf.MoveToNextWord()
|
||||||
case CharTranspose:
|
case CharTranspose:
|
||||||
o.buf.Transpose()
|
o.buf.Transpose()
|
||||||
case MetaPrev:
|
case MetaBackward:
|
||||||
o.buf.MoveToPrevWord()
|
o.buf.MoveToPrevWord()
|
||||||
case MetaDelete:
|
case MetaDelete:
|
||||||
o.buf.DeleteWord()
|
o.buf.DeleteWord()
|
||||||
|
@ -132,7 +141,9 @@ func (o *Operation) ioloop() {
|
||||||
case CharLineEnd:
|
case CharLineEnd:
|
||||||
o.buf.MoveToLineEnd()
|
o.buf.MoveToLineEnd()
|
||||||
case CharDelete:
|
case CharDelete:
|
||||||
o.buf.Delete()
|
if !o.buf.Delete() {
|
||||||
|
o.t.Bell()
|
||||||
|
}
|
||||||
case CharBackspace, CharCtrlH:
|
case CharBackspace, CharCtrlH:
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.SearchBackspace()
|
o.SearchBackspace()
|
||||||
|
@ -141,6 +152,7 @@ func (o *Operation) ioloop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.buf.Len() == 0 {
|
if o.buf.Len() == 0 {
|
||||||
|
o.t.Bell()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
o.buf.Backspace()
|
o.buf.Backspace()
|
||||||
|
@ -167,11 +179,15 @@ func (o *Operation) ioloop() {
|
||||||
buf := o.PrevHistory()
|
buf := o.PrevHistory()
|
||||||
if buf != nil {
|
if buf != nil {
|
||||||
o.buf.Set(buf)
|
o.buf.Set(buf)
|
||||||
|
} else {
|
||||||
|
o.t.Bell()
|
||||||
}
|
}
|
||||||
case CharNext:
|
case CharNext:
|
||||||
buf, ok := o.NextHistory()
|
buf, ok := o.NextHistory()
|
||||||
if ok {
|
if ok {
|
||||||
o.buf.Set(buf)
|
o.buf.Set(buf)
|
||||||
|
} else {
|
||||||
|
o.t.Bell()
|
||||||
}
|
}
|
||||||
case CharInterrupt:
|
case CharInterrupt:
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Config struct {
|
||||||
Prompt string
|
Prompt string
|
||||||
HistoryFile string
|
HistoryFile string
|
||||||
AutoComplete AutoCompleter
|
AutoComplete AutoCompleter
|
||||||
|
VimMode bool
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
|
@ -59,6 +60,14 @@ func (i *Instance) Stderr() io.Writer {
|
||||||
return i.o.Stderr()
|
return i.o.Stderr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
|
||||||
return i.o.Password(prompt)
|
return i.o.Password(prompt)
|
||||||
}
|
}
|
||||||
|
|
66
runebuf.go
66
runebuf.go
|
@ -5,6 +5,11 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type runeBufferBck struct {
|
||||||
|
buf []rune
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
type RuneBuffer struct {
|
type RuneBuffer struct {
|
||||||
buf []rune
|
buf []rune
|
||||||
idx int
|
idx int
|
||||||
|
@ -12,6 +17,22 @@ type RuneBuffer struct {
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
|
||||||
cleanInScreen bool
|
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 {
|
func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
|
||||||
|
@ -98,13 +119,22 @@ func (r *RuneBuffer) MoveForward() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Delete() {
|
func (r *RuneBuffer) Erase() {
|
||||||
|
r.Refresh(func() {
|
||||||
|
r.idx = 0
|
||||||
|
r.buf = r.buf[:0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) Delete() (success bool) {
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
if r.idx == len(r.buf) {
|
if r.idx == len(r.buf) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||||
|
success = true
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) DeleteWord() {
|
func (r *RuneBuffer) DeleteWord() {
|
||||||
|
@ -126,7 +156,7 @@ func (r *RuneBuffer) DeleteWord() {
|
||||||
r.Kill()
|
r.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) MoveToPrevWord() {
|
func (r *RuneBuffer) MoveToPrevWord() (success bool) {
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
if r.idx == 0 {
|
if r.idx == 0 {
|
||||||
return
|
return
|
||||||
|
@ -135,11 +165,14 @@ func (r *RuneBuffer) MoveToPrevWord() {
|
||||||
for i := r.idx - 1; i > 0; i-- {
|
for i := r.idx - 1; i > 0; i-- {
|
||||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||||
r.idx = i
|
r.idx = i
|
||||||
|
success = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.idx = 0
|
r.idx = 0
|
||||||
|
success = true
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) KillFront() {
|
func (r *RuneBuffer) KillFront() {
|
||||||
|
@ -237,6 +270,35 @@ func (r *RuneBuffer) LineCount() int {
|
||||||
return LineCount(RunesWidth(r.buf) + r.PromptLen())
|
return LineCount(RunesWidth(r.buf) + r.PromptLen())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
|
||||||
|
r.Refresh(func() {
|
||||||
|
if reverse {
|
||||||
|
for i := r.idx - 1; i >= 0; i-- {
|
||||||
|
if r.buf[i] == ch {
|
||||||
|
r.idx = i
|
||||||
|
if prevChar {
|
||||||
|
r.idx++
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||||
|
if r.buf[i] == ch {
|
||||||
|
r.idx = i
|
||||||
|
if prevChar {
|
||||||
|
r.idx--
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) IdxLine() int {
|
func (r *RuneBuffer) IdxLine() int {
|
||||||
totalWidth := RunesWidth(r.buf[:r.idx]) + r.PromptLen()
|
totalWidth := RunesWidth(r.buf[:r.idx]) + r.PromptLen()
|
||||||
w := getWidth()
|
w := getWidth()
|
||||||
|
|
|
@ -113,6 +113,10 @@ func (t *Terminal) ioloop() {
|
||||||
expectNextChar = true
|
expectNextChar = true
|
||||||
switch r {
|
switch r {
|
||||||
case CharEsc:
|
case CharEsc:
|
||||||
|
if t.cfg.VimMode {
|
||||||
|
t.outchan <- r
|
||||||
|
break
|
||||||
|
}
|
||||||
isEscape = true
|
isEscape = true
|
||||||
case CharInterrupt, CharEnter, CharCtrlJ:
|
case CharInterrupt, CharEnter, CharCtrlJ:
|
||||||
expectNextChar = false
|
expectNextChar = false
|
||||||
|
@ -123,6 +127,10 @@ func (t *Terminal) ioloop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) Bell() {
|
||||||
|
fmt.Fprintf(t, "%c", CharBell)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) Close() error {
|
func (t *Terminal) Close() error {
|
||||||
if atomic.SwapInt64(&t.closed, 1) != 0 {
|
if atomic.SwapInt64(&t.closed, 1) != 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
4
utils.go
4
utils.go
|
@ -57,9 +57,9 @@ func escapeExKey(r rune) rune {
|
||||||
func escapeKey(r rune) rune {
|
func escapeKey(r rune) rune {
|
||||||
switch r {
|
switch r {
|
||||||
case 'b':
|
case 'b':
|
||||||
r = MetaPrev
|
r = MetaBackward
|
||||||
case 'f':
|
case 'f':
|
||||||
r = MetaNext
|
r = MetaForward
|
||||||
case 'd':
|
case 'd':
|
||||||
r = MetaDelete
|
r = MetaDelete
|
||||||
case CharTranspose:
|
case CharTranspose:
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package readline
|
||||||
|
|
||||||
|
const (
|
||||||
|
VIM_NORMAL = iota
|
||||||
|
VIM_INSERT
|
||||||
|
VIM_VISUAL
|
||||||
|
)
|
||||||
|
|
||||||
|
type opVim struct {
|
||||||
|
cfg *Config
|
||||||
|
op *Operation
|
||||||
|
vimMode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVimMode(op *Operation) *opVim {
|
||||||
|
ov := &opVim{
|
||||||
|
cfg: op.cfg,
|
||||||
|
op: op,
|
||||||
|
}
|
||||||
|
ov.SetVimMode(ov.cfg.VimMode)
|
||||||
|
return ov
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) SetVimMode(on bool) {
|
||||||
|
if o.cfg.VimMode && !on { // turn off
|
||||||
|
o.ExitVimMode()
|
||||||
|
}
|
||||||
|
o.cfg.VimMode = on
|
||||||
|
o.vimMode = VIM_INSERT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) ExitVimMode() {
|
||||||
|
o.vimMode = VIM_INSERT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) IsEnableVimMode() bool {
|
||||||
|
return o.cfg.VimMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) handleVimNormalMovement(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
|
||||||
|
case '0', '^':
|
||||||
|
rb.MoveToLineStart()
|
||||||
|
case '$':
|
||||||
|
rb.MoveToLineEnd()
|
||||||
|
case 'b', 'B':
|
||||||
|
rb.MoveToPrevWord()
|
||||||
|
case 'w', 'W', 'e', 'E':
|
||||||
|
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 '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.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 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) EnterVimInsertMode() {
|
||||||
|
o.vimMode = VIM_INSERT
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opVim) ExitVimInsertMode() {
|
||||||
|
o.vimMode = VIM_NORMAL
|
||||||
|
}
|
||||||
|
|
||||||
|
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 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch o.vimMode {
|
||||||
|
case VIM_INSERT:
|
||||||
|
return r
|
||||||
|
case VIM_VISUAL:
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
Loading…
Reference in New Issue