| 🛟 Updated. 28d5985 k33g 18h ago | 1 | package filetree |
| 2 | |
| 3 | import "github.com/gdamore/tcell/v2" |
| 4 | |
| 5 | // HandleKey walks the tree, opens branches and chooses files. |
| 6 | // |
| 7 | // Left and right do what they do in every tree: right opens a closed branch |
| 8 | // and steps into an open one, left closes an open branch and steps out of a |
| 9 | // closed one, so the two together walk the shape without ever being a no-op. |
| 10 | func (v *View) HandleKey(ev *tcell.EventKey) bool { |
| 11 | if !v.Focused() { |
| 12 | return false |
| 13 | } |
| 14 | if v.handleMovementKey(ev) { |
| 15 | return true |
| 16 | } |
| 17 | |
| 18 | switch ev.Key() { |
| 19 | case tcell.KeyRight: |
| 20 | v.expandOrDescend() |
| 21 | case tcell.KeyLeft: |
| 22 | v.collapseOrAscend() |
| 23 | case tcell.KeyEnter: |
| 24 | v.Choose() |
| 25 | case tcell.KeyF5, tcell.KeyCtrlR: |
| 26 | v.Refresh() |
| 27 | default: |
| 28 | return false |
| 29 | } |
| 30 | return true |
| 31 | } |
| 32 | |
| 33 | // handleMovementKey deals with the keys that only move the highlight. |
| 34 | func (v *View) handleMovementKey(ev *tcell.EventKey) bool { |
| 35 | page := max(v.Bounds().H, 1) |
| 36 | |
| 37 | switch ev.Key() { |
| 38 | case tcell.KeyUp: |
| 39 | v.move(-1) |
| 40 | case tcell.KeyDown: |
| 41 | v.move(1) |
| 42 | case tcell.KeyPgUp: |
| 43 | v.move(-page) |
| 44 | case tcell.KeyPgDn: |
| 45 | v.move(page) |
| 46 | case tcell.KeyHome: |
| 47 | v.moveTo(0) |
| 48 | case tcell.KeyEnd: |
| 49 | v.moveTo(len(v.tree.Rows()) - 1) |
| 50 | default: |
| 51 | return false |
| 52 | } |
| 53 | return true |
| 54 | } |
| 55 | |
| 56 | // move shifts the highlight by a number of rows, stopping at either end. |
| 57 | func (v *View) move(by int) { v.moveTo(v.selected + by) } |
| 58 | |
| 59 | // moveTo puts the highlight on a row, clamped to the rows there are. |
| 60 | func (v *View) moveTo(index int) { |
| 61 | v.selected = min(max(index, 0), max(len(v.tree.Rows())-1, 0)) |
| 62 | } |
| 63 | |
| 64 | // expandOrDescend opens a closed directory, or steps into one already open. |
| 65 | func (v *View) expandOrDescend() { |
| 66 | node := v.Selected() |
| 67 | if node == nil { |
| 68 | return |
| 69 | } |
| 70 | if node.IsDir() && !node.Expanded() { |
| 71 | node.Expand() |
| 72 | return |
| 73 | } |
| 74 | v.move(1) |
| 75 | } |
| 76 | |
| 77 | // collapseOrAscend closes an open directory, or steps out to the parent of |
| 78 | // anything else. |
| 79 | func (v *View) collapseOrAscend() { |
| 80 | node := v.Selected() |
| 81 | if node == nil { |
| 82 | return |
| 83 | } |
| 84 | if node.IsDir() && node.Expanded() { |
| 85 | node.Collapse() |
| 86 | v.keepSelectionInRange() |
| 87 | return |
| 88 | } |
| 89 | v.selectParent() |
| 90 | } |
| 91 | |
| 92 | // selectParent moves the highlight to the branch the current row sits in. |
| 93 | // |
| 94 | // The parent is the nearest row above with a smaller depth, which is what the |
| 95 | // eye reads as "the folder this is in" — and needs no parent pointer on the |
| 96 | // nodes to find. |
| 97 | func (v *View) selectParent() { |
| 98 | rows := v.tree.Rows() |
| 99 | if v.selected <= 0 || v.selected >= len(rows) { |
| 100 | return |
| 101 | } |
| 102 | |
| 103 | depth := rows[v.selected].Depth |
| 104 | for i := v.selected - 1; i >= 0; i-- { |
| 105 | if rows[i].Depth < depth { |
| 106 | v.selected = i |
| 107 | return |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | // HandleMouse highlights the clicked row, chooses it on a second click, and |
| 113 | // scrolls with the wheel. |
| 114 | func (v *View) HandleMouse(ev *tcell.EventMouse) bool { |
| 115 | if !v.Bounds().Contains(ev.Position()) { |
| 116 | return false |
| 117 | } |
| 118 | |
| 119 | switch ev.Buttons() { |
| 120 | case tcell.WheelUp: |
| 121 | v.move(-wheelStep) |
| 122 | case tcell.WheelDown: |
| 123 | v.move(wheelStep) |
| 124 | case tcell.Button1: |
| 125 | v.clickRow(ev) |
| 126 | } |
| 127 | return true |
| 128 | } |
| 129 | |
| 130 | // clickRow selects the row under the pointer, or acts on it when it was |
| 131 | // already the selected one — the same second-click rule the list boxes use. |
| 132 | func (v *View) clickRow(ev *tcell.EventMouse) { |
| 133 | _, y := ev.Position() |
| 134 | index := v.top + y - v.Bounds().Y |
| 135 | |
| 136 | if index == v.selected { |
| 137 | v.Choose() |
| 138 | return |
| 139 | } |
| 140 | v.moveTo(index) |
| 141 | } |