Skip to content

Commit 5484f3f

Browse files
committed
implement command line completion of command names
1 parent c75a332 commit 5484f3f

File tree

6 files changed

+277
-107
lines changed

6 files changed

+277
-107
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ This binary editor is influenced by the Vim editor.
5353
- `:quit`, `ZQ`, `:qall`, `:write`,
5454
`:wq`, `ZZ`, `:xit`, `:xall`, `:cquit`
5555
- Window operations
56-
- `:wincmd [nolhkjtbpKJHL]`, `<C-w>[nolhkjtbpKJHL]`
56+
- `:wincmd [nohjkltbpHJKL]`, `<C-w>[nohjkltbpHJKL]`
5757
- Cursor motions
5858
- `h`, `j`, `k`, `l`, `w`, `b`, `^`, `0`, `$`,
5959
`<C-[fb]>`, `<C-[du]>`, `<C-[ey]>`, `<C-[np]>`,

cmdline/cmdline.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func (c *Cmdline) execute() (finish bool) {
205205
defer c.saveHistory()
206206
switch c.typ {
207207
case ':':
208-
cmd, r, bang, _, arg, err := parse(string(c.cmdline))
208+
cmd, r, bang, _, _, arg, err := parse(string(c.cmdline))
209209
if err != nil {
210210
c.eventCh <- event.Event{Type: event.Error, Error: err}
211211
} else if cmd.name != "" {

cmdline/command.go

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,54 @@ import "github.com/itchyny/bed/event"
44

55
type command struct {
66
name string
7+
fullname string
78
eventType event.Type
9+
rangeType rangeType
10+
}
11+
12+
type rangeType int
13+
14+
const (
15+
rangeNone rangeType = 1 << iota
16+
rangeCount
17+
rangeBoth
18+
)
19+
20+
func (rt rangeType) allows(r *event.Range) bool {
21+
switch {
22+
case r == nil:
23+
return rt&rangeNone != 0
24+
case r.To == nil:
25+
return rt&rangeCount != 0
26+
default:
27+
return rt&rangeBoth != 0
28+
}
829
}
930

1031
var commands = []command{
11-
{"e[dit]", event.Edit},
12-
{"ene[w]", event.Enew},
13-
{"new", event.New},
14-
{"vne[w]", event.Vnew},
15-
{"on[ly]", event.Only},
16-
{"winc[md]", event.Wincmd},
17-
18-
{"go[to]", event.CursorGoto},
19-
{"%", event.CursorGoto},
20-
21-
{"u[ndo]", event.Undo},
22-
{"red[o]", event.Redo},
23-
24-
{"pw[d]", event.Pwd},
25-
{"cd", event.Chdir},
26-
{"chd[ir]", event.Chdir},
27-
{"exi[t]", event.Quit},
28-
{"q[uit]", event.Quit},
29-
{"qa[ll]", event.QuitAll},
30-
{"quita[ll]", event.QuitAll},
31-
{"cq[uit]", event.QuitErr},
32-
{"w[rite]", event.Write},
33-
{"wq", event.WriteQuit},
34-
{"x[it]", event.WriteQuit},
35-
{"xa[ll]", event.WriteQuit},
32+
{"e[dit]", "edit", event.Edit, rangeNone},
33+
{"ene[w]", "enew", event.Enew, rangeNone},
34+
{"new", "new", event.New, rangeNone},
35+
{"vne[w]", "vnew", event.Vnew, rangeNone},
36+
{"on[ly]", "only", event.Only, rangeNone},
37+
{"winc[md]", "wincmd", event.Wincmd, rangeNone},
38+
39+
{"go[to]", "goto", event.CursorGoto, rangeCount},
40+
{"%", "%", event.CursorGoto, rangeCount},
41+
42+
{"u[ndo]", "undo", event.Undo, rangeNone},
43+
{"red[o]", "redo", event.Redo, rangeNone},
44+
45+
{"pw[d]", "pwd", event.Pwd, rangeNone},
46+
{"cd", "cd", event.Chdir, rangeNone},
47+
{"chd[ir]", "chdir", event.Chdir, rangeNone},
48+
{"exi[t]", "exit", event.Quit, rangeNone},
49+
{"q[uit]", "quit", event.Quit, rangeNone},
50+
{"qa[ll]", "qall", event.QuitAll, rangeNone},
51+
{"quita[ll]", "quitall", event.QuitAll, rangeNone},
52+
{"cq[uit]", "cquit", event.QuitErr, rangeNone},
53+
{"w[rite]", "write", event.Write, rangeNone | rangeBoth},
54+
{"wq", "wq", event.WriteQuit, rangeNone | rangeBoth},
55+
{"x[it]", "xit", event.WriteQuit, rangeNone | rangeBoth},
56+
{"xa[ll]", "xall", event.WriteQuit, rangeNone | rangeBoth},
3657
}

cmdline/completor.go

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import (
55
"path/filepath"
66
"slices"
77
"strings"
8+
"unicode"
9+
"unicode/utf8"
810

911
"github.com/itchyny/bed/event"
1012
)
1113

1214
type completor struct {
1315
fs fs
1416
env env
17+
command bool
1518
target string
1619
arg string
1720
results []string
@@ -22,24 +25,24 @@ func newCompletor(fs fs, env env) *completor {
2225
return &completor{fs: fs, env: env}
2326
}
2427

25-
func (c *completor) clear() {
26-
c.target = ""
27-
c.arg = ""
28-
c.results = nil
29-
c.index = 0
30-
}
31-
3228
func (c *completor) complete(cmdline string, forward bool) string {
33-
cmd, _, _, prefix, arg, _ := parse(cmdline)
29+
cmd, r, _, name, prefix, arg, _ := parse(cmdline)
30+
if name == "" || c.command ||
31+
!hasSuffixFunc(prefix, unicode.IsSpace) && cmd.fullname != name {
32+
cmdline = c.completeCommand(cmdline, name, prefix, r, forward)
33+
if c.results != nil {
34+
return cmdline
35+
}
36+
prefix = cmdline
37+
}
3438
switch cmd.eventType {
3539
case event.Edit, event.New, event.Vnew, event.Write:
36-
return c.completeFilepaths(cmdline, prefix, arg, forward, false)
40+
return c.completeFilepath(cmdline, prefix, arg, forward, false)
3741
case event.Chdir:
38-
return c.completeFilepaths(cmdline, prefix, arg, forward, true)
42+
return c.completeFilepath(cmdline, prefix, arg, forward, true)
3943
case event.Wincmd:
4044
return c.completeWincmd(cmdline, prefix, arg, forward)
4145
default:
42-
c.clear()
4346
return cmdline
4447
}
4548
}
@@ -53,35 +56,45 @@ func (c *completor) completeNext(prefix string, forward bool) string {
5356
if c.index < 0 {
5457
return c.target
5558
}
59+
if len(c.results) == 1 {
60+
defer c.clear()
61+
}
5662
return prefix + c.arg + c.results[c.index]
5763
}
5864

59-
func (c *completor) completeFilepaths(
60-
cmdline, prefix, arg string, forward, dirOnly bool,
65+
func (c *completor) completeCommand(
66+
cmdline, name, prefix string, r *event.Range, forward bool,
6167
) string {
62-
if !strings.HasSuffix(prefix, " ") {
63-
prefix += " "
68+
prefix = prefix[:len(prefix)-len(name)]
69+
if c.results == nil {
70+
c.command, c.target, c.index = true, cmdline, -1
71+
c.arg, c.results = "", c.listCommandNames(name, r)
6472
}
65-
if len(c.results) > 0 {
66-
return c.completeNext(prefix, forward)
73+
return c.completeNext(prefix, forward)
74+
}
75+
76+
func (*completor) listCommandNames(name string, r *event.Range) []string {
77+
var targets []string
78+
for _, cmd := range commands {
79+
if strings.HasPrefix(cmd.fullname, name) && cmd.rangeType.allows(r) {
80+
targets = append(targets, cmd.fullname)
81+
}
6782
}
68-
c.target = cmdline
69-
c.index = 0
70-
c.arg, c.results = c.listFileNames(arg, dirOnly)
71-
if len(c.results) == 1 {
72-
cmdline := prefix + c.arg + c.results[0]
73-
c.results = nil
74-
return cmdline
83+
slices.Sort(targets)
84+
return targets
85+
}
86+
87+
func (c *completor) completeFilepath(
88+
cmdline, prefix, arg string, forward, dirOnly bool,
89+
) string {
90+
if !hasSuffixFunc(prefix, unicode.IsSpace) {
91+
prefix += " "
7592
}
76-
if len(c.results) > 1 {
77-
if forward {
78-
c.index = 0
79-
return prefix + c.arg + c.results[0]
80-
}
81-
c.index = len(c.results) - 1
82-
return prefix + c.arg + c.results[len(c.results)-1]
93+
if c.results == nil {
94+
c.command, c.target, c.index = false, cmdline, -1
95+
c.arg, c.results = c.listFileNames(arg, dirOnly)
8396
}
84-
return cmdline
97+
return c.completeNext(prefix, forward)
8598
}
8699

87100
const separator = string(filepath.Separator)
@@ -224,17 +237,25 @@ func (c *completor) expandPath(path string) (string, func(string) string) {
224237
}
225238

226239
func (c *completor) completeWincmd(cmdline, prefix, arg string, forward bool) string {
227-
if !strings.HasSuffix(prefix, " ") {
240+
if !hasSuffixFunc(prefix, unicode.IsSpace) {
228241
prefix += " "
229242
}
230-
if len(c.results) > 0 {
231-
return c.completeNext(prefix, forward)
232-
}
233-
if len(arg) > 0 {
234-
return cmdline
243+
if c.results == nil {
244+
if len(arg) > 0 {
245+
return cmdline
246+
}
247+
c.command, c.target, c.arg, c.index = false, cmdline, "", -1
248+
c.results = strings.Split("nohjkltbpHJKL", "")
235249
}
236-
c.target = cmdline
237-
c.results = []string{"n", "h", "l", "k", "j", "H", "L", "K", "J", "t", "b", "p"}
238-
c.index = -1
239-
return cmdline
250+
return c.completeNext(prefix, forward)
251+
}
252+
253+
func (c *completor) clear() {
254+
c.command, c.target, c.arg = false, "", ""
255+
c.results, c.index = nil, 0
256+
}
257+
258+
func hasSuffixFunc(s string, f func(rune) bool) bool {
259+
r, _ := utf8.DecodeLastRuneInString(s)
260+
return f(r)
240261
}

0 commit comments

Comments
 (0)