Skip to content

Commit fc24ef1

Browse files
authored
Merge pull request #67 from anc95/fature-enableGoUpperDirectory
feature: enable go upper directory
2 parents 66738db + d598a6e commit fc24ef1

File tree

7 files changed

+137
-43
lines changed

7 files changed

+137
-43
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ inquirer.prompt({
2121
```
2222

2323
### Options
24-
Takes `type`, `name`, `message`, [`filter`, `validate`, `transformer`, `default`, `pageSize`, `onlyShowDir`, `onlyShowValid`, `hideChildrenOfValid`, `root`, `hideRoot`, `multiple`] properties.
24+
Takes `type`, `name`, `message`, [`filter`, `validate`, `transformer`, `default`, `pageSize`, `onlyShowDir`, `onlyShowValid`, `hideChildrenOfValid`, `root`, `hideRoot`, `multiple`, `enableGoUpperDirector`] properties.
2525

2626
The extra options that this plugin provides are:
2727
- `onlyShowDir`: (Boolean) if true, will only show directory. Default: false.
@@ -30,6 +30,7 @@ The extra options that this plugin provides are:
3030
- `hideChildrenOfValid`: (Boolean) if true, will hide children of valid directories (if `validate` is provided). Default: false.
3131
- `transformer`: (Function) a hook function to transform the display of directory or file name.
3232
- `multiple`: (Boolean) if true, will enable to select multiple files. Default: false.
33+
- `enableGoUpperDirector`: (Boolean) Show `..` in inside root dir, and the user can press **space** on it to go upper directory. Default: false.
3334

3435
When `multiple` is enabled, `default` should be `string[]` type, otherwise it's `string` type.
3536
### Typescript Support

example/custom.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ inquirer
1010
{
1111
type: 'file-tree-selection',
1212
name: 'file',
13-
default: '/Users/anchao/code/inquirer-file-tree-selection/example/multiple.js',
13+
default: path.join(__dirname, './multiple.js'),
1414
message: 'choose a file',
1515
transformer: (input) => {
1616
const name = input.split(path.sep).pop();

example/jumpUp.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const inquirer = require('inquirer')
2+
const inquirerFileTreeSelection = require('../dist/index')
3+
const path = require('path');
4+
const chalk = require('chalk');
5+
6+
inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection)
7+
8+
inquirer
9+
.prompt([
10+
{
11+
type: 'file-tree-selection',
12+
name: 'file',
13+
message: 'choose a file',
14+
enableGoUpperDirectory: true
15+
},
16+
])
17+
.then(answers => {
18+
console.log(JSON.stringify(answers))
19+
});

src/index.ts

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const observe = require('inquirer/lib/utils/events');
1515
import Base from 'inquirer/lib/prompts/base';
1616
import { Question, Transformer } from 'inquirer'
1717
import Paginator from 'inquirer/lib/utils/paginator';
18+
import { Node } from './types';
19+
import { isSubPath } from './utils';
20+
import { getUpperDirNode } from './upperDir';
1821

1922
type FileTreeSelectionPromptOptions<T = any> = Pick<Question<T>, 'type' | 'name' | 'message' | 'filter' | 'validate' | 'default'> & {
2023
transformer?: Transformer<T>
@@ -47,6 +50,10 @@ type FileTreeSelectionPromptOptions<T = any> = Pick<Question<T>, 'type' | 'name'
4750
*/
4851
hideRoot?: boolean
4952
selectedList?: string[]
53+
/**
54+
* show `..` in inside root dir, and you the user can press space on it to go upper directory. Default: false
55+
*/
56+
enableGoUpperDirectory?: boolean
5057
}
5158

5259
declare module 'inquirer' {
@@ -60,29 +67,36 @@ declare module 'inquirer' {
6067
* onlyShowDir: boolean (default: false)
6168
*/
6269
class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {states: any}> {
63-
fileTree: any
70+
rootNode: Node
6471
firstRender: boolean
6572
shownList: string[] | Record<string, any>
6673
selectedList: string[] | Record<string, any>
6774
paginator: Paginator
6875
done: (...args: any[]) => void
69-
active: Record<string, any>
76+
active: Node
77+
78+
get fileTree() {
79+
if (this.opt.hideRoot) {
80+
return this.rootNode
81+
}
7082

83+
return {
84+
children: [this.rootNode]
85+
}
86+
}
7187

7288
constructor(questions, rl, answers) {
7389
super(questions, rl, answers);
7490

7591
const root = path.resolve(process.cwd(), this.opt.root || '.');
76-
const rootNode = {
92+
const rootNode: Node = {
7793
path: root,
7894
type: 'directory',
7995
name: '.(root directory)',
8096
_rootNode: true
8197
}
8298

83-
this.fileTree = {
84-
children: [rootNode]
85-
};
99+
this.rootNode = rootNode
86100

87101
this.shownList = []
88102

@@ -162,25 +176,21 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
162176

163177
cliCursor.hide();
164178
if (this.firstRender) {
165-
const rootNode = this.fileTree.children[0];
179+
const rootNode = this.rootNode;
166180

167181
await this.prepareChildren(rootNode);
168182
rootNode.open = true;
169-
if (this.opt.hideRoot) {
170-
this.fileTree.children = rootNode.children;
171-
this.active = this.active || this.fileTree.children[0];
172-
} else {
173-
this.active = this.active || rootNode.children[0];
174-
}
175-
this.render();
183+
this.active = this.active || rootNode.children[0];
176184
this.prepareChildren(this.active);
185+
this.render()
177186
}
178187

179188
return this;
180189
}
181190

182191
renderFileTree(root = this.fileTree, indent = 2) {
183192
const children = root.children || []
193+
184194
let output = ''
185195
const transformer = this.opt.transformer;
186196
const isFinal = this.status === 'answered';
@@ -212,7 +222,10 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
212222
const safeIndent = (indent - prefix.length + 2) > 0
213223
? indent - prefix.length + 2
214224
: 0 ;
215-
if (transformer) {
225+
226+
if (itemPath.name == '..') {
227+
showValue = `${' '.repeat(safeIndent)}${prefix}..(Press \`Space\` to go parent directory)\n`
228+
} else if (transformer) {
216229
const transformedValue = transformer(itemPath.path, this.answers, { isFinal });
217230
showValue = ' '.repeat(safeIndent) + prefix + transformedValue + '\n';
218231
} else {
@@ -237,11 +250,11 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
237250
return output
238251
}
239252

240-
async prepareChildren(node) {
253+
async prepareChildren(node: Node) {
241254
const parentPath = node.path;
242255

243256
try {
244-
if (!fs.lstatSync(parentPath).isDirectory() || node.children || node.open === true) {
257+
if (node.name == '..' || !fs.lstatSync(parentPath).isDirectory() || node.children || node.open === true) {
245258
return;
246259
}
247260

@@ -303,37 +316,41 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
303316
await addValidity(node);
304317
}
305318

319+
if (this.opt.enableGoUpperDirectory && node === this.rootNode) {
320+
this.rootNode.children.unshift(getUpperDirNode(this.rootNode.path))
321+
}
322+
306323
// When it's single selection and has default value, we should expand to the default file.
307324
if (this.firstRender && this.opt.default && !this.opt.multiple) {
308325
const defaultPath = this.opt.default;
309-
const exists = fs.existsSync(defaultPath);
326+
const founded = node.children.find(item => {
327+
if (item.name === '..') {
328+
return false
329+
}
310330

311-
if (exists) {
312-
const founded = node.children.find(item => {
313-
if (item.path === defaultPath) {
314-
return true;
315-
}
331+
if (item.path === defaultPath) {
332+
return true;
333+
}
316334

317-
if (defaultPath.includes(`${item.path}${path.sep}`)) {
318-
return true;
319-
}
320-
});
335+
if (defaultPath.includes(`${item.path}${path.sep}`)) {
336+
return true;
337+
}
338+
});
321339

322-
if (founded) {
323-
if (founded.path === defaultPath) {
324-
this.active = founded;
340+
if (founded) {
341+
if (founded.path === defaultPath) {
342+
this.active = founded;
325343

326-
let parent = founded.parent;
344+
let parent = founded.parent;
327345

328-
while (parent && !parent._rootNode) {
329-
parent.open = true;
330-
parent = parent.parent;
331-
}
332-
}
333-
else {
334-
return await this.prepareChildren(founded);
346+
while (parent && !parent._rootNode) {
347+
parent.open = true;
348+
parent = parent.parent;
335349
}
336350
}
351+
else {
352+
return await this.prepareChildren(founded);
353+
}
337354
}
338355
}
339356

@@ -413,7 +430,11 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
413430
}
414431

415432
this.active = this.shownList[index]
416-
this.prepareChildren(this.active);
433+
434+
if (this.active.name !== '..') {
435+
this.prepareChildren(this.active);
436+
}
437+
417438
this.render()
418439
}
419440

@@ -441,8 +462,22 @@ class FileTreeSelectionPrompt extends Base<FileTreeSelectionPromptOptions & {sta
441462
this.render()
442463
}
443464

444-
onSpaceKey(tirggerByTab = false) {
445-
if (!tirggerByTab && this.opt.multiple) {
465+
async onSpaceKey(triggerByTab = false) {
466+
if (!triggerByTab && this.active.name == '..' && isSubPath(this.active.path, this.rootNode.path)) {
467+
this.rootNode = {
468+
...this.active,
469+
name: path.basename(this.active.path),
470+
}
471+
await this.prepareChildren(this.rootNode);
472+
this.active = this.rootNode.children?.[0]
473+
this.firstRender = true
474+
this.rootNode.open = true
475+
this.render()
476+
this.firstRender = false
477+
return
478+
}
479+
480+
if (!triggerByTab && this.opt.multiple) {
446481
if (this.active.isValid === false) {
447482
return
448483
}

src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export interface Node {
2+
name: string,
3+
path: string,
4+
type: 'directory' | 'file',
5+
parent?: Node | undefined,
6+
children?: Node[],
7+
open?: boolean
8+
isValid?: boolean
9+
_rootNode?: boolean
10+
}

src/upperDir.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import path from 'path'
2+
import { Node } from './types'
3+
4+
const getParentDir = (dir: string) => {
5+
return path.dirname(dir)
6+
}
7+
8+
export const getUpperDirNode = (dir: string) => {
9+
const parentDir = getParentDir(dir)
10+
11+
const parentNode: Node = {
12+
name: '..',
13+
path: parentDir,
14+
type: 'directory',
15+
isValid: true
16+
}
17+
18+
return parentNode
19+
}
20+
21+
export const gotoUpperDir = (upperDir: Node, currentRootNode: Node) => {
22+
currentRootNode._rootNode = false
23+
upperDir._rootNode = true
24+
}

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import path from "path"
2+
3+
export const isSubPath = (parent: string, child: string) => {
4+
return !path.relative(parent, child).startsWith('.')
5+
}

0 commit comments

Comments
 (0)