komiterm/src/term.rs
2026-07-17 09:30:05 +03:00

111 lines
5.1 KiB
Rust
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.

use alacritty_terminal::event::{Event, EventListener};
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::term::{Config, Term};
use alacritty_terminal::vte::ansi::Processor;
use alacritty_terminal::Grid;
/// EventListener для alacritty_terminal — сюда прилетают события вроде
/// "заголовок изменился", "нужен звонок" и т.п. Для MVP просто складываем
/// в очередь, ui.rs потом разбирает.
#[derive(Clone)]
pub struct EventProxy(std::sync::mpsc::Sender<Event>);
impl EventListener for EventProxy {
fn send_event(&self, event: Event) {
let _ = self.0.send(event);
}
}
/// `Term::new`/`Term::resize` принимают не размер в пикселях (это отдельное
/// понятие `WindowSize`, для другого — сообщать PTY реальный размер окна
/// в пикселях, нам не нужное), а нечто, реализующее трейт `Dimensions` —
/// то есть размер именно в колонках/строках символьной сетки. Такого
/// готового публичного типа под рукой нет, поэтому просто заводим свой.
struct GridSize {
cols: usize,
rows: usize,
}
impl Dimensions for GridSize {
fn total_lines(&self) -> usize {
self.rows
}
fn screen_lines(&self) -> usize {
self.rows
}
fn columns(&self) -> usize {
self.cols
}
}
/// Держит состояние эмулируемого терминала: сетку ячеек с атрибутами,
/// курсор, режимы (alt screen, wrap и т.д.) и VTE-парсер поверх неё.
pub struct TerminalState {
term: Term<EventProxy>,
parser: Processor,
/// События от alacritty_terminal (смена заголовка через OSC, звонок,
/// clipboard-запросы и т.п.) — очередь собирается, но пока никто её не
/// читает: например, обновление имени таба по Event::Title ещё не
/// подключено к ui.rs/app.rs. Оставлено как задел на будущее, а не
/// удалено, поэтому и глушим dead_code явно, а не молчим о причине.
#[allow(dead_code)]
events: std::sync::mpsc::Receiver<Event>,
}
impl TerminalState {
pub fn new(cols: u16, rows: u16) -> Self {
let (tx, rx) = std::sync::mpsc::channel();
let proxy = EventProxy(tx);
let size = GridSize { cols: cols as usize, rows: rows as usize };
let term = Term::new(Config::default(), &size, proxy);
Self { term, parser: Processor::new(), events: rx }
}
/// Скормить парсеру очередную порцию байт, пришедших от транспорта.
/// Именно тут живёт вся сложность интерпретации escape-последовательностей —
/// alacritty_terminal берёт её на себя целиком.
pub fn advance(&mut self, bytes: &[u8]) {
for byte in bytes {
self.parser.advance(&mut self.term, *byte);
}
}
pub fn resize(&mut self, cols: u16, rows: u16) {
let size = GridSize { cols: cols as usize, rows: rows as usize };
self.term.resize(size);
}
/// Текущее содержимое сетки для рендера в ratatui.
pub fn grid(&self) -> &Grid<alacritty_terminal::term::cell::Cell> {
self.term.grid()
}
/// Позиция курсора в системе координат видимого вьюпорта: (line, column).
/// line может быть отрицательной в теории (если вьюпорт проскроллен в
/// историю), но у активного текстового курсора такого не бывает —
/// проверка `row_idx as i32 == cursor.0` в ui.rs всё равно безопасна.
pub fn cursor_position(&self) -> (i32, usize) {
let point = self.term.grid().cursor.point;
(point.line.0, point.column.0)
}
/// Ширина/высота видимой области терминала — то, что реально нужно
/// пройти циклом в рендере, а не полагаться на то, что area в ratatui
/// всегда совпадает по размеру (на случай рассинхрона после resize).
pub fn columns(&self) -> usize {
self.term.columns()
}
pub fn screen_lines(&self) -> usize {
self.term.screen_lines()
}
/// См. комментарий у поля `events` — задел на будущее (обновление
/// заголовка таба, звонок и т.п.), пока никем не вызывается.
#[allow(dead_code)]
pub fn drain_events(&mut self) -> Vec<Event> {
self.events.try_iter().collect()
}
}