From 2da9d76444fbe756fe69beb86e38d13cf7e34a53 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Mon, 23 Dec 2024 14:24:08 +0000 Subject: [PATCH 1/6] chore: remove trailing space from resource pane title --- internal/tui/workspace/resource.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/internal/tui/workspace/resource.go b/internal/tui/workspace/resource.go index 83570be..ea4fd5a 100644 --- a/internal/tui/workspace/resource.go +++ b/internal/tui/workspace/resource.go @@ -125,19 +125,17 @@ func (m *resourceModel) View() string { } func (m *resourceModel) BorderText() map[tui.BorderPosition]string { - var tainted string + topLeft := fmt.Sprintf("%s %s", + tui.Bold.Render("resource"), + m.resource, + ) if m.resource.Tainted { - tainted = lipgloss.NewStyle(). + topLeft += lipgloss.NewStyle(). Foreground(tui.Red). - Render("(tainted)") + Render(" (tainted)") } return map[tui.BorderPosition]string{ - tui.TopLeftBorder: fmt.Sprintf( - "%s %s %s", - tui.Bold.Render("resource"), - m.resource, - tainted, - ), + tui.TopLeftBorder: topLeft, } } From 849909b821de2851a51a7e3fae8a5d2befa61e29 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Tue, 24 Dec 2024 08:28:33 +0000 Subject: [PATCH 2/6] wip --- cpu.prof | 0 internal/tui/border.go | 6 ++-- internal/tui/explorer/messages.go | 5 ++- internal/tui/explorer/model.go | 59 ++++++++++++++++++++----------- internal/tui/explorer/nodes.go | 23 +++++++++--- internal/tui/explorer/tree.go | 14 ++++++-- internal/tui/top/model.go | 2 +- internal/tui/top/start.go | 4 ++- main.go | 9 +++++ 9 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 cpu.prof diff --git a/cpu.prof b/cpu.prof new file mode 100644 index 0000000..e69de29 diff --git a/internal/tui/border.go b/internal/tui/border.go index e078e68..56ccb6a 100644 --- a/internal/tui/border.go +++ b/internal/tui/border.go @@ -90,8 +90,8 @@ func borderize(content string, active bool, embeddedText map[BorderPosition]stri // Add the corners return style.Render(leftCorner) + s + style.Render(rightCorner) } - // Stack top border onto remaining borders - return lipgloss.JoinVertical(lipgloss.Top, + // Stack top border, content and horizontal borders, and bottom border. + return strings.Join([]string{ buildHorizontalBorder( embeddedText[TopLeftBorder], embeddedText[TopMiddleBorder], @@ -111,5 +111,5 @@ func borderize(content string, active bool, embeddedText map[BorderPosition]stri border.Bottom, border.BottomRight, ), - ) + }, "\n") } diff --git a/internal/tui/explorer/messages.go b/internal/tui/explorer/messages.go index e56227f..8a4ecad 100644 --- a/internal/tui/explorer/messages.go +++ b/internal/tui/explorer/messages.go @@ -1,3 +1,6 @@ package explorer -type builtTreeMsg *tree +type builtTreeMsg struct { + tree *tree + rendered string +} diff --git a/internal/tui/explorer/model.go b/internal/tui/explorer/model.go index b450ed5..369e06d 100644 --- a/internal/tui/explorer/model.go +++ b/internal/tui/explorer/model.go @@ -10,7 +10,6 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - lgtree "github.com/charmbracelet/lipgloss/tree" "github.com/leg100/pug/internal" "github.com/leg100/pug/internal/module" "github.com/leg100/pug/internal/resource" @@ -34,16 +33,17 @@ func (mm *Maker) Make(id resource.ID, width, height int) (tui.ChildModel, error) moduleService: mm.ModuleService, workspaceService: mm.WorkspaceService, } - tree := builder.newTree("") filter := textinput.New() filter.Prompt = "Filter: " + tree, lgtree := builder.newTree("") m := &model{ - Helpers: mm.Helpers, - Workdir: mm.Workdir, - treeBuilder: builder, - tree: tree, - tracker: newTracker(tree, height), - filter: filter, + Helpers: mm.Helpers, + Workdir: mm.Workdir, + treeBuilder: builder, + tree: tree, + renderedTree: lgtree, + tracker: newTracker(tree, height), + filter: filter, } m.common = &tui.ActionHandler{ Helpers: mm.Helpers, @@ -61,11 +61,21 @@ type model struct { treeBuilder *treeBuilder tree *tree tracker *tracker + renderedTree string width, height int filter textinput.Model + status buildTreeStatus } -func (m model) Init() tea.Cmd { +type buildTreeStatus int + +const ( + notBuildingTree buildTreeStatus = iota + buildingTree + queueBuildTree +) + +func (m *model) Init() tea.Cmd { return tea.Batch( m.buildTree, reload(true, m.Modules), @@ -136,10 +146,15 @@ func (m *model) Update(msg tea.Msg) tea.Cmd { return m.common.Update(msg) } case builtTreeMsg: - m.tree = (*tree)(msg) + m.tree = msg.tree + m.renderedTree = msg.rendered // TODO: perform this in a cmd m.tracker.reindex(m.tree, m.treeHeight()) - return nil + if m.status == queueBuildTree { + return m.buildTree + } else { + m.status = notBuildingTree + } case resource.Event[*module.Module]: return m.buildTree case resource.Event[*workspace.Workspace]: @@ -209,12 +224,7 @@ func (m model) View() string { Width(m.width - tui.ScrollbarWidth). MaxWidth(m.width - tui.ScrollbarWidth). Inline(true) - to := lgtree.New(). - Enumerator(enumerator). - Indenter(indentor) - m.tree.render(true, to) - s := to.String() - lines := strings.Split(s, "\n") + lines := strings.Split(m.renderedTree, "\n") numVisibleLines := clamp(m.treeHeight(), 0, len(lines)) visibleLines := lines[m.tracker.start : m.tracker.start+numVisibleLines] for i := range visibleLines { @@ -273,9 +283,18 @@ func (m model) BorderText() map[tui.BorderPosition]string { } } -func (m model) buildTree() tea.Msg { - tree := m.treeBuilder.newTree(m.filter.Value()) - return builtTreeMsg(tree) +func (m *model) buildTree() tea.Msg { + switch m.status { + case notBuildingTree: + tree, rendered := m.treeBuilder.newTree(m.filter.Value()) + return builtTreeMsg{ + tree: tree, + rendered: rendered, + } + case buildingTree: + m.status = queueBuildTree + } + return nil } func (m model) filterVisible() bool { diff --git a/internal/tui/explorer/nodes.go b/internal/tui/explorer/nodes.go index d80b12a..201d2b0 100644 --- a/internal/tui/explorer/nodes.go +++ b/internal/tui/explorer/nodes.go @@ -11,9 +11,10 @@ import ( type node interface { fmt.Stringer - // ID uniquely identifies the node ID() any + // Value returns a value for lexicographic sorting + Value() string } type dirNode struct { @@ -26,14 +27,18 @@ func (d dirNode) ID() any { return d.path } -func (d dirNode) String() string { +func (d dirNode) Value() string { if d.root { - return fmt.Sprintf("%s %s", tui.DirIcon, d.path) + return d.path } else { - return fmt.Sprintf("%s %s", tui.DirIcon, filepath.Base(d.path)) + return filepath.Base(d.path) } } +func (d dirNode) String() string { + return fmt.Sprintf("%s %s", tui.DirIcon, d.Value()) +} + type moduleNode struct { id resource.ID path string @@ -43,8 +48,12 @@ func (m moduleNode) ID() any { return m.id } +func (m moduleNode) Value() string { + return filepath.Base(m.path) +} + func (m moduleNode) String() string { - return tui.ModulePathWithIcon(filepath.Base(m.path), false) + return tui.ModulePathWithIcon(m.Value(), false) } type workspaceNode struct { @@ -59,6 +68,10 @@ func (w workspaceNode) ID() any { return w.id } +func (w workspaceNode) Value() string { + return w.name +} + func (w workspaceNode) String() string { name := lipgloss.NewStyle(). Render(w.name) diff --git a/internal/tui/explorer/tree.go b/internal/tui/explorer/tree.go index 5cec3b3..b54810f 100644 --- a/internal/tui/explorer/tree.go +++ b/internal/tui/explorer/tree.go @@ -37,7 +37,7 @@ type treeBuilderHelpers interface { WorkspaceCost(ws *workspace.Workspace) string } -func (b *treeBuilder) newTree(filter string) *tree { +func (b *treeBuilder) newTree(filter string) (*tree, string) { t := &tree{ value: dirNode{root: true, path: b.wd.PrettyString()}, } @@ -78,7 +78,15 @@ func (b *treeBuilder) newTree(filter string) *tree { modTree.addChild(ws) } } - return t.filter(filter) + // Now render the corresponding lipgloss tree. We do this here rather than + // in View() because it's quite expensive and thus best kept out of the + // bubble event loop. + to := lgtree.New(). + Enumerator(enumerator). + Indenter(indentor) + t.render(true, to) + // Apply any filter and return. + return t.filter(filter), to.String() } func (t *tree) filter(text string) *tree { @@ -135,7 +143,7 @@ func (t *tree) addChild(child node) *tree { t.children = append(t.children, newTree) // keep children lexicographically ordered slices.SortFunc(t.children, func(a, b *tree) int { - if internal.StripAnsi(a.value.String()) < internal.StripAnsi(b.value.String()) { + if a.value.Value() < b.value.Value() { return -1 } return 1 diff --git a/internal/tui/top/model.go b/internal/tui/top/model.go index 7b2f3a6..0292f5b 100644 --- a/internal/tui/top/model.go +++ b/internal/tui/top/model.go @@ -361,7 +361,7 @@ func (m model) View() string { Width(m.width). Render(footer), ) - return lipgloss.JoinVertical(lipgloss.Top, components...) + return strings.Join(components, "\n") } var ( diff --git a/internal/tui/top/start.go b/internal/tui/top/start.go index d959ddc..4f77942 100644 --- a/internal/tui/top/start.go +++ b/internal/tui/top/start.go @@ -18,7 +18,9 @@ func Start(cfg app.Config) error { if err != nil { return err } - defer app.Cleanup() + defer func() { + app.Cleanup() + }() m, err := newModel(cfg, app) if err != nil { diff --git a/main.go b/main.go index 6fc6518..efe55a8 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,9 @@ package main import ( "fmt" + "log" "os" + "runtime/pprof" "github.com/leg100/pug/internal/app" "github.com/leg100/pug/internal/tui/top" @@ -10,6 +12,13 @@ import ( ) func main() { + f, err := os.Create("cpu.prof") + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + if err := run(); err != nil { fmt.Println(err.Error()) os.Exit(1) From c7c38b32e6120b702323cafd4b6db3cb2b99b862 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Tue, 24 Dec 2024 12:04:16 +0000 Subject: [PATCH 3/6] bump --- internal/pubsub/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pubsub/broker.go b/internal/pubsub/broker.go index 470d4d4..5c07f9d 100644 --- a/internal/pubsub/broker.go +++ b/internal/pubsub/broker.go @@ -7,7 +7,7 @@ import ( "github.com/leg100/pug/internal/resource" ) -const bufferSize = 1024 +const bufferSize = 1024 * 1024 type Logger interface { Debug(msg string, args ...any) From 8bd3818e91a13f075e49b328839d8aa491b6017c Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Tue, 24 Dec 2024 12:10:02 +0000 Subject: [PATCH 4/6] wip --- README.md | 2 +- cpu.prof | 0 main.go | 9 --------- 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 cpu.prof diff --git a/README.md b/README.md index c1a4254..7c18174 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Pug automatically loads variables from a .tfvars file. It looks for a file named ### Explorer -The explorer pane a tree of [modules](#module) and [workspaces](#workspace) discovered on your filesystem. From here, terraform commands can be carried out on both modules and workspaces. +The explorer pane shows a tree of [modules](#module) and [workspaces](#workspace) discovered on your filesystem. From here, terraform commands can be carried out on both modules and workspaces. You can select multiple modules or workspaces; you cannot select a combination of the two. Any terraform commands are then carried out on the selection. diff --git a/cpu.prof b/cpu.prof deleted file mode 100644 index e69de29..0000000 diff --git a/main.go b/main.go index efe55a8..6fc6518 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,7 @@ package main import ( "fmt" - "log" "os" - "runtime/pprof" "github.com/leg100/pug/internal/app" "github.com/leg100/pug/internal/tui/top" @@ -12,13 +10,6 @@ import ( ) func main() { - f, err := os.Create("cpu.prof") - if err != nil { - log.Fatal(err) - } - pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() - if err := run(); err != nil { fmt.Println(err.Error()) os.Exit(1) From 13239b0be8166762ce1b7e674211ad7f2f7398c2 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Tue, 24 Dec 2024 12:22:51 +0000 Subject: [PATCH 5/6] wip --- internal/integration/explorer_test.go | 2 ++ internal/tui/explorer/tree.go | 21 +++++++++++++++++---- internal/tui/explorer/tree_test.go | 2 +- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/integration/explorer_test.go b/internal/integration/explorer_test.go index 4d312f1..a85d718 100644 --- a/internal/integration/explorer_test.go +++ b/internal/integration/explorer_test.go @@ -15,6 +15,7 @@ func TestExplorer_Filter(t *testing.T) { // Expect filter prompt waitFor(t, tm, func(s string) bool { + t.Log(s) return strings.Contains(s, "Filter:") }) @@ -23,6 +24,7 @@ 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/tui/explorer/tree.go b/internal/tui/explorer/tree.go index b54810f..e3ece27 100644 --- a/internal/tui/explorer/tree.go +++ b/internal/tui/explorer/tree.go @@ -78,15 +78,16 @@ func (b *treeBuilder) newTree(filter string) (*tree, string) { modTree.addChild(ws) } } + // Apply filter if there is one. + filtered := t.filter(filter) // Now render the corresponding lipgloss tree. We do this here rather than // in View() because it's quite expensive and thus best kept out of the // bubble event loop. to := lgtree.New(). Enumerator(enumerator). Indenter(indentor) - t.render(true, to) - // Apply any filter and return. - return t.filter(filter), to.String() + filtered.render(true, to) + return filtered, to.String() } func (t *tree) filter(text string) *tree { @@ -141,8 +142,20 @@ func (t *tree) addChild(child node) *tree { } newTree := &tree{value: child} t.children = append(t.children, newTree) - // keep children lexicographically ordered + // keep children ordered slices.SortFunc(t.children, func(a, b *tree) int { + // directories come before modules + if _, ok := a.value.(dirNode); ok { + if _, ok := b.value.(moduleNode); ok { + return -1 + } + } + if _, ok := a.value.(moduleNode); ok { + if _, ok := b.value.(dirNode); ok { + return 1 + } + } + // otherwise order lexicographically. if a.value.Value() < b.value.Value() { return -1 } diff --git a/internal/tui/explorer/tree_test.go b/internal/tui/explorer/tree_test.go index f744732..fbcf1fe 100644 --- a/internal/tui/explorer/tree_test.go +++ b/internal/tui/explorer/tree_test.go @@ -52,7 +52,7 @@ func TestTree(t *testing.T) { helpers: &fakeTreeBuilderHelpers{}, } - got := builder.newTree("") + got, _ := builder.newTree("") want := &tree{ value: dirNode{path: builder.wd.String(), root: true}, From 0734ce17ee8b64c5a502f68babb6a46584ed0d23 Mon Sep 17 00:00:00 2001 From: Louis Garman Date: Tue, 24 Dec 2024 12:26:25 +0000 Subject: [PATCH 6/6] wip --- internal/integration/explorer_test.go | 2 -- internal/tui/top/start.go | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/integration/explorer_test.go b/internal/integration/explorer_test.go index a85d718..4d312f1 100644 --- a/internal/integration/explorer_test.go +++ b/internal/integration/explorer_test.go @@ -15,7 +15,6 @@ func TestExplorer_Filter(t *testing.T) { // Expect filter prompt waitFor(t, tm, func(s string) bool { - t.Log(s) return strings.Contains(s, "Filter:") }) @@ -24,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/tui/top/start.go b/internal/tui/top/start.go index 4f77942..d959ddc 100644 --- a/internal/tui/top/start.go +++ b/internal/tui/top/start.go @@ -18,9 +18,7 @@ func Start(cfg app.Config) error { if err != nil { return err } - defer func() { - app.Cleanup() - }() + defer app.Cleanup() m, err := newModel(cfg, app) if err != nil {