turbo-editors/turbo-corepublic Fork 0
v1.0.0
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.

README.md · 75 lines · 4.5 KBmarkdown Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1# filetree
2
3A project's files as an expandable tree, and the widget that shows one.
4
5Depends on `ui` for its place on screen and `theme` for its colours, and on nothing above it. `app` opens and owns the window; this package knows nothing about menus, buffers or language servers.
6
7## Two halves
8
9**`Tree` and `Node`** — the model. It reads directories, remembers what is open, and flattens itself into the rows a widget would paint. No tcell, no `ui`, no screen: it is tested by making files and comparing values, which is where most of this package's tests are.
10
11**`View`** — a `ui.Widget`. It draws the rows, walks them with the keyboard and the mouse, and hands a chosen file's path to whoever set `OnOpen`.
12
13## What it shows
14
15Directories first, then files, each group sorted by name. **`.git` is the only thing hidden.**
16
17That is deliberately not the Open dialog's rule, which hides every dot-entry. `.turbo-go/settings.toml` is a file the editor asks people to edit, and `.gitignore` and `.qlty/` belong to the project too — hiding them would make the editor's own configuration unreachable from the editor's own file browser. `.git` differs in kind rather than spelling: nothing in it is meant to be opened by hand, and it holds enough objects to bury everything else.
18
19## Reading is lazy, refreshing is not a walk
20
21A directory is read the first time it is expanded. Opening a tree on a large project costs one listing.
22
23`Refresh` re-reads **only the directories that have already been read**, so refreshing costs what is on screen rather than a walk of the project. It keeps the shape: a directory that was open stays open, one that has gone takes its branch with it, and the widget puts the highlight back on the same entry — or on the nearest remaining row when that entry is gone.
24
25There is no filesystem watching, and no dependency for one. The caller picks the moments: `app` refreshes after a save, and the view refreshes itself on `F5` or `Ctrl-R`.
26
27## Theme keys
28
29`tree.text`, `tree.directory`, `tree.selected`, `tree.unfocused`.
30
31These could not be borrowed from the `list.*` group, and the reason is worth keeping: `list.selected` is coloured to stand out against a **dialog**. In `turbo-classic` it is white on navy while `window.body` is navy — a tree in a window would have highlighted its selected row in the colour it sits on. `TestTheHighlightIsVisibleInEveryShippedTheme` holds every shipped theme to 64 channel values between `tree.text` and `tree.selected`.
32
33## Public API
34
35| Name | What it does |
36| --- | --- |
37| `New(root string) (*Tree, error)` | Reads a directory; errors on a file or a path that cannot be read |
38| `(*Tree) Root() *Node` | The directory the tree is rooted at |
39| `(*Tree) Rows() []Row` | The visible lines, top to bottom; the root is not one of them |
40| `(*Tree) Refresh()` | Re-reads what has been read, keeping the shape |
41| `Row{Node, Depth}` | One line and how deep it sits |
42| `(*Node) Name/Path/IsDir/Expanded/Children` | What one entry is |
43| `(*Node) Expand/Collapse/Toggle` | Opening and closing a directory |
44| `NewView(root string) (*View, error)` | Starts a tree and returns the widget showing it |
45| `(*View) Title() string` | The project's directory name, for the window |
46| `(*View) Root() string` | The absolute path it is rooted at |
47| `(*View) Selected() *Node` | The highlighted entry, or nil |
48| `(*View) Choose()` | Act on the highlight: open a file, toggle a directory |
49| `(*View) Refresh()` | Re-read, keeping the highlight where it was |
50| `(*View) OnOpen func(path string)` | Called with a chosen **file's** path; a directory never reaches it |
51| `(*View) Draw/SetBounds/HandleKey/HandleMouse` | The `ui` widget contract |
52
53```go
54view, err := filetree.NewView(".")
55if err != nil {
56 return err
57}
58view.OnOpen = func(path string) { editor.Open(path) }
59window := ui.NewWindow(view.Title(), view)
60```
61
62## Keys
63
64`↑` `↓` `PgUp` `PgDn` `Home` `End` move. `→` expands a closed directory and otherwise steps to the next row; `←` collapses an open one and otherwise steps out to its parent — so the two together walk the shape and neither is ever a no-op. `Enter` chooses. `F5` and `Ctrl-R` refresh.
65
66The parent is found as *the nearest row above with a smaller depth*, which is what the eye reads as "the folder this is in" and needs no parent pointer on the nodes.
67
68## Tests
69
70```sh
71make test
72go test ./filetree/
73```
74
75The model tests build real directories under `t.TempDir()`; the view tests draw onto a `tcell.SimulationScreen` and assert on the picture a terminal would show.