web-doc 0.7.0 → 0.9.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 (65) hide show
  1. package/THIRD_PARTY_NOTICES.md +10 -3
  2. package/dist/contracts.d.ts +6 -0
  3. package/dist/edit/ai/outline.d.ts +47 -0
  4. package/dist/edit/ai/outline.js +338 -0
  5. package/dist/edit/ai/targets.d.ts +16 -0
  6. package/dist/edit/ai/targets.js +309 -0
  7. package/dist/edit/ai/tools.d.ts +28 -0
  8. package/dist/edit/ai/tools.js +607 -0
  9. package/dist/edit/ai/types.d.ts +175 -0
  10. package/dist/edit/ai/types.js +1 -0
  11. package/dist/edit/docx/elements.js +19 -1
  12. package/dist/edit/docx/engine.d.ts +10 -4
  13. package/dist/edit/docx/engine.js +80 -6
  14. package/dist/edit/docx/model.d.ts +2 -0
  15. package/dist/edit/docx/model.js +11 -0
  16. package/dist/edit/docx/operations.d.ts +10 -0
  17. package/dist/edit/docx/provider.d.ts +4 -1
  18. package/dist/edit/docx/provider.js +3 -0
  19. package/dist/edit/docx/session.d.ts +12 -1
  20. package/dist/edit/docx/session.js +41 -0
  21. package/dist/edit/docx/structure-ops.js +39 -4
  22. package/dist/edit/docx/table-ops.js +26 -4
  23. package/dist/edit/docx/text-ops.d.ts +22 -0
  24. package/dist/edit/docx/text-ops.js +49 -16
  25. package/dist/edit/docx/text.js +21 -0
  26. package/dist/edit/docx/tracked.d.ts +52 -0
  27. package/dist/edit/docx/tracked.js +347 -0
  28. package/dist/edit/docx/types.d.ts +24 -1
  29. package/dist/edit/docx/write.d.ts +19 -5
  30. package/dist/edit/docx/write.js +31 -8
  31. package/dist/edit/engine.d.ts +14 -3
  32. package/dist/edit/history.d.ts +27 -3
  33. package/dist/edit/history.js +29 -7
  34. package/dist/edit/pdf/engine/document.js +48 -3
  35. package/dist/edit/pdf/engine/elements.d.ts +6 -0
  36. package/dist/edit/pdf/engine/fonts.js +13 -10
  37. package/dist/edit/pdf/engine/pages.js +9 -1
  38. package/dist/edit/pdf/range-map.d.ts +4 -2
  39. package/dist/edit/pdf/schemas.js +4 -1
  40. package/dist/edit/pdf/session.d.ts +10 -0
  41. package/dist/edit/pdf/session.js +39 -0
  42. package/dist/edit/pdf/types.d.ts +15 -3
  43. package/dist/edit/pptx/handler.js +10 -2
  44. package/dist/edit/pptx/session.d.ts +10 -0
  45. package/dist/edit/pptx/session.js +31 -0
  46. package/dist/edit/session.d.ts +11 -0
  47. package/dist/edit/session.js +269 -27
  48. package/dist/edit/types.d.ts +17 -3
  49. package/dist/edit/worker-engine.d.ts +2 -2
  50. package/dist/edit/worker-engine.js +2 -2
  51. package/dist/fonts/THIRD_PARTY_NOTICES.md +6 -3
  52. package/dist/fonts/manifest.json +4 -4
  53. package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
  54. package/dist/fuzzy-alignment.d.ts +11 -4
  55. package/dist/fuzzy-alignment.js +3 -9
  56. package/dist/headless.d.ts +1 -1
  57. package/dist/headless.js +4 -1
  58. package/dist/index.d.ts +5 -2
  59. package/dist/index.js +11 -2
  60. package/dist/limits.js +3 -0
  61. package/dist/worker-protocol.d.ts +1 -1
  62. package/dist/workers/fuzzy-search-worker.js +1 -1
  63. package/dist/workers/ooxml-edit-worker.js +1415 -860
  64. package/dist/workers/pdf-edit-worker.js +64 -16
  65. package/package.json +1 -1
@@ -38,9 +38,16 @@ The release artifact contains or depends on the following principal components.
38
38
  a flat block index over the original bytes with the raw paragraph and run
39
39
  properties kept, property merges that keep the original bytes where a
40
40
  value does not change and place new children in schema order, and
41
- section properties that an edited paragraph never duplicates. The
41
+ section properties that an edited paragraph never duplicates. The AI
42
+ tooling of `packages/viewer/src/edit/ai/` follows its published ideas of
43
+ a numbered document skeleton the model reads first with full content
44
+ pulled on demand within a character budget, writes made only through a
45
+ small validated tool set with a dry run, index addressing guarded by an
46
+ optimistic "document seen" check, a per-turn snapshot to roll back, and
47
+ Word tracked changes authored by the AI as the review channel. The
42
48
  implementations in `packages/viewer/src/edit/pdf/selection.ts`,
43
- `packages/viewer/src/edit/pptx/` and `packages/viewer/src/edit/docx/` are
44
- web-doc's own; no GenOffice code is included.
49
+ `packages/viewer/src/edit/pptx/`, `packages/viewer/src/edit/docx/` and
50
+ `packages/viewer/src/edit/ai/` are web-doc's own; no GenOffice code is
51
+ included.
45
52
  - [Noto Sans](https://github.com/notofonts/noto-fonts) and [Noto Sans CJK](https://github.com/notofonts/noto-cjk) subset fonts — SIL Open Font License 1.1. The font manifest, complete OFL text, pinned source commits and SHA-256 hashes are included in `dist/fonts/`.
46
53
  No Microsoft proprietary font or copyleft runtime component is bundled. Transitive notices and license expressions are verified by `npm run licenses`.
@@ -51,6 +51,12 @@ export interface ResourceLimits {
51
51
  readonly maxEditHistory: number;
52
52
  /** Memory for retained edit checkpoints; fewer are kept when a file is big. */
53
53
  readonly maxEditCheckpointBytes: number;
54
+ /** Nodes one `EditSession.getOutline()` returns; more is cut and reported. */
55
+ readonly maxOutlineNodes: number;
56
+ /** Upper bound of `describe()`'s character budget. */
57
+ readonly maxDescribeChars: number;
58
+ /** Named edit checkpoints alive at once in a session. */
59
+ readonly maxEditCheckpoints: number;
54
60
  }
55
61
  export type BinaryDocumentSource = ArrayBuffer | Uint8Array | Blob;
56
62
  export type DocumentSource = BinaryDocumentSource | URL | string;
@@ -0,0 +1,47 @@
1
+ import type { ResourceLimits } from "../../contracts.js";
2
+ import type { EditableFormat, EditElement, EditState, ElementQuery, ReadItem, ReadOptions, ReadResult } from "../types.js";
3
+ import type { DescribeOptions, DocumentDescription, OutlineNode, OutlineOptions, OutlineResult } from "./types.js";
4
+ /** What the outline needs of a session: its format, its page count and its elements. */
5
+ export interface OutlineSource {
6
+ readonly format: EditableFormat;
7
+ readonly state: EditState;
8
+ getElements(query?: ElementQuery, options?: ReadOptions): Promise<ReadResult<EditElement>>;
9
+ }
10
+ /** The outline of a session's document; one `getElements()` read, reshaped. */
11
+ export declare function readOutline(session: OutlineSource, limits: ResourceLimits, options?: OutlineOptions): Promise<OutlineResult>;
12
+ /** The description of a session's document: the outline rendered within a character budget. */
13
+ export declare function readDescription(session: OutlineSource, limits: ResourceLimits, options?: DescribeOptions): Promise<ReadItem<DocumentDescription>>;
14
+ export interface BuiltOutline {
15
+ readonly nodes: readonly OutlineNode[];
16
+ readonly nodeCount: number;
17
+ readonly truncated: boolean;
18
+ }
19
+ /**
20
+ * Nests elements by `parentId`, numbers them in reading order, keeps the
21
+ * nodes the options ask for (a container stays when a descendant is kept)
22
+ * and cuts the result at `maxNodes`. Ordinals are assigned before any
23
+ * filtering, so "3.2" names the same paragraph whatever the query.
24
+ */
25
+ export declare function buildOutline(format: EditableFormat, elements: readonly EditElement[], options: Pick<OutlineOptions, "pageRange" | "kinds" | "maxTextChars">, maxNodes: number): BuiltOutline;
26
+ /**
27
+ * What a person calls the element, when the format knows: a shape's name
28
+ * or placeholder in a deck, a paragraph's style in a Word document, a
29
+ * table's size everywhere.
30
+ */
31
+ export declare function labelOf(format: EditableFormat, element: EditElement): string | undefined;
32
+ /**
33
+ * The description's grammar, one line per node:
34
+ *
35
+ * <format>: <n> pages|slides, <m> elements
36
+ * [<id>] page <p> <kind> "<label>" (<flags>): <text>…
37
+ * [<child id>] page <p> <kind>: <text>
38
+ *
39
+ * The page is left out while the node has none, the label and the flags
40
+ * while the node has none, the text while it is empty. Tabs in the text
41
+ * become ` | `, line breaks ` ⏎ `; a cut text ends in `…`. Children are
42
+ * indented two spaces per level. The budget cuts whole lines and the last
43
+ * line then says how many were left out.
44
+ */
45
+ export declare function renderDescription(format: EditableFormat, pageCount: number, outline: BuiltOutline | OutlineResult, maxChars: number, maxNodes: number): DocumentDescription;
46
+ /** One line of text: tabs as cell separators, breaks marked, other controls blanked. */
47
+ export declare function foldText(text: string): string;
@@ -0,0 +1,338 @@
1
+ /*
2
+ * The outline is `getElements()` reshaped for a prompt: nested where the
3
+ * format nests (a table's cell paragraphs, a group's members, a paragraph's
4
+ * inline pictures), numbered in reading order, with a short text and a
5
+ * human label per node and nothing a model cannot use (geometry, styles).
6
+ * The description is the outline as text in a fixed line grammar, so a host
7
+ * pastes it into a prompt without a formatter of its own.
8
+ */
9
+ const DEFAULT_TEXT_CHARS = 160;
10
+ const DEFAULT_DESCRIBE_CHARS = 50_000;
11
+ /** The outline of a session's document; one `getElements()` read, reshaped. */
12
+ export async function readOutline(session, limits, options = {}) {
13
+ const { pageRange, signal } = options;
14
+ // A single page is read as such: cheaper for formats that load pages on demand.
15
+ const query = pageRange && pageRange[0] === pageRange[1]
16
+ ? { pageIndex: pageRange[0] }
17
+ : {};
18
+ const read = await session.getElements(query, signal ? { signal } : {});
19
+ const built = buildOutline(session.format, read.items, options, limits.maxOutlineNodes);
20
+ return Object.freeze({
21
+ sessionId: read.sessionId,
22
+ revision: read.revision,
23
+ items: built.nodes,
24
+ nodeCount: built.nodeCount,
25
+ truncated: built.truncated,
26
+ });
27
+ }
28
+ /** The description of a session's document: the outline rendered within a character budget. */
29
+ export async function readDescription(session, limits, options = {}) {
30
+ const { maxChars, ...outlineOptions } = options;
31
+ const outline = await readOutline(session, limits, outlineOptions);
32
+ const budget = Math.max(1, Math.min(maxChars ?? DEFAULT_DESCRIBE_CHARS, limits.maxDescribeChars));
33
+ return Object.freeze({
34
+ sessionId: outline.sessionId,
35
+ revision: outline.revision,
36
+ item: renderDescription(session.format, session.state.pageCount, outline, budget, limits.maxOutlineNodes),
37
+ });
38
+ }
39
+ /**
40
+ * Nests elements by `parentId`, numbers them in reading order, keeps the
41
+ * nodes the options ask for (a container stays when a descendant is kept)
42
+ * and cuts the result at `maxNodes`. Ordinals are assigned before any
43
+ * filtering, so "3.2" names the same paragraph whatever the query.
44
+ */
45
+ export function buildOutline(format, elements, options, maxNodes) {
46
+ const maxText = Math.max(0, options.maxTextChars ?? DEFAULT_TEXT_CHARS);
47
+ const ids = new Set(elements.map((element) => element.id));
48
+ const childrenOf = new Map();
49
+ const roots = [];
50
+ for (const element of elements) {
51
+ const parent = element.parentId;
52
+ if (parent === undefined || parent === element.id || !ids.has(parent)) {
53
+ roots.push(element);
54
+ continue;
55
+ }
56
+ const siblings = childrenOf.get(parent);
57
+ if (siblings)
58
+ siblings.push(element);
59
+ else
60
+ childrenOf.set(parent, [element]);
61
+ }
62
+ const visited = new Set();
63
+ const toNode = (element, ordinal) => {
64
+ visited.add(element.id);
65
+ const children = (childrenOf.get(element.id) ?? [])
66
+ .filter((child) => !visited.has(child.id))
67
+ .map((child, index) => toNode(child, `${ordinal}.${index + 1}`));
68
+ return nodeOf(format, element, ordinal, maxText, children);
69
+ };
70
+ const tree = roots.map((element, index) => toNode(element, `${index + 1}`));
71
+ // Elements whose parents form a cycle are never reached from a root;
72
+ // they are listed after the roots rather than dropped.
73
+ let extra = tree.length;
74
+ for (const element of elements)
75
+ if (!visited.has(element.id)) {
76
+ extra += 1;
77
+ tree.push(toNode(element, `${extra}`));
78
+ }
79
+ const kept = prune(tree, keepPredicate(options));
80
+ const cut = cutNodes(kept, maxNodes);
81
+ return {
82
+ nodes: cut.nodes,
83
+ nodeCount: cut.count,
84
+ truncated: cut.truncated,
85
+ };
86
+ }
87
+ function nodeOf(format, element, ordinal, maxText, children) {
88
+ const text = element.text;
89
+ const label = labelOf(format, element);
90
+ const extra = element;
91
+ return Object.freeze({
92
+ id: element.id,
93
+ ordinal,
94
+ kind: element.kind,
95
+ pageIndex: element.pageIndex,
96
+ ...(text === undefined ? {} : { text: cutText(text, maxText) }),
97
+ textLength: text?.length ?? 0,
98
+ truncated: text !== undefined && text.length > maxText,
99
+ ...(label === undefined ? {} : { label }),
100
+ ...(extra.readOnlyReason === undefined
101
+ ? {}
102
+ : { readOnlyReason: extra.readOnlyReason }),
103
+ ...(extra.hidden ? { hidden: true } : {}),
104
+ operations: Object.freeze([...element.operations]),
105
+ ...(children.length > 0 ? { children: Object.freeze(children) } : {}),
106
+ });
107
+ }
108
+ /** The first `max` code units of `text`, never ending inside a surrogate pair. */
109
+ function cutText(text, max) {
110
+ if (text.length <= max)
111
+ return text;
112
+ let end = max;
113
+ const last = text.charCodeAt(end - 1);
114
+ if (end > 0 && last >= 0xd800 && last <= 0xdbff)
115
+ end -= 1;
116
+ return text.slice(0, end);
117
+ }
118
+ function keepPredicate(options) {
119
+ const { pageRange, kinds } = options;
120
+ if (!pageRange && !kinds)
121
+ return undefined;
122
+ const wanted = kinds ? new Set(kinds) : undefined;
123
+ return (node) => (!wanted || wanted.has(node.kind)) &&
124
+ (!pageRange ||
125
+ (node.pageIndex >= pageRange[0] && node.pageIndex <= pageRange[1]));
126
+ }
127
+ /** Keeps the nodes `keep` accepts and every ancestor of one, in order. */
128
+ function prune(nodes, keep) {
129
+ if (!keep)
130
+ return nodes;
131
+ const out = [];
132
+ for (const node of nodes) {
133
+ const children = node.children ? prune(node.children, keep) : [];
134
+ if (!keep(node) && children.length === 0)
135
+ continue;
136
+ out.push(children.length === node.children?.length
137
+ ? node
138
+ : withChildren(node, children));
139
+ }
140
+ return out;
141
+ }
142
+ /** The first `max` nodes in pre-order; the rest are cut, subtrees included. */
143
+ function cutNodes(nodes, max) {
144
+ let count = 0;
145
+ let truncated = false;
146
+ const take = (list) => {
147
+ const out = [];
148
+ for (const node of list) {
149
+ if (count >= max) {
150
+ truncated = true;
151
+ break;
152
+ }
153
+ count += 1;
154
+ if (!node.children) {
155
+ out.push(node);
156
+ continue;
157
+ }
158
+ const children = take(node.children);
159
+ out.push(children.length === node.children.length
160
+ ? node
161
+ : withChildren(node, children));
162
+ // A cut inside the children leaves nothing for the siblings.
163
+ if (truncated)
164
+ break;
165
+ }
166
+ return Object.freeze(out);
167
+ };
168
+ const kept = take(nodes);
169
+ return { nodes: kept, count, truncated };
170
+ }
171
+ /** `node` with `children`, the key left out when there are none. */
172
+ function withChildren(node, children) {
173
+ const { children: _dropped, ...rest } = node;
174
+ return Object.freeze(children.length > 0 ? { ...rest, children: Object.freeze(children) } : rest);
175
+ }
176
+ /**
177
+ * What a person calls the element, when the format knows: a shape's name
178
+ * or placeholder in a deck, a paragraph's style in a Word document, a
179
+ * table's size everywhere.
180
+ */
181
+ export function labelOf(format, element) {
182
+ const known = element;
183
+ const size = known.table ? tableSize(known.table.rows) : undefined;
184
+ if (format === "pptx") {
185
+ const name = known.name?.trim();
186
+ if (name)
187
+ return size ? `${name} (${size})` : name;
188
+ if (known.placeholder)
189
+ return placeholderLabel(known.placeholder.type);
190
+ }
191
+ if (format === "docx" && element.kind === "paragraph") {
192
+ const style = known.paragraphStyle;
193
+ const styleId = style?.styleId;
194
+ const name = styleId && styleId !== "Normal" ? styleName(styleId) : undefined;
195
+ if (name)
196
+ return name;
197
+ if (style?.numbering)
198
+ return `List item (level ${style.numbering.level + 1})`;
199
+ return undefined;
200
+ }
201
+ if (size)
202
+ return `Table (${size})`;
203
+ return undefined;
204
+ }
205
+ function tableSize(rows) {
206
+ const columns = rows.reduce((max, row) => Math.max(max, row.length), 0);
207
+ return `${rows.length}×${columns}`;
208
+ }
209
+ const PLACEHOLDER_LABELS = {
210
+ title: "Title",
211
+ ctrTitle: "Title",
212
+ subTitle: "Subtitle",
213
+ body: "Body",
214
+ obj: "Content",
215
+ pic: "Picture",
216
+ tbl: "Table",
217
+ chart: "Chart",
218
+ dgm: "Diagram",
219
+ media: "Media",
220
+ clipArt: "Clip art",
221
+ dt: "Date",
222
+ ftr: "Footer",
223
+ sldNum: "Slide number",
224
+ hdr: "Header",
225
+ sldImg: "Slide image",
226
+ };
227
+ function placeholderLabel(type) {
228
+ return PLACEHOLDER_LABELS[type] ?? `${type} placeholder`;
229
+ }
230
+ /** "Heading1" → "Heading 1", "ListParagraph" → "List Paragraph". */
231
+ function styleName(styleId) {
232
+ return styleId
233
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
234
+ .replace(/([A-Za-z])(\d)/g, "$1 $2");
235
+ }
236
+ /**
237
+ * The description's grammar, one line per node:
238
+ *
239
+ * <format>: <n> pages|slides, <m> elements
240
+ * [<id>] page <p> <kind> "<label>" (<flags>): <text>…
241
+ * [<child id>] page <p> <kind>: <text>
242
+ *
243
+ * The page is left out while the node has none, the label and the flags
244
+ * while the node has none, the text while it is empty. Tabs in the text
245
+ * become ` | `, line breaks ` ⏎ `; a cut text ends in `…`. Children are
246
+ * indented two spaces per level. The budget cuts whole lines and the last
247
+ * line then says how many were left out.
248
+ */
249
+ export function renderDescription(format, pageCount, outline, maxChars, maxNodes) {
250
+ const nodes = "items" in outline ? outline.items : outline.nodes;
251
+ const noun = format === "pptx" ? "slide" : "page";
252
+ const lines = [
253
+ `${format}: ${count(pageCount, noun)}, ${count(outline.nodeCount, "element")}`,
254
+ ];
255
+ const push = (node, depth) => {
256
+ lines.push(`${" ".repeat(depth)}${describeNode(node, noun)}`);
257
+ for (const child of node.children ?? [])
258
+ push(child, depth + 1);
259
+ };
260
+ for (const node of nodes)
261
+ push(node, 0);
262
+ // Whole lines within the budget; the last line then says what was left
263
+ // out and makes room for itself. Prefix sums keep this linear in the
264
+ // number of lines: `joined[k]` is the length of the first `k` lines joined.
265
+ const joined = new Array(lines.length + 1);
266
+ joined[0] = 0;
267
+ for (let index = 0; index < lines.length; index += 1)
268
+ joined[index + 1] =
269
+ joined[index] + lines[index].length + (index > 0 ? 1 : 0);
270
+ const tailOf = (keep) => {
271
+ const left = lines.length - keep;
272
+ if (left > 0)
273
+ return `… (${count(left, "more line")})`;
274
+ if (outline.truncated)
275
+ return `… (outline cut at ${count(maxNodes, "node")})`;
276
+ return undefined;
277
+ };
278
+ const lengthOf = (keep) => {
279
+ const tail = tailOf(keep);
280
+ const body = joined[keep];
281
+ if (tail === undefined)
282
+ return body;
283
+ return body === 0 ? tail.length : body + 1 + tail.length;
284
+ };
285
+ let keep = lines.length;
286
+ while (keep > 0 && lengthOf(keep) > maxChars)
287
+ keep -= 1;
288
+ let text;
289
+ let truncated = outline.truncated || keep < lines.length;
290
+ if (keep === 0 && lengthOf(0) > maxChars) {
291
+ // Not even the header fits: hand back what does.
292
+ text = lines[0].slice(0, maxChars);
293
+ truncated = true;
294
+ }
295
+ else {
296
+ const tail = tailOf(keep);
297
+ const body = lines.slice(0, keep).join("\n");
298
+ text = tail === undefined ? body : body ? `${body}\n${tail}` : tail;
299
+ }
300
+ return Object.freeze({
301
+ format,
302
+ pageCount,
303
+ elementCount: outline.nodeCount,
304
+ text,
305
+ truncated,
306
+ });
307
+ }
308
+ function describeNode(node, noun) {
309
+ const parts = [`[${node.id}]`];
310
+ if (node.pageIndex >= 0)
311
+ parts.push(`${noun} ${node.pageIndex + 1}`);
312
+ parts.push(node.kind);
313
+ if (node.label !== undefined)
314
+ parts.push(`"${node.label.replaceAll('"', "'")}"`);
315
+ const flags = [
316
+ ...(node.readOnlyReason ? [`read-only: ${node.readOnlyReason}`] : []),
317
+ ...(node.hidden ? ["hidden"] : []),
318
+ ];
319
+ if (flags.length > 0)
320
+ parts.push(`(${flags.join(", ")})`);
321
+ let line = parts.join(" ");
322
+ if (node.text)
323
+ line += `: ${foldText(node.text)}${node.truncated ? "…" : ""}`;
324
+ else if (node.truncated)
325
+ line += ": …";
326
+ return line;
327
+ }
328
+ /** One line of text: tabs as cell separators, breaks marked, other controls blanked. */
329
+ export function foldText(text) {
330
+ return text
331
+ .replaceAll("\r\n", "\n")
332
+ .replaceAll("\t", " | ")
333
+ .replaceAll(/[\n\r\u2028\u2029\v\f]/g, " ⏎ ")
334
+ .replaceAll(/[\u0000-\u0008\u000e-\u001f\u007f]/g, " ");
335
+ }
336
+ function count(value, noun) {
337
+ return `${value} ${noun}${value === 1 ? "" : "s"}`;
338
+ }
@@ -0,0 +1,16 @@
1
+ import type { EditElement, EditFindOptions, ElementQuery, ReadOptions, ReadResult, TextTarget } from "../types.js";
2
+ import type { TargetCandidate, TargetQuery } from "./types.js";
3
+ /** What resolution needs of a session: its elements and its exact text search. */
4
+ export interface TargetSource {
5
+ getElements(query?: ElementQuery, options?: ReadOptions): Promise<ReadResult<EditElement>>;
6
+ findText(query: string, options?: EditFindOptions): Promise<ReadResult<TextTarget>>;
7
+ }
8
+ export declare function resolveTargets(session: TargetSource, query: TargetQuery, options?: ReadOptions): Promise<ReadResult<TargetCandidate>>;
9
+ /** Whitespace runs to one space, quotes and dashes to ASCII, NFKC, case folded; offsets kept. */
10
+ export declare function foldWithMap(text: string): {
11
+ readonly text: string;
12
+ readonly starts: readonly number[];
13
+ readonly ends: readonly number[];
14
+ };
15
+ /** The matched text with a little context on each side, on one line. */
16
+ export declare function snippetOf(text: string, start: number, end: number): string;