package tui import ( "slices" tea "github.com/charmbracelet/bubbletea" ) type KeyBindingScope int const ( ScopeGlobal KeyBindingScope = iota ScopeTimerBox ScopeProjectsBox ScopeHistoryBoxSummaries ScopeHistoryBoxDetails ScopeModal ) // KeyBinding represents the available key bindings for a view type KeyBinding struct { Key string Description func(AppModel) string Result func(*AppModel) tea.Cmd Hide bool } var Bindings map[KeyBindingScope]map[string]KeyBinding = map[KeyBindingScope]map[string]KeyBinding{ ScopeGlobal: { "ctrl+n": KeyBinding{ Key: "Ctrl+n", Description: func(AppModel) string { return "Next Pane" }, Result: func(*AppModel) tea.Cmd { return navigate(true) }, }, "ctrl+p": KeyBinding{ Key: "Ctrl+p", Description: func(AppModel) string { return "Prev Pane" }, Result: func(*AppModel) tea.Cmd { return navigate(false) }, }, "p": KeyBinding{ Key: "p", Description: func(am AppModel) string { if am.timerBox.timerInfo.IsActive { return "Punch Out" } return "Punch In" }, Result: func(am *AppModel) tea.Cmd { if am.timerBox.timerInfo.IsActive { return punchOut(*am) } return punchIn(*am) }, }, "r": KeyBinding{ Key: "r", Description: func(am AppModel) string { return "Refresh" }, Result: func(am *AppModel) tea.Cmd { return am.refreshCmd }, }, "q": KeyBinding{ Key: "q", Description: func(am AppModel) string { return "Quit" }, Result: func(*AppModel) tea.Cmd { return tea.Quit }, }, "ctrl+c": KeyBinding{ Key: "Ctrl+c", Description: func(am AppModel) string { return "Quit" }, Result: func(*AppModel) tea.Cmd { return tea.Quit }, Hide: true, }, "ctrl+d": KeyBinding{ Key: "Ctrl+d", Description: func(am AppModel) string { return "Quit" }, Result: func(*AppModel) tea.Cmd { return tea.Quit }, Hide: true, }, }, ScopeTimerBox: { "enter": KeyBinding{ Key: "Enter", Description: func(am AppModel) string { if am.timerBox.timerInfo.IsActive { return "Punch Out" } return "Punch In" }, Result: func(am *AppModel) tea.Cmd { if am.timerBox.timerInfo.IsActive { return punchOut(*am) } return punchIn(*am) }, }, }, ScopeProjectsBox: { "j": KeyBinding{ Key: "j", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, }, "k": KeyBinding{ Key: "k", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, }, "down": KeyBinding{ Key: "down", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, Hide: true, }, "up": KeyBinding{ Key: "up", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, Hide: true, }, "enter": KeyBinding{ Key: "Enter", Description: func(AppModel) string { return "Punch In on Selection" }, Result: func(am *AppModel) tea.Cmd { return punchInOnSelection(*am) }, }, "n": KeyBinding{ Key: "n", Description: func(AppModel) string { return "New Project" }, Result: func(*AppModel) tea.Cmd { return createProjectModal() }, }, "N": KeyBinding{ Key: "N", Description: func(AppModel) string { return "New Client" }, Result: func(*AppModel) tea.Cmd { return createClientModal() }, }, }, ScopeHistoryBoxSummaries: { "j": KeyBinding{ Key: "j", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, }, "k": KeyBinding{ Key: "k", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, }, "down": KeyBinding{ Key: "down", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, Hide: true, }, "up": KeyBinding{ Key: "up", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, Hide: true, }, "enter": KeyBinding{ Key: "Enter", Description: func(AppModel) string { return "Select" }, Result: func(*AppModel) tea.Cmd { return selectHistorySummary() }, }, }, ScopeHistoryBoxDetails: { "j": KeyBinding{ Key: "j", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, }, "k": KeyBinding{ Key: "k", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, }, "down": KeyBinding{ Key: "Down", Description: func(AppModel) string { return "Down" }, Result: func(*AppModel) tea.Cmd { return changeSelection(true) }, Hide: true, }, "up": KeyBinding{ Key: "Up", Description: func(AppModel) string { return "Up" }, Result: func(*AppModel) tea.Cmd { return changeSelection(false) }, Hide: true, }, "e": KeyBinding{ Key: "e", Description: func(AppModel) string { return "Edit" }, Result: func(am *AppModel) tea.Cmd { return editCurrentEntry() }, }, "d": KeyBinding{ Key: "d", Description: func(AppModel) string { return "Delete" }, Result: func(*AppModel) tea.Cmd { return confirmDeleteEntry() }, }, "enter": KeyBinding{ Key: "Enter", Description: func(AppModel) string { return "Resume" }, Result: func(am *AppModel) tea.Cmd { return punchInOnSelection(*am) }, }, "b": KeyBinding{ Key: "b", Description: func(AppModel) string { return "Back" }, Result: func(*AppModel) tea.Cmd { return backToHistorySummary() }, }, "esc": KeyBinding{ Key: "Esc", Description: func(AppModel) string { return "Back" }, Result: func(*AppModel) tea.Cmd { return backToHistorySummary() }, Hide: true, }, }, ScopeModal: { "enter": KeyBinding{ Key: "Enter", Description: func(AppModel) string { return "Submit" }, Result: func(am *AppModel) tea.Cmd { return tea.Sequence( closeModal(), am.modalBox.SubmitForm(*am), ) }, }, "esc": KeyBinding{ Key: "Esc", Description: func(AppModel) string { return "Close" }, Result: func(*AppModel) tea.Cmd { return closeModal() }, }, }, } // HandleKeyPress processes key messages and returns the appropriate action func HandleKeyPress(msg tea.KeyMsg, data *AppModel) tea.Cmd { key := msg.String() if data.modalBox.Active { if binding, ok := Bindings[ScopeModal][key]; ok { return binding.Result(data) } return data.modalBox.HandleKeyPress(msg) } if binding, ok := Bindings[ScopeGlobal][key]; ok { return binding.Result(data) } var local map[string]KeyBinding switch data.selectedBox { case TimerBox: local = Bindings[ScopeTimerBox] case ProjectsBox: local = Bindings[ScopeProjectsBox] case HistoryBox: switch data.historyBox.viewLevel { case HistoryLevelSummary: local = Bindings[ScopeHistoryBoxSummaries] case HistoryLevelDetails: local = Bindings[ScopeHistoryBoxDetails] } } if binding, ok := local[key]; ok { return binding.Result(data) } return nil } func activeBindings(box BoxType, level HistoryViewLevel, modal ModalBoxModel) []KeyBinding { out := make([]KeyBinding, 0, len(Bindings[ScopeGlobal])) if modal.Active { for _, binding := range Bindings[ScopeModal] { out = append(out, binding) } } else { for _, binding := range Bindings[ScopeGlobal] { out = append(out, binding) } var scope KeyBindingScope switch box { case TimerBox: scope = ScopeTimerBox case ProjectsBox: scope = ScopeProjectsBox case HistoryBox: switch level { case HistoryLevelSummary: scope = ScopeHistoryBoxSummaries case HistoryLevelDetails: scope = ScopeHistoryBoxDetails } } for _, binding := range Bindings[scope] { out = append(out, binding) } } slices.SortFunc(out, func(a, b KeyBinding) int { if a.Key < b.Key { return -1 } return 1 }) return out }