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

183 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.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 по двойному нажатию) |