@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ресурс — ССЫЛКА на нечто в источнике, а не его содержимое.
|
|
3
|
+
*
|
|
4
|
+
* В v1 дескриптор файла нёс на себе чтение и запись, был привязан к живому источнику и
|
|
5
|
+
* поэтому растёкся по стору, панелям, корпусу знаний и кодогену: любой, кто держал ссылку,
|
|
6
|
+
* держал и канал ввода-вывода. Здесь ресурс — описание, которое можно положить в стор,
|
|
7
|
+
* сериализовать и сравнить; чтение и запись живут в Workspace, который появится в Э2.
|
|
8
|
+
*
|
|
9
|
+
* Модуль владеет двумя вещами, которые нельзя отдавать адаптерам источников:
|
|
10
|
+
*
|
|
11
|
+
* - **нормализацией путей** — иначе два адаптера разойдутся в трактовке `..` и это вскроется
|
|
12
|
+
* на третьем. Адаптеру остаётся смысл пути (что он адресует), а не его форма;
|
|
13
|
+
* - **таблицей расширений** — она решает ровно один вопрос: текст или байты (и подсветку).
|
|
14
|
+
* Вопрос «что это за документ» здесь не решается вовсе: `.json` бывает и схемой формы,
|
|
15
|
+
* и конфигом пакета, и как транспорт он однозначен в обоих случаях. Кто откроет файл —
|
|
16
|
+
* решает редактор по успеху разбора.
|
|
17
|
+
*
|
|
18
|
+
* @module shell/platform/primitives/resource
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Непрозрачный идентификатор ресурса. Формат — `<sourceId>:<path>`.
|
|
22
|
+
*
|
|
23
|
+
* Сравнивать только целиком: у файловой системы внутри путь, у HTTP-источника — то,
|
|
24
|
+
* что вернул сервер, и потребитель не должен их различать. Разбирать идентификатор
|
|
25
|
+
* позволено только {@link parseResourceId} — и то потому, что путевая арифметика
|
|
26
|
+
* (резолвер импортов, дерево, листинг) без этого невозможна.
|
|
27
|
+
*/
|
|
28
|
+
export type ResourceId = string;
|
|
29
|
+
/** Ссылка на ресурс: то, что можно хранить и передавать, не удерживая источник. */
|
|
30
|
+
export interface ResourceRef {
|
|
31
|
+
readonly id: ResourceId;
|
|
32
|
+
readonly sourceId: string;
|
|
33
|
+
/** Путь внутри источника: разделитель `/`, без ведущего слэша, нормализован. */
|
|
34
|
+
readonly path: string;
|
|
35
|
+
/** Последний сегмент пути. Дублирует `path` намеренно — списки рисуют именно его. */
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly kind: 'file' | 'directory';
|
|
38
|
+
/** `application/json`, `text/typescript`, `text/markdown`, `image/png`… */
|
|
39
|
+
readonly mediaType: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Свойства материализованного ресурса.
|
|
43
|
+
*
|
|
44
|
+
* Отдельно от {@link ResourceRef} по времени жизни: ссылка стабильна, а это — нет.
|
|
45
|
+
* Смешать их означало бы, что каждая перепроверка размера меняет объект, на который
|
|
46
|
+
* подписан UI.
|
|
47
|
+
*/
|
|
48
|
+
export interface ResourceStat {
|
|
49
|
+
readonly kind: 'file' | 'directory';
|
|
50
|
+
/**
|
|
51
|
+
* Непрозрачный маркер версии: ETag, mtime, хеш — что дал источник.
|
|
52
|
+
* Сравнивается ТОЛЬКО на равенство: «новее» вычислить нельзя, а конфликт — можно.
|
|
53
|
+
*/
|
|
54
|
+
readonly revision?: string;
|
|
55
|
+
readonly size?: number;
|
|
56
|
+
readonly mediaType?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Собирает идентификатор из источника и пути, попутно нормализуя путь.
|
|
60
|
+
*
|
|
61
|
+
* Нормализация здесь, а не у вызывающего: иначе `fs:./a` и `fs:a` окажутся разными ключами
|
|
62
|
+
* одного и того же ресурса, и кэш Workspace материализует его дважды.
|
|
63
|
+
*
|
|
64
|
+
* @throws если `sourceId` пуст или содержит `:` (разбор идёт по первому двоеточию,
|
|
65
|
+
* и такой идентификатор нельзя было бы разобрать обратно), либо если путь выходит за корень.
|
|
66
|
+
*/
|
|
67
|
+
export declare function makeResourceId(sourceId: string, path: string): ResourceId;
|
|
68
|
+
/**
|
|
69
|
+
* Разбирает идентификатор на источник и путь.
|
|
70
|
+
*
|
|
71
|
+
* Делит по ПЕРВОМУ двоеточию: `:` — легальный символ имени файла, а в `sourceId` он запрещён
|
|
72
|
+
* ({@link makeResourceId}), поэтому неоднозначности нет.
|
|
73
|
+
*
|
|
74
|
+
* Путь нормализуется и на разборе — идентификатор мог прийти извне (из хранилища, из ссылки,
|
|
75
|
+
* из ответа источника), и `fs:../../etc/hosts` обязан быть отвергнут здесь, а не в адаптере,
|
|
76
|
+
* где проверку легко забыть.
|
|
77
|
+
*
|
|
78
|
+
* @throws если строка не похожа на идентификатор ресурса или путь выходит за корень.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseResourceId(id: ResourceId): {
|
|
81
|
+
readonly sourceId: string;
|
|
82
|
+
readonly path: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Приводит путь к канонической форме: разделитель `/`, без ведущего и хвостового слэша,
|
|
86
|
+
* без пустых сегментов и `.`, с разрешёнными `..`.
|
|
87
|
+
*
|
|
88
|
+
* Корень источника — пустая строка: `''`, `'.'`, `'/'` дают один и тот же путь.
|
|
89
|
+
*
|
|
90
|
+
* @throws если `..` уводит выше корня источника. Это не педантизм: путь приходит из ответа
|
|
91
|
+
* источника, из импорта внутри схемы и из ассистента, и молчаливое схлопывание такого
|
|
92
|
+
* пути к корню превратило бы побег в обычное чтение.
|
|
93
|
+
*/
|
|
94
|
+
export declare function normalizePath(path: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Склеивает сегменты и нормализует результат.
|
|
97
|
+
*
|
|
98
|
+
* Это же и «резолв» для резолвера импортов: `joinPath(dirname(schema), './validation')`
|
|
99
|
+
* даёт путь соседа, а побег за корень отсекается нормализацией.
|
|
100
|
+
*/
|
|
101
|
+
export declare function joinPath(...segments: readonly string[]): string;
|
|
102
|
+
/** Родительский каталог. Для ресурса верхнего уровня — корень источника, пустая строка. */
|
|
103
|
+
export declare function dirname(path: string): string;
|
|
104
|
+
/** Последний сегмент пути. Для корня — пустая строка. */
|
|
105
|
+
export declare function basename(path: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Расширение вместе с точкой (`.json`) или пустая строка.
|
|
108
|
+
*
|
|
109
|
+
* Ведущая точка — часть имени скрытого файла, а не расширение: у `.gitignore` расширения нет.
|
|
110
|
+
* Из `schema.d.ts` берётся только `.ts` — составные расширения таблица не различает, и это
|
|
111
|
+
* достаточно: `.d.ts` и `.ts` читаются одинаково.
|
|
112
|
+
*/
|
|
113
|
+
export declare function extname(path: string): string;
|
|
114
|
+
/**
|
|
115
|
+
* Путь `to`, записанный относительно РЕСУРСА `from` — то есть относительно каталога,
|
|
116
|
+
* в котором `from` лежит.
|
|
117
|
+
*
|
|
118
|
+
* База — именно ресурс, а не каталог, потому что единственный потребитель — резолвер
|
|
119
|
+
* импортов, а импорт написан внутри файла: `../shared/rules` в `src/forms/credit/schema.json`
|
|
120
|
+
* означает `src/forms/shared/rules`.
|
|
121
|
+
*
|
|
122
|
+
* Результат всегда начинается с `./` или `..` — без префикса `shared/rules` читался бы
|
|
123
|
+
* как имя пакета, а не как сосед.
|
|
124
|
+
*/
|
|
125
|
+
export declare function relativePath(from: string, to: string): string;
|
|
126
|
+
/**
|
|
127
|
+
* Расширение → медиатип. Таблица отвечает на один вопрос: текст или байты (и какая подсветка).
|
|
128
|
+
*
|
|
129
|
+
* Она сознательно не полна и не должна расти «на всякий случай»: неизвестное расширение —
|
|
130
|
+
* это {@link DEFAULT_MEDIA_TYPE}, и источник всё равно может переопределить ответ.
|
|
131
|
+
*/
|
|
132
|
+
export declare const MEDIA_TYPE_BY_EXTENSION: Readonly<Record<string, string | undefined>>;
|
|
133
|
+
/**
|
|
134
|
+
* Ответ для неизвестного расширения.
|
|
135
|
+
*
|
|
136
|
+
* Текст, а не `application/octet-stream`: файлы без узнаваемого расширения — это `LICENSE`,
|
|
137
|
+
* `.editorconfig`, `.prettierrc` и прочие конфиги, то есть текст, а бинарные форматы как раз
|
|
138
|
+
* всегда носят расширение из таблицы. Обратный выбор запретил бы `readText` там, где он нужен
|
|
139
|
+
* чаще всего.
|
|
140
|
+
*/
|
|
141
|
+
export declare const DEFAULT_MEDIA_TYPE = "text/plain";
|
|
142
|
+
/**
|
|
143
|
+
* Медиатип ресурса: основа — расширение, подсказка источника имеет приоритет.
|
|
144
|
+
*
|
|
145
|
+
* Приоритет именно такой, потому что источник знает больше: `Content-Type` HTTP-ответа —
|
|
146
|
+
* это факт о содержимом, а расширение — догадка по имени. Нераспознаваемая подсказка
|
|
147
|
+
* игнорируется, и ответ падает обратно на таблицу.
|
|
148
|
+
*
|
|
149
|
+
* @param path путь ресурса; для {@link ResourceRef} — его `path`.
|
|
150
|
+
* @param hint то, что сказал источник (заголовок `Content-Type` или его аналог).
|
|
151
|
+
*/
|
|
152
|
+
export declare function mediaTypeFor(path: string, hint?: string): string;
|
|
153
|
+
/**
|
|
154
|
+
* Можно ли читать такой ресурс текстом.
|
|
155
|
+
*
|
|
156
|
+
* Единственный вопрос, на который отвечает медиатип в контуре чтения: `readText` бинарного
|
|
157
|
+
* ресурса обязан быть отказом, а не набором мусорных символов — тихая порча данных хуже отказа.
|
|
158
|
+
*/
|
|
159
|
+
export declare function isTextMediaType(mediaType: string): boolean;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Версии и диапазоны — ровно в том объёме, в каком их спрашивает манифест плагина.
|
|
3
|
+
*
|
|
4
|
+
* ## Решение: своя утилита, а не пакет `semver`
|
|
5
|
+
*
|
|
6
|
+
* Пакета `semver` в зависимостях нет, и заводить его ради трёх сравнений мы не стали. Довод
|
|
7
|
+
* не «лишняя зависимость вообще», а измеримый: библиотека едет в БРАУЗЕРНЫЙ бандл оболочки,
|
|
8
|
+
* потому что диапазон разбирается при обходе каталога плагинов, то есть в рантайме, — и платит
|
|
9
|
+
* за это каждый, кто открыл инструмент, включая тех, у кого плагинов нет вовсе. Взамен мы
|
|
10
|
+
* обязаны назвать, чего эта утилита НЕ умеет: список ниже и есть цена решения.
|
|
11
|
+
*
|
|
12
|
+
* ## Чего здесь нет — названо, а не забыто
|
|
13
|
+
*
|
|
14
|
+
* - **Пререлизы (`1.0.0-beta.1`) отвергаются.** Не «игнорируются»: {@link parseVersion}
|
|
15
|
+
* и {@link parseRange} возвращают `undefined`, а разбор манифеста превращает это в отказ
|
|
16
|
+
* с внятным текстом. Молчаливое отбрасывание суффикса дало бы худший из возможных исходов —
|
|
17
|
+
* плагин, объявивший `^1.0.0-beta`, работал бы против релизного API и узнал бы об этом
|
|
18
|
+
* поведением, а не сообщением.
|
|
19
|
+
* - **Составных диапазонов нет.** Ни конъюнкции (`>=1.2.0 <2.0.0`), ни дизъюнкции (`1.x || 2.x`).
|
|
20
|
+
* Один сравнитель на диапазон — всё остальное отвергается разбором. Требование плагина,
|
|
21
|
+
* которое нельзя выразить каретой или тильдой, почти наверняка означает, что контракт
|
|
22
|
+
* пора делить на два, а не что нам нужна грамматика npm.
|
|
23
|
+
* - **Метаданных сборки (`+build`) нет** — по той же причине, что и пререлизов.
|
|
24
|
+
*
|
|
25
|
+
* ## Отступление от npm, которое стоит знать
|
|
26
|
+
*
|
|
27
|
+
* У npm неполная версия при сравнителе округляется вверх: `>1.2` там значит `>=1.3.0`. Здесь
|
|
28
|
+
* недостающие части заполняются нулями, и `>1.2` значит `>1.2.0`. Правило npm сюрпризно ровно
|
|
29
|
+
* в том месте, где человек пишет диапазон руками в JSON, — а заполнение нулями читается
|
|
30
|
+
* одинаково всеми. Всё остальное (карета на нулевом мажоре, тильда, X-диапазоны) совпадает
|
|
31
|
+
* с npm намеренно: автор плагина приносит привычку из `package.json`.
|
|
32
|
+
*
|
|
33
|
+
* @module shell/platform/primitives/semver
|
|
34
|
+
*/
|
|
35
|
+
/** Разобранная версия. Три числа и ничего больше — см. «чего здесь нет» в шапке модуля. */
|
|
36
|
+
export interface SemVer {
|
|
37
|
+
readonly major: number;
|
|
38
|
+
readonly minor: number;
|
|
39
|
+
readonly patch: number;
|
|
40
|
+
}
|
|
41
|
+
/** Граница диапазона: версия плюс то, входит ли она сама. */
|
|
42
|
+
export interface VersionBound {
|
|
43
|
+
readonly version: SemVer;
|
|
44
|
+
readonly inclusive: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Разобранный диапазон — ВСЕГДА в виде пары границ, какой бы формой он ни был записан.
|
|
48
|
+
*
|
|
49
|
+
* Нормализация к границам, а не хранение сравнителя, нужна ради одного: `satisfies` тогда
|
|
50
|
+
* не ветвится по форме записи вовсе. Карета, тильда и X-диапазон отличаются только тем,
|
|
51
|
+
* как из них считается верхняя граница, и это различие обязано жить в разборе, а не в каждой
|
|
52
|
+
* проверке.
|
|
53
|
+
*
|
|
54
|
+
* `source` сохраняется дословно: его показывают человеку в отказе, и `^1` там обязан выглядеть
|
|
55
|
+
* как `^1`, а не как «>=1.0.0 <2.0.0», которого он не писал.
|
|
56
|
+
*/
|
|
57
|
+
export interface VersionRange {
|
|
58
|
+
readonly source: string;
|
|
59
|
+
/** Нижняя граница. Её нет только у `*`. */
|
|
60
|
+
readonly min?: VersionBound;
|
|
61
|
+
/** Верхняя граница. Её нет у `>=`, `>` и `*`. */
|
|
62
|
+
readonly max?: VersionBound;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Разбирает версию: `1.2.3`, `1.2`, `1`.
|
|
66
|
+
*
|
|
67
|
+
* Неполная версия дополняется нулями — `1.2` это `1.2.0`. Пререлиз и метаданные сборки
|
|
68
|
+
* отвергаются (см. шапку модуля), поэтому `undefined` здесь значит «так версия не пишется»,
|
|
69
|
+
* а не «версия старая».
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseVersion(text: string): SemVer | undefined;
|
|
72
|
+
/** `1.2.3` — для сообщений об отказе и для сравнения строк в тестах. */
|
|
73
|
+
export declare function formatVersion(version: SemVer): string;
|
|
74
|
+
/** Обычный порядок: отрицательное — `a` раньше `b`. */
|
|
75
|
+
export declare function compareVersions(a: SemVer, b: SemVer): number;
|
|
76
|
+
/**
|
|
77
|
+
* Разбирает диапазон: `^1`, `~1.2`, `>=1.2.3`, `>1`, `<=2.0.0`, `<2`, `1.2.3`, `1.x`, `*`.
|
|
78
|
+
*
|
|
79
|
+
* `undefined` — диапазон не той формы: составной (`>=1 <2`, `1.x || 2.x`), с пререлизом,
|
|
80
|
+
* с лишними компонентами. Разбор манифеста превращает это в отказ, а не в «пропустим».
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseRange(text: string): VersionRange | undefined;
|
|
83
|
+
/** Попадает ли версия в диапазон. Обе стороны уже разобраны — отказов здесь не бывает. */
|
|
84
|
+
export declare function satisfiesRange(version: SemVer, range: VersionRange): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Удобная форма для вызывающего, которому нечего делать с разбором: строка против строки.
|
|
87
|
+
*
|
|
88
|
+
* Неразбираемая сторона даёт `false`, а не исключение, — но именно поэтому проверять ФОРМУ
|
|
89
|
+
* записи этой функцией нельзя: «не та версия» и «так версия не пишется» здесь неразличимы.
|
|
90
|
+
* Там, где различие нужно (разбор манифеста), зовут {@link parseVersion} и {@link parseRange}
|
|
91
|
+
* по отдельности.
|
|
92
|
+
*/
|
|
93
|
+
export declare function satisfies(version: string, range: string): boolean;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Disposable } from './disposable.js';
|
|
2
|
+
/**
|
|
3
|
+
* Типизированная ссылка на сервис.
|
|
4
|
+
*
|
|
5
|
+
* Сравнивается и хранится **по `id`**, а не по идентичности объекта. Это осознанно:
|
|
6
|
+
* плагины из каталога проекта грузятся собственным линкером, и один и тот же модуль
|
|
7
|
+
* с объявлением токена может оказаться в памяти дважды. При ключе по объекту второй
|
|
8
|
+
* экземпляр токена не нашёл бы уже зарегистрированный сервис, а диагностика выглядела бы
|
|
9
|
+
* как «сервис есть, но не находится» — худший вид отказа.
|
|
10
|
+
*/
|
|
11
|
+
export interface ServiceToken<T> {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/**
|
|
14
|
+
* Только для вывода типов, в рантайме поля нет.
|
|
15
|
+
*
|
|
16
|
+
* Без него `ServiceToken<A>` и `ServiceToken<B>` структурно одинаковы, и компилятор
|
|
17
|
+
* молча пропустил бы `register(tokenA, implB)`.
|
|
18
|
+
*/
|
|
19
|
+
readonly __type?: T;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Объявляет токен сервиса.
|
|
23
|
+
*
|
|
24
|
+
* `id` попадает в сообщения об ошибках и в ключ реестра, поэтому он обязан быть
|
|
25
|
+
* непустым и осмысленным: `reformer.workspace`, а не `ws`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function defineService<T>(id: string): ServiceToken<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Слот занят или освобождён.
|
|
30
|
+
*
|
|
31
|
+
* Нагрузка — только идентификатор и факт: реализацию подписчик берёт `get`, потому что между
|
|
32
|
+
* уведомлением и чтением слот мог смениться ещё раз, и переданная в событии ссылка была бы
|
|
33
|
+
* устаревшей ровно в том сценарии, ради которого уведомление и заведено (перезагрузка плагина
|
|
34
|
+
* снимает регистрацию и ставит новую).
|
|
35
|
+
*/
|
|
36
|
+
export interface ServiceChange {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
/** `true` — слот занят, `false` — освобождён. */
|
|
39
|
+
readonly present: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ServiceRegistry {
|
|
42
|
+
/**
|
|
43
|
+
* Занимает слот токена.
|
|
44
|
+
*
|
|
45
|
+
* Бросает, если слот уже занят: действующая реализация иначе зависела бы от порядка
|
|
46
|
+
* активации, а он объявлен незначимым. `dispose()` освобождает слот — после него токен
|
|
47
|
+
* снова можно зарегистрировать.
|
|
48
|
+
*/
|
|
49
|
+
register<T>(token: ServiceToken<T>, impl: T): Disposable;
|
|
50
|
+
/** `undefined`, если сервиса нет. Вызывающий обязан деградировать, а не падать. */
|
|
51
|
+
get<T>(token: ServiceToken<T>): T | undefined;
|
|
52
|
+
/** Бросает с внятным сообщением. Только для сервисов Host — см. правило доступности. */
|
|
53
|
+
require<T>(token: ServiceToken<T>): T;
|
|
54
|
+
/**
|
|
55
|
+
* Слот заняли или освободили.
|
|
56
|
+
*
|
|
57
|
+
* Заведено для одного проверяемого случая: **наблюдать за появлением провайдера нечем**.
|
|
58
|
+
* Правило «сервис ищется в момент использования» закрывает команды и обработчики — они
|
|
59
|
+
* спрашивают реестр тогда, когда их позвали, — но не закрывает того, кто обязан ОТРЕАГИРОВАТЬ
|
|
60
|
+
* на появление службы: перерисовать панель, доставшую наконец своего провайдера, или снять
|
|
61
|
+
* деградацию. Без уведомления такому потребителю остаётся опрос по таймеру.
|
|
62
|
+
*
|
|
63
|
+
* Подписчик не должен полагаться на порядок и обязан быть дешёвым: доставка синхронная,
|
|
64
|
+
* внутри `register`/`dispose`. Его исключение не выходит наружу — оно бы превратило отказ
|
|
65
|
+
* наблюдателя в отказ регистрации, то есть уронило бы активацию чужого плагина.
|
|
66
|
+
*/
|
|
67
|
+
onDidChange(listener: (event: ServiceChange) => void): Disposable;
|
|
68
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Контекст применимости — снимок состояния, по которому команда решает, доступна ли она сейчас.
|
|
3
|
+
*
|
|
4
|
+
* Проектируется раньше реестра, и это не порядок изложения. Вторая причина монолитности
|
|
5
|
+
* обработчика клавиш в v1 — охранные условия («фокус в поле ввода», «курсор на канвасе»,
|
|
6
|
+
* «не на кнопке»). Реестр команд сам по себе их не убирает, он их прячет: условия просто
|
|
7
|
+
* переезжают внутрь десятка `enabled`. Убирает их только явный снимок состояния, общий для
|
|
8
|
+
* всех команд, — вот этот.
|
|
9
|
+
*
|
|
10
|
+
* Почему сюда входит режим превью: условие «курсор на канвасе» невозможно выразить, не
|
|
11
|
+
* обращаясь одновременно к режиму поверхности. Проверка по коду v1 дала отрицательный ответ,
|
|
12
|
+
* поэтому режим входит в контекст сразу — иначе контракт пришлось бы ломать на первом же
|
|
13
|
+
* редакторе.
|
|
14
|
+
*
|
|
15
|
+
* Ни одно поле не знает предметной области: `activeResourceKind` и `previewMode` —
|
|
16
|
+
* непрозрачные строки, их смысл задаёт плагин, Host только переносит значение от источника
|
|
17
|
+
* к предикату.
|
|
18
|
+
*
|
|
19
|
+
* @module shell/platform/primitives/when-context
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Куда направлен фокус.
|
|
23
|
+
*
|
|
24
|
+
* Различение `editable` от остальных несущее: именно на нём стоит правило «сочетание
|
|
25
|
+
* пропускается, если фокус в поле ввода» — то самое, из-за которого v1 не разбирается на части.
|
|
26
|
+
*
|
|
27
|
+
* - `editable` — поле ввода, текстовый редактор, `contenteditable`;
|
|
28
|
+
* - `canvas` — поверхность превью;
|
|
29
|
+
* - `tree` — дерево ресурсов;
|
|
30
|
+
* - `panel` — прочий интерфейс оболочки;
|
|
31
|
+
* - `none` — фокуса нет ни на чём осмысленном (например, на `body`).
|
|
32
|
+
*/
|
|
33
|
+
export type FocusTarget = 'editable' | 'control' | 'canvas' | 'tree' | 'panel' | 'none';
|
|
34
|
+
/** Состояние, в котором вычисляется применимость команды. Только чтение, только снимок. */
|
|
35
|
+
export interface WhenContext {
|
|
36
|
+
/** Куда направлен фокус: поле ввода, канвас, дерево, панель. */
|
|
37
|
+
readonly focus: FocusTarget;
|
|
38
|
+
/** Идентификатор активного редактора; `null`, если открытых вкладок нет. */
|
|
39
|
+
readonly activeEditorId: string | null;
|
|
40
|
+
/**
|
|
41
|
+
* Вид активного ресурса — непрозрачная строка от плагина документа
|
|
42
|
+
* (`form.schema`, `markdown`, …). Host её не интерпретирует, только сравнивает.
|
|
43
|
+
*/
|
|
44
|
+
readonly activeResourceKind: string | null;
|
|
45
|
+
/** Есть ли выделение — в дереве, на канвасе или в редакторе; что именно, знает редактор. */
|
|
46
|
+
readonly hasSelection: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Режим поверхности превью; `null` — превью не активно.
|
|
49
|
+
*
|
|
50
|
+
* Без этого поля не выражается «курсор на канвасе»: одного `focus === 'canvas'` мало,
|
|
51
|
+
* потому что в режиме взаимодействия тот же фокус означает противоположное.
|
|
52
|
+
*/
|
|
53
|
+
readonly previewMode: string | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Нейтральный контекст: ничего не в фокусе, ничего не открыто, ничего не выделено.
|
|
57
|
+
*
|
|
58
|
+
* Нужен там, где реального контекста ещё нет: реестр команд без поставщика контекста,
|
|
59
|
+
* тесты, вызов команды из кода при старте. Значение заморожено — им можно делиться.
|
|
60
|
+
*/
|
|
61
|
+
export declare const NEUTRAL_WHEN_CONTEXT: WhenContext;
|
|
62
|
+
/**
|
|
63
|
+
* Собирает контекст из нейтрального и переданных отличий.
|
|
64
|
+
*
|
|
65
|
+
* Существует ради одного свойства: добавление поля в {@link WhenContext} не должно ломать
|
|
66
|
+
* каждое место, где контекст собирается. Поставщики контекста в оболочке и тесты указывают
|
|
67
|
+
* только то, что для них значимо.
|
|
68
|
+
*/
|
|
69
|
+
export declare function whenContext(patch?: Partial<WhenContext>): WhenContext;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Условие применимости как ДАННЫЕ: разбор, вычисление, специфичность.
|
|
3
|
+
*
|
|
4
|
+
* ## Зачем понадобилось, если есть `enabled(ctx)`
|
|
5
|
+
*
|
|
6
|
+
* Предикат отвечает на вопрос «доступна ли команда сейчас» и больше ни на какой. Всё
|
|
7
|
+
* остальное, что нужно клавиатуре, требует условие ПРОЧИТАТЬ, а функцию прочитать нельзя:
|
|
8
|
+
*
|
|
9
|
+
* - **сравнить два условия** — без этого нельзя решить, чья клавиша выигрывает, и `delete`
|
|
10
|
+
* в проекте сегодня разводится порядком регистрации плагинов, который по контракту
|
|
11
|
+
* ничего не значит (см. `plugin/registry.test.ts`);
|
|
12
|
+
* - **доказать, что два условия не пересекаются** — без этого всякая пара на одной клавише
|
|
13
|
+
* выглядит конфликтом, даже «в дереве» против «на канвасе»;
|
|
14
|
+
* - **записать в файл** — манифест плагина и раскладка пользователя это текст;
|
|
15
|
+
* - **показать человеку** — в таблице клавиш условие обязано быть видно, иначе строка
|
|
16
|
+
* «Delete — удалить» повторяется дважды и объяснить разницу нечем.
|
|
17
|
+
*
|
|
18
|
+
* Поэтому условие становится строкой с грамматикой, а `enabled` остаётся — они отвечают
|
|
19
|
+
* на разные вопросы. Водораздел проведён в шапке `./command`: `when` — про состояние
|
|
20
|
+
* платформы (где фокус, что открыто), `enabled` — про приватное состояние владельца
|
|
21
|
+
* (есть ли что отменять). Второе данными быть не может и переводу не подлежит.
|
|
22
|
+
*
|
|
23
|
+
* ## Чего в грамматике нет
|
|
24
|
+
*
|
|
25
|
+
* Арифметики, вызовов, присваивания. Ограничение не про аскетизм: специфичность считается
|
|
26
|
+
* обходом дерева, и как только в условии появится вычисление, она перестанет быть
|
|
27
|
+
* определимой — а вместе с ней и правило «кто выигрывает».
|
|
28
|
+
*
|
|
29
|
+
* ## Отношение к платформе и DOM
|
|
30
|
+
*
|
|
31
|
+
* Никакого. Модуль лежит в `primitives` рядом с `normalizeKeybinding` и по той же причине:
|
|
32
|
+
* его зовут двое — реестр команд (проверка на регистрации) и диспетчер клавиш (вычисление
|
|
33
|
+
* на нажатии), а `primitives` не имеет права импортировать `ui`.
|
|
34
|
+
*
|
|
35
|
+
* @module shell/platform/primitives/when-expr
|
|
36
|
+
*/
|
|
37
|
+
/** Значение в правой части сравнения. Только литерал — см. {@link parseWhen}. */
|
|
38
|
+
export type WhenLiteral = string | number | boolean | null;
|
|
39
|
+
/**
|
|
40
|
+
* Узел разобранного условия.
|
|
41
|
+
*
|
|
42
|
+
* `negated` полем, а не обёрткой `not`, у сравнений — несущее решение: `a != b` и `!(a == b)`
|
|
43
|
+
* обязаны давать ОДНУ структуру. Разойдись они, два одинаковых по смыслу правила получили бы
|
|
44
|
+
* разную специфичность, по-разному участвовали бы в разрешении конфликта и выглядели бы
|
|
45
|
+
* разными в редакторе клавиш — при том, что человек написал одно и то же.
|
|
46
|
+
*/
|
|
47
|
+
export type WhenNode = {
|
|
48
|
+
readonly kind: 'true';
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: 'key';
|
|
51
|
+
readonly key: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly kind: 'not';
|
|
54
|
+
readonly operand: WhenNode;
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: 'and';
|
|
57
|
+
readonly operands: readonly WhenNode[];
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'or';
|
|
60
|
+
readonly operands: readonly WhenNode[];
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: 'eq';
|
|
63
|
+
readonly key: string;
|
|
64
|
+
readonly value: WhenLiteral;
|
|
65
|
+
readonly negated: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: 'match';
|
|
68
|
+
readonly key: string;
|
|
69
|
+
readonly pattern: string;
|
|
70
|
+
readonly flags: string;
|
|
71
|
+
readonly negated: boolean;
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: 'in';
|
|
74
|
+
readonly key: string;
|
|
75
|
+
readonly collection: string;
|
|
76
|
+
readonly negated: boolean;
|
|
77
|
+
};
|
|
78
|
+
/** Разобранное условие вместе со всем, что выводится из него один раз. */
|
|
79
|
+
export interface WhenExpr {
|
|
80
|
+
/** Как написано человеком. Показывается в редакторе клавиш и в диагностике. */
|
|
81
|
+
readonly source: string;
|
|
82
|
+
readonly ast: WhenNode;
|
|
83
|
+
/**
|
|
84
|
+
* Ключи, которые условие ЧИТАЕТ, отсортированные и без повторов. Включает правую часть
|
|
85
|
+
* `in`: по этому набору подписываются на изменения контекста и по нему же считается
|
|
86
|
+
* диагностика «условие ссылается на ключ, которого никто не объявил».
|
|
87
|
+
*/
|
|
88
|
+
readonly keys: readonly string[];
|
|
89
|
+
/** Насколько узко условие называет место. Считается при разборе — см. {@link whenSpecificity}. */
|
|
90
|
+
readonly specificity: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Отказ разбора. Не `CommandError` намеренно: тот живёт в `./command`, который импортирует
|
|
94
|
+
* этот модуль, и обратная ссылка замкнула бы цикл. Реестр команд ловит эту ошибку и
|
|
95
|
+
* заворачивает в свой `CommandError('invalid-when')` — там, где у отказа появляется
|
|
96
|
+
* идентификатор команды.
|
|
97
|
+
*/
|
|
98
|
+
export declare class WhenSyntaxError extends Error {
|
|
99
|
+
/** Смещение в исходной строке — редактор подсветит место, а не всю строку. */
|
|
100
|
+
readonly at: number;
|
|
101
|
+
readonly source: string;
|
|
102
|
+
constructor(message: string, source: string, at: number);
|
|
103
|
+
}
|
|
104
|
+
export interface WhenParseError {
|
|
105
|
+
readonly message: string;
|
|
106
|
+
readonly at: number;
|
|
107
|
+
readonly source: string;
|
|
108
|
+
}
|
|
109
|
+
export type WhenParseResult = {
|
|
110
|
+
readonly ok: true;
|
|
111
|
+
readonly expr: WhenExpr;
|
|
112
|
+
} | {
|
|
113
|
+
readonly ok: false;
|
|
114
|
+
readonly error: WhenParseError;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Насколько узко условие называет место. Больше — уже, значит выигрывает при равном слое.
|
|
118
|
+
*
|
|
119
|
+
* Правила и их обоснование:
|
|
120
|
+
*
|
|
121
|
+
* - **дизъюнкция берёт МИНИМУМ ветвей.** Условие, истинное в объединении состояний,
|
|
122
|
+
* ограничивает ровно настолько, насколько его слабейшая ветвь. Возьми мы максимум —
|
|
123
|
+
* условие с приставкой «или всегда» встало бы ВЫШЕ исходного, совпадая при этом со строго
|
|
124
|
+
* большим числом состояний, и приписка стала бы способом перебить кого угодно. Правило,
|
|
125
|
+
* срабатывающее чаще, не имеет права выигрывать у срабатывающего реже.
|
|
126
|
+
* - **конъюнкция — сумма:** каждый конъюнкт сужает.
|
|
127
|
+
* - **отрицание — тождество:** отрицание ограничивает дополнением, то есть той же силы.
|
|
128
|
+
* Обнули мы его — «работает, когда ничего не выделено» стало бы безусловным.
|
|
129
|
+
* - **сравнение дороже проверки на истинность на единицу:** сравнение выбирает одно значение
|
|
130
|
+
* из открытого множества, а голый ключ делит мир пополам.
|
|
131
|
+
*/
|
|
132
|
+
export declare function whenSpecificity(ast: WhenNode): number;
|
|
133
|
+
/** Все читаемые ключи, отсортированные и без повторов. */
|
|
134
|
+
export declare function whenKeys(ast: WhenNode): readonly string[];
|
|
135
|
+
/**
|
|
136
|
+
* Вычисляет условие по читателю ключей.
|
|
137
|
+
*
|
|
138
|
+
* Читатель, а не снимок `WhenContext`: областей и ключей плагинов в контексте из пяти полей
|
|
139
|
+
* нет, и тип с пятью полями заставил бы их туда положить — то есть расширять контракт на
|
|
140
|
+
* каждый ключ, который завёл чужой плагин.
|
|
141
|
+
*
|
|
142
|
+
* **Неизвестный ключ — ложь, а не отказ.** Условие вправе ссылаться на ключ выключенного
|
|
143
|
+
* плагина: это нормальное состояние приложения, а не поломка. Правило при этом просто
|
|
144
|
+
* перестаёт совпадать, и клавиша достаётся следующему кандидату.
|
|
145
|
+
*/
|
|
146
|
+
export declare function evaluateWhen(expr: WhenExpr, read: (key: string) => unknown): boolean;
|
|
147
|
+
/** Условие «всегда». Пустая строка и отсутствие условия дают именно его. */
|
|
148
|
+
export declare const WHEN_TRUE: WhenExpr;
|
|
149
|
+
/**
|
|
150
|
+
* Разбирает условие, бросая {@link WhenSyntaxError}. Для кода: объявление команды проверяется
|
|
151
|
+
* НА РЕГИСТРАЦИИ — по тому же доводу, что и сочетание клавиш (см. `./command`), — иначе
|
|
152
|
+
* опечатка живёт до того дня, когда кто-то попробует нажать клавишу.
|
|
153
|
+
*/
|
|
154
|
+
export declare function compileWhen(source: string): WhenExpr;
|
|
155
|
+
/**
|
|
156
|
+
* Разбирает условие, НЕ бросая. Для всего, что человек правит руками: манифест плагина,
|
|
157
|
+
* раскладка пользователя. Испорченная запись в файле — обычное состояние, а не авария,
|
|
158
|
+
* и ронять на ней загрузку нельзя.
|
|
159
|
+
*/
|
|
160
|
+
export declare function parseWhen(source: string): WhenParseResult;
|
|
161
|
+
/**
|
|
162
|
+
* Доказуемо ли, что два условия НЕ МОГУТ быть истинны одновременно.
|
|
163
|
+
*
|
|
164
|
+
* Осознанно неполна и доказывает ровно один случай: сравнение ОДНОГО ключа с РАЗНЫМИ
|
|
165
|
+
* литералами (фокус в дереве против фокуса на канвасе). Всё остальное считается
|
|
166
|
+
* пересекающимся.
|
|
167
|
+
*
|
|
168
|
+
* Полная проверка непересекаемости для произвольной булевой формулы над открытым множеством
|
|
169
|
+
* значений в общем виде неразрешима, а приближать её эвристиками здесь нельзя: цена ошибки
|
|
170
|
+
* несимметрична. Не доказали непересекаемость там, где она есть, — человек увидит лишнее
|
|
171
|
+
* предупреждение в редакторе клавиш. «Доказали» там, где её нет, — конфликт молча пропущен,
|
|
172
|
+
* и одна из двух клавиш не работает без единого следа.
|
|
173
|
+
*
|
|
174
|
+
* Ровно этот один случай закрывает то, ради чего функция и заводится: пара `delete` в дереве
|
|
175
|
+
* против `delete` на канвасе.
|
|
176
|
+
*/
|
|
177
|
+
export declare function provablyDisjoint(a: WhenExpr, b: WhenExpr): boolean;
|