refactoring

This commit is contained in:
2026-10-31 01:06:21 +03:00
parent 27385a3edb
commit 19e05e7ed0
17 changed files with 2673 additions and 671 deletions
+82 -106
View File
@@ -1,128 +1,104 @@
# AGENTS.md
Instructions for AI coding agents working on this repository. Read this file
before changing code. The rules below are architectural requirements, not
suggestions.
`qode` — настольный редактор кода на GPUI (`gpui-kit`) с расширением поведения
через Lua и клиентом LSP по stdio. Этот файл описывает архитектуру и принципы,
которые нужно сохранять при изменениях.
## Project shape
## Модули
| Path | Responsibility |
| Путь | Ответственность |
| --- | --- |
| `src/api.rs` | `AppApi` / `PanelApi` — the single control surface of the application |
| `src/script.rs` | Lua engine; publishes `app`, `include`, and `require` |
| `src/keymap.rs` | Key binding parsing (`KeyBinding`) and dispatch (`KeyMap`) |
| `src/app.rs` | GPUI view, rendering, event wiring |
| `src/editor.rs` | `Workspace`, documents, tabs |
| `src/panel.rs` | `PanelState` for left/right/bottom panels |
| `src/lsp.rs` | LSP server manager |
| `src/constants.rs` | CLI arguments and script directory |
| `init.lua` | Example startup script (also a usage reference) |
| `DOCS.md` | User-facing Lua API documentation |
| `src/main.rs` | Точка входа, окно, аргументы командной строки |
| `src/constants.rs` | Константы окна, CLI и каталог сценариев |
| `src/editor.rs` | Доменная модель: документы, вкладки, языки |
| `src/api.rs` | `AppApi` / `PanelApi` / `TreeApi` — единственная поверхность управления |
| `src/script.rs` | Lua-движок, `app`, `include`, `require` |
| `src/keymap.rs` | `KeyBinding`, `KeyMap`, встроенные команды и Lua-обработчики |
| `src/app.rs` | GPUI-вид, отрисовка, события, автодополнение |
| `src/completion.rs` | Всплывающий список, `CaretPopup`, преобразования LSP-элементов |
| `src/panel.rs` | `PanelState` и блоки панелей |
| `src/tree.rs` | Абстрактное дерево для панелей |
| `src/icons.rs` | Каталог иконок Lucide |
| `src/lsp.rs` | Менеджер LSP-серверов, JSON-RPC, файловый вотчер |
| `DOCS.md`, `init.lua` | Пользовательская документация и пример конфигурации |
## 1. `AppApi` is the center of the application
## Главные принципы
- `AppApi` (in `src/api.rs`) is the one object through which the application is
controlled. All operations on documents, tabs, panels, the window, syntax
highlighting, key bindings, and LSP go through it.
- Shared state is held as `Rc<RefCell<_>>` handles (workspace, keymap, panels,
LSP manager). The UI and `AppApi` must observe the *same* instances; never
create a second copy of the state.
- Rust code that needs to act on the application should call
`AppApi::global()` (or an `AppApi` instance passed in) instead of mutating
`Workspace`, `PanelState`, `KeyMap`, or `LspManager` directly.
- Methods that are implementation details of `AppApi` stay private or are
marked `#[lua(skip)]`. Do not add a second, parallel control path.
### 1. `AppApi` — центр приложения
## 2. Every `AppApi` capability is available from Lua
- Документы, вкладки, панели, дерево, окно, подсветка, LSP и привязки клавиш
меняются только через `AppApi` (или `PanelApi` / `TreeApi`).
- Состояние живёт в общих `Rc<RefCell<_>>` / `Arc`, чтобы UI, `AppApi` и Lua
видели одни и те же объекты. Второй копии состояния быть не должно.
- Команды окна складываются в очередь и применяются в начале кадра
(`apply_window_commands`); UI и Lua не вызывают `Window` напрямую.
- Если действие пользователя меняет состояние, оно выражается методом
`AppApi`, а не логикой внутри обработчика UI. Отрисовка состояние только
читает.
- Исключение — системные кнопки окна внутри `gpui-kit::TitleBar`: их
поведение принадлежит библиотеке.
- Everything in the `impl AppApi` block annotated with
`#[mlua::userdata_impl]` is automatically exposed as `app:method(...)`.
- When adding functionality, add it to `AppApi` and expose it to Lua at the same
time. An operation that a user can trigger in the UI must also be callable
from a Lua script.
- Use `#[lua(infallible)]` where the method cannot fail, and return
`mlua::Result` / convert errors to `mlua::Error::RuntimeError` otherwise.
- Generic Rust methods are not exported. Add a thin non-generic Lua wrapper,
following the existing pattern: `save` / `lua_save`, `open` / `lua_open`,
`set_active_text` / `lua_set_active_text`, `set_language` /
`lua_set_language`.
- Use `#[lua(name = "...")]` when the Lua name should differ from the Rust
name (for example `active_tab_index` → `active_tab`).
- Lua API naming: lower `snake_case`, tabs and indices are zero-based, panels
are accessed as `app.left_panel`, `app.right_panel`, `app.bottom_panel`.
### 2. Lua повторяет возможности API
## 3. All UI actions and keyboard shortcuts go through the API
- Публичные методы `impl AppApi` с `#[mlua::userdata_impl]` доступны как
`app:method(...)`. Добавляя возможность, сразу добавляйте и Lua-метод.
- Имена — `snake_case`; вкладки и индексы нумеруются с нуля; панели —
`app.left_panel`, `app.right_panel`, `app.bottom_panel`.
- Для методов, которые не должны попадать в Lua, используется `#[lua(skip)]`.
Для generic-методов добавляется тонкая не-generic Lua-обёртка.
- `DOCS.md` и комментарии в `init.lua` обновляются в том же изменении, что и
публичный API. Документация описывает фактическое поведение, без
«запланированных» методов.
### UI actions
### 3. Клавиатура — только через `KeyMap`
- UI event handlers and rendered controls must invoke `AppApi` methods, not
manipulate state directly. Examples:
- window close button → `app:close()` (i.e. `AppApi::close`)
- minimize button → `app:minimize()`
- maximize / restore → `app:toggle_maximize()` or
`app:set_maximized(bool)`; read the current state with `app:is_maximized()`
- tab close button → `app:close_tab(index)`
- "new tab" button → `app:open_tab()`
- panel toggles → `app.right_panel:toggle()` and friends
- Rendering code may *read* state (for example `workspace.borrow()` to draw the
active document), but must not mutate it. Mutations belong to `AppApi`.
- Window operations are queued as `WindowCommand`s and applied by
`apply_window_commands`; keep this mechanism as the only way the UI and Lua
request window changes.
- If an external component (for example `gpui_kit::component::TitleBar`)
performs an action internally, do not leave it as a separate control path:
intercept the action or replace the component so that it calls `AppApi`.
- Встроенные привязки (`Ctrl/Cmd+S`, `Ctrl/Cmd+Space`, навигация по списку
автодополнения) регистрируются в `KeyMap` и могут быть заменены или удалены
из Lua через `app:map_key` / `app:map_completion_key`.
- `KeyMap` имеет два слоя: глобальный и `completion`. Обработчик может быть
Lua-функцией или встроенной командой.
- Перехватчик клавиш выполняется раньше действий редактора и используется
только для слоя автодополнения; глобальные привязки обрабатываются на корне
вида. Этот порядок менять нельзя.
- Текстовый ввод редактора — не привязка клавиши.
### Keyboard shortcuts
### 4. Модель отделена от GPUI
- Every key binding must be registered through `AppApi::map_key` /
`KeyMap`. There must be no hard-coded shortcuts that scripts cannot inspect,
override, or remove.
- Existing hard-coded checks such as `is_save_shortcut` and
`is_completion_shortcut` in `src/app.rs` are technical debt: when touching
this area, move those defaults into the `KeyMap` mechanism so they can be
rebound from Lua.
- Default bindings should be installed through the same mechanism that scripts
use (or a documented equivalent), so that `app:map_key(...)` can replace them.
Rebinding the same chord must replace the previous handler.
- Dispatch order matters: the key interceptor runs before key bindings, and
`dispatch_keymap` is used for both key down and key up (`<...>` vs `!key`).
Preserve this behavior.
- Any `KeyMap` change must keep the documented chord syntax working:
`a`, `1`, `!a`, `<S-c>`, `<C-z>`, `<A-!r>`, named keys (case-insensitive).
- Editor text input itself is not a shortcut, but all app-level commands
(save, close, panels, completion popup navigation, etc.) must be remappable.
- `editor.rs`, `panel.rs` и `tree.rs` не знают о `Window` и `Context`.
- Вид переносит изменения модели в виджет по `Workspace::revision`; набор
текста в редакторе ревизию не трогает, чтобы не сбрасывать курсор.
- Инвариант `Workspace`: открыт хотя бы один документ. Поэтому `active()`
возвращает документ без `Option`.
- Встроенный старт — одна пустая вкладка. Примеры и демонстрационные
документы не должны попадать в код приложения; `init.lua` — единственное
место для примеров.
## 4. Keep `DOCS.md` in sync
### 5. LSP работает в фоне
`DOCS.md` is the source of truth for the Lua API. In the **same change** that
modifies the public surface, update the docs:
- `LspManager` общий (`Arc`) для вида, API и фоновых задач.
- Процессы, чтение stdout/stderr и файловый вотчер живут в отдельных потоках;
ответы приходят через каналы и ожидаются в `background_executor`, а не в
кадре UI.
- Какие серверы запускать, решает Lua; менеджер отвечает только за жизненный
цикл, JSON-RPC и синхронизацию документов.
- added/removed/renamed `AppApi` or `PanelApi` methods;
- changed arguments, return values, or error behavior;
- new supported languages, LSP configuration fields, CLI flags, or
`include`/`require` behavior;
- changed default shortcuts or their syntax.
## Соглашения
Also update the usage comments in `init.lua` when they describe behavior that
changed. Documentation must describe the code as it actually behaves — no
aspirational APIs.
- Код, комментарии и пользовательская документация — на русском.
- Публичные элементы Rust документируются `///`.
- Новые зависимости добавляются только при реальной необходимости; сначала
используются возможности `gpui-kit`, `mlua` и уже подключённых крейтов.
- Изменения должны быть локальными и в стиле окружающего кода. Не добавляйте
параллельные пути управления и не оставляйте мёртвый код.
## Conventions
## Проверка
- Keep changes focused and consistent with the existing style; comments and
user-facing documentation are written in Russian.
- Rust API docs use `///`; write them for every new public method.
- Validate with `cargo fmt` and `cargo test`, then `cargo build` if behavior
changed. Do not commit unless explicitly asked.
Перед завершением изменения:
## Checklist
```sh
cargo fmt
cargo test
cargo build
```
Before finishing a change, verify:
- [ ] The new behavior is implemented in `AppApi`, not in a UI handler.
- [ ] The operation is callable from Lua and has a documented signature.
- [ ] No new hard-coded keyboard shortcut bypasses `KeyMap`.
- [ ] UI controls call the corresponding `AppApi` method.
- [ ] `DOCS.md` (and `init.lua` comments if needed) are updated.
- [ ] `cargo fmt` and `cargo test` pass (or explain why they could not run).
Если какую-то команду выполнить нельзя, это нужно явно указать в ответе.