anchors: extend Ref grammar with anchor variant ($anchor)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
decodePageURI,
|
||||
encodePageURI,
|
||||
@@ -117,6 +117,50 @@ test("isValidPath() and isValidName()", () => {
|
||||
expect(!isValidName("hello>there")).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("anchor refs", () => {
|
||||
test("parseToRef bare anchor", () => {
|
||||
expect(parseToRef("$pete")).toEqual({
|
||||
path: "",
|
||||
details: { type: "anchor", name: "pete" },
|
||||
});
|
||||
});
|
||||
|
||||
test("parseToRef page-qualified anchor", () => {
|
||||
expect(parseToRef("Some Page$pete")).toEqual({
|
||||
path: "Some Page.md",
|
||||
details: { type: "anchor", name: "pete" },
|
||||
});
|
||||
});
|
||||
|
||||
test("parseToRef accepts allowed character classes", () => {
|
||||
for (const name of ["toc1", "tasks/7", "a:b", "a-b", "_foo"]) {
|
||||
const ref = parseToRef("$" + name);
|
||||
expect(ref?.details).toEqual({ type: "anchor", name });
|
||||
}
|
||||
});
|
||||
|
||||
test("parseToRef rejects digit-leading anchors", () => {
|
||||
// "$100" should NOT be parsed as an anchor; falling through to
|
||||
// the path branch should fail because the path would be "$100"
|
||||
// which is not a valid path either.
|
||||
expect(parseToRef("$100")).toBeNull();
|
||||
});
|
||||
|
||||
test("encodeRef round-trips bare anchor", () => {
|
||||
expect(encodeRef({ path: "", details: { type: "anchor", name: "pete" } }))
|
||||
.toBe("$pete");
|
||||
});
|
||||
|
||||
test("encodeRef round-trips page-qualified anchor", () => {
|
||||
expect(
|
||||
encodeRef({
|
||||
path: "Some Page.md",
|
||||
details: { type: "anchor", name: "pete" },
|
||||
}),
|
||||
).toBe("Some Page$pete");
|
||||
});
|
||||
});
|
||||
|
||||
test("Page URI encoding", () => {
|
||||
expect(encodePageURI("foo")).toEqual("foo");
|
||||
expect(encodePageURI("folder/foo")).toEqual("folder/foo");
|
||||
|
||||
+9
-2
@@ -23,7 +23,8 @@ export type Ref = {
|
||||
details?:
|
||||
| { type: "position"; pos: number }
|
||||
| { type: "linecolumn"; line: number; column: number }
|
||||
| { type: "header"; header: string };
|
||||
| { type: "header"; header: string }
|
||||
| { type: "anchor"; name: string };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -96,7 +97,7 @@ export function isValidPath(path: string): path is Path {
|
||||
* TO THE INNER WORKINGS OF SILVERBULLET AND CHANGES COULD INTRODUCE MAJOR BUGS
|
||||
*/
|
||||
const refRegex =
|
||||
/^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*))?$/;
|
||||
/^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>$]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*)|\$(?<anchor>[A-Za-z_][A-Za-z0-9_/:-]*))?$/;
|
||||
|
||||
/**
|
||||
* Parses a reference string into a ref object.
|
||||
@@ -132,6 +133,8 @@ export function parseToRef(stringRef: string): Ref | null {
|
||||
type: "header",
|
||||
header: groups.header,
|
||||
};
|
||||
} else if (groups.anchor !== undefined) {
|
||||
ref.details = { type: "anchor", name: groups.anchor };
|
||||
}
|
||||
|
||||
return ref;
|
||||
@@ -158,6 +161,8 @@ export function encodeRef(ref: Ref): string {
|
||||
stringRef += `@${ref.details.pos}`;
|
||||
} else if (ref.details?.type === "header") {
|
||||
stringRef += `#${ref.details.header}`;
|
||||
} else if (ref.details?.type === "anchor") {
|
||||
stringRef += `$${ref.details.name}`;
|
||||
}
|
||||
|
||||
return stringRef;
|
||||
@@ -191,6 +196,8 @@ export function getOffsetFromRef(
|
||||
case "header": {
|
||||
return getOffsetFromHeader(parseTree, ref.details.header);
|
||||
}
|
||||
case "anchor":
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user