Skip to content

Terminal Engine ru

Se.Cherkasov edited this page Aug 23, 2026 · 1 revision

Движок терминала

English · Русский

Эмулятор написан в commonMain — чистый Kotlin над сеткой ячеек, без платформенной терминальной библиотеки, поэтому десктоп и Android рисуют одно и то же состояние.

Конвейер

flowchart LR
  CH[ShellChannel<br/>ssh · telnet · serial · mosh · pty] --> SES[TerminalSession<br/>цикл корутины, размер PTY]
  SES --> EMU[TerminalEmulator<br/>сетка, курсор, режимы]
  EMU --> SNAP[снимок для отрисовки<br/>TermSnapshotRow]
  SNAP --> UI[TerminalScreenState → Compose]
  SES --> REC[SessionRecorder<br/>asciicast v2]
  EMU --> MARK[TerminalStepMark<br/>границы шагов ранбука]
Loading

Сетка

Тип Что моделирует
TermCell ячейка: текст (String, чтобы влезали астральные символы и комбинирующие последовательности), стиль, ширина
TermWidth Single / Wide (первая ячейка пары CJK или эмодзи) / Continuation
TermStyle полный набор атрибутов SGR; подчёркивание — форма (4:x: двойное, волнистое, точечное, штриховое) плюс свой цвет
TermColor Default, Indexed (палитра xterm 0–255) или Rgb — без привязки к UI; рендерер отображает в палитру темы
TermRow ячейки плюс флаг wrapped: true — строку разрезал автоперенос (DECAWM), честный \n оставляет false
TermSnapshotRow неизменяемая строка снимка; равные снимки позволяют неизменившемуся экрану пропустить рекомпозицию

Reflow

TerminalReflow — чистая функция над списками строк: мягко перенесённые физические строки склеиваются в логические и заново режутся по новой ширине, курсор следует за своим текстом, нижние строки становятся сеткой, остальные — скроллбэком (с обрезкой по лимиту). Поскольку состояния эмулятора у него нет, он тестируется и напрямую, и через изменение размера.

Вспомогательные части

Задача Где
Разбор SGR SgrParser
Метрики глифов (для попадания мышью и раскладки) CharMetrics
Выделение и его фрагменты TerminalSelection
Поиск по скроллбэку TerminalSearch
Репортинг мыши TerminalMouse
История команд между сессиями CommandHistory, TerminalHistoryStore
Автодополнение в стиле fish AutocompleteEngine, CommandSuggestions, FuzzyMatch
Безопасность ввода SafeTerminalInput
Подсветка синтаксиса terminal/highlight/

Запись

SessionRecorder пишет asciicast v2: строка JSON-заголовка и по строке [time, "o", data] на каждый кусок вывода, поэтому запись играет в любом плеере asciinema. Вся запись держится в памяти и пишется на диск один раз в конце: вывод терминала недоверенный и неограниченный (cat большого файла не кончается), поэтому её ограничивают maxEvents и maxBytes, а truncated честно сообщает о достижении лимита. В записи попадает всё, что напечатал сервер, поэтому она сохраняется только явным экспортом.

Метки шагов

Ранбукам нужно знать границы вывода шага, не печатая протокол на экране. TerminalStepMark задаёт приватный OSC-код OSC 8375 (S,K в десятичном ASCII):

  • OSC 8375 ; <токен> ; BEL — здесь начинается вывод шага;
  • OSC 8375 ; <токен> ; <код возврата> BEL — шаг завершился с этим статусом.

Эмулятор реагирует на метку только для шага, объявленного клиентом через expectStep: обычная сессия ничего не паркует, а хост, повторивший подсмотренный на экране токен, не может закрыть окно захвата, которое этот клиент не открывал. TerminalEchoFilter прячет эхо набранного клиентом. Слой ранбуков лишь строит пробы (runbook/RunbookMarker).

То, что говорит метка, говорит оболочка хоста: скомпрометированный хост может выдать любой статус. Ранбуки не являются границей безопасности над враждебным хостом.

Отрисовка

ui/terminal/TerminalScreenState держит состояние экрана для Compose, включая поиск по скроллбэку. Геометрия, выведенная из настроек (размер шрифта → попадание мышью), пересчитывается при изменении настройки, а не кэшируется навсегда, и на рекомпозиции ничего не аллоцируется.

Clone this wiki locally