Make attribute extensible

This commit is contained in:
Zef Hemel
2023-08-08 16:35:46 +02:00
parent b7b666ee1d
commit 2f1f266482
4 changed files with 172 additions and 97 deletions
+121 -24
View File
@@ -1,5 +1,6 @@
import { index } from "$sb/silverbullet-syscall/mod.ts";
import type { CompleteEvent } from "$sb/app_event.ts";
import { events } from "$sb/plugos-syscall/mod.ts";
export type AttributeContext = "page" | "item" | "task";
@@ -7,6 +8,47 @@ type AttributeEntry = {
type: string;
};
export type AttributeCompleteEvent = {
source: string;
prefix: string;
};
export type AttributeCompletion = {
name: string;
source: string;
type: string;
builtin?: boolean;
};
const builtinAttributes: Record<string, Record<string, string>> = {
page: {
name: "string",
lastModified: "number",
perm: "rw|ro",
contentType: "string",
size: "number",
tags: "array",
},
task: {
name: "string",
done: "boolean",
page: "string",
deadline: "string",
pos: "number",
tags: "array",
},
item: {
name: "string",
page: "string",
pos: "number",
tags: "array",
},
tag: {
name: "string",
freq: "number",
},
};
function determineType(v: any): string {
const t = typeof v;
if (t === "object") {
@@ -37,6 +79,52 @@ export async function indexAttributes(
);
}
export async function customAttributeCompleter(
attributeCompleteEvent: AttributeCompleteEvent,
): Promise<AttributeCompletion[]> {
const sourcePrefix = attributeCompleteEvent.source === "*"
? ""
: `${attributeCompleteEvent.source}:`;
const allAttributes = await index.queryPrefix(
`${attributeKeyPrefix}${sourcePrefix}`,
);
return allAttributes.map((attr) => {
const [_prefix, context, name] = attr.key.split(":");
return {
name,
source: context,
type: attr.value.type,
};
});
}
export function builtinAttributeCompleter(
attributeCompleteEvent: AttributeCompleteEvent,
): AttributeCompletion[] {
let allAttributes = builtinAttributes[attributeCompleteEvent.source];
if (attributeCompleteEvent.source === "*") {
allAttributes = {};
for (const [source, attributes] of Object.entries(builtinAttributes)) {
for (const [name, type] of Object.entries(attributes)) {
allAttributes[name] = `${type}|${source}`;
}
}
}
if (!allAttributes) {
return [];
}
return Object.entries(allAttributes).map(([name, type]) => {
return {
name,
source: attributeCompleteEvent.source === "*"
? type.split("|")[1]
: attributeCompleteEvent.source,
type: attributeCompleteEvent.source === "*" ? type.split("|")[0] : type,
builtin: true,
};
});
}
export async function attributeComplete(completeEvent: CompleteEvent) {
const inlineAttributeMatch = /([^\[\{}]|^)\[(\w+)$/.exec(
completeEvent.linePrefix,
@@ -49,41 +137,50 @@ export async function attributeComplete(completeEvent: CompleteEvent) {
} else if (completeEvent.parentNodes.includes("ListItem")) {
type = "item";
}
const allAttributes = await index.queryPrefix(
`${attributeKeyPrefix}${type}:`,
);
const completions = (await events.dispatchEvent(
`attribute:complete:${type}`,
{
source: type,
prefix: inlineAttributeMatch[2],
} as AttributeCompleteEvent,
)).flat() as AttributeCompletion[];
return {
from: completeEvent.pos - inlineAttributeMatch[2].length,
options: allAttributes.map((attr) => {
const [_prefix, _context, name] = attr.key.split(":");
return {
label: name,
apply: `${name}: `,
detail: attr.value.type,
type: "attribute",
};
}),
options: attributeCompletionsToCMCompletion(
completions.filter((completion) => !completion.builtin),
),
};
}
const attributeMatch = /^(\w+)$/.exec(completeEvent.linePrefix);
if (attributeMatch) {
if (completeEvent.parentNodes.includes("FrontMatterCode")) {
const allAttributes = await index.queryPrefix(
`${attributeKeyPrefix}page:`,
);
const completions = (await events.dispatchEvent(
`attribute:complete:page`,
{
source: "page",
prefix: attributeMatch[1],
} as AttributeCompleteEvent,
)).flat() as AttributeCompletion[];
return {
from: completeEvent.pos - attributeMatch[1].length,
options: allAttributes.map((attr) => {
const [_prefix, _context, name] = attr.key.split(":");
return {
label: name,
apply: `${name}: `,
detail: attr.value.type,
type: "attribute",
};
}),
options: attributeCompletionsToCMCompletion(
completions.filter((completion) => !completion.builtin),
),
};
}
}
return null;
}
export function attributeCompletionsToCMCompletion(
completions: AttributeCompletion[],
) {
return completions.map(
(completion) => ({
label: completion.name,
apply: `${completion.name}: `,
detail: `${completion.type} (${completion.source})`,
type: "attribute",
}),
);
}
+16
View File
@@ -96,6 +96,22 @@ functions:
events:
- editor:complete
customAttributeCompleter:
path: ./attributes.ts:customAttributeCompleter
events:
- attribute:complete:page
- attribute:complete:task
- attribute:complete:item
- attribute:complete:*
builtinAttributeCompleter:
path: ./attributes.ts:builtinAttributeCompleter
events:
- attribute:complete:page
- attribute:complete:task
- attribute:complete:item
- attribute:complete:*
# Commands
commandComplete:
path: "./command.ts:commandComplete"
+33 -69
View File
@@ -2,36 +2,10 @@ import { events } from "$sb/plugos-syscall/mod.ts";
import { CompleteEvent } from "$sb/app_event.ts";
import { buildHandebarOptions } from "./util.ts";
import type { PageMeta } from "../../web/types.ts";
import { index } from "$sb/silverbullet-syscall/mod.ts";
const builtinAttributes: Record<string, Record<string, string>> = {
page: {
name: "string",
lastModified: "number",
perm: "rw|ro",
contentType: "string",
size: "number",
tags: "array",
},
task: {
name: "string",
done: "boolean",
page: "string",
deadline: "string",
pos: "number",
tags: "array",
},
item: {
name: "string",
page: "string",
pos: "number",
tags: "array",
},
tag: {
name: "string",
freq: "number",
},
};
import {
AttributeCompleteEvent,
AttributeCompletion,
} from "../core/attributes.ts";
export async function queryComplete(completeEvent: CompleteEvent) {
const querySourceMatch = /#query\s+([\w\-_]*)$/.exec(
@@ -61,51 +35,22 @@ export async function queryComplete(completeEvent: CompleteEvent) {
if (querySourceMatch && whereMatch) {
const type = querySourceMatch[1];
const attributePrefix = whereMatch[3];
// console.log("Type", type);
// console.log("Where", attributePrefix);
const allAttributes = await index.queryPrefix(
`attr:${type}:`,
);
const completions = (await events.dispatchEvent(
`attribute:complete:${type}`,
{
source: type,
prefix: attributePrefix,
} as AttributeCompleteEvent,
)).flat() as AttributeCompletion[];
return {
from: completeEvent.pos - attributePrefix.length,
options: compileAttributeCompletions(allAttributes, type),
options: attributeCompletionsToCMCompletion(completions),
};
}
}
return null;
}
function compileAttributeCompletions(
allAttributes: { key: string; value: any }[],
type?: string,
) {
let allCompletions: any[] = allAttributes.map((attr) => {
const [_prefix, context, name] = attr.key.split(":");
return {
label: name,
detail: `${attr.value.type} (${context})`,
type: "attribute",
};
});
const allContexts = type ? [type] : Object.keys(builtinAttributes);
for (const context of allContexts) {
allCompletions = allCompletions.concat(
builtinAttributes[context]
? Object.entries(
builtinAttributes[context],
).map(([name, type]) => ({
label: name,
detail: `${type} (${context}: builtin)`,
type: "attribute",
}))
: [],
);
}
return allCompletions;
}
export async function templateVariableComplete(completeEvent: CompleteEvent) {
const match = /\{\{([\w@]*)$/.exec(completeEvent.linePrefix);
if (!match) {
@@ -123,9 +68,16 @@ export async function templateVariableComplete(completeEvent: CompleteEvent) {
})),
);
const allAttributes = await index.queryPrefix(`attr:`);
const completions = (await events.dispatchEvent(
`attribute:complete:*`,
{
source: "*",
prefix: match[1],
} as AttributeCompleteEvent,
)).flat() as AttributeCompletion[];
allCompletions = allCompletions.concat(
compileAttributeCompletions(allAttributes),
attributeCompletionsToCMCompletion(completions),
);
return {
@@ -133,3 +85,15 @@ export async function templateVariableComplete(completeEvent: CompleteEvent) {
options: allCompletions,
};
}
export function attributeCompletionsToCMCompletion(
completions: AttributeCompletion[],
) {
return completions.map(
(completion) => ({
label: completion.name,
detail: `${completion.type} (${completion.source})`,
type: "attribute",
}),
);
}