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 { 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); }), ]; }