mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-15 11:57:55 +08:00
cliedit: Implement completion:smart-start.
This commit is contained in:
parent
572c09d81f
commit
dc67d670b8
|
@ -6,10 +6,12 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/elves/elvish/cli"
|
||||
"github.com/elves/elvish/cli/addons/completion"
|
||||
"github.com/elves/elvish/cli/el"
|
||||
"github.com/elves/elvish/cli/el/codearea"
|
||||
"github.com/elves/elvish/cliedit/complete"
|
||||
"github.com/elves/elvish/eval"
|
||||
"github.com/elves/elvish/eval/vals"
|
||||
|
@ -135,7 +137,12 @@ func complexCandidate(opts complexCandidateOpts, stem string) complexItem {
|
|||
//
|
||||
// Start the completion mode.
|
||||
|
||||
func completionStart(app cli.App, binding el.Handler, cfg complete.Config) {
|
||||
//elvdoc:fn completion:smart-start
|
||||
//
|
||||
// Starts the completion mode. However, if all the candidates share a non-empty
|
||||
// prefix and that prefix starts with the seed, inserts the prefix instead.
|
||||
|
||||
func completionStart(app cli.App, binding el.Handler, cfg complete.Config, smart bool) {
|
||||
buf := app.CodeArea().CopyState().Buffer
|
||||
result, err := complete.Complete(
|
||||
complete.CodeBuffer{Content: buf.Content, Dot: buf.Dot}, cfg)
|
||||
|
@ -143,6 +150,35 @@ func completionStart(app cli.App, binding el.Handler, cfg complete.Config) {
|
|||
app.Notify(err.Error())
|
||||
return
|
||||
}
|
||||
if smart {
|
||||
prefix := ""
|
||||
for i, item := range result.Items {
|
||||
if i == 0 {
|
||||
prefix = item.ToInsert
|
||||
continue
|
||||
}
|
||||
prefix = commonPrefix(prefix, item.ToInsert)
|
||||
if prefix == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if prefix != "" {
|
||||
insertedPrefix := false
|
||||
app.CodeArea().MutateState(func(s *codearea.State) {
|
||||
rep := s.Buffer.Content[result.Replace.From:result.Replace.To]
|
||||
if len(prefix) > len(rep) && strings.HasPrefix(prefix, rep) {
|
||||
s.Pending = codearea.Pending{
|
||||
Content: prefix,
|
||||
From: result.Replace.From, To: result.Replace.To}
|
||||
s.ApplyPending()
|
||||
insertedPrefix = true
|
||||
}
|
||||
})
|
||||
if insertedPrefix {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completion.Start(app, completion.Config{
|
||||
Name: result.Name, Replace: result.Replace, Items: result.Items,
|
||||
Binding: binding})
|
||||
|
@ -157,7 +193,7 @@ func initCompletion(app cli.App, ev *eval.Evaler, ns eval.Ns) {
|
|||
binding := newMapBinding(app, ev, bindingVar)
|
||||
matcherMapVar := newMapVar(vals.EmptyMap)
|
||||
argGeneratorMapVar := newMapVar(vals.EmptyMap)
|
||||
getCfg := func() complete.Config {
|
||||
cfg := func() complete.Config {
|
||||
return complete.Config{
|
||||
PureEvaler: pureEvaler{ev},
|
||||
Filterer: adaptMatcherMap(
|
||||
|
@ -167,7 +203,7 @@ func initCompletion(app cli.App, ev *eval.Evaler, ns eval.Ns) {
|
|||
}
|
||||
}
|
||||
generateForSudo := func(args []string) ([]complete.RawItem, error) {
|
||||
return complete.GenerateForSudo(getCfg(), args)
|
||||
return complete.GenerateForSudo(cfg(), args)
|
||||
}
|
||||
ns.AddGoFns("<edit>", map[string]interface{}{
|
||||
"complete-filename": wrapArgGenerator(complete.GenerateFileNames),
|
||||
|
@ -184,15 +220,16 @@ func initCompletion(app cli.App, ev *eval.Evaler, ns eval.Ns) {
|
|||
"binding": bindingVar,
|
||||
"matcher": matcherMapVar,
|
||||
}.AddGoFns("<edit:completion>", map[string]interface{}{
|
||||
"accept": func() { listingAccept(app) },
|
||||
"start": func() { completionStart(app, binding, getCfg()) },
|
||||
"close": func() { completion.Close(app) },
|
||||
"up": func() { listingUp(app) },
|
||||
"down": func() { listingDown(app) },
|
||||
"up-cycle": func() { listingUpCycle(app) },
|
||||
"down-cycle": func() { listingDownCycle(app) },
|
||||
"left": func() { listingLeft(app) },
|
||||
"right": func() { listingRight(app) },
|
||||
"accept": func() { listingAccept(app) },
|
||||
"smart-start": func() { completionStart(app, binding, cfg(), true) },
|
||||
"start": func() { completionStart(app, binding, cfg(), false) },
|
||||
"close": func() { completion.Close(app) },
|
||||
"up": func() { listingUp(app) },
|
||||
"down": func() { listingDown(app) },
|
||||
"up-cycle": func() { listingUpCycle(app) },
|
||||
"down-cycle": func() { listingDownCycle(app) },
|
||||
"left": func() { listingLeft(app) },
|
||||
"right": func() { listingRight(app) },
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -262,6 +299,20 @@ func wrapArgGenerator(gen complete.ArgGenerator) wrappedArgGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
func commonPrefix(s1, s2 string) string {
|
||||
for i, r := range s1 {
|
||||
if s2 == "" {
|
||||
break
|
||||
}
|
||||
r2, n2 := utf8.DecodeRuneInString(s2)
|
||||
if r2 != r {
|
||||
return s1[:i]
|
||||
}
|
||||
s2 = s2[n2:]
|
||||
}
|
||||
return s1
|
||||
}
|
||||
|
||||
// The type for a native Go matcher. This is not equivalent to the Elvish
|
||||
// counterpart, which streams input and output. This is because we can actually
|
||||
// afford calling a Go function for each item, so omitting the streaming
|
||||
|
|
|
@ -29,6 +29,37 @@ func TestCompletionAddon(t *testing.T) {
|
|||
f.TTYCtrl.TestBuffer(t, wantBuf)
|
||||
}
|
||||
|
||||
func TestCompletionAddon_CompletesLongestCommonPrefix(t *testing.T) {
|
||||
f := setup()
|
||||
defer f.Cleanup()
|
||||
util.ApplyDir(util.Dir{"foo1": "", "foo2": "", "foo": "", "fox": ""})
|
||||
|
||||
feedInput(f.TTYCtrl, "echo \t")
|
||||
wantBuf := bb().
|
||||
WriteMarkedLines(
|
||||
"~> echo fo", styles,
|
||||
" gggg").
|
||||
SetDotHere().
|
||||
Buffer()
|
||||
f.TTYCtrl.TestBuffer(t, wantBuf)
|
||||
|
||||
feedInput(f.TTYCtrl, "\t")
|
||||
wantBuf = bb().
|
||||
WriteMarkedLines(
|
||||
"~> echo foo ", styles,
|
||||
" gggg ----",
|
||||
"COMPLETING argument ", styles,
|
||||
"mmmmmmmmmmmmmmmmmmm ").
|
||||
SetDotHere().
|
||||
Newline().
|
||||
WriteMarkedLines(
|
||||
"foo foo1 foo2 fox", styles,
|
||||
"### ",
|
||||
).
|
||||
Buffer()
|
||||
f.TTYCtrl.TestBuffer(t, wantBuf)
|
||||
}
|
||||
|
||||
func TestCompleteFilename(t *testing.T) {
|
||||
f := setup()
|
||||
defer f.Cleanup()
|
||||
|
@ -83,21 +114,21 @@ func TestCompletionArgCompleter_ArgsAndValueOutput(t *testing.T) {
|
|||
`fn foo { }`,
|
||||
`edit:completion:arg-completer[foo] = [@args]{
|
||||
foo-args = $args
|
||||
put val1
|
||||
edit:complex-candidate val2 &display-suffix=_
|
||||
put 1val
|
||||
edit:complex-candidate 2val &display-suffix=_
|
||||
}`)
|
||||
|
||||
feedInput(f.TTYCtrl, "foo foo1 foo2 \t")
|
||||
wantBuf := bb().
|
||||
WriteMarkedLines(
|
||||
"~> foo foo1 foo2 val1", styles,
|
||||
"~> foo foo1 foo2 1val", styles,
|
||||
" ggg ----",
|
||||
"COMPLETING argument ", styles,
|
||||
"mmmmmmmmmmmmmmmmmmm ").
|
||||
SetDotHere().
|
||||
Newline().
|
||||
WriteMarkedLines(
|
||||
"val1 val2_", styles,
|
||||
"1val 2val_", styles,
|
||||
"#### ",
|
||||
).
|
||||
Buffer()
|
||||
|
@ -113,21 +144,21 @@ func TestCompletionArgCompleter_BytesOutput(t *testing.T) {
|
|||
evals(f.Evaler,
|
||||
`fn foo { }`,
|
||||
`edit:completion:arg-completer[foo] = [@args]{
|
||||
echo val1
|
||||
echo val2
|
||||
echo 1val
|
||||
echo 2val
|
||||
}`)
|
||||
|
||||
feedInput(f.TTYCtrl, "foo foo1 foo2 \t")
|
||||
wantBuf := bb().
|
||||
WriteMarkedLines(
|
||||
"~> foo foo1 foo2 val1", styles,
|
||||
"~> foo foo1 foo2 1val", styles,
|
||||
" ggg ----",
|
||||
"COMPLETING argument ", styles,
|
||||
"mmmmmmmmmmmmmmmmmmm ").
|
||||
SetDotHere().
|
||||
Newline().
|
||||
WriteMarkedLines(
|
||||
"val1 val2", styles,
|
||||
"1val 2val", styles,
|
||||
"#### ",
|
||||
).
|
||||
Buffer()
|
||||
|
|
|
@ -28,7 +28,7 @@ insert:binding = (binding-table [
|
|||
&Ctrl-R= $histlist:start~
|
||||
&Ctrl-L= $location:start~
|
||||
&Ctrl-N= $navigation:start~
|
||||
&Tab= $completion:start~
|
||||
&Tab= $completion:smart-start~
|
||||
&Up= $history:start~
|
||||
|
||||
&Enter= $smart-enter~
|
||||
|
|
Loading…
Reference in New Issue
Block a user