DRAKON Diagram Editor for VS Code

Визуальный редактор DRAKON-схем в VS Code: рисуешь диаграмму мышкой — получаешь код, и обратно: из кода — диаграмму.

Расширение стоит на плечах готового движка DrakonWidget (Unlicense), а вся логика работы с диаграммой — своё ядро без зависимостей.

Что умеет

  • Редактирование *.drakon — собственный редактор (CustomTextEditorProvider), не просто просмотрщик: узлы добавляются, переименовываются и связываются прямо в webview.
  • Генерация кода — команда «DRAKON: Generate Code from Diagram» превращает схему в код рядом с файлом.
  • Диаграмма из текста — команда «DRAKON: Show Diagram from Code» принимает JSON диаграммы, обёрнутый как угодно: голый файл, блок ```json или сниппет var diagram = {…}; из примеров виджета. Для .js/.ts работает отдельный парсер подмножества: функция превращается в DRAKON-схему.
  • Живой предпросмотр JS/TS — команда «DRAKON: Open Diagram Preview» рисует схему активного .js/.ts-файла в панели рядом с кодом и перерисовывает её при каждом изменении. Схема — снимок только для чтения, кнопкой её можно сохранить как *.drakon. Готовый пример для пробы — samples/fibonacci.js.
  • Плагинные языки генерации — JavaScript и псевдокод из коробки; новый язык — это отдельный модуль и одна строка регистрации, ядро не трогается.
  • Настройки — целевой язык, имя функции, режим только для чтения, тема.

Быстрый старт

Нужен Node.js 22 или новее.

npm ci
npm run check   # линтер + формат + типы + тесты
npm run build   # dist/extension.js + dist/webview.js

Запуск в живом VS Code: откройте репозиторий и нажмите F5 — поднимется Extension Development Host с папкой samples/ в качестве рабочего пространства (см. .vscode/launch.json).

Первым делом: в открывшемся окне откройте samples/fibonacci.js и нажмите Ctrl+Alt+D — рядом появится DRAKON-схема этой функции.

Команды

Команда Что делает
DRAKON: Generate Code from Diagram Генерирует код из открытой *.drakon в файл рядом (diagram.drakon → diagram.js). Существующий файл перезаписывается, только если он тоже сгенерирован; иначе — вопрос с выбором.
DRAKON: Show Diagram from Code Открывает диаграмму из активного файла или выделенного ресурса: канонический JSON виджета или подмножество .js/.ts (функция → схема).
DRAKON: Open Diagram Preview Панель рядом с редактором: живая DRAKON-схема активного .js/.ts-файла (обновляется на каждую правку, только чтение). Кнопка в заголовке сохраняет схему как *.drakon. Ctrl+Alt+D.
DRAKON: Export Diagram as SVG Пока не реализовано: показывает уведомление.

Настройки

Ключ Тип По умолчанию Смысл
drakon.codegen.language javascript | pseudocode javascript Целевой язык генерации.
drakon.codegen.functionName string "" Имя функции; пусто — вывести из имени диаграммы.
drakon.editor.readOnly boolean false Открывать диаграммы только для чтения.
drakon.editor.theme auto | light | dark auto Тема редактора.

Формат файла

*.drakon — это нативный JSON виджета, ровно то, что отдаёт exportJson():

{
  "type": "drakon",
  "name": "Price check",
  "items": {
    "1": { "type": "branch", "one": "2", "branchId": 0 },
    "2": { "type": "question", "content": "price > 100?", "one": "3", "two": "4" },
    "3": { "type": "action", "content": "approve", "one": "5" },
    "4": { "type": "action", "content": "reject", "one": "5" },
    "5": { "type": "end" }
  }
}

Поскольку формат канонический, round-trip «виджет → файл → виджет» точный, а лишние поля сохраняются дословно — диаграмма не деградирует при сохранении.

Архитектура

Два пакета в npm workspaces, зависимость строго в одну сторону: расширение знает про ядро, ядро про расширение — нет.

packages/core        @drakon/core — платформо-независимое ядро (L0–L2)
  src/model.ts       модель диаграммы, validate/parse/serialize/normalize
  src/edit.ts        применение батчей правок (applyEdit/applyEdits)
  src/codegen/ir.ts  языко-независимый IR: DRAKON-граф → поток управления
  src/codegen/*.ts   генераторы (javascript, pseudocode) + реестр
  src/parse/*.ts     парсеры текста в модель + реестр
  src/parse/javascript.ts  подмножество JS/TS → модель (обратный ход генератора)

packages/extension   адаптер VS Code (L3–L5)
  src/drakonEditorProvider.ts  CustomTextEditorProvider для *.drakon
  src/diagramDocument.ts       синхронизация модели с TextDocument, undo, dirty
  src/codegen.ts               команда генерации кода
  src/showDiagram.ts           команда «диаграмма из кода»
  src/preview.ts               живая панель предпросмотра JS/TS
  src/webviewHtml.ts           общий HTML/CSP редактора и панели
  webview/main.ts              webview, EditSender, postMessage-мост
  media/drakonwidget.js        vendored движок DrakonWidget 1.5.7

Уровни абстракции

Нумерация L0–L5 идёт снизу вверх: чем меньше номер, тем меньше слой знает о мире вокруг. Импорты разрешены только вниз — L5 знает про L4, L4 про L3 и так далее; обратный импорт (ядро, знающее про VS Code или webview) считается ошибкой слоя.

Слой Файлы Отвечает за Не знает о
L0 packages/core/src/model.ts Типы документа, валидация, парсинг и сериализация JSON виджете, VS Code, DOM, генерации
L1 packages/core/src/edit.ts, codegen/ir.ts, parse/types.ts, parse/drakonJson.ts, parse/javascript.ts, parse/registry.ts Правки модели, обход графа в языко-независимый поток, разбор текста в модель целевом языке, VS Code, DOM
L2 packages/core/src/codegen/types.ts, codegen/javascript.ts, codegen/pseudocode.ts, codegen/registry.ts Рендеринг потока управления в текст конкретного языка + реестр генераторов VS Code, webview, форме хранения
L3 packages/extension/src/diagramDocument.ts Синхронизация модели с TextDocument: чтение, запись, undo, грязный флаг webview, DOM, командах
L4 packages/extension/src/drakonEditorProvider.ts, codegen.ts, showDiagram.ts, preview.ts, webviewHtml.ts CustomTextEditorProvider, команды, файловый ввод-вывод, панель предпросмотра, общий HTML/CSP DOM и устройстве webview
L5 packages/extension/src/extension.ts, packages/extension/webview/main.ts, webview/widget.ts, webview/icons.ts Точка входа расширения, рендер в webview, обвязка DrakonWidget, мост postMessage правилах предметной области — они в ядре

L0 — модель данных. DrakonItemType (список видов узлов: action, question, select, case, branch, end, loopbegin/loopend, sinput и прочие), DrakonItem, DrakonDiagram. Операции: validateDiagram, parseDiagram, serializeDiagram, normalizeDiagram, findEntryIds, createEmptyDiagram, compareNumericIds. Слой знает ровно одно — форму JSON-документа *.drakon; ни виджет, ни генерация его не касаются.

L1 — операции над моделью. Здесь живёт всё, что работает с графом, но ещё не знает целевого языка:

  • edit.ts — EditChange (insert | update | delete), applyEdit/applyEdits/applyEditInPlace, cloneDiagram. Батч правок из webview превращается в новую неизменяемую модель.
  • codegen/ir.ts — buildFlow обходит DRAKON-граф и строит FlowProgram: языко-независимый поток управления (Flow, LoopKind, SelectBranch). Вспомогательные findEntryId, successors, findLoopHeaders, readSelect.
  • parse/* — контракт DiagramParser, утилита extractJsonObject, парсеры DrakonJsonParser (канонический JSON виджета в любой обёртке) и parseJavaScript (подмножество JS/TS → модель), плюс реестр парсеров.

L2 — генерация текста. codegen/types.ts объявляет контракт CodeGenerator (id, label, languageId, fileExtension, generate(program, options)) и общие утилиты indenter, identifierFrom, contentLines. Конкретные языки — JavaScriptGenerator и PseudocodeGenerator; codegen/registry.ts (listGenerators, findGenerator, resolveGenerator) — точка подключения нового языка. Генератор — чистая функция FlowProgram → string: он не видит ни типов узлов, ни идентификаторов, ни рёбер, поэтому новый язык не требует правок ни в ядре, ни в расширении.

L3 — документ VS Code. DiagramDocument держит модель в согласии с TextDocument: читает файл, применяет батчи правок, пишет обратно, ведёт undo и грязный флаг. Единственное место, где модель встречается с файловой системой VS Code.

L4 — интеграция с VS Code. DrakonEditorProvider (это CustomTextEditorProvider, тип представления — DRAKON_VIEW_TYPE), команды генерации и показа диаграммы, parserIdForUri (выбор парсера по расширению файла), панель живого предпросмотра (PREVIEW_EXTENSIONS, openPreview, savePreview), общий HTML/CSP редактора и панели (createNonce, buildDiagramHtml). Слой знает про API VS Code и про то, что нужно отдать в webview, но не трогает DOM.

L5 — webview. main.ts монтирует DrakonWidget, собирает правки виджета в батчи EditSender и шлёт их через postMessage; widget.ts — единственное место, типизирующее необъявленный API вендоренного движка; icons.ts — иконки контекстного меню; media/drakonwidget.js — сам движок. Webview намеренно без состояния: хост всегда присылает полную диаграмму, модель здесь не мутируется.

Поток данных

Поток данных однонаправленный: parse → model → codegen. TextDocument — единственный источник правды; webview рисует модель и шлёт батчи правок, а не правит файл напрямую. Именно поэтому генерация даёт тот же результат, что видно на экране.

Ядро не импортирует vscode и не трогает DOM — это сделано сознательно: этап «другие платформы» (Android/веб) переиспользует @drakon/core, добавив только новый адаптер. Правило проверяется глазами при каждом рефакторинге: любой импорт vscode или обращение к document/window в packages/core/src/ — ошибка слоя. Ядро обязано оставаться запускаемым под node --test без VS Code; тот же шов даёт бесплатную проверку качества — второй генератор (псевдокод) ловит ошибки обхода графа, которые первый может замаскировать совпадением по тексту.

Разработка

npm run lint          # ESLint 9
npm run format        # Prettier --write
npm run format:check  # Prettier --check (входит в check)
npm run typecheck     # tsc --noEmit
npm test              # node --test (нативный раннер, TypeScript напрямую)
npm run check         # всё вышеперечисленное
npm run build         # esbuild: расширение + webview

Сборки и зависимости статического анализа в git не попадают (dist/, node_modules/). Тесты гоняются нативным node --test — Node исполняет TypeScript сам, отдельный шаг компиляции для тестов не нужен.

Тестов 97: модель, правки, парсеры (round-trip JSON и подмножества JS/TS), генератор JavaScript и отдельно генератор псевдокода — второй генератор работает как бесплатная проверка плагинного шва: он ловит то, что первый генератор может замаскировать совпадением по тексту. Round-trip диаграмма → JavaScriptGenerator → parseJavaScript → диаграмма проверяет обратимость подмножества.

Лицензия

Unlicense — программное обеспечение передано в общественное достояние, делайте с ним что хотите. Текст — в файле LICENSE.

Вендоренный движок DrakonWidget (v1.5.7, файл packages/extension/media/drakonwidget.js) распространяется под той же лицензией Unlicense.