mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-01 08:42:55 +08:00
Fixup for #1549.
This commit is contained in:
parent
fa704d6ac6
commit
f043bd33fb
|
@ -223,56 +223,55 @@ func (w *codeArea) expandSimpleAbbr() {
|
|||
}
|
||||
})
|
||||
if len(abbr) > 0 {
|
||||
c := &w.State.Buffer
|
||||
*c = CodeBuffer{
|
||||
Content: c.Content[:c.Dot-len(abbr)] + full + c.Content[c.Dot:],
|
||||
Dot: c.Dot - len(abbr) + len(full),
|
||||
buf := &w.State.Buffer
|
||||
*buf = CodeBuffer{
|
||||
Content: buf.Content[:buf.Dot-len(abbr)] + full + buf.Content[buf.Dot:],
|
||||
Dot: buf.Dot - len(abbr) + len(full),
|
||||
}
|
||||
w.resetInserts()
|
||||
}
|
||||
}
|
||||
|
||||
// Try to expand a command abbreviation. This function assumes the state mutex is held.
|
||||
//
|
||||
// We use a regex rather than parse.Parse() because dealing with the the latter requires a lot of
|
||||
// code. A simple regex is far simpler and good enough for this use case. The regex essentially
|
||||
// matches commands at the start of the line (with potential leading whitespace) and similarly after
|
||||
// the opening brace of a lambda or pipeline char. There are two corner cases it doesn't handle:
|
||||
//
|
||||
// 1) It doesn't handle a caret followed by a newline if the token on the last line would otherwise
|
||||
// be treated as a command given what preceded the caret.
|
||||
//
|
||||
// 2) It only handles bareword commands.
|
||||
var commandRegex = regexp.MustCompile(`(?:^|\||;|{)\s*([\p{L}\p{Nd}!%+,-./:@\_]+)\s\z`)
|
||||
var commandRegex = regexp.MustCompile(`(?:^|[^^]\n|\||;|{\s|\()\s*([\p{L}\p{M}\p{N}!%+,\-./:@\\_<>*]+)(\s)$`)
|
||||
|
||||
// Tries to expand a command abbreviation. This function assumes the state mutex
|
||||
// is held.
|
||||
//
|
||||
// We use a regex rather than parse.Parse() because dealing with the the latter
|
||||
// requires a lot of code. A simple regex is far simpler and good enough for
|
||||
// this use case. The regex essentially matches commands at the start of the
|
||||
// line (with potential leading whitespace) and similarly after the opening
|
||||
// brace of a lambda or pipeline char.
|
||||
//
|
||||
// This only handles bareword commands.
|
||||
func (w *codeArea) expandCommandAbbr() {
|
||||
code := &w.State.Buffer
|
||||
if code.Dot < len(code.Content) {
|
||||
buf := &w.State.Buffer
|
||||
if buf.Dot < len(buf.Content) {
|
||||
// Command abbreviations are only expanded when inserting at the end of the buffer.
|
||||
return
|
||||
}
|
||||
|
||||
// See if there is something that looks like a bareword at the end of the buffer.
|
||||
matches := commandRegex.FindSubmatch([]byte(code.Content))
|
||||
matches := commandRegex.FindStringSubmatch(buf.Content)
|
||||
if len(matches) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Find an abbreviation matching the command.
|
||||
command := string(matches[1])
|
||||
command, whitespace := matches[1], matches[2]
|
||||
var expansion string
|
||||
w.CommandAbbreviations(func(a, e string) {
|
||||
if a == command {
|
||||
expansion = e
|
||||
}
|
||||
})
|
||||
if len(expansion) == 0 {
|
||||
if expansion == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// We found a matching abbreviation -- replace it with its expansion.
|
||||
newContent := code.Content[:code.Dot-len(command)-1] + expansion + " "
|
||||
*code = CodeBuffer{
|
||||
newContent := buf.Content[:buf.Dot-len(command)-1] + expansion + whitespace
|
||||
*buf = CodeBuffer{
|
||||
Content: newContent,
|
||||
Dot: len(newContent),
|
||||
}
|
||||
|
@ -281,8 +280,8 @@ func (w *codeArea) expandCommandAbbr() {
|
|||
|
||||
// Try to expand a small word abbreviation. This function assumes the state mutex is held.
|
||||
func (w *codeArea) expandSmallWordAbbr(trigger rune, categorizer func(rune) int) {
|
||||
c := &w.State.Buffer
|
||||
if c.Dot < len(c.Content) {
|
||||
buf := &w.State.Buffer
|
||||
if buf.Dot < len(buf.Content) {
|
||||
// Word abbreviations are only expanded when inserting at the end of the buffer.
|
||||
return
|
||||
}
|
||||
|
@ -314,8 +313,8 @@ func (w *codeArea) expandSmallWordAbbr(trigger rune, categorizer func(rune) int)
|
|||
}
|
||||
// Verify the rune preceding the abbreviation, if any, creates a word
|
||||
// boundary.
|
||||
if len(c.Content) > len(a)+triggerLen {
|
||||
r1, _ := utf8.DecodeLastRuneInString(c.Content[:len(c.Content)-len(a)-triggerLen])
|
||||
if len(buf.Content) > len(a)+triggerLen {
|
||||
r1, _ := utf8.DecodeLastRuneInString(buf.Content[:len(buf.Content)-len(a)-triggerLen])
|
||||
r2, _ := utf8.DecodeRuneInString(a)
|
||||
if categorizer(r1) == categorizer(r2) {
|
||||
return
|
||||
|
@ -324,9 +323,9 @@ func (w *codeArea) expandSmallWordAbbr(trigger rune, categorizer func(rune) int)
|
|||
abbr, full = a, f
|
||||
})
|
||||
if len(abbr) > 0 {
|
||||
*c = CodeBuffer{
|
||||
Content: c.Content[:c.Dot-len(abbr)-triggerLen] + full + string(trigger),
|
||||
Dot: c.Dot - len(abbr) + len(full),
|
||||
*buf = CodeBuffer{
|
||||
Content: buf.Content[:buf.Dot-len(abbr)-triggerLen] + full + string(trigger),
|
||||
Dot: buf.Dot - len(abbr) + len(full),
|
||||
}
|
||||
w.resetInserts()
|
||||
}
|
||||
|
@ -383,7 +382,7 @@ func (w *codeArea) handleKeyEvent(key ui.Key) bool {
|
|||
w.State.Buffer.InsertAtDot(s)
|
||||
w.inserts += s
|
||||
w.lastCodeBuffer = w.State.Buffer
|
||||
if key.Rune == ' ' {
|
||||
if parse.IsWhitespace(key.Rune) {
|
||||
w.expandCommandAbbr()
|
||||
}
|
||||
w.expandSimpleAbbr()
|
||||
|
|
|
@ -385,6 +385,17 @@ var codeAreaHandleTests = []handleTest{
|
|||
Events: []term.Event{term.K('x'), term.K('|'), term.K('e'), term.K('h'), term.K(' ')},
|
||||
WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "x|echo hello ", Dot: 13}},
|
||||
},
|
||||
{
|
||||
Name: "command abbreviation expansion at start of second line",
|
||||
Given: NewCodeArea(CodeAreaSpec{
|
||||
CommandAbbreviations: func(f func(abbr, full string)) {
|
||||
f("eh", "echo hello")
|
||||
},
|
||||
State: CodeAreaState{Buffer: CodeBuffer{Content: "echo\n", Dot: 5}},
|
||||
}),
|
||||
Events: []term.Event{term.K('e'), term.K('h'), term.K(' ')},
|
||||
WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "echo\necho hello ", Dot: 16}},
|
||||
},
|
||||
{
|
||||
Name: "no command abbreviation expansion when not in command position",
|
||||
Given: NewCodeArea(CodeAreaSpec{
|
||||
|
|
|
@ -35,11 +35,12 @@ import (
|
|||
//
|
||||
// A map from command abbreviations to their expansions.
|
||||
//
|
||||
// A command abbreviation is replaced by its expansion when it is typed in full and consecutively at
|
||||
// the end of the line, without being interrupted by the use of other editing functionalities, such
|
||||
// as cursor movements. It is only expanded when the abbreviation is seen in the command position
|
||||
// followed by a space. This is similar to Fish shell abbreviations but does not trigger the
|
||||
// expansion when pressing Enter -- you must type a space first.
|
||||
// A command abbreviation is replaced by its expansion when seen in the command
|
||||
// position followed by a [whitespace](language.html#whitespace). This is
|
||||
// similar to the Fish shell's
|
||||
// [abbreviations](https://fishshell.com/docs/current/cmds/abbr.html), but does
|
||||
// not trigger when executing a command with Enter -- you must type a space
|
||||
// first.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
|
@ -52,8 +53,7 @@ import (
|
|||
|
||||
//elvdoc:var small-word-abbr
|
||||
//
|
||||
// A map from small-word abbreviations to their expansions. Note that you probably want to create
|
||||
// [command abbreviation](#command-abbr) rather than a small-word abbreviation.
|
||||
// A map from small-word abbreviations to their expansions.
|
||||
//
|
||||
// A small-word abbreviation is replaced by its expansion after it is typed in
|
||||
// full and consecutively, and followed by another character (the *trigger*
|
||||
|
|
Loading…
Reference in New Issue
Block a user