aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ui/textinput.go
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-11-14 14:19:27 +0100
committerRobin Jarry <robin@jarry.cc>2023-11-22 23:35:22 +0100
commitb3dc63d69c1460c2ccf519b74e975c39d5a8b5fb (patch)
tree61b461d30bb6e38df57032fc557df1c4e18f9a38 /lib/ui/textinput.go
parent9b3e3d1b8803d35f761fc69abe74cc80431abdba (diff)
downloadaerc-b3dc63d69c1460c2ccf519b74e975c39d5a8b5fb.tar.gz
complete: only display popover for more than one choice
When there is only one completion choice available, accept the completion immediately upon a <Tab> key press. This is consistent with how most completion engines work (bash, vim, etc.). Reported-by: Johannes Thyssen Tishman <johannes@thyssentishman.com> Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Johannes Thyssen Tishman <johannes@thyssentishman.com>
Diffstat (limited to 'lib/ui/textinput.go')
-rw-r--r--lib/ui/textinput.go138
1 files changed, 74 insertions, 64 deletions
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 9886da9f..32a177e1 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -14,7 +14,6 @@ import (
)
// TODO: Attach history providers
-// TODO: scrolling
type TextInput struct {
sync.Mutex
@@ -129,27 +128,7 @@ func (ti *TextInput) drawPopover(ctx *Context) {
if len(ti.completions) == 0 {
return
}
- cmp := &completions{
- options: ti.completions,
- idx: ti.completeIndex,
- stringLeft: ti.StringLeft(),
- prefix: ti.prefix,
- onSelect: func(idx int) {
- ti.completeIndex = idx
- ti.Invalidate()
- },
- onExec: func() {
- ti.executeCompletion()
- ti.invalidateCompletions()
- ti.Invalidate()
- },
- onStem: func(stem string) {
- ti.Set(ti.prefix + stem + ti.StringRight())
- ti.index = runewidth.StringWidth(ti.prefix + stem)
- ti.Invalidate()
- },
- uiConfig: ti.uiConfig,
- }
+ cmp := &completions{ti: ti}
width := maxLen(ti.completions) + 3
height := len(ti.completions)
@@ -317,7 +296,7 @@ func (ti *TextInput) updateCompletions() {
defer log.PanicHandler()
ti.Lock()
if len(ti.StringLeft()) >= ti.completeMinChars {
- ti.showCompletions()
+ ti.showCompletions(false)
}
ti.Unlock()
})
@@ -327,13 +306,21 @@ func (ti *TextInput) updateCompletions() {
}
}
-func (ti *TextInput) showCompletions() {
+func (ti *TextInput) showCompletions(explicit bool) {
if ti.tabcomplete == nil {
// no completer
return
}
ti.completions, ti.prefix = ti.tabcomplete(ti.StringLeft())
- ti.completeIndex = -1
+
+ if explicit && len(ti.completions) == 1 {
+ // automatically accept if there is only one choice
+ ti.completeIndex = 0
+ ti.executeCompletion()
+ ti.invalidateCompletions()
+ } else {
+ ti.completeIndex = -1
+ }
Invalidate()
}
@@ -351,7 +338,7 @@ func (ti *TextInput) Event(event tcell.Event) bool {
if event, ok := event.(*tcell.EventKey); ok {
c := ti.completeKey
if c != nil && c.Key == event.Key() && c.Modifiers == event.Modifiers() {
- ti.showCompletions()
+ ti.showCompletions(true)
return true
}
@@ -398,14 +385,7 @@ func (ti *TextInput) Event(event tcell.Event) bool {
}
type completions struct {
- options []string
- stringLeft string
- prefix string
- idx int
- onSelect func(int)
- onExec func()
- onStem func(string)
- uiConfig *config.UIConfig
+ ti *TextInput
}
func unquote(s string) string {
@@ -427,28 +407,28 @@ func maxLen(ss []string) int {
}
func (c *completions) Draw(ctx *Context) {
- bg := c.uiConfig.GetStyle(config.STYLE_COMPLETION_DEFAULT)
- gutter := c.uiConfig.GetStyle(config.STYLE_COMPLETION_GUTTER)
- pill := c.uiConfig.GetStyle(config.STYLE_COMPLETION_PILL)
- sel := c.uiConfig.GetStyleSelected(config.STYLE_COMPLETION_DEFAULT)
+ bg := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_DEFAULT)
+ gutter := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_GUTTER)
+ pill := c.ti.uiConfig.GetStyle(config.STYLE_COMPLETION_PILL)
+ sel := c.ti.uiConfig.GetStyleSelected(config.STYLE_COMPLETION_DEFAULT)
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', bg)
numVisible := ctx.Height()
startIdx := 0
- if len(c.options) > numVisible && c.idx+1 > numVisible {
- startIdx = c.idx - (numVisible - 1)
+ if len(c.ti.completions) > numVisible && c.index()+1 > numVisible {
+ startIdx = c.index() - (numVisible - 1)
}
endIdx := startIdx + numVisible - 1
- for idx, opt := range c.options {
+ for idx, opt := range c.ti.completions {
if idx < startIdx {
continue
}
if idx > endIdx {
continue
}
- if c.idx == idx {
+ if c.index() == idx {
ctx.Fill(0, idx-startIdx, ctx.Width(), 1, ' ', sel)
ctx.Printf(0, idx-startIdx, sel, " %s ", unquote(opt))
} else {
@@ -456,7 +436,7 @@ func (c *completions) Draw(ctx *Context) {
}
}
- percentVisible := float64(numVisible) / float64(len(c.options))
+ percentVisible := float64(numVisible) / float64(len(c.ti.completions))
if percentVisible >= 1.0 {
return
}
@@ -465,44 +445,59 @@ func (c *completions) Draw(ctx *Context) {
ctx.Fill(ctx.Width()-1, 0, 1, ctx.Height(), ' ', gutter)
pillSize := int(math.Ceil(float64(ctx.Height()) * percentVisible))
- percentScrolled := float64(startIdx) / float64(len(c.options))
+ percentScrolled := float64(startIdx) / float64(len(c.ti.completions))
pillOffset := int(math.Floor(float64(ctx.Height()) * percentScrolled))
ctx.Fill(ctx.Width()-1, pillOffset, 1, pillSize, ' ', pill)
}
+func (c *completions) index() int {
+ return c.ti.completeIndex
+}
+
func (c *completions) next() {
- idx := c.idx
- idx++
- if idx > len(c.options)-1 {
- idx = -1
+ index := c.index()
+ index++
+ if index >= len(c.ti.completions) {
+ index = -1
}
- c.onSelect(idx)
+ c.ti.completeIndex = index
+ Invalidate()
}
func (c *completions) prev() {
- idx := c.idx
- idx--
- if idx < -1 {
- idx = len(c.options) - 1
+ index := c.index()
+ index--
+ if index < -1 {
+ index = len(c.ti.completions) - 1
}
- c.onSelect(idx)
+ c.ti.completeIndex = index
+ Invalidate()
+}
+
+func (c *completions) exec() {
+ c.ti.executeCompletion()
+ c.ti.invalidateCompletions()
+ Invalidate()
}
func (c *completions) Event(e tcell.Event) bool {
if e, ok := e.(*tcell.EventKey); ok {
- switch e.Key() {
- case tcell.KeyTab:
- if len(c.options) == 1 && c.idx >= 0 {
- c.onExec()
+ k := c.ti.completeKey
+ if k != nil && k.Key == e.Key() && k.Modifiers == e.Modifiers() {
+ if len(c.ti.completions) == 1 {
+ c.ti.completeIndex = 0
+ c.exec()
} else {
- stem := findStem(c.options)
- if stem != "" && c.idx < 0 &&
- len(stem)+len(c.prefix) > len(c.stringLeft) {
- c.onStem(stem)
+ stem := findStem(c.ti.completions)
+ if c.needsStem(stem) {
+ c.stem(stem)
}
c.next()
}
return true
+ }
+
+ switch e.Key() {
case tcell.KeyCtrlN, tcell.KeyDown:
c.next()
return true
@@ -510,8 +505,8 @@ func (c *completions) Event(e tcell.Event) bool {
c.prev()
return true
case tcell.KeyEnter:
- if c.idx >= 0 {
- c.onExec()
+ if c.index() >= 0 {
+ c.exec()
return true
}
}
@@ -519,6 +514,21 @@ func (c *completions) Event(e tcell.Event) bool {
return false
}
+func (c *completions) needsStem(stem string) bool {
+ if stem == "" || c.index() >= 0 {
+ return false
+ }
+ if len(stem)+len(c.ti.prefix) > len(c.ti.StringLeft()) {
+ return true
+ }
+ return false
+}
+
+func (c *completions) stem(stem string) {
+ c.ti.Set(c.ti.prefix + stem + c.ti.StringRight())
+ c.ti.index = runewidth.StringWidth(c.ti.prefix + stem)
+}
+
func findStem(words []string) string {
if len(words) == 0 {
return ""