path_suggest.go RawHistoryFolder
1package modes
2
3import "github.com/patriceckhart/zot/packages/tui"
4
5// pathChoicePopup is shown after a repeated Tab reaches an ambiguous
6// filesystem prefix. It deliberately owns only the transient choice state;
7// the editor remains the source of truth for the prompt text.
8type pathChoicePopup struct {
9 completion pathCompletionResult
10 cursor int
11}
12
13func newPathChoicePopup() *pathChoicePopup { return &pathChoicePopup{} }
14
15func (p *pathChoicePopup) Open(completion pathCompletionResult) {
16 if p == nil {
17 return
18 }
19 p.completion = completion
20 p.cursor = 0
21}
22
23func (p *pathChoicePopup) Reset() {
24 if p == nil {
25 return
26 }
27 p.completion = pathCompletionResult{}
28 p.cursor = 0
29}
30
31func (p *pathChoicePopup) Active(input string) bool {
32 if p == nil || len(p.completion.entries) <= 1 {
33 return false
34 }
35 start := len(input)
36 for start > 0 {
37 r := input[start-1]
38 if r == ' ' || r == '\t' || r == '\n' {
39 break
40 }
41 start--
42 }
43 if start != p.completion.tokenStart || input[start:] != p.completion.token {
44 p.Reset()
45 return false
46 }
47 return true
48}
49
50func (p *pathChoicePopup) Up() {
51 if p.cursor > 0 {
52 p.cursor--
53 }
54}
55
56func (p *pathChoicePopup) Down() {
57 if p.cursor < len(p.completion.entries)-1 {
58 p.cursor++
59 }
60}
61
62// Select replaces the unchanged path token with the highlighted match. It
63// returns false when the editor no longer contains the token that opened the
64// popup, in which case the caller should let the key continue normally.
65func (p *pathChoicePopup) Select(ed *tui.Editor) bool {
66 if ed == nil || !p.Active(ed.Value()) {
67 return false
68 }
69 if p.cursor < 0 || p.cursor >= len(p.completion.entries) {
70 p.cursor = 0
71 }
72 entry := p.completion.entries[p.cursor]
73 newToken := p.completion.displayParent + entry.name
74 if entry.isDir {
75 newToken += "/"
76 }
77 value := ed.Value()
78 ed.SetValue(value[:p.completion.tokenStart] + newToken)
79 p.Reset()
80 return true
81}
82
83// Render returns the candidate list and a short key hint. The list is kept
84// intentionally small and plain: it complements the existing slash and @
85// popups without introducing a second completion model or a new dependency.
86func (p *pathChoicePopup) Render(th tui.Theme, width int) []string {
87 if p == nil || len(p.completion.entries) <= 1 {
88 return nil
89 }
90 header := " " + p.completion.displayParent + p.completion.basePrefix
91 lines := []string{th.FG256(th.Muted, header), ""}
92 const maxVisible = 12
93 visible := p.completion.entries
94 offset := 0
95 if len(visible) > maxVisible {
96 if p.cursor >= maxVisible {
97 offset = p.cursor - maxVisible + 1
98 }
99 end := offset + maxVisible
100 if end > len(visible) {
101 end = len(visible)
102 offset = end - maxVisible
103 }
104 visible = visible[offset:end]
105 }
106 for idx, entry := range visible {
107 idx += offset
108 name := entry.name
109 if entry.isDir {
110 name += "/"
111 }
112 plain := " " + name
113 if idx == p.cursor {
114 lines = append(lines, th.PadHighlight(plain, width))
115 } else {
116 lines = append(lines, th.FG256(th.Muted, plain))
117 }
118 }
119 lines = append(lines, "", th.FG256(th.Muted, " ↑/↓ navigate - tab/enter complete - esc cancel"), "")
120 return lines
121}