@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
package/dist/ui/App.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { useEffect, useReducer, useRef, useState } from 'react';
|
|
5
|
+
import { getDirLazyEntries } from 'dirvi-lib';
|
|
6
|
+
import { reducer } from '../application/reducer.js';
|
|
7
|
+
import { inputToInputResult } from '../infrastructure/input.js';
|
|
8
|
+
import DirEntries from './components/DirEntries.js';
|
|
9
|
+
import { createDisplayRows } from '../application/display-rows.js';
|
|
10
|
+
import { entryAtPath } from '../application/dir-buffer-helpers.js';
|
|
11
|
+
export function App({ cwdAddress, initialView, print, onError }) {
|
|
12
|
+
const [view, dispatch] = useReducer(reducer, initialView);
|
|
13
|
+
const { buffer, folds, cursor } = view;
|
|
14
|
+
const [exitStatus, setExitStatus] = useState();
|
|
15
|
+
const pendingInput = useRef(undefined);
|
|
16
|
+
// Print view on changes
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
print?.({ type: 'view', view: view });
|
|
19
|
+
}, [view, print]);
|
|
20
|
+
// --- Input ---
|
|
21
|
+
useInput((input, key) => {
|
|
22
|
+
const result = inputToInputResult(input, key, view, pendingInput.current);
|
|
23
|
+
if (result === undefined) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (result === 'z') {
|
|
27
|
+
pendingInput.current = result;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
pendingInput.current = undefined;
|
|
31
|
+
const action = result;
|
|
32
|
+
// May need to load entries:
|
|
33
|
+
if (action.kind === 'toggleDir') {
|
|
34
|
+
const currentEntry = entryAtPath(view.buffer, action.path);
|
|
35
|
+
// If the entry at the path is directory with no entries loaded:
|
|
36
|
+
if (currentEntry?.kind === 'directory' &&
|
|
37
|
+
currentEntry.entries === undefined) {
|
|
38
|
+
const address = join(cwdAddress, ...action.path);
|
|
39
|
+
void getDirLazyEntries(address)
|
|
40
|
+
.then((entries) => {
|
|
41
|
+
dispatch({
|
|
42
|
+
kind: 'directoryLoaded',
|
|
43
|
+
path: action.path,
|
|
44
|
+
entries,
|
|
45
|
+
});
|
|
46
|
+
})
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
const appError = error instanceof Error ? error : new Error(String(error));
|
|
49
|
+
onError?.(appError);
|
|
50
|
+
setExitStatus(`Unable to open directory: ${appError.message}`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else if (currentEntry?.kind === 'directory') {
|
|
54
|
+
dispatch({
|
|
55
|
+
kind: 'toggleDir',
|
|
56
|
+
path: action.path,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (action.kind === 'printFile') {
|
|
62
|
+
print?.({
|
|
63
|
+
type: 'file',
|
|
64
|
+
path: action.path,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (action.kind === 'exit') {
|
|
69
|
+
setExitStatus(action.exitMessage);
|
|
70
|
+
}
|
|
71
|
+
dispatch(action);
|
|
72
|
+
});
|
|
73
|
+
// --- Exit Logic ---
|
|
74
|
+
const { exit } = useApp();
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (exitStatus === undefined) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (exitStatus !== '') {
|
|
80
|
+
onError?.(new Error(exitStatus));
|
|
81
|
+
}
|
|
82
|
+
exit();
|
|
83
|
+
}, [exitStatus, exit, onError]);
|
|
84
|
+
if (exitStatus !== undefined) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
// --- JSX ---
|
|
88
|
+
const displayRows = createDisplayRows(buffer, folds);
|
|
89
|
+
return (_jsx(Box, { flexDirection: "column", children: (buffer.entries.length === 0 || cursor === undefined) ? (_jsx(Text, { dimColor: true, children: "Directory is empty." })) : (_jsx(DirEntries, { rows: displayRows, cursor: cursor })) }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UnixAbsolutePath, View } from 'dirvi-lib';
|
|
2
|
+
import { PrintMessage } from '../domain/print-message.js';
|
|
3
|
+
export type EmptyView = {
|
|
4
|
+
cursor: undefined;
|
|
5
|
+
};
|
|
6
|
+
export type ShellAppProps = {
|
|
7
|
+
cwdAddress: UnixAbsolutePath;
|
|
8
|
+
initialView: View | EmptyView;
|
|
9
|
+
print?: (message: PrintMessage) => void;
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function AppShell({ cwdAddress, initialView, print, onError }: ShellAppProps): import("react").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=AppShell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../src/ui/AppShell.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,WAAW,EAAE,IAAI,GAAG,SAAS,CAAC;IAC9B,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,QAAQ,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,aAAa,+BAalF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { App } from './App.js';
|
|
4
|
+
export function AppShell({ cwdAddress, initialView, print, onError }) {
|
|
5
|
+
if (initialView.cursor === undefined) {
|
|
6
|
+
return _jsx(Text, { dimColor: true, children: "Directory is empty." });
|
|
7
|
+
}
|
|
8
|
+
return (_jsx(App, { cwdAddress: cwdAddress, initialView: initialView, print: print, onError: onError }));
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DirEntries.d.ts","sourceRoot":"","sources":["../../../src/ui/components/DirEntries.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQpD,iBAAS,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,+BA8D3E;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useWindowSize } from 'ink';
|
|
3
|
+
import { rowsEqual } from 'dirvi-lib';
|
|
4
|
+
import { UnixEntry as UnixEntryComponent } from './UnixEntry.js';
|
|
5
|
+
import { useRef } from 'react';
|
|
6
|
+
// Display column of entries in directory, indented, and optional
|
|
7
|
+
// cursor in position.
|
|
8
|
+
function DirEntries({ rows, cursor }) {
|
|
9
|
+
const scrollMargin = 3;
|
|
10
|
+
const { rows: terminalRows } = useWindowSize();
|
|
11
|
+
const viewportStartRef = useRef(0);
|
|
12
|
+
const cursorIndex = rows.findIndex((row) => rowsEqual(cursor, row));
|
|
13
|
+
const viewportHeight = Math.max(1, terminalRows);
|
|
14
|
+
const maximumViewportStart = Math.max(0, rows.length - viewportHeight);
|
|
15
|
+
let viewportStart = viewportStartRef.current;
|
|
16
|
+
if (cursorIndex >= 0) {
|
|
17
|
+
const firstVisibleCursorIndex = viewportStart + scrollMargin;
|
|
18
|
+
const lastVisibleCursorIndex = viewportStart + viewportHeight - 1 - scrollMargin;
|
|
19
|
+
if (cursorIndex < firstVisibleCursorIndex) {
|
|
20
|
+
// Cursor moved above the visible area.
|
|
21
|
+
viewportStart = cursorIndex - scrollMargin;
|
|
22
|
+
}
|
|
23
|
+
else if (cursorIndex > lastVisibleCursorIndex) {
|
|
24
|
+
// Cursor moved below the visible area.
|
|
25
|
+
viewportStart = cursorIndex - viewportHeight + 1 + scrollMargin;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
viewportStart = Math.max(0, Math.min(viewportStart, maximumViewportStart));
|
|
29
|
+
viewportStartRef.current = viewportStart;
|
|
30
|
+
const visibleRows = rows.slice(viewportStart, viewportStart + viewportHeight);
|
|
31
|
+
return (_jsx(_Fragment, { children: visibleRows.map((row) => {
|
|
32
|
+
const selected = rowsEqual(cursor, row);
|
|
33
|
+
const indent = row.parentPath.length;
|
|
34
|
+
if (row.kind === 'fold') {
|
|
35
|
+
return (_jsx(Box, { paddingLeft: indent * 2, children: _jsx(Text, { inverse: selected, dimColor: !selected, children: "..." }) }, `fold-${row.parentPath.join('/')}-${row.entryNames[0]}`));
|
|
36
|
+
}
|
|
37
|
+
return (_jsx(Box, { paddingLeft: indent * 2, children: _jsx(UnixEntryComponent, { entry: row.entry, selected: selected }) }, [...row.parentPath, row.entry.name].join('/')));
|
|
38
|
+
}) }));
|
|
39
|
+
}
|
|
40
|
+
export default DirEntries;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnixEntry.d.ts","sourceRoot":"","sources":["../../../src/ui/components/UnixEntry.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACT,EAAE;IACD,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB,+BAuBA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
export function UnixEntry({ entry, selected }) {
|
|
4
|
+
switch (entry.kind) {
|
|
5
|
+
case 'file':
|
|
6
|
+
return (_jsx(Text, { inverse: selected, children: entry.name }));
|
|
7
|
+
case 'symlink':
|
|
8
|
+
return (_jsxs(Text, { inverse: selected, children: [entry.name, " -> ", entry.target] }));
|
|
9
|
+
case 'directory':
|
|
10
|
+
return (_jsxs(Text, { inverse: selected, color: "blue", children: [entry.name, "/"] }));
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mak-98/dirvi-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Vim-inspired TUI for browsing directories.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "GPL-3.0-or-later",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dirvi": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^13.0.0",
|
|
16
|
+
"dirvi-lib": "0.1.0",
|
|
17
|
+
"ink": "^7.1.1",
|
|
18
|
+
"react": "^19.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.0.0",
|
|
22
|
+
"@types/react": "^19.0.0",
|
|
23
|
+
"tsx": "^4.0.0",
|
|
24
|
+
"typescript": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "FORCE_COLOR=3 tsx src/index.tsx",
|
|
29
|
+
"start": "node dist/index.js",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
}
|
|
32
|
+
}
|