@heroiclands/content-language-server 0.2.0 → 0.2.2
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.
|
@@ -37,13 +37,13 @@ An explicitly configured foreign project can be searched even when it is not a d
|
|
|
37
37
|
|
|
38
38
|
### Address completion
|
|
39
39
|
|
|
40
|
-
Completion inside
|
|
40
|
+
Completion inside a `[[...` link searches the selected projects' saved indexes, including when an editor inserts the closing `]]` after the cursor. The query matches anywhere in a note's full name, aliases, shortcode, short Address, or canonical Address. The existing `nameAscii` and `aliasesAscii` fields supply typeable forms of names with non-ASCII characters. The server builds lowercase search keys in memory when it loads an index and transliterates the query with the same rule. The JSONL format carries no separate search field.
|
|
41
41
|
|
|
42
|
-
Each result identifies its owning package and canonical Address. Readable `note` content, systemless `none` targets, and game system documents stay selectable even when they share a Markdown file. An ordinary `[[link|text]]` defaults to `note`, while an embedded `![[link|text]]` defaults to `none` and uses `image` as the type of a bare shortcode. Stating a system selects that exact target: `[[macro-autoattack|Text]]` reaches the readable note, and `[[none-macro-autoattack|Text]]` reaches the Macro. An embed offers asset targets and can reach an icon with `![[icon-anvil|Anvil]]`.
|
|
42
|
+
Each result identifies its owning package and canonical Address. Address completion items also carry `data.address` with that canonical Address, `data.display` with the authored name or exactly matched alias, and `data.exact` to distinguish a full name or alias match from a substring match. Editors can use these fields when completing a link's display text without merging targets that share a file or name. Readable `note` content, systemless `none` targets, and game system documents stay selectable even when they share a Markdown file. An ordinary `[[link|text]]` defaults to `note`, while an embedded `![[link|text]]` defaults to `none` and uses `image` as the type of a bare shortcode. Stating a system selects that exact target: `[[macro-autoattack|Text]]` reaches the readable note, and `[[none-macro-autoattack|Text]]` reaches the Macro. An embed offers asset targets and can reach an icon with `![[icon-anvil|Anvil]]`.
|
|
43
43
|
|
|
44
44
|
The text edit inserts the shortest Address that parses to the selected target at the cursor. A local game system document can require `sohl-being-bctrncml`, while a target in another package requires a fully qualified Address. A `[[...#` query completes anchors from the selected note. Completion in a declared frontmatter Address field uses that field's type and system defaults: image, icon, and folder fields use `none`; other fields use their game system block or `note` outside one. Explicit system segments keep their stated meaning. Input `doc<type>` aliases select readable note records; generated records and completion candidates use the authored type.
|
|
45
45
|
|
|
46
|
-
The server reads open buffer text to locate the cursor and replacement range. Candidate metadata comes from the saved JSONL index; unsaved frontmatter changes do not add candidates. Completion
|
|
46
|
+
The server reads open buffer text to locate the cursor and replacement range. Candidate metadata comes from the saved JSONL index; unsaved frontmatter changes do not add candidates. Completion preserves closing brackets immediately after the cursor and ignores positions within existing display text. A missing foreign index is reported through the usual LSP status message while other indexed projects remain available.
|
|
47
47
|
|
|
48
48
|
## Foreign content projects
|
|
49
49
|
|
|
@@ -82,6 +82,15 @@ function searchKey(value) {
|
|
|
82
82
|
return typeof value === "string" ? (asciiName(value) ?? "").toLowerCase() : "";
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** Preserve the authored spelling when a query exactly names a note or alias. */
|
|
86
|
+
function completionDisplay(record, query) {
|
|
87
|
+
const full = noteName(record);
|
|
88
|
+
const needle = searchKey(query);
|
|
89
|
+
const names = [full, ...(record.name?.aliases ?? [])];
|
|
90
|
+
const exact = needle ? names.find((name) => searchKey(name) === needle) : null;
|
|
91
|
+
return { display: exact ?? full, exact: Boolean(exact) };
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
/** Parse editor symbol filters without looking outside configured projects. */
|
|
86
95
|
function symbolQuery(query) {
|
|
87
96
|
let text = String(query ?? "").trim();
|
|
@@ -108,12 +117,13 @@ function symbolQuery(query) {
|
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
/** A completion item changes the Address text while its label remains readable. */
|
|
111
|
-
function completionItem(label, detail, inserted, text, from, to, filterText) {
|
|
120
|
+
function completionItem(label, detail, inserted, text, from, to, filterText, data) {
|
|
112
121
|
return {
|
|
113
122
|
label,
|
|
114
123
|
kind: 18,
|
|
115
124
|
detail,
|
|
116
125
|
filterText,
|
|
126
|
+
...(data ? { data } : {}),
|
|
117
127
|
textEdit: {
|
|
118
128
|
range: { start: positionAt(text, from), end: positionAt(text, to) },
|
|
119
129
|
newText: inserted,
|
|
@@ -130,10 +140,11 @@ function mayCompleteAddress(text, offset) {
|
|
|
130
140
|
const lineEnd = text.indexOf("\n", offset);
|
|
131
141
|
const before = text.slice(lineStart, offset);
|
|
132
142
|
const open = before.lastIndexOf("[[");
|
|
143
|
+
const after = text.slice(offset, lineEnd < 0 ? text.length : lineEnd);
|
|
133
144
|
return (
|
|
134
145
|
open >= 0 &&
|
|
135
146
|
!before.slice(open + 2).includes("]]") &&
|
|
136
|
-
!
|
|
147
|
+
(!after.includes("]]") || after.startsWith("]]"))
|
|
137
148
|
);
|
|
138
149
|
}
|
|
139
150
|
|
|
@@ -495,7 +506,8 @@ export class ContentWorkspace {
|
|
|
495
506
|
const open = before.lastIndexOf("[[");
|
|
496
507
|
if (open < 0 || before.slice(open + 2).includes("]]")) return null;
|
|
497
508
|
const embed = open > 0 && before[open - 1] === "!";
|
|
498
|
-
|
|
509
|
+
const after = text.slice(offset, lineEnd < 0 ? text.length : lineEnd);
|
|
510
|
+
if (after.includes("]]") && !after.startsWith("]]")) return null;
|
|
499
511
|
const from = lineStart + open + 2;
|
|
500
512
|
const written = text.slice(from, offset);
|
|
501
513
|
if (written.includes("|") || written.includes("[") || written.includes("]")) return null;
|
|
@@ -644,6 +656,10 @@ export class ContentWorkspace {
|
|
|
644
656
|
context.from,
|
|
645
657
|
context.to,
|
|
646
658
|
context.query,
|
|
659
|
+
{
|
|
660
|
+
address: canonical,
|
|
661
|
+
...completionDisplay(record, context.query),
|
|
662
|
+
},
|
|
647
663
|
),
|
|
648
664
|
);
|
|
649
665
|
}
|