Skip to content

Commit e73df17

Browse files
committed
WIP: Tree/DirTree refactor
1 parent c133563 commit e73df17

File tree

8 files changed

+185
-4
lines changed

8 files changed

+185
-4
lines changed

META6.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"build-depends": [
77
],
88
"depends": [
9+
"Color::DirColors:auth<zef:japhb>:ver<0.0.1+>",
910
"Terminal::Capabilities:auth<zef:japhb>:ver<0.0.12+>",
1011
"Terminal::LineEditor:auth<zef:japhb>:ver<0.0.23+>",
1112
"Terminal::Print:auth<zef:terminal-printers>:ver<0.977+>",
@@ -56,7 +57,9 @@
5657
"Terminal::Widgets::TreeView": "lib/Terminal/Widgets/TreeView.rakumod",
5758
"Terminal::Widgets::Utils": "lib/Terminal/Widgets/Utils.rakumod",
5859
"Terminal::Widgets::Utils::Color": "lib/Terminal/Widgets/Utils/Color.rakumod",
60+
"Terminal::Widgets::Viewer::DirTree": "lib/Terminal/Widgets/Viewer/DirTree.rakumod",
5961
"Terminal::Widgets::Viewer::Log": "lib/Terminal/Widgets/Viewer/Log.rakumod",
62+
"Terminal::Widgets::Viewer::Tree": "lib/Terminal/Widgets/Viewer/Tree.rakumod",
6063
"Terminal::Widgets::Viz::SmokeChart": "lib/Terminal/Widgets/Viz/SmokeChart.rakumod",
6164
"Terminal::Widgets::Volatile::DirTree": "lib/Terminal/Widgets/Volatile/DirTree.rakumod",
6265
"Terminal::Widgets::Volatile::Tree": "lib/Terminal/Widgets/Volatile/Tree.rakumod",

examples/dir-tree.raku

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ABSTRACT: Demonstrate the tree view widget for dynamic directory navigation
2+
3+
use Terminal::Widgets::Simple;
4+
use Terminal::Widgets::Events;
5+
use Terminal::Widgets::Volatile::DirTree;
6+
7+
#| A top level UI container based on Terminal::Widgets::Simple::TopLevel
8+
class DirTreeDemo is TopLevel {
9+
method initial-layout($builder, $width, $height) {
10+
my %style;
11+
12+
with $builder {
13+
.button(label => 'Quit', process-input => { $.terminal.quit }),
14+
.divider(line-style => 'light1', style => %(set-h => 1)),
15+
.node(
16+
.with-scrollbars(
17+
.dir-tree-viewer(id => 'dir-tree', style => %(set-w => 15)),
18+
),
19+
.spacer(),
20+
),
21+
.divider(line-style => 'light1', style => %(set-h => 1)),
22+
.with-scrollbars(.log-viewer(id => 'click-log')),
23+
}
24+
}
25+
26+
multi method handle-event(Terminal::Widgets::Events::LayoutBuilt:D, BubbleUp) {
27+
%.by-id<dir-tree>.set-root(dir-tree-node('/'));
28+
%.by-id<dir-tree>.display-root.set-expanded(True);
29+
%.by-id<dir-tree>.display-root.children.first(*.data.short-name eq 'boot').set-expanded(True);
30+
}
31+
}
32+
33+
sub MAIN() {
34+
# Boot a Terminal::Widgets::App and jump right to the demo screen
35+
App.new.boot-to-screen('demo', DirTreeDemo, title => 'Directory Tree Demo');
36+
}

lib/Terminal/Widgets/Layout.rakumod

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,12 @@ class TreeView is Leaf { }
433433
#| A multi-line auto-scrolling log viewer
434434
class LogViewer is Leaf { }
435435

436+
#| A navigable tree viewer widget
437+
class TreeViewer is Leaf { }
438+
439+
#| A navigable tree viewer widget, specialized for directory trees
440+
class DirTreeViewer is TreeViewer { }
441+
436442
#| A simple smoke chart visualization
437443
class SmokeChart is Leaf { }
438444

@@ -574,11 +580,15 @@ class Builder {
574580
method divider(|c) { self.build-leaf(Divider, |c) }
575581
method hscroll(|c) { self.build-leaf(HScrollBar, |c) }
576582
method vscroll(|c) { self.build-leaf(VScrollBar, |c) }
583+
method plain-text(|c) { self.build-leaf(PlainText, |c) }
584+
method smoke-chart(|c) { self.build-leaf(SmokeChart, |c) }
585+
586+
# Viewer leaf nodes (no children ever)
577587
method rich-text(|c) { self.build-leaf(RichText, |c) }
578588
method tree-view(|c) { self.build-leaf(TreeView, |c) }
579589
method log-viewer(|c) { self.build-leaf(LogViewer, |c) }
580-
method plain-text(|c) { self.build-leaf(PlainText, |c) }
581-
method smoke-chart(|c) { self.build-leaf(SmokeChart, |c) }
590+
method tree-viewer(|c) { self.build-leaf(TreeViewer, |c) }
591+
method dir-tree-viewer(|c) { self.build-leaf(DirTreeViewer, |c) }
582592

583593
# Input leaf nodes (no children ever)
584594
method menu(|c) { self.build-leaf(Menu, |c) }

lib/Terminal/Widgets/StandardWidgetBuilder.rakumod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use Terminal::Widgets::Input::RadioButton;
1212
use Terminal::Widgets::Input::ToggleButton;
1313
use Terminal::Widgets::Input::Text;
1414
use Terminal::Widgets::Viewer::Log;
15+
use Terminal::Widgets::Viewer::Tree;
16+
use Terminal::Widgets::Viewer::DirTree;
1517
use Terminal::Widgets::Viz::SmokeChart;
1618

1719

@@ -33,6 +35,8 @@ class Terminal::Widgets::StandardWidgetBuilder {
3335
(Terminal::Widgets::Layout::ToggleButton) => Terminal::Widgets::Input::ToggleButton,
3436
(Terminal::Widgets::Layout::TextInput) => Terminal::Widgets::Input::Text,
3537
(Terminal::Widgets::Layout::LogViewer) => Terminal::Widgets::Viewer::Log,
38+
(Terminal::Widgets::Layout::TreeViewer) => Terminal::Widgets::Viewer::Tree,
39+
(Terminal::Widgets::Layout::DirTreeViewer) => Terminal::Widgets::Viewer::DirTree,
3640
(Terminal::Widgets::Layout::SmokeChart) => Terminal::Widgets::Viz::SmokeChart,
3741
;
3842
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# ABSTRACT: Tree viewer specialized for directory trees
2+
3+
use Color::DirColors;
4+
5+
use Terminal::Widgets::Viewer::Tree;
6+
7+
8+
class Terminal::Widgets::Viewer::DirTree
9+
is Terminal::Widgets::Viewer::Tree {
10+
has Color::DirColors:D $.dir-colors .= new-from-env;
11+
12+
#| Displayed content for a given node itself, not including children
13+
method node-content($node) {
14+
# XXXX: TEMP HACK
15+
my $color = $.dir-colors.sgr-for($node.data.path);
16+
$color ~ $node.data.short-name ~ "\e[0m"
17+
}
18+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# ABSTRACT: A viewer/browser for a Volatile::Tree
2+
3+
use Terminal::Widgets::Widget;
4+
use Terminal::Widgets::Scrollable;
5+
use Terminal::Widgets::Focusable;
6+
use Terminal::Widgets::Volatile::Tree;
7+
8+
constant VTree = Terminal::Widgets::Volatile::Tree;
9+
10+
11+
my role DisplayNode {
12+
has DisplayNode $.parent;
13+
has VTree::Node $.data is required;
14+
has UInt:D $.depth is required;
15+
16+
# REQUIRED: Total number of entries in this node and any visible children
17+
method branch-size(--> UInt:D) { ... }
18+
}
19+
20+
my class DisplayLeaf does DisplayNode {
21+
method branch-size(--> 1) { }
22+
}
23+
24+
my class DisplayParent does DisplayNode {
25+
has DisplayNode:D @.children;
26+
has Bool:D $.expanded = False;
27+
28+
method refresh-children() {
29+
my $depth = $!depth + 1;
30+
@!children = $.data.children(:refresh).sort(*.short-name).map: {
31+
$_ ~~ VTree::Parent
32+
?? DisplayParent.new(parent => self, data => $_, :$depth)
33+
!! DisplayLeaf.new( parent => self, data => $_, :$depth)
34+
}
35+
}
36+
37+
method toggle-expanded() { self.set-expanded(!$!expanded) }
38+
39+
method set-expanded($!expanded) {
40+
if $!expanded {
41+
self.refresh-children;
42+
}
43+
else {
44+
@!children = Empty;
45+
}
46+
}
47+
48+
method branch-size(--> UInt:D) {
49+
$!expanded ?? 1 + @!children.map(*.branch-size).sum
50+
!! 1
51+
}
52+
}
53+
54+
55+
class Terminal::Widgets::Viewer::Tree
56+
is Terminal::Widgets::Widget
57+
does Terminal::Widgets::Scrollable
58+
does Terminal::Widgets::Focusable {
59+
has VTree::Node $.root;
60+
has DisplayParent $.display-root is built(False);
61+
62+
# Keep root and display-root in sync
63+
method set-root(VTree::Node:D $!root) { self!remap-root }
64+
method !remap-root() {
65+
$!display-root = DisplayParent.new(data => $!root, depth => 0);
66+
}
67+
68+
method draw-content() {
69+
my @lines = self.node-lines($!display-root);
70+
.note for @lines;
71+
}
72+
73+
#| Displayable lines for a given node
74+
method node-lines($node) {
75+
my $is-parent = $node ~~ DisplayParent;
76+
my $first-line = self.prefix-string($node)
77+
~ self.node-content($node);
78+
79+
$is-parent ?? ($first-line,
80+
$node.children.map({ self.node-lines($_).Slip })).flat
81+
!! ($first-line, )
82+
}
83+
84+
#| Prefix for first line of a given node
85+
method prefix-string($node) {
86+
' ' x $node.depth
87+
~ ($node ~~ DisplayParent ?? self.arrows()[+$node.expanded] !! ' ')
88+
~ ' '
89+
}
90+
91+
#| Displayed content for a given node itself, not including children
92+
method node-content($node) {
93+
$node.data.short-name
94+
}
95+
96+
#| Arrow glyphs for given terminal capabilities
97+
method arrows($caps = self.terminal.caps) {
98+
my constant %arrows =
99+
ASCII => « > v »,
100+
MES2 => « > ∨ »,
101+
Uni7 => « ⮞ ⮟ »;
102+
103+
$caps.best-symbol-choice(%arrows)
104+
}
105+
}

lib/Terminal/Widgets/Volatile/DirTree.rakumod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ class Dir does Parent {
6060
}
6161
}
6262

63-
sub dir-tree-node(IO::Path:D() $path, Node :$parent) is export {
63+
sub dir-tree-node(IO::Path:D() $path, VTree::Node :$parent) is export {
6464
with $path {
65-
.d ?? Dir.new( :$parent, path => $_) !!
6665
.l ?? SymLink.new(:$parent, path => $_, target => .readlink) !!
66+
.d ?? Dir.new( :$parent, path => $_) !!
6767
.f ?? File.new( :$parent, path => $_) !!
6868
.dev ?? Dev.new( :$parent, path => $_) !!
6969
Misc.new( :$parent, path => $_) ;

t/00-use.rakutest

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ use Terminal::Widgets::Input::Text;
5151

5252
use Terminal::Widgets::Progress::Tracker;
5353

54+
use Terminal::Widgets::Volatile::Tree;
55+
use Terminal::Widgets::Volatile::DirTree;
56+
5457
use Terminal::Widgets::Viewer::Log;
58+
use Terminal::Widgets::Viewer::Tree;
59+
use Terminal::Widgets::Viewer::DirTree;
5560

5661
use Terminal::Widgets::Viz::SmokeChart;
5762

0 commit comments

Comments
 (0)