blob: 4aa17ea793ef00098175728f7838784241ace6bc [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
use alacritty_terminal::{
event::{Event, EventListener},
grid::GridCell,
vte::ansi::*,
};
use unicode_width::UnicodeWidthChar;
const MAX_REPLIES: usize = 1024 * 1024;
#[derive(Default)]
pub(super) struct ReplyBuffer {
text: String,
failed: bool,
}
#[derive(Clone)]
pub(super) struct Events(pub Rc<RefCell<ReplyBuffer>>);
impl EventListener for Events {
fn send_event(&self, event: Event) {
// UI effects (including OSC clipboard/title requests) never escape the
// headless parser. Only protocol replies can reach the native PTY.
if let Event::PtyWrite(text) = event {
let mut replies = self.0.borrow_mut();
if replies.text.len() + text.len() > MAX_REPLIES {
replies.failed = true;
} else if !replies.failed {
replies.text.push_str(&text);
}
}
}
}
pub(super) struct State {
pub term: Term<Events>,
events: Events,
pub truncated: bool,
pub last_alternate: Option<std::sync::Arc<maka_agent_facts::terminal::display::Alternate>>,
pub mouse: MouseTracking,
pub encoding: MouseEncoding,
failed: bool,
region: std::ops::Range<usize>,
}
impl State {
pub fn new(term: Term<Events>, events: Events) -> Self {
let rows = term.screen_lines();
Self {
term,
events,
truncated: false,
last_alternate: None,
mouse: MouseTracking::None,
encoding: MouseEncoding::Default,
failed: false,
region: 0..rows,
}
}
pub fn failed(&self) -> bool {
self.failed || self.events.0.borrow().failed
}
pub fn take_replies(&self) -> String {
std::mem::take(&mut self.events.0.borrow_mut().text)
}
fn loses_history(&mut self, origin: usize, lines: usize) {
self.truncated |= !self.term.mode().contains(TermMode::ALT_SCREEN)
&& origin == 0
&& self.term.history_size().saturating_add(lines)
> maka_agent_facts::terminal::history_rows(self.term.columns() as u16);
}
fn line_wrap(&mut self) {
if self.term.grid().cursor.point.line.0 as usize == self.region.end - 1 {
self.loses_history(self.region.start, 1);
}
}
pub fn resized(&mut self) {
self.region = 0..self.term.screen_lines();
}
fn alternate(&mut self, enabled: bool) {
let active = self.term.mode().contains(TermMode::ALT_SCREEN);
if enabled && !active {
self.last_alternate = None;
} else if !enabled && active {
self.last_alternate = Some(std::sync::Arc::new(
maka_agent_facts::terminal::display::Alternate {
size: TerminalSize::new(
self.term.columns() as u16,
self.term.screen_lines() as u16,
)
.expect("native terminal geometry"),
rows: display::rows(&self.term, 0..self.term.screen_lines() as i32),
},
));
}
if active != enabled {
self.term.swap_alt();
}
}
}
macro_rules! forward {
($($name:ident($($arg:ident: $ty:ty),*);)*) => {$(
fn $name(&mut self, $($arg: $ty),*) {
self.term.$name($($arg),*);
}
)*};
}
impl Handler for State {
forward! {
set_cursor_style(style: Option<CursorStyle>);
set_cursor_shape(shape: CursorShape);
goto(line: i32, col: usize);
goto_line(line: i32);
goto_col(col: usize);
insert_blank(count: usize);
move_up(count: usize);
move_down(count: usize);
identify_terminal(intermediate: Option<char>);
device_status(kind: usize);
move_forward(count: usize);
move_backward(count: usize);
move_down_and_cr(count: usize);
move_up_and_cr(count: usize);
backspace();
carriage_return();
substitute();
set_horizontal_tabstop();
erase_chars(count: usize);
delete_chars(count: usize);
save_cursor_position();
restore_cursor_position();
clear_line(mode: LineClearMode);
clear_tabs(mode: TabulationClearMode);
set_tabs(interval: u16);
reverse_index();
terminal_attribute(attr: Attr);
set_mode(mode: Mode);
unset_mode(mode: Mode);
report_mode(mode: Mode);
set_keypad_application_mode();
unset_keypad_application_mode();
set_active_charset(index: CharsetIndex);
configure_charset(index: CharsetIndex, charset: StandardCharset);
set_color(index: usize, color: Rgb);
reset_color(index: usize);
decaln();
}
fn input(&mut self, c: char) {
let grid = self.term.grid();
if c.width() == Some(0) {
let point = grid.cursor.point;
let mut col = point.column.0;
if !grid.cursor.input_needs_wrap {
col = col.saturating_sub(1);
}
if grid[point.line][Column(col)]
.flags
.contains(Flags::WIDE_CHAR_SPACER)
{
col = col.saturating_sub(1);
}
if grid[point.line][Column(col)]
.zerowidth()
.is_some_and(|chars| chars.len() >= 32)
{
self.failed = true;
return;
}
} else if self.term.mode().contains(TermMode::LINE_WRAP)
&& (grid.cursor.input_needs_wrap
|| (c.width() == Some(2) && grid.cursor.point.column.0 + 1 >= self.term.columns()))
{
self.line_wrap();
}
self.term.input(c);
}
fn linefeed(&mut self) {
self.line_wrap();
self.term.linefeed();
}
fn newline(&mut self) {
self.line_wrap();
self.term.newline();
}
fn scroll_up(&mut self, count: usize) {
self.loses_history(self.region.start, count.min(self.region.len()));
self.term.scroll_up(count);
}
fn scroll_down(&mut self, count: usize) {
self.term.scroll_down(count);
}
fn insert_blank_lines(&mut self, count: usize) {
self.term.insert_blank_lines(count);
}
fn delete_lines(&mut self, count: usize) {
let origin = self.term.grid().cursor.point.line.0 as usize;
if self.region.contains(&origin) {
self.loses_history(origin, count.min(self.region.len()));
}
self.term.delete_lines(count);
}
fn put_tab(&mut self, count: u16) {
if self.term.grid().cursor.input_needs_wrap
&& self.term.mode().contains(TermMode::LINE_WRAP)
{
self.line_wrap();
}
self.term.put_tab(count);
}
fn move_forward_tabs(&mut self, count: u16) {
self.term.move_forward_tabs(count);
}
fn move_backward_tabs(&mut self, count: u16) {
self.term.move_backward_tabs(count);
}
fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
let bottom = bottom
.unwrap_or(self.term.screen_lines())
.min(self.term.screen_lines());
if top > 0 && top < bottom {
self.region = top - 1..bottom;
}
self.term.set_scrolling_region(top, Some(bottom));
}
fn clear_screen(&mut self, mode: ClearMode) {
if matches!(mode, ClearMode::All) {
let lines = (0..self.term.screen_lines())
.rev()
.find(|&y| {
(0..self.term.columns())
.any(|x| !self.term.grid()[Line(y as i32)][Column(x)].is_empty())
})
.map_or(0, |y| y + 1);
self.loses_history(0, lines);
}
self.term.clear_screen(mode);
}
fn reset_state(&mut self) {
self.term.reset_state();
self.last_alternate = None;
self.mouse = MouseTracking::None;
self.encoding = MouseEncoding::Default;
self.resized();
}
fn set_private_mode(&mut self, mode: PrivateMode) {
match mode.raw() {
47 | 1047 | 1049 => {
self.alternate(true);
return;
}
9 => self.mouse = MouseTracking::X10,
1000 => self.mouse = MouseTracking::Vt200,
1002 => self.mouse = MouseTracking::Drag,
1003 => self.mouse = MouseTracking::Any,
1006 => self.encoding = MouseEncoding::Sgr,
1016 => self.encoding = MouseEncoding::SgrPixels,
_ => {}
}
self.term.set_private_mode(mode);
}
fn unset_private_mode(&mut self, mode: PrivateMode) {
match mode.raw() {
47 | 1047 | 1049 => {
self.alternate(false);
return;
}
9 | 1000 | 1002 | 1003 => self.mouse = MouseTracking::None,
1006 | 1016 => self.encoding = MouseEncoding::Default,
_ => {}
}
self.term.unset_private_mode(mode);
}
fn report_private_mode(&mut self, mode: PrivateMode) {
// Do not advertise keyboard encodings the Host's typed input API does
// not implement. Pixel mouse input is likewise rejected by that API.
match mode.raw() {
9 | 1016 => {
let enabled = if mode.raw() == 9 {
self.mouse == MouseTracking::X10
} else {
self.encoding == MouseEncoding::SgrPixels
};
self.events.send_event(Event::PtyWrite(format!(
"\x1b[?{};{}$y",
mode.raw(),
if enabled { 1 } else { 2 }
)));
}
_ => self.term.report_private_mode(mode),
}
}
fn text_area_size_chars(&mut self) {
self.events.send_event(Event::PtyWrite(format!(
"\x1b[8;{};{}t",
self.term.screen_lines(),
self.term.columns()
)));
}
}