fconvert 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fconvert",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Fast local CLI conversion tool with graph-based anything-to-anything routing",
5
5
  "type": "module",
6
6
  "main": "src/cli/main.ts",
@@ -1,26 +1,35 @@
1
1
  module github.com/timmy6942025/convert/tools/format-picker
2
2
 
3
- go 1.22
3
+ go 1.24.2
4
+
5
+ toolchain go1.24.4
4
6
 
5
7
  require (
6
- github.com/charmbracelet/bubbletea v1.3.4
7
- github.com/charmbracelet/lipgloss v1.0.0
8
+ github.com/charmbracelet/bubbles v1.0.0
9
+ github.com/charmbracelet/bubbletea v1.3.10
10
+ github.com/charmbracelet/lipgloss v1.1.0
8
11
  )
9
12
 
10
13
  require (
14
+ github.com/atotto/clipboard v0.1.4 // indirect
11
15
  github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
12
- github.com/charmbracelet/x/ansi v0.8.0 // indirect
13
- github.com/charmbracelet/x/term v0.2.1 // indirect
16
+ github.com/charmbracelet/colorprofile v0.4.1 // indirect
17
+ github.com/charmbracelet/x/ansi v0.11.6 // indirect
18
+ github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
19
+ github.com/charmbracelet/x/term v0.2.2 // indirect
20
+ github.com/clipperhouse/displaywidth v0.9.0 // indirect
21
+ github.com/clipperhouse/stringish v0.1.1 // indirect
22
+ github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
14
23
  github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
15
- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
24
+ github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
16
25
  github.com/mattn/go-isatty v0.0.20 // indirect
17
26
  github.com/mattn/go-localereader v0.0.1 // indirect
18
- github.com/mattn/go-runewidth v0.0.16 // indirect
27
+ github.com/mattn/go-runewidth v0.0.19 // indirect
19
28
  github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
20
29
  github.com/muesli/cancelreader v0.2.2 // indirect
21
- github.com/muesli/termenv v0.15.2 // indirect
30
+ github.com/muesli/termenv v0.16.0 // indirect
22
31
  github.com/rivo/uniseg v0.4.7 // indirect
23
- golang.org/x/sync v0.11.0 // indirect
24
- golang.org/x/sys v0.30.0 // indirect
32
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
33
+ golang.org/x/sys v0.38.0 // indirect
25
34
  golang.org/x/text v0.3.8 // indirect
26
35
  )
@@ -1,37 +1,56 @@
1
+ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2
+ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
1
3
  github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
2
4
  github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
3
- github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
4
- github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
5
- github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
6
- github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
7
- github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
8
- github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
9
- github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
10
- github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
5
+ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
6
+ github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
7
+ github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
8
+ github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
9
+ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
10
+ github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
11
+ github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
12
+ github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
13
+ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
14
+ github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
15
+ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
16
+ github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
17
+ github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
18
+ github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
19
+ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
20
+ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
21
+ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
22
+ github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
23
+ github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
24
+ github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
25
+ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
26
+ github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
27
+ github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
28
+ github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
11
29
  github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
12
30
  github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
13
- github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
14
- github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
31
+ github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
32
+ github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
15
33
  github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
16
34
  github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
17
35
  github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
18
36
  github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
19
- github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
20
- github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
37
+ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
38
+ github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
21
39
  github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
22
40
  github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
23
41
  github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
24
42
  github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
25
- github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
26
- github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
27
- github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
43
+ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
44
+ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
28
45
  github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
29
46
  github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
30
- golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
31
- golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
47
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
48
+ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
49
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
50
+ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
32
51
  golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33
52
  golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34
- golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
35
- golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
53
+ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
54
+ golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
36
55
  golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
37
56
  golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
@@ -8,6 +8,9 @@ import (
8
8
  "sort"
9
9
  "strings"
10
10
 
11
+ "github.com/charmbracelet/bubbles/help"
12
+ "github.com/charmbracelet/bubbles/key"
13
+ "github.com/charmbracelet/bubbles/textinput"
11
14
  tea "github.com/charmbracelet/bubbletea"
12
15
  "github.com/charmbracelet/lipgloss"
13
16
  )
@@ -30,9 +33,54 @@ type scoredOption struct {
30
33
  score int
31
34
  }
32
35
 
36
+ type keyMap struct {
37
+ Up key.Binding
38
+ Down key.Binding
39
+ PageUp key.Binding
40
+ PageDown key.Binding
41
+ Accept key.Binding
42
+ Quit key.Binding
43
+ }
44
+
45
+ func (k keyMap) ShortHelp() []key.Binding {
46
+ return []key.Binding{k.Up, k.Down, k.Accept, k.Quit}
47
+ }
48
+
49
+ func (k keyMap) FullHelp() [][]key.Binding {
50
+ return [][]key.Binding{{k.Up, k.Down, k.PageUp, k.PageDown}, {k.Accept, k.Quit}}
51
+ }
52
+
53
+ func defaultKeys() keyMap {
54
+ return keyMap{
55
+ Up: key.NewBinding(
56
+ key.WithKeys("up", "k", "ctrl+p"),
57
+ key.WithHelp("up", "move up"),
58
+ ),
59
+ Down: key.NewBinding(
60
+ key.WithKeys("down", "j", "ctrl+n"),
61
+ key.WithHelp("down", "move down"),
62
+ ),
63
+ PageUp: key.NewBinding(
64
+ key.WithKeys("pgup", "u"),
65
+ key.WithHelp("pgup", "page up"),
66
+ ),
67
+ PageDown: key.NewBinding(
68
+ key.WithKeys("pgdown", "d"),
69
+ key.WithHelp("pgdown", "page down"),
70
+ ),
71
+ Accept: key.NewBinding(
72
+ key.WithKeys("enter"),
73
+ key.WithHelp("enter", "select"),
74
+ ),
75
+ Quit: key.NewBinding(
76
+ key.WithKeys("esc", "ctrl+c"),
77
+ key.WithHelp("esc", "cancel"),
78
+ ),
79
+ }
80
+ }
81
+
33
82
  type model struct {
34
83
  prompt string
35
- query string
36
84
  preferred string
37
85
  all []option
38
86
  filtered []option
@@ -41,17 +89,25 @@ type model struct {
41
89
  cancelled bool
42
90
  width int
43
91
  height int
92
+ input textinput.Model
93
+ help help.Model
94
+ keys keyMap
44
95
  }
45
96
 
46
97
  var (
47
- frameStyle = lipgloss.NewStyle().
98
+ appStyle = lipgloss.NewStyle().
99
+ BorderStyle(lipgloss.RoundedBorder()).
100
+ BorderForeground(lipgloss.AdaptiveColor{Light: "251", Dark: "238"}).
48
101
  Padding(0, 1)
49
102
 
50
- headerStyle = lipgloss.NewStyle().
103
+ titleStyle = lipgloss.NewStyle().
51
104
  Bold(true)
52
105
 
106
+ mutedStyle = lipgloss.NewStyle().
107
+ Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "240"})
108
+
53
109
  promptStyle = lipgloss.NewStyle().
54
- Foreground(lipgloss.AdaptiveColor{Light: "241", Dark: "248"})
110
+ Foreground(lipgloss.AdaptiveColor{Light: "63", Dark: "111"})
55
111
 
56
112
  hintStyle = lipgloss.NewStyle().
57
113
  Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "240"})
@@ -71,7 +127,7 @@ var (
71
127
  )
72
128
 
73
129
  func (m model) Init() tea.Cmd {
74
- return nil
130
+ return textinput.Blink
75
131
  }
76
132
 
77
133
  func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -79,53 +135,64 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
79
135
  case tea.WindowSizeMsg:
80
136
  m.width = msg.Width
81
137
  m.height = msg.Height
138
+ if m.width > 0 {
139
+ m.input.Width = maxInt(12, m.width-24)
140
+ }
82
141
  return m, nil
83
142
  case tea.KeyMsg:
84
- switch msg.String() {
85
- case "ctrl+c", "esc":
143
+ switch {
144
+ case key.Matches(msg, m.keys.Quit):
86
145
  m.cancelled = true
87
146
  return m, tea.Quit
88
- case "enter":
147
+ case key.Matches(msg, m.keys.Accept):
89
148
  if len(m.filtered) == 0 {
90
149
  return m, nil
91
150
  }
92
151
  selected := m.filtered[m.cursor]
93
152
  m.selected = &selected
94
153
  return m, tea.Quit
95
- case "up", "k", "ctrl+p":
154
+ case key.Matches(msg, m.keys.Up):
96
155
  if m.cursor > 0 {
97
156
  m.cursor--
98
157
  }
99
158
  return m, nil
100
- case "down", "j", "ctrl+n":
159
+ case key.Matches(msg, m.keys.Down):
101
160
  if m.cursor < len(m.filtered)-1 {
102
161
  m.cursor++
103
162
  }
104
163
  return m, nil
105
- case "backspace", "ctrl+h":
106
- if len(m.query) == 0 {
107
- return m, nil
164
+ case key.Matches(msg, m.keys.PageUp):
165
+ step := m.visibleRows()
166
+ m.cursor = maxInt(0, m.cursor-step)
167
+ return m, nil
168
+ case key.Matches(msg, m.keys.PageDown):
169
+ step := m.visibleRows()
170
+ if len(m.filtered) > 0 {
171
+ m.cursor = minInt(len(m.filtered)-1, m.cursor+step)
108
172
  }
109
- m.query = m.query[:len(m.query)-1]
110
- m.refilter()
111
173
  return m, nil
112
174
  }
175
+ }
113
176
 
114
- if msg.Type == tea.KeyRunes {
115
- m.query += msg.String()
116
- m.refilter()
117
- }
177
+ previousQuery := m.input.Value()
178
+ var cmd tea.Cmd
179
+ m.input, cmd = m.input.Update(msg)
180
+ if previousQuery != m.input.Value() {
181
+ m.refilter()
118
182
  }
119
183
 
120
- return m, nil
184
+ return m, cmd
121
185
  }
122
186
 
123
187
  func (m model) View() string {
124
188
  var builder strings.Builder
125
- line := fmt.Sprintf("%s > %s", promptStyle.Render(m.prompt), m.query)
126
- builder.WriteString(headerStyle.Render(line))
189
+ builder.WriteString(titleStyle.Render("fconvert format picker"))
127
190
  builder.WriteString("\n")
128
- if m.query == "" && m.preferred != "" {
191
+ builder.WriteString(promptStyle.Render(m.prompt))
192
+ builder.WriteString("\n")
193
+ builder.WriteString(m.input.View())
194
+ builder.WriteString("\n")
195
+ if strings.TrimSpace(m.input.Value()) == "" && m.preferred != "" {
129
196
  builder.WriteString(hintStyle.Render(fmt.Sprintf("hint: original extension '.%s' is ranked first", m.preferred)))
130
197
  builder.WriteString("\n")
131
198
  }
@@ -133,17 +200,11 @@ func (m model) View() string {
133
200
  if len(m.filtered) == 0 {
134
201
  builder.WriteString(emptyStyle.Render("no matches"))
135
202
  builder.WriteString("\n")
136
- builder.WriteString(footerStyle.Render("keys: type to search, backspace delete, esc cancel"))
137
- return frameStyle.Render(builder.String())
203
+ builder.WriteString(footerStyle.Render(m.help.View(m.keys)))
204
+ return appStyle.Render(builder.String())
138
205
  }
139
206
 
140
- maxRows := 8
141
- if m.height > 6 {
142
- maxRows = m.height - 4
143
- }
144
- if maxRows < 4 {
145
- maxRows = 4
146
- }
207
+ maxRows := m.visibleRows()
147
208
  if maxRows > len(m.filtered) {
148
209
  maxRows = len(m.filtered)
149
210
  }
@@ -162,11 +223,15 @@ func (m model) View() string {
162
223
  prefix := " "
163
224
  style := rowStyle
164
225
  if index == m.cursor {
165
- prefix = "> "
226
+ prefix = "->"
166
227
  style = selectedStyle
167
228
  }
168
229
  item := m.filtered[index]
169
- line := fmt.Sprintf("%s%-10s .%-6s %s", prefix, item.ID, item.Extension, item.Name)
230
+ extra := ""
231
+ if strings.EqualFold(item.Extension, m.preferred) {
232
+ extra = mutedStyle.Render(" same-ext")
233
+ }
234
+ line := fmt.Sprintf("%s %-10s .%-6s %s%s", prefix, item.ID, item.Extension, item.Name, extra)
170
235
  builder.WriteString(style.Render(line))
171
236
  builder.WriteString("\n")
172
237
  }
@@ -177,13 +242,24 @@ func (m model) View() string {
177
242
  }
178
243
  builder.WriteString(footerStyle.Render(status))
179
244
  builder.WriteString("\n")
180
- builder.WriteString(footerStyle.Render("keys: up/down move, enter select, esc cancel"))
245
+ builder.WriteString(footerStyle.Render(m.help.View(m.keys)))
246
+
247
+ return appStyle.Render(builder.String())
248
+ }
181
249
 
182
- return frameStyle.Render(builder.String())
250
+ func (m model) visibleRows() int {
251
+ rows := 10
252
+ if m.height > 0 {
253
+ rows = m.height - 11
254
+ }
255
+ if rows < 4 {
256
+ rows = 4
257
+ }
258
+ return rows
183
259
  }
184
260
 
185
261
  func (m *model) refilter() {
186
- query := strings.ToLower(strings.TrimSpace(m.query))
262
+ query := strings.ToLower(strings.TrimSpace(m.input.Value()))
187
263
  scored := make([]scoredOption, 0, len(m.all))
188
264
 
189
265
  for _, item := range m.all {
@@ -278,6 +354,20 @@ func fuzzyScore(query string, item option, preferred string) int {
278
354
  return score
279
355
  }
280
356
 
357
+ func minInt(a int, b int) int {
358
+ if a < b {
359
+ return a
360
+ }
361
+ return b
362
+ }
363
+
364
+ func maxInt(a int, b int) int {
365
+ if a > b {
366
+ return a
367
+ }
368
+ return b
369
+ }
370
+
281
371
  func readPayload(path string) (payload, error) {
282
372
  raw, err := os.ReadFile(path)
283
373
  if err != nil {
@@ -317,9 +407,25 @@ func main() {
317
407
 
318
408
  picker := model{
319
409
  prompt: data.Prompt,
320
- query: data.Query,
321
410
  preferred: strings.TrimSpace(data.Preferred),
322
411
  all: data.Options,
412
+ keys: defaultKeys(),
413
+ help: help.New(),
414
+ }
415
+ picker.input = textinput.New()
416
+ picker.input.Prompt = ""
417
+ picker.input.Placeholder = "type to fuzzy filter"
418
+ picker.input.CharLimit = 128
419
+ picker.input.SetValue(data.Query)
420
+ picker.input.Focus()
421
+ picker.help.ShowAll = false
422
+ picker.help.Styles.ShortKey = mutedStyle
423
+ picker.help.Styles.ShortDesc = mutedStyle
424
+ picker.help.Styles.FullKey = mutedStyle
425
+ picker.help.Styles.FullDesc = mutedStyle
426
+
427
+ if picker.prompt == "" {
428
+ picker.prompt = "output format"
323
429
  }
324
430
  picker.refilter()
325
431