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

16 KiB
Raw Blame History

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()