dirvi-lib 0.7.0 → 0.8.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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tree-surfer/cursor.d.ts +52 -26
- package/dist/tree-surfer/cursor.d.ts.map +1 -1
- package/dist/tree-surfer/cursor.js +19 -44
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
- package/dist/tree-surfer/index.d.ts +3 -1
- package/dist/tree-surfer/index.d.ts.map +1 -1
- package/dist/tree-surfer/index.js +3 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
- package/dist/tree-surfer/state/state.impl.d.ts +7 -0
- package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.impl.js +89 -0
- package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
- package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.schemas.js +8 -0
- package/dist/tree-surfer/state/state.types.d.ts +21 -0
- package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.types.js +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
- package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
- package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.js +1 -1
- package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
- package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
- package/package.json +3 -2
- package/dist/tree-surfer/state.d.ts +0 -25
- package/dist/tree-surfer/state.d.ts.map +0 -1
- package/dist/tree-surfer/state.js +0 -118
- package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
- /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
|
@@ -1,117 +1,185 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import * as fc from 'fast-check';
|
|
3
3
|
import { createTreeNodeApi } from './tree-node.impl.js';
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
describe('
|
|
7
|
-
it('
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { newBranchesOrNullArb, nodesArrayAndBranchPathArb, nodeAndPathAndExpectedArb, } from './tree-node.arb.js';
|
|
5
|
+
const api = createTreeNodeApi();
|
|
6
|
+
describe('getChildById', () => {
|
|
7
|
+
it('UT: returns the matching direct child', () => {
|
|
8
|
+
const child = { id: 'child' };
|
|
9
|
+
const root = {
|
|
10
|
+
id: 'root',
|
|
11
|
+
children: [child],
|
|
12
|
+
};
|
|
13
|
+
expect(api.getChildById(root, 'child')).toBe(child);
|
|
14
|
+
});
|
|
15
|
+
it('UT: returns undefined for a missing child', () => {
|
|
16
|
+
const root = {
|
|
17
|
+
id: 'root',
|
|
18
|
+
children: [],
|
|
19
|
+
};
|
|
20
|
+
expect(api.getChildById(root, 'missing')).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('getAtPath', () => {
|
|
24
|
+
// Using `selector = vi.fn...` here is too heavy
|
|
25
|
+
it('applies selector to the node at a valid path', () => {
|
|
26
|
+
fc.assert(fc.property(nodeAndPathAndExpectedArb, ({ root, path, expected }) => {
|
|
27
|
+
expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
|
|
28
|
+
expect(api.getAtPath(root, path, (node) => node.id)).toBe(expected.id);
|
|
10
29
|
}));
|
|
11
30
|
});
|
|
12
|
-
it('
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
expect(
|
|
31
|
+
it('UT: calls the selector once with the resolved node', () => {
|
|
32
|
+
const child = { id: 'child' };
|
|
33
|
+
const root = {
|
|
34
|
+
id: 'root',
|
|
35
|
+
children: [child],
|
|
36
|
+
};
|
|
37
|
+
const selector = vi.fn((node) => node.id);
|
|
38
|
+
expect(api.getAtPath(root, ['child'], selector)).toBe('child');
|
|
39
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
40
|
+
expect(selector).toHaveBeenCalledWith(child);
|
|
20
41
|
});
|
|
21
|
-
it('
|
|
22
|
-
const
|
|
23
|
-
|
|
42
|
+
it('UT: returns undefined for an invalid root path without calling the selector', () => {
|
|
43
|
+
const selector = vi.fn((node) => node.id);
|
|
44
|
+
const result = api.getAtPath([{ id: 'root' }], ['missing'], selector);
|
|
45
|
+
expect(result).toBeUndefined();
|
|
46
|
+
expect(selector).not.toHaveBeenCalled();
|
|
24
47
|
});
|
|
25
|
-
it('
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
];
|
|
32
|
-
expect(
|
|
48
|
+
it('UT: returns undefined when a child is missing from an open branch', () => {
|
|
49
|
+
const root = {
|
|
50
|
+
id: 'root',
|
|
51
|
+
children: [{ id: 'child' }],
|
|
52
|
+
};
|
|
53
|
+
const selector = vi.fn((node) => node.id);
|
|
54
|
+
const result = api.getAtPath(root, ['root', 'missing'], selector);
|
|
55
|
+
expect(result).toBeUndefined();
|
|
56
|
+
expect(selector).not.toHaveBeenCalled();
|
|
33
57
|
});
|
|
34
|
-
it('
|
|
35
|
-
const
|
|
36
|
-
|
|
58
|
+
it('UT: resolves the root for an empty path', () => {
|
|
59
|
+
const root = { id: 'root' };
|
|
60
|
+
const selector = vi.fn((node) => node.id);
|
|
61
|
+
expect(api.getAtPath(root, [], selector)).toBe('root');
|
|
62
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
63
|
+
expect(selector).toHaveBeenCalledWith(root);
|
|
37
64
|
});
|
|
38
|
-
it('
|
|
39
|
-
const child = { id: 'child' };
|
|
65
|
+
it('UT: returns undefined through a closed branch without calling the selector', () => {
|
|
40
66
|
const root = {
|
|
41
67
|
id: 'root',
|
|
42
|
-
children:
|
|
68
|
+
children: null,
|
|
43
69
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
const selector = vi.fn((node) => node.id);
|
|
71
|
+
const result = api.getAtPath(root, ['child'], selector);
|
|
72
|
+
expect(result).toBeUndefined();
|
|
73
|
+
expect(selector).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('UT: returns undefined when the selector returns undefined', () => {
|
|
76
|
+
const selector = vi.fn((_node) => undefined);
|
|
77
|
+
const result = api.getAtPath({ id: 'root' }, [], selector);
|
|
78
|
+
expect(result).toBeUndefined();
|
|
79
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
80
|
+
expect(selector).toHaveBeenCalledWith({ id: 'root' });
|
|
50
81
|
});
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
});
|
|
83
|
+
describe('modifyAtPath', () => {
|
|
84
|
+
it('replaces the generated node', () => {
|
|
85
|
+
fc.assert(fc.property(nodeAndPathAndExpectedArb, ({ root, path, expected }) => {
|
|
53
86
|
const replacement = {
|
|
54
87
|
id: expected.id,
|
|
55
88
|
};
|
|
56
|
-
const
|
|
57
|
-
expect(
|
|
58
|
-
if (
|
|
89
|
+
const updatedRoot = api.modifyAtPath(root, path, () => replacement);
|
|
90
|
+
expect(updatedRoot).not.toBeUndefined();
|
|
91
|
+
if (updatedRoot === undefined) {
|
|
59
92
|
return;
|
|
60
93
|
}
|
|
61
|
-
expect(
|
|
62
|
-
expect(
|
|
94
|
+
expect(api.getAtPath(updatedRoot, path, (node) => node)).toBe(replacement);
|
|
95
|
+
expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
|
|
63
96
|
}));
|
|
64
|
-
});
|
|
65
|
-
it('
|
|
66
|
-
fc.assert(fc.property(nodesArrayAndBranchPathArb, newBranchesOrNullArb, ({
|
|
67
|
-
expect(
|
|
68
|
-
const
|
|
97
|
+
}, 12000);
|
|
98
|
+
it('replaces children at a branch path', () => {
|
|
99
|
+
fc.assert(fc.property(nodesArrayAndBranchPathArb, newBranchesOrNullArb, ({ root, path, expected }, newBranches) => {
|
|
100
|
+
expect(api.isBranch(expected)).toBe(true);
|
|
101
|
+
const updatedRoot = api.modifyAtPath(root, path, (node) => ({
|
|
69
102
|
...node,
|
|
70
103
|
children: newBranches,
|
|
71
104
|
}));
|
|
72
|
-
expect(
|
|
73
|
-
if (
|
|
105
|
+
expect(updatedRoot).not.toBeUndefined();
|
|
106
|
+
if (updatedRoot === undefined) {
|
|
74
107
|
return;
|
|
75
108
|
}
|
|
76
|
-
const updatedNode =
|
|
109
|
+
const updatedNode = api.getAtPath(updatedRoot, path, (node) => node);
|
|
77
110
|
expect(updatedNode).not.toBeUndefined();
|
|
78
111
|
expect(updatedNode).not.toBe(expected);
|
|
79
112
|
if (updatedNode === undefined ||
|
|
80
|
-
!
|
|
81
|
-
(!
|
|
113
|
+
!api.isBranch(updatedNode) ||
|
|
114
|
+
(!api.isOpenBranch(updatedNode) && newBranches !== null)) {
|
|
82
115
|
return;
|
|
83
116
|
}
|
|
84
|
-
if (
|
|
85
|
-
expect([...
|
|
117
|
+
if (api.isOpenBranch(updatedNode)) {
|
|
118
|
+
expect([...api.getChildren(updatedNode)]).toStrictEqual(newBranches);
|
|
86
119
|
}
|
|
87
|
-
expect(
|
|
120
|
+
expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
|
|
88
121
|
}));
|
|
89
122
|
});
|
|
90
|
-
it('
|
|
91
|
-
const
|
|
92
|
-
|
|
123
|
+
it('UT: calls the modifier once with the resolved node', () => {
|
|
124
|
+
const child = { id: 'child' };
|
|
125
|
+
const root = {
|
|
126
|
+
id: 'root',
|
|
127
|
+
children: [child],
|
|
128
|
+
};
|
|
129
|
+
const replacement = { id: 'replacement' };
|
|
130
|
+
const modifier = vi.fn(() => replacement);
|
|
131
|
+
const updatedRoot = api.modifyAtPath(root, ['child'], modifier);
|
|
132
|
+
expect(updatedRoot).toBeDefined();
|
|
133
|
+
expect(modifier).toHaveBeenCalledOnce();
|
|
134
|
+
expect(modifier).toHaveBeenCalledWith(child);
|
|
135
|
+
expect(api.getAtPath(updatedRoot, ['replacement'], (node) => node)).toBe(replacement);
|
|
136
|
+
expect(api.getAtPath(updatedRoot, ['child'], (node) => node)).toBeUndefined();
|
|
93
137
|
});
|
|
94
|
-
it('
|
|
95
|
-
const
|
|
96
|
-
|
|
138
|
+
it('UT: modifies the root for an empty path', () => {
|
|
139
|
+
const root = { id: 'root' };
|
|
140
|
+
const replacement = { id: 'replacement' };
|
|
141
|
+
const modifier = vi.fn(() => replacement);
|
|
142
|
+
const updatedRoot = api.modifyAtPath(root, [], modifier);
|
|
143
|
+
expect(updatedRoot).toBe(replacement);
|
|
144
|
+
expect(modifier).toHaveBeenCalledOnce();
|
|
145
|
+
expect(modifier).toHaveBeenCalledWith(root);
|
|
97
146
|
});
|
|
98
|
-
it(
|
|
99
|
-
|
|
100
|
-
|
|
147
|
+
it.each([
|
|
148
|
+
{
|
|
149
|
+
name: 'a missing child',
|
|
150
|
+
root: {
|
|
101
151
|
id: 'root',
|
|
102
152
|
children: [],
|
|
103
153
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
154
|
+
path: ['missing'],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'a path through a leaf',
|
|
158
|
+
root: {
|
|
159
|
+
id: 'root',
|
|
160
|
+
},
|
|
161
|
+
path: ['child'],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'a path through a closed branch',
|
|
165
|
+
root: {
|
|
111
166
|
id: 'root',
|
|
112
167
|
children: null,
|
|
113
168
|
},
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
path: ['child'],
|
|
170
|
+
},
|
|
171
|
+
])('UT: returns undefined and does not call the modifier', ({ root, path }) => {
|
|
172
|
+
const modifier = vi.fn((node) => node);
|
|
173
|
+
const result = api.modifyAtPath(root, path, modifier);
|
|
174
|
+
expect(result).toBeUndefined();
|
|
175
|
+
expect(modifier).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
it('UT: returns undefined when the modifier aborts', () => {
|
|
178
|
+
const root = { id: 'root' };
|
|
179
|
+
const modifier = vi.fn(() => undefined);
|
|
180
|
+
const result = api.modifyAtPath(root, [], modifier);
|
|
181
|
+
expect(result).toBeUndefined();
|
|
182
|
+
expect(modifier).toHaveBeenCalledOnce();
|
|
183
|
+
expect(modifier).toHaveBeenCalledWith(root);
|
|
116
184
|
});
|
|
117
185
|
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A serializable primitive suitable for use as a stable tree ID.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
4
|
+
* @remarks
|
|
5
|
+
*
|
|
6
|
+
* Numeric IDs must be finite. TypeScript cannot represent the exclusion of
|
|
7
|
+
* `NaN` and infinities from `number`, so values from untrusted sources should
|
|
8
|
+
* be validated with `serializableKeySchema`.
|
|
8
9
|
*
|
|
9
10
|
* For application-specific IDs, define a narrower branded schema and derive
|
|
10
|
-
* its type with `z.output
|
|
11
|
+
* its type with `z.output`:
|
|
11
12
|
*
|
|
12
13
|
* @example
|
|
13
14
|
*
|
|
@@ -27,146 +28,127 @@
|
|
|
27
28
|
*/
|
|
28
29
|
export type SerializableKey = string | number;
|
|
29
30
|
/**
|
|
30
|
-
* A node
|
|
31
|
+
* A node in a tree.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
*
|
|
35
|
+
* A node's `id` identifies it among its siblings.
|
|
36
|
+
*
|
|
37
|
+
* Nodes are discriminated by the shape of their `children` property:
|
|
38
|
+
*
|
|
39
|
+
* - a leaf has no children and does not have a child collection;
|
|
40
|
+
* - an open branch has a loaded array of child nodes;
|
|
41
|
+
* - a closed branch has children that have not been loaded yet and uses
|
|
42
|
+
* `children: null`.
|
|
43
|
+
*
|
|
44
|
+
* `Node` is the concrete application-specific node type. It may contain
|
|
45
|
+
* additional properties, provided that it preserves the leaf-or-branch shape
|
|
46
|
+
* described by this type.
|
|
47
|
+
*
|
|
48
|
+
* The recursive `Node` parameter allows application-specific properties
|
|
49
|
+
* to be available on every descendant.
|
|
50
|
+
*
|
|
51
|
+
* @typeParam Id - The type of node IDs.
|
|
52
|
+
* @typeParam Node - The application-specific recursive node type.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* type DirectoryEntry = TreeNode<string, DirectoryEntry> & {
|
|
58
|
+
* kind: 'file' | 'directory';
|
|
59
|
+
* };
|
|
60
|
+
* ```
|
|
31
61
|
*/
|
|
62
|
+
export type TreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = LeafTreeNode<Id> | BranchTreeNode<Id, Node>;
|
|
63
|
+
/** A node with no children. */
|
|
32
64
|
export type LeafTreeNode<Id extends SerializableKey> = {
|
|
33
65
|
id: Id;
|
|
34
66
|
children?: never;
|
|
35
67
|
};
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
*/
|
|
39
|
-
export type
|
|
68
|
+
/** A tree node that has children, either loaded or unloaded. */
|
|
69
|
+
export type BranchTreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = OpenBranchTreeNode<Id, Node> | ClosedBranchTreeNode<Id>;
|
|
70
|
+
/** A branch whose direct children have been loaded. */
|
|
71
|
+
export type OpenBranchTreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
|
|
40
72
|
id: Id;
|
|
41
|
-
children:
|
|
73
|
+
children: Node[];
|
|
42
74
|
};
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
* TODO Later: Add a dictionary by ID for faster lookup.
|
|
47
|
-
* Then children will just be an array for keeping order.
|
|
48
|
-
*/
|
|
49
|
-
export type OpenBranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = {
|
|
75
|
+
/** A branch whose direct children have not been loaded. */
|
|
76
|
+
export type ClosedBranchTreeNode<Id extends SerializableKey> = {
|
|
50
77
|
id: Id;
|
|
51
|
-
children:
|
|
78
|
+
children: null;
|
|
52
79
|
};
|
|
53
80
|
/**
|
|
54
|
-
*
|
|
55
|
-
*/
|
|
56
|
-
export type BranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = OpenBranchTreeNode<Id, ChildNode> | ClosedBranchTreeNode<Id>;
|
|
57
|
-
/**
|
|
58
|
-
* A node in a tree.
|
|
59
|
-
*
|
|
60
|
-
* `ChildNode` is the concrete application-specific node type. It may contain
|
|
61
|
-
* additional properties, provided that it has the shape of either a leaf or a
|
|
62
|
-
* branch.
|
|
63
|
-
*/
|
|
64
|
-
export type TreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = LeafTreeNode<Id> | BranchTreeNode<Id, ChildNode>;
|
|
65
|
-
/**
|
|
66
|
-
* Selects a value from a node reached by a path.
|
|
67
|
-
*
|
|
68
|
-
* The selector is called only when the complete path exists and identifies
|
|
69
|
-
* a node. Returning `undefined` from the selector is allowed, but is
|
|
70
|
-
* indistinguishable from an unsuccessful lookup in the return value.
|
|
71
|
-
*/
|
|
72
|
-
export type TreeNodeSelector<ChildNode, Result> = (node: ChildNode) => Result;
|
|
73
|
-
/**
|
|
74
|
-
* Produces a replacement for a node reached by a path.
|
|
81
|
+
* Operations for inspecting and immutably updating a tree rooted at a node.
|
|
75
82
|
*
|
|
76
|
-
*
|
|
77
|
-
* to return `undefined`.
|
|
78
|
-
*/
|
|
79
|
-
export type TreeNodeModifier<ChildNode> = (node: ChildNode) => ChildNode | undefined;
|
|
80
|
-
/**
|
|
81
|
-
* Fundamental operations for inspecting and immutably updating a tree forest.
|
|
83
|
+
* @remarks
|
|
82
84
|
*
|
|
83
|
-
* A
|
|
84
|
-
* of IDs beginning at a root node and continuing through its descendants.
|
|
85
|
+
* A path is an array of IDs relative to the supplied root node.
|
|
85
86
|
*
|
|
86
87
|
* For example, given tree with IDs:
|
|
87
88
|
*
|
|
88
89
|
* root
|
|
89
90
|
* └── child
|
|
90
91
|
*
|
|
91
|
-
* the path to `child` is `['
|
|
92
|
+
* the path to `root` is `[]`, and the path to `child` is `['child']`.
|
|
92
93
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* iterate over children themselves.
|
|
94
|
+
* Paths cannot pass through leaves or closed branches. A closed branch may be
|
|
95
|
+
* the final node in a path. An empty path identifies the root node.
|
|
96
96
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
97
|
+
* @typeParam Id - The type of node IDs.
|
|
98
|
+
* @typeParam Node - The application-specific recursive node type.
|
|
99
99
|
*/
|
|
100
|
-
export type TreeNodeApi<Id extends SerializableKey,
|
|
101
|
-
isLeaf(node:
|
|
102
|
-
isBranch(node:
|
|
103
|
-
isClosedBranch(node: BranchTreeNode<Id,
|
|
104
|
-
isOpenBranch(node: BranchTreeNode<Id,
|
|
100
|
+
export type TreeNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
|
|
101
|
+
isLeaf(node: Node): node is Node & LeafTreeNode<Id>;
|
|
102
|
+
isBranch(node: Node): node is Node & BranchTreeNode<Id, Node>;
|
|
103
|
+
isClosedBranch(node: BranchTreeNode<Id, Node>): node is ClosedBranchTreeNode<Id>;
|
|
104
|
+
isOpenBranch(node: BranchTreeNode<Id, Node>): node is OpenBranchTreeNode<Id, Node>;
|
|
105
105
|
/**
|
|
106
|
-
* Returns the loaded children of an open branch.
|
|
106
|
+
* Returns the loaded direct children of an open branch.
|
|
107
107
|
*
|
|
108
|
-
* The
|
|
109
|
-
*
|
|
108
|
+
* The children are returned in sibling order. The returned nodes are the
|
|
109
|
+
* existing nodes and are not cloned.
|
|
110
110
|
*
|
|
111
|
-
*
|
|
111
|
+
* @param node - An open branch.
|
|
112
|
+
* @returns The branch's loaded children.
|
|
112
113
|
*/
|
|
113
|
-
getChildren(node: OpenBranchTreeNode<Id,
|
|
114
|
+
getChildren(node: OpenBranchTreeNode<Id, Node>): Iterable<Node>;
|
|
114
115
|
/**
|
|
115
|
-
*
|
|
116
|
+
* Finds a direct child by ID.
|
|
116
117
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
118
|
+
* @param node - An open branch.
|
|
119
|
+
* @param id - The ID of the child to find.
|
|
120
|
+
* @returns The existing child, or `undefined` when no matching child exists.
|
|
119
121
|
*/
|
|
120
|
-
getChildById(node: OpenBranchTreeNode<Id,
|
|
122
|
+
getChildById(node: OpenBranchTreeNode<Id, Node>, id: Id): Node | undefined;
|
|
121
123
|
/**
|
|
122
|
-
* Applies
|
|
123
|
-
*
|
|
124
|
-
* The path must:
|
|
125
|
-
*
|
|
126
|
-
* - be non-empty;
|
|
127
|
-
* - begin at one of the supplied forest entries;
|
|
128
|
-
* - contain only IDs that identify existing entries;
|
|
129
|
-
* - not traverse through a leaf; and
|
|
130
|
-
* - not traverse through a closed branch.
|
|
131
|
-
*
|
|
132
|
-
* A branch at the end of the path is valid even when it is closed.
|
|
124
|
+
* Applies a selector to the node at a path relative to `root`.
|
|
133
125
|
*
|
|
134
126
|
* The selector is called only after the complete path has been resolved.
|
|
135
|
-
*
|
|
127
|
+
* The selected node is not cloned.
|
|
136
128
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
129
|
+
* @param root - The node searching through.
|
|
130
|
+
* @param path - Path relative to root, with empty selecting the root.
|
|
131
|
+
* @param selector - The function to apply to the resolved node.
|
|
132
|
+
* @returns The selector result, or `undefined` when the path cannot be
|
|
133
|
+
* resolved or the selector returns `undefined`.
|
|
139
134
|
*/
|
|
140
|
-
getAtPath<Result>(
|
|
135
|
+
getAtPath<Result>(root: Node, path: readonly Id[], selector: (node: Node) => Result): Result | undefined;
|
|
141
136
|
/**
|
|
142
|
-
* Immutably
|
|
137
|
+
* Immutably replaces the node at a path relative to `root`.
|
|
143
138
|
*
|
|
144
139
|
* The modifier is called only after the complete path has been resolved.
|
|
145
|
-
*
|
|
146
|
-
* the modification.
|
|
147
|
-
*
|
|
148
|
-
* The operation creates new arrays and ancestor nodes along the path.
|
|
149
|
-
* Nodes and arrays unrelated to the path retain their original object
|
|
150
|
-
* identity. The input forest and its nodes (as objects) are never modified.
|
|
151
|
-
*
|
|
152
|
-
* The path must:
|
|
153
|
-
*
|
|
154
|
-
* - be non-empty;
|
|
155
|
-
* - begin at one of the supplied forest entries;
|
|
156
|
-
* - contain only IDs that identify existing entries;
|
|
157
|
-
* - not traverse through a leaf; and
|
|
158
|
-
* - not traverse through a closed branch.
|
|
159
|
-
*
|
|
160
|
-
* A leaf or branch, including a closed branch, may be the final target.
|
|
140
|
+
* Returning `undefined` aborts the modification.
|
|
161
141
|
*
|
|
162
|
-
*
|
|
142
|
+
* New arrays and ancestor nodes are created along the modified path.
|
|
143
|
+
* Unrelated nodes and arrays retain their original object identity. The
|
|
144
|
+
* input array is never modified.
|
|
163
145
|
*
|
|
164
|
-
* -
|
|
165
|
-
* -
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
146
|
+
* @param root - The root node to modify.
|
|
147
|
+
* @param path - A path relative to root. An empty path selects the root.
|
|
148
|
+
* @param modifier - Produces the replacement node.
|
|
149
|
+
* @returns A new root, or `undefined` when the path cannot be resolved or
|
|
150
|
+
* the modifier returns `undefined`.
|
|
169
151
|
*/
|
|
170
|
-
modifyAtPath(
|
|
152
|
+
modifyAtPath(root: Node, path: readonly Id[], modifier: (node: Node) => Node | undefined): Node | undefined;
|
|
171
153
|
};
|
|
172
154
|
//# sourceMappingURL=tree-node.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,QAAQ,CAClB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAEhD,+BAA+B;AAC/B,MAAM,MAAM,YAAY,CAAC,EAAE,SAAS,eAAe,IAAI;IACrD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;AAK5D,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,CAC5B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,EAAE,CAAC;CAClB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,oBAAoB,CAAC,EAAE,SAAS,eAAe,IAAI;IAC7D,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,WAAW,CACrB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9D,cAAc,CACZ,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACpC,YAAY,CACV,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAExC;;;;;;;;OAQG;IACH,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAE3E;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,MAAM,EACd,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAC/B,MAAM,GAAG,SAAS,CAAC;IAEtB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CACV,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,SAAS,GACzC,IAAI,GAAG,SAAS,CAAC;CACrB,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { SerializableKey, TreeNode, TreeNodeApi } from '../../tree-surfer/index.js';
|
|
2
2
|
import { Effect, Intent } from '../domain/index.js';
|
|
3
3
|
import { CursorApi, State, StateApi } from '../../tree-surfer/index.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* By design "effect = intent + state", so it awaits based on the intent and
|
|
6
|
+
* the current state whether an effect should be executed.
|
|
7
|
+
*/
|
|
8
|
+
export type IntentToEffect<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = (intent: Intent, state: State<Id, Node>) => Effect<Id, Node> | undefined;
|
|
9
|
+
export declare function createIntentToEffect<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(stateApi: StateApi<Id, Node>, cursorApi: CursorApi<Id>, treeNodeApi: TreeNodeApi<Id, Node>): IntentToEffect<Id, Node>;
|
|
6
10
|
//# sourceMappingURL=intent-to-effect.d.ts.map
|
|
@@ -1 +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,EACL,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"intent-to-effect.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/intent-to-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAGxE;;;GAGG;AACH,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,CACF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,KACnB,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC;AAElC,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAE/B,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,GACjC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAmE1B"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { parseCommand } from './parse-command.js';
|
|
2
|
-
// Factory method, since Apis are needed to derive intent
|
|
3
2
|
export function createIntentToEffect(stateApi, cursorApi, treeNodeApi) {
|
|
4
3
|
return (intent, state) => {
|
|
5
4
|
switch (intent.intentType) {
|
|
@@ -73,10 +72,6 @@ function normalBufferToEffectResult(updatedNormalBuffer) {
|
|
|
73
72
|
normalBuffer: updatedNormalBuffer,
|
|
74
73
|
},
|
|
75
74
|
};
|
|
76
|
-
case 'za':
|
|
77
|
-
return dispatchAction({
|
|
78
|
-
effectActionType: 'toggleFold',
|
|
79
|
-
});
|
|
80
75
|
case 'zc':
|
|
81
76
|
return dispatchAction({
|
|
82
77
|
effectActionType: 'fold',
|
|
@@ -120,7 +115,7 @@ function normalInteractRightToEffect(state, stateApi, cursorApi, treeNodeApi) {
|
|
|
120
115
|
return;
|
|
121
116
|
}
|
|
122
117
|
function normalInteractLeftToEffect(state) {
|
|
123
|
-
if (state.cursor.
|
|
118
|
+
if (state.cursor.length === 0) {
|
|
124
119
|
return undefined;
|
|
125
120
|
}
|
|
126
121
|
return dispatchAction({
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import { UserInput,
|
|
2
|
-
|
|
1
|
+
import { UserInput, InputModeState, Intent } from '../domain/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* By design "intent = input + mode + modeState", so it interprets the key
|
|
4
|
+
* inputs depending on the mode and input so far. No context in terms of UI.
|
|
5
|
+
*
|
|
6
|
+
* @param userInput - the key user input.
|
|
7
|
+
* @param inputModeState - the current mode and the user inputs so far.
|
|
8
|
+
* @returns The user's intent.
|
|
9
|
+
*/
|
|
10
|
+
export declare function userInputToIntent(userInput: UserInput, inputModeState: InputModeState): Intent | undefined;
|
|
3
11
|
//# sourceMappingURL=user-input-to-intent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-input-to-intent.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/user-input-to-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"user-input-to-intent.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/user-input-to-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,cAAc,GAC7B,MAAM,GAAG,SAAS,CAQpB"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* By design "intent = input + mode + modeState", so it interprets the key
|
|
3
|
+
* inputs depending on the mode and input so far. No context in terms of UI.
|
|
4
|
+
*
|
|
5
|
+
* @param userInput - the key user input.
|
|
6
|
+
* @param inputModeState - the current mode and the user inputs so far.
|
|
7
|
+
* @returns The user's intent.
|
|
8
|
+
*/
|
|
9
|
+
export function userInputToIntent(userInput, inputModeState) {
|
|
10
|
+
switch (inputModeState.inputMode) {
|
|
4
11
|
case 'normal':
|
|
5
|
-
return normalUserInputToIntent(userInput,
|
|
12
|
+
return normalUserInputToIntent(userInput, inputModeState.normalBuffer);
|
|
6
13
|
case 'command':
|
|
7
|
-
return commandUserInputToIntent(userInput,
|
|
14
|
+
return commandUserInputToIntent(userInput, inputModeState.commandLine);
|
|
8
15
|
}
|
|
9
16
|
}
|
|
10
17
|
function normalUserInputToIntent(userInput, normalBuffer) {
|