Files
qode/DOCS.md
T
2026-10-30 23:11:51 +03:00

354 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lua API
Приложение выполняет `init.lua` при запуске и предоставляет сценариям объект `app`
для управления окном, вкладками, панелями, файловой системой, подсветкой и
LSP-серверами.
## Запуск и каталог сценариев
Каталог поиска `init.lua` задаётся константой `SCRIPT_DIR` в `src/constants.rs`
(по умолчанию — текущий каталог). Его можно переопределить аргументом командной строки:
```sh
gpui-editor-test --config /path/to/project main.go
cargo run -- --config ./config
cargo run -- main.go
```
- В каталоге `--config` должны лежать `init.lua` и подключаемые им модули.
- Первый позиционный аргумент открывается после выполнения `init.lua`
через `app:open(path)` — как `vim main.go` или `nano main.go`.
## Объект `app`
Номера вкладок во всех методах нумеруются с нуля.
### Вкладки
| Метод | Описание |
| --- | --- |
| `app:tabs()` | Список вкладок: `{{ index = 0, title = "main.rs", icon = "file" }, ...}` |
| `app:active_tab()` | Индекс активной вкладки |
| `app:set_active_tab(index)` | Сделать вкладку активной; `true`, если вкладка существует |
| `app:next_tab()` | Переключить на следующую вкладку, вернуть индекс |
| `app:previous_tab()` | Переключить на предыдущую вкладку, вернуть индекс |
| `app:open_tab()` | Открыть пустую вкладку, вернуть индекс |
| `app:open(path)` | Открыть файл с диска, вернуть индекс вкладки. Если файла нет — открывается пустой буфер с таким именем |
| `app:close_tab(index)` | Закрыть вкладку; `true`, если вкладка закрыта |
| `app:set_tab_icon(index, name)` | Назначить иконку вкладке; `false`, если вкладки нет или иконка неизвестна |
| `app:clear_tab_icon(index)` | Вернуть иконку по умолчанию (`file`) |
| `app:tab_icon(index)` | Имя иконки вкладки или `nil`, если вкладки нет |
### Иконки вкладок
Слева от названия вкладки показывается иконка; по умолчанию это `file`.
Имя — название иконки [Lucide](https://lucide.dev/icons/), регистр не важен.
Например:
```lua
app:set_tab_icon(0, "file-code") -- main.rs
app:set_tab_icon(1, "book-open") -- notes.md
app:clear_tab_icon(1) -- снова "file"
```
Подчёркивания и пробелы считаются дефисом, а `icons/` и `.svg` можно
не писать: `file_code`, `file-code` и `icons/file-code.svg` равнозначны.
Неизвестное имя не назначается — метод вернёт `false`.
### Текст
| Метод | Описание |
| --- | --- |
| `app:active_text()` | Текст активной вкладки |
| `app:set_active_text(text)` | Заменить текст активной вкладки |
### Подсветка синтаксиса
Подсветка по умолчанию выключена. Поддерживаемые языки: `rust`, `go`, `js`, `json`,
`python`.
| Метод | Описание |
| --- | --- |
| `app:set_language(index, "rust")` | Включить подсветку для вкладки; `false`, если вкладки нет или язык не поддерживается |
| `app:language(index)` | Язык подсветки вкладки или `nil` |
| `app:clear_language(index)` | Выключить подсветку для вкладки |
```lua
app:set_language(0, "rust")
```
### Сохранение
| Метод | Описание |
| --- | --- |
| `app:save()` | Сохранить активную вкладку в её файл |
### Файловая система
| Метод | Описание |
| --- | --- |
| `app:list_dir(path)` | Содержимое каталога: `{{ id = "src/main.rs", name = "main.rs", is_dir = false }, ...}` |
Путь отсчитывается от текущего рабочего каталога процесса. Пустая строка или
`"."` означают сам текущий каталог. Поле `id` — путь элемента относительно
текущего каталога: его можно передать в `app:open(id)` и использовать как
идентификатор узла дерева. Элементы уже отсортированы по имени без учёта
регистра.
```lua
for _, entry in ipairs(app:list_dir("src")) do
print(entry.name, entry.is_dir)
end
```
### Привязка клавиш
`app:map_key(keys, fn)` назначает обработчик на нажатие или отпускание клавиши
(синтаксис в духе nvim):
| Запись | Значение |
| --- | --- |
| `"a"` | нажатие A |
| `"1"` | нажатие 1 |
| `"!a"` | отпускание A |
| `"<S-c>"` | C при зажатом Shift |
| `"<C-z>"` | Z при зажатом Ctrl |
| `"<A-!r>"` | отпускание R при зажатом Alt |
Клавиша сравнивается с именем клавиши от GPUI. Caps Lock не важен. На Linux
и Windows буквы берутся по аппаратному keycode, поэтому `<C-q>` работает
и на русской раскладке.
```lua
app:map_key("<C-s>", function() app:save() end)
app:map_key("<!Esc>", function() app:close() end)
app:map_key("<C-q>", function() app:close() end)
app:map_key("<C-e>", function() app.right_panel:toggle() end)
app:map_key("<C-t>", function() app.bottom_panel:toggle() end)
```
### Окно
| Метод | Описание |
| --- | --- |
| `app:close()` | Закрыть окно |
| `app:toggle_maximize()` | Развернуть окно / вернуть прежний размер |
| `app:set_maximized(true/false)` | Развернуть (`true`) или восстановить (`false`) окно |
| `app:minimize()` | Свернуть окно |
| `app:is_maximized()` | Развёрнуто ли окно прямо сейчас |
## LSP-серверы
Запуск серверов целиком настраивается Lua-сценарием.
```lua
app:run_lsp({
name = "gopls", -- условное имя для stop/restart
cmd = "gopls", -- путь к бинарнику или имя в PATH
args = { "serve" }, -- аргументы процесса
env = { GOFLAGS = "-mod=mod" }, -- переменные окружения
languages = { "go" }, -- какие languageId обслуживает
watcher = "**/*.go", -- шаблон файлового вотчера
root = ".", -- корень workspace (по умолчанию cwd)
initialization_options = {}, -- initializationOptions сервера
})
```
| Метод | Описание |
| --- | --- |
| `app:run_lsp(config)` | Запустить сервер по описанию |
| `app:lsp_servers()` | Имена запущенных серверов: `{ "gopls", ... }` |
| `app:stop_lsp("gopls")` | Остановить сервер; `true`, если сервер был запущен |
| `app:restart_lsp("gopls")` | Перезапустить сервер с прежними настройками |
Поле `watcher` принимает строку или таблицу:
```lua
watcher = "**/*.go"
watcher = { pattern = "**/*.go", ignore = { "vendor/**" } }
```
Автодополнение включается само при вводе идентификатора; `Ctrl+Space` открывает
список вручную. Пока список открыт, `↑`/`↓` и `PageUp`/`PageDown` перемещают
выделение (курсор при этом стоит на месте), `Enter` или `Tab` вставляют выбранный
элемент, `Esc` закрывает список. Горизонтальные стрелки, `Home` и `End` закрывают
список и работают с курсором как обычно. Документация выбранного элемента
показывается второй всплывающей панелью справа от списка.
Примеры запуска:
```lua
app:run_lsp({ name = "gopls", cmd = "gopls", args = { "serve" },
languages = { "go" }, watcher = "**/*.go" })
app:run_lsp({ name = "rust-analyzer", cmd = "rust-analyzer",
languages = { "rust" }, watcher = "**/*.rs" })
```
Пример из `init.lua` с защитой от ошибки запуска:
```lua
local gopls_ok, gopls_err = pcall(function()
app:run_lsp({
name = "gopls",
cmd = "gopls",
args = { "serve" },
root = ".",
languages = { "go" },
watcher = { pattern = "**/*.go", ignore = { "vendor/**" } },
initialization_options = {
usePlaceholders = true,
completeUnimported = true,
},
})
end)
if not gopls_ok then
print("[lsp] gopls не запущен: " .. tostring(gopls_err))
end
```
> `--config` меняет только каталог `init.lua`, поэтому `root = "."` указывает
> на текущий каталог запуска — проект должен быть текущим рабочим каталогом.
## Панели окна
По умолчанию панели закрыты, размер меняется перетаскиванием.
Для `app.left_panel`, `app.right_panel` и `app.bottom_panel` доступны одинаковые методы:
| Метод | Описание |
| --- | --- |
| `app.left_panel:open()` | Открыть панель |
| `app.left_panel:close()` | Закрыть панель |
| `app.left_panel:toggle()` | Переключить панель; вернуть `true`, если панель открыта |
| `app.left_panel:is_open()` | Открыта ли панель |
```lua
app.left_panel:open()
app.right_panel:toggle()
app.bottom_panel:close()
```
### Блоки панелей
Панель может содержать блоки. Блок создаётся через API панели и живёт вместе
с ней; сейчас поддерживается один вид блока — абстрактное дерево.
```lua
local tree = app.left_panel:add_tree()
```
Редактор отвечает только за отрисовку дерева. Что означает каждый узел,
решает сценарий: это могут быть файлы проекта, задачи, настройки — что угодно.
Файловое дерево проекта в `init.lua` собирается из `app:list_dir`, но сам
редактор о файловой системе в блоке ничего не знает.
| Метод | Описание |
| --- | --- |
| `tree:append(nodes, parent)` | Добавить узлы к `parent`; `parent = ""` — корень |
| `tree:remove(id)` | Удалить узел и всех потомков рекурсивно; `false`, если узла нет |
| `tree:on_toggle(fn)` | Обработчик раскрытия: `fn(expanded, id, loaded)` |
| `tree:on_click(fn)` | Обработчик клика по конечной ноде: `fn(id)` |
| `tree:selected()` | Идентификатор выбранной строки или `nil` |
| `tree:focus(id)` | Раскрыть всех родителей, выбрать узел и передать фокус дереву |
Узел — таблица:
```lua
{
id = "src/main.rs", -- строковый идентификатор (путь файла)
label = "main.rs", -- отображаемый текст
icon = "file-code", -- имя иконки Lucide (необязательно)
expandable = false, -- раскрываемая ли нода (папка)
expanded = false, -- раскрыта ли она сейчас
}
```
`append` добавляет узлы, а не заменяет дерево. Повторная передача того же `id`
обновляет описание узла, но не создаёт копию.
Дети каждого уровня сортируются по `label` без учёта регистра. Сначала идут
раскрываемые узлы (`expandable = true`), затем конечные; внутри каждой группы —
по алфавиту.
В обработчик `on_toggle` приходит `loaded = true`, если дочерние узлы уже
добавляли в эту ноду; при первом раскрытии — `false`.
Клик по строке выбирает её. Выбранную строку можно перемещать стрелками
`↑`/`↓`; `↑`/`↓` двигают выбор, `→` раскрывает папку или переходит к первому
ребёнку, `←` сворачивает папку или выбирает родителя, `Enter` раскрывает папку
или вызывает `on_click` для конечной ноды. Выбор сохраняется, пока панель
закрыта; при повторном открытии панели фокус возвращается на выбранную строку.
Пример отложенной загрузки дерева:
```lua
local tree = app.left_panel:add_tree()
local function load_dir(path)
local nodes = {}
for _, entry in ipairs(app:list_dir(path)) do
nodes[#nodes + 1] = {
id = entry.id,
label = entry.name,
icon = entry.is_dir and "folder" or "file",
expandable = entry.is_dir,
expanded = false,
}
end
tree:append(nodes, path)
end
tree:on_toggle(function(expanded, id, loaded)
if expanded and not loaded then
load_dir(id)
end
end)
tree:on_click(function(id)
app:open(id)
end)
app.left_panel:open()
load_dir("")
-- При открытии панели вернуть фокус на последнюю выбранную строку.
app:map_key("<C-b>", function()
if app.left_panel:toggle() then
local selected = tree:selected()
if selected then
tree:focus(selected)
end
end
end)
```
## Подключение других Lua-файлов
Файлы подключаются из каталога сценариев:
| Способ | Описание |
| --- | --- |
| `include("tools.lua")` | Выполнить файл сразу |
| `require("tools")` | Выполнить один раз (как обычный Lua-модуль) |
## Добавление новых методов
Методы добавляются в `src/api.rs`: всё, что есть в `impl AppApi`, помеченном
`#[mlua::userdata_impl]`, автоматически доступно из Lua.
Чтобы не экспортировать метод, его помечают `#[lua(skip)]`.
## Полный пример `init.lua`
```lua
print("[lua] init.lua выполнен")
app:set_language(0, "rust")
app:next_tab()
app:map_key("<C-q>", function() app:close() end)
app:map_key("<C-e>", function() app.right_panel:toggle() end)
app:map_key("<C-t>", function() app.bottom_panel:toggle() end)
app:open_tab()
```