1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
// Drawing the picker: the popup that lists the agent's commands or the
// project's files while a "/" or "@" word is being typed. Local coordinates,
// like everything else in view_draw.go.
package acp
import (
"strings"
"codeberg.org/turbo-editors/turbo-core/theme"
"codeberg.org/turbo-editors/turbo-core/ui"
)
// drawPicker paints the popup just above the rule, over the bottom of the
// conversation, when there is something to list.
func (v *View) drawPicker(p *ui.Painter, th *theme.Theme) {
choices := v.refresh()
room := v.transcriptHeight()
if len(choices) == 0 || room < 3 {
v.picker.bounds = ui.Rect{}
return
}
height := min(min(len(choices), pickerRows)+2, room)
width := min(max(widestChoice(choices)+4, pickerMinWidth), p.Size().W)
bounds := ui.Rect{X: 0, Y: room - height, W: width, H: height}
v.picker.bounds = bounds
frame := th.Style(theme.KeyCompletionFrame)
for row := range height {
p.HLine(bounds.X, bounds.Y+row, width, ' ', th.Style(theme.KeyCompletionItem))
}
ui.DrawFrame(p, bounds, ui.FrameSingle, frame)
p.TextLimited(bounds.X+2, bounds.Y, max(width-4, 0), v.pickerTitle(), frame)
for row := range height - 2 {
index := v.picker.top + row
if index >= len(choices) {
break
}
v.drawChoice(p, th, row, choices[index], index == v.picker.selected)
}
}
// pickerTitle names what is being listed, on the frame.
func (v *View) pickerTitle() string {
if _, word := v.typedWord(); strings.HasPrefix(word, string(mentionRune)) {
return " files "
}
return " commands "
}
// widestChoice is the width of the longest line the popup will show.
func widestChoice(choices []Choice) int {
widest := 0
for _, choice := range choices {
widest = max(widest, len([]rune(choiceText(choice))))
}
return widest
}
// choiceText is one line of the popup as plain text: the label, the
// description, and the hint in angle brackets.
func choiceText(choice Choice) string {
text := choice.Label
if choice.Detail != "" {
text += " " + choice.Detail
}
if choice.Hint != "" {
text += " <" + choice.Hint + ">"
}
return text
}
// drawChoice paints one line of the popup — row rows below the frame's top,
// inside the bounds drawPicker just recorded: the label in the item colour,
// the rest in the detail colour, the whole row highlighted when it is the one.
func (v *View) drawChoice(p *ui.Painter, th *theme.Theme, row int, choice Choice, selected bool) {
style := th.Style(theme.KeyCompletionItem)
detail := th.Style(theme.KeyCompletionDetail)
if selected {
style = th.Style(theme.KeyCompletionSelected)
detail = style
}
bounds := v.picker.bounds
x, y := bounds.X+1, bounds.Y+1+row
width := bounds.W - 2
p.HLine(x, y, width, ' ', style)
end := p.TextLimited(x, y, width, choice.Label, style)
rest := strings.TrimPrefix(choiceText(choice), choice.Label)
if rest != "" && end < x+width {
p.TextLimited(end, y, x+width-end, rest, detail)
}
}
|