From 41a5ce24faac4b5ccdea3713fe6b83dc0b6ad143 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Sun, 22 Dec 2024 17:57:22 +0000 Subject: [PATCH] feat: re-introduce history for top right pane --- internal/integration/cost_test.go | 1 - internal/integration/explorer_test.go | 1 - internal/integration/history_test.go | 59 +++++++++++++++++++++++++ internal/integration/module_test.go | 2 - internal/integration/terragrunt_test.go | 1 - internal/integration/workspace_test.go | 2 - internal/tui/keys/common.go | 5 +++ internal/tui/pane_manager.go | 40 +++++++++++++++-- internal/tui/top/model.go | 4 +- 9 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 internal/integration/history_test.go diff --git a/internal/integration/cost_test.go b/internal/integration/cost_test.go index 7bca152..b400a22 100644 --- a/internal/integration/cost_test.go +++ b/internal/integration/cost_test.go @@ -30,7 +30,6 @@ func TestCost(t *testing.T) { // Wait for infracost task to produce overall total. Each workspace in the // explorer should also have a cost alongside it. waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "cost") && strings.Contains(s, "exited $2621.90") && matchPattern(t, `OVERALL TOTAL.*\$2\,621\.90`, s) && diff --git a/internal/integration/explorer_test.go b/internal/integration/explorer_test.go index 5d56544..4d312f1 100644 --- a/internal/integration/explorer_test.go +++ b/internal/integration/explorer_test.go @@ -23,7 +23,6 @@ func TestExplorer_Filter(t *testing.T) { // Expect table title to show 1 module filtered out of a total 3. waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "󰠱 1  0") }) } diff --git a/internal/integration/history_test.go b/internal/integration/history_test.go new file mode 100644 index 0000000..0d177fd --- /dev/null +++ b/internal/integration/history_test.go @@ -0,0 +1,59 @@ +package app + +import ( + "strings" + "testing" + + tea "github.com/charmbracelet/bubbletea" +) + +func TestHistory(t *testing.T) { + t.Parallel() + + tm := setup(t, "./testdata/single_module") + + // Show task list + tm.Type("t") + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "tasks") + }) + + // Try go back but get error + tm.Send(tea.KeyMsg{Type: tea.KeyEsc}) + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "already at first page") + }) + + // Show logs + tm.Type("l") + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "logs") + }) + + // Go back, expect task list + tm.Send(tea.KeyMsg{Type: tea.KeyEsc}) + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "tasks") + }) + + // Try go back but get error + tm.Send(tea.KeyMsg{Type: tea.KeyEsc}) + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "already at first page") + }) + + // Focus explorer + tm.Type("0") + + // Start init task + tm.Type("i") + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "init 󰠱 modules/a") + }) + + // Go back, expect task list + tm.Send(tea.KeyMsg{Type: tea.KeyEsc}) + waitFor(t, tm, func(s string) bool { + return strings.Contains(s, "tasks") + }) +} diff --git a/internal/integration/module_test.go b/internal/integration/module_test.go index 961dfa4..ac30c51 100644 --- a/internal/integration/module_test.go +++ b/internal/integration/module_test.go @@ -261,7 +261,6 @@ func TestExplorer_SingleApply(t *testing.T) { // Give approval waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "Auto-apply 1 workspaces? (y/N):") }) tm.Type("y") @@ -408,7 +407,6 @@ func TestExplorer_MultipleDestroys(t *testing.T) { tm.Type("y") waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "apply (destroy) 3/3") && matchPattern(t, `modules/a.*default.*apply \(destroy\).*exited.*\+0~0-10`, s) && matchPattern(t, `modules/b.*default.*apply \(destroy\).*exited.*\+0~0-10`, s) && diff --git a/internal/integration/terragrunt_test.go b/internal/integration/terragrunt_test.go index e28d203..3f65aff 100644 --- a/internal/integration/terragrunt_test.go +++ b/internal/integration/terragrunt_test.go @@ -104,7 +104,6 @@ func TestTerragrunt_Dependencies(t *testing.T) { // Expect 6 apply tasks. waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "apply (destroy) 6/6") && matchPattern(t, `modules/vpc.*default.*apply \(destroy\).*exited.*\+0~0-0`, s) && matchPattern(t, `modules/redis.*default.*apply \(destroy\).*exited.*\+0~0-0`, s) && diff --git a/internal/integration/workspace_test.go b/internal/integration/workspace_test.go index 137e03c..b4d111c 100644 --- a/internal/integration/workspace_test.go +++ b/internal/integration/workspace_test.go @@ -223,13 +223,11 @@ func TestWorkspace_Delete(t *testing.T) { // Give approval waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "Delete workspace dev? (y/N):") }) tm.Type("y") waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "workspace delete 󰠱 modules/a") && strings.Contains(s, `Deleted workspace "dev"!`) }) diff --git a/internal/tui/keys/common.go b/internal/tui/keys/common.go index 948b0df..38cd4ae 100644 --- a/internal/tui/keys/common.go +++ b/internal/tui/keys/common.go @@ -20,6 +20,7 @@ type common struct { Format key.Binding Cost key.Binding LastTask key.Binding + Back key.Binding } // Keys shared by several models. @@ -92,4 +93,8 @@ var Common = common{ key.WithKeys("o"), key.WithHelp("o", "last task output"), ), + Back: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "back"), + ), } diff --git a/internal/tui/pane_manager.go b/internal/tui/pane_manager.go index bd46594..23435d3 100644 --- a/internal/tui/pane_manager.go +++ b/internal/tui/pane_manager.go @@ -43,6 +43,8 @@ type PaneManager struct { leftPaneWidth int // topRightPaneHeight is the height of the top right pane. topRightHeight int + // history tracks previously visited models for the top right pane. + history []pane } type pane struct { @@ -79,6 +81,22 @@ func (p *PaneManager) Update(msg tea.Msg) tea.Cmd { switch msg := msg.(type) { case tea.KeyMsg: switch { + case key.Matches(msg, keys.Common.Back): + if p.active != TopRightPane { + // History is only maintained for the top right pane. + break + } + if len(p.history) == 1 { + // At dawn of history; can't go further back. + return ReportError(errors.New("already at first page")) + } + // Pop current model from history + p.history = p.history[:len(p.history)-1] + // Set pane to last model + p.panes[TopRightPane] = p.history[len(p.history)-1] + // A new top right pane replaces any bottom right pane as well. + delete(p.panes, BottomRightPane) + p.updateChildSizes() case key.Matches(msg, keys.Global.ShrinkPaneWidth): p.updateLeftWidth(-1) p.updateChildSizes() @@ -234,14 +252,17 @@ func (m *PaneManager) setPane(msg NavigationMsg) (cmd tea.Cmd) { m.cache.Put(msg.Page, model) cmd = model.Init() } - if msg.Position == TopRightPane { - // A new top right pane replaces any bottom right pane as well. - delete(m.panes, BottomRightPane) - } m.panes[msg.Position] = pane{ model: model, page: msg.Page, } + if msg.Position == TopRightPane { + // A new top right pane replaces any bottom right pane as well. + delete(m.panes, BottomRightPane) + // Track the models for the top right pane, so that the user can go back + // to previous models. + m.history = append(m.history, m.panes[TopRightPane]) + } m.updateChildSizes() if !msg.DisableFocus { focus := m.focusPane(msg.Position) @@ -337,6 +358,17 @@ func (m *PaneManager) renderPane(position Position) string { return borderize(renderedPane, isActive, borderTexts) } +func (m *PaneManager) HelpBindings() (bindings []key.Binding) { + if m.active == TopRightPane { + // Only the top right pane has the ability to "go back" + bindings = append(bindings, keys.Common.Back) + } + if model, ok := m.ActiveModel().(ModelHelpBindings); ok { + bindings = append(bindings, model.HelpBindings()...) + } + return bindings +} + func removeEmptyStrings(strs ...string) []string { n := 0 for _, s := range strs { diff --git a/internal/tui/top/model.go b/internal/tui/top/model.go index 3311a09..bc7130e 100644 --- a/internal/tui/top/model.go +++ b/internal/tui/top/model.go @@ -414,9 +414,7 @@ func (m model) help() string { case filterMode: bindings = append(bindings, keys.KeyMapToSlice(keys.Filter)...) default: - if model, ok := m.ActiveModel().(tui.ModelHelpBindings); ok { - bindings = append(bindings, model.HelpBindings()...) - } + bindings = append(bindings, m.PaneManager.HelpBindings()...) } bindings = append(bindings, keys.KeyMapToSlice(keys.Global)...) bindings = append(bindings, keys.KeyMapToSlice(keys.Navigation)...)