2016-02-27 22:20:11 +08:00
|
|
|
package edit
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/elves/elvish/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Builtins related to insert and command mode.
|
|
|
|
|
2016-02-28 04:26:59 +08:00
|
|
|
type insert struct{}
|
|
|
|
|
|
|
|
func (*insert) Mode() ModeType {
|
|
|
|
return modeInsert
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert mode is the default mode and has an empty mode.
|
|
|
|
func (*insert) ModeLine(int) *buffer {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type command struct{}
|
|
|
|
|
|
|
|
func (*command) Mode() ModeType {
|
|
|
|
return modeCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*command) ModeLine(width int) *buffer {
|
|
|
|
return makeModeLine(" COMMAND ", width)
|
|
|
|
}
|
|
|
|
|
2016-02-27 22:20:11 +08:00
|
|
|
func startInsert(ed *Editor) {
|
2016-02-28 04:26:59 +08:00
|
|
|
ed.mode = &ed.insert
|
2016-02-27 22:20:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func startCommand(ed *Editor) {
|
2016-02-28 04:26:59 +08:00
|
|
|
ed.mode = &ed.command
|
2016-02-27 22:20:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func killLineLeft(ed *Editor) {
|
|
|
|
sol := util.FindLastSOL(ed.line[:ed.dot])
|
|
|
|
ed.line = ed.line[:sol] + ed.line[ed.dot:]
|
|
|
|
ed.dot = sol
|
|
|
|
}
|
|
|
|
|
|
|
|
func killLineRight(ed *Editor) {
|
|
|
|
eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
|
|
|
|
ed.line = ed.line[:ed.dot] + ed.line[eol:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(xiaq): A word is now defined as a series of non-whitespace chars.
|
|
|
|
func killWordLeft(ed *Editor) {
|
|
|
|
if ed.dot == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
space := strings.LastIndexFunc(
|
|
|
|
strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
|
|
|
|
unicode.IsSpace) + 1
|
|
|
|
ed.line = ed.line[:space] + ed.line[ed.dot:]
|
|
|
|
ed.dot = space
|
|
|
|
}
|
|
|
|
|
|
|
|
func killRuneLeft(ed *Editor) {
|
|
|
|
if ed.dot > 0 {
|
|
|
|
_, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
|
|
|
|
ed.line = ed.line[:ed.dot-w] + ed.line[ed.dot:]
|
|
|
|
ed.dot -= w
|
|
|
|
} else {
|
|
|
|
ed.flash()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func killRuneRight(ed *Editor) {
|
|
|
|
if ed.dot < len(ed.line) {
|
|
|
|
_, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
|
|
|
|
ed.line = ed.line[:ed.dot] + ed.line[ed.dot+w:]
|
|
|
|
} else {
|
|
|
|
ed.flash()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotLeft(ed *Editor) {
|
|
|
|
_, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
|
|
|
|
ed.dot -= w
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotRight(ed *Editor) {
|
|
|
|
_, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
|
|
|
|
ed.dot += w
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotLeftWord(ed *Editor) {
|
|
|
|
if ed.dot == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
space := strings.LastIndexFunc(
|
|
|
|
strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
|
|
|
|
unicode.IsSpace) + 1
|
|
|
|
ed.dot = space
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotRightWord(ed *Editor) {
|
|
|
|
// Move to first space
|
|
|
|
p := strings.IndexFunc(ed.line[ed.dot:], unicode.IsSpace)
|
|
|
|
if p == -1 {
|
|
|
|
ed.dot = len(ed.line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ed.dot += p
|
|
|
|
// Move to first nonspace
|
|
|
|
p = strings.IndexFunc(ed.line[ed.dot:], notSpace)
|
|
|
|
if p == -1 {
|
|
|
|
ed.dot = len(ed.line)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ed.dot += p
|
|
|
|
}
|
|
|
|
|
|
|
|
func notSpace(r rune) bool {
|
|
|
|
return !unicode.IsSpace(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotSOL(ed *Editor) {
|
|
|
|
sol := util.FindLastSOL(ed.line[:ed.dot])
|
|
|
|
ed.dot = sol
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotEOL(ed *Editor) {
|
|
|
|
eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
|
|
|
|
ed.dot = eol
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotUp(ed *Editor) {
|
|
|
|
sol := util.FindLastSOL(ed.line[:ed.dot])
|
|
|
|
if sol == 0 {
|
|
|
|
ed.flash()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
prevEOL := sol - 1
|
|
|
|
prevSOL := util.FindLastSOL(ed.line[:prevEOL])
|
|
|
|
width := WcWidths(ed.line[sol:ed.dot])
|
|
|
|
ed.dot = prevSOL + len(TrimWcWidth(ed.line[prevSOL:prevEOL], width))
|
|
|
|
}
|
|
|
|
|
|
|
|
func moveDotDown(ed *Editor) {
|
|
|
|
eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
|
|
|
|
if eol == len(ed.line) {
|
|
|
|
ed.flash()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
nextSOL := eol + 1
|
|
|
|
nextEOL := util.FindFirstEOL(ed.line[nextSOL:]) + nextSOL
|
|
|
|
sol := util.FindLastSOL(ed.line[:ed.dot])
|
|
|
|
width := WcWidths(ed.line[sol:ed.dot])
|
|
|
|
ed.dot = nextSOL + len(TrimWcWidth(ed.line[nextSOL:nextEOL], width))
|
|
|
|
}
|
|
|
|
|
|
|
|
func insertLastWord(ed *Editor) {
|
|
|
|
_, lastLine, err := ed.store.LastCmd(-1, "", true)
|
|
|
|
if err == nil {
|
|
|
|
ed.insertAtDot(lastWord(lastLine))
|
|
|
|
} else {
|
|
|
|
ed.addTip("db error: %s", err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func lastWord(s string) string {
|
|
|
|
s = strings.TrimRightFunc(s, unicode.IsSpace)
|
|
|
|
i := strings.LastIndexFunc(s, unicode.IsSpace) + 1
|
|
|
|
return s[i:]
|
|
|
|
}
|
|
|
|
|
|
|
|
func insertKey(ed *Editor) {
|
|
|
|
k := ed.lastKey
|
|
|
|
ed.insertAtDot(string(k.Rune))
|
|
|
|
}
|
|
|
|
|
|
|
|
func returnLine(ed *Editor) {
|
|
|
|
ed.nextAction = action{exitReadLine, LineRead{Line: ed.line}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func smartEnter(ed *Editor) {
|
|
|
|
if ed.parseErrorAtEnd {
|
|
|
|
// There is a parsing error at the end. Insert a newline and copy
|
|
|
|
// indents from previous line.
|
|
|
|
indent := findLastIndent(ed.line[:ed.dot])
|
|
|
|
ed.insertAtDot("\n" + indent)
|
|
|
|
} else {
|
|
|
|
returnLine(ed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func findLastIndent(s string) string {
|
|
|
|
line := s[util.FindLastSOL(s):]
|
|
|
|
trimmed := strings.TrimLeft(line, " \t")
|
|
|
|
return line[:len(line)-len(trimmed)]
|
|
|
|
}
|
|
|
|
|
|
|
|
func returnEOF(ed *Editor) {
|
|
|
|
if len(ed.line) == 0 {
|
|
|
|
ed.nextAction = action{exitReadLine, LineRead{EOF: true}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultInsert(ed *Editor) {
|
|
|
|
k := ed.lastKey
|
2016-02-28 02:40:28 +08:00
|
|
|
if likeChar(k) {
|
2016-02-27 22:20:11 +08:00
|
|
|
insertKey(ed)
|
|
|
|
} else {
|
2016-02-27 22:25:59 +08:00
|
|
|
ed.notify("Unbound: %s", k)
|
2016-02-27 22:20:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultCommand(ed *Editor) {
|
|
|
|
k := ed.lastKey
|
2016-02-27 22:25:59 +08:00
|
|
|
ed.notify("Unbound: %s", k)
|
2016-02-27 22:20:11 +08:00
|
|
|
}
|
2016-02-28 02:40:28 +08:00
|
|
|
|
|
|
|
// likeChar returns if a key looks like a character meant to be input (as
|
|
|
|
// opposed to a function key).
|
|
|
|
func likeChar(k Key) bool {
|
|
|
|
return k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune)
|
|
|
|
}
|