@mak-98/dirvi-cli 0.1.0
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/LICENSE +674 -0
- package/dist/application/dir-buffer-helpers.d.ts +3 -0
- package/dist/application/dir-buffer-helpers.d.ts.map +1 -0
- package/dist/application/dir-buffer-helpers.js +21 -0
- package/dist/application/display-rows.d.ts +4 -0
- package/dist/application/display-rows.d.ts.map +1 -0
- package/dist/application/display-rows.js +50 -0
- package/dist/application/fold-helpers.d.ts +8 -0
- package/dist/application/fold-helpers.d.ts.map +1 -0
- package/dist/application/fold-helpers.js +60 -0
- package/dist/application/reducer.d.ts +29 -0
- package/dist/application/reducer.d.ts.map +1 -0
- package/dist/application/reducer.js +187 -0
- package/dist/domain/print-message.d.ts +9 -0
- package/dist/domain/print-message.d.ts.map +1 -0
- package/dist/domain/print-message.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +74 -0
- package/dist/infrastructure/input.d.ts +7 -0
- package/dist/infrastructure/input.d.ts.map +1 -0
- package/dist/infrastructure/input.js +72 -0
- package/dist/infrastructure/load-initial-props.d.ts +3 -0
- package/dist/infrastructure/load-initial-props.d.ts.map +1 -0
- package/dist/infrastructure/load-initial-props.js +31 -0
- package/dist/ui/App.d.ts +10 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +90 -0
- package/dist/ui/AppShell.d.ts +13 -0
- package/dist/ui/AppShell.d.ts.map +1 -0
- package/dist/ui/AppShell.js +9 -0
- package/dist/ui/components/DirEntries.d.ts +7 -0
- package/dist/ui/components/DirEntries.d.ts.map +1 -0
- package/dist/ui/components/DirEntries.js +40 -0
- package/dist/ui/components/UnixEntry.d.ts +6 -0
- package/dist/ui/components/UnixEntry.d.ts.map +1 -0
- package/dist/ui/components/UnixEntry.js +12 -0
- package/package.json +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function entryAtPath(buffer, path) {
|
|
2
|
+
if (path.length === 0) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
let entries = buffer.entries;
|
|
6
|
+
let entry;
|
|
7
|
+
for (const [index, name] of path.entries()) {
|
|
8
|
+
entry = entries.find((candidate) => candidate.name === name);
|
|
9
|
+
if (entry === undefined) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (index === path.length - 1) {
|
|
13
|
+
return entry;
|
|
14
|
+
}
|
|
15
|
+
if (entry.kind !== 'directory' || entry.entries === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
entries = entry.entries;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { DirectoryBuffer, DisplayRow, EntryPath, FoldNode } from 'dirvi-lib';
|
|
2
|
+
export declare function createDisplayRows(buffer: DirectoryBuffer, folds: FoldNode): DisplayRow[];
|
|
3
|
+
export declare function displayRowAtPath(buffer: DirectoryBuffer, folds: FoldNode, path: EntryPath): DisplayRow | undefined;
|
|
4
|
+
//# sourceMappingURL=display-rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-rows.d.ts","sourceRoot":"","sources":["../../src/application/display-rows.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,UAAU,EACV,SAAS,EACT,QAAQ,EAGT,MAAM,WAAW,CAAC;AAsDnB,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,QAAQ,GACd,UAAU,EAAE,CAEd;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,SAAS,GACd,UAAU,GAAG,SAAS,CAqBxB"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { pathsEqual, } from 'dirvi-lib';
|
|
2
|
+
import { hasFold } from './fold-helpers.js';
|
|
3
|
+
function displayRowsAtPath(entries, path, foldNode) {
|
|
4
|
+
const rows = [];
|
|
5
|
+
let index = 0;
|
|
6
|
+
while (index < entries.length) {
|
|
7
|
+
const entry = entries[index];
|
|
8
|
+
if (foldNode !== undefined && hasFold(foldNode, entry.name)) {
|
|
9
|
+
const entryNames = [];
|
|
10
|
+
while (index < entries.length && hasFold(foldNode, entries[index].name)) {
|
|
11
|
+
entryNames.push(entries[index].name);
|
|
12
|
+
index += 1;
|
|
13
|
+
}
|
|
14
|
+
rows.push({
|
|
15
|
+
kind: 'fold',
|
|
16
|
+
parentPath: path,
|
|
17
|
+
entryNames,
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
rows.push({
|
|
22
|
+
kind: 'entry',
|
|
23
|
+
parentPath: path,
|
|
24
|
+
entry,
|
|
25
|
+
});
|
|
26
|
+
if (entry.kind === 'directory' && entry.entries !== undefined) {
|
|
27
|
+
rows.push(...displayRowsAtPath(entry.entries, [...path, entry.name], foldNode?.children[entry.name]));
|
|
28
|
+
}
|
|
29
|
+
index += 1;
|
|
30
|
+
}
|
|
31
|
+
return rows;
|
|
32
|
+
}
|
|
33
|
+
export function createDisplayRows(buffer, folds) {
|
|
34
|
+
return displayRowsAtPath(buffer.entries, [], folds);
|
|
35
|
+
}
|
|
36
|
+
export function displayRowAtPath(buffer, folds, path) {
|
|
37
|
+
if (path.length === 0) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const rows = createDisplayRows(buffer, folds);
|
|
41
|
+
const parentPath = path.slice(0, -1);
|
|
42
|
+
const entryName = path[path.length - 1];
|
|
43
|
+
return rows.find((row) => {
|
|
44
|
+
if (row.kind === 'entry') {
|
|
45
|
+
return (row.entry.name === entryName && pathsEqual(row.parentPath, parentPath));
|
|
46
|
+
}
|
|
47
|
+
return (pathsEqual(row.parentPath, parentPath) &&
|
|
48
|
+
row.entryNames.includes(entryName));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UnixEntry, UnixEntryName, FoldNode } from 'dirvi-lib';
|
|
2
|
+
export declare function hasFold(node: FoldNode, entryName: UnixEntryName): boolean;
|
|
3
|
+
export declare function entryIsFolded(node: FoldNode | undefined, entry: UnixEntry): boolean;
|
|
4
|
+
export declare function updateFoldNodeAtPath(node: FoldNode, path: UnixEntryName[], update: (node: FoldNode) => FoldNode): FoldNode;
|
|
5
|
+
export declare function setFolds(node: FoldNode, entries: UnixEntry[]): FoldNode;
|
|
6
|
+
export declare function addFold(node: FoldNode, entryName: UnixEntryName): FoldNode;
|
|
7
|
+
export declare function unfoldFoldSequence(node: FoldNode, entries: UnixEntry[], entryName: UnixEntryName): FoldNode;
|
|
8
|
+
//# sourceMappingURL=fold-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold-helpers.d.ts","sourceRoot":"","sources":["../../src/application/fold-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGpE,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,GAAG,OAAO,CAEzE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAEnF;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,aAAa,EAAE,EACrB,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,GACnC,QAAQ,CAkBV;AAGD,wBAAgB,QAAQ,CACtB,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,EAAE,GACnB,QAAQ,CAOV;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,GAAG,QAAQ,CAS1E;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,EAAE,EACpB,SAAS,EAAE,aAAa,GACvB,QAAQ,CA8BV"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createEmptyFoldNode } from 'dirvi-lib';
|
|
2
|
+
export function hasFold(node, entryName) {
|
|
3
|
+
return node.folds.includes(entryName);
|
|
4
|
+
}
|
|
5
|
+
export function entryIsFolded(node, entry) {
|
|
6
|
+
return node !== undefined && hasFold(node, entry.name);
|
|
7
|
+
}
|
|
8
|
+
export function updateFoldNodeAtPath(node, path, update) {
|
|
9
|
+
const [currentName, ...remainingPath] = path;
|
|
10
|
+
// Base case
|
|
11
|
+
if (currentName === undefined) {
|
|
12
|
+
return update(node);
|
|
13
|
+
}
|
|
14
|
+
// Create childNode to create path in tree to the needed node
|
|
15
|
+
const childNode = node.children[currentName] ?? createEmptyFoldNode();
|
|
16
|
+
return {
|
|
17
|
+
...node,
|
|
18
|
+
children: {
|
|
19
|
+
...node.children,
|
|
20
|
+
[currentName]: updateFoldNodeAtPath(childNode, remainingPath, update),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Replaces all folds at a node
|
|
25
|
+
export function setFolds(node, entries) {
|
|
26
|
+
const entryNames = entries.map((entry) => entry.name);
|
|
27
|
+
return {
|
|
28
|
+
...node,
|
|
29
|
+
folds: entryNames,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function addFold(node, entryName) {
|
|
33
|
+
if (hasFold(node, entryName)) {
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...node,
|
|
38
|
+
folds: [...node.folds, entryName],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function unfoldFoldSequence(node, entries, entryName) {
|
|
42
|
+
const entryIndex = entries.findIndex((entry) => entry.name === entryName);
|
|
43
|
+
if (entryIndex === -1 || !hasFold(node, entryName)) {
|
|
44
|
+
return node;
|
|
45
|
+
}
|
|
46
|
+
let firstIndex = entryIndex;
|
|
47
|
+
while (firstIndex > 0 && hasFold(node, entries[firstIndex - 1].name)) {
|
|
48
|
+
firstIndex -= 1;
|
|
49
|
+
}
|
|
50
|
+
let lastIndex = entryIndex;
|
|
51
|
+
while (lastIndex < entries.length - 1 &&
|
|
52
|
+
hasFold(node, entries[lastIndex + 1].name)) {
|
|
53
|
+
lastIndex += 1;
|
|
54
|
+
}
|
|
55
|
+
const unfoldedNames = new Set(entries.slice(firstIndex, lastIndex + 1).map((entry) => entry.name));
|
|
56
|
+
return {
|
|
57
|
+
...node,
|
|
58
|
+
folds: node.folds.filter((foldedName) => !unfoldedNames.has(foldedName)),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { UnixEntry, UnixEntryName, EntryPath, View } from 'dirvi-lib';
|
|
2
|
+
export type Action = {
|
|
3
|
+
kind: 'nextEntry';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'prevEntry';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'toggleDir';
|
|
8
|
+
path: EntryPath;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'printFile';
|
|
11
|
+
path: EntryPath;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'outDir';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'toggleFold';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'fold';
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'unfold';
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'directoryLoaded';
|
|
22
|
+
path: UnixEntryName[];
|
|
23
|
+
entries: UnixEntry[];
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'exit';
|
|
26
|
+
exitMessage: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function reducer(view: View, action: Action): View;
|
|
29
|
+
//# sourceMappingURL=reducer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../../src/application/reducer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,EAEb,SAAS,EACT,IAAI,EAGL,MAAM,WAAW,CAAC;AAKnB,MAAM,MAAM,MAAM,GACd;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;CACnB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;CACjB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;CACjB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;CAChB,GACD;IACE,IAAI,EAAE,YAAY,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;CAChB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA8EN,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAmNxD"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { rowsEqual, entryNamesEqual, } from 'dirvi-lib';
|
|
2
|
+
import { updateFoldNodeAtPath, unfoldFoldSequence, addFold } from './fold-helpers.js';
|
|
3
|
+
import { createDisplayRows, displayRowAtPath } from './display-rows.js';
|
|
4
|
+
import { entryAtPath } from './dir-buffer-helpers.js';
|
|
5
|
+
function updateEntries(entries, currentPath, newEntries) {
|
|
6
|
+
const [currentName, ...remainingPath] = currentPath;
|
|
7
|
+
if (currentName === undefined) {
|
|
8
|
+
throw new Error('Cannot update directory entries with an empty path');
|
|
9
|
+
}
|
|
10
|
+
let found = false;
|
|
11
|
+
const updatedEntries = entries.map((entry) => {
|
|
12
|
+
if (entry.name !== currentName) {
|
|
13
|
+
return entry;
|
|
14
|
+
}
|
|
15
|
+
found = true;
|
|
16
|
+
if (remainingPath.length === 0) {
|
|
17
|
+
if (entry.kind !== 'directory') {
|
|
18
|
+
throw new Error(`Cannot load entries for non-directory entry "${entry.name}" of kind "${entry.kind}"`);
|
|
19
|
+
}
|
|
20
|
+
if (newEntries === undefined) {
|
|
21
|
+
return {
|
|
22
|
+
kind: 'directory',
|
|
23
|
+
name: entry.name,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...entry,
|
|
28
|
+
entries: newEntries,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (entry.kind !== 'directory') {
|
|
32
|
+
throw new Error(`Cannot descend through non-directory entry "${entry.name}"`);
|
|
33
|
+
}
|
|
34
|
+
if (entry.entries === undefined) {
|
|
35
|
+
throw new Error(`Cannot descend into unexpanded directory "${entry.name}"`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...entry,
|
|
39
|
+
entries: updateEntries(entry.entries, remainingPath, newEntries),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
if (!found) {
|
|
43
|
+
throw new Error(`Directory path does not contain entry "${currentName}"`);
|
|
44
|
+
}
|
|
45
|
+
return updatedEntries;
|
|
46
|
+
}
|
|
47
|
+
function updateEntriesAtPath(buffer, path, newEntries) {
|
|
48
|
+
return {
|
|
49
|
+
...buffer,
|
|
50
|
+
entries: updateEntries(buffer.entries, path, newEntries),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function reducer(view, action) {
|
|
54
|
+
switch (action.kind) {
|
|
55
|
+
case 'nextEntry':
|
|
56
|
+
case 'prevEntry': {
|
|
57
|
+
const displayRows = createDisplayRows(view.buffer, view.folds);
|
|
58
|
+
const currentIndex = displayRows.findIndex((row) => rowsEqual(view.cursor, row));
|
|
59
|
+
if (currentIndex === -1) {
|
|
60
|
+
return view;
|
|
61
|
+
}
|
|
62
|
+
const direction = action.kind === 'nextEntry' ? 1 : -1;
|
|
63
|
+
const targetIndex = currentIndex + direction;
|
|
64
|
+
if (targetIndex < 0 || targetIndex >= displayRows.length) {
|
|
65
|
+
return view;
|
|
66
|
+
}
|
|
67
|
+
const newCursor = displayRows[targetIndex];
|
|
68
|
+
return {
|
|
69
|
+
...view,
|
|
70
|
+
cursor: newCursor,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
case 'toggleDir': {
|
|
74
|
+
const buffer = updateEntriesAtPath(view.buffer, action.path, undefined);
|
|
75
|
+
return {
|
|
76
|
+
...view,
|
|
77
|
+
buffer,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case 'printFile':
|
|
81
|
+
// Handled by App
|
|
82
|
+
return view;
|
|
83
|
+
case 'directoryLoaded': {
|
|
84
|
+
// Add new entries to path
|
|
85
|
+
const buffer = updateEntriesAtPath(view.buffer, action.path, action.entries);
|
|
86
|
+
return {
|
|
87
|
+
...view,
|
|
88
|
+
buffer,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// 1. Identify the directory represented by parentPath
|
|
92
|
+
// 2. Check whether the cursor fold contains all its entries
|
|
93
|
+
// 3. Unload the directory entries
|
|
94
|
+
// 4. Recalculate the parent display row
|
|
95
|
+
// 5. Move the cursor to that parent row
|
|
96
|
+
case 'outDir': {
|
|
97
|
+
const cursor = view.cursor;
|
|
98
|
+
const parentPath = cursor.parentPath;
|
|
99
|
+
if (parentPath.length === 0) {
|
|
100
|
+
return view;
|
|
101
|
+
}
|
|
102
|
+
const parentEntry = entryAtPath(view.buffer, parentPath);
|
|
103
|
+
const shouldUnloadDirectory = cursor.kind === 'fold' &&
|
|
104
|
+
parentEntry?.kind === 'directory' &&
|
|
105
|
+
parentEntry.entries !== undefined &&
|
|
106
|
+
entryNamesEqual(cursor.entryNames, parentEntry.entries.map((entry) => entry.name));
|
|
107
|
+
const buffer = shouldUnloadDirectory
|
|
108
|
+
? updateEntriesAtPath(view.buffer, parentPath, undefined)
|
|
109
|
+
: view.buffer;
|
|
110
|
+
const updatedView = {
|
|
111
|
+
...view,
|
|
112
|
+
buffer,
|
|
113
|
+
};
|
|
114
|
+
const parentDisplayRow = displayRowAtPath(updatedView.buffer, updatedView.folds, parentPath);
|
|
115
|
+
if (parentDisplayRow === undefined || parentDisplayRow.kind !== 'entry') {
|
|
116
|
+
return view;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...updatedView,
|
|
120
|
+
cursor: parentDisplayRow,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
case 'toggleFold':
|
|
124
|
+
return reducer(view, view.cursor.kind === 'fold' ? { kind: 'unfold' } : { kind: 'fold' });
|
|
125
|
+
case 'fold': {
|
|
126
|
+
const cursor = view.cursor;
|
|
127
|
+
if (cursor.kind !== 'entry') {
|
|
128
|
+
return view;
|
|
129
|
+
}
|
|
130
|
+
const currentName = cursor.entry.name;
|
|
131
|
+
const parentPath = cursor.parentPath;
|
|
132
|
+
const entry = entryAtPath(view.buffer, [...parentPath, currentName]);
|
|
133
|
+
if (entry === undefined) {
|
|
134
|
+
return view;
|
|
135
|
+
}
|
|
136
|
+
const folds = updateFoldNodeAtPath(view.folds, parentPath, (node) => addFold(node, currentName));
|
|
137
|
+
const updatedView = {
|
|
138
|
+
...view,
|
|
139
|
+
folds,
|
|
140
|
+
};
|
|
141
|
+
const foldedRow = displayRowAtPath(updatedView.buffer, updatedView.folds, [...parentPath, currentName]);
|
|
142
|
+
return foldedRow === undefined
|
|
143
|
+
? updatedView
|
|
144
|
+
: {
|
|
145
|
+
...updatedView,
|
|
146
|
+
cursor: foldedRow,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case 'unfold': {
|
|
150
|
+
const cursor = view.cursor;
|
|
151
|
+
if (cursor.kind !== 'fold') {
|
|
152
|
+
return view;
|
|
153
|
+
}
|
|
154
|
+
const firstEntryName = cursor.entryNames[0];
|
|
155
|
+
if (firstEntryName === undefined) {
|
|
156
|
+
return view;
|
|
157
|
+
}
|
|
158
|
+
const { parentPath } = cursor;
|
|
159
|
+
const parentEntry = parentPath.length === 0
|
|
160
|
+
? undefined
|
|
161
|
+
: entryAtPath(view.buffer, parentPath);
|
|
162
|
+
const entries = parentPath.length === 0
|
|
163
|
+
? view.buffer.entries
|
|
164
|
+
: parentEntry?.kind === 'directory'
|
|
165
|
+
? parentEntry.entries
|
|
166
|
+
: undefined;
|
|
167
|
+
if (entries === undefined) {
|
|
168
|
+
return view;
|
|
169
|
+
}
|
|
170
|
+
const folds = updateFoldNodeAtPath(view.folds, parentPath, (node) => unfoldFoldSequence(node, entries, firstEntryName));
|
|
171
|
+
const updatedView = {
|
|
172
|
+
...view,
|
|
173
|
+
folds,
|
|
174
|
+
};
|
|
175
|
+
const unfoldedRow = displayRowAtPath(updatedView.buffer, updatedView.folds, [...parentPath, firstEntryName]);
|
|
176
|
+
return unfoldedRow === undefined
|
|
177
|
+
? updatedView
|
|
178
|
+
: {
|
|
179
|
+
...updatedView,
|
|
180
|
+
cursor: unfoldedRow,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
case 'exit':
|
|
184
|
+
// Exit is handled by App.
|
|
185
|
+
return view;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-message.d.ts","sourceRoot":"","sources":["../../src/domain/print-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;CACZ,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { render } from 'ink';
|
|
5
|
+
import { AppShell } from './ui/AppShell.js';
|
|
6
|
+
import { loadInitialProps } from './infrastructure/load-initial-props.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
// ---*--- TERMINAL ALT SCREEN ---*---
|
|
9
|
+
// Restore the terminal during normal cleanup and as a
|
|
10
|
+
// last-resort fallback when Node is exiting.
|
|
11
|
+
// Write UI to stderr
|
|
12
|
+
const uiOutput = process.stderr;
|
|
13
|
+
// Write Views to stdout. The newline makes each update a
|
|
14
|
+
// separate JSON Lines message.
|
|
15
|
+
// Note: if you pipe output you'll need to use FORCE_COLOR=3
|
|
16
|
+
// to keep interactive screen colored.
|
|
17
|
+
const print = (message) => {
|
|
18
|
+
if (process.stdout.isTTY) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
22
|
+
};
|
|
23
|
+
const enterAlternateScreen = '\u001b[?1049h\u001b[2J\u001b[H\u001b[?25l';
|
|
24
|
+
const leaveAlternateScreen = '\u001b[?25h\u001b[?1049l';
|
|
25
|
+
// Idempotent terminal restoration
|
|
26
|
+
let alternateScreenActive = false;
|
|
27
|
+
const restoreTerminal = () => {
|
|
28
|
+
if (!alternateScreenActive) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
uiOutput.write(leaveAlternateScreen);
|
|
32
|
+
alternateScreenActive = false;
|
|
33
|
+
};
|
|
34
|
+
// Node exit last resort
|
|
35
|
+
process.on('exit', restoreTerminal);
|
|
36
|
+
// ---*--- COMPOSITION ---*---
|
|
37
|
+
program
|
|
38
|
+
.name('direx')
|
|
39
|
+
.description('View and manage directories.')
|
|
40
|
+
.version('0.1.0')
|
|
41
|
+
.helpOption('--help')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
const initialProps = await loadInitialProps();
|
|
44
|
+
let appError;
|
|
45
|
+
const useAlternateScreen = uiOutput.isTTY === true;
|
|
46
|
+
if (useAlternateScreen) {
|
|
47
|
+
uiOutput.write(enterAlternateScreen);
|
|
48
|
+
alternateScreenActive = true;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const app = render(_jsx(AppShell, { ...initialProps, print: print, onError: (error) => {
|
|
52
|
+
appError = error;
|
|
53
|
+
} }), { stdout: uiOutput });
|
|
54
|
+
// Errors thrown while leaving this await still execute the finally block.
|
|
55
|
+
await app.waitUntilExit();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
restoreTerminal(); // Always restore terminal regardless of error
|
|
59
|
+
}
|
|
60
|
+
// Re-throw an error reported by AppShell after the terminal
|
|
61
|
+
// has been restored, so the outer catch can format it.
|
|
62
|
+
if (appError !== undefined) {
|
|
63
|
+
throw appError;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// ---*--- RUN AND HANDLE ERROR ---*---
|
|
67
|
+
try {
|
|
68
|
+
await program.parseAsync(process.argv);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
process.stderr.write(`direx: ${message}\n`);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Key } from 'ink';
|
|
2
|
+
import type { View } from 'dirvi-lib';
|
|
3
|
+
import type { Action } from '../application/reducer.js';
|
|
4
|
+
export type PendingInput = 'z';
|
|
5
|
+
export type InputResult = Action | PendingInput | undefined;
|
|
6
|
+
export declare function inputToInputResult(input: string, key: Key, view: View, pendingInput?: PendingInput): InputResult;
|
|
7
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/infrastructure/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC;AAC/B,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5D,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,EACV,YAAY,CAAC,EAAE,YAAY,GAC1B,WAAW,CAsFb"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function inputToInputResult(input, key, view, pendingInput) {
|
|
2
|
+
// Adding Pending Input
|
|
3
|
+
if (pendingInput === undefined && input === 'z') {
|
|
4
|
+
return 'z';
|
|
5
|
+
}
|
|
6
|
+
// z prefixed inputs
|
|
7
|
+
if (pendingInput === 'z') {
|
|
8
|
+
if (input === 'a') {
|
|
9
|
+
return {
|
|
10
|
+
kind: 'toggleFold',
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (input === 'c') {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'fold',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (input === 'o') {
|
|
19
|
+
return {
|
|
20
|
+
kind: 'unfold',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Unknown z command.
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
// Not prefixed inputs
|
|
27
|
+
if (input === 'l' || key.rightArrow) {
|
|
28
|
+
// To do: `L` does recursive opening directories
|
|
29
|
+
if (view.cursor.kind !== 'entry') {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const path = [...view.cursor.parentPath, view.cursor.entry.name];
|
|
33
|
+
if (view.cursor.entry.kind === 'directory') {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'toggleDir',
|
|
36
|
+
path,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (view.cursor.entry.kind === 'file') {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'printFile',
|
|
42
|
+
path,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (input === 'h' || key.leftArrow) {
|
|
48
|
+
if (view.cursor.parentPath.length === 0) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: 'outDir',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (input === 'k' || key.upArrow) {
|
|
56
|
+
return {
|
|
57
|
+
kind: 'prevEntry',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (input === 'j' || key.downArrow) {
|
|
61
|
+
return {
|
|
62
|
+
kind: 'nextEntry',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (input === 'q' || key.escape) {
|
|
66
|
+
return {
|
|
67
|
+
kind: 'exit',
|
|
68
|
+
exitMessage: '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-initial-props.d.ts","sourceRoot":"","sources":["../../src/infrastructure/load-initial-props.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAmC/D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getCwdAbsPath, getDirLazyEntries, unixEntryToDisplayEntry, createEmptyFoldNode, } from 'dirvi-lib';
|
|
2
|
+
export async function loadInitialProps() {
|
|
3
|
+
const cwdAddress = getCwdAbsPath();
|
|
4
|
+
const lazyEntries = await getDirLazyEntries(cwdAddress);
|
|
5
|
+
if (lazyEntries.length === 0) {
|
|
6
|
+
return {
|
|
7
|
+
cwdAddress,
|
|
8
|
+
initialView: {
|
|
9
|
+
cursor: undefined,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const firstEntry = lazyEntries[0];
|
|
14
|
+
if (firstEntry === undefined) {
|
|
15
|
+
throw new Error('Expected a first entry after checking that the directory is non-empty');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
cwdAddress,
|
|
19
|
+
initialView: {
|
|
20
|
+
buffer: {
|
|
21
|
+
entries: lazyEntries,
|
|
22
|
+
},
|
|
23
|
+
cursor: {
|
|
24
|
+
kind: 'entry',
|
|
25
|
+
parentPath: [],
|
|
26
|
+
entry: unixEntryToDisplayEntry(firstEntry),
|
|
27
|
+
},
|
|
28
|
+
folds: createEmptyFoldNode(),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dist/ui/App.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UnixAbsolutePath, View } from 'dirvi-lib';
|
|
2
|
+
import { PrintMessage } from '../domain/print-message.js';
|
|
3
|
+
export type AppProps = {
|
|
4
|
+
cwdAddress: UnixAbsolutePath;
|
|
5
|
+
initialView: View;
|
|
6
|
+
print?: (message: PrintMessage) => void;
|
|
7
|
+
onError?: (error: Error) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function App({ cwdAddress, initialView, print, onError }: AppProps): import("react").JSX.Element | null;
|
|
10
|
+
//# sourceMappingURL=App.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/ui/App.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMxD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,IAAI,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,GAAG,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,QAAQ,sCAgHxE"}
|