@@ -58,6 +58,7 @@ class Terminal::Widgets::Viewer::Tree
5858 does Terminal::Widgets::Focusable {
5959 has VTree::Node $ . root ;
6060 has DisplayParent $ . display-root is built(False );
61+ has DisplayNode $ . current-node is built(False );
6162 has & . process-click ;
6263
6364 has @ ! flat-node-cache ;
@@ -97,6 +98,7 @@ class Terminal::Widgets::Viewer::Tree
9798 method set-root (VTree::Node: D $ ! root ) { self ! remap-root }
9899 method ! remap-root () {
99100 $ ! display-root = DisplayParent. new (data => $ ! root , depth => 0 );
101+ $ ! current-node = $ ! display-root ;
100102 self . clear-caches;
101103 }
102104
@@ -152,10 +154,103 @@ class Terminal::Widgets::Viewer::Tree
152154 $ caps . best-symbol-choice(% arrows )
153155 }
154156
155- method line-to-display-node ($ line ) {
157+ method line-to-display-node (UInt : D $ line ) {
156158 self . flat-node-cache[$ line ]
157159 }
158160
161+ method display-node-to-line ($ node ) {
162+ self . flat-node-cache. first (* === $ node , : k)
163+ }
164+
165+ method select-node ($ node ) {
166+ $ ! current-node = $ node ;
167+ self . ensure-parents-expanded($ node );
168+ $ _ ($ node ) with & ! process-click ;
169+
170+ # XXXX: Ensure visible?
171+ }
172+
173+ method select-prev-node () {
174+ my $ line = self . display-node-to-line($ ! current-node );
175+ return unless $ line ;
176+
177+ if self . line-to-display-node($ line - 1 ) -> $ node {
178+ self . select-node($ node );
179+ self . ensure-y-span-visible($ line - 1 , $ line );
180+ self . refresh-for-scroll;
181+ }
182+ }
183+
184+ method select-next-node () {
185+ my $ line = self . display-node-to-line($ ! current-node );
186+ return unless $ line . defined ;
187+
188+ if self . line-to-display-node($ line + 1 ) -> $ node {
189+ self . select-node($ node );
190+ self . ensure-y-span-visible($ line , $ line + 1 );
191+ self . refresh-for-scroll;
192+ }
193+ }
194+
195+ method refresh-for-expand-change () {
196+ self . clear-caches;
197+ self . fix-scroll-maxes;
198+ self . refresh-for-scroll;
199+ }
200+
201+ method ensure-parents-expanded ($ node ) {
202+ my $ parent = $ node . parent ;
203+ my $ changed = False ;
204+
205+ while $ parent {
206+ unless $ parent . expanded {
207+ $ parent . set-expanded(True );
208+ $ changed = True ;
209+ }
210+ $ parent .= parent;
211+ }
212+
213+ self . refresh-for-expand-change if $ changed ;
214+ }
215+
216+ method set-node-expanded ($ node , Bool : D $ expanded = True ) {
217+ if $ node ~~ DisplayParent && $ node . expanded != $ expanded {
218+ $ node . set-expanded($ expanded );
219+ self . refresh-for-expand-change;
220+ }
221+ }
222+
223+ method toggle-node-expanded ($ node ) {
224+ if $ node ~~ DisplayParent {
225+ $ node . toggle-expanded;
226+ self . refresh-for-expand-change;
227+ }
228+ }
229+
230+ multi method handle-event (Terminal::Widgets::Events::KeyboardEvent: D
231+ $ event where *. key . defined , AtTarget) {
232+ my constant % keymap =
233+ CursorDown => ' node-next' ,
234+ CursorUp => ' node-prev' ,
235+ CursorRight => ' node-expand' ,
236+ CursorLeft => ' node-collapse' ,
237+ Ctrl-M => ' node-toggle' , # Enter
238+ Ctrl-I => ' focus-next' , # Tab
239+ ShiftTab => ' focus-prev' , # Shift-Tab is weird and special
240+ ;
241+
242+ my $ keyname = $ event . keyname;
243+ with % keymap {$ keyname } {
244+ when ' node-next' { self . select-next-node }
245+ when ' node-prev' { self . select-prev-node }
246+ when ' node-expand' { self . set-node-expanded($ . current-node , True ) }
247+ when ' node-collapse' { self . set-node-expanded($ . current-node , False ) }
248+ when ' node-toggle' { self . toggle-node-expanded($ . current-node ) }
249+ when ' focus-next' { self . focus-next }
250+ when ' focus-prev' { self . focus-prev }
251+ }
252+ }
253+
159254 multi method handle-event (Terminal::Widgets::Events::MouseEvent: D
160255 $ event where !*. mouse. pressed, AtTarget) {
161256 # Take focus even if clicked on framing instead of content area
@@ -169,12 +264,8 @@ class Terminal::Widgets::Viewer::Tree
169264 my $ clicked-line = $ . y-scroll + $ y ;
170265 my $ node = self . line-to-display-node($ clicked-line );
171266
172- if $ node ~~ DisplayParent {
173- $ node . toggle-expanded;
174- self . clear-caches;
175- self . fix-scroll-maxes;
176- self . refresh-for-scroll;
177- }
267+ self . select-node($ node );
268+ self . toggle-node-expanded($ node );
178269
179270 if $ node {
180271 $ _ ($ node ) with & ! process-click ;
0 commit comments