111 lines
5.1 KiB
Rust
111 lines
5.1 KiB
Rust
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()
|
||
}
|
||
}
|