refactor & improve the fstree logic
This commit is contained in:
+2
-4
@@ -14,7 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func walkFileSystemAndBuildTree(rootPath string, node *FsNode, flatTree *[]*FsNode) error {
|
func walkFileSystemAndBuildTree(rootPath string, node *FsNode) error {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return errors.New("node cannot be nil")
|
return errors.New("node cannot be nil")
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,6 @@ func walkFileSystemAndBuildTree(rootPath string, node *FsNode, flatTree *[]*FsNo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*flatTree = append(*flatTree, node) // add self
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
newNode := &FsNode{
|
newNode := &FsNode{
|
||||||
nodeType: FileNode,
|
nodeType: FileNode,
|
||||||
@@ -53,7 +52,6 @@ func walkFileSystemAndBuildTree(rootPath string, node *FsNode, flatTree *[]*FsNo
|
|||||||
expanded: false,
|
expanded: false,
|
||||||
}
|
}
|
||||||
node.children = append(node.children, newNode)
|
node.children = append(node.children, newNode)
|
||||||
*flatTree = append(*flatTree, newNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, folder := range folders {
|
for _, folder := range folders {
|
||||||
@@ -65,7 +63,7 @@ func walkFileSystemAndBuildTree(rootPath string, node *FsNode, flatTree *[]*FsNo
|
|||||||
expanded: true, // all expanded by default
|
expanded: true, // all expanded by default
|
||||||
}
|
}
|
||||||
node.children = append(node.children, newNode)
|
node.children = append(node.children, newNode)
|
||||||
walkFileSystemAndBuildTree(newNode.path, newNode, flatTree)
|
walkFileSystemAndBuildTree(newNode.path, newNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+69
-43
@@ -33,7 +33,7 @@ type Msg interface{}
|
|||||||
type MsgMoveUp struct{}
|
type MsgMoveUp struct{}
|
||||||
type MsgMoveDown struct{}
|
type MsgMoveDown struct{}
|
||||||
type MsgToggleExpand struct{}
|
type MsgToggleExpand struct{}
|
||||||
type MsgSelectAtLine struct {
|
type MsgLeftClickLine struct {
|
||||||
Line int
|
Line int
|
||||||
}
|
}
|
||||||
type MsgCreateNode struct {
|
type MsgCreateNode struct {
|
||||||
@@ -56,8 +56,7 @@ type FsNode struct {
|
|||||||
path string
|
path string
|
||||||
children []*FsNode
|
children []*FsNode
|
||||||
parent *FsNode // for fast traversal up the tree
|
parent *FsNode // for fast traversal up the tree
|
||||||
// for view layer
|
expanded bool // makes sense only for folder nodes
|
||||||
expanded bool // makes sense only for folder nodes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *FsNode) FileName() string {
|
func (n *FsNode) FileName() string {
|
||||||
@@ -66,10 +65,10 @@ func (n *FsNode) FileName() string {
|
|||||||
|
|
||||||
// ==================== FsNode definition ====================
|
// ==================== FsNode definition ====================
|
||||||
type FsTree struct {
|
type FsTree struct {
|
||||||
root *FsNode
|
root *FsNode
|
||||||
selected int
|
lines map[int]*FsNode // flattened view of nodes for easy line access, map so that I can handle blank padding
|
||||||
lines []*FsNode // flattened view of nodes
|
selectedLine int
|
||||||
hoverLine int // for mouse hover highlighting
|
hoverLine int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Bubble Tea Interface Implementation ====================
|
// ==================== Bubble Tea Interface Implementation ====================
|
||||||
@@ -85,8 +84,14 @@ func (t *FsTree) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
_ = t.MoveDown()
|
_ = t.MoveDown()
|
||||||
case MsgToggleExpand:
|
case MsgToggleExpand:
|
||||||
_ = t.ToggleSelectedExpand()
|
_ = t.ToggleSelectedExpand()
|
||||||
case MsgSelectAtLine:
|
case MsgLeftClickLine:
|
||||||
_ = t.SelectNodeAtLine(m.Line)
|
node := t.lines[m.Line]
|
||||||
|
switch node.nodeType {
|
||||||
|
case FolderNode:
|
||||||
|
_ = t.ToggleExpand(node)
|
||||||
|
case FileNode:
|
||||||
|
_ = t.SelectNodeAtLine(m.Line)
|
||||||
|
}
|
||||||
case MsgCreateNode:
|
case MsgCreateNode:
|
||||||
_ = t.CreateNode(m.Parent, m.Name, m.NodeType)
|
_ = t.CreateNode(m.Parent, m.Name, m.NodeType)
|
||||||
case MsgDeleteNode:
|
case MsgDeleteNode:
|
||||||
@@ -99,20 +104,13 @@ func (t *FsTree) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (t *FsTree) View() string {
|
func (t *FsTree) View() string {
|
||||||
builder := &strings.Builder{}
|
builder := &strings.Builder{}
|
||||||
lineCounter := 0
|
curLine := -1
|
||||||
t.renderNode(t.root, 0, builder, t.hoverLine, &lineCounter)
|
t.renderNode(t.root, 0, builder, &curLine)
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== FsTree helper methods ====================
|
// ==================== FsTree helper methods ====================
|
||||||
|
|
||||||
func (t *FsTree) selectedNode() *FsNode {
|
|
||||||
if t.selected <= 0 || t.selected >= len(t.lines) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return t.lines[t.selected]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFsTree(rootPath string) *FsTree {
|
func NewFsTree(rootPath string) *FsTree {
|
||||||
root := &FsNode{
|
root := &FsNode{
|
||||||
nodeType: FolderNode,
|
nodeType: FolderNode,
|
||||||
@@ -120,20 +118,26 @@ func NewFsTree(rootPath string) *FsTree {
|
|||||||
children: make([]*FsNode, 0),
|
children: make([]*FsNode, 0),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
}
|
}
|
||||||
flatTree := make([]*FsNode, 0)
|
// root is at -1, rest all are 0 indexed, REM: this walk func does no handle spaces right now
|
||||||
walkFileSystemAndBuildTree(rootPath, root, &flatTree)
|
// but since I use a map, it's easy to add
|
||||||
|
walkFileSystemAndBuildTree(rootPath, root)
|
||||||
|
|
||||||
|
var tree *FsTree
|
||||||
if len(root.children) > 0 {
|
if len(root.children) > 0 {
|
||||||
return &FsTree{
|
tree = &FsTree{
|
||||||
root: root,
|
root: root,
|
||||||
selected: 1, // first child
|
selectedLine: 0,
|
||||||
lines: flatTree,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &FsTree{
|
tree = &FsTree{
|
||||||
root: root,
|
root: root,
|
||||||
lines: flatTree,
|
|
||||||
}
|
}
|
||||||
|
tree.buildLines()
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *FsTree) selectedNode() *FsNode {
|
||||||
|
return t.lines[t.selectedLine]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FsTree) CreateNode(parent *FsNode, name string, nodeType FsNodeType) error {
|
func (t *FsTree) CreateNode(parent *FsNode, name string, nodeType FsNodeType) error {
|
||||||
@@ -156,6 +160,7 @@ func (t *FsTree) CreateNode(parent *FsNode, name string, nodeType FsNodeType) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent.children = append(parent.children, newNode)
|
parent.children = append(parent.children, newNode)
|
||||||
|
t.buildLines()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +174,7 @@ func (t *FsTree) DeleteNode(node *FsNode) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent.children = utils.RemoveFromSlice(parent.children, node)
|
parent.children = utils.RemoveFromSlice(parent.children, node)
|
||||||
|
t.buildLines()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +187,7 @@ func (t *FsTree) ToggleExpand(node *FsNode) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.expanded = !node.expanded
|
node.expanded = !node.expanded
|
||||||
|
t.buildLines()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,11 +199,12 @@ func (t *FsTree) move(delta int) error {
|
|||||||
return errors.New("delta must be either -1 (up) or 1 (down)")
|
return errors.New("delta must be either -1 (up) or 1 (down)")
|
||||||
}
|
}
|
||||||
|
|
||||||
newIndex := t.selected + delta
|
newIndex := t.selectedLine + delta
|
||||||
if newIndex < 1 || newIndex >= len(t.lines) {
|
if newIndex == -1 || t.lines[newIndex] == nil {
|
||||||
return nil // noop
|
return nil // noop
|
||||||
}
|
}
|
||||||
t.selected = newIndex
|
|
||||||
|
t.selectedLine = newIndex
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,10 +212,10 @@ func (t *FsTree) MoveUp() error { return t.move(-1) }
|
|||||||
func (t *FsTree) MoveDown() error { return t.move(1) }
|
func (t *FsTree) MoveDown() error { return t.move(1) }
|
||||||
|
|
||||||
func (t *FsTree) SelectNodeAtLine(line int) error {
|
func (t *FsTree) SelectNodeAtLine(line int) error {
|
||||||
if line < 0 || line >= len(t.lines) {
|
if line <= 0 || line >= len(t.lines) {
|
||||||
return errors.New("line number out of bounds")
|
return errors.New("line number out of bounds")
|
||||||
}
|
}
|
||||||
t.selected = line
|
t.selectedLine = line
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,13 +247,7 @@ func (t *FsTree) GetSelectedContent() (string, error) {
|
|||||||
return string(contentBytes), nil
|
return string(contentBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use View() instead as part of Bubble Tea model
|
func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, currentLine *int) {
|
||||||
func (t *FsTree) Render(hoverLine int) string {
|
|
||||||
t.hoverLine = hoverLine
|
|
||||||
return t.View()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, hoverLine int, currentLine *int) {
|
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -267,9 +269,10 @@ func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// highlight if selected or hovered
|
// highlight if selected or hovered
|
||||||
|
// note: the logic of lines cache needs to match render
|
||||||
fileName := node.FileName()
|
fileName := node.FileName()
|
||||||
isSelected := node == t.selectedNode()
|
isSelected := *currentLine == t.selectedLine
|
||||||
isHovered := hoverLine >= 0 && *currentLine == hoverLine
|
isHovered := *currentLine == t.hoverLine
|
||||||
|
|
||||||
if isSelected {
|
if isSelected {
|
||||||
fileName = lipgloss.NewStyle().Foreground(styles.Highlight).Bold(true).Render(fileName)
|
fileName = lipgloss.NewStyle().Foreground(styles.Highlight).Bold(true).Render(fileName)
|
||||||
@@ -278,7 +281,6 @@ func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
line := icon + " " + fileName + "\n"
|
line := icon + " " + fileName + "\n"
|
||||||
// the depth 0 is the dummy root node, I don't render that
|
|
||||||
if depth > 0 {
|
if depth > 0 {
|
||||||
builder.WriteString(line)
|
builder.WriteString(line)
|
||||||
}
|
}
|
||||||
@@ -286,7 +288,31 @@ func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder, h
|
|||||||
|
|
||||||
if node.expanded {
|
if node.expanded {
|
||||||
for _, child := range node.children {
|
for _, child := range node.children {
|
||||||
t.renderNode(child, depth+1, builder, hoverLine, currentLine)
|
t.renderNode(child, depth+1, builder, currentLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// builds a cache of line num to rendered node in view
|
||||||
|
func (t *FsTree) buildLines() {
|
||||||
|
t.lines = make(map[int]*FsNode)
|
||||||
|
line := -1
|
||||||
|
t.buildLinesRec(t.root, &line) // root has -1 depth and -1 index, as it's not meant to be rendered
|
||||||
|
// everything is a child of root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: isn't meant to be used directly
|
||||||
|
func (t *FsTree) buildLinesRec(node *FsNode, currentLine *int) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.lines[*currentLine] = node
|
||||||
|
(*currentLine)++
|
||||||
|
|
||||||
|
if node.expanded {
|
||||||
|
for _, child := range node.children {
|
||||||
|
t.buildLinesRec(child, currentLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.tree.Update(fs.MsgHover{Line: -1})
|
m.tree.Update(fs.MsgHover{Line: -1})
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Button == tea.MouseButtonLeft {
|
if msg.Button == tea.MouseButtonLeft && msg.Action == tea.MouseActionPress {
|
||||||
// within file tree
|
// within file tree
|
||||||
if msg.X < m.width {
|
if msg.X < m.width {
|
||||||
m.tree.Update(fs.MsgSelectAtLine{Line: msg.Y})
|
m.tree.Update(fs.MsgLeftClickLine{Line: msg.Y})
|
||||||
content, err := m.tree.GetSelectedContent()
|
content, err := m.tree.GetSelectedContent()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m.viewport.SetContent(content)
|
m.viewport.SetContent(content)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# what is bubbletea?
|
||||||
|
|
||||||
|
it's a simple framework based on elm architecture
|
||||||
Reference in New Issue
Block a user