pi-hashline-edit-pro 7.1.0 → 7.1.1
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/README.md +17 -8
- package/package.json +1 -1
- package/src/auto-read-all.ts +5 -5
- package/src/edit-common.ts +1 -1
- package/src/grep.ts +78 -10
- package/src/hashline/resolve.ts +16 -5
- package/src/outline/index.ts +5 -2
- package/src/outline/parsers.ts +2 -1
- package/src/outline/render.ts +2 -0
- package/src/outline/vendor/parsers/blade.ts +6 -1
- package/src/outline/vendor/parsers/index.ts +3 -2
- package/src/outline/vendor/parsers/manager.ts +280 -259
- package/src/outline/vendor/parsers/sass.ts +15 -9
- package/src/outline-tool.ts +205 -7
- package/src/prompts.ts +1 -1
- package/src/read.ts +2 -1
- package/src/replace-undo.ts +13 -0
- package/tool-prompts/outline-guidelines.md +2 -2
- package/tool-prompts/outline-snippet.md +1 -1
- package/tool-prompts/outline.md +6 -2
|
@@ -170,10 +170,10 @@ export function languageLabel(key: string): string {
|
|
|
170
170
|
export async function parseCode(
|
|
171
171
|
key: string,
|
|
172
172
|
source: string,
|
|
173
|
-
): Promise<SymbolInfo[]> {
|
|
173
|
+
): Promise<{ symbols: SymbolInfo[]; hasError: boolean }> {
|
|
174
174
|
// Markdown uses regex-based parsing, not tree-sitter
|
|
175
175
|
if (key === "markdown") {
|
|
176
|
-
return extractMarkdown(source);
|
|
176
|
+
return { symbols: extractMarkdown(source), hasError: false };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
await ensureInit();
|
|
@@ -183,9 +183,9 @@ export async function parseCode(
|
|
|
183
183
|
|
|
184
184
|
sharedParser.setLanguage(lang);
|
|
185
185
|
const tree = sharedParser.parse(source);
|
|
186
|
-
if (!tree) return [];
|
|
186
|
+
if (!tree) return { symbols: [], hasError: false };
|
|
187
187
|
|
|
188
|
-
return extractSymbols(key, tree.rootNode, source, 0);
|
|
188
|
+
return { symbols: extractSymbols(key, tree.rootNode, source, 0), hasError: tree.rootNode.hasError };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
// ---- Symbol extraction dispatch ----
|
|
@@ -275,33 +275,16 @@ function extractJavaScriptLike(
|
|
|
275
275
|
const child = node.namedChild(i);
|
|
276
276
|
if (!child) continue;
|
|
277
277
|
|
|
278
|
-
const symbol = extractJSChild(child, source);
|
|
278
|
+
const symbol = extractJSChild(child, source) ?? (node.type === "program" ? commonJsExportSymbol(child, source) : null);
|
|
279
279
|
if (!symbol) continue;
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
"enum_declaration",
|
|
289
|
-
]) ?? child
|
|
290
|
-
: child;
|
|
291
|
-
|
|
292
|
-
// For class/interface/enum, look inside their bodies
|
|
293
|
-
if (
|
|
294
|
-
symbol.type === "class" ||
|
|
295
|
-
symbol.type === "interface" ||
|
|
296
|
-
symbol.type === "enum"
|
|
297
|
-
) {
|
|
298
|
-
const bodyNode = findChildByType(declaration, [
|
|
299
|
-
"class_body",
|
|
300
|
-
"interface_body",
|
|
301
|
-
"enum_body",
|
|
302
|
-
"object_type",
|
|
303
|
-
]);
|
|
304
|
-
if (bodyNode && depth < MAX_DEPTH) {
|
|
281
|
+
// Look inside the bodies of container declarations
|
|
282
|
+
if (JS_CONTAINER_TYPES.has(symbol.type ?? "") && depth < MAX_DEPTH) {
|
|
283
|
+
const declaration = jsDeclarationNode(child);
|
|
284
|
+
const bodyNode =
|
|
285
|
+
declaration.childForFieldName("body") ??
|
|
286
|
+
findChildByType(declaration, ["class_body", "interface_body", "enum_body", "object_type", "statement_block"]);
|
|
287
|
+
if (bodyNode) {
|
|
305
288
|
symbol.children = extractJavaScriptLike(bodyNode, source, depth + 1);
|
|
306
289
|
}
|
|
307
290
|
}
|
|
@@ -312,23 +295,60 @@ function extractJavaScriptLike(
|
|
|
312
295
|
return results;
|
|
313
296
|
}
|
|
314
297
|
|
|
298
|
+
const JS_CONTAINER_TYPES = new Set(["class", "interface", "enum", "namespace"]);
|
|
299
|
+
|
|
300
|
+
/** Unwrap `export`, `declare` and the expression statement TypeScript puts around `namespace`. */
|
|
301
|
+
function jsDeclarationNode(child: Node): Node {
|
|
302
|
+
if (child.type === "export_statement" || child.type === "ambient_declaration" || child.type === "expression_statement") {
|
|
303
|
+
const inner = child.namedChildren.find((c) => c.type !== "decorator" && c.type !== "comment");
|
|
304
|
+
if (inner && inner.type !== "statement_block") return jsDeclarationNode(inner);
|
|
305
|
+
}
|
|
306
|
+
return child;
|
|
307
|
+
}
|
|
308
|
+
|
|
315
309
|
const JS_SYMBOL_TYPES: Record<string, string> = {
|
|
316
310
|
function_declaration: "function",
|
|
311
|
+
generator_function_declaration: "function",
|
|
312
|
+
function_signature: "function",
|
|
317
313
|
function_expression: "function",
|
|
318
314
|
arrow_function: "arrow_function",
|
|
319
315
|
method_definition: "method",
|
|
316
|
+
method_signature: "method",
|
|
317
|
+
abstract_method_signature: "method",
|
|
320
318
|
class_declaration: "class",
|
|
321
319
|
class: "class",
|
|
322
320
|
interface_declaration: "interface",
|
|
323
321
|
enum_declaration: "enum",
|
|
322
|
+
enum_assignment: "enum_member",
|
|
324
323
|
type_alias_declaration: "type",
|
|
325
324
|
abstract_class_declaration: "class",
|
|
325
|
+
internal_module: "namespace",
|
|
326
|
+
module: "namespace",
|
|
327
|
+
public_field_definition: "field",
|
|
328
|
+
field_definition: "field",
|
|
329
|
+
property_signature: "field",
|
|
326
330
|
lexical_declaration: "variable",
|
|
327
331
|
variable_declaration: "variable",
|
|
328
332
|
};
|
|
329
333
|
|
|
330
334
|
function isFunctionNode(node: Node): boolean {
|
|
331
|
-
return node.type === "arrow_function" || node.type === "function_expression" || node.type === "function";
|
|
335
|
+
return node.type === "arrow_function" || node.type === "function_expression" || node.type === "function" || node.type === "generator_function";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const COMMONJS_TARGET = /^(module\.exports(\.[\w$]+)?|exports\.[\w$]+|[\w$]+\.prototype\.[\w$]+)$/;
|
|
339
|
+
|
|
340
|
+
/** Top-level `module.exports = ...`, `exports.x = ...` and `Foo.prototype.m = ...`. */
|
|
341
|
+
function commonJsExportSymbol(child: Node, source: string): SymbolInfo | null {
|
|
342
|
+
if (child.type !== "expression_statement") return null;
|
|
343
|
+
const expr = child.namedChild(0);
|
|
344
|
+
if (!expr || expr.type !== "assignment_expression") return null;
|
|
345
|
+
const left = expr.childForFieldName("left");
|
|
346
|
+
const right = expr.childForFieldName("right");
|
|
347
|
+
if (!left || left.type !== "member_expression") return null;
|
|
348
|
+
const name = source.slice(left.startIndex, left.endIndex);
|
|
349
|
+
if (!COMMONJS_TARGET.test(name)) return null;
|
|
350
|
+
const [sl, el] = nodeRange(child);
|
|
351
|
+
return { name, type: right && isFunctionNode(right) ? "function" : "variable", startLine: sl, endLine: el };
|
|
332
352
|
}
|
|
333
353
|
|
|
334
354
|
function extractJSChild(
|
|
@@ -389,6 +409,10 @@ function extractJSChild(
|
|
|
389
409
|
const result = extractJSChild(inner, source);
|
|
390
410
|
if (result) {
|
|
391
411
|
if (isDefault && result.name === "<anonymous>") result.name = "default";
|
|
412
|
+
const [sl, el] = nodeRange(child);
|
|
413
|
+
// Keep the row on the `export` line when the declaration starts there.
|
|
414
|
+
if (inner.type === "expression_statement" && result.startLine > sl) result.startLine = sl;
|
|
415
|
+
if (result.endLine < el && inner.type === "expression_statement") result.endLine = el;
|
|
392
416
|
return result;
|
|
393
417
|
}
|
|
394
418
|
}
|
|
@@ -396,18 +420,50 @@ function extractJSChild(
|
|
|
396
420
|
return null;
|
|
397
421
|
}
|
|
398
422
|
|
|
399
|
-
|
|
423
|
+
// `declare module "x" {}`, `declare function f(): void;`, `declare global {}`
|
|
424
|
+
if (type === "ambient_declaration") {
|
|
425
|
+
const inner = child.namedChildren.find((c) => c.type !== "comment");
|
|
426
|
+
if (!inner) return null;
|
|
427
|
+
const [sl, el] = nodeRange(child);
|
|
428
|
+
if (inner.type === "statement_block") return { name: "global", type: "namespace", startLine: sl, endLine: el };
|
|
429
|
+
const result = extractJSChild(inner, source);
|
|
430
|
+
if (result) {
|
|
431
|
+
result.startLine = sl;
|
|
432
|
+
result.endLine = el;
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (type === "expression_statement") {
|
|
438
|
+
const inner = child.namedChild(0);
|
|
439
|
+
if (inner?.type === "internal_module") {
|
|
440
|
+
const result = extractJSChild(inner, source);
|
|
441
|
+
if (result) [result.startLine, result.endLine] = nodeRange(child);
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
return testCallSymbol(child, source);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Bare enum members: `enum E { Red, Green }`
|
|
448
|
+
if (type === "property_identifier" && child.parent?.type === "enum_body") {
|
|
449
|
+
const [sl, el] = nodeRange(child);
|
|
450
|
+
return { name: source.slice(child.startIndex, child.endIndex), type: "enum_member", startLine: sl, endLine: el };
|
|
451
|
+
}
|
|
400
452
|
|
|
401
453
|
const symbolType = JS_SYMBOL_TYPES[type];
|
|
402
454
|
if (!symbolType) return null;
|
|
403
455
|
|
|
404
456
|
const name =
|
|
405
457
|
fieldChildText(child, "name", source) ||
|
|
458
|
+
fieldChildText(child, "property", source) ||
|
|
406
459
|
childText(child, "property_identifier", source) ||
|
|
407
460
|
childText(child, "type_identifier", source) ||
|
|
408
461
|
"<anonymous>";
|
|
409
462
|
|
|
410
|
-
|
|
463
|
+
// Decorators on their own lines are children of fields and non-exported classes; start at the declaration.
|
|
464
|
+
const declarationStart = child.children.find((part) => part.type !== "decorator" && part.type !== "comment");
|
|
465
|
+
const [, el] = nodeRange(child);
|
|
466
|
+
const sl = (declarationStart ?? child).startPosition.row + 1;
|
|
411
467
|
|
|
412
468
|
return { name, type: symbolType, startLine: sl, endLine: el };
|
|
413
469
|
}
|
|
@@ -477,30 +533,15 @@ function extractPython(
|
|
|
477
533
|
|
|
478
534
|
let symbol: SymbolInfo | null = null;
|
|
479
535
|
|
|
480
|
-
if (child.type === "
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
symbol =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
} else if (child.type === "function_definition") {
|
|
491
|
-
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
492
|
-
const [sl, el] = nodeRange(child);
|
|
493
|
-
symbol = { name, type: "function", startLine: sl, endLine: el };
|
|
494
|
-
} else if (child.type === "decorated_definition") {
|
|
495
|
-
// Unwrap to the inner definition
|
|
496
|
-
const def = child.namedChild(child.namedChildCount - 1);
|
|
497
|
-
if (def) {
|
|
498
|
-
const inner = extractPythonSingle(def, source);
|
|
499
|
-
if (inner) {
|
|
500
|
-
inner.type = "decorated_" + inner.type;
|
|
501
|
-
symbol = inner;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
536
|
+
if (child.type === "decorated_definition") {
|
|
537
|
+
// The row and span start at the definition line; the decorators above it are excluded.
|
|
538
|
+
const def = child.childForFieldName("definition");
|
|
539
|
+
symbol = def ? extractPythonDefinition(def, source, depth, MAX_DEPTH) : null;
|
|
540
|
+
if (symbol) symbol.type = "decorated_" + symbol.type;
|
|
541
|
+
} else if (child.type === "expression_statement" || child.type === "type_alias_statement") {
|
|
542
|
+
symbol = extractPythonAssignment(child, source);
|
|
543
|
+
} else {
|
|
544
|
+
symbol = extractPythonDefinition(child, source, depth, MAX_DEPTH);
|
|
504
545
|
}
|
|
505
546
|
|
|
506
547
|
if (symbol) results.push(symbol);
|
|
@@ -509,14 +550,21 @@ function extractPython(
|
|
|
509
550
|
return results;
|
|
510
551
|
}
|
|
511
552
|
|
|
512
|
-
function
|
|
553
|
+
function extractPythonDefinition(
|
|
513
554
|
child: Node,
|
|
514
555
|
source: string,
|
|
556
|
+
depth: number,
|
|
557
|
+
maxDepth: number,
|
|
515
558
|
): SymbolInfo | null {
|
|
516
559
|
if (child.type === "class_definition") {
|
|
517
560
|
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
518
561
|
const [sl, el] = nodeRange(child);
|
|
519
|
-
|
|
562
|
+
const symbol: SymbolInfo = { name, type: "class", startLine: sl, endLine: el };
|
|
563
|
+
const body = child.childForFieldName("body");
|
|
564
|
+
if (body && depth < maxDepth) {
|
|
565
|
+
symbol.children = extractPython(body, source, depth + 1);
|
|
566
|
+
}
|
|
567
|
+
return symbol;
|
|
520
568
|
}
|
|
521
569
|
if (child.type === "function_definition") {
|
|
522
570
|
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
@@ -526,6 +574,20 @@ function extractPythonSingle(
|
|
|
526
574
|
return null;
|
|
527
575
|
}
|
|
528
576
|
|
|
577
|
+
/** `NAME = ...`, `NAME: T = ...` and `type NAME = ...` at module or class level; other targets are skipped. */
|
|
578
|
+
function extractPythonAssignment(child: Node, source: string): SymbolInfo | null {
|
|
579
|
+
const [sl, el] = nodeRange(child);
|
|
580
|
+
if (child.type === "type_alias_statement") {
|
|
581
|
+
const left = child.namedChild(0);
|
|
582
|
+
return left ? { name: source.slice(left.startIndex, left.endIndex), type: "type", startLine: sl, endLine: el } : null;
|
|
583
|
+
}
|
|
584
|
+
const assignment = child.namedChild(0);
|
|
585
|
+
if (!assignment || assignment.type !== "assignment") return null;
|
|
586
|
+
const left = assignment.childForFieldName("left");
|
|
587
|
+
if (!left || left.type !== "identifier") return null;
|
|
588
|
+
return { name: source.slice(left.startIndex, left.endIndex), type: "variable", startLine: sl, endLine: el };
|
|
589
|
+
}
|
|
590
|
+
|
|
529
591
|
// ========================================================================
|
|
530
592
|
// Go
|
|
531
593
|
// ========================================================================
|
|
@@ -562,7 +624,7 @@ function extractGo(
|
|
|
562
624
|
const specs: SymbolInfo[] = [];
|
|
563
625
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
564
626
|
const spec = child.namedChild(j);
|
|
565
|
-
if (!spec || spec.type !== "type_spec") continue;
|
|
627
|
+
if (!spec || (spec.type !== "type_spec" && spec.type !== "type_alias")) continue;
|
|
566
628
|
const name = fieldChildText(spec, "name", source) || "<anonymous>";
|
|
567
629
|
const [sl, el] = nodeRange(spec);
|
|
568
630
|
const typeNode = spec.childForFieldName("type");
|
|
@@ -575,60 +637,62 @@ function extractGo(
|
|
|
575
637
|
|
|
576
638
|
// Recurse into struct/interface body
|
|
577
639
|
if (typeNode && depth < MAX_DEPTH) {
|
|
578
|
-
const body =
|
|
579
|
-
|
|
580
|
-
|
|
640
|
+
const body = typeNode.type === "interface_type"
|
|
641
|
+
? typeNode
|
|
642
|
+
: findChildByType(typeNode, ["field_declaration_list"]);
|
|
581
643
|
if (body) {
|
|
582
|
-
|
|
644
|
+
const fields = extractGoFields(body, source);
|
|
645
|
+
if (fields.length > 0) entry.children = fields;
|
|
583
646
|
}
|
|
584
647
|
}
|
|
585
648
|
|
|
586
649
|
specs.push(entry);
|
|
587
650
|
}
|
|
588
|
-
if (
|
|
651
|
+
if (child.children.some((part) => part.type === "(")) {
|
|
652
|
+
const [sl, el] = nodeRange(child);
|
|
653
|
+
symbol = { name: "type", type: "type", startLine: sl, endLine: el, children: specs };
|
|
654
|
+
} else if (specs.length === 1) {
|
|
589
655
|
symbol = specs[0];
|
|
590
|
-
} else if (specs.length > 1) {
|
|
591
|
-
symbol = {
|
|
592
|
-
name: "",
|
|
593
|
-
type: "type",
|
|
594
|
-
startLine: specs[0].startLine,
|
|
595
|
-
endLine: specs[specs.length - 1].endLine,
|
|
596
|
-
children: specs,
|
|
597
|
-
};
|
|
598
656
|
}
|
|
599
657
|
if (symbol) results.push(symbol);
|
|
600
658
|
continue;
|
|
601
659
|
} else if (child.type === "const_declaration") {
|
|
602
660
|
const [sl, el] = nodeRange(child);
|
|
603
|
-
const
|
|
661
|
+
const specs: SymbolInfo[] = [];
|
|
604
662
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
605
663
|
const spec = child.namedChild(j);
|
|
606
|
-
if (spec
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
664
|
+
if (!spec || spec.type !== "const_spec") continue;
|
|
665
|
+
const name = fieldChildText(spec, "name", source) || "<anonymous>";
|
|
666
|
+
const [specStart, specEnd] = nodeRange(spec);
|
|
667
|
+
specs.push({ name, type: "const", startLine: specStart, endLine: specEnd });
|
|
610
668
|
}
|
|
669
|
+
const grouped = child.children.some((part) => part.type === "(");
|
|
611
670
|
symbol = {
|
|
612
|
-
name:
|
|
671
|
+
name: grouped ? "const" : specs.map((spec) => spec.name).join(", ") || "const",
|
|
613
672
|
type: "const",
|
|
614
673
|
startLine: sl,
|
|
615
674
|
endLine: el,
|
|
675
|
+
...(grouped ? { children: specs } : {}),
|
|
616
676
|
};
|
|
617
677
|
} else if (child.type === "var_declaration") {
|
|
618
678
|
const [sl, el] = nodeRange(child);
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
679
|
+
const specs: SymbolInfo[] = [];
|
|
680
|
+
const list = findChildByType(child, ["var_spec_list"]);
|
|
681
|
+
const container = list ?? child;
|
|
682
|
+
for (let j = 0; j < container.namedChildCount; j++) {
|
|
683
|
+
const spec = container.namedChild(j);
|
|
684
|
+
if (!spec || spec.type !== "var_spec") continue;
|
|
685
|
+
const name = fieldChildText(spec, "name", source) || "<anonymous>";
|
|
686
|
+
const [specStart, specEnd] = nodeRange(spec);
|
|
687
|
+
specs.push({ name, type: "var", startLine: specStart, endLine: specEnd });
|
|
626
688
|
}
|
|
689
|
+
const grouped = list !== null;
|
|
627
690
|
symbol = {
|
|
628
|
-
name:
|
|
691
|
+
name: grouped ? "var" : specs.map((spec) => spec.name).join(", ") || "var",
|
|
629
692
|
type: "var",
|
|
630
693
|
startLine: sl,
|
|
631
694
|
endLine: el,
|
|
695
|
+
...(grouped ? { children: specs } : {}),
|
|
632
696
|
};
|
|
633
697
|
}
|
|
634
698
|
|
|
@@ -646,14 +710,16 @@ function extractGoFields(
|
|
|
646
710
|
|
|
647
711
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
648
712
|
const child = node.namedChild(i);
|
|
649
|
-
if (!child ||
|
|
713
|
+
if (!child || !["field_declaration", "method_elem", "type_elem"].includes(child.type)) continue;
|
|
650
714
|
|
|
651
|
-
const
|
|
715
|
+
const first = child.namedChild(0);
|
|
716
|
+
const name = fieldChildText(child, "name", source) ||
|
|
717
|
+
(first ? source.slice(first.startIndex, first.endIndex) : "");
|
|
652
718
|
if (!name) continue;
|
|
653
719
|
const [sl, el] = nodeRange(child);
|
|
654
720
|
results.push({
|
|
655
721
|
name,
|
|
656
|
-
type: "field",
|
|
722
|
+
type: child.type === "method_elem" ? "method" : "field",
|
|
657
723
|
startLine: sl,
|
|
658
724
|
endLine: el,
|
|
659
725
|
});
|
|
@@ -670,6 +736,7 @@ function extractRust(
|
|
|
670
736
|
node: Node,
|
|
671
737
|
source: string,
|
|
672
738
|
depth: number,
|
|
739
|
+
inImplOrTrait = false,
|
|
673
740
|
): SymbolInfo[] {
|
|
674
741
|
const results: SymbolInfo[] = [];
|
|
675
742
|
const MAX_DEPTH = depth + 10;
|
|
@@ -678,88 +745,77 @@ function extractRust(
|
|
|
678
745
|
const child = node.namedChild(i);
|
|
679
746
|
if (!child) continue;
|
|
680
747
|
|
|
748
|
+
const [sl, el] = nodeRange(child);
|
|
749
|
+
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
681
750
|
let symbol: SymbolInfo | null = null;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
: "";
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
: "";
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
751
|
+
// Body whose items become children, and whether its functions are methods.
|
|
752
|
+
let body: Node | null = null;
|
|
753
|
+
let bodyIsImplOrTrait = false;
|
|
754
|
+
|
|
755
|
+
switch (child.type) {
|
|
756
|
+
case "function_item":
|
|
757
|
+
case "function_signature_item":
|
|
758
|
+
symbol = { name, type: inImplOrTrait ? "method" : "function", startLine: sl, endLine: el };
|
|
759
|
+
break;
|
|
760
|
+
case "struct_item":
|
|
761
|
+
case "union_item":
|
|
762
|
+
symbol = { name, type: child.type === "struct_item" ? "struct" : "union", startLine: sl, endLine: el };
|
|
763
|
+
body = child.childForFieldName("body");
|
|
764
|
+
break;
|
|
765
|
+
case "enum_item":
|
|
766
|
+
symbol = { name, type: "enum", startLine: sl, endLine: el };
|
|
767
|
+
body = child.childForFieldName("body");
|
|
768
|
+
break;
|
|
769
|
+
case "field_declaration":
|
|
770
|
+
symbol = { name, type: "field", startLine: sl, endLine: el };
|
|
771
|
+
break;
|
|
772
|
+
case "enum_variant":
|
|
773
|
+
symbol = { name, type: "enum_variant", startLine: sl, endLine: el };
|
|
774
|
+
break;
|
|
775
|
+
case "trait_item":
|
|
776
|
+
symbol = { name, type: "trait", startLine: sl, endLine: el };
|
|
777
|
+
body = child.childForFieldName("body");
|
|
778
|
+
bodyIsImplOrTrait = true;
|
|
779
|
+
break;
|
|
780
|
+
case "impl_item": {
|
|
781
|
+
const trait = child.childForFieldName("trait");
|
|
782
|
+
const implType = child.childForFieldName("type");
|
|
783
|
+
const traitStr = trait ? source.slice(trait.startIndex, trait.endIndex) + " for " : "";
|
|
784
|
+
const typeStr = implType ? source.slice(implType.startIndex, implType.endIndex) : "";
|
|
785
|
+
symbol = { name: `impl ${traitStr}${typeStr}`, type: "impl", startLine: sl, endLine: el };
|
|
786
|
+
body = child.childForFieldName("body");
|
|
787
|
+
bodyIsImplOrTrait = true;
|
|
788
|
+
break;
|
|
716
789
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
790
|
+
case "mod_item":
|
|
791
|
+
symbol = { name, type: "module", startLine: sl, endLine: el };
|
|
792
|
+
body = child.childForFieldName("body");
|
|
793
|
+
break;
|
|
794
|
+
case "foreign_mod_item": {
|
|
795
|
+
const modifier = findChildByType(child, ["extern_modifier"]);
|
|
796
|
+
symbol = { name: modifier ? source.slice(modifier.startIndex, modifier.endIndex) : "extern", type: "extern", startLine: sl, endLine: el };
|
|
797
|
+
body = child.childForFieldName("body") ?? findChildByType(child, ["declaration_list"]);
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
case "type_item":
|
|
801
|
+
case "associated_type":
|
|
802
|
+
symbol = { name, type: "type", startLine: sl, endLine: el };
|
|
803
|
+
break;
|
|
804
|
+
case "const_item":
|
|
805
|
+
symbol = { name, type: "const", startLine: sl, endLine: el };
|
|
806
|
+
break;
|
|
807
|
+
case "static_item":
|
|
808
|
+
symbol = { name, type: "static", startLine: sl, endLine: el };
|
|
809
|
+
break;
|
|
810
|
+
case "macro_definition":
|
|
811
|
+
symbol = { name: `${name}!`, type: "macro", startLine: sl, endLine: el };
|
|
812
|
+
break;
|
|
729
813
|
}
|
|
730
814
|
|
|
731
|
-
if (symbol)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
return results;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function extractRustImplBody(
|
|
738
|
-
node: Node,
|
|
739
|
-
source: string,
|
|
740
|
-
): SymbolInfo[] {
|
|
741
|
-
const results: SymbolInfo[] = [];
|
|
742
|
-
|
|
743
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
744
|
-
const child = node.namedChild(i);
|
|
745
|
-
if (!child) continue;
|
|
746
|
-
|
|
747
|
-
let symbol: SymbolInfo | null = null;
|
|
748
|
-
|
|
749
|
-
if (child.type === "function_item") {
|
|
750
|
-
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
751
|
-
const [sl, el] = nodeRange(child);
|
|
752
|
-
symbol = { name, type: "method", startLine: sl, endLine: el };
|
|
753
|
-
} else if (child.type === "const_item") {
|
|
754
|
-
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
755
|
-
const [sl, el] = nodeRange(child);
|
|
756
|
-
symbol = { name, type: "const", startLine: sl, endLine: el };
|
|
757
|
-
} else if (child.type === "type_item") {
|
|
758
|
-
const name = fieldChildText(child, "name", source) || "<anonymous>";
|
|
759
|
-
const [sl, el] = nodeRange(child);
|
|
760
|
-
symbol = { name, type: "type", startLine: sl, endLine: el };
|
|
815
|
+
if (symbol && body && depth < MAX_DEPTH) {
|
|
816
|
+
const children = extractRust(body, source, depth + 1, bodyIsImplOrTrait);
|
|
817
|
+
if (children.length > 0) symbol.children = children;
|
|
761
818
|
}
|
|
762
|
-
|
|
763
819
|
if (symbol) results.push(symbol);
|
|
764
820
|
}
|
|
765
821
|
|
|
@@ -1352,84 +1408,72 @@ function extractSolidityImports(node: Node, source: string): ImportInfo[] {
|
|
|
1352
1408
|
*/
|
|
1353
1409
|
function extractMarkdown(source: string): SymbolInfo[] {
|
|
1354
1410
|
const lines = source.split("\n");
|
|
1411
|
+
// A trailing newline does not start another line.
|
|
1412
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
1355
1413
|
const results: SymbolInfo[] = [];
|
|
1356
|
-
|
|
1414
|
+
|
|
1357
1415
|
// Track heading hierarchy for nesting
|
|
1358
1416
|
const stack: SymbolInfo[] = [];
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
1364
|
-
|
|
1365
|
-
if (headingMatch) {
|
|
1366
|
-
const level = headingMatch[1].length;
|
|
1367
|
-
const title = headingMatch[2].trim();
|
|
1368
|
-
const startLine = i + 1; // 1-indexed
|
|
1369
|
-
|
|
1370
|
-
const symbol: SymbolInfo = {
|
|
1371
|
-
name: title,
|
|
1372
|
-
type: `heading${level}`,
|
|
1373
|
-
startLine,
|
|
1374
|
-
endLine: lines.length, // Will be updated later
|
|
1375
|
-
children: [],
|
|
1376
|
-
};
|
|
1377
|
-
|
|
1378
|
-
// Find parent: pop stack until we find a heading with lower level
|
|
1379
|
-
while (stack.length > 0) {
|
|
1380
|
-
const parentLevel = parseInt(stack[stack.length - 1].type?.replace('heading', '') || '0', 10);
|
|
1381
|
-
if (parentLevel >= level) {
|
|
1382
|
-
stack.pop();
|
|
1383
|
-
} else {
|
|
1384
|
-
break;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// Add to parent or root
|
|
1389
|
-
if (stack.length > 0) {
|
|
1390
|
-
stack[stack.length - 1].children!.push(symbol);
|
|
1391
|
-
} else {
|
|
1392
|
-
results.push(symbol);
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
stack.push(symbol);
|
|
1417
|
+
const addHeading = (level: number, title: string, startLine: number): void => {
|
|
1418
|
+
const symbol: SymbolInfo = { name: title, type: `heading${level}`, startLine, endLine: lines.length, children: [] };
|
|
1419
|
+
while (stack.length > 0 && parseInt(stack[stack.length - 1].type?.replace("heading", "") || "0", 10) >= level) {
|
|
1420
|
+
stack.pop();
|
|
1396
1421
|
}
|
|
1422
|
+
if (stack.length > 0) stack[stack.length - 1].children!.push(symbol);
|
|
1423
|
+
else results.push(symbol);
|
|
1424
|
+
stack.push(symbol);
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
let fence: { char: string; length: number } | null = null;
|
|
1428
|
+
// Whether the previous line is paragraph text that a setext underline can turn into a heading.
|
|
1429
|
+
let paragraph = false;
|
|
1430
|
+
let i = 0;
|
|
1431
|
+
|
|
1432
|
+
if (lines[0]?.trim() === "---") {
|
|
1433
|
+
const close = lines.findIndex((line, index) => index > 0 && (line.trim() === "---" || line.trim() === "..."));
|
|
1434
|
+
const first = close === -1 ? undefined : lines.slice(1, close).find((line) => {
|
|
1435
|
+
const trimmed = line.trim();
|
|
1436
|
+
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1437
|
+
});
|
|
1438
|
+
if (first !== undefined && /^[A-Za-z0-9_.-]+\s*:/.test(first.trim())) i = close + 1;
|
|
1397
1439
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
const language = match[1];
|
|
1407
|
-
const startLine = source.slice(0, match.index).split("\n").length;
|
|
1408
|
-
|
|
1409
|
-
// Find closing ```
|
|
1410
|
-
const afterMatch = source.slice(match.index + match[0].length);
|
|
1411
|
-
const closingIdx = afterMatch.search(/^```\s*$/m);
|
|
1412
|
-
let endLine = startLine + 1;
|
|
1413
|
-
if (closingIdx !== -1) {
|
|
1414
|
-
endLine = startLine + afterMatch.slice(0, closingIdx).split("\n").length + 1;
|
|
1440
|
+
|
|
1441
|
+
for (; i < lines.length; i++) {
|
|
1442
|
+
const line = lines[i];
|
|
1443
|
+
|
|
1444
|
+
if (fence) {
|
|
1445
|
+
const close = line.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
1446
|
+
if (close && close[1][0] === fence.char && close[1].length >= fence.length) fence = null;
|
|
1447
|
+
continue;
|
|
1415
1448
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
endLine,
|
|
1422
|
-
};
|
|
1423
|
-
|
|
1424
|
-
// Find the heading that contains this code block
|
|
1425
|
-
const parent = findParentHeading(results, startLine);
|
|
1426
|
-
if (parent) {
|
|
1427
|
-
parent.children!.push(codeBlock);
|
|
1428
|
-
} else {
|
|
1429
|
-
results.push(codeBlock);
|
|
1449
|
+
const open = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
1450
|
+
if (open) {
|
|
1451
|
+
fence = { char: open[1][0], length: open[1].length };
|
|
1452
|
+
paragraph = false;
|
|
1453
|
+
continue;
|
|
1430
1454
|
}
|
|
1455
|
+
|
|
1456
|
+
const atx = line.match(/^ {0,3}(#{1,6})[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/);
|
|
1457
|
+
if (atx && atx[2].length > 0) {
|
|
1458
|
+
addHeading(atx[1].length, atx[2].trim(), i + 1);
|
|
1459
|
+
paragraph = false;
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const setext = paragraph ? line.match(/^ {0,3}(=+|-+)[ \t]*$/) : null;
|
|
1464
|
+
if (setext) {
|
|
1465
|
+
addHeading(setext[1][0] === "=" ? 1 : 2, lines[i - 1].trim(), i);
|
|
1466
|
+
paragraph = false;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// Blank lines, indented code, quotes, list items and thematic breaks are not setext paragraph text.
|
|
1471
|
+
paragraph = line.trim() !== "" && !/^( {4}|\t| {0,3}(>|[-*+][ \t]|\d+[.)][ \t]|[-*_]{3,}\s*$))/.test(line);
|
|
1431
1472
|
}
|
|
1432
|
-
|
|
1473
|
+
|
|
1474
|
+
// Update endLine for each heading: it ends just before the next sibling or parent's next sibling
|
|
1475
|
+
updateEndLines(results, lines.length);
|
|
1476
|
+
|
|
1433
1477
|
return results;
|
|
1434
1478
|
}
|
|
1435
1479
|
|
|
@@ -1449,31 +1493,8 @@ function updateEndLines(symbols: SymbolInfo[], totalLines: number): void {
|
|
|
1449
1493
|
// Recurse into children
|
|
1450
1494
|
if (symbol.children && symbol.children.length > 0) {
|
|
1451
1495
|
updateEndLines(symbol.children, symbol.endLine);
|
|
1452
|
-
// The last child's endLine might extend to parent's endLine
|
|
1453
|
-
const lastChild = symbol.children[symbol.children.length - 1];
|
|
1454
|
-
if (lastChild.endLine < symbol.endLine) {
|
|
1455
|
-
lastChild.endLine = symbol.endLine;
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
/** Find the heading that contains the given line number. */
|
|
1462
|
-
function findParentHeading(symbols: SymbolInfo[], line: number): SymbolInfo | null {
|
|
1463
|
-
for (const symbol of symbols) {
|
|
1464
|
-
if (symbol.type?.startsWith("heading")) {
|
|
1465
|
-
// Check if line is within this heading's range
|
|
1466
|
-
if (line >= symbol.startLine && line <= symbol.endLine) {
|
|
1467
|
-
// Try to find a more specific child
|
|
1468
|
-
if (symbol.children && symbol.children.length > 0) {
|
|
1469
|
-
const child = findParentHeading(symbol.children, line);
|
|
1470
|
-
if (child) return child;
|
|
1471
|
-
}
|
|
1472
|
-
return symbol;
|
|
1473
|
-
}
|
|
1474
1496
|
}
|
|
1475
1497
|
}
|
|
1476
|
-
return null;
|
|
1477
1498
|
}
|
|
1478
1499
|
|
|
1479
1500
|
/**
|