- *.archify-decision files: id (unique, words), title, status, date, elements, diagrams, supersedes, and prose; parsed, merged and link-checked in the model index; diagnostics on the file - diagrams point at decisions with decisions: [...] on the diagram, nodes and edges; stripped before Archify, unknown ids reported, completion, hover and go-to-definition for the ids and for elements/diagrams/ supersedes in decision files - Decisions panel on the main.archify page; Decisions rail panel under a diagram (hover lights up the node or connection); decisions on element cards and in the element dialog; decision view for the file, with a lost-model note outside a model - Archify | New Decision… command and template; editor schemas; the three New Model examples carry decisions; docs, changelog, 0.8.0
105 lines
5.6 KiB
TypeScript
105 lines
5.6 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import { MODEL_EXAMPLES, mainFromExample } from '../language/examples';
|
|
import { diagramName, invalidNameReason } from '../language/templates';
|
|
import { DECISION_EXT, slugify } from '../model/decisions';
|
|
import { MAIN_FILE } from '../model/format';
|
|
import { DECISION_VIEW_TYPE } from '../views/decisionView';
|
|
import { readText } from '../util/files';
|
|
import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView';
|
|
import { CommandContext, exists, targetUri } from './context';
|
|
|
|
/** Folder the new model folder goes into: the clicked folder (or a file's folder), else a workspace folder. */
|
|
async function parentFolder(arg?: unknown): Promise<vscode.Uri | undefined> {
|
|
if (arg instanceof vscode.Uri) {
|
|
const stat = await vscode.workspace.fs.stat(arg);
|
|
return stat.type & vscode.FileType.Directory ? arg : vscode.Uri.joinPath(arg, '..');
|
|
}
|
|
const folders = vscode.workspace.workspaceFolders ?? [];
|
|
if (folders.length <= 1) return folders[0]?.uri;
|
|
return (await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Folder for the new model' }))?.uri;
|
|
}
|
|
|
|
export function modelCommands(ctx: CommandContext): vscode.Disposable[] {
|
|
return [
|
|
// Opens the model page (main.archify) of the model the current file is in.
|
|
vscode.commands.registerCommand('archify.openModel', async (arg?: unknown) => {
|
|
const uri = targetUri(ctx, arg);
|
|
const root = uri ? await ctx.models.rootOf(uri) : undefined;
|
|
if (!root) {
|
|
vscode.window.showInformationMessage(`Archify: this file is not inside a model (no ${MAIN_FILE} in a folder above it).`);
|
|
return;
|
|
}
|
|
await vscode.commands.executeCommand('vscode.openWith', vscode.Uri.joinPath(root, MAIN_FILE), DIAGRAM_EDITOR_VIEW_TYPE, vscode.ViewColumn.Active);
|
|
}),
|
|
|
|
// Creates a decision file in the clicked folder of a model and opens it.
|
|
vscode.commands.registerCommand('archify.newDecision', async (arg?: unknown) => {
|
|
const folder = await parentFolder(arg);
|
|
if (!folder) return;
|
|
const root = await ctx.models.rootOf(vscode.Uri.joinPath(folder, 'x'));
|
|
if (!root) {
|
|
vscode.window.showWarningMessage(`Archify: ${vscode.workspace.asRelativePath(folder, true)} is not inside a model (no ${MAIN_FILE} in it or a folder above it). Decisions belong to a model.`);
|
|
return;
|
|
}
|
|
const index = await ctx.models.index(root);
|
|
const title = await vscode.window.showInputBox({
|
|
title: 'Archify | New Decision',
|
|
prompt: `Title of the decision (file goes into ${vscode.workspace.asRelativePath(folder, true)})`,
|
|
placeHolder: 'OpenSearch for product search',
|
|
validateInput: async (value) => {
|
|
if (!value.trim()) return 'Enter a title.';
|
|
const id = slugify(value);
|
|
if (index.decisions.decisions.has(id)) return `A decision with id "${id}" already exists in this model.`;
|
|
if (await exists(vscode.Uri.joinPath(folder, `${id}${DECISION_EXT}`))) return `${id}${DECISION_EXT} already exists.`;
|
|
return undefined;
|
|
},
|
|
});
|
|
if (title === undefined) return;
|
|
const id = slugify(title);
|
|
const target = vscode.Uri.joinPath(folder, `${id}${DECISION_EXT}`);
|
|
const template = await readText(vscode.Uri.joinPath(ctx.extension.extensionUri, 'templates', 'decision.archify-decision'));
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
const text = template.replace('__ID__', id).replace('__TITLE__', JSON.stringify(title.trim())).replace('__DATE__', today);
|
|
await vscode.workspace.fs.writeFile(target, Buffer.from(text, 'utf8'));
|
|
await vscode.commands.executeCommand('vscode.openWith', target, DECISION_VIEW_TYPE);
|
|
}),
|
|
|
|
// Creates a new model folder from one of the shipped examples and opens its model page.
|
|
vscode.commands.registerCommand('archify.newModel', async (arg?: unknown) => {
|
|
const picked = await vscode.window.showQuickPick(
|
|
MODEL_EXAMPLES.map((example) => ({ label: example.label, description: example.description, example })),
|
|
{ title: 'Archify | New Model', placeHolder: 'Example to start from' },
|
|
);
|
|
if (!picked) return;
|
|
const { example } = picked;
|
|
|
|
let parent = await parentFolder(arg);
|
|
if (!parent) {
|
|
const chosen = await vscode.window.showOpenDialog({ title: 'Archify | New Model', canSelectFiles: false, canSelectFolders: true, openLabel: 'Create model here' });
|
|
parent = chosen?.[0];
|
|
if (!parent) return;
|
|
}
|
|
const name = await vscode.window.showInputBox({
|
|
title: 'Archify | New Model',
|
|
prompt: `Folder name in ${vscode.workspace.asRelativePath(parent, true)}`,
|
|
value: example.folder,
|
|
validateInput: async (value) =>
|
|
invalidNameReason(value) ?? ((await exists(vscode.Uri.joinPath(parent, diagramName(value)))) ? `${diagramName(value)} already exists.` : undefined),
|
|
});
|
|
if (name === undefined) return;
|
|
const folderName = diagramName(name);
|
|
const target = vscode.Uri.joinPath(parent, folderName);
|
|
if (await exists(target)) {
|
|
vscode.window.showWarningMessage(`Archify: ${vscode.workspace.asRelativePath(target, true)} already exists.`);
|
|
return;
|
|
}
|
|
|
|
const source = vscode.Uri.joinPath(ctx.extension.extensionUri, 'templates', 'models', example.folder);
|
|
await vscode.workspace.fs.copy(source, target, { overwrite: false });
|
|
const main = vscode.Uri.joinPath(target, MAIN_FILE);
|
|
await vscode.workspace.fs.writeFile(main, Buffer.from(mainFromExample(await readText(main), folderName, example), 'utf8'));
|
|
await vscode.commands.executeCommand('vscode.openWith', main, DIAGRAM_EDITOR_VIEW_TYPE);
|
|
}),
|
|
];
|
|
}
|