refactoring

This commit is contained in:
2026-10-31 15:33:00 +03:00
parent e8e2d25600
commit 838627fb33
27 changed files with 2715 additions and 1259 deletions
+276 -118
View File
@@ -10,15 +10,31 @@
--
-- В каталоге --config должны лежать init.lua и подключаемые им модули.
-- Первый позиционный аргумент открывается после выполнения этого файла
-- через app.editor:open(path) — как `vim main.go` или `nano main.go`.
-- через api.editor:open(path) — как `vim main.go` или `nano main.go`.
print("[lua] init.lua выполнен")
-- Объект app: окно, панели и глобальные привязки клавиш:
-- app:list_dir(path) -- {{ id = "src/main.rs", name = "main.rs",
-- qode — конструктор IDE: Rust даёт универсальные примитивы (окно, вкладки,
-- редактор, панели, дерево, LSP, файловая система, клавиатура), а вся
-- бизнес-логика IDE живёт в Lua. Подробная документация — в docs/.
--
-- Объект api разбит на группы: api.editor (редактор), api.panels (панели),
-- api.path (файловая система), api.window (окно и поле ввода) и
-- api.keymap (глобальные привязки). Все методы api.path принимают путь
-- строкой "foo/bar" или массивом сегментов { "foo", "bar" }.
--
-- api.path:list(path) -- {{ id = "src/main.rs", name = "main.rs",
-- -- is_dir = false }, ...}; пустой путь —
-- -- текущий каталог, id — путь от него
-- app:map_key(modes, keys, handler)
-- api.path:mkdir(path) -- создать каталоги, вернуть созданный путь
-- api.path:remove(path) -- удалить файл или каталог рекурсивно;
-- -- false, если пути не было
-- api.path:touch(path) -- создать пустой файл, вернуть путь
-- api.path:read(path) -- содержимое файла строкой
-- api.path:write(path, text, append)
-- -- записать текст; append = true — дописать,
-- -- false/nil — перезаписать с нуля
-- api.keymap:map_key(modes, keys, handler)
-- -- глобальный обработчик клавиши/сочетания:
-- -- функция, имя команды или nil (удалить)
-- -- modes — строка режимов:
@@ -40,20 +56,20 @@ print("[lua] init.lua выполнен")
-- -- Caps Lock не важен; на Linux и Windows буквы
-- -- берутся по аппаратному keycode, поэтому
-- -- <C-q> работает и на русской раскладке
-- app:map_completion_key(keys, handler)
-- api.keymap:map_completion_key(keys, handler)
-- -- то же для списка автодополнения; команды:
-- -- completion_trigger, completion_next,
-- -- completion_previous, completion_page_up,
-- -- completion_page_down, completion_accept,
-- -- completion_cancel, completion_dismiss
-- app:set_mode(mode) -- режим глобальных привязок (один символ)
-- app:mode() -- текущий режим глобальных привязок
-- app:close() -- закрыть окно
-- app:toggle_maximize()
-- app:set_maximized(true/false)
-- app:minimize()
-- app:is_maximized()
-- app:open_input(options) -- плавающее поле ввода чуть выше центра;
-- api.keymap:set_mode(mode) -- режим глобальных привязок (один символ)
-- api.keymap:mode() -- текущий режим глобальных привязок
-- api.window:close() -- закрыть окно
-- api.window:toggle_maximize()
-- api.window:set_maximized(true/false)
-- api.window:minimize()
-- api.window:is_maximized()
-- api.window:open_input(options) -- плавающее поле ввода чуть выше центра;
-- -- options = { title = "...",
-- -- placeholder = "...",
-- -- callback = function(text) ... end };
@@ -61,52 +77,56 @@ print("[lua] init.lua выполнен")
-- -- Esc или клик вне окна закрывают без него;
-- -- пока окно открыто, привязки клавиш молчат
--
-- Редактор app.editor (номера вкладок — с нуля):
-- app.editor:tabs() -- {{ index = 0, title = "main.rs",
-- Редактор api.editor (номера вкладок — с нуля):
-- api.editor:tabs() -- {{ index = 0, title = "main.rs",
-- -- icon = "file" }, ...}
-- app.editor:active_tab() -- индекс активной вкладки
-- app.editor:set_active_tab(index) -- true, если вкладка существует
-- app.editor:next_tab() -- переключить на следующую, вернуть индекс
-- app.editor:previous_tab() -- переключить на предыдущую, вернуть индекс
-- app.editor:open_tab() -- открыть пустую вкладку, вернуть индекс
-- app.editor:open(path) -- открыть файл с диска, вернуть индекс вкладки;
-- api.editor:active_tab() -- индекс активной вкладки
-- api.editor:set_active_tab(index) -- true, если вкладка существует
-- api.editor:next_tab() -- переключить на следующую, вернуть индекс
-- api.editor:previous_tab() -- переключить на предыдущую, вернуть индекс
-- api.editor:open_tab() -- открыть пустую вкладку, вернуть индекс
-- api.editor:open(path) -- открыть файл с диска, вернуть индекс вкладки;
-- -- если файла нет — пустой буфер с таким именем
-- app.editor:open(path, {}) -- открыть и перевести фокус в редактор,
-- api.editor:open(path, {}) -- открыть и перевести фокус в редактор,
-- -- курсор оставить на месте
-- app.editor:open(path, {m, n}) -- открыть, перевести фокус и поставить
-- api.editor:open(path, {m, n}) -- открыть, перевести фокус и поставить
-- -- курсор на строку m и столбец n (с нуля)
-- app.editor:close_tab(index) -- true, если вкладка закрыта
-- app.editor:set_tab_icon(i, name) -- иконка вкладки из каталога Lucide
-- api.editor:close_tab(index) -- true, если вкладка закрыта
-- api.editor:set_tab_icon(i, name) -- иконка вкладки из каталога Lucide
-- -- (file, file-code, book-open, ...);
-- -- false — нет вкладки или иконки
-- app.editor:clear_tab_icon(i) -- вернуть иконку по умолчанию (file)
-- app.editor:tab_icon(i) -- имя иконки вкладки или nil
-- app.editor:set_gutter_icon(line, name[, color])
-- api.editor:clear_tab_icon(i) -- вернуть иконку по умолчанию (file)
-- api.editor:tab_icon(i) -- имя иконки вкладки или nil
-- api.editor:set_gutter_icon(line, name[, color])
-- -- иконка на поле слева от редактора
-- -- (активная вкладка, строка с нуля);
-- -- color — #RGB/#RGBA/#RRGGBB/#RRGGBBAA;
-- -- false — неизвестная иконка или цвет
-- app.editor:gutter_icon(line) -- имя иконки gutter или nil
-- app.editor:gutter_color(line) -- цвет иконки gutter или nil
-- app.editor:clear_gutter_icon(line) -- убрать иконку со строки
-- app.editor:clear_gutter() -- убрать все иконки активной вкладки
-- app.editor:active_text() -- текст активной вкладки
-- app.editor:set_active_text(text) -- заменить текст активной вкладки
-- app.editor:set_language(i, "rust") -- включить подсветку
-- -- (rust|go|js|json|python) для вкладки i;
-- api.editor:gutter_icon(line) -- имя иконки gutter или nil
-- api.editor:gutter_color(line) -- цвет иконки gutter или nil
-- api.editor:clear_gutter_icon(line) -- убрать иконку со строки
-- api.editor:clear_gutter() -- убрать все иконки активной вкладки
-- api.editor:active_text() -- текст активной вкладки
-- api.editor:set_active_text(text) -- заменить текст активной вкладки
-- api.editor:set_language(i, "rust") -- включить подсветку
-- -- (rust|go|js|json|python|lua|bash|yaml|
-- -- make|dockerfile|sql|markdown|css|c|
-- -- cpp|html|xml) для вкладки i;
-- -- false — нет вкладки/языка
-- app.editor:language(i) -- язык подсветки вкладки или nil
-- app.editor:clear_language(i) -- выключить подсветку для вкладки
-- app.editor:save() -- сохранить активную вкладку в её файл
-- app.editor:map_key(modes, keys, handler)
-- api.editor:language(i) -- язык подсветки вкладки или nil
-- api.editor:clear_language(i) -- выключить подсветку для вкладки
-- api.editor:save() -- сохранить активную вкладку в её файл
-- api.editor:map_key(modes, keys, handler)
-- -- привязка, которая работает дополнительно
-- -- к глобальной, когда фокус в редакторе;
-- -- синтаксис modes/keys/handler как у app:map_key
-- app.editor:set_mode(mode) -- режим привязок редактора
-- app.editor:mode() -- текущий режим привязок редактора
-- -- синтаксис modes/keys/handler как у api.keymap:map_key
-- api.editor:set_mode(mode) -- режим привязок редактора
-- api.editor:mode() -- текущий режим привязок редактора
-- api.editor:focus() -- перевести фокус в редактор, не меняя
-- -- активную вкладку и курсор
--
-- LSP-серверы (запуск целиком настраивается этим сценарием):
-- app.editor:run_lsp({
-- api.editor:run_lsp({
-- name = "gopls", -- условное имя для stop/restart
-- cmd = "gopls", -- путь к бинарнику или имя в PATH
-- args = { "serve" }, -- аргументы процесса
@@ -116,10 +136,10 @@ print("[lua] init.lua выполнен")
-- root = ".", -- корень workspace (по умолчанию cwd)
-- initialization_options = {}, -- initializationOptions сервера
-- })
-- app.editor:lsp_servers() -- { "gopls", ... }
-- app.editor:stop_lsp("gopls") -- true, если сервер был запущен
-- app.editor:restart_lsp("gopls") -- перезапуск с прежними настройками
-- app.editor:on_lsp_diagnostics(delay_ms, fn)
-- api.editor:lsp_servers() -- { "gopls", ... }
-- api.editor:stop_lsp("gopls") -- true, если сервер был запущен
-- api.editor:restart_lsp("gopls") -- перезапуск с прежними настройками
-- api.editor:on_lsp_diagnostics(delay_ms, fn)
-- -- fn(servers) вызывается один раз
-- -- после затишья delay_ms; nil вместо
-- -- функции удаляет обработчик.
@@ -150,42 +170,42 @@ print("[lua] init.lua выполнен")
-- список; горизонтальные стрелки закрывают его и двигают курсор.
-- Документация выбранного элемента показывается второй всплывающей панелью
-- справа от списка. Примеры запуска:
-- app.editor:run_lsp({ name = "gopls", cmd = "gopls", args = { "serve" },
-- api.editor:run_lsp({ name = "gopls", cmd = "gopls", args = { "serve" },
-- languages = { "go" }, watcher = "**/*.go" })
-- app.editor:run_lsp({ name = "rust-analyzer", cmd = "rust-analyzer",
-- api.editor:run_lsp({ name = "rust-analyzer", cmd = "rust-analyzer",
-- languages = { "rust" }, watcher = "**/*.rs" })
--
-- Панели окна (по умолчанию закрыты, размер меняется перетаскиванием):
-- app.left_panel:open()
-- app.left_panel:close()
-- app.left_panel:toggle() -- вернуть true, если панель открыта
-- app.left_panel:is_open()
-- app.right_panel:open() -- те же методы, что у левой панели
-- app.right_panel:close()
-- app.right_panel:toggle()
-- app.right_panel:is_open()
-- app.bottom_panel:open() -- те же методы, что у левой панели
-- app.bottom_panel:close()
-- app.bottom_panel:toggle()
-- app.bottom_panel:is_open()
-- api.panels.left:open()
-- api.panels.left:close()
-- api.panels.left:toggle() -- вернуть true, если панель открыта
-- api.panels.left:is_open()
-- api.panels.right:open() -- те же методы, что у левой панели
-- api.panels.right:close()
-- api.panels.right:toggle()
-- api.panels.right:is_open()
-- api.panels.bottom:open() -- те же методы, что у левой панели
-- api.panels.bottom:close()
-- api.panels.bottom:toggle()
-- api.panels.bottom:is_open()
--
-- Верхняя панель редактора (полоса между вкладками и редактором).
-- Элементы левой области идут от левого края вправо, правой — от правого
-- края влево. id нужен, чтобы скрыть или снова показать элемент.
-- app.toolbar:add_label(text, icon, background, id, side)
-- app.toolbar:add_button(text, icon, background, id, side, fn)
-- api.panels.toolbar:add_label(text, icon, background, id, side)
-- api.panels.toolbar:add_button(text, icon, background, id, side, fn)
-- -- fn() вызывается при нажатии;
-- -- background — "#RRGGBB", "#RRGGBBAA" или nil;
-- -- icon — имя иконки Lucide или nil;
-- -- side — "left" или "right"
-- app.toolbar:hide(id) -- true, если элемент найден
-- app.toolbar:show(id) -- снова показать скрытый элемент
-- app.toolbar:clear() -- удалить все элементы
-- api.panels.toolbar:hide(id) -- true, если элемент найден
-- api.panels.toolbar:show(id) -- снова показать скрытый элемент
-- api.panels.toolbar:clear() -- удалить все элементы
--
-- Блоки панелей. Сценарий сам решает, что показывает блок: редактор умеет
-- рисовать только абстрактное дерево.
-- local tree = app.left_panel:add_tree()
-- local tab = app.bottom_panel:add_tree("Диагностика")
-- local tree = api.panels.left:add_tree()
-- local tab = api.panels.bottom:add_tree("Диагностика")
-- -- title показывается вкладкой в нижней
-- -- панели; боковые панели его не рисуют
-- tree:append(nodes, parent_id) -- nodes = { { id=..., label=..., icon=...,
@@ -197,28 +217,32 @@ print("[lua] init.lua выполнен")
-- tree:on_click(fn) -- fn(id) — клик по конечной ноде
-- tree:selected() -- id выбранной строки или nil
-- tree:focus(id) -- раскрыть родителей, выбрать и дать фокус
-- tree:focus() -- только дать фокус, не меняя выбор
-- tree:has_focus() -- владеет ли дерево фокусом сейчас
-- tree:map_key(modes, keys, handler)
-- -- привязка, которая работает дополнительно
-- -- к глобальной, когда фокус на этом дереве
-- tree:set_mode(mode) -- режим привязок блока (один символ)
-- tree:mode() -- текущий режим привязок блока
--
-- Методы добавляются в src/api.rs: всё, что есть в impl AppApi или
-- impl EditorApi, помеченном #[mlua::userdata_impl], автоматически доступно
-- из Lua как app:method(...) или app.editor:method(...).
-- Методы добавляются в src/api.rs. Публичные методы impl-блока с
-- #[mlua::userdata_impl] автоматически доступны из Lua: методы AppApi — у api,
-- EditorApi — у api.editor, PanelApi — у api.panels.left/.right/.bottom,
-- ToolbarApi — у api.panels.toolbar, PathApi — у api.path.
-- Чтобы не экспортировать метод, его помечают #[lua(skip)].
--
-- Другие Lua-файлы можно подключать из каталога сценариев:
-- include("tools.lua") -- выполнить файл сразу
-- require("tools") -- выполнить один раз (как обычный Lua-модуль)
--
-- Подсветка синтаксиса по умолчанию выключена. Включить её можно так:
-- app.editor:set_language(0, "rust")
-- api.editor:set_language(0, "rust")
-- gopls запускается автоматически при старте редактора.
-- Сервер обслуживает только документы с languageId "go" и получает изменения
-- всех .go-файлов проекта (кроме vendor/) через файловый вотчер.
local gopls_ok, gopls_err = pcall(function()
app.editor:run_lsp({
api.editor:run_lsp({
name = "gopls",
cmd = "gopls",
args = { "serve" },
@@ -242,75 +266,209 @@ if not gopls_ok then
end
-- Пример привязки: Ctrl+S сохраняет вкладку, отпускание Esc закрывает окно.
-- app:map_key("", "<C-s>", function() app.editor:save() end)
-- app:map_key("", "<!Esc>", function() app:close() end)
-- api.keymap:map_key("", "<C-s>", function() api.editor:save() end)
-- api.keymap:map_key("", "<!Esc>", function() api.window:close() end)
-- Убрать встроенную привязку можно так:
-- app:map_key("", "<C-s>", nil)
-- app:map_completion_key("<C-n>", "completion_next")
-- app:map_completion_key("<C-p>", "completion_previous")
-- api.keymap:map_key("", "<C-s>", nil)
-- api.keymap:map_completion_key("<C-n>", "completion_next")
-- api.keymap:map_completion_key("<C-p>", "completion_previous")
-- Файловое дерево проекта.
--
-- Редактор рисует абстрактное дерево; смысл узлов (файлы, папки, что угодно)
-- задаёт этот сценарий. Здесь корень — текущий каталог, а каждая папка
-- дозагружается при первом раскрытии.
local project_tree = app.left_panel:add_tree()
local project_tree = api.panels.left:add_tree()
-- Какие узлы являются каталогами. Нужно при создании пути: новый файл
-- появляется внутри выбранной папки или рядом с выбранным файлом.
local node_is_dir = {}
-- Какие папки раскрыты. `load_dir` обновляет узлы, и без этого списка
-- повторная загрузка каталога сворачивала бы уже открытые папки.
local expanded_nodes = {}
local function split_path(path)
local parts = {}
for part in path:gmatch("[^/]+") do
parts[#parts + 1] = part
end
return parts
end
local function parent_path(path)
return path:match("^(.*)/[^/]*$") or ""
end
local function merge_path(base, parts)
local segments = {}
for _, part in ipairs(base) do
segments[#segments + 1] = part
end
for _, part in ipairs(parts) do
segments[#segments + 1] = part
end
return segments
end
local function join_path(segments)
return table.concat(segments, "/")
end
local function load_dir(tree, path)
local nodes = {}
for _, entry in ipairs(app:list_dir(path)) do
for _, entry in ipairs(api.path:list(path)) do
node_is_dir[entry.id] = entry.is_dir
nodes[#nodes + 1] = {
id = entry.id,
label = entry.name,
icon = entry.is_dir and "folder" or "file",
expandable = entry.is_dir,
expanded = false,
expanded = entry.is_dir and expanded_nodes[entry.id] == true or false,
}
end
tree:append(nodes, path)
end
project_tree:on_toggle(function(expanded, id, loaded)
expanded_nodes[id] = expanded or nil
-- loaded == false только при первом раскрытии: позже дети уже в дереве.
if expanded and not loaded then
load_dir(project_tree, id)
end
end)
-- Раскрывает папку и сохраняет это в `expanded_nodes`, чтобы последующая
-- загрузка соседних каталогов не свернула её обратно.
local function expand_dir(path, label)
expanded_nodes[path] = true
project_tree:append({
{
id = path,
label = label,
icon = "folder",
expandable = true,
expanded = true,
},
}, parent_path(path))
end
project_tree:on_click(function(id)
-- Пустая таблица: фокус уходит в редактор, курсор остаётся на месте.
app.editor:open(id, {})
api.editor:open(id, {})
end)
app.left_panel:open()
api.panels.left:open()
load_dir(project_tree, "")
-- Открытие/закрытие панели с деревом. При открытии возвращаем фокус на
-- последнюю выбранную строку, чтобы можно было сразу продолжить навигацию.
app:map_key("", "<C-b>", function()
local opened = app.left_panel:toggle()
if opened then
local selected = project_tree:selected()
if selected then
project_tree:focus(selected)
end
-- Каталог, от которого отсчитывается новый путь: выбранная папка или
-- родитель выбранного файла.
local function creation_base()
local id = project_tree:selected()
if not id or id == "" then
return {}
end
end)
if node_is_dir[id] then
return split_path(id)
end
return split_path(parent_path(id))
end
app:map_key("", "<C-q>", function() app:close() end)
-- Создаёт путь, введённый в поле. Последний сегмент — файл, если строка не
-- заканчивается на "/"; иначе создаётся папка. Все промежуточные сегменты —
-- папки. Путь отсчитывается от текущей позиции в дереве.
local function create_path(text)
text = text:match("^%s*(.-)%s*$")
if text == "" then
return
end
-- Плавающее поле ввода. Пока оно открыто, сочетания клавиш приложения
-- заблокированы; Enter вызывает callback, Esc и клик вне окна — отменяют.
app:map_key("", "<C-g>", function()
app:open_input({
-- title = "Открыть файл",
placeholder = "src/main.rs",
callback = function(text) app.editor:open(text, {}) end,
local base = creation_base()
local parts = split_path(text)
if #parts == 0 then
return
end
local is_directory = text:sub(-1) == "/"
local parent_parts = {}
local last_index = is_directory and #parts or #parts - 1
for index = 1, last_index do
parent_parts[#parent_parts + 1] = parts[index]
end
local parent_segments = merge_path(base, parent_parts)
if #parent_segments > 0 then
api.path:mkdir(parent_segments)
end
local path = join_path(merge_path(base, parts))
if not is_directory then
-- Создаём файл на диске, затем открываем его вкладкой. `open` без
-- второго аргумента не забирает фокус у дерева.
api.path:touch(path)
api.editor:open(path)
end
-- Раскрываем всё созданное поддерево и загружаем содержимое каждого
-- каталога, чтобы конечный элемент был виден в дереве.
local current = {}
for _, part in ipairs(base) do
current[#current + 1] = part
end
for _, part in ipairs(parent_parts) do
load_dir(project_tree, join_path(current))
current[#current + 1] = part
local dir_path = join_path(current)
expand_dir(dir_path, part)
load_dir(project_tree, dir_path)
end
-- Загружаем каталог, в котором появился конечный элемент (для пути без
-- промежуточных папок это базовый каталог).
load_dir(project_tree, join_path(current))
project_tree:focus(path)
if not is_directory then
-- Созданный файл уже открыт вкладкой; переводим туда фокус, выбор
-- в дереве при этом остаётся на нём.
api.editor:focus()
end
end
project_tree:map_key("", "a", function()
api.window:open_input({
title = "Новый файл или папка",
placeholder = "foo/bar/omg.txt",
callback = create_path,
})
end)
app:map_key("", "<C-e>", "toggle_left_panel")
app:map_key("", "<C-t>", "toggle_bottom_panel")
-- Ctrl+E открывает панель и переводит фокус на дерево, а если панель уже
-- открыта — только переводит фокус. На дереве Ctrl+Q закрывает панель,
-- в остальных местах — по-прежнему закрывает приложение.
api.keymap:map_key("", "<C-e>", function()
api.panels.left:open()
project_tree:focus()
end)
api.keymap:map_key("", "<C-q>", function()
if project_tree:has_focus() then
api.panels.left:close()
api.editor:focus()
else
api.window:close()
end
end)
-- Плавающее поле ввода. Пока оно открыто, сочетания клавиш приложения
-- заблокированы; Enter вызывает callback, Esc и клик вне окна — отменяют.
api.keymap:map_key("", "<C-g>", function()
api.window:open_input({
-- title = "Открыть файл",
placeholder = "src/main.rs",
callback = function(text) api.editor:open(text, {}) end,
})
end)
api.keymap:map_key("", "<C-t>", "toggle_bottom_panel")
-- Дерево диагностики в нижней панели.
--
@@ -318,7 +476,7 @@ app:map_key("", "<C-t>", "toggle_bottom_panel")
-- тишины: если серверы присылают диагностику чаще, промежуточные вызовы
-- пропускаются, а последняя остаётся и обрабатывается после затишья.
-- При каждом вызове дерево очищается целиком и строится заново.
local diagnostics_tree = app.bottom_panel:add_tree("Диагностика")
local diagnostics_tree = api.panels.bottom:add_tree("Диагностика")
local severity_icons = {
error = "circle-x",
@@ -346,8 +504,8 @@ end
local diagnostic_gutter = {}
local function active_tab_title()
local active = app.editor:active_tab()
for _, tab in ipairs(app.editor:tabs()) do
local active = api.editor:active_tab()
for _, tab in ipairs(api.editor:tabs()) do
if tab.index == active then
return tab.title
end
@@ -370,14 +528,14 @@ local function path_matches_title(path, title)
return #path > #suffix and path:sub(-#suffix) == suffix
end
app.editor:on_lsp_diagnostics(100, function(servers)
api.editor:on_lsp_diagnostics(100, function(servers)
diagnostics_tree:clear()
-- Иконки прошлого обновления активной вкладки могли устареть.
local title = active_tab_title()
if title and diagnostic_gutter[title] then
for line in pairs(diagnostic_gutter[title]) do
app.editor:clear_gutter_icon(line)
api.editor:clear_gutter_icon(line)
end
diagnostic_gutter[title] = nil
end
@@ -449,7 +607,7 @@ app.editor:on_lsp_diagnostics(100, function(servers)
local group = groups[key]
local icon = severity_icons[group.severity] or "circle-alert"
if active then
app.editor:set_gutter_icon(group.line, icon)
api.editor:set_gutter_icon(group.line, icon)
gutter_lines[group.line] = true
end
errors[#errors + 1] = {
@@ -498,21 +656,21 @@ end)
diagnostics_tree:on_click(function(id)
local path, line, character = id:match("^diag:(.*):(%d+):(%d+):%d+$")
if path then
app.editor:open(path, { tonumber(line), tonumber(character) })
api.editor:open(path, { tonumber(line), tonumber(character) })
else
-- Клик по файлу: фокус в редактор, курсор не переставляем.
app.editor:open(id, {})
api.editor:open(id, {})
end
end)
app.bottom_panel:open()
api.panels.bottom:open()
-- Верхняя панель: пример лейблов и кнопок.
-- app.toolbar:add_label("Готово", nil, nil, "status", "left")
app.toolbar:add_label("UTF-8", nil, "#efefe6", "encoding", "right")
app.toolbar:add_button("Запустить", "play", nil, "run", "right", function()
-- api.panels.toolbar:add_label("Готово", nil, nil, "status", "left")
api.panels.toolbar:add_label("UTF-8", nil, "#efefe6", "encoding", "right")
api.panels.toolbar:add_button("Запустить", "play", nil, "run", "right", function()
print("[toolbar] кнопка «Запустить» нажата")
end)
app.editor:set_gutter_icon(2, "play", "#efefe6")
app.editor:set_gutter_icon(90, "play")
api.editor:set_gutter_icon(2, "play", "#efefe6")
api.editor:set_gutter_icon(90, "play")