bare-tui 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/filepicker.js +42 -7
- package/package.json +1 -1
package/components/filepicker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// filepicker — browse a filesystem and pick a file.
|
|
1
|
+
// filepicker — browse a filesystem and pick a file (or a directory).
|
|
2
2
|
//
|
|
3
3
|
// Dependency-injected so the framework core stays filesystem-free: pass your
|
|
4
4
|
// own { fs, path } to create(), or let it lazily require('bare-fs') /
|
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
// filepicker, so consumers who don't use it never pull those modules in.
|
|
7
7
|
//
|
|
8
8
|
// const fp = filepicker.create({ height: 12 }) // real fs
|
|
9
|
+
// const fp = filepicker.create({ pick: 'dir' }) // choose a directory
|
|
9
10
|
// const fp = filepicker.create({ ...filepicker.mock(tree), cwd: '/' }) // tests
|
|
10
11
|
//
|
|
12
|
+
// `pick` ('file' default, or 'dir') decides what enter selects. In 'file' mode
|
|
13
|
+
// enter/→/l opens a directory or selects a file. In 'dir' mode →/l descends into
|
|
14
|
+
// the highlighted directory while enter SELECTS it; files are shown dimmed and
|
|
15
|
+
// can't be chosen.
|
|
16
|
+
//
|
|
11
17
|
// Directory reads happen through Cmds (async), surfacing as Msgs:
|
|
12
18
|
// { type: 'filepicker.entries', dir, entries }
|
|
13
19
|
// { type: 'filepicker.error', dir, error }
|
|
14
|
-
// { type: 'filepicker.select', path } // a file was chosen
|
|
20
|
+
// { type: 'filepicker.select', path } // a file (or directory) was chosen
|
|
15
21
|
//
|
|
16
22
|
// The only fs surface used is fs.readdir(dir, { withFileTypes: true }, cb); the
|
|
17
23
|
// only path surface is path.join and path.dirname. The mock implements exactly
|
|
@@ -27,6 +33,8 @@ const keys = {
|
|
|
27
33
|
up: key.binding({ keys: ['up', 'k'], help: { key: '↑/k', desc: 'up' } }),
|
|
28
34
|
down: key.binding({ keys: ['down', 'j'], help: { key: '↓/j', desc: 'down' } }),
|
|
29
35
|
open: key.binding({ keys: ['enter', 'right', 'l'], help: { key: '↵', desc: 'open' } }),
|
|
36
|
+
descend: key.binding({ keys: ['right', 'l'], help: { key: '→/l', desc: 'open dir' } }),
|
|
37
|
+
choose: key.binding({ keys: ['enter'], help: { key: '↵', desc: 'select' } }),
|
|
30
38
|
back: key.binding({ keys: ['backspace', 'left', 'h'], help: { key: '⌫', desc: 'up dir' } })
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -55,6 +63,7 @@ class FilePicker {
|
|
|
55
63
|
this.cwd = opts.cwd
|
|
56
64
|
this.height = opts.height || 12
|
|
57
65
|
this.showHidden = !!opts.showHidden
|
|
66
|
+
this.pick = opts.pick === 'dir' ? 'dir' : 'file'
|
|
58
67
|
|
|
59
68
|
this.entries = []
|
|
60
69
|
this.cursor = 0
|
|
@@ -113,10 +122,32 @@ class FilePicker {
|
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
_key(msg) {
|
|
116
|
-
if (key.matches(msg, keys.up))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
if (key.matches(msg, keys.up)) {
|
|
126
|
+
this._move(-1)
|
|
127
|
+
return [this, null]
|
|
128
|
+
}
|
|
129
|
+
if (key.matches(msg, keys.down)) {
|
|
130
|
+
this._move(1)
|
|
131
|
+
return [this, null]
|
|
132
|
+
}
|
|
133
|
+
if (key.matches(msg, keys.back)) return this._open(this.path.dirname(this.cwd))
|
|
134
|
+
|
|
135
|
+
// dir mode: →/l descends into the highlighted directory, enter selects it;
|
|
136
|
+
// files aren't selectable.
|
|
137
|
+
if (this.pick === 'dir') {
|
|
138
|
+
const entry = this.entries[this.cursor]
|
|
139
|
+
if (!entry || !entry.directory) return [this, null]
|
|
140
|
+
const full = this.path.join(this.cwd, entry.name)
|
|
141
|
+
if (key.matches(msg, keys.descend)) return this._open(full)
|
|
142
|
+
if (key.matches(msg, keys.choose)) {
|
|
143
|
+
this.selected = full
|
|
144
|
+
return [this, () => ({ type: 'filepicker.select', path: full })]
|
|
145
|
+
}
|
|
146
|
+
return [this, null]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// file mode: enter/→/l opens a directory or selects a file.
|
|
150
|
+
if (key.matches(msg, keys.open)) {
|
|
120
151
|
const entry = this.entries[this.cursor]
|
|
121
152
|
if (!entry) return [this, null]
|
|
122
153
|
const full = this.path.join(this.cwd, entry.name)
|
|
@@ -159,7 +190,11 @@ class FilePicker {
|
|
|
159
190
|
const entry = this.entries[p]
|
|
160
191
|
const label = entry.directory ? entry.name + '/' : entry.name
|
|
161
192
|
const text = (p === this.cursor ? '› ' : ' ') + label
|
|
162
|
-
|
|
193
|
+
// In dir mode files aren't selectable, so render them dimmed.
|
|
194
|
+
const plain = this.pick === 'dir' && !entry.directory ? dim(text) : text
|
|
195
|
+
rows.push(
|
|
196
|
+
p === this.cursor ? selectedStyle(text) : entry.directory ? dirStyle(text) : plain
|
|
197
|
+
)
|
|
163
198
|
}
|
|
164
199
|
}
|
|
165
200
|
while (rows.length < this.height) rows.push('')
|