Files
server-manager/TERMINAL_AUDIT.md
2026-02-23 15:06:20 -05:00

9.7 KiB
Raw Blame History

Terminal Audit — Plan & Status

Аудит терминала ServerManager для полной совместимости с TUI-программами (mc, vim, htop, nano, less, claude, opencode и др.) Цель: уровень MobaXterm / PuTTY


КРИТИЧНО (без этого TUI не работают)

1. [DONE v1.5.3] 256-color / truecolor рендеринг

  • Файл: gui/widgets/terminal_widget.py_color_tag(), _make_tags()
  • pyte возвращает hex-строки ("ff0000") для 256/truecolor
  • Старый код распознавал только 16 именованных ANSI-цветов → все остальные игнорировались
  • Решение: динамическое создание тегов tkinter для любого hex-цвета с кешем (лимит 4096)

2. [DONE v1.5.3] Reverse video + strikethrough

  • Файл: gui/widgets/terminal_widget.py_render(), _make_tags()
  • char_data.reverse и char_data.strikethrough не извлекались из буфера
  • mc/vim/htop активно используют reverse для выделения/меню
  • Решение: добавлены в кортеж атрибутов, reverse swap fg/bg в _make_tags

3. [DONE v1.5.3] Alternate screen buffer (smcup/rmcup)

  • Файл: gui/widgets/terminal_widget.py — класс _Screen
  • pyte 0.8.x не реализует alternate buffer (private modes 47, 1047, 1049)
  • Все TUI-программы используют alternate screen → при выходе экран был мусором
  • Решение: подкласс _Screen(pyte.Screen) с _enter_alt() / _exit_alt(), сохраняет/восстанавливает buffer + cursor

4. [DONE v1.5.3] write_process_input → SSH bridge

  • Файл: gui/widgets/terminal_widget.py_Screen.write_process_input()
  • pyte вызывает write_process_input() для DA/DSR ответов (Device Attributes, Cursor Position Report)
  • Без этого программы зависали ожидая ответа от терминала
  • Решение: переопределён метод → _write_cb_send() → SSH

5. [DONE v1.5.3] Alt+key комбинации

  • Файл: gui/widgets/terminal_widget.py_on_key()
  • Alt+1/2 для переключения панелей mc, Alt+B/F для readline, Alt+key для nano
  • Не обрабатывались вообще
  • Решение: проверка event.state & 0x20000 (Alt на Windows), отправка ESC + key

6. [DONE v1.5.3] Shift+Arrow / Ctrl+Arrow (модификаторы)

  • Файл: gui/widgets/terminal_widget.py_on_key(), _xterm_modifier()
  • Ctrl+Left/Right для навигации по словам, Shift+Arrow для выделения
  • Не было поддержки модификаторов → отправлялись обычные стрелки
  • Решение: xterm modifier encoding ESC[1;{mod}A, функция _xterm_modifier()

7. [DONE v1.5.3] Ctrl+A блокировался

  • Файл: gui/widgets/terminal_widget.py
  • bind("<Control-a>", lambda e: "break") съедал Ctrl+A
  • Ctrl+A = tmux prefix, readline home, select all в nano
  • Решение: убран отдельный bind, Ctrl+A проходит через общий ctrl-handler → \x01

8. [DONE v1.5.2] DECCKM (Application Cursor Keys)

  • Файл: gui/widgets/terminal_widget.py
  • mc/vim/htop включают ?1h (DECCKM) → стрелки должны слать ESC O A вместо ESC [ A
  • Решение: проверка _DECCKM in self._screen.mode, словарь _APP_CURSOR_MAP

ВАЖНО (для стабильной работы)

9. [DONE v1.5.3] Shift+Tab (backtab)

  • Файл: gui/widgets/terminal_widget.py_on_key()
  • ESC[Z — обратная табуляция, нужна в mc и для reverse completion
  • Решение: обработка ISO_Left_Tab и Tab + Shift

10. [DONE v1.5.3] Bracketed paste mode

  • Файл: gui/widgets/terminal_widget.py_on_ctrl_v()
  • Без обёртки ESC[200~...ESC[201~ вставка в vim/nano добавляла auto-indent мусор
  • Решение: проверка _BRACKETED_PASTE in screen.mode, оборачивание при вставке

11. [DONE v1.5.3] Cursor hidden (DECTCEM)

  • Файл: gui/widgets/terminal_widget.py_render()
  • htop/vim скрывают курсор при перерисовке → курсор-блок рисовался зря
  • Решение: if is_cursor_row and not cursor_hidden

12. [DONE v1.5.3] Incremental UTF-8 decoder

  • Файл: gui/widgets/terminal_widget.pyfeed()
  • SSH мог разрезать multi-byte UTF-8 → mojibake
  • Решение: codecs.getincrementaldecoder("utf-8")("replace")

13. [DONE v1.5.3] Mouse tracking

  • Файл: gui/widgets/terminal_widget.py_on_mouse_*
  • Клики в mc панелях, htop элементах, vim mouse mode
  • Поддержка: basic (1000), button-event (1002), any-event (1003), SGR (1006)

14. [DONE v1.5.3] pyte dirty set для оптимизации

  • Файл: gui/widgets/terminal_widget.py_render()
  • Старый код сравнивал каждую строку с prev_buffer
  • Решение: используем screen.dirty для пропуска чистых строк

15. [DONE v1.5.3] Thread safety: session races

  • Файл: gui/tabs/terminal_tab.py
  • _session читалась из main thread и писалась из SSH thread
  • Решение: все записи _session через self.after(0, ...), локальные ref в _send_to_shell

16. [DONE v1.5.3] _on_disconnected из неправильного потока

  • Файл: gui/tabs/terminal_tab.py
  • Вызывался из SSH read thread, менял _session = None
  • Решение: вся логика обёрнута в self.after(0, _handle)

17. [DONE v1.5.3] Ctrl+special chars

  • Файл: gui/widgets/terminal_widget.py_on_key()
  • Ctrl+[ → ESC, Ctrl+\ → \x1c, Ctrl+] → \x1d, Ctrl+Space → NUL
  • Не обрабатывались

18. [DONE v1.5.4] Smart Ctrl+C

  • Одинарный Ctrl+C с выделением → копирует
  • Одинарный Ctrl+C без выделения → SIGINT
  • Двойной Ctrl+C (<300мс) → всегда SIGINT
  • Защита от случайного убийства программы

19. [DONE v1.5.3] Mousewheel smooth scroll

  • В alternate screen → 3 стрелки вместо PageUp/Down
  • С mouse tracking → mouse wheel events (btn 64/65)

ЖЕЛАТЕЛЬНО (полировка)

20. [ ] Wide characters (CJK)

  • Файл: gui/widgets/terminal_widget.py_render()
  • CJK символы занимают 2 колонки, pyte ставит stub во вторую
  • Без обработки → смещение колонок после wide char
  • Решение: проверять wcwidth(), пропускать stub-ячейки
  • Приоритет: низкий (актуально для CJK-пользователей)

21. [ ] Scrollback buffer

  • Файл: gui/widgets/terminal_widget.py
  • Нет истории: вывод выше экрана потерян
  • Решение: использовать pyte.HistoryScreen как базу для _Screen
  • Требует рендеринг scrollback + mousewheel навигация
  • Приоритет: средний (удобство для обычных команд)

22. [DONE v1.5.3] Numpad keys

  • Файл: gui/widgets/terminal_widget.py_on_key()
  • KP_0..9, KP_Add, KP_Subtract и др.

23. [DONE v1.5.3] Resize debounce 200ms → 100ms

  • Файл: gui/widgets/terminal_widget.py_on_configure()

24. [DONE v1.5.3] Send errors → disconnect

  • Файл: core/ssh_client.pysend()
  • При OSError вызывается on_disconnect вместо тихого проглатывания

25. [DONE v1.5.3] Right-click context menu

  • Copy / Paste / Select All
  • Как в MobaXterm / Windows Terminal

26. [DONE v1.5.3] Double-click word select

27. [ ] Ctrl+Shift+C/V стиль Windows Terminal

  • [DONE] Ctrl+Shift+C → копирование
  • [DONE] Ctrl+Shift+V → вставка
  • Возможно добавить визуальный feedback при копировании (flash)

28. [ ] TERM variable alignment

  • Shell открывается с term="xterm-256color", но pyte не поддерживает все xterm features
  • Пока OK, но если будут проблемы — можно сменить на "xterm"

29. [ ] Font fallbacks для Unicode

  • Consolas покрывает box-drawing, но не все Unicode блоки
  • Tkinter не поддерживает font fallback stacks
  • Приоритет: низкий

Статистика

Статус Количество
DONE 26
TODO 3

Версии релизов

Версия Изменения
v1.5.1 Убран LNM, дебаунсинг рендера, батчинг данных, recv 64KB
v1.5.2 DECCKM, thread-safe queue
v1.5.3 Полная переработка: alt buffer, 256/truecolor, reverse, mouse, DA/DSR, Alt+key, modifiers, bracketed paste, UTF-8, dirty optimization, context menu
v1.5.4 Smart Ctrl+C (copy/SIGINT по двойному нажатию)