perf: drop reflection usage and use string builder

Raphael Amorim created

Change summary

internal/llm/tools/diagnostics.go                   | 32 +++++++-------
internal/llm/tools/grep.go                          | 18 ++++----
internal/llm/tools/ls.go                            |  2 
internal/tui/components/chat/editor/keys.go         | 11 ++++
internal/tui/components/completions/keys.go         | 13 +++++
internal/tui/components/core/layout/layout.go       | 15 +-----
internal/tui/components/core/list/keys.go           | 19 +++++++
internal/tui/components/core/status/keys.go         | 13 +++++
internal/tui/components/dialogs/commands/keys.go    | 25 ++++++++++-
internal/tui/components/dialogs/compact/keys.go     | 14 +++++
internal/tui/components/dialogs/filepicker/keys.go  | 15 ++++++
internal/tui/components/dialogs/init/keys.go        | 14 +++++
internal/tui/components/dialogs/keys.go             | 10 +++
internal/tui/components/dialogs/models/keys.go      | 13 +++++
internal/tui/components/dialogs/permissions/keys.go | 16 ++++++
internal/tui/components/dialogs/quit/keys.go        | 15 ++++++
internal/tui/components/dialogs/sessions/keys.go    | 13 +++++
internal/tui/keys.go                                | 14 +++++
internal/tui/page/chat/keys.go                      | 13 +++++
internal/tui/page/logs/keys.go                      | 10 +++
20 files changed, 224 insertions(+), 71 deletions(-)

Detailed changes

internal/llm/tools/diagnostics.go 🔗

@@ -245,28 +245,28 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
 		return projectDiagnostics[i] < projectDiagnostics[j]
 	})
 
-	output := ""
+	var output strings.Builder
 
 	if len(fileDiagnostics) > 0 {
-		output += "\n<file_diagnostics>\n"
+		output.WriteString("\n<file_diagnostics>\n")
 		if len(fileDiagnostics) > 10 {
-			output += strings.Join(fileDiagnostics[:10], "\n")
-			output += fmt.Sprintf("\n... and %d more diagnostics", len(fileDiagnostics)-10)
+			output.WriteString(strings.Join(fileDiagnostics[:10], "\n"))
+			fmt.Fprintf(&output, "\n... and %d more diagnostics", len(fileDiagnostics)-10)
 		} else {
-			output += strings.Join(fileDiagnostics, "\n")
+			output.WriteString(strings.Join(fileDiagnostics, "\n"))
 		}
-		output += "\n</file_diagnostics>\n"
+		output.WriteString("\n</file_diagnostics>\n")
 	}
 
 	if len(projectDiagnostics) > 0 {
-		output += "\n<project_diagnostics>\n"
+		output.WriteString("\n<project_diagnostics>\n")
 		if len(projectDiagnostics) > 10 {
-			output += strings.Join(projectDiagnostics[:10], "\n")
-			output += fmt.Sprintf("\n... and %d more diagnostics", len(projectDiagnostics)-10)
+			output.WriteString(strings.Join(projectDiagnostics[:10], "\n"))
+			fmt.Fprintf(&output, "\n... and %d more diagnostics", len(projectDiagnostics)-10)
 		} else {
-			output += strings.Join(projectDiagnostics, "\n")
+			output.WriteString(strings.Join(projectDiagnostics, "\n"))
 		}
-		output += "\n</project_diagnostics>\n"
+		output.WriteString("\n</project_diagnostics>\n")
 	}
 
 	if len(fileDiagnostics) > 0 || len(projectDiagnostics) > 0 {
@@ -275,13 +275,13 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
 		projectErrors := countSeverity(projectDiagnostics, "Error")
 		projectWarnings := countSeverity(projectDiagnostics, "Warn")
 
-		output += "\n<diagnostic_summary>\n"
-		output += fmt.Sprintf("Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
-		output += fmt.Sprintf("Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
-		output += "</diagnostic_summary>\n"
+		output.WriteString("\n<diagnostic_summary>\n")
+		fmt.Fprintf(&output, "Current file: %d errors, %d warnings\n", fileErrors, fileWarnings)
+		fmt.Fprintf(&output, "Project: %d errors, %d warnings\n", projectErrors, projectWarnings)
+		output.WriteString("</diagnostic_summary>\n")
 	}
 
-	return output
+	return output.String()
 }
 
 func countSeverity(diagnostics []string, severity string) int {

internal/llm/tools/grep.go 🔗

@@ -198,35 +198,35 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
 		return ToolResponse{}, fmt.Errorf("error searching files: %w", err)
 	}
 
-	var output string
+	var output strings.Builder
 	if len(matches) == 0 {
-		output = "No files found"
+		output.WriteString("No files found")
 	} else {
-		output = fmt.Sprintf("Found %d matches\n", len(matches))
+		fmt.Fprintf(&output, "Found %d matches\n", len(matches))
 
 		currentFile := ""
 		for _, match := range matches {
 			if currentFile != match.path {
 				if currentFile != "" {
-					output += "\n"
+					output.WriteString("\n")
 				}
 				currentFile = match.path
-				output += fmt.Sprintf("%s:\n", match.path)
+				fmt.Fprintf(&output, "%s:\n", match.path)
 			}
 			if match.lineNum > 0 {
-				output += fmt.Sprintf("  Line %d: %s\n", match.lineNum, match.lineText)
+				fmt.Fprintf(&output, "  Line %d: %s\n", match.lineNum, match.lineText)
 			} else {
-				output += fmt.Sprintf("  %s\n", match.path)
+				fmt.Fprintf(&output, "  %s\n", match.path)
 			}
 		}
 
 		if truncated {
-			output += "\n(Results are truncated. Consider using a more specific path or pattern.)"
+			output.WriteString("\n(Results are truncated. Consider using a more specific path or pattern.)")
 		}
 	}
 
 	return WithResponseMetadata(
-		NewTextResponse(output),
+		NewTextResponse(output.String()),
 		GrepResponseMetadata{
 			NumberOfMatches: len(matches),
 			Truncated:       truncated,

internal/llm/tools/ls.go 🔗

@@ -209,7 +209,7 @@ func printNode(builder *strings.Builder, node *TreeNode, level int) {
 
 	nodeName := node.Name
 	if node.Type == "directory" {
-		nodeName += string(filepath.Separator)
+		nodeName = nodeName + string(filepath.Separator)
 	}
 
 	fmt.Fprintf(builder, "%s- %s\n", indent, nodeName)

internal/tui/components/chat/editor/keys.go 🔗

@@ -2,7 +2,6 @@ package editor
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type EditorKeyMap struct {
@@ -23,10 +22,18 @@ func DefaultEditorKeyMap() EditorKeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k EditorKeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Send,
+		k.OpenEditor,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k EditorKeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/completions/keys.go 🔗

@@ -2,7 +2,6 @@ package completions
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Down,
+		k.Up,
+		k.Select,
+		k.Cancel,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/core/layout/layout.go 🔗

@@ -1,8 +1,6 @@
 package layout
 
 import (
-	"reflect"
-
 	"github.com/charmbracelet/bubbles/v2/help"
 	"github.com/charmbracelet/bubbles/v2/key"
 	tea "github.com/charmbracelet/bubbletea/v2"
@@ -27,14 +25,7 @@ type Positionable interface {
 	SetPosition(x, y int) tea.Cmd
 }
 
-func KeyMapToSlice(t any) (bindings []key.Binding) {
-	typ := reflect.TypeOf(t)
-	if typ.Kind() != reflect.Struct {
-		return nil
-	}
-	for i := range typ.NumField() {
-		v := reflect.ValueOf(t).Field(i)
-		bindings = append(bindings, v.Interface().(key.Binding))
-	}
-	return
+// KeyMapProvider defines an interface for types that can provide their key bindings as a slice
+type KeyMapProvider interface {
+	KeyBindings() []key.Binding
 }

internal/tui/components/core/list/keys.go 🔗

@@ -2,7 +2,6 @@ package list
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -53,10 +52,26 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Down,
+		k.Up,
+		k.NDown,
+		k.NUp,
+		k.DownOneItem,
+		k.UpOneItem,
+		k.HalfPageDown,
+		k.HalfPageUp,
+		k.Home,
+		k.End,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/core/status/keys.go 🔗

@@ -2,7 +2,6 @@ package status
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap(tabHelp string) KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Tab,
+		k.Commands,
+		k.Sessions,
+		k.Help,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/commands/keys.go 🔗

@@ -2,7 +2,6 @@ package commands
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type CommandsDialogKeyMap struct {
@@ -38,10 +37,21 @@ func DefaultCommandsDialogKeyMap() CommandsDialogKeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k CommandsDialogKeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Select,
+		k.Next,
+		k.Previous,
+		k.Tab,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k CommandsDialogKeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])
@@ -86,10 +96,19 @@ func DefaultArgumentsDialogKeyMap() ArgumentsDialogKeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k ArgumentsDialogKeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Confirm,
+		k.Next,
+		k.Previous,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k ArgumentsDialogKeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/compact/keys.go 🔗

@@ -2,7 +2,6 @@ package compact
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 // KeyMap defines the key bindings for the compact dialog.
@@ -40,10 +39,21 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.ChangeSelection,
+		k.Select,
+		k.Y,
+		k.N,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/filepicker/keys.go 🔗

@@ -2,7 +2,6 @@ package filepicker
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 // KeyMap defines keyboard bindings for dialog management.
@@ -45,10 +44,22 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Select,
+		k.Down,
+		k.Up,
+		k.Forward,
+		k.Backward,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/init/keys.go 🔗

@@ -2,7 +2,6 @@ package init
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -38,10 +37,21 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.ChangeSelection,
+		k.Select,
+		k.Y,
+		k.N,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/keys.go 🔗

@@ -2,7 +2,6 @@ package dialogs
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 // KeyMap defines keyboard bindings for dialog management.
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/models/keys.go 🔗

@@ -2,7 +2,6 @@ package models
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Select,
+		k.Next,
+		k.Previous,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/permissions/keys.go 🔗

@@ -2,7 +2,6 @@ package permissions
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -48,10 +47,23 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Left,
+		k.Right,
+		k.Tab,
+		k.Select,
+		k.Allow,
+		k.AllowSession,
+		k.Deny,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/quit/keys.go 🔗

@@ -2,7 +2,6 @@ package quit
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 // KeyMap defines the keyboard bindings for the quit dialog.
@@ -44,10 +43,22 @@ func DefaultKeymap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.LeftRight,
+		k.EnterSpace,
+		k.Yes,
+		k.No,
+		k.Tab,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/components/dialogs/sessions/keys.go 🔗

@@ -2,7 +2,6 @@ package sessions
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Select,
+		k.Next,
+		k.Previous,
+		k.Close,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/keys.go 🔗

@@ -2,7 +2,6 @@ package tui
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -42,7 +41,7 @@ func DefaultKeyMap() KeyMap {
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])
@@ -50,6 +49,17 @@ func (k KeyMap) FullHelp() [][]key.Binding {
 	return m
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Logs,
+		k.Quit,
+		k.Help,
+		k.Commands,
+		k.Sessions,
+	}
+}
+
 // ShortHelp implements help.KeyMap.
 func (k KeyMap) ShortHelp() []key.Binding {
 	return []key.Binding{}

internal/tui/page/chat/keys.go 🔗

@@ -2,7 +2,6 @@ package chat
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -33,10 +32,20 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.NewSession,
+		k.FilePicker,
+		k.Cancel,
+		k.Tab,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])

internal/tui/page/logs/keys.go 🔗

@@ -2,7 +2,6 @@ package logs
 
 import (
 	"github.com/charmbracelet/bubbles/v2/key"
-	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 )
 
 type KeyMap struct {
@@ -18,10 +17,17 @@ func DefaultKeyMap() KeyMap {
 	}
 }
 
+// KeyBindings implements layout.KeyMapProvider
+func (k KeyMap) KeyBindings() []key.Binding {
+	return []key.Binding{
+		k.Back,
+	}
+}
+
 // FullHelp implements help.KeyMap.
 func (k KeyMap) FullHelp() [][]key.Binding {
 	m := [][]key.Binding{}
-	slice := layout.KeyMapToSlice(k)
+	slice := k.KeyBindings()
 	for i := 0; i < len(slice); i += 4 {
 		end := min(i+4, len(slice))
 		m = append(m, slice[i:end])