dirvi-lib 0.4.0 → 0.5.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/dist/domain/posix-node.d.ts +29 -0
- package/dist/domain/posix-node.d.ts.map +1 -0
- package/dist/domain/posix-node.js +20 -0
- package/dist/domain/unix-path.d.ts +1 -1
- package/dist/domain/unix-path.js +6 -9
- package/dist/index.d.ts +9 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -13
- package/dist/tree-surfer/cursor.d.ts +24 -0
- package/dist/tree-surfer/cursor.d.ts.map +1 -0
- package/dist/tree-surfer/cursor.js +65 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +5 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.js +143 -0
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts +70 -0
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/fold-node.types.js +1 -0
- package/dist/tree-surfer/index.d.ts +9 -0
- package/dist/tree-surfer/index.d.ts.map +1 -0
- package/dist/tree-surfer/index.js +8 -0
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +6 -0
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -0
- package/dist/tree-surfer/nav-node/nav-node.impl.js +191 -0
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +42 -0
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -0
- package/dist/tree-surfer/nav-node/nav-node.types.js +3 -0
- package/dist/tree-surfer/state.d.ts +19 -0
- package/dist/tree-surfer/state.d.ts.map +1 -0
- package/dist/tree-surfer/state.js +109 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +31 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.js +80 -0
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +13 -0
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.impl.js +87 -0
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts +2 -0
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.test.js +117 -0
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts +184 -0
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.types.js +12 -0
- package/dist/tree-surfer-ui/application/index.d.ts +3 -0
- package/dist/tree-surfer-ui/application/index.d.ts.map +1 -0
- package/dist/tree-surfer-ui/application/index.js +2 -0
- package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts +2 -0
- package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts.map +1 -0
- package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.js +192 -0
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -0
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -0
- package/dist/tree-surfer-ui/application/intent-to-effect.js +137 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +3 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.js +98 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts +2 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts.map +1 -0
- package/dist/tree-surfer-ui/application/user-input-to-intent.test.js +118 -0
- package/dist/tree-surfer-ui/domain/effect.d.ts +36 -0
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -0
- package/dist/tree-surfer-ui/domain/index.d.ts +5 -0
- package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -0
- package/dist/tree-surfer-ui/domain/index.js +4 -0
- package/dist/tree-surfer-ui/domain/input-state.d.ts +8 -0
- package/dist/tree-surfer-ui/domain/input-state.d.ts.map +1 -0
- package/dist/tree-surfer-ui/domain/intent.d.ts +25 -0
- package/dist/tree-surfer-ui/domain/intent.d.ts.map +1 -0
- package/dist/tree-surfer-ui/domain/user-input.d.ts +19 -0
- package/dist/tree-surfer-ui/domain/user-input.d.ts.map +1 -0
- package/package.json +3 -2
- package/dist/application/intent-to-effect-no-state.test.d.ts +0 -2
- package/dist/application/intent-to-effect-no-state.test.d.ts.map +0 -1
- package/dist/application/intent-to-effect-no-state.test.js +0 -178
- package/dist/application/intent-to-effect.d.ts +0 -8
- package/dist/application/intent-to-effect.d.ts.map +0 -1
- package/dist/application/intent-to-effect.js +0 -142
- package/dist/application/intent-to-effect.test-no-state.d.ts +0 -2
- package/dist/application/intent-to-effect.test-no-state.d.ts.map +0 -1
- package/dist/application/intent-to-effect.test-no-state.js +0 -178
- package/dist/application/user-input-to-intent.d.ts +0 -8
- package/dist/application/user-input-to-intent.d.ts.map +0 -1
- package/dist/application/user-input-to-intent.js +0 -98
- package/dist/application/user-input-to-intent.test.d.ts +0 -2
- package/dist/application/user-input-to-intent.test.d.ts.map +0 -1
- package/dist/application/user-input-to-intent.test.js +0 -125
- package/dist/domain/cursor.d.ts +0 -19
- package/dist/domain/cursor.d.ts.map +0 -1
- package/dist/domain/cursor.js +0 -46
- package/dist/domain/display-row.d.ts +0 -34
- package/dist/domain/display-row.d.ts.map +0 -1
- package/dist/domain/display-row.js +0 -33
- package/dist/domain/effect.d.ts +0 -48
- package/dist/domain/effect.d.ts.map +0 -1
- package/dist/domain/fold-node.d.ts +0 -25
- package/dist/domain/fold-node.d.ts.map +0 -1
- package/dist/domain/fold-node.js +0 -93
- package/dist/domain/input-state.d.ts +0 -10
- package/dist/domain/input-state.d.ts.map +0 -1
- package/dist/domain/intent.d.ts +0 -34
- package/dist/domain/intent.d.ts.map +0 -1
- package/dist/domain/navigation-node.d.ts +0 -58
- package/dist/domain/navigation-node.d.ts.map +0 -1
- package/dist/domain/navigation-node.js +0 -210
- package/dist/domain/state.d.ts +0 -12
- package/dist/domain/state.d.ts.map +0 -1
- package/dist/domain/state.js +0 -12
- package/dist/domain/unix-entry.d.ts +0 -44
- package/dist/domain/unix-entry.d.ts.map +0 -1
- package/dist/domain/unix-entry.js +0 -108
- package/dist/domain/user-input.d.ts +0 -27
- package/dist/domain/user-input.d.ts.map +0 -1
- package/dist/infrastructure/get-cwd.d.ts +0 -3
- package/dist/infrastructure/get-cwd.d.ts.map +0 -1
- package/dist/infrastructure/get-cwd.js +0 -15
- package/dist/infrastructure/get-dir-lazy-entries.d.ts +0 -6
- package/dist/infrastructure/get-dir-lazy-entries.d.ts.map +0 -1
- package/dist/infrastructure/get-dir-lazy-entries.js +0 -40
- /package/dist/{domain → tree-surfer-ui/domain}/effect.js +0 -0
- /package/dist/{domain → tree-surfer-ui/domain}/input-state.js +0 -0
- /package/dist/{domain → tree-surfer-ui/domain}/intent.js +0 -0
- /package/dist/{domain → tree-surfer-ui/domain}/user-input.js +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import * as fc from 'fast-check';
|
|
3
|
+
import { createTreeNodeApi } from './tree-node.impl.js';
|
|
4
|
+
import { forestAndPathAndExpectedArb, newBranchesOrNullArb, nodesArrayAndBranchPathArb, } from './tree-node.arb.js';
|
|
5
|
+
const stringNodeApi = createTreeNodeApi();
|
|
6
|
+
describe('tree node API', () => {
|
|
7
|
+
it('getAtPath returns the node at the generated path', () => {
|
|
8
|
+
fc.assert(fc.property(forestAndPathAndExpectedArb, ({ entries, path, expected }) => {
|
|
9
|
+
expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
|
|
10
|
+
}));
|
|
11
|
+
});
|
|
12
|
+
it('getAtPath applies the selector', () => {
|
|
13
|
+
const entries = [
|
|
14
|
+
{
|
|
15
|
+
id: 'root',
|
|
16
|
+
children: [{ id: 'child' }],
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
expect(stringNodeApi.getAtPath(entries, ['root', 'child'], (node) => node.id)).toBe('child');
|
|
20
|
+
});
|
|
21
|
+
it('getAtPath returns undefined for an empty path', () => {
|
|
22
|
+
const entries = [{ id: 'root' }];
|
|
23
|
+
expect(stringNodeApi.getAtPath(entries, [], (node) => node)).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
it('getAtPath returns undefined through a closed branch', () => {
|
|
26
|
+
const entries = [
|
|
27
|
+
{
|
|
28
|
+
id: 'root',
|
|
29
|
+
children: null,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
expect(stringNodeApi.getAtPath(entries, ['root', 'child'], (node) => node)).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
it('getAtPath returns undefined when the selector returns undefined', () => {
|
|
35
|
+
const entries = [{ id: 'root' }];
|
|
36
|
+
expect(stringNodeApi.getAtPath(entries, ['root'], () => undefined)).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
it('getChildById returns the matching child', () => {
|
|
39
|
+
const child = { id: 'child' };
|
|
40
|
+
const root = {
|
|
41
|
+
id: 'root',
|
|
42
|
+
children: [child],
|
|
43
|
+
};
|
|
44
|
+
expect(stringNodeApi.isOpenBranch(root)).toBe(true);
|
|
45
|
+
if (!stringNodeApi.isOpenBranch(root)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
expect(stringNodeApi.getChildById(root, 'child')).toBe(child);
|
|
49
|
+
expect(stringNodeApi.getChildById(root, 'missing')).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it('modifyAtPath replaces the generated node', () => {
|
|
52
|
+
fc.assert(fc.property(forestAndPathAndExpectedArb, ({ entries, path, expected }) => {
|
|
53
|
+
const replacement = {
|
|
54
|
+
id: expected.id,
|
|
55
|
+
};
|
|
56
|
+
const updatedForest = stringNodeApi.modifyAtPath(entries, path, () => replacement);
|
|
57
|
+
expect(updatedForest).not.toBeUndefined();
|
|
58
|
+
if (updatedForest === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
expect(stringNodeApi.getAtPath(updatedForest, path, (node) => node)).toBe(replacement);
|
|
62
|
+
expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
it('modifyAtPath replaces children at a branch path', () => {
|
|
66
|
+
fc.assert(fc.property(nodesArrayAndBranchPathArb, newBranchesOrNullArb, ({ entries, path, expected }, newBranches) => {
|
|
67
|
+
expect(stringNodeApi.isBranch(expected)).toBe(true);
|
|
68
|
+
const updatedForest = stringNodeApi.modifyAtPath(entries, path, (node) => ({
|
|
69
|
+
...node,
|
|
70
|
+
children: newBranches,
|
|
71
|
+
}));
|
|
72
|
+
expect(updatedForest).not.toBeUndefined();
|
|
73
|
+
if (updatedForest === undefined) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const updatedNode = stringNodeApi.getAtPath(updatedForest, path, (node) => node);
|
|
77
|
+
expect(updatedNode).not.toBeUndefined();
|
|
78
|
+
expect(updatedNode).not.toBe(expected);
|
|
79
|
+
if (updatedNode === undefined ||
|
|
80
|
+
!stringNodeApi.isBranch(updatedNode) ||
|
|
81
|
+
(!stringNodeApi.isOpenBranch(updatedNode) && newBranches !== null)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (stringNodeApi.isOpenBranch(updatedNode)) {
|
|
85
|
+
expect([...stringNodeApi.getChildren(updatedNode)]).toStrictEqual(newBranches);
|
|
86
|
+
}
|
|
87
|
+
expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
it('modifyAtPath returns undefined when the modifier aborts', () => {
|
|
91
|
+
const entries = [{ id: 'root' }];
|
|
92
|
+
expect(stringNodeApi.modifyAtPath(entries, ['root'], () => undefined)).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
it('modifyAtPath returns undefined for an empty path', () => {
|
|
95
|
+
const entries = [{ id: 'root' }];
|
|
96
|
+
expect(stringNodeApi.modifyAtPath(entries, [], (node) => node)).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
it('modifyAtPath returns undefined for an invalid path', () => {
|
|
99
|
+
const entries = [
|
|
100
|
+
{
|
|
101
|
+
id: 'root',
|
|
102
|
+
children: [],
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
expect(stringNodeApi.modifyAtPath(entries, ['missing'], (node) => node)).toBeUndefined();
|
|
106
|
+
expect(stringNodeApi.modifyAtPath(entries, ['root', 'missing'], (node) => node)).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
it('modifyAtPath cannot traverse a closed branch', () => {
|
|
109
|
+
const entries = [
|
|
110
|
+
{
|
|
111
|
+
id: 'root',
|
|
112
|
+
children: null,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
expect(stringNodeApi.modifyAtPath(entries, ['root', 'child'], (node) => node)).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime schema for IDs that can be safely represented as serializable
|
|
4
|
+
* primitive values.
|
|
5
|
+
*
|
|
6
|
+
* Strings are accepted as-is. Numbers must be finite, so `NaN`, `Infinity`,
|
|
7
|
+
* and `-Infinity` are rejected.
|
|
8
|
+
*
|
|
9
|
+
* Applications should derive a more specific schema when those constraints
|
|
10
|
+
* are needed.
|
|
11
|
+
*/
|
|
12
|
+
export declare const serializableKeySchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
13
|
+
/**
|
|
14
|
+
* A serializable primitive suitable for use as a stable tree ID.
|
|
15
|
+
*
|
|
16
|
+
* The runtime schema guarantees that numeric values are finite. TypeScript
|
|
17
|
+
* cannot represent the exclusion of `NaN` and infinities from `number`, so
|
|
18
|
+
* values from untrusted sources should be validated with
|
|
19
|
+
* `serializableKeySchema` before being used as IDs.
|
|
20
|
+
*
|
|
21
|
+
* For application-specific IDs, define a narrower branded schema and derive
|
|
22
|
+
* its type with `z.output`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const menuIdSchema = z
|
|
28
|
+
* .string()
|
|
29
|
+
* .min(1)
|
|
30
|
+
* .brand<'MenuId'>();
|
|
31
|
+
*
|
|
32
|
+
* type MenuId = z.output<typeof menuIdSchema>;
|
|
33
|
+
*
|
|
34
|
+
* const menuId = menuIdSchema.parse(input);
|
|
35
|
+
* // menuId: MenuId
|
|
36
|
+
*
|
|
37
|
+
* type MenuNode = TreeNode<MenuId, MenuNode>;
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export type SerializableKey = string | number;
|
|
41
|
+
/**
|
|
42
|
+
* A node with no children.
|
|
43
|
+
*/
|
|
44
|
+
export type LeafTreeNode<Id extends SerializableKey> = {
|
|
45
|
+
id: Id;
|
|
46
|
+
children?: never;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* A branch whose children have not been loaded.
|
|
50
|
+
*/
|
|
51
|
+
export type ClosedBranchTreeNode<Id extends SerializableKey> = {
|
|
52
|
+
id: Id;
|
|
53
|
+
children: null;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* A branch whose children have been loaded.
|
|
57
|
+
*
|
|
58
|
+
* TODO Later: Add a dictionary by ID for faster lookup.
|
|
59
|
+
* Then children will just be an array for keeping order.
|
|
60
|
+
*/
|
|
61
|
+
export type OpenBranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = {
|
|
62
|
+
id: Id;
|
|
63
|
+
children: ChildNode[];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* A tree node that has children, either loaded or unloaded.
|
|
67
|
+
*/
|
|
68
|
+
export type BranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = OpenBranchTreeNode<Id, ChildNode> | ClosedBranchTreeNode<Id>;
|
|
69
|
+
/**
|
|
70
|
+
* A node in a tree.
|
|
71
|
+
*
|
|
72
|
+
* `ChildNode` is the concrete application-specific node type. It may contain
|
|
73
|
+
* additional properties, provided that it has the shape of either a leaf or a
|
|
74
|
+
* branch.
|
|
75
|
+
*/
|
|
76
|
+
export type TreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = LeafTreeNode<Id> | BranchTreeNode<Id, ChildNode>;
|
|
77
|
+
/**
|
|
78
|
+
* Selects a value from a node reached by a path.
|
|
79
|
+
*
|
|
80
|
+
* The selector is called only when the complete path exists and identifies
|
|
81
|
+
* a node. Returning `undefined` from the selector is allowed, but is
|
|
82
|
+
* indistinguishable from an unsuccessful lookup in the return value.
|
|
83
|
+
*/
|
|
84
|
+
export type TreeNodeSelector<ChildNode, Result> = (node: ChildNode) => Result;
|
|
85
|
+
/**
|
|
86
|
+
* Produces a replacement for a node reached by a path.
|
|
87
|
+
*
|
|
88
|
+
* Returning `undefined` aborts the modification and causes `modifyAtPath`
|
|
89
|
+
* to return `undefined`.
|
|
90
|
+
*/
|
|
91
|
+
export type TreeNodeModifier<ChildNode> = (node: ChildNode) => ChildNode | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Fundamental operations for inspecting and immutably updating a tree forest.
|
|
94
|
+
*
|
|
95
|
+
* A forest is represented by an array of root-level nodes. A path is an array
|
|
96
|
+
* of IDs beginning at a root node and continuing through its descendants.
|
|
97
|
+
*
|
|
98
|
+
* For example, given tree with IDs:
|
|
99
|
+
*
|
|
100
|
+
* root
|
|
101
|
+
* └── child
|
|
102
|
+
*
|
|
103
|
+
* the path to `child` is `['root', 'child']`.
|
|
104
|
+
*
|
|
105
|
+
* Path traversal and child lookup are performed by this API so that callers
|
|
106
|
+
* do not depend on the internal child-storage representation or need to
|
|
107
|
+
* iterate over children themselves.
|
|
108
|
+
*
|
|
109
|
+
* An empty path does not identify a node and is invalid for both lookup and
|
|
110
|
+
* modification operations.
|
|
111
|
+
*/
|
|
112
|
+
export type TreeNodeApi<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = {
|
|
113
|
+
isLeaf(node: ChildNode): node is ChildNode & LeafTreeNode<Id>;
|
|
114
|
+
isBranch(node: ChildNode): node is ChildNode & BranchTreeNode<Id, ChildNode>;
|
|
115
|
+
isClosedBranch(node: BranchTreeNode<Id, ChildNode>): node is ClosedBranchTreeNode<Id>;
|
|
116
|
+
isOpenBranch(node: BranchTreeNode<Id, ChildNode>): node is OpenBranchTreeNode<Id, ChildNode>;
|
|
117
|
+
/**
|
|
118
|
+
* Returns the loaded children of an open branch.
|
|
119
|
+
*
|
|
120
|
+
* The returned iterable preserves the branch's sibling order. The method
|
|
121
|
+
* can only be called after narrowing the branch with `isOpenBranch`.
|
|
122
|
+
*
|
|
123
|
+
* The returned children are the existing child nodes; they are not cloned.
|
|
124
|
+
*/
|
|
125
|
+
getChildren(node: OpenBranchTreeNode<Id, ChildNode>): Iterable<ChildNode>;
|
|
126
|
+
/**
|
|
127
|
+
* Returns the child with the supplied ID.
|
|
128
|
+
*
|
|
129
|
+
* Returns `undefined` when the branch has no child with that ID.
|
|
130
|
+
* The returned child is the existing node; it is not cloned.
|
|
131
|
+
*/
|
|
132
|
+
getChildById(node: OpenBranchTreeNode<Id, ChildNode>, id: Id): ChildNode | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* Applies `selector` to the node at `path`.
|
|
135
|
+
*
|
|
136
|
+
* The path must:
|
|
137
|
+
*
|
|
138
|
+
* - be non-empty;
|
|
139
|
+
* - begin at one of the supplied forest entries;
|
|
140
|
+
* - contain only IDs that identify existing entries;
|
|
141
|
+
* - not traverse through a leaf; and
|
|
142
|
+
* - not traverse through a closed branch.
|
|
143
|
+
*
|
|
144
|
+
* A branch at the end of the path is valid even when it is closed.
|
|
145
|
+
*
|
|
146
|
+
* The selector is called only after the complete path has been resolved.
|
|
147
|
+
* This method does not clone the tree or the selected node.
|
|
148
|
+
*
|
|
149
|
+
* Returns `undefined` when the path cannot be resolved, or when the
|
|
150
|
+
* selector returns `undefined`.
|
|
151
|
+
*/
|
|
152
|
+
getAtPath<Result>(entries: ChildNode[], path: Id[], selector: TreeNodeSelector<ChildNode, Result>): Result | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Immutably modifies the node at `path`.
|
|
155
|
+
*
|
|
156
|
+
* The modifier is called only after the complete path has been resolved.
|
|
157
|
+
* Its return value replaces the target node. Returning `undefined` aborts
|
|
158
|
+
* the modification.
|
|
159
|
+
*
|
|
160
|
+
* The operation creates new arrays and ancestor nodes along the path.
|
|
161
|
+
* Nodes and arrays unrelated to the path retain their original object
|
|
162
|
+
* identity. The input forest and its nodes (as objects) are never modified.
|
|
163
|
+
*
|
|
164
|
+
* The path must:
|
|
165
|
+
*
|
|
166
|
+
* - be non-empty;
|
|
167
|
+
* - begin at one of the supplied forest entries;
|
|
168
|
+
* - contain only IDs that identify existing entries;
|
|
169
|
+
* - not traverse through a leaf; and
|
|
170
|
+
* - not traverse through a closed branch.
|
|
171
|
+
*
|
|
172
|
+
* A leaf or branch, including a closed branch, may be the final target.
|
|
173
|
+
*
|
|
174
|
+
* Returns `undefined` when:
|
|
175
|
+
*
|
|
176
|
+
* - `path` is empty;
|
|
177
|
+
* - the path does not exist;
|
|
178
|
+
* - an intermediate node is a leaf;
|
|
179
|
+
* - an intermediate branch is closed; or
|
|
180
|
+
* - the modifier returns `undefined`.
|
|
181
|
+
*/
|
|
182
|
+
modifyAtPath(entries: ChildNode[], path: Id[], modifier: TreeNodeModifier<ChildNode>): ChildNode[] | undefined;
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=tree-node.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime schema for IDs that can be safely represented as serializable
|
|
4
|
+
* primitive values.
|
|
5
|
+
*
|
|
6
|
+
* Strings are accepted as-is. Numbers must be finite, so `NaN`, `Infinity`,
|
|
7
|
+
* and `-Infinity` are rejected.
|
|
8
|
+
*
|
|
9
|
+
* Applications should derive a more specific schema when those constraints
|
|
10
|
+
* are needed.
|
|
11
|
+
*/
|
|
12
|
+
export const serializableKeySchema = z.union([z.string(), z.number().finite()]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-to-effect-no-state.test.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/intent-to-effect-no-state.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
// import { describe, expect, it } from 'vitest';
|
|
3
|
+
//
|
|
4
|
+
// import type { PosixState } from '../../domain/posix-node.js';
|
|
5
|
+
// import { intentToEffect } from './intent-to-effect.js';
|
|
6
|
+
//
|
|
7
|
+
// const state = {} as PosixState;
|
|
8
|
+
//
|
|
9
|
+
// describe('intentToEffectNoState', () => {
|
|
10
|
+
// describe('normal mode buffer', () => {
|
|
11
|
+
// it('stores z as a possible command prefix', () => {
|
|
12
|
+
// expect(
|
|
13
|
+
// intentToEffect(
|
|
14
|
+
// {
|
|
15
|
+
// intentType: 'setNormalBuffer',
|
|
16
|
+
// normalBuffer: 'z',
|
|
17
|
+
// },
|
|
18
|
+
// state,
|
|
19
|
+
// ),
|
|
20
|
+
// ).toEqual({
|
|
21
|
+
// effectType: 'setInputState',
|
|
22
|
+
// inputState: {
|
|
23
|
+
// inputMode: 'normal',
|
|
24
|
+
// normalBuffer: 'z',
|
|
25
|
+
// },
|
|
26
|
+
// });
|
|
27
|
+
// });
|
|
28
|
+
//
|
|
29
|
+
// it.each([
|
|
30
|
+
// ['za', 'toggleFold'],
|
|
31
|
+
// ['zc', 'fold'],
|
|
32
|
+
// ['zo', 'unfold'],
|
|
33
|
+
// ] as const)(
|
|
34
|
+
// 'converts %s into a %s effect action',
|
|
35
|
+
// (normalBuffer, effectActionType) => {
|
|
36
|
+
// expect(
|
|
37
|
+
// intentToEffect(
|
|
38
|
+
// {
|
|
39
|
+
// intentType: 'setNormalBuffer',
|
|
40
|
+
// normalBuffer,
|
|
41
|
+
// },
|
|
42
|
+
// state,
|
|
43
|
+
// ),
|
|
44
|
+
// ).toEqual({
|
|
45
|
+
// effectType: 'dispatchEffectAction',
|
|
46
|
+
// action: {
|
|
47
|
+
// effectActionType,
|
|
48
|
+
// },
|
|
49
|
+
// });
|
|
50
|
+
// },
|
|
51
|
+
// );
|
|
52
|
+
//
|
|
53
|
+
// it('clears the normal buffer when the input is not a known command', () => {
|
|
54
|
+
// expect(
|
|
55
|
+
// intentToEffect(
|
|
56
|
+
// {
|
|
57
|
+
// intentType: 'setNormalBuffer',
|
|
58
|
+
// normalBuffer: 'x',
|
|
59
|
+
// },
|
|
60
|
+
// state,
|
|
61
|
+
// ),
|
|
62
|
+
// ).toEqual({
|
|
63
|
+
// effectType: 'setInputState',
|
|
64
|
+
// inputState: {
|
|
65
|
+
// inputMode: 'normal',
|
|
66
|
+
// normalBuffer: '',
|
|
67
|
+
// },
|
|
68
|
+
// });
|
|
69
|
+
// });
|
|
70
|
+
// });
|
|
71
|
+
//
|
|
72
|
+
// describe('normal mode navigation', () => {
|
|
73
|
+
// it('moves to the next entry', () => {
|
|
74
|
+
// expect(
|
|
75
|
+
// intentToEffect(
|
|
76
|
+
// {
|
|
77
|
+
// intentType: 'normalDown',
|
|
78
|
+
// },
|
|
79
|
+
// state,
|
|
80
|
+
// ),
|
|
81
|
+
// ).toEqual({
|
|
82
|
+
// effectType: 'dispatchEffectAction',
|
|
83
|
+
// action: {
|
|
84
|
+
// effectActionType: 'nextEntry',
|
|
85
|
+
// },
|
|
86
|
+
// });
|
|
87
|
+
// });
|
|
88
|
+
//
|
|
89
|
+
// it('moves to the previous entry', () => {
|
|
90
|
+
// expect(
|
|
91
|
+
// intentToEffect(
|
|
92
|
+
// {
|
|
93
|
+
// intentType: 'normalUp',
|
|
94
|
+
// },
|
|
95
|
+
// state,
|
|
96
|
+
// ),
|
|
97
|
+
// ).toEqual({
|
|
98
|
+
// effectType: 'dispatchEffectAction',
|
|
99
|
+
// action: {
|
|
100
|
+
// effectActionType: 'prevEntry',
|
|
101
|
+
// },
|
|
102
|
+
// });
|
|
103
|
+
// });
|
|
104
|
+
// });
|
|
105
|
+
//
|
|
106
|
+
// describe('command-line mode', () => {
|
|
107
|
+
// it('enters command-line mode', () => {
|
|
108
|
+
// expect(
|
|
109
|
+
// intentToEffect(
|
|
110
|
+
// {
|
|
111
|
+
// intentType: 'enterCommandLineMode',
|
|
112
|
+
// },
|
|
113
|
+
// state,
|
|
114
|
+
// ),
|
|
115
|
+
// ).toEqual({
|
|
116
|
+
// effectType: 'setInputState',
|
|
117
|
+
// inputState: {
|
|
118
|
+
// inputMode: 'command',
|
|
119
|
+
// commandLine: ':',
|
|
120
|
+
// },
|
|
121
|
+
// });
|
|
122
|
+
// });
|
|
123
|
+
//
|
|
124
|
+
// it('updates the command line', () => {
|
|
125
|
+
// expect(
|
|
126
|
+
// intentToEffect(
|
|
127
|
+
// {
|
|
128
|
+
// intentType: 'setCommandLine',
|
|
129
|
+
// commandLine: ':w',
|
|
130
|
+
// },
|
|
131
|
+
// state,
|
|
132
|
+
// ),
|
|
133
|
+
// ).toEqual({
|
|
134
|
+
// effectType: 'setInputState',
|
|
135
|
+
// inputState: {
|
|
136
|
+
// inputMode: 'command',
|
|
137
|
+
// commandLine: ':w',
|
|
138
|
+
// },
|
|
139
|
+
// });
|
|
140
|
+
// });
|
|
141
|
+
//
|
|
142
|
+
// it('exits command-line mode', () => {
|
|
143
|
+
// expect(
|
|
144
|
+
// intentToEffect(
|
|
145
|
+
// {
|
|
146
|
+
// intentType: 'exitCommandLineMode',
|
|
147
|
+
// },
|
|
148
|
+
// state,
|
|
149
|
+
// ),
|
|
150
|
+
// ).toEqual({
|
|
151
|
+
// effectType: 'setInputState',
|
|
152
|
+
// inputState: {
|
|
153
|
+
// inputMode: 'normal',
|
|
154
|
+
// normalBuffer: '',
|
|
155
|
+
// },
|
|
156
|
+
// });
|
|
157
|
+
// });
|
|
158
|
+
//
|
|
159
|
+
// it('returns to normal mode after executing a gibberish command line', () => {
|
|
160
|
+
// expect(
|
|
161
|
+
// intentToEffect(
|
|
162
|
+
// {
|
|
163
|
+
// intentType: 'executeCommandLine',
|
|
164
|
+
// commandLine: ':sijbas',
|
|
165
|
+
// },
|
|
166
|
+
// state,
|
|
167
|
+
// ),
|
|
168
|
+
// ).toEqual({
|
|
169
|
+
// effectType: 'setInputState',
|
|
170
|
+
// inputState: {
|
|
171
|
+
// inputMode: 'normal',
|
|
172
|
+
// normalBuffer: '',
|
|
173
|
+
// },
|
|
174
|
+
// });
|
|
175
|
+
// });
|
|
176
|
+
//
|
|
177
|
+
// it('quits when the :q command is executed', () => {
|
|
178
|
+
// expect(
|
|
179
|
+
// intentToEffect(
|
|
180
|
+
// {
|
|
181
|
+
// intentType: 'executeCommandLine',
|
|
182
|
+
// commandLine: ':q',
|
|
183
|
+
// },
|
|
184
|
+
// state,
|
|
185
|
+
// ),
|
|
186
|
+
// ).toEqual({
|
|
187
|
+
// effectType: 'quit',
|
|
188
|
+
// exitMessage: '',
|
|
189
|
+
// });
|
|
190
|
+
// });
|
|
191
|
+
// });
|
|
192
|
+
// });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { SerializableKey, TreeNode, TreeNodeApi } from '../../tree-surfer/tree-node/tree-node.types.js';
|
|
2
|
+
import { Effect, Intent } from '../domain/index.js';
|
|
3
|
+
import { CursorApi, State, StateApi } from '../../tree-surfer/index.js';
|
|
4
|
+
export type IntentToEffect<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = (intent: Intent, state: State<Id, BufferNode>) => Effect<Id, BufferNode> | undefined;
|
|
5
|
+
export declare function createIntentToEffect<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(stateApi: StateApi<Id, BufferNode>, cursorApi: CursorApi<Id>, treeNodeApi: TreeNodeApi<Id, BufferNode>): IntentToEffect<Id, BufferNode>;
|
|
6
|
+
//# sourceMappingURL=intent-to-effect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-to-effect.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/intent-to-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,gDAAgD,CAAC;AACxG,OAAO,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAGxE,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,IACzC,CACF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,KACzB,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC;AAGxC,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAE3C,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAClC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,UAAU,CAAC,GACvC,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,CAyEhC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Factory method, since Apis are needed to derive intent
|
|
2
|
+
export function createIntentToEffect(stateApi, cursorApi, treeNodeApi) {
|
|
3
|
+
return (intent, state) => {
|
|
4
|
+
switch (intent.intentType) {
|
|
5
|
+
case 'setNormalBuffer':
|
|
6
|
+
return normalBufferToEffectResult(intent.normalBuffer);
|
|
7
|
+
case 'normalRight':
|
|
8
|
+
return normalInteractRightToEffect(state, stateApi, cursorApi, treeNodeApi);
|
|
9
|
+
case 'normalLeft':
|
|
10
|
+
return normalInteractLeftToEffect(state);
|
|
11
|
+
case 'normalDown':
|
|
12
|
+
return dispatchAction({
|
|
13
|
+
effectActionType: 'nextEntry',
|
|
14
|
+
});
|
|
15
|
+
case 'normalUp':
|
|
16
|
+
return dispatchAction({
|
|
17
|
+
effectActionType: 'prevEntry',
|
|
18
|
+
});
|
|
19
|
+
case 'enterCommandLineMode':
|
|
20
|
+
return {
|
|
21
|
+
effectType: 'setInputState',
|
|
22
|
+
inputState: {
|
|
23
|
+
inputMode: 'command',
|
|
24
|
+
commandLine: ':',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
case 'setCommandLine':
|
|
28
|
+
return {
|
|
29
|
+
effectType: 'setInputState',
|
|
30
|
+
inputState: {
|
|
31
|
+
inputMode: 'command',
|
|
32
|
+
commandLine: intent.commandLine,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
case 'exitCommandLineMode':
|
|
36
|
+
return {
|
|
37
|
+
effectType: 'setInputState',
|
|
38
|
+
inputState: {
|
|
39
|
+
inputMode: 'normal',
|
|
40
|
+
normalBuffer: '',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
case 'executeCommandLine':
|
|
44
|
+
if (intent.commandLine === ':q') {
|
|
45
|
+
return {
|
|
46
|
+
effectType: 'quit',
|
|
47
|
+
exitMessage: '',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
effectType: 'setInputState',
|
|
52
|
+
inputState: {
|
|
53
|
+
inputMode: 'normal',
|
|
54
|
+
normalBuffer: '',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function dispatchAction(action) {
|
|
61
|
+
return {
|
|
62
|
+
effectType: 'dispatchEffectAction',
|
|
63
|
+
action,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Sets the buffer. Three possibilities:
|
|
67
|
+
// - New command buffer is recognized as a motion, this dispatches the
|
|
68
|
+
// "effect action". Consumer will know to reset the input state.
|
|
69
|
+
// - Prefix of a possible motion, updates normal buffer.
|
|
70
|
+
// - Not a prefix, clears the buffer.
|
|
71
|
+
function normalBufferToEffectResult(updatedNormalBuffer) {
|
|
72
|
+
switch (updatedNormalBuffer) {
|
|
73
|
+
case 'z':
|
|
74
|
+
return {
|
|
75
|
+
effectType: 'setInputState',
|
|
76
|
+
inputState: {
|
|
77
|
+
inputMode: 'normal',
|
|
78
|
+
normalBuffer: updatedNormalBuffer,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
case 'za':
|
|
82
|
+
return dispatchAction({
|
|
83
|
+
effectActionType: 'toggleFold',
|
|
84
|
+
});
|
|
85
|
+
case 'zc':
|
|
86
|
+
return dispatchAction({
|
|
87
|
+
effectActionType: 'fold',
|
|
88
|
+
});
|
|
89
|
+
case 'zo':
|
|
90
|
+
return dispatchAction({
|
|
91
|
+
effectActionType: 'unfold',
|
|
92
|
+
});
|
|
93
|
+
default:
|
|
94
|
+
return {
|
|
95
|
+
effectType: 'setInputState',
|
|
96
|
+
inputState: {
|
|
97
|
+
inputMode: 'normal',
|
|
98
|
+
normalBuffer: '',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function normalInteractRightToEffect(state, stateApi, cursorApi, treeNodeApi) {
|
|
104
|
+
const currentEntry = stateApi.getNodeAtCursor(state);
|
|
105
|
+
if (currentEntry === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const path = cursorApi.getPath(state.cursor);
|
|
109
|
+
if (path === undefined) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (treeNodeApi.isBranch(currentEntry)) {
|
|
113
|
+
if (currentEntry.children === null) {
|
|
114
|
+
return {
|
|
115
|
+
effectType: 'loadBranchEntries',
|
|
116
|
+
path,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return dispatchAction({
|
|
120
|
+
effectActionType: 'setBranchEntries',
|
|
121
|
+
path,
|
|
122
|
+
entries: null,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
effectType: 'emitPath',
|
|
127
|
+
path,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function normalInteractLeftToEffect(state) {
|
|
131
|
+
if (state.cursor.parentPath.length === 0) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return dispatchAction({
|
|
135
|
+
effectActionType: 'navigateToParent',
|
|
136
|
+
});
|
|
137
|
+
}
|