2023-08-01 19:35:19 +00:00
|
|
|
import type { CompleteEvent } from "$sb/app_event.ts";
|
2023-08-28 15:12:15 +00:00
|
|
|
import { events } from "$sb/syscalls.ts";
|
2023-12-21 17:37:50 +00:00
|
|
|
import { queryObjects } from "./api.ts";
|
2023-10-03 12:16:33 +00:00
|
|
|
import { ObjectValue, QueryExpression } from "$sb/types.ts";
|
2023-12-21 17:37:50 +00:00
|
|
|
import { determineTags } from "./cheap_yaml.ts";
|
2023-08-01 19:35:19 +00:00
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
export type AttributeObject = ObjectValue<{
|
|
|
|
name: string;
|
|
|
|
attributeType: string;
|
|
|
|
tag: string;
|
|
|
|
page: string;
|
2023-12-21 17:37:50 +00:00
|
|
|
readOnly: boolean;
|
2023-10-03 12:16:33 +00:00
|
|
|
}>;
|
2023-08-01 19:35:19 +00:00
|
|
|
|
2023-08-08 14:35:46 +00:00
|
|
|
export type AttributeCompleteEvent = {
|
|
|
|
source: string;
|
|
|
|
prefix: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type AttributeCompletion = {
|
|
|
|
name: string;
|
|
|
|
source: string;
|
2023-10-03 12:16:33 +00:00
|
|
|
attributeType: string;
|
2023-12-21 17:37:50 +00:00
|
|
|
readOnly: boolean;
|
2023-08-08 14:35:46 +00:00
|
|
|
};
|
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
export function determineType(v: any): string {
|
2023-08-01 19:35:19 +00:00
|
|
|
const t = typeof v;
|
|
|
|
if (t === "object") {
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
return "array";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
2023-12-21 17:37:50 +00:00
|
|
|
/**
|
|
|
|
* Triggered by the `attribute:complete:*` event (that is: gimme all attribute completions)
|
|
|
|
* @param attributeCompleteEvent
|
|
|
|
* @returns
|
|
|
|
*/
|
2023-10-03 12:16:33 +00:00
|
|
|
export async function objectAttributeCompleter(
|
2023-08-08 14:35:46 +00:00
|
|
|
attributeCompleteEvent: AttributeCompleteEvent,
|
|
|
|
): Promise<AttributeCompletion[]> {
|
2023-12-21 17:37:50 +00:00
|
|
|
const prefixFilter: QueryExpression = ["call", "startsWith", [[
|
|
|
|
"attr",
|
|
|
|
"name",
|
|
|
|
], ["string", attributeCompleteEvent.prefix]]];
|
2023-10-03 12:16:33 +00:00
|
|
|
const attributeFilter: QueryExpression | undefined =
|
|
|
|
attributeCompleteEvent.source === ""
|
2023-12-21 17:37:50 +00:00
|
|
|
? prefixFilter
|
|
|
|
: ["and", prefixFilter, ["=", ["attr", "tag"], [
|
|
|
|
"string",
|
|
|
|
attributeCompleteEvent.source,
|
|
|
|
]]];
|
2023-10-03 12:16:33 +00:00
|
|
|
const allAttributes = await queryObjects<AttributeObject>("attribute", {
|
|
|
|
filter: attributeFilter,
|
2023-12-21 17:37:50 +00:00
|
|
|
distinct: true,
|
|
|
|
select: [{ name: "name" }, { name: "attributeType" }, { name: "tag" }, {
|
|
|
|
name: "readOnly",
|
|
|
|
}],
|
2023-08-08 14:35:46 +00:00
|
|
|
});
|
2023-10-03 12:16:33 +00:00
|
|
|
return allAttributes.map((value) => {
|
2023-08-08 14:35:46 +00:00
|
|
|
return {
|
2023-10-03 12:16:33 +00:00
|
|
|
name: value.name,
|
|
|
|
source: value.tag,
|
|
|
|
attributeType: value.attributeType,
|
2023-12-21 17:37:50 +00:00
|
|
|
readOnly: value.readOnly,
|
2023-10-03 12:16:33 +00:00
|
|
|
} as AttributeCompletion;
|
2023-08-08 14:35:46 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-21 17:37:50 +00:00
|
|
|
/**
|
|
|
|
* Offer completions for _setting_ attributes on objects (either in frontmatter or inline)
|
|
|
|
* Triggered by `editor:complete` events from the editor
|
|
|
|
*/
|
2023-08-01 19:35:19 +00:00
|
|
|
export async function attributeComplete(completeEvent: CompleteEvent) {
|
2023-09-01 14:57:29 +00:00
|
|
|
if (/([\-\*]\s+\[)([^\]]+)$/.test(completeEvent.linePrefix)) {
|
|
|
|
// Don't match task states, which look similar
|
|
|
|
return null;
|
|
|
|
}
|
2023-12-21 17:37:50 +00:00
|
|
|
|
|
|
|
// Inline attribute completion (e.g. [myAttr: 10])
|
2023-08-04 19:35:58 +00:00
|
|
|
const inlineAttributeMatch = /([^\[\{}]|^)\[(\w+)$/.exec(
|
2023-08-01 19:35:19 +00:00
|
|
|
completeEvent.linePrefix,
|
|
|
|
);
|
|
|
|
if (inlineAttributeMatch) {
|
|
|
|
// console.log("Parents", completeEvent.parentNodes);
|
|
|
|
let type = "page";
|
|
|
|
if (completeEvent.parentNodes.includes("Task")) {
|
|
|
|
type = "task";
|
|
|
|
} else if (completeEvent.parentNodes.includes("ListItem")) {
|
|
|
|
type = "item";
|
|
|
|
}
|
2023-08-08 14:35:46 +00:00
|
|
|
const completions = (await events.dispatchEvent(
|
|
|
|
`attribute:complete:${type}`,
|
|
|
|
{
|
|
|
|
source: type,
|
|
|
|
prefix: inlineAttributeMatch[2],
|
|
|
|
} as AttributeCompleteEvent,
|
|
|
|
)).flat() as AttributeCompletion[];
|
2023-08-01 19:35:19 +00:00
|
|
|
return {
|
|
|
|
from: completeEvent.pos - inlineAttributeMatch[2].length,
|
2023-08-08 14:35:46 +00:00
|
|
|
options: attributeCompletionsToCMCompletion(
|
2023-12-21 17:37:50 +00:00
|
|
|
// Filter out read-only attributes
|
|
|
|
completions.filter((completion) => !completion.readOnly),
|
2023-08-08 14:35:46 +00:00
|
|
|
),
|
2023-08-01 19:35:19 +00:00
|
|
|
};
|
|
|
|
}
|
2023-12-21 17:37:50 +00:00
|
|
|
|
|
|
|
// Frontmatter attribute completion
|
2023-08-01 19:35:19 +00:00
|
|
|
const attributeMatch = /^(\w+)$/.exec(completeEvent.linePrefix);
|
|
|
|
if (attributeMatch) {
|
2023-12-21 17:37:50 +00:00
|
|
|
const frontmatterParent = completeEvent.parentNodes.find((node) =>
|
|
|
|
node.startsWith("FrontMatter:")
|
|
|
|
);
|
|
|
|
if (frontmatterParent) {
|
|
|
|
const tags = [
|
2023-10-09 18:39:03 +00:00
|
|
|
"page",
|
2023-12-21 17:37:50 +00:00
|
|
|
...determineTags(frontmatterParent.slice("FrontMatter:".length)),
|
|
|
|
];
|
|
|
|
|
2023-10-09 18:39:03 +00:00
|
|
|
const completions = (await Promise.all(tags.map((tag) =>
|
|
|
|
events.dispatchEvent(
|
|
|
|
`attribute:complete:${tag}`,
|
|
|
|
{
|
|
|
|
source: tag,
|
|
|
|
prefix: attributeMatch[1],
|
|
|
|
} as AttributeCompleteEvent,
|
|
|
|
)
|
|
|
|
))).flat(2) as AttributeCompletion[];
|
|
|
|
// console.log("Completions", completions);
|
2023-08-01 19:35:19 +00:00
|
|
|
return {
|
|
|
|
from: completeEvent.pos - attributeMatch[1].length,
|
2023-08-08 14:35:46 +00:00
|
|
|
options: attributeCompletionsToCMCompletion(
|
2023-10-09 18:39:03 +00:00
|
|
|
completions.filter((completion) =>
|
2023-12-21 17:37:50 +00:00
|
|
|
!completion.readOnly
|
2023-10-09 18:39:03 +00:00
|
|
|
),
|
2023-08-08 14:35:46 +00:00
|
|
|
),
|
2023-08-01 19:35:19 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2023-08-08 14:35:46 +00:00
|
|
|
|
|
|
|
export function attributeCompletionsToCMCompletion(
|
|
|
|
completions: AttributeCompletion[],
|
|
|
|
) {
|
|
|
|
return completions.map(
|
|
|
|
(completion) => ({
|
|
|
|
label: completion.name,
|
|
|
|
apply: `${completion.name}: `,
|
2023-10-03 12:16:33 +00:00
|
|
|
detail: `${completion.attributeType} (${completion.source})`,
|
2023-08-08 14:35:46 +00:00
|
|
|
type: "attribute",
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|