turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

view.go · 127 lines · 3.4 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package filetree
2
3import (
4 "codeberg.org/turbo-editors/turbo-core/ui"
5)
6
7// The markers drawn before a directory, and the indentation of one level.
8//
9// A file gets a marker's worth of blank so that names line up whatever their
10// neighbours are, which is what makes a column of names readable at a glance.
11const (
12 openMarker = "▼ "
13 closedMarker = "▶ "
14 fileMarker = " "
15 indent = " "
16)
17
18// wheelStep is how many rows one notch of the mouse wheel moves.
19const wheelStep = 3
20
21// View shows a project's files in a window and opens the one that is chosen.
22//
23// It is a ui.Widget, so it goes into an ordinary window and gets the desktop's
24// moving, resizing and maximising for free.
25//
26// view, err := filetree.NewView(".")
27// if err != nil {
28// return err
29// }
30// view.OnOpen = func(path string) { editor.Open(path) }
31// window := ui.NewWindow(view.Title(), view)
32type View struct {
33 ui.FocusBox
34
35 tree *Tree
36 selected int
37 top int
38
39 // OnOpen is called with the absolute path of a file that was chosen. A
40 // directory never reaches it: choosing one opens or closes the branch.
41 OnOpen func(path string)
42}
43
44// NewView reads a directory and returns the widget showing it.
45//
46// The error is the one New gives: a root that is not a directory, or cannot be
47// read at all, leaves the caller with nothing to show.
48func NewView(root string) (*View, error) {
49 tree, err := New(root)
50 if err != nil {
51 return nil, err
52 }
53 return &View{tree: tree}, nil
54}
55
56// Title returns what the window holding this view should be called: the name
57// of the directory the tree is rooted at.
58func (v *View) Title() string { return v.tree.Root().Name() }
59
60// Root returns the absolute path the tree is rooted at, which is how a caller
61// can tell whether it already has a tree on this project.
62func (v *View) Root() string { return v.tree.Root().Path() }
63
64// Refresh re-reads the project and keeps the highlight on the same entry when
65// it is still there.
66//
67// view.Refresh() // after a build, or when a file is saved
68func (v *View) Refresh() {
69 wanted := v.selectedPath()
70 v.tree.Refresh()
71 v.selectPath(wanted)
72}
73
74// selectedPath returns the path of the highlighted row, or "" when there is
75// no row to be on.
76func (v *View) selectedPath() string {
77 rows := v.tree.Rows()
78 if v.selected < 0 || v.selected >= len(rows) {
79 return ""
80 }
81 return rows[v.selected].Node.Path()
82}
83
84// selectPath puts the highlight back on a path, or on the nearest row that
85// still exists when it has gone.
86func (v *View) selectPath(path string) {
87 rows := v.tree.Rows()
88 for i, row := range rows {
89 if row.Node.Path() == path {
90 v.selected = i
91 return
92 }
93 }
94 v.selected = min(max(v.selected, 0), max(len(rows)-1, 0))
95}
96
97// Selected returns the highlighted node, or nil when the project is empty.
98func (v *View) Selected() *Node {
99 rows := v.tree.Rows()
100 if v.selected < 0 || v.selected >= len(rows) {
101 return nil
102 }
103 return rows[v.selected].Node
104}
105
106// Choose acts on the highlighted row: a directory opens or closes, and a file
107// is handed to OnOpen.
108func (v *View) Choose() {
109 node := v.Selected()
110 if node == nil {
111 return
112 }
113 if node.IsDir() {
114 node.Toggle()
115 v.keepSelectionInRange()
116 return
117 }
118 if v.OnOpen != nil {
119 v.OnOpen(node.Path())
120 }
121}
122
123// keepSelectionInRange holds the highlight inside the rows there are, which
124// collapsing a branch above it can otherwise leave past the end.
125func (v *View) keepSelectionInRange() {
126 v.selected = min(max(v.selected, 0), max(len(v.tree.Rows())-1, 0))
127}