diff --git a/internal/github/usecase/usecase_test.go b/internal/github/usecase/usecase_test.go index 84ffba7..fe40a7e 100644 --- a/internal/github/usecase/usecase_test.go +++ b/internal/github/usecase/usecase_test.go @@ -71,6 +71,12 @@ func TestUseCase_TriggerWorkflow(t *testing.T) { }) if err != nil { t.Error(err) + return // Exit early if there's an error + } + + if workflow == nil || workflow.Workflow == nil { + t.Error("workflow or workflow.Workflow is nil") + return } for i, w := range workflow.Workflow.Inputs { diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index fd0195e..7ae09b8 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -212,7 +212,7 @@ func (m *ModelGithubTrigger) View() string { var selectedRow = m.tableTrigger.SelectedRow() var selector = m.emptySelector() - if len(m.tableTrigger.Rows()) > 0 { + if len(m.tableTrigger.Rows()) > 0 && len(selectedRow) > 1 { if selectedRow[1] == "input" { selector = m.inputSelector() } else { @@ -227,6 +227,10 @@ func (m *ModelGithubTrigger) View() string { func (m *ModelGithubTrigger) switchBetweenInputAndTable() { var selectedRow = m.tableTrigger.SelectedRow() + + if len(selectedRow) < 5 { + return // Insufficient data to operate safely + } if selectedRow[1] == "input" || selectedRow[1] == "bool" { m.textInput.Focus() @@ -235,7 +239,7 @@ func (m *ModelGithubTrigger) switchBetweenInputAndTable() { m.textInput.Blur() m.tableTrigger.Focus() } - m.textInput.SetValue(m.tableTrigger.SelectedRow()[4]) + m.textInput.SetValue(selectedRow[4]) m.textInput.SetCursor(len(m.textInput.Value())) } @@ -246,8 +250,8 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { if len(m.tableTrigger.Rows()) > 0 { var selectedRow = m.tableTrigger.SelectedRow() - if len(selectedRow) == 0 { - return + if len(selectedRow) < 5 { + return // Need at least 5 elements for safe access } switch selectedRow[1] { @@ -297,14 +301,14 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { var selectedRow = m.tableTrigger.SelectedRow() var rows = m.tableTrigger.Rows() - if len(selectedRow) == 0 || len(rows) == 0 { + if len(selectedRow) < 5 || len(rows) == 0 || m.optionCursor >= len(m.optionValues) { return } if fmt.Sprintf("%d", choice.ID) == selectedRow[0] { m.workflowContent.Choices[i].SetValue(m.optionValues[m.optionCursor]) for i, row := range rows { - if row[0] == selectedRow[0] { + if len(row) >= 5 && row[0] == selectedRow[0] { rows[i][4] = m.optionValues[m.optionCursor] } } @@ -317,14 +321,14 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { for i, boolean := range m.workflowContent.Boolean { var selectedRow = m.tableTrigger.SelectedRow() var rows = m.tableTrigger.Rows() - if len(selectedRow) == 0 || len(rows) == 0 { + if len(selectedRow) < 5 || len(rows) == 0 || m.optionCursor >= len(m.optionValues) { return } if fmt.Sprintf("%d", boolean.ID) == selectedRow[0] { m.workflowContent.Boolean[i].SetValue(m.optionValues[m.optionCursor]) for i, row := range rows { - if row[0] == selectedRow[0] { + if len(row) >= 5 && row[0] == selectedRow[0] { rows[i][4] = m.optionValues[m.optionCursor] } } @@ -341,7 +345,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { var selectedRow = m.tableTrigger.SelectedRow() var rows = m.tableTrigger.Rows() - if len(selectedRow) == 0 || len(rows) == 0 { + if len(selectedRow) < 5 || len(rows) == 0 { return } @@ -351,7 +355,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { m.workflowContent.Inputs[i].SetValue(m.textInput.Value()) for i, row := range rows { - if row[0] == selectedRow[0] { + if len(row) >= 5 && row[0] == selectedRow[0] { rows[i][4] = m.textInput.Value() } } @@ -366,7 +370,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { m.workflowContent.KeyVals[i].SetValue(m.textInput.Value()) for i, row := range rows { - if row[0] == selectedRow[0] { + if len(row) >= 5 && row[0] == selectedRow[0] { rows[i][4] = m.textInput.Value() } } diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 072242c..a867195 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/termkit/gama/internal/config" gu "github.com/termkit/gama/internal/github/usecase" "github.com/termkit/gama/pkg/browser" "github.com/termkit/skeleton" @@ -63,10 +62,7 @@ type workflowHistoryUpdateMsg struct { // ----------------------------------------------------------------------------- func SetupModelGithubWorkflowHistory(s *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } + cfg := loadConfig() modelStatus := SetupModelStatus(s) tabOptions := NewOptions(s, modelStatus) diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 91d33bf..55824a4 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -1,7 +1,6 @@ package handler import ( - "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/termkit/gama/internal/config" gu "github.com/termkit/gama/internal/github/usecase" @@ -12,7 +11,14 @@ import ( func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { cfg, err := config.LoadConfig() if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) + // Return a skeleton with minimal error handling + // Instead of panicking, we'll create a basic working terminal + cfg = &config.Config{} + // Apply defaults manually + cfg.Shortcuts.SwitchTabRight = "shift+right" + cfg.Shortcuts.SwitchTabLeft = "shift+left" + cfg.Shortcuts.Quit = "ctrl+c" + cfg.Settings.LiveMode.Enabled = false } s := skeleton.NewSkeleton() @@ -37,6 +43,8 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod s.SetTerminalViewportWidth(MinTerminalWidth) s.SetTerminalViewportHeight(MinTerminalHeight) + handlerKeys := createHandlerKeys(cfg) + s.KeyMap.SetKeyNextTab(handlerKeys.SwitchTabRight) s.KeyMap.SetKeyPrevTab(handlerKeys.SwitchTabLeft) s.KeyMap.SetKeyQuit(handlerKeys.Quit) diff --git a/internal/terminal/handler/keymap.go b/internal/terminal/handler/keymap.go index 1feb45c..c4fdadc 100644 --- a/internal/terminal/handler/keymap.go +++ b/internal/terminal/handler/keymap.go @@ -2,31 +2,65 @@ package handler import ( "fmt" + "time" "github.com/termkit/gama/internal/config" teakey "github.com/charmbracelet/bubbles/key" ) +// --------------------------------------------------------------------------- + +type handlerKeyMap struct { + SwitchTabRight teakey.Binding + SwitchTabLeft teakey.Binding + Quit teakey.Binding +} + func loadConfig() *config.Config { cfg, err := config.LoadConfig() if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) + // Return a config with default values instead of panicking + cfg = &config.Config{} + cfg = fillDefaultShortcuts(cfg) + cfg = fillDefaultSettings(cfg) } return cfg } -// --------------------------------------------------------------------------- - -type handlerKeyMap struct { - SwitchTabRight teakey.Binding - SwitchTabLeft teakey.Binding - Quit teakey.Binding +func fillDefaultShortcuts(cfg *config.Config) *config.Config { + if cfg.Shortcuts.SwitchTabRight == "" { + cfg.Shortcuts.SwitchTabRight = "shift+right" + } + if cfg.Shortcuts.SwitchTabLeft == "" { + cfg.Shortcuts.SwitchTabLeft = "shift+left" + } + if cfg.Shortcuts.Quit == "" { + cfg.Shortcuts.Quit = "ctrl+c" + } + if cfg.Shortcuts.Refresh == "" { + cfg.Shortcuts.Refresh = "ctrl+r" + } + if cfg.Shortcuts.Enter == "" { + cfg.Shortcuts.Enter = "enter" + } + if cfg.Shortcuts.Tab == "" { + cfg.Shortcuts.Tab = "tab" + } + if cfg.Shortcuts.LiveMode == "" { + cfg.Shortcuts.LiveMode = "ctrl+l" + } + return cfg } -var handlerKeys = func() handlerKeyMap { - cfg := loadConfig() +func fillDefaultSettings(cfg *config.Config) *config.Config { + if cfg.Settings.LiveMode.Interval == 0 { + cfg.Settings.LiveMode.Interval = 15 * time.Second + } + return cfg +} +func createHandlerKeys(cfg *config.Config) handlerKeyMap { return handlerKeyMap{ SwitchTabRight: teakey.NewBinding( teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), @@ -38,7 +72,7 @@ var handlerKeys = func() handlerKeyMap { teakey.WithKeys(cfg.Shortcuts.Quit), ), } -}() +} // --------------------------------------------------------------------------- diff --git a/internal/terminal/handler/taboptions.go b/internal/terminal/handler/taboptions.go index 5ac3c21..bd4c017 100644 --- a/internal/terminal/handler/taboptions.go +++ b/internal/terminal/handler/taboptions.go @@ -141,14 +141,18 @@ func (o *ModelTabOptions) resetOptionsWithOriginal() { o.isTabSelected = true o.timer = 3 for o.timer >= 0 { - o.optionsAction[0] = fmt.Sprintf("> %ds", o.timer) + if len(o.optionsAction) > 0 { + o.optionsAction[0] = fmt.Sprintf("> %ds", o.timer) + } time.Sleep(1 * time.Second) o.timer-- o.skeleton.TriggerUpdate() } o.modelLock = false o.switchToPreviousError() - o.optionsAction[0] = string(StatusIdle) + if len(o.optionsAction) > 0 { + o.optionsAction[0] = string(StatusIdle) + } o.cursor = 0 o.isTabSelected = false } @@ -163,8 +167,12 @@ func (o *ModelTabOptions) updateCursor(cursor int) { func (o *ModelTabOptions) SetStatus(status OptionStatus) { o.optionStatus = status - o.options[0] = status.String() - o.optionsAction[0] = status.String() + if len(o.options) > 0 { + o.options[0] = status.String() + } + if len(o.optionsAction) > 0 { + o.optionsAction[0] = status.String() + } } func (o *ModelTabOptions) AddOption(option string, action func()) { @@ -177,6 +185,9 @@ func (o *ModelTabOptions) AddOption(option string, action func()) { } func (o *ModelTabOptions) getOptionMessage() string { + if o.cursor >= len(o.options) { + return "" + } option := o.options[o.cursor] option = strings.TrimPrefix(option, fmt.Sprintf("%d) ", o.cursor)) return option @@ -206,7 +217,9 @@ func (o *ModelTabOptions) switchToPreviousError() { } func (o *ModelTabOptions) executeOption() { - go o.optionsWithFunc[o.cursor]() + if fn, exists := o.optionsWithFunc[o.cursor]; exists { + go fn() + } o.cursor = 0 o.timer = -1 } diff --git a/main.go b/main.go index 6d7e1d5..5c2d05a 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,8 @@ var Version = "under development" // will be set by build flag func main() { cfg, err := config.LoadConfig() if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) + fmt.Fprintf(os.Stderr, "Error: failed to load config: %v\n", err) + os.Exit(1) } version := pkgversion.New(repositoryOwner, repositoryName, Version)