@reformer/builder-plugin-api 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +206 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +76 -0
- package/dist/internal.d.ts +78 -0
- package/dist/internal.js +289 -0
- package/dist/permissions-BWlsUVPs.js +551 -0
- package/dist/plugin/bundled-modules.d.ts +25 -0
- package/dist/plugin/layout.d.ts +27 -0
- package/dist/plugin/manifest-parser.d.ts +54 -0
- package/dist/plugin/manifest.d.ts +320 -0
- package/dist/plugin/messages-bundle.d.ts +24 -0
- package/dist/plugin/permissions.d.ts +48 -0
- package/dist/plugin/plugin-exports.d.ts +10 -0
- package/dist/plugin/runtime-modules.d.ts +20 -0
- package/dist/plugin/storage.d.ts +70 -0
- package/dist/plugin/types.d.ts +136 -0
- package/dist/primitives/capability.d.ts +72 -0
- package/dist/primitives/command.d.ts +262 -0
- package/dist/primitives/disposable.d.ts +23 -0
- package/dist/primitives/event.d.ts +38 -0
- package/dist/primitives/extension-point.d.ts +53 -0
- package/dist/primitives/module-path.d.ts +21 -0
- package/dist/primitives/resource.d.ts +159 -0
- package/dist/primitives/semver.d.ts +93 -0
- package/dist/primitives/service.d.ts +68 -0
- package/dist/primitives/when-context.d.ts +69 -0
- package/dist/primitives/when-expr.d.ts +177 -0
- package/dist/resource-clipboard-Bdk_Is5N.js +381 -0
- package/dist/runtime-modules-CiUDFMIn.js +597 -0
- package/dist/services/context-keys.d.ts +52 -0
- package/dist/services/diagnostics/fixes.d.ts +40 -0
- package/dist/services/diagnostics/service.d.ts +34 -0
- package/dist/services/diagnostics/types.d.ts +148 -0
- package/dist/services/document-models.d.ts +20 -0
- package/dist/services/documents.d.ts +94 -0
- package/dist/services/host-messages.d.ts +10 -0
- package/dist/services/i18n.d.ts +43 -0
- package/dist/services/modules.d.ts +49 -0
- package/dist/services/notifications.d.ts +56 -0
- package/dist/services/plugin-settings.d.ts +10 -0
- package/dist/services/plugins-catalog.d.ts +58 -0
- package/dist/services/preview.d.ts +163 -0
- package/dist/services/prompt.d.ts +111 -0
- package/dist/services/resource-clipboard.d.ts +28 -0
- package/dist/services/selection.d.ts +46 -0
- package/dist/services/settings.d.ts +44 -0
- package/dist/services/theme.d.ts +15 -0
- package/dist/services/validation/types.d.ts +98 -0
- package/dist/services/workspace-files.d.ts +84 -0
- package/dist/services/workspace-resources.d.ts +41 -0
- package/dist/services/workspace-save.d.ts +18 -0
- package/dist/tooling.d.ts +28 -0
- package/dist/tooling.js +35 -0
- package/dist/ui/contributions/decorations.d.ts +55 -0
- package/dist/ui/contributions/editors.d.ts +58 -0
- package/dist/ui/contributions/plugin-settings.d.ts +54 -0
- package/dist/ui/keyboard/keybinding-rules.d.ts +48 -0
- package/dist/ui/keyboard/keybindings.d.ts +55 -0
- package/dist/ui/keyboard/keymap.d.ts +81 -0
- package/dist/ui/keyboard/scope.d.ts +38 -0
- package/dist/ui/menu/editor-menu.d.ts +25 -0
- package/dist/ui/menu/menu.d.ts +228 -0
- package/dist/ui/menu/palette.d.ts +36 -0
- package/dist/ui/menu/resource-menu.d.ts +45 -0
- package/dist/ui/slots.d.ts +113 -0
- package/dist/ui/useActiveDocument.d.ts +5 -0
- package/dist/ui/useLocale.d.ts +9 -0
- package/dist/ui/useTranslate.d.ts +4 -0
- package/dist/workspace/document.d.ts +42 -0
- package/dist/workspace/model/editor-view-states.d.ts +29 -0
- package/dist/workspace/model/model-document.d.ts +86 -0
- package/dist/workspace/model/provider.d.ts +140 -0
- package/dist/workspace/model/text-editor-focus.d.ts +22 -0
- package/dist/workspace/resource-names.d.ts +92 -0
- package/dist/workspace/write-options.d.ts +36 -0
- package/package.json +69 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { CapabilityAccess } from '../primitives/capability.js';
|
|
2
|
+
import { PluginCommandRegistry } from '../primitives/command.js';
|
|
3
|
+
import { Disposable } from '../primitives/disposable.js';
|
|
4
|
+
import { EventBus } from '../primitives/event.js';
|
|
5
|
+
import { ExtensionRegistry } from '../primitives/extension-point.js';
|
|
6
|
+
import { ServiceRegistry } from '../primitives/service.js';
|
|
7
|
+
import { PluginI18n } from '../services/i18n.js';
|
|
8
|
+
import { PluginStorage, SecretStorage } from './storage.js';
|
|
9
|
+
/**
|
|
10
|
+
* Всё, что плагин получает от платформы. Другого способа дотянуться до Host у него нет.
|
|
11
|
+
*
|
|
12
|
+
* Контекст создаётся рантаймом на каждую активацию заново (см. `createPluginContext`)
|
|
13
|
+
* и перестаёт быть действительным после деактивации: подписки из него сняты, вклады убраны.
|
|
14
|
+
* Держать контекст в модульной переменной плагина и пользоваться им после выключения —
|
|
15
|
+
* ошибка, которую рантайм не в состоянии предотвратить, но которая проявится сразу:
|
|
16
|
+
* зарегистрированное после деактивации уже никто не снимет.
|
|
17
|
+
*/
|
|
18
|
+
export interface PluginContext {
|
|
19
|
+
/** Идентификатор плагина, которому принадлежит контекст. Совпадает с `Plugin.id`. */
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/**
|
|
22
|
+
* Реестр сервисов — общий, не вид на него.
|
|
23
|
+
*
|
|
24
|
+
* Регистрация чужого токена не запрещена технически (изоляция здесь — гигиена, а не граница
|
|
25
|
+
* безопасности), но занят токен может быть только один раз: повторная регистрация бросает.
|
|
26
|
+
*/
|
|
27
|
+
readonly services: ServiceRegistry;
|
|
28
|
+
/**
|
|
29
|
+
* Возможности — ВИД на тот же реестр служб, а не второе хранилище.
|
|
30
|
+
*
|
|
31
|
+
* Разница со `services` не в данных, а в вопросе. `services` отвечает «дай реализацию по
|
|
32
|
+
* токену»; `capabilities` отвечает «выполнен ли контракт, который я объявил в `requires`»,
|
|
33
|
+
* и потому умеет три вещи, которых у реестра нет: назвать в отказе версию и возможного
|
|
34
|
+
* провайдера (`require`), деградировать штатно (`get`) и дождаться появления реализации
|
|
35
|
+
* (`observe`) — без опроса по таймеру.
|
|
36
|
+
*
|
|
37
|
+
* Правило «сервис ищется в момент использования» здесь В СИЛЕ целиком: `require` в `activate`
|
|
38
|
+
* запрещён ровно так же, как `services.require` чужого сервиса, — провайдер имеет право
|
|
39
|
+
* подняться позже. `observe` для того и существует.
|
|
40
|
+
*/
|
|
41
|
+
readonly capabilities: CapabilityAccess;
|
|
42
|
+
/**
|
|
43
|
+
* Точки расширения — **вид реестра для этого плагина** (`RootExtensionRegistry.forPlugin`),
|
|
44
|
+
* а не корневой реестр.
|
|
45
|
+
*
|
|
46
|
+
* Разница несущая: в виде нет ни параметра `pluginId`, ни метода `forPlugin`, поэтому плагин
|
|
47
|
+
* физически не располагает способом внести вклад анонимно или от чужого имени. Ответ
|
|
48
|
+
* на вопрос «откуда здесь эта панель» гарантирован строением API, а не дисциплиной.
|
|
49
|
+
*/
|
|
50
|
+
readonly extensions: ExtensionRegistry;
|
|
51
|
+
/** Реестр команд. Снятие команды — через `dispose()` регистрации, то есть через `subscriptions`. */
|
|
52
|
+
/**
|
|
53
|
+
* Реестр команд — ВИД на него для этого плагина: `register` проставляет владельца сам.
|
|
54
|
+
*
|
|
55
|
+
* Владелец нужен не реестру, а палитре: заголовок команды разрешается словарём того,
|
|
56
|
+
* кто её внёс. Без этого `titleKey` команды плагина искался бы в словаре Host и всегда
|
|
57
|
+
* промахивался. Метода `forPlugin` в виде нет — зарегистрировать команду от чужого
|
|
58
|
+
* имени не получится, потому что пути к этому не существует.
|
|
59
|
+
*/
|
|
60
|
+
readonly commands: PluginCommandRegistry;
|
|
61
|
+
/** Шина событий. Доставка синхронная, ошибка подписчика не касается отправителя. */
|
|
62
|
+
readonly events: EventBus;
|
|
63
|
+
/**
|
|
64
|
+
* Словарь плагина: его строки в ЕГО пространстве имён.
|
|
65
|
+
*
|
|
66
|
+
* Ключи префиксуются идентификатором плагина автоматически, поэтому `editor.label` двух
|
|
67
|
+
* разных плагинов — две разные строки; общего пространства имён у словарей нет и быть
|
|
68
|
+
* не должно. `contribute` регистрирует словарь (обычно в `activate`, но можно и по частям —
|
|
69
|
+
* например, вместе с ленивой панелью), `t` переводит.
|
|
70
|
+
*
|
|
71
|
+
* Показывать переведённое в React надо через `useTranslate` из этого же пакета: `t` отвечает,
|
|
72
|
+
* как строка звучит СЕЙЧАС, и сама по себе смену языка не переживает.
|
|
73
|
+
*
|
|
74
|
+
* Что НЕ переводится здесь: тексты диагностик (`errors.<code>`). Одна и та же ошибка обязана
|
|
75
|
+
* выглядеть одинаково в редакторе, в панели проблем и в логе, поэтому её строки живут
|
|
76
|
+
* в словаре оболочки, а плагин просит перевод, а не переводит сам.
|
|
77
|
+
*/
|
|
78
|
+
readonly i18n: PluginI18n;
|
|
79
|
+
/** Изолированное хранилище плагина. Пространство имён — идентификатор плагина. */
|
|
80
|
+
readonly storage: PluginStorage;
|
|
81
|
+
/** Секреты плагина. По умолчанию — только память сессии, см. {@link SecretStorage}. */
|
|
82
|
+
readonly secrets: SecretStorage;
|
|
83
|
+
/**
|
|
84
|
+
* Всё, что сюда положено, освобождается при деактивации.
|
|
85
|
+
*
|
|
86
|
+
* Массив, а не реестр: плагин пишет в него `push`, и это единственная операция, которая
|
|
87
|
+
* от него требуется. Рантайм освобождает список сам — этим снимается целый класс утечек
|
|
88
|
+
* «выключили плагин, а его панель и команда остались навсегда».
|
|
89
|
+
*
|
|
90
|
+
* Обратная сторона: вклад, **не** положенный в `subscriptions`, при деактивации не снимется.
|
|
91
|
+
* Рантайм не в состоянии это обнаружить: он не знает, что именно плагин зарегистрировал.
|
|
92
|
+
*/
|
|
93
|
+
readonly subscriptions: Disposable[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Контракт плагина.
|
|
97
|
+
*
|
|
98
|
+
* `id` — пространство имён владельца во всех реестрах (`editor-schema`, `ai`): по нему
|
|
99
|
+
* подписаны вклады, по нему изолированы хранилище и секреты, по нему плагин включается
|
|
100
|
+
* и выключается.
|
|
101
|
+
*/
|
|
102
|
+
export interface Plugin {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
/**
|
|
105
|
+
* Регистрирует всё, что даёт плагин. Вызывается рантаймом один раз на активацию.
|
|
106
|
+
*
|
|
107
|
+
* Синхронный по решению выше. Асинхронную работу запускать можно и нужно, но **не ждать**:
|
|
108
|
+
* `void activate` не даёт вернуть промис, поэтому запущенное обязано само доложить о себе
|
|
109
|
+
* через сервис или вклад.
|
|
110
|
+
*
|
|
111
|
+
* Исключение отсюда не роняет запуск: рантайм ловит его, помечает плагин отказавшим
|
|
112
|
+
* и продолжает активировать остальные (см. `createPluginRegistry`).
|
|
113
|
+
*/
|
|
114
|
+
activate(ctx: PluginContext): void;
|
|
115
|
+
/**
|
|
116
|
+
* Необязательный обратный вызов при выключении.
|
|
117
|
+
*
|
|
118
|
+
* Нужен только для того, что не выражается через `subscriptions`: остановить таймер, отменить
|
|
119
|
+
* запрос, закрыть соединение. Снимать вклады руками здесь не надо — это делает рантайм,
|
|
120
|
+
* освобождая `subscriptions` сразу после `deactivate`.
|
|
121
|
+
*/
|
|
122
|
+
deactivate?(): void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Объявляет плагин.
|
|
126
|
+
*
|
|
127
|
+
* Функция выглядит тождественной и почти ею является — её ценность в трёх вещах:
|
|
128
|
+
* вывод типов на `ctx` в месте объявления (без неё автор пишет аннотацию руками или теряет
|
|
129
|
+
* типы), проверка `id` в момент объявления, а не в момент регистрации, и единственное имя,
|
|
130
|
+
* которое загрузчик подставляет плагину каталога как `@builder/sdk`.
|
|
131
|
+
*
|
|
132
|
+
* Результат заморожен: `id` — ключ во всех реестрах, и его изменение после регистрации
|
|
133
|
+
* означало бы вклады, найденные по одному имени и снимаемые по другому. Состояние плагина
|
|
134
|
+
* живёт в замыкании `activate`, а не на объекте, поэтому заморозка ничего не отнимает.
|
|
135
|
+
*/
|
|
136
|
+
export declare function definePlugin(p: Plugin): Plugin;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Disposable } from './disposable.js';
|
|
2
|
+
import { ServiceToken } from './service.js';
|
|
3
|
+
/**
|
|
4
|
+
* Токен службы с объявленной версией контракта.
|
|
5
|
+
*
|
|
6
|
+
* Расширение, а не обёртка: всё, что принимает `ServiceToken`, принимает и capability.
|
|
7
|
+
*/
|
|
8
|
+
export interface Capability<T> extends ServiceToken<T> {
|
|
9
|
+
/** Версия контракта: три числа, `1.0.0`. Диапазоны здесь недопустимы — это объявление. */
|
|
10
|
+
readonly version: string;
|
|
11
|
+
}
|
|
12
|
+
/** Объявление «я даю такую-то возможность такой-то версии». Форма поля `provides` манифеста. */
|
|
13
|
+
export interface CapabilityDeclaration {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly version: string;
|
|
16
|
+
}
|
|
17
|
+
/** Требование «мне нужна такая-то возможность в таком-то диапазоне». Форма поля `requires`. */
|
|
18
|
+
export interface CapabilityRequirement {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
/** Диапазон в записи `./semver`: `^1`, `~1.2`, `>=1.2.3`, `1.x`, `*`. */
|
|
21
|
+
readonly range: string;
|
|
22
|
+
}
|
|
23
|
+
/** Кто именно объявил возможность. `by` — идентификатор плагина, и он нужен диагностике. */
|
|
24
|
+
export interface CapabilityProvider extends CapabilityDeclaration {
|
|
25
|
+
readonly by: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Объявляет capability.
|
|
29
|
+
*
|
|
30
|
+
* Проверки — в момент объявления, а не в момент использования, по той же причине, что
|
|
31
|
+
* у `defineService` и `definePlugin`: пустой идентификатор превратил бы диагностику
|
|
32
|
+
* в «возможность «» не предоставлена», а диапазон вместо версии (`^1` в поле `version`)
|
|
33
|
+
* сделал бы сравнение бессмысленным, причём молча — `satisfies('^1', '^1')` это `false`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function defineCapability<T>(spec: CapabilityDeclaration): Capability<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Удовлетворяет ли объявление требованию.
|
|
38
|
+
*
|
|
39
|
+
* Неразбираемый диапазон даёт `false`: требование, которое нельзя прочитать, не выполнено
|
|
40
|
+
* ничем. Разбор манифеста отвергает такое требование раньше и с внятным текстом — эта
|
|
41
|
+
* функция всего лишь не делает вид, что понимает написанное.
|
|
42
|
+
*/
|
|
43
|
+
export declare function meetsRequirement(declaration: CapabilityDeclaration, requirement: CapabilityRequirement): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Вид на реестр служб в терминах capability — то, что получает плагин как `ctx.capabilities`.
|
|
46
|
+
*
|
|
47
|
+
* Три метода отвечают на три разных вопроса, и подменять один другим нельзя:
|
|
48
|
+
* «возьму, если есть» (`get`), «без этого мне нечего делать» (`require`), «скажи, когда
|
|
49
|
+
* появится» (`observe`).
|
|
50
|
+
*/
|
|
51
|
+
export interface CapabilityAccess {
|
|
52
|
+
/** `undefined` — возможности нет. Вызывающий обязан деградировать, а не падать. */
|
|
53
|
+
get<T>(cap: Capability<T>): T | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Бросает, если возможности нет, — с текстом, называющим и требование, и того, кто мог бы
|
|
56
|
+
* его удовлетворить.
|
|
57
|
+
*
|
|
58
|
+
* Годится только там, где отсутствие возможности и есть отказ операции: внутри команды,
|
|
59
|
+
* обработчика, тела панели. В `activate` его звать нельзя — порядок активации ничего
|
|
60
|
+
* не значит, и провайдер имеет полное право подняться позже (см. `../plugin/types`).
|
|
61
|
+
*/
|
|
62
|
+
require<T>(cap: Capability<T>): T;
|
|
63
|
+
/**
|
|
64
|
+
* Сообщает о появлении и об исчезновении реализации.
|
|
65
|
+
*
|
|
66
|
+
* Зовёт обработчик СРАЗУ с текущим значением (`undefined`, если возможности нет), и это
|
|
67
|
+
* решение, а не побочный эффект: наблюдатель почти всегда рисует состояние, и без
|
|
68
|
+
* немедленного вызова каждый его потребитель писал бы `get` плюс `observe` — две строки,
|
|
69
|
+
* между которыми помещается гонка.
|
|
70
|
+
*/
|
|
71
|
+
observe<T>(cap: Capability<T>, listener: (impl: T | undefined) => void): Disposable;
|
|
72
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { Disposable } from './disposable.js';
|
|
2
|
+
import { WhenContext } from './when-context.js';
|
|
3
|
+
import { WhenExpr } from './when-expr.js';
|
|
4
|
+
/**
|
|
5
|
+
* Как команда выглядит для модели. Заполняется осознанно — см.
|
|
6
|
+
* {@link CommandRegistry.agentCommands}.
|
|
7
|
+
*/
|
|
8
|
+
export interface CommandAgentSpec {
|
|
9
|
+
/** Описание для модели, а не ключ i18n: модель читает по-английски и перевода не ждёт. */
|
|
10
|
+
readonly description: string;
|
|
11
|
+
/** JSON Schema аргументов. Проверяется до вызова — это работа моста ассистента (Э10). */
|
|
12
|
+
readonly schema: object;
|
|
13
|
+
}
|
|
14
|
+
/** Вклад в точку расширения `command`. */
|
|
15
|
+
export interface CommandContribution {
|
|
16
|
+
/** Уникальный идентификатор с пространством имён владельца: `workspace.save`. */
|
|
17
|
+
readonly id: string;
|
|
18
|
+
/**
|
|
19
|
+
* Ключ i18n, не литерал: иначе одна из локалей становится главной, а перевод —
|
|
20
|
+
* привязанным к месту объявления команды.
|
|
21
|
+
*
|
|
22
|
+
* Разрешается словарём ВЛАДЕЛЬЦА (см. {@link CommandContribution.pluginId}), а не Host:
|
|
23
|
+
* иначе команда плагина гарантированно промахивается мимо ключа и показывает маркер.
|
|
24
|
+
*/
|
|
25
|
+
readonly titleKey: string;
|
|
26
|
+
/**
|
|
27
|
+
* Кто внёс команду. Проставляет РЕЕСТР, а не объявление, — тем же приёмом, что у вкладов
|
|
28
|
+
* (`ExtensionRegistry.forPlugin`): будь это параметр, его забыли бы, ошиблись
|
|
29
|
+
* бы в нём или подставили чужой.
|
|
30
|
+
*
|
|
31
|
+
* Отсутствие означает команду самой оболочки, и её `titleKey` — ключ словаря Host.
|
|
32
|
+
* Разница видна пользователю: без владельца заголовок команды плагина был бы маркером
|
|
33
|
+
* промаха вроде `⟦editor-schema.command.delete⟧`.
|
|
34
|
+
*/
|
|
35
|
+
readonly pluginId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Действие. Может быть синхронным или асинхронным; реестр вызывает его синхронно
|
|
38
|
+
* (см. {@link CommandRegistry.execute}).
|
|
39
|
+
*/
|
|
40
|
+
readonly run: (args?: unknown) => unknown | Promise<unknown>;
|
|
41
|
+
/**
|
|
42
|
+
* Применимость. Отсутствие означает «доступна всегда».
|
|
43
|
+
*
|
|
44
|
+
* Предикат обязан быть чистым и дешёвым: его зовут на каждую перерисовку палитры и на
|
|
45
|
+
* каждое нажатие, которое диспетчер сопоставит с сочетанием.
|
|
46
|
+
*/
|
|
47
|
+
readonly enabled?: (ctx: WhenContext) => boolean;
|
|
48
|
+
/** Например `mod+s`, `mod+alt+v`. `mod` = Cmd на macOS, Ctrl на остальных. */
|
|
49
|
+
readonly keybinding?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Условие ПРИВЯЗКИ КЛАВИШИ: `focus == tree`, `activeResourceKind == form.schema`.
|
|
52
|
+
* Разбирается на регистрации, синтаксис — см. `./when-expr`.
|
|
53
|
+
*
|
|
54
|
+
* ## Чем отличается от `enabled` — водораздел, а не два способа одного
|
|
55
|
+
*
|
|
56
|
+
* `when` отвечает «где и в каком режиме» (состояние платформы: куда направлен фокус, что
|
|
57
|
+
* открыто), `enabled` — «есть ли чему сработать» (приватное состояние владельца: есть ли
|
|
58
|
+
* что отменять, лежит ли что-то в буфере). Первое обязано быть ДАННЫМИ — их сравнивают,
|
|
59
|
+
* чтобы решить, чья клавиша выигрывает, показывают человеку в таблице клавиш и пишут в
|
|
60
|
+
* файл раскладки. Второе данными быть не может: оно читает то, чего у платформы нет.
|
|
61
|
+
*
|
|
62
|
+
* Прямая польза, ради которой поле и заведено: `delete` в проекте зарегистрирован дважды —
|
|
63
|
+
* деревом файлов и редактором схемы, — и сегодня их разводит только порядок регистрации,
|
|
64
|
+
* который по контракту рантайма плагинов ничего не значит. С условиями `focus == tree` и
|
|
65
|
+
* `focus == canvas` они становятся ДОКАЗУЕМО непересекающимися.
|
|
66
|
+
*
|
|
67
|
+
* ## Проверяет его диспетчер клавиш, а НЕ этот реестр
|
|
68
|
+
*
|
|
69
|
+
* `isEnabled` и `execute` условие не смотрят, и это осознанно: `when` ограничивает клавишу,
|
|
70
|
+
* а не команду. Смотри его реестр — пункт контекстного меню «Переименовать» пропал бы в тот
|
|
71
|
+
* момент, когда меню открыто правым щелчком без фокуса на строке, то есть ровно тогда, когда
|
|
72
|
+
* он нужен. Из палитры, меню и от ассистента команда вызывается по-прежнему по `enabled`.
|
|
73
|
+
*/
|
|
74
|
+
readonly when?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Разрешить сочетание, когда фокус в поле ввода. По умолчанию — нет.
|
|
77
|
+
*
|
|
78
|
+
* Помечаются единицы: сохранение, палитра команд. Всё остальное в поле ввода принадлежит
|
|
79
|
+
* тому, кто в это поле печатает.
|
|
80
|
+
*
|
|
81
|
+
* Поле живёт здесь, рядом с `keybinding`, а не вносится дополнением объявления из диспетчера:
|
|
82
|
+
* раз `keybinding` уже здесь, знание о клавиатуре в этом модуле уже есть, и разносить два
|
|
83
|
+
* поля одного понятия по разным файлам — хуже, чем держать их вместе.
|
|
84
|
+
*/
|
|
85
|
+
readonly allowInEditable?: boolean;
|
|
86
|
+
/** Опционально: как эта команда выглядит для модели. */
|
|
87
|
+
readonly agent?: CommandAgentSpec;
|
|
88
|
+
}
|
|
89
|
+
/** Команда, у которой есть блок `agent`, — с сужением типа, чтобы не проверять его повторно. */
|
|
90
|
+
export type AgentVisibleCommand = CommandContribution & {
|
|
91
|
+
readonly agent: CommandAgentSpec;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Причина отказа. Код, а не переведённая фраза: по коду ассистент чинится сам, по фразе — нет,
|
|
95
|
+
* и одна ошибка выглядит одинаково в интерфейсе, логе и тесте.
|
|
96
|
+
*/
|
|
97
|
+
export type CommandErrorKind = 'invalid-id' | 'invalid-keybinding' | 'invalid-when' | 'duplicate' | 'not-found' | 'disabled';
|
|
98
|
+
/**
|
|
99
|
+
* Отказ реестра команд.
|
|
100
|
+
*
|
|
101
|
+
* `message` — диагностика для разработчика; интерфейс обязан строить текст из {@link kind}
|
|
102
|
+
* и {@link params}, а не показывать его пользователю.
|
|
103
|
+
*/
|
|
104
|
+
export declare class CommandError extends Error {
|
|
105
|
+
readonly kind: CommandErrorKind;
|
|
106
|
+
/** Параметры кода: `commandId`, `keybinding`. */
|
|
107
|
+
readonly params: Readonly<Record<string, string>>;
|
|
108
|
+
constructor(kind: CommandErrorKind, message: string, params?: Readonly<Record<string, string>>, options?: ErrorOptions);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Приводит сочетание к каноническому виду: `mod+shift+K` → `mod+shift+k`.
|
|
112
|
+
*
|
|
113
|
+
* Чистая функция и единственное место, где решается, как выглядит сочетание. Регистрация
|
|
114
|
+
* и будущий диспетчер зовут её обе — иначе они разойдутся в написании, и расхождение
|
|
115
|
+
* проявится не отказом, а молчаливо не сработавшей клавишей.
|
|
116
|
+
*
|
|
117
|
+
* Что делает: снимает регистр и пробелы, разворачивает синонимы (`cmd` → `meta`,
|
|
118
|
+
* `esc` → `escape`, `option+left` → `alt+arrowleft`), выстраивает модификаторы в
|
|
119
|
+
* фиксированном порядке. Чего не делает: не разрешает `mod` в платформенный модификатор
|
|
120
|
+
* и не проверяет, существует ли такая клавиша, — первое знает только диспетчер, второе
|
|
121
|
+
* отсекло бы раскладки и клавиши, о которых мы не подумали.
|
|
122
|
+
*
|
|
123
|
+
* @throws {CommandError} `invalid-keybinding`, если есть пустая часть, повторён модификатор,
|
|
124
|
+
* клавиш больше одной или их нет вовсе.
|
|
125
|
+
*/
|
|
126
|
+
export declare function normalizeKeybinding(keybinding: string): string;
|
|
127
|
+
/** Объявление в объёме, которого хватает для условия: диспетчеру больше ничего не нужно. */
|
|
128
|
+
export interface WhenBearing {
|
|
129
|
+
readonly when?: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Разобранное условие команды. Без условия — {@link WHEN_TRUE}, то есть «всегда».
|
|
133
|
+
*
|
|
134
|
+
* Разбор здесь никогда не бросает: до этой точки объявление уже прошло регистрацию, которая
|
|
135
|
+
* отвергает неразбираемое условие. Команда, собранная в обход реестра с испорченным условием,
|
|
136
|
+
* получает «никогда» — тихо пропустить её безопаснее, чем уронить обработчик нажатия.
|
|
137
|
+
*/
|
|
138
|
+
export declare function whenOf(command: WhenBearing): WhenExpr;
|
|
139
|
+
/**
|
|
140
|
+
* Кладёт в кэш уже разобранное условие.
|
|
141
|
+
*
|
|
142
|
+
* Нужна реестру: он разбирает условие на РЕГИСТРАЦИИ, чтобы отвергнуть неразбираемое сразу,
|
|
143
|
+
* — и без этой функции разобранное им пришлось бы выбросить, а {@link whenOf} разбирал бы
|
|
144
|
+
* то же самое второй раз при первом нажатии. Сам кэш наружу не отдаётся: он тайна модуля,
|
|
145
|
+
* и ключом в нём служит объект объявления, а не его идентификатор.
|
|
146
|
+
*/
|
|
147
|
+
export declare function rememberWhen(command: WhenBearing, expr: WhenExpr): void;
|
|
148
|
+
/**
|
|
149
|
+
* Сколько нажатий может быть в аккорде.
|
|
150
|
+
*
|
|
151
|
+
* Два, как в VS Code и WebStorm. Не потому, что три технически сложнее, а потому что аккорд
|
|
152
|
+
* из трёх ступеней человек не воспроизводит по памяти, и такая клавиша существует только
|
|
153
|
+
* в списке.
|
|
154
|
+
*/
|
|
155
|
+
export declare const MAX_CHORD_STEPS = 2;
|
|
156
|
+
/**
|
|
157
|
+
* Разбирает аккорд: `mod+k mod+s` в две ступени, обычное сочетание — в одну.
|
|
158
|
+
*
|
|
159
|
+
* Разделитель ступеней — пробел, и здесь есть ловушка, ради которой написана первая строка:
|
|
160
|
+
* пробел ВНУТРИ ступени незначим (`mod + alt + V` — одно сочетание, и это закреплено тестом
|
|
161
|
+
* реестра). Наивное деление по пробелам сломало бы существующее написание. Правило звучит
|
|
162
|
+
* так: **пробел, прилегающий к `+`, — украшение; пробел между двумя завершёнными ступенями —
|
|
163
|
+
* разделитель.**
|
|
164
|
+
*
|
|
165
|
+
* @throws {CommandError} `invalid-keybinding` — ступеней больше {@link MAX_CHORD_STEPS}
|
|
166
|
+
* либо ступень не разбирается.
|
|
167
|
+
*/
|
|
168
|
+
export declare function normalizeChord(keybinding: string): readonly string[];
|
|
169
|
+
/** Где именно упал чужой код. Пока причина одна, но она не последняя. */
|
|
170
|
+
export interface CommandErrorInfo {
|
|
171
|
+
readonly commandId: string;
|
|
172
|
+
readonly phase: 'enabled';
|
|
173
|
+
}
|
|
174
|
+
export interface CommandRegistryOptions {
|
|
175
|
+
/**
|
|
176
|
+
* Откуда брать контекст, если он не передан явно. По умолчанию — {@link NEUTRAL_WHEN_CONTEXT}.
|
|
177
|
+
*
|
|
178
|
+
* Поставщик, а не аргумент на каждом вызове: контекст обязан быть один на приложение.
|
|
179
|
+
* Если бы его собирал каждый вызывающий, палитра, диспетчер и ассистент разошлись бы в том,
|
|
180
|
+
* что считается «текущим состоянием», — и охранные условия вернулись бы туда, откуда
|
|
181
|
+
* их убрали.
|
|
182
|
+
*/
|
|
183
|
+
readonly getContext?: () => WhenContext;
|
|
184
|
+
/**
|
|
185
|
+
* Куда сообщать об ошибке в чужом коде. По умолчанию — `console.error`.
|
|
186
|
+
*
|
|
187
|
+
* Тот же канал и та же причина, что у шины событий: упавший предикат — это чужая поломка,
|
|
188
|
+
* и она не должна ни ронять палитру, ни исчезать бесследно.
|
|
189
|
+
*/
|
|
190
|
+
readonly onError?: (error: unknown, info: CommandErrorInfo) => void;
|
|
191
|
+
}
|
|
192
|
+
/** Вид реестра команд для плагина: то же, что {@link CommandRegistry}, но без `forPlugin`. */
|
|
193
|
+
export type PluginCommandRegistry = Omit<CommandRegistry, 'forPlugin'>;
|
|
194
|
+
export interface CommandRegistry {
|
|
195
|
+
/**
|
|
196
|
+
* Регистрирует команду. `dispose()` снимает её — на этом держится выключение плагина
|
|
197
|
+
* и уход команд вместе с закрытым редактором.
|
|
198
|
+
*
|
|
199
|
+
* @throws {CommandError} `invalid-id` — пустой идентификатор; `duplicate` — идентификатор
|
|
200
|
+
* занят; `invalid-keybinding` — сочетание не разбирается.
|
|
201
|
+
*/
|
|
202
|
+
register(command: CommandContribution): Disposable;
|
|
203
|
+
/**
|
|
204
|
+
* Вид реестра для плагина: `register` проставляет владельца сам. Повторный вызов
|
|
205
|
+
* с тем же идентификатором возвращает тот же объект — идентичность стабильна, чтобы вид
|
|
206
|
+
* годился в зависимости хуков.
|
|
207
|
+
*
|
|
208
|
+
* В самом виде метода `forPlugin` нет: плагин не располагает способом зарегистрировать
|
|
209
|
+
* команду от чужого имени, потому что пути к этому не существует, а не потому, что так
|
|
210
|
+
* договорились.
|
|
211
|
+
*/
|
|
212
|
+
forPlugin(pluginId: string): PluginCommandRegistry;
|
|
213
|
+
/** Команда по идентификатору или `undefined`. */
|
|
214
|
+
get(id: string): CommandContribution | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Все зарегистрированные, в порядке регистрации. Порядок показа решает палитра.
|
|
217
|
+
*
|
|
218
|
+
* Между изменениями возвращается **та же** ссылка на массив. Это не оптимизация, а
|
|
219
|
+
* требование `useSyncExternalStore`: он сравнивает снимки по ссылке и падает с «результат
|
|
220
|
+
* getSnapshot должен кэшироваться», получая новый массив на каждый вызов. Снимок
|
|
221
|
+
* сбрасывается ровно тогда же, когда зовутся наблюдатели {@link onDidChange}.
|
|
222
|
+
*/
|
|
223
|
+
getAll(): readonly CommandContribution[];
|
|
224
|
+
/**
|
|
225
|
+
* Уведомление о появлении и снятии команды.
|
|
226
|
+
*
|
|
227
|
+
* Существует потому, что набор команд меняется ПОСЛЕ первой отрисовки: команды оболочки
|
|
228
|
+
* регистрируются эффектами компонентов (палитра, справка), а команды плагинов — при
|
|
229
|
+
* активации. Без этого события меню, построенное на первом кадре, навсегда осталось бы
|
|
230
|
+
* без них — и показывало бы пустой «Вид» рядом с работающим сочетанием клавиш.
|
|
231
|
+
*
|
|
232
|
+
* Реестр вкладов решает ту же задачу тем же способом (`observe`), и намеренно одинаково:
|
|
233
|
+
* два разных механизма подписки на два соседних реестра пришлось бы каждый раз вспоминать.
|
|
234
|
+
*/
|
|
235
|
+
onDidChange(cb: () => void): Disposable;
|
|
236
|
+
/**
|
|
237
|
+
* Применима ли команда. Незнакомый идентификатор — `false`: палитра и меню спрашивают
|
|
238
|
+
* про то, что сами же перечислили, и отказ им здесь не нужен.
|
|
239
|
+
*/
|
|
240
|
+
isEnabled(id: string, ctx?: WhenContext): boolean;
|
|
241
|
+
/**
|
|
242
|
+
* Выполняет команду, предварительно проверив применимость.
|
|
243
|
+
*
|
|
244
|
+
* `run` вызывается синхронно — до первого `await` внутри самой команды. Это нужно, чтобы
|
|
245
|
+
* команда могла участвовать в контуре правки и укладываться в одну запись отмены;
|
|
246
|
+
* асинхронен только её результат.
|
|
247
|
+
*
|
|
248
|
+
* @throws {CommandError} `not-found` или `disabled` — отказом, а не тихим ничем: молчание
|
|
249
|
+
* здесь неотличимо от «команда отработала и ничего не сделала», и ассистент по нему
|
|
250
|
+
* не поймёт, что промахнулся.
|
|
251
|
+
*/
|
|
252
|
+
execute(id: string, args?: unknown, ctx?: WhenContext): Promise<unknown>;
|
|
253
|
+
/**
|
|
254
|
+
* Команды, у которых объявлен блок `agent`, — поверхность инструментов ассистента.
|
|
255
|
+
*
|
|
256
|
+
* Применимость здесь **не фильтруется**, и это осознанно: набор инструментов уходит
|
|
257
|
+
* в каждый запрос к модели, поэтому он обязан быть устойчивым — иначе префикс запроса
|
|
258
|
+
* меняется от фокуса пользователя и кэширование префикса перестаёт работать. Применимость
|
|
259
|
+
* проверяется в момент вызова, там же, где у человека.
|
|
260
|
+
*/
|
|
261
|
+
agentCommands(): readonly AgentVisibleCommand[];
|
|
262
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Освобождение ресурса — общий примитив для всех реестров.
|
|
3
|
+
*
|
|
4
|
+
* Каждая регистрация возвращает `Disposable`, и это не украшение: на нём держится снятие
|
|
5
|
+
* вкладов при выключении плагина. Плагин складывает всё в `subscriptions` своего контекста,
|
|
6
|
+
* а рантайм освобождает их сам — без этого выключение плагина оставляло бы его панели
|
|
7
|
+
* и команды в реестрах навсегда.
|
|
8
|
+
*
|
|
9
|
+
* @module shell/platform/primitives/disposable
|
|
10
|
+
*/
|
|
11
|
+
/** Что-то, что умеет освободить занятое. Повторный вызов обязан быть безвредным. */
|
|
12
|
+
export interface Disposable {
|
|
13
|
+
dispose(): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Оборачивает функцию в {@link Disposable} с защитой от повторного вызова.
|
|
17
|
+
*
|
|
18
|
+
* Идемпотентность здесь несущая: реестр может освободить подписку сам (например, при
|
|
19
|
+
* замене вклада), а владелец потом вызовет `dispose()` ещё раз из своего списка.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toDisposable(fn: () => void): Disposable;
|
|
22
|
+
/** Освобождает набор подписок. Ошибка одной не мешает остальным. */
|
|
23
|
+
export declare function disposeAll(items: readonly Disposable[]): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Disposable } from './disposable.js';
|
|
2
|
+
/**
|
|
3
|
+
* Типизированный ключ события: связывает идентификатор с типом полезной нагрузки.
|
|
4
|
+
*
|
|
5
|
+
* Тот же приём, что у токена сервиса, и по той же причине — строка с приведением типа
|
|
6
|
+
* на каждой подписке рано или поздно разъезжается с тем, что кладут в `emit`.
|
|
7
|
+
*/
|
|
8
|
+
export interface EventType<T> {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** Только для вывода типов, в рантайме отсутствует. */
|
|
11
|
+
readonly __type?: T;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Объявляет тип события.
|
|
15
|
+
*
|
|
16
|
+
* Идентификатор — с пространством имён владельца (`workspace.didChange`, `editor.didFocus`):
|
|
17
|
+
* шина различает события только по нему, и совпадение у двух плагинов означает перекрёстную
|
|
18
|
+
* доставку с чужой нагрузкой.
|
|
19
|
+
*/
|
|
20
|
+
export declare function defineEvent<T>(id: string): EventType<T>;
|
|
21
|
+
/** Шина: публикация и подписка. Больше ничего — всё остальное строится поверх. */
|
|
22
|
+
export interface EventBus {
|
|
23
|
+
/**
|
|
24
|
+
* Синхронно доставляет нагрузку всем подписчикам этого типа.
|
|
25
|
+
*
|
|
26
|
+
* Возвращает управление только после того, как отработал последний подписчик. Ошибка
|
|
27
|
+
* любого из них не прерывает рассылку и не выходит наружу.
|
|
28
|
+
*/
|
|
29
|
+
emit<T>(type: EventType<T>, payload: T): void;
|
|
30
|
+
/**
|
|
31
|
+
* Подписывает обработчик. `dispose()` отписывает ровно эту подписку — один и тот же
|
|
32
|
+
* обработчик, подписанный дважды, вызывается дважды и отписывается по одному.
|
|
33
|
+
*
|
|
34
|
+
* Отписка действует немедленно, в том числе изнутри доставки: подписчик, снятый другим
|
|
35
|
+
* подписчиком, уже не получит текущее событие.
|
|
36
|
+
*/
|
|
37
|
+
on<T>(type: EventType<T>, cb: (payload: T) => void): Disposable;
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Disposable } from './disposable.js';
|
|
2
|
+
/**
|
|
3
|
+
* Типизированное имя точки расширения.
|
|
4
|
+
*
|
|
5
|
+
* Ключ — `id`, а не идентичность объекта, по той же причине, что у токена сервиса:
|
|
6
|
+
* модуль с объявлением может оказаться в памяти дважды, и вклады не должны разъезжаться
|
|
7
|
+
* по двум разным точкам с одинаковым именем.
|
|
8
|
+
*/
|
|
9
|
+
export interface ExtensionPoint<T> {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Только для вывода типов, в рантайме отсутствует. */
|
|
12
|
+
readonly __type?: T;
|
|
13
|
+
}
|
|
14
|
+
/** Объявляет точку расширения. `id` виден в диагностике, поэтому пустым быть не может. */
|
|
15
|
+
export declare function defineExtensionPoint<T>(id: string): ExtensionPoint<T>;
|
|
16
|
+
/** Один вклад в точку расширения. Заморожен: порядок и происхождение не переписываются извне. */
|
|
17
|
+
export interface Contribution<T> {
|
|
18
|
+
/** Уникален в пределах точки. Годится как React-ключ и как адрес в диагностике. */
|
|
19
|
+
readonly id: string;
|
|
20
|
+
/** Кто внёс. Проставляет реестр, а не вносящий. */
|
|
21
|
+
readonly pluginId: string;
|
|
22
|
+
/** Меньше — раньше. По умолчанию `0`. */
|
|
23
|
+
readonly order: number;
|
|
24
|
+
readonly value: T;
|
|
25
|
+
}
|
|
26
|
+
/** Необязательные свойства вклада, задаваемые вносящим. */
|
|
27
|
+
export interface ContributionMeta {
|
|
28
|
+
/** Устойчивый идентификатор вклада; уникален в пределах точки. */
|
|
29
|
+
readonly id?: string;
|
|
30
|
+
/** Меньше — раньше. По умолчанию `0`. */
|
|
31
|
+
readonly order?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Вид реестра для одного плагина — то, что лежит в `PluginContext.extensions`.
|
|
35
|
+
*
|
|
36
|
+
* `get` и `observe` видят вклады всех плагинов: панель слота рисуется целиком, кем бы
|
|
37
|
+
* её части ни были внесены. Разделение касается только записи.
|
|
38
|
+
*/
|
|
39
|
+
export interface ExtensionRegistry {
|
|
40
|
+
/**
|
|
41
|
+
* Вносит вклад от имени плагина, которому принадлежит этот вид реестра.
|
|
42
|
+
*
|
|
43
|
+
* Без `meta.id` идентификатор генерируется как `<pluginId>:<point>#<n>`; явный `id`
|
|
44
|
+
* обязан быть уникален в пределах точки. `meta.order` по умолчанию `0`.
|
|
45
|
+
*
|
|
46
|
+
* `dispose()` снимает вклад и уведомляет наблюдателей.
|
|
47
|
+
*/
|
|
48
|
+
contribute<T>(point: ExtensionPoint<T>, item: T, meta?: ContributionMeta): Disposable;
|
|
49
|
+
/** Вклады, упорядоченные по `order`; при равенстве — по порядку регистрации. */
|
|
50
|
+
get<T>(point: ExtensionPoint<T>): readonly Contribution<T>[];
|
|
51
|
+
/** Для реактивности UI: вызывается при добавлении и при снятии вклада. */
|
|
52
|
+
observe<T>(point: ExtensionPoint<T>, cb: () => void): Disposable;
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Нормализация пути внутри набора файлов модуля: плагина или сайдкаров формы.
|
|
3
|
+
*
|
|
4
|
+
* Отдельно от `normalizePath` ресурсов — у них разный ответ на побег за корень. Адрес ресурса
|
|
5
|
+
* за корнем источника — ошибка программы, и там бросают. Путь модуля за корнем набора файлов —
|
|
6
|
+
* это то, что написал автор плагина или формы (`"main": "../../secrets.ts"`), и ответ на него
|
|
7
|
+
* обязан быть ДАННЫМИ: разбор манифеста превращает его в отказ с объяснением, линковщик —
|
|
8
|
+
* в ненайденный модуль. Бросок здесь уронил бы разбор чужого каталога целиком.
|
|
9
|
+
*
|
|
10
|
+
* Одна функция на линковщик, разбор манифеста и сборку плагина: разойдись они, валидатор
|
|
11
|
+
* пропускал бы точку входа, которую оболочка отвергнет.
|
|
12
|
+
*
|
|
13
|
+
* @module @reformer/builder-plugin-api/primitives/module-path
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Нормализует путь: убирает `.`, схлопывает `..`, приводит разделители к `/`.
|
|
17
|
+
*
|
|
18
|
+
* Возвращает `undefined`, если путь вылез за корень набора файлов. Это не педантизм: набор
|
|
19
|
+
* файлов — весь мир исполняемого кода, и `../../../etc` обязан быть отказом, а не промахом.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeModulePath(path: string): string | undefined;
|