@mak-98/dirvi-cli 0.4.0 → 0.6.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.
Files changed (92) hide show
  1. package/dist/application/effect-to-action.d.ts +5 -7
  2. package/dist/application/effect-to-action.d.ts.map +1 -1
  3. package/dist/application/effect-to-action.js +83 -86
  4. package/dist/application/reducer-action.d.ts +23 -23
  5. package/dist/application/reducer-action.d.ts.map +1 -1
  6. package/dist/application/reducer.d.ts +4 -3
  7. package/dist/application/reducer.d.ts.map +1 -1
  8. package/dist/application/reducer.js +67 -42
  9. package/dist/domain/app-api.d.ts +47 -0
  10. package/dist/domain/app-api.d.ts.map +1 -0
  11. package/dist/domain/app-api.js +21 -0
  12. package/dist/domain/event-message.d.ts +12 -15
  13. package/dist/domain/event-message.d.ts.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +56 -45
  16. package/dist/infrastructure/get-dir-entries.d.ts +3 -0
  17. package/dist/infrastructure/get-dir-entries.d.ts.map +1 -0
  18. package/dist/infrastructure/get-dir-entries.js +33 -0
  19. package/dist/infrastructure/get-dir-lazy-entries.d.ts +3 -0
  20. package/dist/infrastructure/get-dir-lazy-entries.d.ts.map +1 -0
  21. package/dist/infrastructure/get-dir-lazy-entries.js +33 -0
  22. package/dist/infrastructure/get-unix-abs-path.d.ts +3 -0
  23. package/dist/infrastructure/get-unix-abs-path.d.ts.map +1 -0
  24. package/dist/infrastructure/get-unix-abs-path.js +14 -0
  25. package/dist/infrastructure/ink-input-to-user-input.d.ts +2 -5
  26. package/dist/infrastructure/ink-input-to-user-input.js +42 -42
  27. package/dist/infrastructure/load-posix-app-api.d.ts +4 -0
  28. package/dist/infrastructure/load-posix-app-api.d.ts.map +1 -0
  29. package/dist/infrastructure/load-posix-app-api.js +82 -0
  30. package/dist/ui/App.d.ts +16 -15
  31. package/dist/ui/App.d.ts.map +1 -1
  32. package/dist/ui/App.js +124 -129
  33. package/dist/ui/AppShell.d.ts +6 -16
  34. package/dist/ui/AppShell.d.ts.map +1 -1
  35. package/dist/ui/AppShell.js +10 -13
  36. package/dist/ui/LoadingApp.d.ts +11 -0
  37. package/dist/ui/LoadingApp.d.ts.map +1 -0
  38. package/dist/ui/LoadingApp.js +67 -0
  39. package/dist/ui/components/StatusBar.d.ts +3 -5
  40. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  41. package/dist/ui/components/StatusBar.js +3 -28
  42. package/dist/ui/components/ViewRowComponent.d.ts +4 -6
  43. package/dist/ui/components/ViewRowComponent.js +10 -30
  44. package/dist/ui/hooks/useView.d.ts +4 -7
  45. package/dist/ui/hooks/useView.d.ts.map +1 -1
  46. package/dist/ui/hooks/useView.js +20 -29
  47. package/dist/ui/view.d.ts +13 -17
  48. package/dist/ui/view.d.ts.map +1 -1
  49. package/dist/ui/view.js +84 -64
  50. package/package.json +2 -2
  51. package/dist/application/action.d.ts +0 -26
  52. package/dist/application/action.d.ts.map +0 -1
  53. package/dist/application/action.js +0 -1
  54. package/dist/application/dir-buffer-helpers.d.ts +0 -6
  55. package/dist/application/dir-buffer-helpers.d.ts.map +0 -1
  56. package/dist/application/dir-buffer-helpers.js +0 -21
  57. package/dist/application/display-rows.d.ts +0 -11
  58. package/dist/application/display-rows.d.ts.map +0 -1
  59. package/dist/application/display-rows.js +0 -63
  60. package/dist/application/fold-helpers.d.ts +0 -24
  61. package/dist/application/fold-helpers.d.ts.map +0 -1
  62. package/dist/application/fold-helpers.js +0 -47
  63. package/dist/application/intent-to-effect.d.ts +0 -59
  64. package/dist/application/intent-to-effect.d.ts.map +0 -1
  65. package/dist/application/intent-to-effect.js +0 -131
  66. package/dist/application/user-input-to-intent.d.ts +0 -39
  67. package/dist/application/user-input-to-intent.d.ts.map +0 -1
  68. package/dist/application/user-input-to-intent.js +0 -110
  69. package/dist/domain/print-message.d.ts +0 -11
  70. package/dist/domain/print-message.d.ts.map +0 -1
  71. package/dist/domain/print-message.js +0 -1
  72. package/dist/infrastructure/input.d.ts +0 -12
  73. package/dist/infrastructure/input.d.ts.map +0 -1
  74. package/dist/infrastructure/input.js +0 -81
  75. package/dist/infrastructure/load-initial-props.d.ts +0 -3
  76. package/dist/infrastructure/load-initial-props.d.ts.map +0 -1
  77. package/dist/infrastructure/load-initial-props.js +0 -36
  78. package/dist/infrastructure/user-input.d.ts +0 -32
  79. package/dist/infrastructure/user-input.d.ts.map +0 -1
  80. package/dist/infrastructure/user-input.js +0 -45
  81. package/dist/ui/App2.d.ts +0 -16
  82. package/dist/ui/App2.d.ts.map +0 -1
  83. package/dist/ui/App2.js +0 -107
  84. package/dist/ui/components/DirEntries.d.ts +0 -11
  85. package/dist/ui/components/DirEntries.d.ts.map +0 -1
  86. package/dist/ui/components/DirEntries.js +0 -64
  87. package/dist/ui/components/UnixEntry.d.ts +0 -9
  88. package/dist/ui/components/UnixEntry.d.ts.map +0 -1
  89. package/dist/ui/components/UnixEntry.js +0 -19
  90. package/dist/ui/components/UnixEntryComponent.d.ts +0 -9
  91. package/dist/ui/components/UnixEntryComponent.d.ts.map +0 -1
  92. package/dist/ui/components/UnixEntryComponent.js +0 -19
@@ -1,14 +1,11 @@
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, initialState, print, onError }) {
5
- if (initialState.cursor === undefined) {
6
- return _jsx(Text, { dimColor: true, children: 'Directory is empty.' });
7
- }
8
- return _jsx(App, {
9
- cwdAddress: cwdAddress,
10
- initialState: initialState,
11
- print: print,
12
- onError: onError,
13
- });
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { loadPosixAppApi } from '../infrastructure/load-posix-app-api.js';
3
+ import { LoadingApp } from './LoadingApp.js';
4
+ import { useMemo } from 'react';
5
+ // App Shell is for choosing the active app, then loading the api,
6
+ // and showing the app.
7
+ export function AppShell({ directory, stdout, clipboard, onError }) {
8
+ // Api owns directory watcher and subscriptions
9
+ const posixAppApi = useMemo(() => loadPosixAppApi(directory), [directory]);
10
+ return (_jsx(LoadingApp, { appApi: posixAppApi, ...(stdout === undefined ? {} : { stdout }), ...(clipboard === undefined ? {} : { clipboard }), ...(onError === undefined ? {} : { onError }) }));
14
11
  }
@@ -0,0 +1,11 @@
1
+ import { SerializableKey, TreeNode } from 'dirvi-lib';
2
+ import { AppApi } from '../domain/app-api.js';
3
+ type LoadingAppProps<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = {
4
+ appApi: AppApi<Id, BufferNode>;
5
+ stdout?: (message: string) => void;
6
+ clipboard?: (value: string) => void;
7
+ onError?: (error: Error) => void;
8
+ };
9
+ export declare function LoadingApp<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>({ appApi, stdout, clipboard, onError }: LoadingAppProps<Id, BufferNode>): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=LoadingApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadingApp.d.ts","sourceRoot":"","sources":["../../src/ui/LoadingApp.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,eAAe,EAEf,QAAQ,EACT,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAI9C,KAAK,eAAe,CAClB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,IACzC;IACF,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AA0BF,wBAAgB,UAAU,CACxB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAC3C,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,eAAe,CAAC,EAAE,EAAE,UAAU,CAAC,+BAgGxE"}
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import { createIntentToEffect, } from 'dirvi-lib';
5
+ import { App } from './App.js';
6
+ import { createReducer } from '../application/reducer.js';
7
+ import { createEffectToAction } from '../application/effect-to-action.js';
8
+ function createInitialState(rootBranches, createEmptyFoldRoot) {
9
+ if (rootBranches.length === 0) {
10
+ throw new Error('createInitialState cannot create a state for an empty forest');
11
+ }
12
+ return {
13
+ buffer: rootBranches,
14
+ foldNode: createEmptyFoldRoot(),
15
+ cursor: {
16
+ kind: 'entry',
17
+ parentPath: [],
18
+ entryId: rootBranches[0].id,
19
+ },
20
+ };
21
+ }
22
+ export function LoadingApp({ appApi, stdout, clipboard, onError }) {
23
+ const [initialState, setInitialState] = useState();
24
+ const [empty, setEmpty] = useState(false);
25
+ const [error, setError] = useState();
26
+ useEffect(() => {
27
+ let mounted = true;
28
+ appApi
29
+ .loadBranches([])
30
+ .then((rootBranches) => {
31
+ if (!mounted) {
32
+ return;
33
+ }
34
+ if (rootBranches.length === 0) {
35
+ setEmpty(true);
36
+ return;
37
+ }
38
+ setInitialState(createInitialState(rootBranches, () => appApi.foldNodeService.createEmptyRoot()));
39
+ })
40
+ .catch((cause) => {
41
+ const nextError = cause instanceof Error ? cause : new Error(String(cause));
42
+ if (!mounted) {
43
+ return;
44
+ }
45
+ setError(nextError);
46
+ onError?.(nextError);
47
+ });
48
+ return () => {
49
+ mounted = false;
50
+ };
51
+ }, [appApi, onError]);
52
+ // --- Pure function deps ---
53
+ const reducer = useMemo(() => createReducer(appApi.treeNodeApi, appApi.foldNodeService), [appApi.treeNodeApi, appApi.foldNodeService]);
54
+ const intentToEffect = useMemo(() => createIntentToEffect(appApi.stateApi, appApi.cursorApi, appApi.treeNodeApi), [appApi.stateApi, appApi.cursorApi, appApi.treeNodeApi]);
55
+ const effectToAction = useMemo(() => createEffectToAction(appApi.treeNodeApi, appApi.cursorApi, appApi.navNodeApi), [appApi.treeNodeApi, appApi.navNodeApi, appApi.cursorApi]);
56
+ // --- JSX ---
57
+ if (error !== undefined) {
58
+ return _jsx(Text, { color: "red", children: error.message });
59
+ }
60
+ if (empty) {
61
+ return _jsx(Text, { dimColor: true, children: appApi.emptyForestMessage });
62
+ }
63
+ if (initialState === undefined) {
64
+ return _jsx(Text, { dimColor: true, children: "Loading." });
65
+ }
66
+ return (_jsx(App, { appApi: appApi, initialState: initialState, reducer: reducer, intentToEffect: intentToEffect, effectToAction: effectToAction, ...(stdout === undefined ? {} : { stdout }), ...(clipboard === undefined ? {} : { clipboard }), ...(onError === undefined ? {} : { onError }) }));
67
+ }
@@ -1,10 +1,8 @@
1
1
  import { InputState } from 'dirvi-lib';
2
2
  export declare const STATUS_BAR_HEIGHT = 1;
3
3
  type StatusBarProps = {
4
- inputState: InputState;
4
+ inputState: InputState;
5
5
  };
6
- export declare function StatusBar({
7
- inputState,
8
- }: StatusBarProps): import('react').JSX.Element;
6
+ export declare function StatusBar({ inputState }: StatusBarProps): import("react").JSX.Element;
9
7
  export {};
10
- //# sourceMappingURL=StatusBar.d.ts.map
8
+ //# sourceMappingURL=StatusBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/ui/components/StatusBar.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,KAAK,cAAc,GAAG;IACpB,UAAU,EAAE,UAAU,CAAA;CACvB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,cAAc,+BAyBvD"}
1
+ {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/ui/components/StatusBar.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,KAAK,cAAc,GAAG;IACpB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,cAAc,+BAyBvD"}
@@ -1,32 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  export const STATUS_BAR_HEIGHT = 1;
4
4
  export function StatusBar({ inputState }) {
5
- const inputMode = inputState.inputMode;
6
- return _jsxs(Box, {
7
- width: '100%',
8
- height: STATUS_BAR_HEIGHT,
9
- flexDirection: 'row',
10
- justifyContent: 'space-between',
11
- flexShrink: 0,
12
- backgroundColor: 'gray',
13
- children: [
14
- _jsx(Box, {
15
- flexShrink: 1,
16
- children: _jsx(Text, {
17
- color: 'black',
18
- wrap: 'truncate-end',
19
- children: inputMode === 'command' ? inputState.commandLine : '',
20
- }),
21
- }),
22
- _jsx(Box, {
23
- flexShrink: 1,
24
- children: _jsx(Text, {
25
- color: 'black',
26
- wrap: 'truncate-start',
27
- children: inputMode === 'normal' ? inputState.normalBuffer : '',
28
- }),
29
- }),
30
- ],
31
- });
5
+ const inputMode = inputState.inputMode;
6
+ return (_jsxs(Box, { width: "100%", height: STATUS_BAR_HEIGHT, flexDirection: "row", justifyContent: "space-between", flexShrink: 0, backgroundColor: 'gray', children: [_jsx(Box, { flexShrink: 1, children: _jsx(Text, { color: "black", wrap: "truncate-end", children: inputMode === 'command' ? inputState.commandLine : '' }) }), _jsx(Box, { flexShrink: 1, children: _jsx(Text, { color: "black", wrap: "truncate-start", children: inputMode === 'normal' ? inputState.normalBuffer : '' }) })] }));
32
7
  }
@@ -1,7 +1,5 @@
1
1
  import { ViewRow } from '../view.js';
2
- export declare function ViewRowComponent({
3
- row,
4
- }: {
5
- row: ViewRow;
6
- }): import('react').JSX.Element;
7
- //# sourceMappingURL=ViewRowComponent.d.ts.map
2
+ export declare function ViewRowComponent({ row }: {
3
+ row: ViewRow;
4
+ }): import("react").JSX.Element;
5
+ //# sourceMappingURL=ViewRowComponent.d.ts.map
@@ -1,33 +1,13 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime';
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  export function ViewRowComponent({ row }) {
4
- const prefix = '';
5
- switch (row.type) {
6
- case 'file':
7
- return _jsx(Box, {
8
- paddingLeft: row.indent * 2,
9
- children: _jsxs(Text, {
10
- inverse: row.selected,
11
- children: [prefix, row.content],
12
- }),
13
- });
14
- case 'directory':
15
- return _jsx(Box, {
16
- paddingLeft: row.indent * 2,
17
- children: _jsxs(Text, {
18
- inverse: row.selected,
19
- color: 'blue',
20
- children: [prefix, row.content],
21
- }),
22
- });
23
- case 'fold':
24
- return _jsx(Box, {
25
- paddingLeft: row.indent * 2,
26
- children: _jsxs(Text, {
27
- inverse: row.selected,
28
- dimColor: true,
29
- children: [prefix, row.content],
30
- }),
31
- });
32
- }
4
+ const prefix = '';
5
+ switch (row.type) {
6
+ case 'leaf':
7
+ return (_jsx(Box, { paddingLeft: row.indent * 2, children: _jsxs(Text, { inverse: row.selected, children: [prefix, row.content] }) }));
8
+ case 'branch':
9
+ return (_jsx(Box, { paddingLeft: row.indent * 2, children: _jsxs(Text, { inverse: row.selected, color: "blue", children: [prefix, row.content] }) }));
10
+ case 'fold':
11
+ return (_jsx(Box, { paddingLeft: row.indent * 2, children: _jsxs(Text, { inverse: row.selected, dimColor: true, children: [prefix, row.content] }) }));
12
+ }
33
13
  }
@@ -1,8 +1,5 @@
1
- import { NavigationNode, State } from 'dirvi-lib';
1
+ import { CursorApi, NavNode, SerializableKey, State, TreeNode } from 'dirvi-lib';
2
2
  import { View } from '../view.js';
3
- export declare function useView(
4
- navigation: NavigationNode,
5
- state: State,
6
- terminalRows: number,
7
- ): View;
8
- //# sourceMappingURL=useView.d.ts.map
3
+ export type UseView = <Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(navigation: NavNode<Id, BufferNode>, state: State<Id, BufferNode>, terminalRows: number) => View;
4
+ export declare function useView<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(navigation: NavNode<Id, BufferNode>, state: State<Id, BufferNode>, cursorApi: CursorApi<Id>, terminalRows: number): View;
5
+ //# sourceMappingURL=useView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useView.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,IAAI,EAAW,MAAM,YAAY,CAAC;AAO3C,wBAAgB,OAAO,CACrB,UAAU,EAAE,cAAc,EAC1B,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,MAAM,GACnB,IAAI,CAkBN"}
1
+ {"version":3,"file":"useView.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useView.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,OAAO,EACP,eAAe,EACf,KAAK,EACL,QAAQ,EACT,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,IAAI,EAAW,MAAM,YAAY,CAAC;AAI3C,MAAM,MAAM,OAAO,GAAG,CACpB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAE3C,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,EACnC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,EAC5B,YAAY,EAAE,MAAM,KACjB,IAAI,CAAC;AAIV,wBAAgB,OAAO,CACrB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAE3C,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,EACnC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,YAAY,EAAE,MAAM,GACnB,IAAI,CAoBN"}
@@ -3,36 +3,27 @@ import { useRef } from 'react';
3
3
  import { STATUS_BAR_HEIGHT } from '../components/StatusBar.js';
4
4
  // Hooks that keeps Ref of the viewport's start, and returns View sliced to
5
5
  // only the rows that should be visible.
6
- export function useView(navigation, state, terminalRows) {
7
- const viewportStartRef = useRef(0);
8
- const viewportHeight = Math.max(1, terminalRows - STATUS_BAR_HEIGHT);
9
- const rows = View.createRows(navigation, state.cursor);
10
- viewportStartRef.current = viewportStart(
11
- rows,
12
- viewportHeight,
13
- viewportStartRef.current,
14
- );
15
- return View.create(
16
- navigation,
17
- state.cursor,
18
- viewportHeight,
19
- viewportStartRef.current,
20
- );
6
+ export function useView(navigation, state, cursorApi, terminalRows) {
7
+ const viewportStartRef = useRef(0);
8
+ const viewportHeight = Math.max(1, terminalRows - STATUS_BAR_HEIGHT);
9
+ const rows = View.createRows(navigation, state.cursor, cursorApi);
10
+ viewportStartRef.current = viewportStart(rows, viewportHeight, viewportStartRef.current);
11
+ return View.create(navigation, state.cursor, cursorApi, viewportHeight, viewportStartRef.current);
21
12
  }
22
13
  function viewportStart(rows, viewportHeight, currentViewportStart) {
23
- const scrollMargin = 3;
24
- const cursorIndex = rows.findIndex((row) => row.cursor);
25
- const maximumViewportStart = Math.max(0, rows.length - viewportHeight);
26
- let viewportStart = currentViewportStart;
27
- if (cursorIndex >= 0) {
28
- const firstVisibleCursorIndex = viewportStart + scrollMargin;
29
- const lastVisibleCursorIndex =
30
- viewportStart + viewportHeight - 1 - scrollMargin;
31
- if (cursorIndex < firstVisibleCursorIndex) {
32
- viewportStart = cursorIndex - scrollMargin;
33
- } else if (cursorIndex > lastVisibleCursorIndex) {
34
- viewportStart = cursorIndex - viewportHeight + 1 + scrollMargin;
14
+ const scrollMargin = 3;
15
+ const cursorIndex = rows.findIndex((row) => row.cursor);
16
+ const maximumViewportStart = Math.max(0, rows.length - viewportHeight);
17
+ let viewportStart = currentViewportStart;
18
+ if (cursorIndex >= 0) {
19
+ const firstVisibleCursorIndex = viewportStart + scrollMargin;
20
+ const lastVisibleCursorIndex = viewportStart + viewportHeight - 1 - scrollMargin;
21
+ if (cursorIndex < firstVisibleCursorIndex) {
22
+ viewportStart = cursorIndex - scrollMargin;
23
+ }
24
+ else if (cursorIndex > lastVisibleCursorIndex) {
25
+ viewportStart = cursorIndex - viewportHeight + 1 + scrollMargin;
26
+ }
35
27
  }
36
- }
37
- return Math.max(0, Math.min(viewportStart, maximumViewportStart));
28
+ return Math.max(0, Math.min(viewportStart, maximumViewportStart));
38
29
  }
package/dist/ui/view.d.ts CHANGED
@@ -1,23 +1,19 @@
1
- import { Cursor, NavigationNode } from 'dirvi-lib';
2
- export type ViewRowType = 'file' | 'directory' | 'fold';
1
+ import { CursorApi, SerializableKey, TreeNode } from 'dirvi-lib';
2
+ import { Cursor, NavNode } from 'dirvi-lib';
3
+ export type ViewRowType = 'leaf' | 'branch' | 'fold';
3
4
  export type ViewRow = {
4
- id: string;
5
- indent: number;
6
- selected: boolean;
7
- cursor: boolean;
8
- content: string;
9
- type: ViewRowType;
5
+ id: string;
6
+ indent: number;
7
+ selected: boolean;
8
+ cursor: boolean;
9
+ content: string;
10
+ type: ViewRowType;
10
11
  };
11
12
  export type View = {
12
- rows: ViewRow[];
13
+ rows: ViewRow[];
13
14
  };
14
15
  export declare const View: {
15
- createRows(navigation: NavigationNode, cursor: Cursor): ViewRow[];
16
- create(
17
- navigation: NavigationNode,
18
- cursor: Cursor,
19
- viewportHeight: number,
20
- viewportStart: number,
21
- ): View;
16
+ createRows<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(navigation: NavNode<Id, BufferNode>, cursor: Cursor<Id>, cursorApi: CursorApi<Id>): ViewRow[];
17
+ create<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(navigation: NavNode<Id, BufferNode>, cursor: Cursor<Id>, cursorApi: CursorApi<Id>, viewportHeight: number, viewportStart: number): View;
22
18
  };
23
- //# sourceMappingURL=view.d.ts.map
19
+ //# sourceMappingURL=view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/ui/view.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAEN,cAAc,EAEf,MAAM,WAAW,CAAC;AAEnB,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAMxD,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,IAAI;2BACQ,cAAc,UAAU,MAAM,GAAG,OAAO,EAAE;uBAKnD,cAAc,UAClB,MAAM,kBACE,MAAM,iBACP,MAAM,GACpB,IAAI;CAOR,CAAC"}
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/ui/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,MAAM,EAAyB,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAMrD,MAAM,MAAM,OAAO,GAAG;IAEpB,EAAE,EAAE,MAAM,CAAC;IAGX,MAAM,EAAE,MAAM,CAAC;IAGf,QAAQ,EAAE,OAAO,CAAC;IAGlB,MAAM,EAAE,OAAO,CAAC;IAGhB,OAAO,EAAE,MAAM,CAAC;IAGhB,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,IAAI;eAEb,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,cAE/B,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,UAC3B,MAAM,CAAC,EAAE,CAAC,aACP,SAAS,CAAC,EAAE,CAAC,GACvB,OAAO,EAAE;WAKV,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,cAE/B,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,UAC3B,MAAM,CAAC,EAAE,CAAC,aACP,SAAS,CAAC,EAAE,CAAC,kBACR,MAAM,iBACP,MAAM,GACpB,IAAI;CAOR,CAAC"}
package/dist/ui/view.js CHANGED
@@ -1,78 +1,98 @@
1
- import { UnixEntryPath } from 'dirvi-lib';
1
+ import { isNavBranch } from 'dirvi-lib';
2
2
  export const View = {
3
- createRows(navigation, cursor) {
4
- return viewRowsAtNode(navigation, [], cursor);
5
- },
6
- create(navigation, cursor, viewportHeight, viewportStart) {
7
- const rows = View.createRows(navigation, cursor);
8
- return {
9
- rows: rows.slice(viewportStart, viewportStart + viewportHeight),
10
- };
11
- },
3
+ createRows(navigation, cursor, cursorApi) {
4
+ return viewRowsAtNode(navigation, [], cursor, cursorApi);
5
+ },
6
+ create(navigation, cursor, cursorApi, viewportHeight, viewportStart) {
7
+ const rows = View.createRows(navigation, cursor, cursorApi);
8
+ return {
9
+ rows: rows.slice(viewportStart, viewportStart + viewportHeight),
10
+ };
11
+ },
12
12
  };
13
- function viewRowsAtNode(node, parentPath, cursor) {
14
- const rows = [];
15
- for (const entry of node.entries) {
16
- const isCursor =
17
- cursor.kind === 'entry' &&
18
- UnixEntryPath.equal(cursor.parentPath, parentPath) &&
19
- cursor.entryName === entry.name;
20
- rows.push(viewRowForEntry(entry, parentPath, isCursor));
21
- if (entry.kind !== 'directory' || entry.node === undefined) {
22
- continue;
13
+ /**
14
+ * Converts one NavNode and its descendants into display ViewRows.
15
+ *
16
+ * `node` is the directory currently being visited.
17
+ *
18
+ * `parentPath` is the path of that directory. For example:
19
+ *
20
+ * [] root directory
21
+ * ['src'] root/src
22
+ * ['src', 'lib'] root/src/lib
23
+ *
24
+ * The function uses a depth-first, pre-order traversal:
25
+ *
26
+ * 1. Add an entry's row.
27
+ * 2. If the entry is an opened branch, recursively add its children.
28
+ * 3. After all visible entries, add the directory's fold row.
29
+ *
30
+ * This ordering is what makes the flat row list visually represent a tree.
31
+ */
32
+ function viewRowsAtNode(node, parentPath, cursor, cursorApi) {
33
+ const rows = [];
34
+ /*
35
+ * `node.entries` contains only the entries currently visible in this
36
+ * directory. Folded entries are kept separately in `node.foldedEntries`
37
+ * and are represented by one fold row added below.
38
+ */
39
+ for (const entry of node.entries) {
40
+ const entryCursor = {
41
+ kind: 'entry',
42
+ parentPath,
43
+ entryId: entry.id,
44
+ };
45
+ // If cursor at entry, needs to be rendered differently
46
+ const isCursor = cursorApi.equal(cursor, entryCursor);
47
+ // Add entry before visiting branches
48
+ rows.push(viewRowForEntry(entry, parentPath, isCursor));
49
+ // Case where there's no branches
50
+ if (!isNavBranch(entry) || entry.children === null) {
51
+ continue;
52
+ }
53
+ rows.push(...viewRowsAtNode(entry.children, [...parentPath, entry.id], cursor, cursorApi));
54
+ }
55
+ /*
56
+ * Folded entries are not rendered individually. They are represented
57
+ * by one synthetic row placed after all visible entries and descendants
58
+ * of this directory.
59
+ */
60
+ if (node.foldedEntries.length > 0) {
61
+ const foldCursor = {
62
+ kind: 'fold',
63
+ parentPath,
64
+ };
65
+ const isCursor = cursorApi.equal(cursor, foldCursor);
66
+ rows.push({
67
+ id: foldId(parentPath),
68
+ indent: parentPath.length,
69
+ selected: isCursor,
70
+ cursor: isCursor,
71
+ content: `⋯ ${node.foldedEntries.length} folded`,
72
+ type: 'fold',
73
+ });
23
74
  }
24
- rows.push(
25
- ...viewRowsAtNode(entry.node, [...parentPath, entry.name], cursor),
26
- );
27
- }
28
- if (node.foldedEntries.length > 0) {
29
- const isCursor =
30
- cursor.kind === 'fold' &&
31
- UnixEntryPath.equal(cursor.parentPath, parentPath);
32
- rows.push({
33
- id: foldId(parentPath),
34
- indent: parentPath.length,
35
- selected: isCursor,
36
- cursor: isCursor,
37
- content: `⋯ ${node.foldedEntries.length} folded`,
38
- type: 'fold',
39
- });
40
- }
41
- return rows;
75
+ return rows;
42
76
  }
43
77
  function viewRowForEntry(entry, parentPath, cursor) {
44
- return {
45
- id: entryId(parentPath, entry.name),
46
- indent: parentPath.length,
47
- selected: cursor,
48
- cursor,
49
- content: content(entry),
50
- type: type(entry),
51
- };
78
+ return {
79
+ id: entryId(parentPath, entry.id),
80
+ indent: parentPath.length,
81
+ selected: cursor,
82
+ cursor,
83
+ content: content(entry),
84
+ type: type(entry),
85
+ };
52
86
  }
53
87
  function entryId(parentPath, entryName) {
54
- return `entry:${JSON.stringify([...parentPath, entryName])}`;
88
+ return `entry:${JSON.stringify([...parentPath, entryName])}`;
55
89
  }
56
90
  function foldId(parentPath) {
57
- return `fold:${JSON.stringify(parentPath)}`;
91
+ return `fold:${JSON.stringify(parentPath)}`;
58
92
  }
59
93
  function content(entry) {
60
- switch (entry.kind) {
61
- case 'file':
62
- return entry.name;
63
- case 'directory':
64
- return `${entry.name}/`;
65
- case 'symlink':
66
- return `${entry.name} -> ${entry.target}`;
67
- }
94
+ return isNavBranch(entry) ? `${String(entry.id)}/` : String(entry.id);
68
95
  }
69
96
  function type(entry) {
70
- switch (entry.kind) {
71
- case 'file':
72
- return 'file';
73
- case 'directory':
74
- return 'directory';
75
- case 'symlink':
76
- return 'file';
77
- }
97
+ return isNavBranch(entry) ? 'branch' : 'leaf';
78
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mak-98/dirvi-cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A Vim-inspired TUI for browsing directories.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "dependencies": {
15
15
  "commander": "^13.0.0",
16
- "dirvi-lib": "0.4.0",
16
+ "dirvi-lib": "0.6.0",
17
17
  "ink": "^7.1.1",
18
18
  "react": "^19.0.0"
19
19
  },
@@ -1,26 +0,0 @@
1
- import { UnixEntry, UnixEntryPath } from 'dirvi-lib';
2
- export type Action =
3
- | {
4
- kind: 'nextEntry';
5
- }
6
- | {
7
- kind: 'prevEntry';
8
- }
9
- | {
10
- kind: 'updateDir';
11
- path: UnixEntryPath;
12
- entries: UnixEntry[] | undefined;
13
- }
14
- | {
15
- kind: 'outDir';
16
- }
17
- | {
18
- kind: 'toggleFold';
19
- }
20
- | {
21
- kind: 'fold';
22
- }
23
- | {
24
- kind: 'unfold';
25
- };
26
- //# sourceMappingURL=action.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/application/action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAErD,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,aAAa,CAAC;IACpB,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;CAClC,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,CAAC"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- import type { UnixEntry, DirectoryBuffer, EntryPath } from 'dirvi-lib';
2
- export declare function entryAtPath(
3
- buffer: DirectoryBuffer,
4
- path: EntryPath,
5
- ): UnixEntry | undefined;
6
- //# sourceMappingURL=dir-buffer-helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dir-buffer-helpers.d.ts","sourceRoot":"","sources":["../../src/application/dir-buffer-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEvE,wBAAgB,WAAW,CACzB,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,SAAS,GACd,SAAS,GAAG,SAAS,CA2BvB"}
@@ -1,21 +0,0 @@
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
- }
@@ -1,11 +0,0 @@
1
- import { DisplayRow, UnixEntryPath, FoldNode, UnixEntry } from 'dirvi-lib';
2
- export declare function createDisplayRows(
3
- buffer: UnixEntry[],
4
- folds: FoldNode,
5
- ): DisplayRow[];
6
- export declare function displayRowAtPath(
7
- buffer: UnixEntry[],
8
- folds: FoldNode,
9
- path: UnixEntryPath,
10
- ): DisplayRow | undefined;
11
- //# sourceMappingURL=display-rows.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"display-rows.d.ts","sourceRoot":"","sources":["../../src/application/display-rows.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EAEV,MAAM,WAAW,CAAC;AAuDnB,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EAAE,EACnB,KAAK,EAAE,QAAQ,GACd,UAAU,EAAE,CAEd;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,aAAa,GAClB,UAAU,GAAG,SAAS,CAsBxB"}