add keyboard based movement on the file tree
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func walkFileSystemAndBuildTree(rootPath string, node *FsNode) error {
|
||||
@@ -60,6 +61,14 @@ func walkFileSystemAndBuildTree(rootPath string, node *FsNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort children: files first, then folders
|
||||
sort.Slice(node.children, func(i, j int) bool {
|
||||
if node.children[i].nodeType == node.children[j].nodeType {
|
||||
return node.children[i].FileName() < node.children[j].FileName()
|
||||
}
|
||||
return node.children[i].nodeType == FileNode
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+96
-3
@@ -34,7 +34,7 @@ type FsNode struct {
|
||||
children []*FsNode
|
||||
parent *FsNode // for fast traversal up the tree
|
||||
// for view layer
|
||||
expanded bool
|
||||
expanded bool // makes sense only for folder nodes
|
||||
}
|
||||
|
||||
func (n *FsNode) FileName() string {
|
||||
@@ -43,7 +43,8 @@ func (n *FsNode) FileName() string {
|
||||
|
||||
// ==================== FsNode definition ====================
|
||||
type FsTree struct {
|
||||
root *FsNode
|
||||
root *FsNode
|
||||
selected *FsNode // currently selected node
|
||||
}
|
||||
|
||||
func NewFsTree(rootPath string) *FsTree {
|
||||
@@ -55,6 +56,12 @@ func NewFsTree(rootPath string) *FsTree {
|
||||
}
|
||||
walkFileSystemAndBuildTree(rootPath, root)
|
||||
|
||||
if len(root.children) > 0 {
|
||||
return &FsTree{
|
||||
root: root,
|
||||
selected: root.children[0],
|
||||
}
|
||||
}
|
||||
return &FsTree{
|
||||
root: root,
|
||||
}
|
||||
@@ -108,6 +115,86 @@ func (t *FsTree) ToggleExpand(node *FsNode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// At this point I think there HAS to be a simpler way to do this
|
||||
// but since I'm going to refactor it some day let's leave this
|
||||
// as it is
|
||||
func (t *FsTree) move(delta int) error {
|
||||
if t.selected == nil {
|
||||
return errors.New("no node is currently selected")
|
||||
}
|
||||
if delta != -1 && delta != 1 {
|
||||
return errors.New("delta must be either -1 (up) or 1 (down)")
|
||||
}
|
||||
|
||||
// base case: if it's a folder and expanded and going down
|
||||
if delta == 1 && t.selected.nodeType == FolderNode && t.selected.expanded && len(t.selected.children) > 0 {
|
||||
t.selected = t.selected.children[0]
|
||||
return nil
|
||||
}
|
||||
// base case: if it's the first element of a folder and going up
|
||||
if delta == -1 && t.selected.parent != t.root {
|
||||
parent := t.selected.parent
|
||||
if len(parent.children) > 0 && parent.children[0] == t.selected {
|
||||
t.selected = parent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// same level sibling move
|
||||
// the idea is really simple for now:
|
||||
// - go up the parent and find the sibling in the direction
|
||||
// - if no sibling go up again
|
||||
// - if no parent stop
|
||||
siblingFor := t.selected
|
||||
for siblingFor.parent != nil {
|
||||
parent := siblingFor.parent
|
||||
// O(n) for now, though easy to obtimize with index tracking
|
||||
nextIdx := -1
|
||||
for idx, child := range parent.children {
|
||||
if child == siblingFor {
|
||||
nextIdx = idx + delta
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nextIdx >= 0 && nextIdx < len(parent.children) {
|
||||
// found case
|
||||
t.selected = parent.children[nextIdx]
|
||||
break
|
||||
}
|
||||
// not found case, go up again
|
||||
siblingFor = parent
|
||||
}
|
||||
|
||||
// case: folder and I'm going up, I want to go as deep and last
|
||||
if delta == -1 && t.selected.nodeType == FolderNode {
|
||||
current := t.selected
|
||||
for {
|
||||
if len(current.children) == 0 || !current.expanded {
|
||||
break
|
||||
}
|
||||
current = current.children[len(current.children)-1]
|
||||
}
|
||||
t.selected = current
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *FsTree) MoveUp() error { return t.move(-1) }
|
||||
func (t *FsTree) MoveDown() error { return t.move(1) }
|
||||
|
||||
func (t *FsTree) ToggleSelectedExpand() error {
|
||||
if t.selected == nil {
|
||||
return errors.New("no node is currently selected")
|
||||
}
|
||||
if t.selected.nodeType != FolderNode {
|
||||
return errors.New("only folder nodes can be expanded or collapsed")
|
||||
}
|
||||
|
||||
t.ToggleExpand(t.selected)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *FsTree) Render() string {
|
||||
builder := &strings.Builder{}
|
||||
t.renderNode(t.root, 0, builder)
|
||||
@@ -135,7 +222,13 @@ func (t *FsTree) renderNode(node *FsNode, depth int, builder *strings.Builder) {
|
||||
icon = lipgloss.NewStyle().Faint(true).Render(prevIndent + icon + indent)
|
||||
}
|
||||
|
||||
line := icon + " " + node.FileName() + "\n"
|
||||
// Apply highlight if selected
|
||||
fileName := node.FileName()
|
||||
if node == t.selected {
|
||||
fileName = lipgloss.NewStyle().Foreground(styles.Highlight).Bold(true).Render(fileName)
|
||||
}
|
||||
|
||||
line := icon + " " + fileName + "\n"
|
||||
builder.WriteString(line)
|
||||
|
||||
if node.expanded {
|
||||
|
||||
@@ -95,6 +95,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
return m, tea.Quit
|
||||
case "up", "k":
|
||||
if err := m.tree.MoveUp(); err != nil {
|
||||
fmt.Println("Error moving up:", err)
|
||||
}
|
||||
case "down", "j":
|
||||
if err := m.tree.MoveDown(); err != nil {
|
||||
fmt.Println("Error moving down:", err)
|
||||
}
|
||||
case "enter", " ":
|
||||
if err := m.tree.ToggleSelectedExpand(); err != nil {
|
||||
fmt.Println("Error toggling expand:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -14,6 +14,7 @@ var (
|
||||
Primary = lipgloss.Color("#7D56F4")
|
||||
Secondary = lipgloss.Color("#FF5C8F")
|
||||
Background = lipgloss.Color("#1E1E2E")
|
||||
Highlight = lipgloss.Color("#FFA500") // Orange highlight for selected node
|
||||
// TODO: to extend as needed, right now this is just a
|
||||
// placeholder
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user