16 KiB
Lua API
Приложение выполняет init.lua при запуске и предоставляет сценариям объект app
для управления окном, вкладками, панелями, файловой системой, подсветкой и
LSP-серверами.
Запуск и каталог сценариев
Каталог поиска init.lua задаётся константой SCRIPT_DIR в src/constants.rs
(по умолчанию — текущий каталог). Его можно переопределить аргументом командной строки:
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, регистр не важен.
Например:
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) |
Выключить подсветку для вкладки |
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) и использовать как
идентификатор узла дерева. Элементы уже отсортированы по имени без учёта
регистра.
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> работает
и на русской раскладке.
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-сценарием.
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 принимает строку или таблицу:
watcher = "**/*.go"
watcher = { pattern = "**/*.go", ignore = { "vendor/**" } }
Автодополнение включается само при вводе идентификатора; Ctrl+Space открывает
список вручную. Пока список открыт, ↑/↓ и PageUp/PageDown перемещают
выделение (курсор при этом стоит на месте), Enter или Tab вставляют выбранный
элемент, Esc закрывает список. Горизонтальные стрелки, Home и End закрывают
список и работают с курсором как обычно. Документация выбранного элемента
показывается второй всплывающей панелью справа от списка.
Примеры запуска:
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 с защитой от ошибки запуска:
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() |
Открыта ли панель |
app.left_panel:open()
app.right_panel:toggle()
app.bottom_panel:close()
Блоки панелей
Панель может содержать блоки. Блок создаётся через API панели и живёт вместе с ней; сейчас поддерживается один вид блока — абстрактное дерево.
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) |
Раскрыть всех родителей, выбрать узел и передать фокус дереву |
Узел — таблица:
{
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 для конечной ноды. Выбор сохраняется, пока панель
закрыта; при повторном открытии панели фокус возвращается на выбранную строку.
Пример отложенной загрузки дерева:
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
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()