diff --git a/TERMINAL_AUDIT.md b/TERMINAL_AUDIT.md new file mode 100644 index 0000000..c00cf68 --- /dev/null +++ b/TERMINAL_AUDIT.md @@ -0,0 +1,182 @@ +# 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("", 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.py` — `feed()` +- 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.py` — `send()` +- При 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 по двойному нажатию) |