12 KiB
Панели — api.panels
Группа api.panels управляет панелями окна и верхней панелью редактора:
| Объект | Назначение |
|---|---|
api.panels.left |
Левая панель |
api.panels.right |
Правая панель |
api.panels.bottom |
Нижняя панель |
api.panels.toolbar |
Верхняя панель редактора: лейблы и кнопки |
По умолчанию панели закрыты, размер меняется перетаскиванием разделителя. Боковые панели рисуют все свои блоки подряд и не показывают заголовки; блоки нижней панели показываются вкладками.
Если у правой панели нет блоков, она показывает состояние LSP-серверов.
Панели
Для api.panels.left, api.panels.right и api.panels.bottom доступны
одинаковые методы:
| Метод | Описание |
|---|---|
api.panels.left:open() |
Открыть панель |
api.panels.left:close() |
Закрыть панель |
api.panels.left:toggle() |
Переключить панель; вернуть true, если панель открыта |
api.panels.left:is_open() |
Открыта ли панель |
api.panels.left:add_tree(title) |
Добавить блок-дерево и вернуть его API |
api.panels.left:open()
api.panels.right:toggle()
api.panels.bottom:close()
Верхняя панель редактора
Полоса между вкладками и редактором. У панели две области: элементы левой выстраиваются от левого края вправо, элементы правой — от правого края влево.
| Метод | Описание |
|---|---|
api.panels.toolbar:add_label(text, icon, background, id, side) |
Добавить лейбл |
api.panels.toolbar:add_button(text, icon, background, id, side, fn) |
Добавить кнопку |
api.panels.toolbar:hide(id) |
Скрыть элемент; false, если элемента с таким id нет |
api.panels.toolbar:show(id) |
Снова показать скрытый элемент |
api.panels.toolbar:clear() |
Удалить все элементы панели |
icon— имя иконки Lucide (play,settings,file, ...) илиnil.background—#RGB,#RGBA,#RRGGBB,#RRGGBBAAилиnil(прозрачный фон). Цвет текста подбирается автоматически: тёмный на светлом фоне, светлый на тёмном.id— идентификатор элемента дляhideиshow. Повторное добавление с тем жеidзаменяет элемент, а не создаёт дубликат.side—"left"или"right".fn— обработчик нажатия; вызывается без аргументов.
api.panels.toolbar:add_label("Готово", nil, nil, "status", "left")
api.panels.toolbar:add_label("UTF-8", nil, "#3f3f46", "encoding", "right")
api.panels.toolbar:add_button("Запустить", "play", nil, "run", "right", function()
print("Запуск")
end)
api.panels.toolbar:hide("run")
api.panels.toolbar:show("run")
Блоки панелей
Панель может содержать блоки. Блок создаётся через API панели и живёт вместе с ней; сейчас поддерживается один вид блока — абстрактное дерево.
local tree = api.panels.left:add_tree()
local diagnostics = api.panels.bottom:add_tree("Диагностика")
Необязательный аргумент add_tree — заголовок вкладки. В нижней панели он
показывается на вкладке; в боковых панелях заголовок сохраняется, но не
рисуется. Без заголовка используется «Дерево». Новый блок становится
активной вкладкой нижней панели.
Редактор отвечает только за отрисовку дерева. Что означает каждый узел,
решает сценарий: это могут быть файлы проекта, задачи, настройки — что угодно.
Файловое дерево проекта в init.lua собирается из api.path:list, но сам
редактор о файловой системе в блоке ничего не знает.
| Метод | Описание |
|---|---|
tree:append(nodes, parent) |
Добавить узлы к parent; parent = "" — корень |
tree:clear() |
Удалить все узлы и сбросить выбор |
tree:remove(id) |
Удалить узел и всех потомков рекурсивно; false, если узла нет |
tree:on_toggle(fn) |
Обработчик раскрытия: fn(expanded, id, loaded) |
tree:on_click(fn) |
Обработчик клика по конечной ноде: fn(id) |
tree:selected() |
Идентификатор выбранной строки или nil |
tree:focus(id) |
Раскрыть всех родителей, выбрать узел и передать фокус дереву |
tree:focus() |
Передать фокус дереву, не меняя выбранную строку |
tree:has_focus() |
Владеет ли дерево клавиатурным фокусом прямо сейчас |
tree:map_key(modes, keys, handler) |
Привязка, которая действует, когда фокус на этом блоке |
tree:set_mode(mode) |
Переключить режим привязок блока |
tree:mode() |
Текущий режим привязок блока |
Узел — таблица:
{
id = "src/main.rs", -- строковый идентификатор (путь файла)
label = "main.rs", -- отображаемый текст
icon = "file-code", -- имя иконки Lucide (необязательно)
expandable = false, -- раскрываемая ли нода (папка)
expanded = false, -- раскрыта ли она сейчас
}
append добавляет узлы, а не заменяет дерево. Повторная передача того же id
обновляет описание узла, но не создаёт копию. clear удаляет всё дерево;
обработчики on_toggle и on_click продолжают действовать, поэтому дерево
удобно строить заново на каждое обновление данных.
Дети каждого уровня сортируются по label без учёта регистра. Сначала идут
раскрываемые узлы (expandable = true), затем конечные; внутри каждой группы —
по алфавиту.
В обработчик on_toggle приходит loaded = true, если дочерние узлы уже
добавляли в эту ноду; при первом раскрытии — false.
Клик по строке выбирает её. Выбранную строку можно перемещать стрелками
↑/↓; → раскрывает папку или переходит к первому ребёнку, ← сворачивает
папку или выбирает родителя, Enter раскрывает папку или вызывает on_click
для конечной ноды. Выбор сохраняется, пока панель закрыта; при повторном
открытии панели фокус возвращается на выбранную строку. tree:focus() без
аргумента передаёт фокус дереву, даже если ни одна строка ещё не выбрана.
tree:has_focus() возвращает состояние фокуса на момент последнего события
клавиатуры — его удобно проверять в глобальных обработчиках.
Пример отложенной загрузки дерева
local tree = api.panels.left:add_tree()
local function load_dir(path)
local nodes = {}
for _, entry in ipairs(api.path:list(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)
-- Фокус в редактор, курсор остаётся на прежнем месте.
api.editor:open(id, {})
end)
api.panels.left:open()
load_dir("")
-- При открытии панели вернуть фокус на последнюю выбранную строку.
api.keymap:map_key("", "<C-b>", function()
if api.panels.left:toggle() then
local selected = tree:selected()
if selected then
tree:focus(selected)
end
end
end)
Пример дерева диагностики
На первом уровне — файлы с полным путём, на втором — сообщения с иконкой
уровня. Сообщения с одинаковыми началом и концом диапазона объединяются в одну
запись; клик открывает файл на нужной позиции. Источник данных — обработчик
api.editor:on_lsp_diagnostics, описанный в lsp.md.
local diagnostics = api.panels.bottom:add_tree("Диагностика")
local icons = {
error = "circle-x",
warning = "triangle-alert",
information = "circle-alert",
hint = "lightbulb",
}
api.editor:on_lsp_diagnostics(1000, function(servers)
diagnostics:clear()
local files = {}
local order = {}
for _, entry in ipairs(servers) do
local path = entry.path or entry.uri
if not files[path] then
files[path] = {}
order[#order + 1] = path
end
for _, item in ipairs(entry.diagnostics) do
table.insert(files[path], item)
end
end
for _, path in ipairs(order) do
local groups = {}
local keys = {}
for _, item in ipairs(files[path]) do
local key = string.format("%d:%d:%d:%d", item.line, item.character,
item.end_line, item.end_character)
if not groups[key] then
groups[key] = { item = item, messages = {}, seen = {} }
keys[#keys + 1] = key
end
local group = groups[key]
if not group.seen[item.message] then
group.seen[item.message] = true
group.messages[#group.messages + 1] = item.message
end
end
diagnostics:append({
{ id = path, label = path, icon = "file",
expandable = true, expanded = true },
}, "")
local errors = {}
for index, key in ipairs(keys) do
local group = groups[key]
local item = group.item
local severity = item.severity_name or "error"
errors[#errors + 1] = {
id = string.format("diag:%s:%d:%d:%d", path, item.line,
item.character, index),
label = string.format("%d:%d %s", item.line + 1,
item.character + 1,
table.concat(group.messages, "; ")),
icon = icons[severity] or "circle-alert",
}
end
diagnostics:append(errors, path)
end
end)
diagnostics:on_click(function(id)
local path, line, character = id:match("^diag:(.*):(%d+):(%d+):%d+$")
if path then
api.editor:open(path, { tonumber(line), tonumber(character) })
else
api.editor:open(id, {})
end
end)
api.panels.bottom:open()