on main help sign in
Merge pull request #31 from charmbracelet/drop-reflection-and-use-string-builder

perf: drop reflection usage and use string builder

commitd9577ecb5af194c65118a0e460b07290d72ea998 authorRaphael Amorim <rapha850@gmail.com> dateMon 16 Jun 2025, 15:08 (1 year ago) parent5b68d7a032e738
23 files changed+209−82
internal/llm/tools/diagnostics.go+16−16 internal/llm/tools/grep.go+9−9 internal/llm/tools/grep_test.go+9−9 internal/llm/tools/ls.go+1−1 internal/tui/components/chat/chat.go+1−1 internal/tui/components/chat/editor/editor.go+1−8 internal/tui/components/chat/editor/keys.go+15 internal/tui/components/chat/sidebar/sidebar.go+1−1 internal/tui/components/completions/item.go+1−1 internal/tui/components/completions/keys.go+11−2 internal/tui/components/core/layout/layout.go+3−12 internal/tui/components/core/list/keys.go+16 internal/tui/components/dialogs/commands/item.go+1−1 internal/tui/components/dialogs/commands/keys.go+22−3 internal/tui/components/dialogs/compact/keys.go+12−2 internal/tui/components/dialogs/filepicker/keys.go+13−2 internal/tui/components/dialogs/init/keys.go+12−2 internal/tui/components/dialogs/keys.go+8−2 internal/tui/components/dialogs/models/keys.go+11−2 internal/tui/components/dialogs/permissions/keys.go+14−2 internal/tui/components/dialogs/quit/keys.go+13−2 internal/tui/components/dialogs/sessions/keys.go+11−2 internal/tui/page/logs/keys.go+8−2
internal/llm/tools/diagnostics.go+16−16
@@ -245,28 +245,28 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
245245 return projectDiagnostics[i] < projectDiagnostics[j]
246246 })
247247
248− output := ""
248+ var output strings.Builder
249249
250250 if len(fileDiagnostics) > 0 {
251− output += "\n<file_diagnostics>\n"
251+ output.WriteString("\n<file_diagnostics>\n")
252252 if len(fileDiagnostics) > 10 {
253− output += strings.Join(fileDiagnostics[:10], "\n")
254− output += fmt.Sprintf("\n... and %d more diagnostics", len(fileDiagnostics)-10)
253+ output.WriteString(strings.Join(fileDiagnostics[:10], "\n"))
254+ fmt.Fprintf(&output, "\n... and %d more diagnostics", len(fileDiagnostics)-10)
255255 } else {
256− output += strings.Join(fileDiagnostics, "\n")
256+ output.WriteString(strings.Join(fileDiagnostics, "\n"))
257257 }
258− output += "\n</file_diagnostics>\n"
258+ output.WriteString("\n</file_diagnostics>\n")
259259 }
260260
261261 if len(projectDiagnostics) > 0 {
262− output += "\n<project_diagnostics>\n"
262+ output.WriteString("\n<project_diagnostics>\n")
263263 if len(projectDiagnostics) > 10 {
264− output += strings.Join(projectDiagnostics[:10], "\n")
265− output += fmt.Sprintf("\n... and %d more diagnostics", len(projectDiagnostics)-10)
264+ output.WriteString(strings.Join(projectDiagnostics[:10], "\n"))
265+ fmt.Fprintf(&output, "\n... and %d more diagnostics", len(projectDiagnostics)-10)
266266 } else {
267− output += strings.Join(projectDiagnostics, "\n")
267+ output.WriteString(strings.Join(projectDiagnostics, "\n"))
268268 }
269− output += "\n</project_diagnostics>\n"
269+ output.WriteString("\n</project_diagnostics>\n")
270270 }
271271
272272 if len(fileDiagnostics) > 0 || len(projectDiagnostics) > 0 {
@@ -275,13 +275,13 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
275275 projectErrors := countSeverity(projectDiagnostics, "Error")
276276 projectWarnings := countSeverity(projectDiagnostics, "Warn")
277277
278− output += "\n<diagnostic_summary>\n"
279− output += fmt.Sprintf("Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
280− output += fmt.Sprintf("Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
281− output += "</diagnostic_summary>\n"
278+ output.WriteString("\n<diagnostic_summary>\n")
279+ fmt.Fprintf(&output, "Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
280+ fmt.Fprintf(&output, "Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
281+ output.WriteString("</diagnostic_summary>\n")
282282 }
283283
284− return output
284+ return output.String()
285285}
286286
287287func 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)
198198 return ToolResponse{}, fmt.Errorf("error searching files: %w", err)
199199 }
200200
201− var output string
201+ var output strings.Builder
202202 if len(matches) == 0 {
203− output = "No files found"
203+ output.WriteString("No files found")
204204 } else {
205− output = fmt.Sprintf("Found %d matches\n", len(matches))
205+ fmt.Fprintf(&output, "Found %d matches\n", len(matches))
206206
207207 currentFile := ""
208208 for _, match := range matches {
209209 if currentFile != match.path {
210210 if currentFile != "" {
211− output += "\n"
211+ output.WriteString("\n")
212212 }
213213 currentFile = match.path
214− output += fmt.Sprintf("%s:\n", match.path)
214+ fmt.Fprintf(&output, "%s:\n", match.path)
215215 }
216216 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)
218218 } else {
219− output += fmt.Sprintf(" %s\n", match.path)
219+ fmt.Fprintf(&output, " %s\n", match.path)
220220 }
221221 }
222222
223223 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.)")
225225 }
226226 }
227227
228228 return WithResponseMetadata(
229− NewTextResponse(output),
229+ NewTextResponse(output.String()),
230230 GrepResponseMetadata{
231231 NumberOfMatches: len(matches),
232232 Truncated: truncated,
internal/llm/tools/grep_test.go+9−9
@@ -7,24 +7,24 @@ import (
77
88func TestRegexCache(t *testing.T) {
99 cache := newRegexCache()
10−
10+
1111 // Test basic caching
1212 pattern := "test.*pattern"
1313 regex1, err := cache.get(pattern)
1414 if err != nil {
1515 t.Fatalf("Failed to compile regex: %v", err)
1616 }
17−
17+
1818 regex2, err := cache.get(pattern)
1919 if err != nil {
2020 t.Fatalf("Failed to get cached regex: %v", err)
2121 }
22−
22+
2323 // Should be the same instance (cached)
2424 if regex1 != regex2 {
2525 t.Error("Expected cached regex to be the same instance")
2626 }
27−
27+
2828 // Test that it actually works
2929 if !regex1.MatchString("test123pattern") {
3030 t.Error("Regex should match test string")
@@ -34,13 +34,13 @@ func TestRegexCache(t *testing.T) {
3434func TestGlobToRegexCaching(t *testing.T) {
3535 // Test that globToRegex uses pre-compiled regex
3636 pattern1 := globToRegex("*.{js,ts}")
37−
37+
3838 // Should not panic and should work correctly
3939 regex1, err := regexp.Compile(pattern1)
4040 if err != nil {
4141 t.Fatalf("Failed to compile glob regex: %v", err)
4242 }
43−
43+
4444 if !regex1.MatchString("test.js") {
4545 t.Error("Glob regex should match .js files")
4646 }
@@ -56,7 +56,7 @@ func TestGlobToRegexCaching(t *testing.T) {
5656func BenchmarkRegexCacheVsCompile(b *testing.B) {
5757 cache := newRegexCache()
5858 pattern := "test.*pattern.*[0-9]+"
59−
59+
6060 b.Run("WithCache", func(b *testing.B) {
6161 for b.Loop() {
6262 _, err := cache.get(pattern)
@@ -65,7 +65,7 @@ func BenchmarkRegexCacheVsCompile(b *testing.B) {
6565 }
6666 }
6767 })
68−
68+
6969 b.Run("WithoutCache", func(b *testing.B) {
7070 for b.Loop() {
7171 _, err := regexp.Compile(pattern)
@@ -74,4 +74,4 @@ func BenchmarkRegexCacheVsCompile(b *testing.B) {
7474 }
7575 }
7676 })
77−}
77+}
internal/llm/tools/ls.go+1−1
@@ -209,7 +209,7 @@ func printNode(builder *strings.Builder, node *TreeNode, level int) {
209209
210210 nodeName := node.Name
211211 if node.Type == "directory" {
212− nodeName += string(filepath.Separator)
212+ nodeName = nodeName + string(filepath.Separator)
213213 }
214214
215215 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 {
500500}
501501
502502func (m *messageListCmp) Bindings() []key.Binding {
503− bindings := layout.KeyMapToSlice(m.defaultListKeyMap)
503+ bindings := m.defaultListKeyMap.KeyBindings()
504504 return bindings
505505}
internal/tui/components/chat/editor/editor.go+1−8
@@ -18,7 +18,6 @@ import (
1818 "github.com/charmbracelet/crush/internal/session"
1919 "github.com/charmbracelet/crush/internal/tui/components/chat"
2020 "github.com/charmbracelet/crush/internal/tui/components/completions"
21− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
2221 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
2322 "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
2423 "github.com/charmbracelet/crush/internal/tui/components/dialogs/quit"
@@ -49,12 +48,6 @@ type editorCmp struct {
4948 isCompletionsOpen bool
5049}
5150
52−type DeleteAttachmentKeyMaps struct {
53− AttachmentDeleteMode key.Binding
54− Escape key.Binding
55− DeleteAllAttachments key.Binding
56−}
57−
5851var DeleteKeyMaps = DeleteAttachmentKeyMaps{
5952 AttachmentDeleteMode: key.NewBinding(
6053 key.WithKeys("ctrl+r"),
@@ -372,7 +365,7 @@ func (c *editorCmp) IsFocused() bool {
372365}
373366
374367func (c *editorCmp) Bindings() []key.Binding {
375− return layout.KeyMapToSlice(c.keyMap)
368+ return c.keyMap.KeyBindings()
376369}
377370
378371func NewEditorCmp(app *app.App) util.Model {
internal/tui/components/chat/editor/keys.go+15
@@ -27,6 +27,21 @@ func DefaultEditorKeyMap() EditorKeyMap {
2727 }
2828}
2929
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+
3045// TODO: update this to use the new keymap concepts
3146var AttachmentsKeyMaps = DeleteAttachmentKeyMaps{
3247 AttachmentDeleteMode: key.NewBinding(
internal/tui/components/chat/sidebar/sidebar.go+1−1
@@ -10,8 +10,8 @@ import (
1010 "github.com/charmbracelet/crush/internal/session"
1111 "github.com/charmbracelet/crush/internal/tui/components/chat"
1212 "github.com/charmbracelet/crush/internal/tui/components/core"
13− "github.com/charmbracelet/crush/internal/tui/components/logo"
1413 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
14+ "github.com/charmbracelet/crush/internal/tui/components/logo"
1515 "github.com/charmbracelet/crush/internal/tui/styles"
1616 "github.com/charmbracelet/crush/internal/tui/util"
1717 "github.com/charmbracelet/crush/internal/version"
internal/tui/components/completions/item.go+1−1
@@ -4,8 +4,8 @@ import (
44 "image/color"
55
66 tea "github.com/charmbracelet/bubbletea/v2"
7− "github.com/charmbracelet/crush/internal/tui/components/core/list"
87 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
8+ "github.com/charmbracelet/crush/internal/tui/components/core/list"
99 "github.com/charmbracelet/crush/internal/tui/styles"
1010 "github.com/charmbracelet/crush/internal/tui/util"
1111 "github.com/charmbracelet/lipgloss/v2"
internal/tui/components/completions/keys.go+11−2
@@ -2,7 +2,6 @@ package completions
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
3332 }
3433}
3534
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+
3645// FullHelp implements help.KeyMap.
3746func (k KeyMap) FullHelp() [][]key.Binding {
3847 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
4049 for i := 0; i < len(slice); i += 4 {
4150 end := min(i+4, len(slice))
4251 m = append(m, slice[i:end])
internal/tui/components/core/layout/layout.go+3−12
@@ -1,8 +1,6 @@
11package layout
22
33import (
4− "reflect"
5−
64 "github.com/charmbracelet/bubbles/v2/key"
75 tea "github.com/charmbracelet/bubbletea/v2"
86)
@@ -26,14 +24,7 @@ type Positionable interface {
2624 SetPosition(x, y int) tea.Cmd
2725}
2826
29−func KeyMapToSlice(t any) (bindings []key.Binding) {
30− typ := reflect.TypeOf(t)
31− if typ.Kind() != reflect.Struct {
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
27+// KeyMapProvider defines an interface for types that can provide their key bindings as a slice
28+type KeyMapProvider interface {
29+ KeyBindings() []key.Binding
3930}
internal/tui/components/core/list/keys.go+16
@@ -51,3 +51,19 @@ func DefaultKeyMap() KeyMap {
5151 ),
5252 }
5353}
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
33import (
44 tea "github.com/charmbracelet/bubbletea/v2"
55 "github.com/charmbracelet/crush/internal/tui/components/core"
6− "github.com/charmbracelet/crush/internal/tui/components/core/list"
76 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
7+ "github.com/charmbracelet/crush/internal/tui/components/core/list"
88 "github.com/charmbracelet/crush/internal/tui/styles"
99 "github.com/charmbracelet/crush/internal/tui/util"
1010 "github.com/charmbracelet/x/ansi"
internal/tui/components/dialogs/commands/keys.go+22−3
@@ -2,7 +2,6 @@ package commands
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type CommandsDialogKeyMap struct {
@@ -38,10 +37,21 @@ func DefaultCommandsDialogKeyMap() CommandsDialogKeyMap {
3837 }
3938}
4039
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+
4151// FullHelp implements help.KeyMap.
4252func (k CommandsDialogKeyMap) FullHelp() [][]key.Binding {
4353 m := [][]key.Binding{}
44− slice := layout.KeyMapToSlice(k)
54+ slice := k.KeyBindings()
4555 for i := 0; i < len(slice); i += 4 {
4656 end := min(i+4, len(slice))
4757 m = append(m, slice[i:end])
@@ -86,10 +96,19 @@ func DefaultArgumentsDialogKeyMap() ArgumentsDialogKeyMap {
8696 }
8797}
8898
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+
89108// FullHelp implements help.KeyMap.
90109func (k ArgumentsDialogKeyMap) FullHelp() [][]key.Binding {
91110 m := [][]key.Binding{}
92− slice := layout.KeyMapToSlice(k)
111+ slice := k.KeyBindings()
93112 for i := 0; i < len(slice); i += 4 {
94113 end := min(i+4, len(slice))
95114 m = append(m, slice[i:end])
internal/tui/components/dialogs/compact/keys.go+12−2
@@ -2,7 +2,6 @@ package compact
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87// KeyMap defines the key bindings for the compact dialog.
@@ -40,10 +39,21 @@ func DefaultKeyMap() KeyMap {
4039 }
4140}
4241
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+
4353// FullHelp implements help.KeyMap.
4454func (k KeyMap) FullHelp() [][]key.Binding {
4555 m := [][]key.Binding{}
46− slice := layout.KeyMapToSlice(k)
56+ slice := k.KeyBindings()
4757 for i := 0; i < len(slice); i += 4 {
4858 end := min(i+4, len(slice))
4959 m = append(m, slice[i:end])
internal/tui/components/dialogs/filepicker/keys.go+13−2
@@ -2,7 +2,6 @@ package filepicker
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87// KeyMap defines keyboard bindings for dialog management.
@@ -45,10 +44,22 @@ func DefaultKeyMap() KeyMap {
4544 }
4645}
4746
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+
4859// FullHelp implements help.KeyMap.
4960func (k KeyMap) FullHelp() [][]key.Binding {
5061 m := [][]key.Binding{}
51− slice := layout.KeyMapToSlice(k)
62+ slice := k.KeyBindings()
5263 for i := 0; i < len(slice); i += 4 {
5364 end := min(i+4, len(slice))
5465 m = append(m, slice[i:end])
internal/tui/components/dialogs/init/keys.go+12−2
@@ -2,7 +2,6 @@ package init
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -38,10 +37,21 @@ func DefaultKeyMap() KeyMap {
3837 }
3938}
4039
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+
4151// FullHelp implements help.KeyMap.
4252func (k KeyMap) FullHelp() [][]key.Binding {
4353 m := [][]key.Binding{}
44− slice := layout.KeyMapToSlice(k)
54+ slice := k.KeyBindings()
4555 for i := 0; i < len(slice); i += 4 {
4656 end := min(i+4, len(slice))
4757 m = append(m, slice[i:end])
internal/tui/components/dialogs/keys.go+8−2
@@ -2,7 +2,6 @@ package dialogs
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87// KeyMap defines keyboard bindings for dialog management.
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
1817 }
1918}
2019
20+// KeyBindings implements layout.KeyMapProvider
21+func (k KeyMap) KeyBindings() []key.Binding {
22+ return []key.Binding{
23+ k.Close,
24+ }
25+}
26+
2127// FullHelp implements help.KeyMap.
2228func (k KeyMap) FullHelp() [][]key.Binding {
2329 m := [][]key.Binding{}
24− slice := layout.KeyMapToSlice(k)
30+ slice := k.KeyBindings()
2531 for i := 0; i < len(slice); i += 4 {
2632 end := min(i+4, len(slice))
2733 m = append(m, slice[i:end])
internal/tui/components/dialogs/models/keys.go+11−2
@@ -2,7 +2,6 @@ package models
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
3332 }
3433}
3534
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+
3645// FullHelp implements help.KeyMap.
3746func (k KeyMap) FullHelp() [][]key.Binding {
3847 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
4049 for i := 0; i < len(slice); i += 4 {
4150 end := min(i+4, len(slice))
4251 m = append(m, slice[i:end])
internal/tui/components/dialogs/permissions/keys.go+14−2
@@ -2,7 +2,6 @@ package permissions
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -48,10 +47,23 @@ func DefaultKeyMap() KeyMap {
4847 }
4948}
5049
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+
5163// FullHelp implements help.KeyMap.
5264func (k KeyMap) FullHelp() [][]key.Binding {
5365 m := [][]key.Binding{}
54− slice := layout.KeyMapToSlice(k)
66+ slice := k.KeyBindings()
5567 for i := 0; i < len(slice); i += 4 {
5668 end := min(i+4, len(slice))
5769 m = append(m, slice[i:end])
internal/tui/components/dialogs/quit/keys.go+13−2
@@ -2,7 +2,6 @@ package quit
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87// KeyMap defines the keyboard bindings for the quit dialog.
@@ -44,10 +43,22 @@ func DefaultKeymap() KeyMap {
4443 }
4544}
4645
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+
4758// FullHelp implements help.KeyMap.
4859func (k KeyMap) FullHelp() [][]key.Binding {
4960 m := [][]key.Binding{}
50− slice := layout.KeyMapToSlice(k)
61+ slice := k.KeyBindings()
5162 for i := 0; i < len(slice); i += 4 {
5263 end := min(i+4, len(slice))
5364 m = append(m, slice[i:end])
internal/tui/components/dialogs/sessions/keys.go+11−2
@@ -2,7 +2,6 @@ package sessions
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
3332 }
3433}
3534
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+
3645// FullHelp implements help.KeyMap.
3746func (k KeyMap) FullHelp() [][]key.Binding {
3847 m := [][]key.Binding{}
39− slice := layout.KeyMapToSlice(k)
48+ slice := k.KeyBindings()
4049 for i := 0; i < len(slice); i += 4 {
4150 end := min(i+4, len(slice))
4251 m = append(m, slice[i:end])
internal/tui/page/logs/keys.go+8−2
@@ -2,7 +2,6 @@ package logs
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
5− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
65)
76
87type KeyMap struct {
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
1817 }
1918}
2019
20+// KeyBindings implements layout.KeyMapProvider
21+func (k KeyMap) KeyBindings() []key.Binding {
22+ return []key.Binding{
23+ k.Back,
24+ }
25+}
26+
2127// FullHelp implements help.KeyMap.
2228func (k KeyMap) FullHelp() [][]key.Binding {
2329 m := [][]key.Binding{}
24− slice := layout.KeyMapToSlice(k)
30+ slice := k.KeyBindings()
2531 for i := 0; i < len(slice); i += 4 {
2632 end := min(i+4, len(slice))
2733 m = append(m, slice[i:end])