Skip to content

Commit 8d4695c

Browse files
committed
Fixes #736 adds auto complete of tags: key in frontmatter
1 parent 6c94a8f commit 8d4695c

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

plugs/index/index.plug.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ functions:
6060
events:
6161
- editor:complete
6262

63+
frontmatterTagComplete:
64+
path: tags.ts:frontmatterTagComplete
65+
events:
66+
- editor:complete
67+
6368
frontmatterComplete:
6469
path: frontmatter.ts:frontmatterComplete
6570
events:

plugs/index/tags.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,14 @@ export async function tagComplete(completeEvent: CompleteEvent) {
6969
return null;
7070
}
7171

72-
const match = /#[^#\s[\]]+\w*$/.exec(completeEvent.linePrefix);
72+
const match = /#[^#\s[\]]*$/.exec(completeEvent.linePrefix);
7373
if (!match) {
7474
return null;
7575
}
76+
// Don't trigger on markdown headers (# Heading, ## Heading, etc.)
77+
if (match.index === 0 && /^#{1,6}(\s|$)/.test(completeEvent.linePrefix)) {
78+
return null;
79+
}
7680

7781
// Query all tags with a matching parent
7882
const allTags: string[] = await index.queryLuaObjects<string>("tag", {
@@ -89,6 +93,73 @@ export async function tagComplete(completeEvent: CompleteEvent) {
8993
};
9094
}
9195

96+
export async function frontmatterTagComplete(completeEvent: CompleteEvent) {
97+
// Only trigger inside frontmatter
98+
const frontmatterNode = completeEvent.parentNodes.find((n) =>
99+
n.startsWith("FrontMatter:")
100+
);
101+
if (!frontmatterNode) {
102+
return null;
103+
}
104+
105+
const fmContent = frontmatterNode.substring("FrontMatter:".length);
106+
107+
// Determine if the cursor line is within a tags section
108+
// Pattern 1: tags: value or tags: [v1, v2, partial (comma or space separated)
109+
const tagsLineMatch = /tags:\s+\[?(?:.*[,\s]\s*)?([^\s!@$%^&*(),.?":{}|<>\\[\]]*)$/.exec(
110+
completeEvent.linePrefix,
111+
);
112+
113+
let prefix = "";
114+
if (tagsLineMatch) {
115+
prefix = tagsLineMatch[1];
116+
} else {
117+
// Pattern 2: list item under a tags: key (e.g. " - partial")
118+
const listItemMatch = /^\s+-\s+([^\s!@$%^&*(),.?":{}|<>\\[\]]*)$/.exec(completeEvent.linePrefix);
119+
if (!listItemMatch) {
120+
return null;
121+
}
122+
123+
// Check if this list item is under the tags: key using cheap YAML parsing
124+
const lines = fmContent.split("\n");
125+
const cursorLineText = completeEvent.linePrefix;
126+
let inTagsSection = false;
127+
let foundCursorInTags = false;
128+
129+
for (const line of lines) {
130+
const kvMatch = /^\s*(\w+):/.exec(line);
131+
if (kvMatch) {
132+
inTagsSection = kvMatch[1] === "tags";
133+
}
134+
if (inTagsSection && line.trimEnd() === cursorLineText.trimEnd()) {
135+
foundCursorInTags = true;
136+
break;
137+
}
138+
}
139+
140+
if (!foundCursorInTags) {
141+
return null;
142+
}
143+
prefix = listItemMatch[1];
144+
}
145+
146+
// Strip # prefix if present (frontmatter tags can use #tag syntax)
147+
const cleanPrefix = prefix.replace(/^#/, "");
148+
149+
const allTags: string[] = await index.queryLuaObjects<string>("tag", {
150+
distinct: true,
151+
select: { type: "Variable", name: "name", ctx: {} as any },
152+
});
153+
154+
return {
155+
from: completeEvent.pos - prefix.length,
156+
options: allTags.map((tag) => ({
157+
label: tag,
158+
type: "tag",
159+
})),
160+
};
161+
}
162+
92163
export function updateITags<T>(obj: ObjectValue<T>, frontmatter: FrontMatter) {
93164
const itags = new Set<string>([obj.tag, ...(frontmatter.tags || [])]);
94165
for (const tag of obj.tags || []) {

0 commit comments

Comments
 (0)