Add TERMINAL_AUDIT.md — full audit plan with status tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
182
TERMINAL_AUDIT.md
Normal file
182
TERMINAL_AUDIT.md
Normal file
@@ -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("<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 по двойному нажатию) |
|
||||||
Reference in New Issue
Block a user