Перейти к содержанию

Архитектура

Общая форма

  Neovim              bugsaur (один процесс)                адаптер        программа
 ┌────────┐   IPC   ┌─────────────────────────────┐  DAP   ┌─────────┐   ┌─────────┐
 │  Lua   │◄───────►│ nvim-ipc  →  debugger-core  │◄──────►│ dlv     │──►│ ваш     │
 │ source │  Unix   │              ↕              │  TCP / │ codelldb│   │ код     │
 │frontend│  сокет  │           debugger-ui       │  stdio │ debugpy │   │         │
 └────────┘         └─────────────────────────────┘        └─────────┘   └─────────┘

Всё внутри средней коробки — один процесс. Окно не отдельная программа, а редактора в пути отладки нет вовсе.

Кто чем владеет

Владелец Чем владеет
debugger-core состоянием сессии — единственный источник истины о том, как сейчас обстоят дела
dap-client соединением с адаптером и запросами в полёте
адаптер отлаживаемой программой и всем знанием о языке
редактор набором точек останова файла и показом исходника
debugger-ui тем, как состояние отображается, и ничем — тем, каково оно

Отсюда два правила, а вместе с ними и большая часть структуры кода.

Состояние решает только debugger-core. Интерфейс рисует проекцию, редактор шлёт намерения. Ни тот, ни другой не меняют модель напрямую.

Язык интерпретирует только адаптер. Bugsaur никогда не разбирает значение, не угадывает тип и не переписывает тело запроса. Там, где специфики адаптера не избежать, она живёт в каталоге адаптеров как данные, а не как ветвление в общем коде.

Цикл команд и событий

debugger-core — это редьюсер, а не контроллер:

команда  →  reduce(состояние, команда)  →  эффекты
событие  →  reduce(состояние, событие)  →  новое состояние + эффекты
  • Команды — это намерения: продолжить, шагнуть, вычислить, развернуть переменную. Они приходят от интерфейса или от редактора.
  • События — это факты: адаптер остановился, появился поток, запрос ответил.
  • Эффекты — что должно произойти дальше: отправить DAP-запрос, попросить фронтенд открыть файл, поднять окно.

Редукция чистая, и именно поэтому модель состояния тестируется без адаптера, редактора и окна — см. crates/debugger-core/tests/.

Поколения остановок

У каждой остановки есть номер поколения, и это механизм, который не пускает на экран устаревшие данные.

Как только программа сдвинулась, поколение растёт. Ответы, пришедшие позже — страница переменных, запрошенная до шага, или слишком долгий scope, — отбрасываются, а не показываются. Значение с прошлой остановки рядом с текущей строкой было бы хуже, чем отсутствие значения.

Тем же поколением ограничен кэш переменных: поэтому разворачивание дерева дёшево внутри одной остановки и начинается заново после следующей.

Фазы сессии

idle → building → starting → running ⇄ stopped → terminated
                                   ↘ failed

Фаза building существует потому, что часть программ Bugsaur компилирует сам, до старта адаптера — измерения, которые к этому привели, есть в Языки → Go. Неудачная сборка до адаптера не доходит.

Окно — это представление

debugger-ui построен на egui и никакой властью не обладает. Он рисует проекцию состояния — уплощённый виртуализированный список видимых строк — и превращает ввод в команды.

Два следствия, заметных пользователю:

  • перетащенные мышью панели не сохраняются: раскладка строится из конфигурации на каждый запуск;
  • точку останова нельзя снять из окна, потому что владелец у неё — редактор.

Где читать дальше

Тема Страница
Слой протокола Модель DAP
Граница с редактором IPC
Почему принято именно такое решение ADR