commit cdb68f6
Hubert Hirtz
·
2020-08-19 19:53:01 +0000 UTC
parent 7a1312b
ui: Autocompletion of nicknames
3 files changed,
+126,
-7
M
app.go
+54,
-0
1@@ -34,6 +34,9 @@ func NewApp(cfg Config) (app *App, err error) {
2
3 app.win, err = ui.New(ui.Config{
4 NickColWidth: cfg.NickColWidth,
5+ AutoComplete: func(cursorIdx int, text []rune) []ui.Completion {
6+ return app.completions(cursorIdx, text)
7+ },
8 })
9 if err != nil {
10 return
11@@ -229,6 +232,11 @@ func (app *App) handleUIEvent(ev tcell.Event) {
12 if ok {
13 app.typing()
14 }
15+ case tcell.KeyTab:
16+ ok := app.win.InputAutoComplete()
17+ if ok {
18+ app.typing()
19+ }
20 case tcell.KeyCR, tcell.KeyLF:
21 buffer := app.win.CurrentBuffer()
22 input := app.win.InputEnter()
23@@ -404,3 +412,49 @@ func (app *App) handleInput(buffer, content string) {
24 }
25 }
26 }
27+
28+func (app *App) completions(cursorIdx int, text []rune) []ui.Completion {
29+ var cs []ui.Completion
30+
31+ if len(text) == 0 {
32+ return cs
33+ }
34+
35+ var start int
36+ for start = cursorIdx - 1; 0 <= start; start-- {
37+ if text[start] == ' ' {
38+ break
39+ }
40+ }
41+ start++
42+ word := string(text[start:cursorIdx])
43+ wordCf := strings.ToLower(word)
44+ for _, name := range app.s.Names(app.win.CurrentBuffer()) {
45+ if strings.HasPrefix(strings.ToLower(name.Nick), wordCf) {
46+ nickComp := []rune(name.Nick)
47+ if start == 0 {
48+ nickComp = append(nickComp, ':')
49+ }
50+ nickComp = append(nickComp, ' ')
51+ c := make([]rune, len(text)+len(nickComp)-len(word))
52+ copy(c[:start], text[:start])
53+ if cursorIdx < len(text) {
54+ copy(c[start+len(nickComp):], text[cursorIdx:])
55+ }
56+ copy(c[start:], nickComp)
57+ cs = append(cs, ui.Completion{
58+ Text: c,
59+ CursorIdx: start + len(nickComp),
60+ })
61+ }
62+ }
63+
64+ if cs != nil {
65+ cs = append(cs, ui.Completion{
66+ Text: text,
67+ CursorIdx: cursorIdx,
68+ })
69+ }
70+
71+ return cs
72+}
+62,
-6
1@@ -4,6 +4,11 @@ import (
2 "github.com/gdamore/tcell"
3 )
4
5+type Completion struct {
6+ Text []rune
7+ CursorIdx int
8+}
9+
10 // editor is the text field where the user writes messages and commands.
11 type editor struct {
12 // text contains the written runes. An empty slice means no text is written.
13@@ -26,13 +31,19 @@ type editor struct {
14
15 // width is the width of the screen.
16 width int
17+
18+ autoComplete func(cursorIdx int, text []rune) []Completion
19+ autoCache []Completion
20+ autoCacheIdx int
21+ completeIdx int
22 }
23
24-func newEditor(width int) editor {
25+func newEditor(width int, autoComplete func(cursorIdx int, text []rune) []Completion) editor {
26 return editor{
27- text: [][]rune{{}},
28- textWidth: []int{0},
29- width: width,
30+ text: [][]rune{{}},
31+ textWidth: []int{0},
32+ width: width,
33+ autoComplete: autoComplete,
34 }
35 }
36
37@@ -40,6 +51,7 @@ func (e *editor) Resize(width int) {
38 if width < e.width {
39 e.cursorIdx = 0
40 e.offsetIdx = 0
41+ e.autoCache = nil
42 }
43 e.width = width
44 }
45@@ -53,6 +65,12 @@ func (e *editor) TextLen() int {
46 }
47
48 func (e *editor) PutRune(r rune) {
49+ e.putRune(r)
50+ e.Right()
51+ e.autoCache = nil
52+}
53+
54+func (e *editor) putRune(r rune) {
55 e.text[e.lineIdx] = append(e.text[e.lineIdx], ' ')
56 copy(e.text[e.lineIdx][e.cursorIdx+1:], e.text[e.lineIdx][e.cursorIdx:])
57 e.text[e.lineIdx][e.cursorIdx] = r
58@@ -63,8 +81,6 @@ func (e *editor) PutRune(r rune) {
59 for i := e.cursorIdx + 1; i < len(e.textWidth); i++ {
60 e.textWidth[i] = rw + e.textWidth[i-1]
61 }
62-
63- e.Right()
64 }
65
66 func (e *editor) RemRune() (ok bool) {
67@@ -73,6 +89,7 @@ func (e *editor) RemRune() (ok bool) {
68 return
69 }
70 e.remRuneAt(e.cursorIdx - 1)
71+ e.autoCache = nil
72 e.Left()
73 return
74 }
75@@ -83,6 +100,7 @@ func (e *editor) RemRuneForward() (ok bool) {
76 return
77 }
78 e.remRuneAt(e.cursorIdx)
79+ e.autoCache = nil
80 return
81 }
82
83@@ -110,10 +128,16 @@ func (e *editor) Flush() (content string) {
84 e.textWidth = e.textWidth[:1]
85 e.cursorIdx = 0
86 e.offsetIdx = 0
87+ e.autoCache = nil
88 return
89 }
90
91 func (e *editor) Right() {
92+ e.right()
93+ e.autoCache = nil
94+}
95+
96+func (e *editor) right() {
97 if e.cursorIdx == len(e.text[e.lineIdx]) {
98 return
99 }
100@@ -138,18 +162,27 @@ func (e *editor) Left() {
101 e.offsetIdx = 0
102 }
103 }
104+ e.autoCache = nil
105 }
106
107 func (e *editor) Home() {
108+ if e.cursorIdx == 0 {
109+ return
110+ }
111 e.cursorIdx = 0
112 e.offsetIdx = 0
113+ e.autoCache = nil
114 }
115
116 func (e *editor) End() {
117+ if e.cursorIdx == len(e.text[e.lineIdx]) {
118+ return
119+ }
120 e.cursorIdx = len(e.text[e.lineIdx])
121 for e.width < e.textWidth[e.cursorIdx]-e.textWidth[e.offsetIdx]+16 {
122 e.offsetIdx++
123 }
124+ e.autoCache = nil
125 }
126
127 func (e *editor) Up() {
128@@ -160,6 +193,7 @@ func (e *editor) Up() {
129 e.computeTextWidth()
130 e.cursorIdx = 0
131 e.offsetIdx = 0
132+ e.autoCache = nil
133 e.End()
134 }
135
136@@ -175,9 +209,31 @@ func (e *editor) Down() {
137 e.computeTextWidth()
138 e.cursorIdx = 0
139 e.offsetIdx = 0
140+ e.autoCache = nil
141 e.End()
142 }
143
144+func (e *editor) AutoComplete() (ok bool) {
145+ if e.autoCache == nil {
146+ e.autoCache = e.autoComplete(e.cursorIdx, e.text[e.lineIdx])
147+ if e.autoCache == nil {
148+ return false
149+ }
150+ if len(e.autoCache) == 0 {
151+ e.autoCache = nil
152+ return false
153+ }
154+ e.autoCacheIdx = 0
155+ }
156+
157+ e.text[e.lineIdx] = e.autoCache[e.autoCacheIdx].Text
158+ e.cursorIdx = e.autoCache[e.autoCacheIdx].CursorIdx
159+ e.computeTextWidth()
160+ e.autoCacheIdx = (e.autoCacheIdx + 1) % len(e.autoCache)
161+
162+ return true
163+}
164+
165 func (e *editor) computeTextWidth() {
166 e.textWidth = e.textWidth[:1]
167 rw := 0
M
ui/ui.go
+10,
-1
1@@ -10,6 +10,7 @@ import (
2
3 type Config struct {
4 NickColWidth int
5+ AutoComplete func(cursorIdx int, text []rune) []Completion
6 }
7
8 type UI struct {
9@@ -55,7 +56,7 @@ func New(config Config) (ui *UI, err error) {
10 ui.bs.Add(Home)
11 ui.bs.AddLine("", NewLineNow("--", homeMessages[hmIdx]))
12
13- ui.e = newEditor(w)
14+ ui.e = newEditor(w, ui.config.AutoComplete)
15
16 ui.Resize()
17
18@@ -199,6 +200,14 @@ func (ui *UI) InputDelete() (ok bool) {
19 return
20 }
21
22+func (ui *UI) InputAutoComplete() (ok bool) {
23+ ok = ui.e.AutoComplete()
24+ if ok {
25+ ui.draw()
26+ }
27+ return
28+}
29+
30 func (ui *UI) InputEnter() (content string) {
31 content = ui.e.Flush()
32 ui.draw()