Запись 54. От бесконечной простыни к интерактивной карте: семантическая разметка команд (OSC 133)
⏱️ Время: 30 сентября 2026, 09:00 — 11:30 (~2.5 часа)1. Повод: когда терминал становится слепой стеной текста
Любой инженер, системный администратор или разработчик рано или поздно сталкивается с одной и той же картиной: ты развернул сервис, запустил миграцию базы данных, выполнил объемный поиск или вывел логи контейнера — и окно терминала заполняется сотнями строк вывода.
В этой бесконечной «простыне» ориентироваться крайне утомительно:
- Где именно началась последняя команда, а где вывод предыдущей?
- Завершилась ли утилита успешно или упала с ошибкой где-то посередине экрана?
- Как скопировать только вывод этой конкретной команды, не возя мышью по экрану через несколько экранов с риском зацепить системные подсказки?
В открытом коде есть особый момент радости — когда разработчик другого интересного инструмента заходит в твой репозиторий и оставляет звезду на GitHub. Так случилось с проектом uxiew/terminale (быстрый кроссплатформенный эмулятор терминала на Flutter). Я перешел на страницу репозитория, чтобы познакомиться с наработками коллеги, и мы сели с ИИ детально сопоставить его сильные стороны с Shellit: что уже реализовано у нас, а в чём коллеги нашли изящные эргономические находки.
И здесь, при внимательном взгляде на древо коммитов и форков на GitHub, открылась важнейшая деталь: репозиторий uxiew является форком оригинальной разработки итальянского инженера Фабрицио Ла Роза — fbrzlarosa/terminale.
Здесь хочется сделать важное отступление об открытом коде. Я не программист по профессии и создаю архитектуру Shellit в диалоге с искусственным интеллектом, направляя инженерные требования от сценариев реального использования. Но уважение к первоисточникам в опенсорс-сообществе — это вопрос профессиональной этики. Наша искренняя и главная благодарность направляется именно Фабрицио Ла Роза (fbrzlarosa) как создателю оригинальной концепции и архитектуры Terminale, который первым реализовал идею нативного терминала на Flutter, а также форку uxiew за активное развитие проекта, благодаря которому состоялось это вдохновляющее знакомство.
Изучив современные тенденции, мы поняли, что Shellit обязан получить первоклассную поддержку семантической разметки команд.
2. Под капотом: протокол OSC 133 (FinalTerm / Shell Integration)
В терминальных стандартах существует изящный протокол, используемый в VS Code, iTerm2 и Warp — управляющие последовательности OSC 133:
\x1b]133;A\x07— начало строки приглашения (Prompt Start).\x1b]133;B\x07— завершение ввода и запуск команды (Command Executed).\x1b]133;C\x07— начало полезного вывода утилиты (Output Start).\x1b]133;D;<exit_code>\x07— команда завершена с явным кодом возврата (0 — успех, $\ne 0$ — ошибка).
Мы реализовали архитектурный модуль ShellIntegrationController в пакете packages/terminal_ui, перехватывающий эти маркеры прямо из потока VT-последовательностей эмулятора xterm.dart.
На базе этого ядра родилась комплексная система навигации:
- Левые маркеры промпта (
ShellGutterMarkersOverlay): В левом отступе экрана напротив каждой введенной команды загорается аккуратная точка:- 🟢 Изумрудный кружок — команда завершилась успешно (exit code 0).
- 🔴 Красный кружок — команда упала с ошибкой (exit code $\ne 0$).
- 🔷 Бирюзовый пульсирующий индикатор — команда прямо сейчас выполняется в фоне (
Running...).
- Копирование вывода в один клик: Клик по точке мгновенно копирует весь чистый стандартный вывод команды в буфер обмена без заголовка промпта.
- Миникарта на полосе скроллбара (
ShellCommandMarkersOverlay): На треке прокрутки отображаются цветные засечки, позволяющие визуально оценить успешность команд по всей глубине истории сессии. - Тумблер в тулбаре: Иконка
[ ⚡ Markers ]в верхнем меню позволяет одним нажатием скрыть или показать разметку, если требуется абсолютно минималистичный вид.
Запись 55. Подводные камни PowerShell и точный расчет экранных координат
⏱️ Время: 30 сентября 2026, 12:15 — 12:40 (~25 минут)Когда мы запустили первые тесты на реальной машине под Windows в PowerShell, интерфейс преподнес неожиданные сюрпризы:
- Команда с ошибкой окрасилась в зеленый цвет: Команда
Get-Item NonExistentзавершилась ошибкой, но кружок рядом с ней загорелся оптимистичным зеленым светом! - Плавающие артефакты при коротком выводе: На чистом старте, когда в терминале было всего 3 строчки, справа отображались засечки несуществующего скроллбара, а перед боковой панелью проступала серая вертикальная черта.
- Нажатие на кнопку в тулбаре ломало ввод: Клик по бейджу вставлял код инициализации прямо в командную строку шелла, вызывая многострочный ввод
>>.
Разбор причин и устранение дефектов (BUG-047)
- Коварство
$?в PowerShell: В настроечной функции промпта строка$e = [char]27;выполнялась успешно, автоматически сбрасывая переменную статуса$?в$True. Из-за этого последующая проверка считала любую упавшую команду успешной. Мы перенесли захват статуса в самое первое выражение:
$c = if (-not $global:?) { 1 } elseif ($LASTEXITCODE) { $LASTEXITCODE } else { 0 };. - Точные экранные координаты: Вместо приблизительной геометрии позиционирование кружков переведено на прямой запрос координат рендера:
RenderTerminal.getOffset(CellOffset(0, block.promptLine)).dy. - Адаптивный скроллбар: Миникарта на полосе прокрутки теперь активируется исключительно тогда, когда буфер терминала реально выходит за пределы высоты окна (
lines.length > viewHeight).
Запись 56. Пошаговая навигация: физический скролл от метки к метке и измерение времени выполнения
⏱️ Время: 30 сентября 2026, 13:10 (~20 минут)Следующим шагом стала эргономика перехода по истории. Когда в терминале десятки команд, пользователь хочет листать историю не колесиком мыши, а привычными шорткатами Alt + ↑ (к предыдущей команде) и Alt + ↓ (к следующей).
И здесь обнаружились два тонких момента:
- Залипание скролла на дне окна: Находясь в самом низу терминала, нажатие
Alt + ↑не приводило ни к какому эффекту. Скролл оставался на месте. - Отсутствие времени выполнения: Подсказка маркера показывала
Success (exit 0), но тайминг[1.2s]отсутствовал.
Инженерное решение (BUG-048)
- Причина залипания: Предыдущий расчет зажимал целевую координату
(promptLine * lineHeight)черезclamp(0.0, maxScrollExtent). Для команд, находящихся в пределах нижнего экрана, оффсет зажимался вmaxScrollExtent— ровно то значение, где скролл уже находился! ВызовanimateToпытался сместить окно на 0 пикселей.
Решение: Алгоритм_jumpToPreviousCommandпереработан на поиск контрольной точки, пиксельная позиция которой строго меньше текущей видимой зоны (targetPixels < currentOffset - 1.0). Теперь каждый кликAlt + ↑гарантированно сдвигает экран вверх к предыдущему промпту, аAlt + ↓— вниз. - Тайминг выполнения (
notifyCommandStarted): Оболочки запускают функциюpromptуже после завершения работы утилиты. Мы перехватили нажатие клавишиEnterв терминале и начали фиксировать точный момент старта (startTime = DateTime.now()). Теперь наведение мыши на маркер раскрывает исчерпывающую информацию:
Command #3: Success (exit 0) [1.4s].
Запись 57. Оживление локальных терминалов: как спящий режим вкладок подружился с локальными консолями PowerShell, CMD и WSL
⏱️ Время: 30 сентября 2026, 14:00 (~25 минут)1. Контекст: когда «спящие» вкладки отказываются просыпаться
В версии 0.8.6 мы внедрили долгожданное сохранение вкладок рабочего пространства: закрываешь Shellit, открываешь снова — и все сессии аккуратно восстанавливаются на своих местах в режиме бережного ожидания («lazy disconnected»). Нажал кнопку подключения — и сессия мгновенно подхватывается.
Для удаленных SSH-хостов и SFTP эта схема работала безотказно. Но стоило пользователю перезапустить приложение с открытой вкладкой локального терминала (PowerShell, командная строка Windows или WSL), как начинались странности:
- Восстановленная вкладка показывала статус ожидания, но клик по кнопке «Подключиться» (
Connect) не приводил вообще ни к какому результату: процесс шелла не стартовал, консоль оставалась немой. - В карточке ожидания локальной вкладки отображались неуместные сетевые фразы: «Resolving endpoint…», шаги расшифровки паролей сейфа и SSH-рукопожатия — для локального PowerShell на собственном компьютере!
- Пункт контекстного меню вкладки «Reconnect» также игнорировал локальные терминалы.
2. Подводные камни: потеря профиля и скрытая проверка host == null
Глубокий разбор логики десериализации сессий выявил цепочку незаметных архитектурных нестыковок:
- Потеря ссылки на профиль шелла: При сохранении состояния рабочего пространства идентификатор профиля
localShellIdчестно записывался в БД, но при вызовеrestoreWorkspaceTabsпередавался только список удаленных хостов (allHosts). Локальные профили в метод не передавались вовсе! В результате вкладка восстанавливалась сlocalShellProfile == null, теряя свою идентичность (какой именно шелл нужно запускать — PowerShell 7, CMD или Ubuntu WSL). - Сетевое допущение
if (tab.host == null) return;: Обработчик нажатия кнопки повторного запуска_handleRetryConnectionбыл исторически написан в расчете только на удаленные серверы. Для локальных вкладок полеtab.hostестественно равноnull. Встретивnull, метод молча прерывал выполнение без единого сообщения в лог. - Отсутствие метода инъекции PTY в существующую вкладку: В контроллере
SessionManagerNotifierсуществовал методopenLocalTerminalTab, который всегда создавал новую вкладку. Способа запустить PTY-процесс в уже существующую восстановленную вкладку не было в контрактах.
3. Логика решений с ИИ (BUG-049)
Мы последовательно довели цикл восстановления локальных сессий до ума:
- Резолв локальных профилей при восстановлении:
В
restoreWorkspaceTabsдобавлена передача спискаlocalShellProfilesи системного дефолтного шеллаdefaultShellProfile. Теперь система находит точный профиль поsaved.localShellId, а если профиль был удален из ОС (например, удален дистрибутив WSL) — плавно переключается на стандартный шелл без падений. - Прямой запуск PTY в существующую вкладку (
launchLocalTerminalForTab): Реализован метод контроллера, который инициализируетLocalTerminalSession.startи бесшовно монтирует живой поток псевдотерминала прямо в ожидающую вкладку, снимая статус ожидания и очищая ошибки. - Разблокировка обработчиков запуска:
В
_handleRetryConnection, контекстном менюTopBarTabs.onReconnectTabи фоновом автоконнекте снято ограничение по хосту: приtab.type == TabType.localTerminalмгновенно вызывается старт локального терминала. - Адаптированный экран ожидания (
TerminalConnectingView): Экран ожидания локального шелла избавлен от сетевых этапов: отображается фирменный неоновый значок>_, название шелла (PowerShell / CMD), путь к исполняемому файлу и кнопка «Запустить терминал» (Icons.play_arrow_rounded). Расположение кнопок переведено наWrap, полностью исключая ошибки переполненияRenderFlex overflow.
Запись 58. Архитектура невмешательства: почему терминал должен молчать без спроса и как мы пришли к честной интеграции оболочки
⏱️ Время: 30 сентября 2026, 15:40 (~25 минут)1. Соблазн фоновой автоматизации: ловушка PTY-эхо
Когда базовая механика OSC 133 заработала, возникло естественное желание сделать её абсолютно незаметной для пользователя. Идея казалась привлекательной: при открытии SSH-вкладки к удаленному серверу клиент через полсекунды сам, в фоновом режиме, отправляет в шелл компактный хук, на лету прописывая функцию перехвата промпта. Пользователь просто открывает терминал — и маркеры уже на месте.
Но реальный запуск на боевых серверах преподнес отрезвляющий урок.
В интерактивной сессии SSH удаленный терминал работает с включенным эхо (ECHO): каждый отправленный клиентом байт сервер транслирует обратно на экран. Когда клиент отправляет многострочный скрипт в поток ввода, код начинает заворачиваться в несколько ярусов. Попытки скрыть его служебными кодами стирания (\033[1A\033[2K) очищают только последнюю строку, оставляя в окне консоли многострочные обрывки кода вместо чистого приглашения.
Хуже того: негласная отправка команд в консоль администратора противоречит культуре работы с серверной инфраструктурой. Ни один инструмент не имеет права печатать что-либо в боевой шелл без прямого согласия человека.
2. Инженерное решение (BUG-050)
Мы полностью переработали архитектуру внедрения хуков и вернули пользователю безусловный контроль:
- Полный демонтаж фоновых инъекций:
Из реестра сессий
TerminalSessionRegistryудалены все фоновые таймеры и попытки отправки скрипта вinputStream. Сессия открывается кристально чистой. - Нулевой шум по умолчанию:
Отображение маркеров в левом поле и на скроллбаре теперь выключено по умолчанию (
_showGutterMarkers = false), а кнопка[⚡ Markers]в тулбаре отображается в спокойном приглушенном сером цвете. - Модальный диалог настройки (
ShellIntegrationSetupDialog): При первом нажатии на кнопку[⚡ Markers]или через контекстное меню открывается лаконичный диалог с тремя прозрачными вариантами:- Активировать в текущей сессии: отправляет компактный хук, завершающийся немедленной командой
; clear\n. Экран сервера мгновенно очищается от эхо, оставаясь безупречно чистым, а все последующие команды сессии получают цветные маркеры статуса и хронометраж. - Установить навсегда (
~/.bashrc): аккуратно дописывает хук в конфигурационный файл пользователя на сервере с последующим применением. При последующих подключениях маркеры подхватываются автоматически. - Скопировать скрипт: копирует код в буфер обмена для ручной проверки или вставки.
- Активировать в текущей сессии: отправляет компактный хук, завершающийся немедленной командой
- Безопасная верстка без переполнений:
Внутреннее содержимое диалога защищено скролл-контейнером
SingleChildScrollView, гарантируя адаптивность на любых разрешениях экрана.
Итог акта
- Простыня терминала Shellit превратилась в умный структурированный журнал.
- Команды получили статусную индикацию, пошаговую клавиатурную навигацию и копирование вывода в один клик.
- Архитектура шелл-интеграции построена на принципе невмешательства: нулевой фоновый шум, чистое подключение и осознанный выбор пользователя.
- Все 150 тестов пакета
packages/terminal_uiуспешно пройдены. - Открыто и честно зафиксирована дань уважения сообществу Open Source — автору оригинального проекта fbrzlarosa/terminale и форку uxiew/terminale.