on main help sign in
Merge pull request #31 from charmbracelet/drop-reflection-and-use-string-builder
d9577ecRaphael Amorim
internal/llm/tools/diagnostics.go+16−16
@@ -245,28 +245,28 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
245 return projectDiagnostics[i] < projectDiagnostics[j]
245 return projectDiagnostics[i] < projectDiagnostics[j]
246 })
246 })
247
247
248− output := ""
248+ var output strings.Builder
249
249
250 if len(fileDiagnostics) > 0 {
250 if len(fileDiagnostics) > 0 {
251− output += "\n<file_diagnostics>\n"
251+ output.WriteString("\n<file_diagnostics>\n")
252 if len(fileDiagnostics) > 10 {
252 if len(fileDiagnostics) > 10 {
253− output += strings.Join(fileDiagnostics[:10], "\n")
253+ output.WriteString(strings.Join(fileDiagnostics[:10], "\n"))
254− output += fmt.Sprintf("\n... and %d more diagnostics", len(fileDiagnostics)-10)
254+ fmt.Fprintf(&output, "\n... and %d more diagnostics", len(fileDiagnostics)-10)
255 } else {
255 } else {
256− output += strings.Join(fileDiagnostics, "\n")
256+ output.WriteString(strings.Join(fileDiagnostics, "\n"))
257 }
257 }
258− output += "\n</file_diagnostics>\n"
258+ output.WriteString("\n</file_diagnostics>\n")
259 }
259 }
260
260
261 if len(projectDiagnostics) > 0 {
261 if len(projectDiagnostics) > 0 {
262− output += "\n<project_diagnostics>\n"
262+ output.WriteString("\n<project_diagnostics>\n")
263 if len(projectDiagnostics) > 10 {
263 if len(projectDiagnostics) > 10 {
264− output += strings.Join(projectDiagnostics[:10], "\n")
264+ output.WriteString(strings.Join(projectDiagnostics[:10], "\n"))
265− output += fmt.Sprintf("\n... and %d more diagnostics", len(projectDiagnostics)-10)
265+ fmt.Fprintf(&output, "\n... and %d more diagnostics", len(projectDiagnostics)-10)
266 } else {
266 } else {
267− output += strings.Join(projectDiagnostics, "\n")
267+ output.WriteString(strings.Join(projectDiagnostics, "\n"))
268 }
268 }
269− output += "\n</project_diagnostics>\n"
269+ output.WriteString("\n</project_diagnostics>\n")
270 }
270 }
271
271
272 if len(fileDiagnostics) > 0 || len(projectDiagnostics) > 0 {
272 if len(fileDiagnostics) > 0 || len(projectDiagnostics) > 0 {
@@ -275,13 +275,13 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
275 projectErrors := countSeverity(projectDiagnostics, "Error")
275 projectErrors := countSeverity(projectDiagnostics, "Error")
276 projectWarnings := countSeverity(projectDiagnostics, "Warn")
276 projectWarnings := countSeverity(projectDiagnostics, "Warn")
277
277
278− output += "\n<diagnostic_summary>\n"
278+ output.WriteString("\n<diagnostic_summary>\n")
279− output += fmt.Sprintf("Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
279+ fmt.Fprintf(&output, "Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
280− output += fmt.Sprintf("Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
280+ fmt.Fprintf(&output, "Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
281− output += "</diagnostic_summary>\n"
281+ output.WriteString("</diagnostic_summary>\n")
282 }
282 }
283
283
284− return output
284+ return output.String()
285}
285}
286
286
287func countSeverity(diagnostics []string, severity string) int {
287func countSeverity(diagnostics []string, severity string) int {
internal/llm/tools/grep.go+9−9
@@ -198,35 +198,35 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
198 return ToolResponse{}, fmt.Errorf("error searching files: %w", err)
198 return ToolResponse{}, fmt.Errorf("error searching files: %w", err)
199 }
199 }
200
200
201− var output string
201+ var output strings.Builder
202 if len(matches) == 0 {
202 if len(matches) == 0 {
203− output = "No files found"
203+ output.WriteString("No files found")
204 } else {
204 } else {
205− output = fmt.Sprintf("Found %d matches\n", len(matches))
205+ fmt.Fprintf(&output, "Found %d matches\n", len(matches))
206
206
207 currentFile := ""
207 currentFile := ""
208 for _, match := range matches {
208 for _, match := range matches {
209 if currentFile != match.path {
209 if currentFile != match.path {
210 if currentFile != "" {
210 if currentFile != "" {
211− output += "\n"
211+ output.WriteString("\n")
212 }
212 }
213 currentFile = match.path
213 currentFile = match.path
214− output += fmt.Sprintf("%s:\n", match.path)
214+ fmt.Fprintf(&output, "%s:\n", match.path)
215 }
215 }
216 if match.lineNum > 0 {
216 if match.lineNum > 0 {
217− output += fmt.Sprintf(" Line %d: %s\n", match.lineNum, match.lineText)
217+ fmt.Fprintf(&output, " Line %d: %s\n", match.lineNum, match.lineText)
218 } else {
218 } else {
219− output += fmt.Sprintf(" %s\n", match.path)
219+ fmt.Fprintf(&output, " %s\n", match.path)
220 }
220 }
221 }
221 }
222
222
223 if truncated {
223 if truncated {
224− output += "\n(Results are truncated. Consider using a more specific path or pattern.)"
224+ output.WriteString("\n(Results are truncated. Consider using a more specific path or pattern.)")
225 }
225 }
226 }
226 }
227
227
228 return WithResponseMetadata(
228 return WithResponseMetadata(
229− NewTextResponse(output),
229+ NewTextResponse(output.String()),
230 GrepResponseMetadata{
230 GrepResponseMetadata{
231 NumberOfMatches: len(matches),
231 NumberOfMatches: len(matches),
232 Truncated: truncated,
232 Truncated: truncated,
internal/llm/tools/grep_test.go+9−9
@@ -7,24 +7,24 @@ import (
7
7
8func TestRegexCache(t *testing.T) {
8func TestRegexCache(t *testing.T) {
9 cache := newRegexCache()
9 cache := newRegexCache()
10−
10+
11 // Test basic caching
11 // Test basic caching
12 pattern := "test.*pattern"
12 pattern := "test.*pattern"
13 regex1, err := cache.get(pattern)
13 regex1, err := cache.get(pattern)
14 if err != nil {
14 if err != nil {
15 t.Fatalf("Failed to compile regex: %v", err)
15 t.Fatalf("Failed to compile regex: %v", err)
16 }
16 }
17−
17+
18 regex2, err := cache.get(pattern)
18 regex2, err := cache.get(pattern)
19 if err != nil {
19 if err != nil {
20 t.Fatalf("Failed to get cached regex: %v", err)
20 t.Fatalf("Failed to get cached regex: %v", err)
21 }
21 }
22−
22+
23 // Should be the same instance (cached)
23 // Should be the same instance (cached)
24 if regex1 != regex2 {
24 if regex1 != regex2 {
25 t.Error("Expected cached regex to be the same instance")
25 t.Error("Expected cached regex to be the same instance")
26 }
26 }
27−
27+
28 // Test that it actually works
28 // Test that it actually works
29 if !regex1.MatchString("test123pattern") {
29 if !regex1.MatchString("test123pattern") {
30 t.Error("Regex should match test string")
30 t.Error("Regex should match test string")
@@ -34,13 +34,13 @@ func TestRegexCache(t *testing.T) {
34func TestGlobToRegexCaching(t *testing.T) {
34func TestGlobToRegexCaching(t *testing.T) {
35 // Test that globToRegex uses pre-compiled regex
35 // Test that globToRegex uses pre-compiled regex
36 pattern1 := globToRegex("*.{js,ts}")
36 pattern1 := globToRegex("*.{js,ts}")
37−
37+
38 // Should not panic and should work correctly
38 // Should not panic and should work correctly
39 regex1, err := regexp.Compile(pattern1)
39 regex1, err := regexp.Compile(pattern1)
40 if err != nil {
40 if err != nil {
41 t.Fatalf("Failed to compile glob regex: %v", err)
41 t.Fatalf("Failed to compile glob regex: %v", err)
42 }
42 }
43−
43+
44 if !regex1.MatchString("test.js") {
44 if !regex1.MatchString("test.js") {
45 t.Error("Glob regex should match .js files")
45 t.Error("Glob regex should match .js files")
46 }
46 }
@@ -56,7 +56,7 @@ func TestGlobToRegexCaching(t *testing.T) {
56func BenchmarkRegexCacheVsCompile(b *testing.B) {
56func BenchmarkRegexCacheVsCompile(b *testing.B) {
57 cache := newRegexCache()
57 cache := newRegexCache()
58 pattern := "test.*pattern.*[0-9]+"
58 pattern := "test.*pattern.*[0-9]+"
59−
59+
60 b.Run("WithCache", func(b *testing.B) {
60 b.Run("WithCache", func(b *testing.B) {
61 for b.Loop() {
61 for b.Loop() {
62 _, err := cache.get(pattern)
62 _, err := cache.get(pattern)
@@ -65,7 +65,7 @@ func BenchmarkRegexCacheVsCompile(b *testing.B) {
65 }
65 }
66 }
66 }
67 })
67 })
68−
68+
69 b.Run("WithoutCache", func(b *testing.B) {
69 b.Run("WithoutCache", func(b *testing.B) {
70 for b.Loop() {
70 for b.Loop() {
71 _, err := regexp.Compile(pattern)
71 _, err := regexp.Compile(pattern)
@@ -74,4 +74,4 @@ func BenchmarkRegexCacheVsCompile(b *testing.B) {
74 }
74 }
75 }
75 }
76 })
76 })
77−}
77+}
internal/llm/tools/ls.go+1−1
@@ -209,7 +209,7 @@ func printNode(builder *strings.Builder, node *TreeNode, level int) {
209
209
210 nodeName := node.Name
210 nodeName := node.Name
211 if node.Type == "directory" {
211 if node.Type == "directory" {
212− nodeName += string(filepath.Separator)
212+ nodeName = nodeName + string(filepath.Separator)
213 }
213 }
214
214
215 fmt.Fprintf(builder, "%s- %s\n", indent, nodeName)
215 fmt.Fprintf(builder, "%s- %s\n", indent, nodeName)
internal/tui/components/chat/chat.go+1−1
@@ -500,6 +500,6 @@ func (m *messageListCmp) IsFocused() bool {
500}
500}
501
501
502func (m *messageListCmp) Bindings() []key.Binding {
502func (m *messageListCmp) Bindings() []key.Binding {
503− bindings := layout.KeyMapToSlice(m.defaultListKeyMap)
503+ bindings := m.defaultListKeyMap.KeyBindings()
504 return bindings
504 return bindings
505}
505}
internal/tui/components/chat/editor/editor.go+1−8
@@ -18,7 +18,6 @@ import (
18 "github.com/charmbracelet/crush/internal/session"
18 "github.com/charmbracelet/crush/internal/session"
19 "github.com/charmbracelet/crush/internal/tui/components/chat"
19 "github.com/charmbracelet/crush/internal/tui/components/chat"
20 "github.com/charmbracelet/crush/internal/tui/components/completions"
20 "github.com/charmbracelet/crush/internal/tui/components/completions"
21− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
22 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
21 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
23 "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
22 "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
24 "github.com/charmbracelet/crush/internal/tui/components/dialogs/quit"
23 "github.com/charmbracelet/crush/internal/tui/components/dialogs/quit"
@@ -49,12 +48,6 @@ type editorCmp struct {
49 isCompletionsOpen bool
48 isCompletionsOpen bool
50}
49}
51
50
52−type DeleteAttachmentKeyMaps struct {
53− AttachmentDeleteMode key.Binding
54− Escape key.Binding
55− DeleteAllAttachments key.Binding
56−}
57−
58var DeleteKeyMaps = DeleteAttachmentKeyMaps{
51var DeleteKeyMaps = DeleteAttachmentKeyMaps{
59 AttachmentDeleteMode: key.NewBinding(
52 AttachmentDeleteMode: key.NewBinding(
60 key.WithKeys("ctrl+r"),
53 key.WithKeys("ctrl+r"),
@@ -372,7 +365,7 @@ func (c *editorCmp) IsFocused() bool {
372}
365}
373
366
374func (c *editorCmp) Bindings() []key.Binding {
367func (c *editorCmp) Bindings() []key.Binding {
375− return layout.KeyMapToSlice(c.keyMap)
368+ return c.keyMap.KeyBindings()
376}
369}
377
370
378func NewEditorCmp(app *app.App) util.Model {
371func NewEditorCmp(app *app.App) util.Model {
internal/tui/components/chat/editor/keys.go+15
@@ -27,6 +27,21 @@ func DefaultEditorKeyMap() EditorKeyMap {
27 }
27 }
28}
28}
29
29
30+// KeyBindings implements layout.KeyMapProvider
31+func (k EditorKeyMap) KeyBindings() []key.Binding {
32+ return []key.Binding{
33+ k.AddFile,
34+ k.SendMessage,
35+ k.OpenEditor,
36+ }
37+}
38+
39+type DeleteAttachmentKeyMaps struct {
40+ AttachmentDeleteMode key.Binding
41+ Escape key.Binding
42+ DeleteAllAttachments key.Binding
43+}
44+
30// TODO: update this to use the new keymap concepts
45// TODO: update this to use the new keymap concepts
31var AttachmentsKeyMaps = DeleteAttachmentKeyMaps{
46var AttachmentsKeyMaps = DeleteAttachmentKeyMaps{
32 AttachmentDeleteMode: key.NewBinding(
47 AttachmentDeleteMode: key.NewBinding(
internal/tui/components/chat/sidebar/sidebar.go+1−1
@@ -10,8 +10,8 @@ import (
10 "github.com/charmbracelet/crush/internal/session"
10 "github.com/charmbracelet/crush/internal/session"
11 "github.com/charmbracelet/crush/internal/tui/components/chat"
11 "github.com/charmbracelet/crush/internal/tui/components/chat"
12 "github.com/charmbracelet/crush/internal/tui/components/core"
12 "github.com/charmbracelet/crush/internal/tui/components/core"
13− "github.com/charmbracelet/crush/internal/tui/components/logo"
14 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
13 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
14+ "github.com/charmbracelet/crush/internal/tui/components/logo"
15 "github.com/charmbracelet/crush/internal/tui/styles"
15 "github.com/charmbracelet/crush/internal/tui/styles"
16 "github.com/charmbracelet/crush/internal/tui/util"
16 "github.com/charmbracelet/crush/internal/tui/util"
17 "github.com/charmbracelet/crush/internal/version"
17 "github.com/charmbracelet/crush/internal/version"
internal/tui/components/completions/item.go+1−1
@@ -4,8 +4,8 @@ import (
4 "image/color"
4 "image/color"
5
5
6 tea "github.com/charmbracelet/bubbletea/v2"
6 tea "github.com/charmbracelet/bubbletea/v2"
7− "github.com/charmbracelet/crush/internal/tui/components/core/list"
8 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
7 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
8+ "github.com/charmbracelet/crush/internal/tui/components/core/list"
9 "github.com/charmbracelet/crush/internal/tui/styles"
9 "github.com/charmbracelet/crush/internal/tui/styles"
10 "github.com/charmbracelet/crush/internal/tui/util"
10 "github.com/charmbracelet/crush/internal/tui/util"
11 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/charmbracelet/lipgloss/v2"
internal/tui/components/completions/keys.go+11−2
@@ -2,7 +2,6 @@ package completions
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
33 }
32 }
34}
33}
35
34
35+// KeyBindings implements layout.KeyMapProvider
36+func (k KeyMap) KeyBindings() []key.Binding {
37+ return []key.Binding{
38+ k.Down,
39+ k.Up,
40+ k.Select,
41+ k.Cancel,
42+ }
43+}
44+
36// FullHelp implements help.KeyMap.
45// FullHelp implements help.KeyMap.
37func (k KeyMap) FullHelp() [][]key.Binding {
46func (k KeyMap) FullHelp() [][]key.Binding {
38 m := [][]key.Binding{}
47 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
40 for i := 0; i < len(slice); i += 4 {
49 for i := 0; i < len(slice); i += 4 {
41 end := min(i+4, len(slice))
50 end := min(i+4, len(slice))
42 m = append(m, slice[i:end])
51 m = append(m, slice[i:end])
internal/tui/components/core/layout/layout.go+3−12
@@ -1,8 +1,6 @@
1package layout
1package layout
2
2
3import (
3import (
4− "reflect"
5−
6 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
7 tea "github.com/charmbracelet/bubbletea/v2"
5 tea "github.com/charmbracelet/bubbletea/v2"
8)
6)
@@ -26,14 +24,7 @@ type Positionable interface {
26 SetPosition(x, y int) tea.Cmd
24 SetPosition(x, y int) tea.Cmd
27}
25}
28
26
29−func KeyMapToSlice(t any) (bindings []key.Binding) {
27+// KeyMapProvider defines an interface for types that can provide their key bindings as a slice
30− typ := reflect.TypeOf(t)
28+type KeyMapProvider interface {
31− if typ.Kind() != reflect.Struct {
29+ KeyBindings() []key.Binding
32− return nil
33− }
34− for i := range typ.NumField() {
35− v := reflect.ValueOf(t).Field(i)
36− bindings = append(bindings, v.Interface().(key.Binding))
37− }
38− return
39}
30}
internal/tui/components/core/list/keys.go+16
@@ -51,3 +51,19 @@ func DefaultKeyMap() KeyMap {
51 ),
51 ),
52 }
52 }
53}
53}
54+
55+// KeyBindings implements layout.KeyMapProvider
56+func (k KeyMap) KeyBindings() []key.Binding {
57+ return []key.Binding{
58+ k.Down,
59+ k.Up,
60+ k.NDown,
61+ k.NUp,
62+ k.DownOneItem,
63+ k.UpOneItem,
64+ k.HalfPageDown,
65+ k.HalfPageUp,
66+ k.Home,
67+ k.End,
68+ }
69+}
internal/tui/components/dialogs/commands/item.go+1−1
@@ -3,8 +3,8 @@ package commands
3import (
3import (
4 tea "github.com/charmbracelet/bubbletea/v2"
4 tea "github.com/charmbracelet/bubbletea/v2"
5 "github.com/charmbracelet/crush/internal/tui/components/core"
5 "github.com/charmbracelet/crush/internal/tui/components/core"
6− "github.com/charmbracelet/crush/internal/tui/components/core/list"
7 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
7+ "github.com/charmbracelet/crush/internal/tui/components/core/list"
8 "github.com/charmbracelet/crush/internal/tui/styles"
8 "github.com/charmbracelet/crush/internal/tui/styles"
9 "github.com/charmbracelet/crush/internal/tui/util"
9 "github.com/charmbracelet/crush/internal/tui/util"
10 "github.com/charmbracelet/x/ansi"
10 "github.com/charmbracelet/x/ansi"
internal/tui/components/dialogs/commands/keys.go+22−3
@@ -2,7 +2,6 @@ package commands
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type CommandsDialogKeyMap struct {
7type CommandsDialogKeyMap struct {
@@ -38,10 +37,21 @@ func DefaultCommandsDialogKeyMap() CommandsDialogKeyMap {
38 }
37 }
39}
38}
40
39
40+// KeyBindings implements layout.KeyMapProvider
41+func (k CommandsDialogKeyMap) KeyBindings() []key.Binding {
42+ return []key.Binding{
43+ k.Select,
44+ k.Next,
45+ k.Previous,
46+ k.Tab,
47+ k.Close,
48+ }
49+}
50+
41// FullHelp implements help.KeyMap.
51// FullHelp implements help.KeyMap.
42func (k CommandsDialogKeyMap) FullHelp() [][]key.Binding {
52func (k CommandsDialogKeyMap) FullHelp() [][]key.Binding {
43 m := [][]key.Binding{}
53 m := [][]key.Binding{}
44− slice := layout.KeyMapToSlice(k)
54+ slice := k.KeyBindings()
45 for i := 0; i < len(slice); i += 4 {
55 for i := 0; i < len(slice); i += 4 {
46 end := min(i+4, len(slice))
56 end := min(i+4, len(slice))
47 m = append(m, slice[i:end])
57 m = append(m, slice[i:end])
@@ -86,10 +96,19 @@ func DefaultArgumentsDialogKeyMap() ArgumentsDialogKeyMap {
86 }
96 }
87}
97}
88
98
99+// KeyBindings implements layout.KeyMapProvider
100+func (k ArgumentsDialogKeyMap) KeyBindings() []key.Binding {
101+ return []key.Binding{
102+ k.Confirm,
103+ k.Next,
104+ k.Previous,
105+ }
106+}
107+
89// FullHelp implements help.KeyMap.
108// FullHelp implements help.KeyMap.
90func (k ArgumentsDialogKeyMap) FullHelp() [][]key.Binding {
109func (k ArgumentsDialogKeyMap) FullHelp() [][]key.Binding {
91 m := [][]key.Binding{}
110 m := [][]key.Binding{}
92− slice := layout.KeyMapToSlice(k)
111+ slice := k.KeyBindings()
93 for i := 0; i < len(slice); i += 4 {
112 for i := 0; i < len(slice); i += 4 {
94 end := min(i+4, len(slice))
113 end := min(i+4, len(slice))
95 m = append(m, slice[i:end])
114 m = append(m, slice[i:end])
internal/tui/components/dialogs/compact/keys.go+12−2
@@ -2,7 +2,6 @@ package compact
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8// KeyMap defines the key bindings for the compact dialog.
7// KeyMap defines the key bindings for the compact dialog.
@@ -40,10 +39,21 @@ func DefaultKeyMap() KeyMap {
40 }
39 }
41}
40}
42
41
42+// KeyBindings implements layout.KeyMapProvider
43+func (k KeyMap) KeyBindings() []key.Binding {
44+ return []key.Binding{
45+ k.ChangeSelection,
46+ k.Select,
47+ k.Y,
48+ k.N,
49+ k.Close,
50+ }
51+}
52+
43// FullHelp implements help.KeyMap.
53// FullHelp implements help.KeyMap.
44func (k KeyMap) FullHelp() [][]key.Binding {
54func (k KeyMap) FullHelp() [][]key.Binding {
45 m := [][]key.Binding{}
55 m := [][]key.Binding{}
46− slice := layout.KeyMapToSlice(k)
56+ slice := k.KeyBindings()
47 for i := 0; i < len(slice); i += 4 {
57 for i := 0; i < len(slice); i += 4 {
48 end := min(i+4, len(slice))
58 end := min(i+4, len(slice))
49 m = append(m, slice[i:end])
59 m = append(m, slice[i:end])
internal/tui/components/dialogs/filepicker/keys.go+13−2
@@ -2,7 +2,6 @@ package filepicker
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8// KeyMap defines keyboard bindings for dialog management.
7// KeyMap defines keyboard bindings for dialog management.
@@ -45,10 +44,22 @@ func DefaultKeyMap() KeyMap {
45 }
44 }
46}
45}
47
46
47+// KeyBindings implements layout.KeyMapProvider
48+func (k KeyMap) KeyBindings() []key.Binding {
49+ return []key.Binding{
50+ k.Select,
51+ k.Down,
52+ k.Up,
53+ k.Forward,
54+ k.Backward,
55+ k.Close,
56+ }
57+}
58+
48// FullHelp implements help.KeyMap.
59// FullHelp implements help.KeyMap.
49func (k KeyMap) FullHelp() [][]key.Binding {
60func (k KeyMap) FullHelp() [][]key.Binding {
50 m := [][]key.Binding{}
61 m := [][]key.Binding{}
51− slice := layout.KeyMapToSlice(k)
62+ slice := k.KeyBindings()
52 for i := 0; i < len(slice); i += 4 {
63 for i := 0; i < len(slice); i += 4 {
53 end := min(i+4, len(slice))
64 end := min(i+4, len(slice))
54 m = append(m, slice[i:end])
65 m = append(m, slice[i:end])
internal/tui/components/dialogs/init/keys.go+12−2
@@ -2,7 +2,6 @@ package init
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -38,10 +37,21 @@ func DefaultKeyMap() KeyMap {
38 }
37 }
39}
38}
40
39
40+// KeyBindings implements layout.KeyMapProvider
41+func (k KeyMap) KeyBindings() []key.Binding {
42+ return []key.Binding{
43+ k.ChangeSelection,
44+ k.Select,
45+ k.Y,
46+ k.N,
47+ k.Close,
48+ }
49+}
50+
41// FullHelp implements help.KeyMap.
51// FullHelp implements help.KeyMap.
42func (k KeyMap) FullHelp() [][]key.Binding {
52func (k KeyMap) FullHelp() [][]key.Binding {
43 m := [][]key.Binding{}
53 m := [][]key.Binding{}
44− slice := layout.KeyMapToSlice(k)
54+ slice := k.KeyBindings()
45 for i := 0; i < len(slice); i += 4 {
55 for i := 0; i < len(slice); i += 4 {
46 end := min(i+4, len(slice))
56 end := min(i+4, len(slice))
47 m = append(m, slice[i:end])
57 m = append(m, slice[i:end])
internal/tui/components/dialogs/keys.go+8−2
@@ -2,7 +2,6 @@ package dialogs
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8// KeyMap defines keyboard bindings for dialog management.
7// KeyMap defines keyboard bindings for dialog management.
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
18 }
17 }
19}
18}
20
19
20+// KeyBindings implements layout.KeyMapProvider
21+func (k KeyMap) KeyBindings() []key.Binding {
22+ return []key.Binding{
23+ k.Close,
24+ }
25+}
26+
21// FullHelp implements help.KeyMap.
27// FullHelp implements help.KeyMap.
22func (k KeyMap) FullHelp() [][]key.Binding {
28func (k KeyMap) FullHelp() [][]key.Binding {
23 m := [][]key.Binding{}
29 m := [][]key.Binding{}
24− slice := layout.KeyMapToSlice(k)
30+ slice := k.KeyBindings()
25 for i := 0; i < len(slice); i += 4 {
31 for i := 0; i < len(slice); i += 4 {
26 end := min(i+4, len(slice))
32 end := min(i+4, len(slice))
27 m = append(m, slice[i:end])
33 m = append(m, slice[i:end])
internal/tui/components/dialogs/models/keys.go+11−2
@@ -2,7 +2,6 @@ package models
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
33 }
32 }
34}
33}
35
34
35+// KeyBindings implements layout.KeyMapProvider
36+func (k KeyMap) KeyBindings() []key.Binding {
37+ return []key.Binding{
38+ k.Select,
39+ k.Next,
40+ k.Previous,
41+ k.Close,
42+ }
43+}
44+
36// FullHelp implements help.KeyMap.
45// FullHelp implements help.KeyMap.
37func (k KeyMap) FullHelp() [][]key.Binding {
46func (k KeyMap) FullHelp() [][]key.Binding {
38 m := [][]key.Binding{}
47 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
40 for i := 0; i < len(slice); i += 4 {
49 for i := 0; i < len(slice); i += 4 {
41 end := min(i+4, len(slice))
50 end := min(i+4, len(slice))
42 m = append(m, slice[i:end])
51 m = append(m, slice[i:end])
internal/tui/components/dialogs/permissions/keys.go+14−2
@@ -2,7 +2,6 @@ package permissions
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -48,10 +47,23 @@ func DefaultKeyMap() KeyMap {
48 }
47 }
49}
48}
50
49
50+// KeyBindings implements layout.KeyMapProvider
51+func (k KeyMap) KeyBindings() []key.Binding {
52+ return []key.Binding{
53+ k.Left,
54+ k.Right,
55+ k.Tab,
56+ k.Select,
57+ k.Allow,
58+ k.AllowSession,
59+ k.Deny,
60+ }
61+}
62+
51// FullHelp implements help.KeyMap.
63// FullHelp implements help.KeyMap.
52func (k KeyMap) FullHelp() [][]key.Binding {
64func (k KeyMap) FullHelp() [][]key.Binding {
53 m := [][]key.Binding{}
65 m := [][]key.Binding{}
54− slice := layout.KeyMapToSlice(k)
66+ slice := k.KeyBindings()
55 for i := 0; i < len(slice); i += 4 {
67 for i := 0; i < len(slice); i += 4 {
56 end := min(i+4, len(slice))
68 end := min(i+4, len(slice))
57 m = append(m, slice[i:end])
69 m = append(m, slice[i:end])
internal/tui/components/dialogs/quit/keys.go+13−2
@@ -2,7 +2,6 @@ package quit
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8// KeyMap defines the keyboard bindings for the quit dialog.
7// KeyMap defines the keyboard bindings for the quit dialog.
@@ -44,10 +43,22 @@ func DefaultKeymap() KeyMap {
44 }
43 }
45}
44}
46
45
46+// KeyBindings implements layout.KeyMapProvider
47+func (k KeyMap) KeyBindings() []key.Binding {
48+ return []key.Binding{
49+ k.LeftRight,
50+ k.EnterSpace,
51+ k.Yes,
52+ k.No,
53+ k.Tab,
54+ k.Close,
55+ }
56+}
57+
47// FullHelp implements help.KeyMap.
58// FullHelp implements help.KeyMap.
48func (k KeyMap) FullHelp() [][]key.Binding {
59func (k KeyMap) FullHelp() [][]key.Binding {
49 m := [][]key.Binding{}
60 m := [][]key.Binding{}
50− slice := layout.KeyMapToSlice(k)
61+ slice := k.KeyBindings()
51 for i := 0; i < len(slice); i += 4 {
62 for i := 0; i < len(slice); i += 4 {
52 end := min(i+4, len(slice))
63 end := min(i+4, len(slice))
53 m = append(m, slice[i:end])
64 m = append(m, slice[i:end])
internal/tui/components/dialogs/sessions/keys.go+11−2
@@ -2,7 +2,6 @@ package sessions
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
33 }
32 }
34}
33}
35
34
35+// KeyBindings implements layout.KeyMapProvider
36+func (k KeyMap) KeyBindings() []key.Binding {
37+ return []key.Binding{
38+ k.Select,
39+ k.Next,
40+ k.Previous,
41+ k.Close,
42+ }
43+}
44+
36// FullHelp implements help.KeyMap.
45// FullHelp implements help.KeyMap.
37func (k KeyMap) FullHelp() [][]key.Binding {
46func (k KeyMap) FullHelp() [][]key.Binding {
38 m := [][]key.Binding{}
47 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
40 for i := 0; i < len(slice); i += 4 {
49 for i := 0; i < len(slice); i += 4 {
41 end := min(i+4, len(slice))
50 end := min(i+4, len(slice))
42 m = append(m, slice[i:end])
51 m = append(m, slice[i:end])
internal/tui/page/logs/keys.go+8−2
@@ -2,7 +2,6 @@ package logs
2
2
3import (
3import (
4 "github.com/charmbracelet/bubbles/v2/key"
4 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6)
5)
7
6
8type KeyMap struct {
7type KeyMap struct {
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
18 }
17 }
19}
18}
20
19
20+// KeyBindings implements layout.KeyMapProvider
21+func (k KeyMap) KeyBindings() []key.Binding {
22+ return []key.Binding{
23+ k.Back,
24+ }
25+}
26+
21// FullHelp implements help.KeyMap.
27// FullHelp implements help.KeyMap.
22func (k KeyMap) FullHelp() [][]key.Binding {
28func (k KeyMap) FullHelp() [][]key.Binding {
23 m := [][]key.Binding{}
29 m := [][]key.Binding{}
24− slice := layout.KeyMapToSlice(k)
30+ slice := k.KeyBindings()
25 for i := 0; i < len(slice); i += 4 {
31 for i := 0; i < len(slice); i += 4 {
26 end := min(i+4, len(slice))
32 end := min(i+4, len(slice))
27 m = append(m, slice[i:end])
33 m = append(m, slice[i:end])