API плагинов PejavaCommander
Здесь описано, как устроен PejavaCommander, и приведён полный API для плагинов. Пошаговые примеры собраны в Cookbook.
- 1. Архитектура
- 2. Устройство плагина
- 3. Манифест (
plugin.json) - 4. API UI-части (
pc) - 5. API Node-части
- 6. Панели: провайдеры, поля, режимы
- 7. Клавиши и условия
when - 8. Цвета и палитры
- 9. Файлы на диске
- 10. Разработка
- 11. Встроенные плагины и команды
1. Архитектура
┌──────────────────────────── Main-процесс (Node) ─────────────────────────────┐
│ PluginHost SettingsService ThemeService KeybindingService PtyService │
│ (поиск, (settings.json) (палитры) (keybindings.json) (shell) │
│ Node-части) │
└──────────────▲──────────────────────────────▲─────────────────────────────────┘
│ IPC (preload: window.pcBridge)│
┌──────────────┴──── Главное окно (UI) ────────┴────────┐ ┌── Окно настроек ────┐
│ Workbench: команды, хоткеи, контекстные ключи, меню, │ │ General / Colors / │
│ тема, диалоги, реестры (провайдеры/поля/режимы) │ │ Keyboard │
│ Слой 1: TerminalLayer (xterm.js + shell) │ └─────────────────────┘
│ Слой 2: PanelsLayer (1–2 панели + строка F-клавиш) │
│ PluginLoader → activate(pc, context) UI-частей │
└────────────────────────────────────────────────────────┘
Два слоя. Слой 1 — ваш shell ($SHELL) в настоящем PTY. Его отрисовывает xterm.js с полной поддержкой цветов. Слой 2 — панели. Они закрывают всё окно, кроме последней строки: там видно приглашение shell. Ctrl+O показывает и скрывает панели.
Рабочую директорию задаёт shell. Интеграция с shell сообщает текущую директорию при каждом приглашении, и активная файловая панель переходит туда же. Когда пользователь переходит в другую директорию в панели, приложение отправляет в shell cd, но только если shell стоит на приглашении.
Всё сделано плагинами. Файловые панели, режимы отображения, файловые операции, палитры, менеджер плагинов и даже команды приложения — встроенные плагины из src/plugins/. Ваши плагины пользуются тем же API. Пользовательский плагин с тем же id заменяет встроенный.
Декларации и код. Статичные вещи плагин объявляет в plugin.json: команды, клавиши, меню, цвета, палитры, настройки, режимы. Динамичные регистрирует в коде: обработчики команд, провайдеры панелей, поля.
Без перезапуска. Включение, выключение, установка и удаление плагина применяются сразу. Всё, что плагин зарегистрировал через API, снимается автоматически.
Безопасность. Как и расширения VS Code, плагины считаются доверенным кодом: у Node-части полный доступ к Node.js. Ставьте плагины только из источников, которым доверяете.
2. Устройство плагина
my-plugin/
plugin.json манифест (обязателен)
renderer.js UI-часть, ES-модуль (необязательно)
main.js Node-часть, CommonJS (необязательно)
styles.css дополнительный CSS (перечисляется в "styles")
palettes/… файлы палитр
UI-часть (renderer.js) работает в главном окне:
export function activate(pc, context) {
pc.commands.register('my.hello', () => pc.ui.showMessage('Привет!'));
}
export function deactivate() {} // необязательно: регистрации снимаются автоматически
Node-часть (main.js) работает в main-процессе Electron:
exports.activate = (context) => {
context.rpc.handle('sum', (a, b) => a + b); // вызов из UI: pc.rpc.call('sum', 1, 2)
};
exports.deactivate = () => {};
Жизненный цикл
- При запуске приложение ищет плагины в папке встроенных, в пользовательской папке и в папках
--plugin-dev. - Активирует Node-части включённых плагинов.
- Главное окно применяет декларативные вклады плагина, затем импортирует модуль
rendererи вызываетactivate(pc, context). Встроенные плагины идут первыми. - При отключении или удалении приложение вызывает
deactivate()и снимает всё, что плагин зарегистрировал.
3. Манифест (plugin.json)
| Поле | Тип | Описание |
|---|---|---|
id |
string, обязательно | Уникальный id, [a-z0-9][a-z0-9._-]*. Используйте префикс: yourname.feature. |
name |
string, обязательно | Отображаемое имя. |
version |
string, обязательно | Semver, например 1.2.0. |
description |
string | Показывается в менеджере плагинов. |
author |
string | |
renderer |
string | Путь к UI-модулю (ES-модуль). |
main |
string | Путь к Node-модулю (CommonJS). |
styles |
string[] | CSS-файлы для главного окна. |
required |
boolean | Только для встроенных: плагин нельзя отключить. |
contributes |
object | Точки расширения, см. ниже. |
contributes.commands
{ "command": "my.cmd", "title": "Сделать что-то", "category": "My", "keybarTitle": "Делай", "enablement": "activePanelHasTarget", "description": "…" }
Задаёт заголовки для палитры команд, меню, строки F-клавиш и редактора клавиш. Обработчик регистрируется в коде через pc.commands.register. enablement — when-условие: пока оно ложно, клавиша команды ничего не делает, а подпись в строке F-клавиш приглушена (для случая «клавиша здесь есть, но сейчас не над чем работать»; если команда в этой панели вообще неуместна, используйте when привязки).
contributes.keybindings
{ "key": "ctrl+shift+d", "mac": "meta+shift+d", "command": "my.cmd", "when": "panelFocus", "args": ["x"], "keybarTitle": "Дубль" }
key— клавиша для всех платформ.mac,linuxиwinпереопределяют её для своей платформы.argsпередаётся обработчику команды (одно значение или массив).keybarTitle— подпись именно этой привязки в строке F-клавиш (важнее подписи команды).- Синтаксис клавиш и
whenописан в разделе 7.
contributes.menubar / contributes.menus
"menubar": [{ "id": "tools", "label": "Tools", "order": 45 }],
"menus": [
{ "menu": "tools", "command": "my.cmd", "group": "1", "order": 10 },
{ "menu": "tools", "command": "panels.setMode", "args": ["left", "columns3"], "label": "Columns 3", "checked": "leftPanelMode == 'columns3'" },
{ "menu": "tools", "submenu": "tools.more", "label": "Ещё" },
{ "menu": "tools.more", "command": "my.other", "when": "activePanelProvider == 'fs'" }
]
Встроенные верхние меню: left (10), file (20), command (30), options (40), right (50).
Пункты сортируются по group, затем по order, между группами ставится разделитель. when скрывает пункт, checked ставит галочку; оба — выражения над контекстными ключами. Сочетание клавиш рядом с пунктом берётся из хоткеев.
contributes.colors
{ "id": "myplugin.badge.foreground", "default": "#ffcc00", "description": "Текст бейджа" }
Превращается в CSS-переменную --myplugin-badge-foreground и редактируется в Settings → Colors.
contributes.palettes
{ "id": "my-theme", "label": "My Theme", "path": "palettes/my-theme.json" }
Формат файла палитры описан в разделе 8.
contributes.settings
{ "key": "myplugin.limit", "type": "number", "default": 100, "description": "Максимум элементов", "enum": null, "hidden": false }
type: boolean | number | string. С enum: [...] настройка показывается выпадающим списком. Настройки появляются в Settings → General.
contributes.panelModes
{ "id": "wide", "title": "Wide", "columns": 2, "order": 50, "providers": ["fs"],
"fields": [{ "field": "name" }, { "field": "size", "width": 8, "align": "right", "title": "Байт" }] }
"details" решает, показывают ли панели в этом режиме блок details: true (по умолчанию), false или ключ булевой настройки (тогда режим следует этой настройке, и команда или hotkey, переключающие её, показывают и скрывают блок). Подробнее — в разделе 6.
4. API UI-части (pc)
activate(pc, context) получает:
context: { id, manifest, baseUrl, asUrl(relPath), subscriptions: { push(...disposables) }, state }
context.state.get(key, fallback)иcontext.state.update(key, value)— постоянное хранилище плагина (асинхронное).
Каждая функция register…/on… возвращает disposable ({ dispose() }). Освобождать их вручную не нужно: они снимаются при остановке плагина.
pc.commands
register(id, handler, meta?) |
Регистрирует обработчик. meta: { title, category, keybarTitle } для команд, которых нет в манифесте. |
execute(id, ...args) |
Выполняет команду и возвращает Promise с её результатом. |
list() |
[{ id, title, category, keybarTitle, available }] |
getKeys(id, args?) |
Клавиши, привязанные к команде, например ['f5']; с args — только привязки с такими аргументами (getKeys('panels.setMode', ['column'])). |
pc.keybindings
register({ key, command, when?, args?, mac?, keybarTitle? }) |
Добавляет привязку из кода. |
format(key) |
Форма для показа: 'shift+meta+p' → ⇧⌘P на macOS. |
pc.context
set(key, value) и get(key) читают и пишут контекстные ключи для условий when. Можно заводить свои ключи, например myplugin.busy.
pc.menus
registerMenu({ id, label, order }) и registerItem({ menu, command, … }) — в тех же форматах, что и в манифесте.
pc.panels
registerProvider(id, provider) |
Источник панели (§6). |
registerField(id, field) |
Поле колонки (§6). |
registerMode(mode) |
Режим отображения (формат как в манифесте). |
registerView(id, view) |
Вид панели: свой способ отрисовки (§6). |
getField(id) |
Описание зарегистрированного поля. |
providers, fields, modes, views |
Что зарегистрировано. |
active, passive, left, right, getPanel(side) |
Дескрипторы панелей (ниже). |
activeSide |
'left' или 'right'. |
visible, isDual |
Виден ли слой? Показаны ли две панели? |
show(), hide(), toggle() |
Показать или скрыть слой панелей. |
activate(side), switch(), swap() |
Сменить активную панель или поменять панели местами. |
resizable, toggleResizable() |
Режим изменения ширины: разделитель можно тянуть; более узкая панель сохраняет ширину в пикселях (50/50, пока окно для неё слишком узкое). Переключение сбрасывает на 50/50 и возвращает новый режим. |
togglePanel(side), setDual(bool) |
Одна панель или две. |
onDidChangeLocation(fn) |
fn({ panel, location }) для любой панели. |
onDidChangeProviders(fn), onDidChangeModes(fn) |
Изменения реестров. |
Дескриптор панели
| Свойство | |
|---|---|
side, isActive |
|
providerId, location |
Что показано и где. |
modeId, mode (нормализованный объект режима), availableModes, defaultModeId, sort ({ field, reverse }) |
|
title, error |
Заголовок панели, последняя ошибка чтения. |
itemsVersion |
Растёт при каждой перезагрузке или пересортировке элементов (виды по нему решают, когда перестроиться). |
locationInfo |
Объект info, который провайдер вернул вместе со списком (fs: { dev }). |
items, cursorItem, cursorIndex |
|
selectedItems |
Отмеченные элементы. |
targetItems |
С чем работают операции: выбор вида (см. хуки видов), иначе отмеченные элементы, иначе элемент под курсором. |
currentItem |
С чем работают F3/F4: выбор вида, иначе элемент под курсором. |
view |
{ id, sortable, itemOps } текущего вида. |
rowsPerColumn, pageSize |
Текущая геометрия. |
| Метод | |
|---|---|
navigate(location, { focus? }) |
Тот же провайдер, новое место. focus — имя элемента, на который встанет курсор. |
setProvider(id, location?) |
Сменить источник. back() возвращает к предыдущему. |
refresh(), openItem(item?), goParent() |
|
setMode(id), resetMode(), setSort(field, reverse?) |
Режим, не подходящий провайдеру, заменяется режимом по умолчанию. |
setCursor(i), moveCursor(delta), setCursorByName(name) |
|
moveDirection(dir) |
'up' 'down' 'left' 'right' 'pageUp' 'pageDown'; шаг определяет вид. |
click(index, { shiftKey, metaKey, ctrlKey }) |
Поведение мыши: клик — курсор, Cmd/Ctrl — переключить отметку, Shift — отметить диапазон. |
isSelected(item) |
|
setSelection(names, { cursorName? }) |
Заменить выделение (так работает выделение рамкой). |
toggleSelect(item?, value?), select(names, value), selectWhere(pred, value), invertSelection(), clearSelection() |
|
expand(item), collapse(item), toggleExpand(item), isExpanded(item) |
Раскрытие папок на месте в виде с tree: true. Их элементы идут в items сразу после папки, с именами "<папка>/<имя>", полями depth и treeParent; место папки — её item.path. При перечитывании того же места папки остаются раскрытыми. |
onDidChangeLocation(fn), onDidChange(fn) |
pc.terminal
cwd, onDidChangeCwd(fn) |
Рабочая директория shell. |
onDidPrompt(fn) |
Срабатывает на каждом новом приглашении (команда завершилась). |
cd(dir) |
cd в shell. Возвращает { ok, reason? }; reason: 'busy' значит, что запущена программа. |
run(command, { showOutput = true }) |
Выполнить командную строку. Панели скрываются до следующего приглашения. |
sendText(text) |
Напечатать в командную строку (без Enter). '\r' выполняет строку. |
clearCommandLine() |
|
write(data) |
Сырые байты в PTY. |
quote(str) |
Безопасное экранирование для shell. |
focus(), isIdle() |
pc.fs
Асинхронные: list(dir) → { path, parent, entries[] }; stat(path); isDirectory(path); roots() → [{ label, path, kind: 'home' \| 'root' \| 'volume', total?, free? }] (размеры — если том ответил за 400 мс); mkdir(path); trash(paths[]); rename(from, to); openPath(path) (приложение по умолчанию); reveal(path).
dirSize(paths) → { bytes, files, dirs, errors } (рекурсивно, ссылки не раскрываются); readText(path, maxBytes = 256K) → { text, truncated, binary, size }; thumbnail(path, size, mtime?) → data URL или null (системные миниатюры: картинки, видео, PDF на macOS; с кэшем и очередью); cachedThumbnail(path, size, mtime?) (синхронно, undefined, если ещё не загружено).
Синхронные: fileUrl(path) → URL pc://local/… для <img>/<video>/<audio> (с перемоткой); info() → { home, sep, platform, uid, user }; работа с путями — join, dirname, basename, extname, normalize, resolve(base, input).
Элемент: { name, path, isDir, isLink, linkBroken?, linkTarget?, size, mtime, ctime, birthtime, atime, mode, uid, gid, isExecutable, error? }.
pc.ui
showQuickPick(items, { title, placeholder, activeIndex, panel?, anchor? }) |
Элементы — строки или { label, description?, detail?, value? }; { separator: true, label } начинает группу с заголовком; фильтр ищет и по заголовку группы и оставляет его над найденными. Возвращает выбранный элемент или undefined. |
showForm({ title, fields, submitLabel, validate, onChange, actions }) |
Диалог с несколькими полями. Поле: { id, label, type: 'text' | 'password' | 'number' | 'select' | 'checkbox' | 'separator', value, placeholder, hint, options: [{ label, value }], visible(values), autofocus, button: { label, run(values) } }. validate(values) возвращает текст ошибки; onChange(values, id) может вернуть { id: value }, чтобы поменять другие поля; actions: [{ label, run(values, { setMessage }) }] — дополнительные кнопки, не закрывающие диалог (например, «Проверить»). Возвращает { id: value } или undefined. |
showInputBox({ title, prompt, value, placeholder, validate, password }) |
validate(v) возвращает текст ошибки или ничего; password: true скрывает ввод. Возвращает строку или undefined. |
confirm(message, { title, buttons = ['Yes','No'], danger }) |
Возвращает подпись нажатой кнопки или undefined. |
| размещение | Пока панели показаны, диалоги открываются по центру активной панели. panel: 'left' | 'right' | дескриптор — по центру указанной панели, anchor: 'window' — по центру всего окна (для общих окон приложения). Работает для всех трёх видов диалогов. |
showMessage(text, level = 'info') |
Уведомление: 'info', 'warning' или 'error'. |
fileKind(item), fileExt(name), hasThumbnail(item), canShowInBrowser(name) |
Типы файлов: folder, image, video, audio, archive, pdf, document, source, text, executable, other; canShowInBrowser → 'image' | 'video' | 'audio' | null. |
fileIconSvg(item), renderFileIcon(host, item) |
Иконки форматов (с учётом item.icon). |
showOpenDialog(options), showSaveDialog(options) |
Системные диалоги (опции Electron). |
pc.settings
get(key, fallback), update(key, value) (null сбрасывает значение), onDidChange(fn(keys[])).
pc.theme
getColor(id), palette ({ id, name, type }), cssVar(id), listPalettes(), setPalette(id), onDidChange(fn).
pc.plugins
list(), errors(), setEnabled(id, bool), installFromFolder(dir?), installFromZip(file?), installFromUrl(url), uninstall(id), onDidChange(fn). Функции установки без пути открывают диалог выбора файла.
pc.rpc
call(method, ...args) вызывает Node-часть этого плагина. on(event, fn) принимает события, которые она отправляет.
pc.app
openSettings(tab?) ('general' | 'colors' | 'keys'), reload(), quit(), info(), toggleDevTools().
pc.tabs
Каждый таб окна — отдельная страница со своим терминалом, панелями и экземплярами плагинов (Node-части плагинов общие). Вызовы без id относятся к табу самого плагина: new(), newWindow(), close(id?) (спрашивает, если идёт программа), next(), previous(), selectIndex(i) (8 и больше — последний), rename(name, id?) (пустое имя — автоматический заголовок), info() → { id, title, customName, count }, moveToNewWindow(id?). Автоматический заголовок — document.title страницы: папка шелла или «папка — программа», пока идёт команда (заголовок, выставленный самой программой, важнее). Команды: tabs.new ⌘T, tabs.newWindow ⌘N, tabs.close ⌘W, tabs.next ⌘⇧] / ⌃Tab, tabs.previous ⌘⇧[ / ⌃⇧Tab, tabs.select ⌘1…⌘9, tabs.rename, tabs.moveToNewWindow. Цвета полосы табов: tabs.*.
5. API Node-части
exports.activate = (context) => { … };
context. |
|
|---|---|
id, path, manifest |
id плагина, абсолютный путь к папке, разобранный манифест. |
storagePath |
Папка для ваших данных (<userData>/plugin-data/<id>); создайте её сами. |
rpc.handle(method, fn) |
Открывает fn для pc.rpc.call(method, …). Может быть async; результат должен сериализоваться. Ошибки приходят вызывающей стороне как отклонённый Promise. |
rpc.emit(event, payload) |
Отправляет событие в UI-часть (pc.rpc.on(event, fn)). |
subscriptions |
Сюда кладут объекты { dispose() }, их освободят при деактивации. |
log(...args) |
Вывод в консоль с id плагина. |
6. Панели: провайдеры, поля, режимы
Панель показывает элементы от провайдера в некотором месте (location) и раскладывает их по режиму. Режим выбирает вид (по умолчанию list), а колонки списка показывают поля.
Провайдер (назначение панели)
pc.panels.registerProvider('my', {
title: 'My Source', // для выбора источника и заголовок по умолчанию
description: 'видно в списке Alt+F1',
hidden: false, // true — не показывать в списке источников
defaultMode: 'my.mode',
syncWithTerminal: false, // true только для локальных директорий (провайдер fs)
sortItems: true,
modesOf: 'fs', // необязательно: брать и режимы этого провайдера (элементы похожи на файлы) // false — оставить порядок из list()
getDefaultLocation(ctx) { return 'root'; },
// Необязательно: пункты для окна Select Source (Alt+F1/F2) под своими заголовками групп.
// Пункт открывает `location` в этом провайдере (или в `providerId`) либо выполняет
// `command` с `args` (по умолчанию — сторона панели). Отвечайте быстро: через 1,5 с окно откроется без них.
async sources(ctx) { return [{ separator: true, label: 'Моё' }, { label: 'Корень', description: '/', location: 'root', current: false }]; },
// Необязательно: кликабельные части пути в шапке панели. Без этого показан
// весь заголовок, а клик по нему открывает выбор источника.
breadcrumbs(location, ctx) { return [{ label: 'root', location: 'root' }, { label: 'sub', location: 'root/sub' }]; },
async list(location, ctx) { // обязательно
return {
location, // нормализованное место (необязательно)
title: `My: ${location}`, // заголовок панели (необязательно)
items: [
{ name: '..', isParent: true, isDir: true },
{ name: 'a', label: 'Item A', isDir: false, size: 10, color: 'panel.executable.foreground' },
],
};
},
async open(item, ctx) { // Enter / двойной клик
if (item.isDir) return { location: item.name, focus: undefined }; // перейти
// либо сделать что угодно и вернуть undefined
},
parentLocation(location, ctx) { return location === 'root' ? null : 'root'; }, // null → panel.back()
childName(location) { return location; }, // куда встанет курсор после подъёма
statusText(item, ctx) { return item.name; },
// Необязательно: строки блока details — [label, value, { wide }?]
details(item, ctx) { return [['Path', item.path, { wide: true }], ['Size', '10 B']]; },
// Необязательно: дополнительные строки, пока что-то отмечено (после строки «Selected»).
selectionDetails(items, ctx) { return [['Size', 'show ⌘I', { command: 'my.calc', action: 'show ⌘I' }]]; },
// Необязательно: перетаскивание
canDrag(item) { return true; }, // элементы можно перетаскивать из панели
dropEffect(drop, ctx) { return 'copy'; }, // 'copy' | 'move' | 'none'
async acceptDrop(drop, ctx) { /* drop.paths → drop.target.location */ },
});
Файловые операции в других провайдерах. С fileOps: true клавиши F3/F5/F6/F7/F8 (core.fileops) работают и здесь, через хуки провайдера: exportItems(items, ctx) → { paths, cleanup } (локальные копии для чтения), importPaths(paths, location, ctx) (положить файлы внутрь), removeItems(items, ctx) (удалить насовсем, без Корзины), makeDirectory(location, name, ctx), readOnly(location) → false или причина (тогда F6/F7/F8 приглушены), hostPath(location) (где лежит, место по умолчанию для F5 наружу). Режим с "providers": ["fs"] предлагается и провайдерам с modesOf: 'fs', если в нём нет "strictProviders": true (так помечен Visual: он сканирует настоящие папки). Часть пути в breadcrumbs с полем provider переключает панель на этот провайдер.
Открыватели. pc.panels.registerOpener(id, { match(item, ctx), open(item, panel) }): Enter на файле в файловой панели сначала спрашивает открыватели, и только потом запускает файл или открывает его приложением. PejavaArchive так открывает архивы как папки. pc.panels.findOpener(item, ctx) возвращает первый подходящий.
Превьюеры. pc.panels.registerPreviewer(id, { match(item, ctx), async create(host, item, ctx) }): вид Preview (Ctrl+8, Quick View Ctrl+Q) сначала спрашивает превьюеры, а уже потом показывает элемент по-своему. ctx — { panel, source, providerId, addMeta(label, value), isStale() } (source — панель, чей элемент показан). create заполняет host и возвращает экземпляр или null, если элемент всё-таки не его (тогда Preview покажет его как обычно). Хуки экземпляра, все необязательные: dispose(); focus() / hasFocus() (активная панель Preview передаёт ему фокус); accepts(item, providerId) + update(item) (тот же файл изменился: оставить экземпляр, а не создавать новый). PejavaCodeEditor — превьюер.
Правка файла из другого плагина. await pc.commands.execute('codeEditor.open', localPath, { title, afterSave, onClose }) открывает локальный файл в редакторе кода на всё окно и возвращает false, если это не текст (бинарный, слишком большой). afterSave() вызывается после каждого сохранения; если он бросает ошибку, текст остаётся несохранённым (ошибка с cancelled: true не показывается). Так PejavaRemote правит файлы на сервере: скачанная копия, afterSave загружает её обратно, onClose удаляет.
Элементы, забирающие клавиши. Пока фокус внутри элемента с атрибутом data-pc-own-keys (редактор кода), клавиши панелей не срабатывают и набор не уходит в командную строку; клавиши без when (⌘T, ⌘Q, ⌘⇧P…) работают. Контекстный ключ editorFocus тогда истинен, а panelFocus ложен.
PejavaArchive (pejava.archive) — полный пример всего этого: провайдер archive, место "<путь к архиву>::<папка внутри>". Читает всё, что знает bsdtar (libarchive): zip и родственные (jar, apk, epub, whl…), tar с gz/bz2/xz/zst/lzma, 7z, rar, iso, cab, xar/pkg, cpio, ar/deb, rpm, а также одиночные .gz/.bz2/.xz/.zst. zip меняется на месте через Info-ZIP zip, tar.* и 7z пересобираются bsdtar, остальные форматы и архивы внутри архивов — только для чтения. Зашифрованные архивы спрашивают пароль.
PejavaRemote (pejava.remote) добавляет сетевые подключения: провайдер remote (список подключений) и remote.files (место "<id подключения>::<путь на сервере>"), а также пункты sources() в окне выбора источника. SFTP (с учётом ~/.ssh/config: HostName, User, Port, IdentityFile, ProxyJump, ProxyCommand; ключи хостов сверяются с ~/.ssh/known_hosts), FTP, FTPS (явный и неявный TLS) и WebDAV (Basic и Digest) открываются прямо в панели, работают F3–F8 и Shift+F6; F4 правит копию и закачивает её обратно, когда редактор закрыт. Ресурсы SMB, AFP и NFS монтирует система (macOS mount volume, Linux GVfs gio mount), и они открываются в обычной файловой панели. Сохранённые подключения лежат в ~/.pejava/connections.json (права 0600; пароли шифруются через Electron safeStorage, пока включена настройка remote.encryptPasswords и доступна связка ключей). Также предлагаются хосты из ~/.ssh/config, менеджера сайтов FileZilla, ~/.netrc и Bonjour/mDNS в локальной сети.
Перетаскивание. Виды помечают элементы через data-index и draggable, остальное делает панель. Перетаскиваются отмеченные элементы или тот, который тянут. drop — это { source: { side, providerId, location, items } | null, external, target: { location, item, info }, modifiers: { alt, meta, ctrl, shift }, effect, paths }. external означает, что файлы пришли из другого приложения (например, Finder). Без acceptDrop панель не принимает перетаскивание. Провайдер fs работает по правилам Finder: тот же диск — перенос, другой диск — копирование, Option — копирование, Cmd — перенос. Если под указателем папка, файлы попадут в неё, иначе — в текущее место панели. Провайдер вызывает команды fileops.moveTo / fileops.copyTo ((paths, destDir), спрашивают только о конфликтах).
Блок details. Под каждой панелью во всех режимах, если режим не указал "details": false (см. contributes.panelModes) и вид его не скрыл, — информация о текущем элементе в две колонки из provider.details(item) и маленькое превью справа (настройка panels.detailsPreview, Ctrl+Alt+I). Если что-то отмечено, первой идёт строка «Selected». Без details() блок показывает имя и statusText(). Когда блок виден, он заменяет строку статуса. Кнопка [▼] на его верхней рамке (F9, Ctrl+I, команда panels.toggleDetailsCompact) делает блок компактным: только строка Path; [▲] возвращает полный вид. Каждая панель помнит свой выбор. Значение Path (и любая строка с { copy: true }) по клику копируется в буфер. Строка с { command, args?, action? } — кнопка: клик по значению (или только по его части action) запускает команду. fs так показывает Size: show ⌘I: подсчёт размера папки (или всего отмеченного; команда fs.calcSize, ⌘I / Ctrl+Alt+L), результат заменяет и <DIR> в колонке Size. Изменив данные на месте, вызовите redraw() у панели. У панели: hasDetails, detailsCompact, setDetailsCompact(bool); контекстные ключи activePanelHasDetails, activePanelDetailsCompact.
ctx — это { panel, api, settings }, где panel — дескриптор запрашивающей панели.
Поля элемента
name |
Уникален внутри списка, по нему работает выделение. |
label |
Поле name показывает его вместо name. |
title |
Человеческое имя для видов, отличных от списка (подписи иконок, превью). |
icon |
SVG-разметка или URL картинки для вида «Иконки» и превью. |
description |
Показывается как «Info» в превью для элементов, которые не являются файлами. |
isDir, isParent |
|
color |
id цвета для всей строки. |
classes |
Дополнительные CSS-классы строки. |
selectable: false |
Элемент нельзя отметить. |
| любые другие | Доступны вашим полям. |
Поле (содержимое колонки)
pc.panels.registerField('age', {
title: 'Age', width: 5, align: 'right', // ширина в символах или '*' = всё оставшееся место
render(item, ctx) { return '3d'; }, // текст ячейки
sortValue(item) { return item.mtime; }, // позволяет сортировать по полю
className(item) { return 'is-old'; }, // CSS-класс ячейки (необязательно)
format(bytes) { … }, // поле 'size' использует его для суммы выделения
});
Встроенные поля: name (core.panels); size, mtime, perms, owner, ext (core.filesystem); plugin.* (менеджер плагинов).
Режим (вид панели)
columns — сколько перетекающих колонок заполняют элементы: сверху вниз, затем следующая колонка, как в режиме Columns 3. fields — ячейки в каждой такой колонке.
| Режим | columns | fields |
|---|---|---|
column |
1 | name; метаданные: short — size, mtime; long — ещё perms, owner |
columns2 |
2 | name; метаданные: short — size; long — ещё mtime |
columns3 |
3 | name; метаданные как у columns2 |
columns |
"auto" |
name; столько колонок шириной panels.columnWidth символов (по умолчанию 30), сколько влезает |
visual |
вид treemap |
treemap занятого места (плагин visual.disk-usage) |
outline |
вид outline |
список как в Finder: папки раскрываются на месте (клик по шеврону или →/←), вложенные строки с отступом, остальные колонки выровнены |
icons |
вид icons |
сетка как в Finder, миниатюры картинок/видео/PDF |
preview |
вид preview |
показывает текущий элемент другой панели (Quick View, Ctrl+Q) |
Свойства режима: id, title, order, columns (число или "auto"), columnWidth (символы или ключ числовой настройки), fields, meta (уровень метаданных по умолчанию: "off", "short" (по умолчанию) или "long"), header (по умолчанию true), providers, view (по умолчанию "list"), options (произвольные параметры для вида).
Метаданные. Элемент fields с "meta": "short" или "long" необязательный: панель показывает его на этом уровне и выше (None → Short → Long). Уровень запоминается для каждой панели и каждого режима; пользователь выбирает его в окне Listing Mode, в меню Left/Right или клавишей Ctrl+4 (panels.cycleMeta, panels.setMeta(level)). У поля метаданных могут быть свои providers (column показывает размеры и даты только для fs и провайдеров с modesOf: 'fs'). У панели: metaLevel (null, если у режима нет метаданных), metaFields, setMeta(level), cycleMeta(). Старые id режимов работают: brief → columns3 без метаданных, medium → columns2, full → column short, long → column long, full2 → columns2 short.
Элемент fields может переопределить width, align и title. providers: [...] ограничивает режим отдельными провайдерами. Если запросить неподходящий режим (например, Ctrl+5 (Outline) в менеджере плагинов), панель покажет режим провайдера по умолчанию; Ctrl+0 сбрасывает режим к нему. Режим запоминается для каждой панели и каждого провайдера.
Вид (как рисуется панель)
pc.panels.registerView('my.view', {
title: 'My view',
create(container, ctx) {
// ctx: { panel, api, settings, mode, fieldDef(id), metrics: { rowHeight(), charWidth() } }
const root = document.createElement('div');
container.append(root);
return {
render() { /* рисуем ctx.panel.items, ctx.panel.cursorIndex, ctx.panel.isSelected(item) */ },
step(direction) { return { up: -1, down: 1 }[direction] ?? 0; }, // сдвиг курсора стрелками
pageSize: 20, // необязательно
statusText() { return '…'; }, // необязательно: заменяет строку статуса
dispose() { root.remove(); },
};
},
});
render() вызывается после каждого изменения: курсор, выделение, элементы, размер, активность. При смене режима панели экземпляр вида создаётся заново.
Описание вида с tree: true разрешает панели раскрывать папки на месте (expand/collapse у панели); таков вид outline.
Хуки блока details: в описании вида details: false скрывает блок, detailsPreview: false скрывает его мини-превью. У экземпляра: details / detailsPreview (то же, но динамически); detailsTarget() → { item, providerId, panel, thumbnail? }, чтобы описывать другой элемент (Preview описывает элемент соседней панели; thumbnail включает или отключает системную миниатюру); detailsExtra() → дополнительные строки (показываются и без элемента). Когда они меняются, вид вызывает ctx.refreshDetails().
Клавиши следуют виду: в описании вида sortable: false скрывает клавиши сортировки (activePanelSortable), а itemOps: false — F3–F8 (activePanelItemOps), так что в строке F-клавиш остаётся только то, что здесь работает. Вид, который выбирает элементы по-своему (кликнутый блок в карте диска), реализует currentItem() и targetItems(), возвращающие объекты { name, path, isDir, size }, и вызывает ctx.changed(), когда выбор меняется. refresh() вызывается, когда панель перечитывает ту же папку (после удаления, копирования, команды в шелле), — для видов со своими данными. Режим подключает вид через "view": "my.view". Встроенные виды: list (ядро), icons и preview (core.views). Плагин может заменить и list.
Для кликов используйте ctx.panel.click(index, mouseEvent), для двойного клика — ctx.api.commands.execute('panels.open'): так выделение везде работает одинаково.
7. Клавиши и условия when
Синтаксис клавиш: модификаторы+клавиша, в нижнем регистре. Модификаторы: ctrl, alt, shift и meta (клавиша Cmd на macOS; синонимы cmd, win). Клавиши:
- буквы
a–z, цифры0–9,f1–f24; enter,escape,tab,space,backspace,delete,insert,home,end,pageup,pagedown,up,down,left,right;numpad_add,numpad_subtract,numpad_multiply,numpad_divide,numpad0–numpad9;- знаки `- = [ ] \ ; ’ , . / ``.
Клавиши сопоставляются по физическому положению, поэтому ctrl+o работает и в русской раскладке.
Приоритет: пользовательские привязки важнее привязок плагинов. Среди привязок плагинов выигрывает та, у которой when конкретнее (больше условий через &&), а при равенстве — зарегистрированная позже. Пользовательская запись { "key": "f8", "command": "-fileops.delete" } удаляет привязку по умолчанию.
Синтаксис when: &&, ||, !, ==, !=, скобки, 'строки', числа, true/false и контекстные ключи.
Контекстные ключи
| Ключ | Значение |
|---|---|
panelsVisible, panelFocus, terminalFocus, inputFocus, dialogOpen |
Фокус и видимость |
terminalAltScreen |
Запущена полноэкранная программа (vim, less, htop) |
commandLineDirty |
В командной строке шелла есть текст (zsh сообщает сам, для других шеллов он читается с экрана после приглашения). Тогда ←/→/Enter/Backspace уходят в командную строку |
dualPanels, leftPanelVisible, rightPanelVisible |
Раскладка |
panelsResizable |
Включён режим изменения ширины панелей (panels.toggleResize, ⌘⇧D / Ctrl+Shift+D или перетаскивание разделителя) |
activePanel |
'left' / 'right' |
activePanelProvider, passivePanelProvider, leftPanelProvider, rightPanelProvider |
например 'fs', 'plugins' |
activePanelMode, passivePanelMode, leftPanelMode, rightPanelMode |
например 'column' |
activePanelMeta, leftPanelMeta, rightPanelMeta |
Уровень метаданных: 'off', 'short', 'long' или '', если у режима их нет |
activePanelView |
'list', 'icons', 'preview', … |
cursorIsDirectory, cursorIsParent, hasSelection |
Состояние курсора |
activePanelSortable, activePanelItemOps |
Что умеет активный вид (см. хуки видов) |
activePanelHasCurrent, activePanelHasTarget |
Есть элемент для F3/F4 / для копирования, переноса, удаления |
editorFocus |
Фокус в элементе, забирающем клавиши (data-pc-own-keys, например редактор кода) |
overlayOpen |
Что-то закрывает всё окно (редактор кода по F4); Ctrl+O и Ctrl+` не срабатывают |
isMac, platform |
|
fsShowHidden |
Задаёт core.filesystem |
8. Цвета и палитры
- Цвет — это id со значением по умолчанию (
contributes.colors). В CSS он доступен какvar(--id-через-дефисы). - Палитра — именованный набор значений цветов. Итоговая тема = значения по умолчанию, поверх которых наложена активная палитра.
- Встроенные палитры приходят из плагинов и доступны только для чтения: чтобы править, сначала сделайте копию (Duplicate). Пользовательские палитры лежат в
<userData>/palettes/*.json. - В Settings → Colors палитры можно активировать, копировать, переименовывать, удалять, экспортировать и импортировать, а каждый цвет — править с живым предпросмотром.
Файл палитры (формат экспорта; format и version при импорте необязательны):
{
"format": "pejava-palette",
"version": 1,
"name": "My Palette",
"type": "dark",
"colors": { "panel.background": "#0000a8", "panel.foreground": "#c0c0c0" }
}
Значения цветов: #rgb, #rgba, #rrggbb или #rrggbbaa.
9. Файлы на диске
<userData> — это ~/Library/Application Support/PejavaCommander на macOS и ~/.config/PejavaCommander на Linux.
| Файл | Содержимое |
|---|---|
settings.json |
Только изменённые настройки. |
keybindings.json |
Пользовательские хоткеи (формат VS Code, -command удаляет). |
palettes/*.json |
Пользовательские палитры. |
plugins/<id>/ |
Установленные плагины. |
plugins.json |
{ "disabled": [ids] } |
plugin-data/<id>/ |
Рекомендуемая папка данных для Node-частей. |
state.json |
Состояние UI (пути и режимы панелей, context.state плагинов). |
10. Разработка
PC=/Applications/PejavaCommander.app/Contents/MacOS/PejavaCommander # macOS; Windows: PejavaCommander.exe, Linux: AppImage
"$PC" --plugin-dev=/path/to/my-plugin # загрузить плагин с диска (можно несколько раз; подходит и папка с плагинами)
PC_PLUGIN_DEV=/a:/b "$PC" # то же через переменную окружения
PC_USER_DATA=/tmp/pc-test "$PC" # отдельный профиль
Cmd/Ctrl+Shift+Rперезагружает окно. Shell при этом продолжает работать, его вывод восстанавливается.Cmd+Alt+I/Ctrl+Shift+Iоткрывает DevTools. Объект workbench доступен какwindow.pcWorkbench.- Ошибки в
activateпоказываются уведомлением и в менеджере плагинов (Cmd/Ctrl+Shift+X). - Чтобы подхватить изменения Node-части, выключите и снова включите плагин или перезапустите приложение.
Упаковка: заархивируйте папку плагина в zip. plugin.json должен лежать в корне архива или в единственной папке верхнего уровня. Установка: менеджер плагинов → F5 → From .zip / From URL. Установка по URL — основа будущего сервера плагинов.
11. Встроенные плагины и команды
| Плагин | Что даёт |
|---|---|
core.workbench (обязательный) |
Базовые цвета и настройки, палитра команд, окно настроек, выход, связь панелей с командной строкой, основа меню |
core.panels (обязательный) |
Движение курсора, выделение, раскладка, сортировка, выбор источника, поле name, режимы column/columns2/columns3/columns |
core.filesystem |
Провайдер fs, поля size/mtime/perms/owner/ext, цвета типов файлов |
core.fileops |
F3 просмотр, F4 правка, F5 копирование, F6 перенос, F7 новая папка, F8 удаление (Node-часть для копирования и переноса) |
core.palettes |
Встроенные цветовые палитры: классическая синяя, тёмная, светлая, Solarized Dark и Solarized Light |
core.views |
Вид outline (список как в Finder, папки плавно раскрываются на месте), вид icons (плавный скролл, миниатюры, выделение рамкой) и вид preview, Quick View по Ctrl+Q |
core.plugin-manager |
Провайдер plugins (панель менеджера плагинов) |
visual.disk-usage |
Режим visual / вид treemap: сканирует папку в Node-части (параллельно, с прогрессом, Esc отменяет) и рисует «подушечный» treemap; переход внутрь использует уже собранные данные |
pejava.codeeditor |
Правка текста и кода в панели Preview через Monaco (редактор VS Code): превьюер; ⌘S / Ctrl+S сохраняет, Esc возвращает в другую панель; F4 — правка на весь экран, Shift+F4 — новый файл (команды codeEditor.edit, codeEditor.newFile); настройки codeEditor.* |
pejava.remote |
Сетевые подключения: провайдеры remote и remote.files, режим remote.list, ⌘K / Ctrl+Shift+K — подключиться по адресу, F4/F7/F8 — изменить/добавить/удалить подключение в панели Network; на сервере F4 открывает файл в редакторе кода (каждый ⌘S загружает его обратно, а если файл на сервере успел измениться — сначала спрашивает), Shift+F4 — новый файл |
Актуальный список всех команд и клавиш — в Settings → Keyboard или по F1 в панелях (по группам). Cmd/Ctrl+Shift+P открывает палитру команд.