dirvi-lib 0.7.1 → 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.
Files changed (66) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/tree-surfer/cursor.d.ts +52 -26
  5. package/dist/tree-surfer/cursor.d.ts.map +1 -1
  6. package/dist/tree-surfer/cursor.js +19 -44
  7. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
  8. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
  9. package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
  10. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
  11. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
  12. package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
  13. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +2 -5
  14. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
  15. package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
  16. package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
  17. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
  18. package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
  19. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
  20. package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
  21. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
  22. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
  23. package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
  24. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
  25. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
  26. package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
  27. package/dist/tree-surfer/state/state.impl.d.ts.map +1 -1
  28. package/dist/tree-surfer/state/state.impl.js +34 -36
  29. package/dist/tree-surfer/state/state.schemas.d.ts +3 -3
  30. package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -1
  31. package/dist/tree-surfer/state/state.schemas.js +3 -3
  32. package/dist/tree-surfer/state/state.types.d.ts +11 -7
  33. package/dist/tree-surfer/state/state.types.d.ts.map +1 -1
  34. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +7 -9
  35. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
  36. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
  37. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
  38. package/dist/tree-surfer/tree-node/tree-node.arb.js +18 -23
  39. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
  40. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
  41. package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
  42. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
  43. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
  44. package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
  45. package/dist/tree-surfer/tree-node/tree-node.test.js +76 -81
  46. package/dist/tree-surfer/tree-node/tree-node.types.d.ts +14 -17
  47. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
  48. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
  49. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
  50. package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -12
  51. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
  52. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
  53. package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
  54. package/dist/tree-surfer-ui/domain/effect.d.ts +6 -8
  55. package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
  56. package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
  57. package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
  58. package/dist/tree-surfer-ui/domain/index.js +1 -1
  59. package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
  60. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
  61. package/package.json +1 -1
  62. package/dist/tree-surfer/tree-node/tree-node.arb.debug.d.ts +0 -2
  63. package/dist/tree-surfer/tree-node/tree-node.arb.debug.d.ts.map +0 -1
  64. package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
  65. /package/dist/tree-surfer/tree-node/{tree-node.arb.debug.js → tree-node.arb.debug.test.js} +0 -0
  66. /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
@@ -1,70 +1,193 @@
1
- import { SerializableKey } from '../tree-node/tree-node.types.js';
1
+ import { SerializableKey, TreeNodeApi } from '../tree-node/tree-node.types.js';
2
2
  /**
3
- * The whole fold state for a tree-node tree.
3
+ * A node in the fold-state tree.
4
4
  *
5
- * The root has no ID because it represents the fold at the root.
6
- * Its `children` contain the fold nodes for the forest's root entries.
5
+ * @remarks
7
6
  *
8
- * `folds` contains the IDs folded at this root. Fold order is not
9
- * represented here; the buffer tree determines entry order.
7
+ * A fold-state tree is a sparse projection of a buffer tree. Apart from its
8
+ * root, it stores only nodes that are folded or that have fold information
9
+ * somewhere below them. It does not contain buffer entries or buffer-specific
10
+ * properties.
11
+ *
12
+ * The `id` identifies the corresponding buffer entry among its siblings.
13
+ *
14
+ * `children` contains fold-state nodes for direct buffer entries that are not
15
+ * folded at this node but have fold information below them.
16
+ *
17
+ * `foldedChildren` contains fold-state nodes for direct buffer entries whose contents
18
+ * are folded at this node. Each folded entry is represented by a complete
19
+ * `FoldNode`, allowing fold state within a folded entry to be preserved
20
+ * recursively.
21
+ *
22
+ * A direct buffer entry with no fold state at or below it is omitted from both
23
+ * collections. Consequently, the union of `children` and `foldedChildren` does not
24
+ * need to contain every direct buffer child.
25
+ *
26
+ * The following invariant holds for every fold-state node:
27
+ *
28
+ * - no ID occurs more than once in `children`;
29
+ * - no ID occurs more than once in `foldedChildren`;
30
+ * - no ID occurs in both collections;
31
+ * - every ID in either collection identifies a direct child in the
32
+ * corresponding buffer-tree node.
33
+ *
34
+ * Every fold node is an open branch from the perspective of `TreeNodeApi`.
35
+ * Missing paths may nevertheless be created lazily by `FoldNodeService`.
36
+ *
37
+ * @typeParam Id - The type of IDs in the actual tree nodes.
38
+ *
39
+ * @example
40
+ *
41
+ * ```ts
42
+ * type FileId = string;
43
+ * type FileFoldNode = FoldNode<FileId>;
44
+ *
45
+ * const root: FileFoldNode = {
46
+ * id: '/',
47
+ * children: [],
48
+ * foldedChildren: [],
49
+ * };
50
+ * ```
10
51
  */
11
- export type FoldNodeRoot<Id extends SerializableKey> = {
52
+ export type FoldNode<Id extends SerializableKey> = {
53
+ id: Id;
12
54
  children: FoldNode<Id>[];
13
- folds: ReadonlySet<Id>;
55
+ foldedChildren: FoldNode<Id>[];
14
56
  };
15
57
  /**
16
- * Fold state associated with a node in the buffer tree.
58
+ * Operations for inspecting and immutably updating a fold-state tree.
59
+ *
60
+ * @remarks
17
61
  *
18
- * A fold node has the shape of an open tree node: its children are always
19
- * represented by an array, and an empty `children` array means that no child
20
- * fold nodes are currently represented.
62
+ * This API is `TreeNodeApi` specialized for `FoldNode`. It provides structural
63
+ * tree operations such as node lookup and immutable path updates.
21
64
  *
22
- * The `folds` set contains the IDs of entries folded at this node. Fold
23
- * order is not represented here; the buffer tree determines entry order.
65
+ * Paths are relative to the supplied root node:
24
66
  *
25
- * The type is structurally compatible with `TreeNode`, allowing the shared
26
- * `TreeNodeApi` to be used for path traversal and immutable updates.
67
+ * - `[]` identifies the root;
68
+ * - `['child']` identifies a direct child of the root;
69
+ * - `['child', 'grandchild']` identifies a descendant.
70
+ *
71
+ * Path operations are strict. They operate only on nodes that already exist
72
+ * in the fold-state tree. Use `FoldNodeService` when missing fold-state nodes
73
+ * should be automatically created.
74
+ *
75
+ * The API does not interpret the `foldedChildren` collection. It only navigates and
76
+ * updates the structural `children` tree. The semantic operations for adding
77
+ * and removing foldedChildren belong to `FoldNodeService`.
78
+ *
79
+ * @typeParam Id - The type of IDs in the actual tree nodes.
27
80
  */
28
- export type FoldNode<Id extends SerializableKey> = FoldNodeRoot<Id> & {
29
- id: Id;
30
- };
81
+ export type FoldNodeApi<Id extends SerializableKey> = TreeNodeApi<Id, FoldNode<Id>>;
31
82
  /**
32
- * Operations for navigating and immutably updating fold-tree structure.
83
+ * Semantic operations for managing fold state.
84
+ *
85
+ * @remarks
86
+ *
87
+ * `FoldNodeService` provides operations in terms of folded buffer entries
88
+ * rather than generic tree-node updates.
89
+ *
90
+ * Paths are relative to the supplied root node:
33
91
  *
34
- * This API is a projection of `TreeNodeApi` onto the fold-tree
35
- * representation. Path operations are strict: they only operate on
36
- * paths that already exist. Creation of missing fold paths belongs to
37
- * `FoldNodeService`.
92
+ * - `[]` addresses the root
93
+ * - `['child']` addresses a direct child of the root;
94
+ * - `['child', 'grandchild']` addresses a descendant.
95
+ *
96
+ * Lookup and removal operations are strict and return `undefined` when the
97
+ * requested path does not exist. Adding a folded entry may create missing
98
+ * fold-state nodes along the requested path.
99
+ *
100
+ * @typeParam Id - The type of IDs in the actual tree nodes.
38
101
  */
39
- export type FoldNodeApi<Id extends SerializableKey> = {
102
+ export type FoldNodeService<Id extends SerializableKey> = {
40
103
  /**
41
- * Returns the children of either the fold root or a fold node.
104
+ * Creates an empty fold-state node.
105
+ *
106
+ * It has no structural children and no folded entries.
107
+ *
108
+ * @returns A new empty fold-state node.
42
109
  */
43
- getChildren(node: FoldNodeRoot<Id> | FoldNode<Id>): Iterable<FoldNode<Id>>;
110
+ createEmptyNode(id: Id): FoldNode<Id>;
44
111
  /**
45
- * Returns a direct child by ID.
112
+ * Tests whether a node is folded.
113
+ *
114
+ * The path identifies the node whose `foldedChildren` collection is inspected. An
115
+ * empty path selects the root.
116
+ *
117
+ * This operation does not create missing fold-state nodes, or otherwise
118
+ * mutate the `rootNode`.
119
+ *
120
+ * @param rootNode - The root of the fold-state tree.
121
+ * @param path - A path relative to `rootNode`.
122
+ * @param entryId - The ID of the direct buffer entry to test.
123
+ * @returns `true` when the entry is folded at the resolved node;
124
+ * otherwise `false`. Missing paths also return `false`.
46
125
  */
47
- getChildById(node: FoldNodeRoot<Id> | FoldNode<Id>, id: Id): FoldNode<Id> | undefined;
126
+ getIfEntryFoldedAtPath(rootNode: FoldNode<Id>, path: readonly Id[], entryId: Id): boolean;
48
127
  /**
49
- * Selects a fold node at a path below the root.
128
+ * Returns the IDs of folded entries at the node at the path.
129
+ *
130
+ * Returns undefined if path has no fold node.
50
131
  */
51
- getAtPath<Result>(rootNode: FoldNodeRoot<Id>, path: Id[], selector: (node: FoldNode<Id>) => Result): Result | undefined;
132
+ foldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): Id[] | undefined;
52
133
  /**
53
- * Immutably modifies a fold node at a path below the root.
134
+ * Marks a direct buffer entry as folded at a node.
135
+ *
136
+ * Missing fold-state nodes along the path are created. If the entry is
137
+ * already folded, the existing fold-state tree is returned unchanged.
138
+ * If the entry is currently in `children`, it is moved to `foldedChildren` while
139
+ * preserving its nested fold state.
140
+ *
141
+ * Moving an entry preserves its nested `children` and `foldedChildren`, so fold
142
+ * state below the newly folded entry is not lost.
143
+ *
144
+ * @param rootNode - The root of the fold-state tree.
145
+ * @param path - A path relative to `rootNode`. An empty path selects the
146
+ * root.
147
+ * @param entryId - The ID of the direct buffer entry to fold.
148
+ * @returns A new root containing the fold, or `undefined` when the path
149
+ * cannot be updated.
54
150
  */
55
- modifyAtPath(rootNode: FoldNodeRoot<Id>, path: Id[], modifier: (node: FoldNode<Id>) => FoldNode<Id> | undefined): FoldNodeRoot<Id> | undefined;
56
- };
57
- /**
58
- * Semantic operations for managing folded buffer entries.
59
- *
60
- * Unlike `FoldNodeApi`, the service may create missing fold paths when
61
- * adding a fold. This allows an empty root to be populated lazily.
62
- */
63
- export type FoldNodeService<Id extends SerializableKey> = {
64
- createEmptyRoot(): FoldNodeRoot<Id>;
65
- getIfEntryFoldedAtPath(rootNode: FoldNodeRoot<Id>, path: Id[], entryId: Id): boolean;
66
- addFoldedEntryAtPath(rootNode: FoldNodeRoot<Id>, path: Id[], entryId: Id): FoldNodeRoot<Id> | undefined;
67
- removeFoldedEntryAtPath(rootNode: FoldNodeRoot<Id>, path: Id[], entryId: Id): FoldNodeRoot<Id> | undefined;
68
- clearFoldedEntriesAtPath(rootNode: FoldNodeRoot<Id>, path: Id[]): FoldNodeRoot<Id> | undefined;
151
+ addFoldedEntryAtPath(rootNode: FoldNode<Id>, path: readonly Id[], entryId: Id): FoldNode<Id> | undefined;
152
+ /**
153
+ * Removes the folded state for a direct buffer entry at a node.
154
+ *
155
+ * The entry is removed from `foldedChildren`. If the entry contains nested fold
156
+ * state in either its `children` or `foldedChildren` collections, it is moved to
157
+ * `children` so that the nested state is preserved. If it has no nested
158
+ * fold state, it is omitted from both collections.
159
+ *
160
+ * This operation does not create missing fold-state nodes. If the entry is
161
+ * not currently folded, the existing fold-state tree is returned unchanged.
162
+ *
163
+ * @param rootNode - The root of the fold-state tree.
164
+ * @param path - A path relative to `rootNode`. An empty path selects the
165
+ * root.
166
+ * @param entryId - The ID of the direct buffer entry to unfold.
167
+ * @returns A new root without the fold, or `undefined` when the path does
168
+ * not exist.
169
+ */
170
+ removeFoldedEntryAtPath(rootNode: FoldNode<Id>, path: readonly Id[], entryId: Id): FoldNode<Id> | undefined;
171
+ /**
172
+ * Removes the folded state for all direct buffer entries at a node.
173
+ *
174
+ * Each entry is removed from `foldedChildren`. Entries that contain nested
175
+ * fold state in either their `children` or `foldedChildren` collections are
176
+ * moved to `children` so that their nested fold state is preserved. Entries
177
+ * with no nested fold state are omitted from both collections.
178
+ *
179
+ * This operation does not create missing fold-state nodes. If the path does
180
+ * not exist, the existing fold-state tree cannot be updated and `undefined`
181
+ * is returned. When the resolved node has no folded entries, the existing
182
+ * fold-state tree is returned unchanged.
183
+ *
184
+ * @param rootNode - The root of the fold-state tree.
185
+ * @param path - A path relative to `rootNode`. An empty path selects the
186
+ * root.
187
+ * @returns A new root without the folds at the resolved node, or
188
+ * `undefined` when the path does not exist.
189
+ */
190
+ clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
191
+ clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
69
192
  };
70
193
  //# sourceMappingURL=fold-node.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fold-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,CAAC,EAAE,SAAS,eAAe,IAAI;IACrD,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,IAAI,YAAY,CAAC,EAAE,CAAC,GAAG;IACpE,EAAE,EAAE,EAAE,CAAC;CACR,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,eAAe,IAAI;IACpD;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAE3E;;OAEG;IACH,YAAY,CACV,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,EACrC,EAAE,EAAE,EAAE,GACL,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;OAEG;IACH,SAAS,CAAC,MAAM,EACd,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,EACV,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,MAAM,GACvC,MAAM,GAAG,SAAS,CAAC;IAEtB;;OAEG;IACH,YAAY,CACV,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,EACV,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,GACzD,YAAY,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,CAAC,EAAE,SAAS,eAAe,IAAI;IACxD,eAAe,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;IAEpC,sBAAsB,CACpB,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,EACV,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAEX,oBAAoB,CAClB,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,EACV,OAAO,EAAE,EAAE,GACV,YAAY,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAEhC,uBAAuB,CACrB,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,EACV,OAAO,EAAE,EAAE,GACV,YAAY,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAEhC,wBAAwB,CACtB,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,EAC1B,IAAI,EAAE,EAAE,EAAE,GACT,YAAY,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"fold-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,IAAI;IACjD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,eAAe,IAAI,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,eAAe,CAAC,EAAE,SAAS,eAAe,IAAI;IACxD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEtC;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CACpB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAEX;;;;OAIG;IACH,mBAAmB,CACjB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,EAAE,EAAE,GAAG,SAAS,CAAC;IAEpB;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAClB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;OAiBG;IACH,uBAAuB,CACrB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CACtB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAC5B,wBAAwB,CACtB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CAC7B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tree-node.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-node.test.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/tree-node.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,90 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createTreeNodeApi } from '../tree-node/tree-node.impl.js';
3
+ import { createFoldNodeService } from './fold-node.impl.js';
4
+ const foldNodeApi = createTreeNodeApi();
5
+ const service = createFoldNodeService(foldNodeApi);
6
+ describe('createFoldNodeService', () => {
7
+ it('UT: adds a new entry to foldedChildren', () => {
8
+ const root = service.createEmptyNode(0);
9
+ const updated = service.addFoldedEntryAtPath(root, [], 0);
10
+ expect(updated).toStrictEqual({
11
+ id: 0,
12
+ children: [],
13
+ foldedChildren: [
14
+ {
15
+ id: 0,
16
+ children: [],
17
+ foldedChildren: [],
18
+ },
19
+ ],
20
+ });
21
+ });
22
+ it('UT: moves an existing structural entry to foldedChildren', () => {
23
+ const child = {
24
+ id: 1,
25
+ children: [],
26
+ foldedChildren: [],
27
+ };
28
+ const root = {
29
+ id: 0,
30
+ children: [child],
31
+ foldedChildren: [],
32
+ };
33
+ const updated = service.addFoldedEntryAtPath(root, [], 1);
34
+ expect(updated).toStrictEqual({
35
+ id: 0,
36
+ children: [],
37
+ foldedChildren: [child],
38
+ });
39
+ });
40
+ it('UT: preserves nested fold state when moving an entry', () => {
41
+ const nestedChild = {
42
+ id: 2,
43
+ children: [],
44
+ foldedChildren: [],
45
+ };
46
+ const child = {
47
+ id: 1,
48
+ children: [],
49
+ foldedChildren: [nestedChild],
50
+ };
51
+ const root = {
52
+ id: 0,
53
+ children: [child],
54
+ foldedChildren: [],
55
+ };
56
+ const updated = service.addFoldedEntryAtPath(root, [], 1);
57
+ expect(updated).toStrictEqual({
58
+ id: 0,
59
+ children: [],
60
+ foldedChildren: [
61
+ {
62
+ id: 1,
63
+ children: [],
64
+ foldedChildren: [nestedChild],
65
+ },
66
+ ],
67
+ });
68
+ expect(updated?.foldedChildren[0]).toBe(child);
69
+ expect(updated?.foldedChildren[0]?.foldedChildren[0]).toBe(nestedChild);
70
+ });
71
+ it('UT: rejects a path through a folded entry', () => {
72
+ const foldedChild = {
73
+ id: 1,
74
+ children: [],
75
+ foldedChildren: [],
76
+ };
77
+ const root = {
78
+ id: 0,
79
+ children: [],
80
+ foldedChildren: [foldedChild],
81
+ };
82
+ const updated = service.addFoldedEntryAtPath(root, [1], 2);
83
+ expect(updated).toBeUndefined();
84
+ expect(root).toStrictEqual({
85
+ id: 0,
86
+ children: [],
87
+ foldedChildren: [foldedChild],
88
+ });
89
+ });
90
+ });
@@ -1,6 +1,6 @@
1
- import { SerializableKey, TreeNode, TreeNodeApi } from '../tree-node/tree-node.types.js';
2
- import { FoldNodeApi } from '../fold-node/fold-node.types.js';
3
- import { CursorApi } from '../cursor.js';
4
- import { NavNodeApi } from './nav-node.types.js';
5
- export declare function createNavNodeApi<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(treeNodeApi: TreeNodeApi<Id, BufferNode>, foldNodeApi: FoldNodeApi<Id>, cursorApi: CursorApi<Id>): NavNodeApi<Id, BufferNode>;
1
+ import type { SerializableKey, TreeNode, TreeNodeApi } from '../tree-node/tree-node.types.js';
2
+ import type { FoldNodeService } from '../fold-node/fold-node.types.js';
3
+ import type { CursorApi } from '../cursor.js';
4
+ import type { NavNodeApi } from './nav-node.types.js';
5
+ export declare function createNavNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(treeNodeApi: TreeNodeApi<Id, Node>, foldNodeService: FoldNodeService<Id>, cursorApi: CursorApi<Id>): NavNodeApi<Id, Node>;
6
6
  //# sourceMappingURL=nav-node.impl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/nav-node/nav-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,WAAW,EAAgB,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAU,SAAS,EAAc,MAAM,cAAc,CAAC;AAC7D,OAAO,EAKL,UAAU,EACX,MAAM,qBAAqB,CAAC;AAE7B,wBAAgB,gBAAgB,CAC9B,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAE3C,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,UAAU,CAAC,EACxC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,GACvB,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,CAqP5B"}
1
+ {"version":3,"file":"nav-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/nav-node/nav-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAY,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAIV,UAAU,EACX,MAAM,qBAAqB,CAAC;AAE7B,wBAAgB,gBAAgB,CAC9B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAE/B,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,EAClC,eAAe,EAAE,eAAe,CAAC,EAAE,CAAC,EACpC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,GACvB,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CA2RtB"}
@@ -1,44 +1,76 @@
1
- import { CursorKind } from '../cursor.js';
2
- import { isNavBranch, } from './nav-node.types.js';
3
- export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
4
- const navNodeApi = {
5
- // Return a nav node from the BufferNode tree and FoldNode tree.
6
- from(entries, foldNode) {
7
- const visibleEntriesSoFar = [];
8
- const foldedEntriesSoFar = [];
9
- for (const entry of entries) {
10
- // Find the node
11
- let navigationEntry;
12
- if (treeNodeApi.isBranch(entry)) {
13
- // If fold node does not have children (recursively no folds),
14
- // then just use empty.
15
- const childFoldNode = foldNodeApi.getChildById(foldNode, entry.id) ??
16
- createEmptyFoldNode();
17
- navigationEntry = {
18
- ...entry,
19
- children: entry.children === null
20
- ? null
21
- : navNodeApi.from(entry.children, childFoldNode),
22
- };
23
- }
24
- else if (treeNodeApi.isLeaf(entry)) {
25
- navigationEntry = entry;
26
- }
27
- else {
28
- // This should be unreachable if BufferNode correctly extends TreeNode.
29
- throw new Error('Unsupported tree node');
30
- }
31
- if (foldNode.folds.has(entry.id)) {
32
- foldedEntriesSoFar.push(navigationEntry);
33
- }
34
- else {
35
- visibleEntriesSoFar.push(navigationEntry);
36
- }
1
+ export function createNavNodeApi(treeNodeApi, foldNodeService, cursorApi) {
2
+ function entryIsBranch(entry) {
3
+ return 'children' in entry;
4
+ }
5
+ function createNavNode(entries, foldRoot, parentPath) {
6
+ const visibleEntries = [];
7
+ const foldedEntries = [];
8
+ const foldedEntryIds = foldRoot === undefined
9
+ ? undefined
10
+ : foldNodeService.foldedEntriesAtPath(foldRoot, parentPath);
11
+ for (const entry of entries) {
12
+ const entryPath = [...parentPath, entry.id];
13
+ const isFolded = foldRoot !== undefined &&
14
+ foldNodeService.getIfEntryFoldedAtPath(foldRoot, parentPath, entry.id);
15
+ const navigationEntry = createNavEntry(entry, foldRoot, entryPath);
16
+ if (isFolded) {
17
+ foldedEntries.push(navigationEntry);
18
+ }
19
+ else {
20
+ visibleEntries.push(navigationEntry);
37
21
  }
22
+ }
23
+ return {
24
+ entries: visibleEntries,
25
+ folded: foldedEntryIds === undefined || foldedEntryIds.length === 0
26
+ ? null
27
+ : {
28
+ entries: foldedEntries,
29
+ },
30
+ };
31
+ }
32
+ function createNavEntry(entry, foldRoot, entryPath) {
33
+ if (treeNodeApi.isLeaf(entry)) {
38
34
  return {
39
- entries: visibleEntriesSoFar,
40
- foldedEntries: foldedEntriesSoFar,
35
+ id: entry.id,
41
36
  };
37
+ }
38
+ return createNavBranch(entry, foldRoot, entryPath);
39
+ }
40
+ function createNavBranch(entry, foldRoot, entryPath) {
41
+ if (!treeNodeApi.isBranch(entry)) {
42
+ throw new Error('Navigation root must be a branch');
43
+ }
44
+ return {
45
+ id: entry.id,
46
+ children: entry.children === null
47
+ ? null
48
+ : createNavNode(entry.children, foldRoot, entryPath),
49
+ };
50
+ }
51
+ function rootCursors(rootNode) {
52
+ if (rootNode.children === null) {
53
+ return [[]];
54
+ }
55
+ return [[], ...cursorsInNode(rootNode.children, [])];
56
+ }
57
+ function cursorsInNode(node, parentPath) {
58
+ const cursors = [];
59
+ for (const entry of node.entries) {
60
+ const entryPath = [...parentPath, entry.id];
61
+ cursors.push(entryPath);
62
+ if (!entryIsBranch(entry) || entry.children === null) {
63
+ continue;
64
+ }
65
+ cursors.push(...cursorsInNode(entry.children, entryPath));
66
+ }
67
+ return cursors;
68
+ }
69
+ const navNodeApi = {
70
+ entryIsBranch,
71
+ // Return a nav node from the TreeNode and FoldNode trees.
72
+ from(root, foldRoot) {
73
+ return createNavBranch(root, foldRoot, []);
42
74
  },
43
75
  getNodeAtPath(navigation, path) {
44
76
  const [currentId, ...remainingPath] = path;
@@ -46,11 +78,11 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
46
78
  if (currentId === undefined) {
47
79
  return navigation;
48
80
  }
49
- const entry = [...navigation.entries, ...navigation.foldedEntries].find((candidate) => candidate.id === currentId);
81
+ const entry = navigation.entries.find((candidate) => candidate.id === currentId);
50
82
  if (entry === undefined) {
51
83
  return undefined;
52
84
  }
53
- if (!isNavBranch(entry)) {
85
+ if (!entryIsBranch(entry)) {
54
86
  return undefined;
55
87
  }
56
88
  // The directory has not been loaded/opened.
@@ -64,20 +96,20 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
64
96
  if (currentId === undefined) {
65
97
  return undefined;
66
98
  }
67
- const entry = [...navigation.entries, ...navigation.foldedEntries].find((candidate) => candidate.id === currentId);
99
+ const entry = navigation.entries.find((candidate) => candidate.id === currentId);
68
100
  if (entry === undefined) {
69
101
  return undefined;
70
102
  }
71
103
  if (remainingPath.length === 0) {
72
104
  return entry;
73
105
  }
74
- if (!isNavBranch(entry) || entry.children === null) {
106
+ if (!entryIsBranch(entry) || entry.children === null) {
75
107
  return undefined;
76
108
  }
77
109
  return navNodeApi.getEntryAtPath(entry.children, remainingPath);
78
110
  },
79
111
  nextCursor(rootNode, cursor) {
80
- const cursors = cursorsInNode(rootNode, []);
112
+ const cursors = rootCursors(rootNode);
81
113
  const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
82
114
  if (currentIndex === -1 || currentIndex === cursors.length - 1) {
83
115
  return undefined;
@@ -85,7 +117,7 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
85
117
  return cursors[currentIndex + 1];
86
118
  },
87
119
  previousCursor(rootNode, cursor) {
88
- const cursors = cursorsInNode(rootNode, []);
120
+ const cursors = rootCursors(rootNode);
89
121
  const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
90
122
  if (currentIndex <= 0) {
91
123
  return undefined;
@@ -93,52 +125,32 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
93
125
  return cursors[currentIndex - 1];
94
126
  },
95
127
  cursorAfterFold(rootNode, cursor) {
96
- if (cursorApi.isFold(cursor)) {
128
+ if (cursor.length === 0) {
97
129
  return undefined;
98
130
  }
99
- const cursors = cursorsInNode(rootNode, []);
131
+ const cursors = rootCursors(rootNode);
100
132
  const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
101
133
  if (currentIndex === -1) {
102
134
  return undefined;
103
135
  }
104
- const foldedPath = [...cursor.parentPath, cursor.entryId];
105
136
  for (let index = currentIndex + 1; index < cursors.length; index += 1) {
106
137
  const candidate = cursors[index];
107
138
  if (candidate === undefined) {
108
139
  continue;
109
140
  }
110
- if (cursorApi.cursorBelongsToSubtree(candidate, foldedPath)) {
141
+ if (cursorApi.cursorBelongsToSubtree(candidate, cursor)) {
111
142
  continue;
112
143
  }
113
144
  return candidate;
114
145
  }
115
146
  // The fold row will be created at the end of this directory.
116
- return {
117
- kind: CursorKind.Fold,
118
- parentPath: cursor.parentPath,
119
- };
147
+ return [...cursor];
120
148
  },
121
- parentCursor(navigation, cursor) {
122
- const parentPath = cursor.parentPath;
123
- // The cursor is already in the root directory.
124
- if (parentPath.length === 0) {
125
- return undefined;
126
- }
127
- const entryId = parentPath.at(-1);
128
- if (entryId === undefined) {
129
- return undefined;
130
- }
131
- const containingPath = parentPath.slice(0, -1);
132
- const entry = navNodeApi.getEntryAtPath(navigation, [...parentPath]);
133
- // The parent path must identify a navigable branch.
134
- if (entry === undefined || !isNavBranch(entry)) {
149
+ parentCursor(_navigation, cursor) {
150
+ if (cursor.length === 0) {
135
151
  return undefined;
136
152
  }
137
- return {
138
- kind: CursorKind.Entry,
139
- parentPath: containingPath,
140
- entryId: entryId,
141
- };
153
+ return cursor.slice(0, -1);
142
154
  },
143
155
  cursors(navigation, parentPath = []) {
144
156
  return cursorsInNode(navigation, parentPath);
@@ -147,7 +159,7 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
147
159
  const paths = [];
148
160
  for (const entry of navigation.entries) {
149
161
  const entryPath = [...parentPath, entry.id];
150
- if (isNavBranch(entry)) {
162
+ if (entryIsBranch(entry)) {
151
163
  // A null children value means that the directory has not been
152
164
  // loaded/opened, so there are no visible descendant files.
153
165
  if (entry.children !== null) {
@@ -162,30 +174,3 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
162
174
  };
163
175
  return navNodeApi;
164
176
  }
165
- function createEmptyFoldNode() {
166
- return {
167
- children: [],
168
- folds: new Set(),
169
- };
170
- }
171
- function cursorsInNode(node, parentPath) {
172
- const cursors = [];
173
- for (const entry of node.entries) {
174
- cursors.push({
175
- kind: CursorKind.Entry,
176
- parentPath,
177
- entryId: entry.id,
178
- });
179
- if (!isNavBranch(entry) || entry.children === null) {
180
- continue;
181
- }
182
- cursors.push(...cursorsInNode(entry.children, [...parentPath, entry.id]));
183
- }
184
- if (node.foldedEntries.length > 0) {
185
- cursors.push({
186
- kind: CursorKind.Fold,
187
- parentPath,
188
- });
189
- }
190
- return cursors;
191
- }