Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resolve performance issues #140

Merged
merged 6 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion internal/pubsub/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions internal/tui/border.go
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -111,5 +111,5 @@ func borderize(content string, active bool, embeddedText map[BorderPosition]stri
border.Bottom,
border.BottomRight,
),
)
}, "\n")
}
5 changes: 4 additions & 1 deletion internal/tui/explorer/messages.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package explorer

type builtTreeMsg *tree
type builtTreeMsg struct {
tree *tree
rendered string
}
59 changes: 39 additions & 20 deletions internal/tui/explorer/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
23 changes: 18 additions & 5 deletions internal/tui/explorer/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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)
Expand Down
29 changes: 25 additions & 4 deletions internal/tui/explorer/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()},
}
Expand Down Expand Up @@ -78,7 +78,16 @@ func (b *treeBuilder) newTree(filter string) *tree {
modTree.addChild(ws)
}
}
return t.filter(filter)
// 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)
filtered.render(true, to)
return filtered, to.String()
}

func (t *tree) filter(text string) *tree {
Expand Down Expand Up @@ -133,9 +142,21 @@ 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 {
if internal.StripAnsi(a.value.String()) < internal.StripAnsi(b.value.String()) {
// 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
}
return 1
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/explorer/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion internal/tui/top/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
16 changes: 7 additions & 9 deletions internal/tui/workspace/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
Loading