aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml3
-rw-r--r--src/app/input.rs66
-rw-r--r--src/app/state.rs1
-rw-r--r--src/pane.rs56
-rw-r--r--src/ui.rs271
-rw-r--r--vendor/vt100/.cargo-ok1
-rw-r--r--vendor/vt100/.cargo_vcs_info.json6
-rw-r--r--vendor/vt100/CHANGELOG.md396
-rw-r--r--vendor/vt100/Cargo.lock540
-rw-r--r--vendor/vt100/Cargo.toml75
-rw-r--r--vendor/vt100/Cargo.toml.orig28
-rw-r--r--vendor/vt100/LICENSE21
-rw-r--r--vendor/vt100/README.md36
-rw-r--r--vendor/vt100/src/attrs.rs144
-rw-r--r--vendor/vt100/src/callbacks.rs69
-rw-r--r--vendor/vt100/src/cell.rs179
-rw-r--r--vendor/vt100/src/grid.rs743
-rw-r--r--vendor/vt100/src/lib.rs64
-rw-r--r--vendor/vt100/src/parser.rs96
-rw-r--r--vendor/vt100/src/perform.rs277
-rw-r--r--vendor/vt100/src/row.rs474
-rw-r--r--vendor/vt100/src/screen.rs1356
-rw-r--r--vendor/vt100/src/term.rs551
24 files changed, 5383 insertions, 72 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ffdba8e4..6d9489d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1704,8 +1704,6 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vt100"
version = "0.16.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "054ff75fb8fa83e609e685106df4faeffdf3a735d3c74ebce97ec557d5d36fd9"
dependencies = [
"itoa",
"unicode-width",
diff --git a/Cargo.toml b/Cargo.toml
index 66db635c..e4519b18 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,3 +26,6 @@ tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
tui-term = "0.3"
vt100 = "0.16"
+
+[patch.crates-io]
+vt100 = { path = "vendor/vt100" }
diff --git a/src/app/input.rs b/src/app/input.rs
index 9827d4d2..05667ab9 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -10,6 +10,12 @@ use tracing::warn;
use crate::layout::{NavDirection, PaneInfo, SplitBorder};
use crate::selection::Selection;
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum ScrollbarClickTarget {
+ Thumb { grab_row_offset: u16 },
+ Track { offset_from_bottom: usize },
+}
+
use super::state::{
key_matches, AppState, ContextMenuKind, ContextMenuState, DragState, DragTarget, Mode,
};
@@ -926,14 +932,23 @@ impl AppState {
return None;
}
- if let Some((pane_id, offset_from_bottom)) =
+ if let Some((pane_id, target)) =
self.scrollbar_target_at(mouse.column, mouse.row)
{
self.focus_pane(pane_id);
- self.set_pane_scroll_offset(pane_id, offset_from_bottom);
- self.drag = Some(DragState {
- target: DragTarget::PaneScrollbar { pane_id },
- });
+ match target {
+ ScrollbarClickTarget::Thumb { grab_row_offset } => {
+ self.drag = Some(DragState {
+ target: DragTarget::PaneScrollbar {
+ pane_id,
+ grab_row_offset,
+ },
+ });
+ }
+ ScrollbarClickTarget::Track { offset_from_bottom } => {
+ self.set_pane_scroll_offset(pane_id, offset_from_bottom);
+ }
+ }
if self.mode != Mode::Terminal {
self.mode = Mode::Terminal;
}
@@ -1022,10 +1037,15 @@ impl AppState {
ws.layout.set_ratio_at(&path, ratio);
}
}
- DragTarget::PaneScrollbar { pane_id } => {
- if let Some(offset_from_bottom) =
- self.scrollbar_offset_for_pane_row(*pane_id, mouse.row)
- {
+ DragTarget::PaneScrollbar {
+ pane_id,
+ grab_row_offset,
+ } => {
+ if let Some(offset_from_bottom) = self.scrollbar_offset_for_pane_row(
+ *pane_id,
+ mouse.row,
+ *grab_row_offset,
+ ) {
self.set_pane_scroll_offset(*pane_id, offset_from_bottom);
}
}
@@ -1331,7 +1351,11 @@ impl AppState {
}
}
- fn scrollbar_target_at(&self, col: u16, row: u16) -> Option<(crate::layout::PaneId, usize)> {
+ fn scrollbar_target_at(
+ &self,
+ col: u16,
+ row: u16,
+ ) -> Option<(crate::layout::PaneId, ScrollbarClickTarget)> {
let ws = self.active.and_then(|i| self.workspaces.get(i))?;
let info = self.view.pane_infos.iter().find(|info| {
crate::ui::pane_scrollbar_rect(info).is_some_and(|track| {
@@ -1347,16 +1371,23 @@ impl AppState {
return None;
}
let track = crate::ui::pane_scrollbar_rect(info)?;
- Some((
- info.id,
- crate::ui::scrollbar_offset_from_row(metrics, track, row),
- ))
+ if let Some(grab_row_offset) = crate::ui::scrollbar_thumb_grab_offset(metrics, track, row) {
+ Some((info.id, ScrollbarClickTarget::Thumb { grab_row_offset }))
+ } else {
+ Some((
+ info.id,
+ ScrollbarClickTarget::Track {
+ offset_from_bottom: crate::ui::scrollbar_offset_from_row(metrics, track, row),
+ },
+ ))
+ }
}
fn scrollbar_offset_for_pane_row(
&self,
pane_id: crate::layout::PaneId,
row: u16,
+ grab_row_offset: u16,
) -> Option<usize> {
let ws = self.active.and_then(|i| self.workspaces.get(i))?;
let info = self
@@ -1370,7 +1401,12 @@ impl AppState {
if metrics.max_offset_from_bottom == 0 {
return None;
}
- Some(crate::ui::scrollbar_offset_from_row(metrics, track, row))
+ Some(crate::ui::scrollbar_offset_from_drag_row(
+ metrics,
+ track,
+ row,
+ grab_row_offset,
+ ))
}
}
diff --git a/src/app/state.rs b/src/app/state.rs
index caedb419..cd45bbb6 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -397,6 +397,7 @@ pub(crate) enum DragTarget {
},
PaneScrollbar {
pane_id: crate::layout::PaneId,
+ grab_row_offset: u16,
},
SidebarDivider,
}
diff --git a/src/pane.rs b/src/pane.rs
index c6b8e175..f25c6a42 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -100,6 +100,12 @@ pub struct ScrollMetrics {
pub viewport_rows: usize,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ScrollState {
+ pub metrics: ScrollMetrics,
+ pub alternate_screen: bool,
+}
+
impl Drop for PaneRuntime {
fn drop(&mut self) {
// Abort detection task immediately.
@@ -124,7 +130,7 @@ fn max_scrollback(parser: &mut vt100::Parser<PtyResponses>) -> usize {
max_scrollback
}
-fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> String {
+fn parser_rows(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> Vec<String> {
let max_scrollback = max_scrollback(parser);
let screen = parser.screen_mut();
let original_scrollback = screen.scrollback();
@@ -150,7 +156,10 @@ fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usiz
screen.set_scrollback(original_scrollback);
rows.extend(visible_rows);
trim_trailing_blank_rows(&mut rows);
+ rows
+}
+fn recent_text_from_rows(rows: &[String], lines: usize) -> String {
let start = rows.len().saturating_sub(lines);
let text = rows[start..].join("\n");
if text.is_empty() {
@@ -160,6 +169,11 @@ fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usiz
}
}
+fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> String {
+ let rows = parser_rows(parser, lines);
+ recent_text_from_rows(&rows, lines)
+}
+
fn wait_for_processes_to_exit(pids: &[u32], timeout: std::time::Duration) -> bool {
let deadline = std::time::Instant::now() + timeout;
loop {
@@ -324,6 +338,7 @@ impl PaneRuntime {
Ok(n) => {
if let Ok(mut p) = parser.write() {
p.process(&buf[..n]);
+
// Snapshot live screen content for detection.
// Always reads at scrollback 0 (current view),
// without touching the user's scroll position.
@@ -557,20 +572,27 @@ impl PaneRuntime {
}
}
- pub fn scroll_metrics(&self) -> Option<ScrollMetrics> {
+ pub fn scroll_state(&self) -> Option<ScrollState> {
let Ok(mut parser) = self.parser.write() else {
return None;
};
let max_offset_from_bottom = max_scrollback(&mut parser);
let screen = parser.screen();
let (viewport_rows, _) = screen.size();
- Some(ScrollMetrics {
- offset_from_bottom: screen.scrollback(),
- max_offset_from_bottom,
- viewport_rows: viewport_rows as usize,
+ Some(ScrollState {
+ metrics: ScrollMetrics {
+ offset_from_bottom: screen.scrollback(),
+ max_offset_from_bottom,
+ viewport_rows: viewport_rows as usize,
+ },
+ alternate_screen: screen.alternate_screen(),
})
}
+ pub fn scroll_metrics(&self) -> Option<ScrollMetrics> {
+ self.scroll_state().map(|state| state.metrics)
+ }
+
pub fn visible_text(&self) -> String {
let Ok(content) = self.screen_content.read() else {
return String::new();
@@ -637,6 +659,28 @@ mod tests {
}
#[test]
+ fn alternate_screen_accumulates_its_own_scrollback() {
+ let responses = PtyResponses::new();
+ let mut parser = vt100::Parser::new_with_callbacks(2, 10, 100, responses);
+ parser.process(b"\x1b[?1049h1\r\n2\r\n3");
+
+ assert!(parser.screen().alternate_screen());
+ assert_eq!(max_scrollback(&mut parser), 1);
+ assert_eq!(recent_text_from_parser(&mut parser, 3), "1\n2\n3\n");
+ }
+
+ #[test]
+ fn top_anchored_scroll_regions_feed_scrollback() {
+ let responses = PtyResponses::new();
+ let mut parser = vt100::Parser::new_with_callbacks(5, 10, 100, responses);
+ parser.process(b"\x1b[?1049h1\r\n2\r\n3\r\n4\r\n5");
+ parser.process(b"\x1b[1;3r\x1b[3;1H\r\nX");
+
+ assert!(parser.screen().alternate_screen());
+ assert_eq!(max_scrollback(&mut parser), 1);
+ }
+
+ #[test]
fn claude_working_is_sticky_for_short_gap() {
let now = std::time::Instant::now();
let mut last_working = None;
diff --git a/src/ui.rs b/src/ui.rs
index 64564487..df9216a1 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -1,11 +1,8 @@
use ratatui::{
- layout::{Constraint, Layout, Margin, Rect},
+ layout::{Constraint, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
- widgets::{
- Block, Borders, Clear, List, ListItem, ListState, Paragraph, Scrollbar,
- ScrollbarOrientation, ScrollbarState, Tabs,
- },
+ widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Tabs},
Frame,
};
use tui_term::widget::PseudoTerminal;
@@ -124,10 +121,14 @@ fn compute_pane_infos(app: &AppState, area: Rect) -> Vec<PaneInfo> {
let mut inner_rect = area;
let mut scrollbar_rect = None;
if let Some(rt) = ws.runtimes.get(&focused_id) {
- if rt
- .scroll_metrics()
- .is_some_and(|metrics| metrics.max_offset_from_bottom > 0 && area.width > 1)
- {
+ let detected_agent = ws
+ .panes
+ .get(&focused_id)
+ .and_then(|pane| pane.detected_agent);
+ if rt.scroll_state().is_some_and(|state| {
+ should_show_scrollbar(state.metrics, state.alternate_screen, detected_agent)
+ && area.width > 1
+ }) {
inner_rect.width = inner_rect.width.saturating_sub(1);
scrollbar_rect = Some(Rect::new(
area.x + area.width.saturating_sub(1),
@@ -167,10 +168,11 @@ fn compute_pane_infos(app: &AppState, area: Rect) -> Vec<PaneInfo> {
let mut inner_rect = pane_inner;
let mut scrollbar_rect = None;
if let Some(rt) = ws.runtimes.get(&info.id) {
- if rt
- .scroll_metrics()
- .is_some_and(|metrics| metrics.max_offset_from_bottom > 0 && pane_inner.width > 1)
- {
+ let detected_agent = ws.panes.get(&info.id).and_then(|pane| pane.detected_agent);
+ if rt.scroll_state().is_some_and(|state| {
+ should_show_scrollbar(state.metrics, state.alternate_screen, detected_agent)
+ && pane_inner.width > 1
+ }) {
inner_rect.width = inner_rect.width.saturating_sub(1);
scrollbar_rect = Some(Rect::new(
pane_inner.x + pane_inner.width.saturating_sub(1),
@@ -633,7 +635,9 @@ fn render_panes(app: &AppState, frame: &mut Frame, area: Rect) {
// Draw terminal content
if let Ok(parser) = rt.parser.read() {
- let pt = PseudoTerminal::new(parser.screen());
+ let show_cursor = parser.screen().scrollback() == 0;
+ let pt = PseudoTerminal::new(parser.screen())
+ .cursor(tui_term::widget::Cursor::default().visibility(show_cursor));
frame.render_widget(pt, info.inner_rect);
}
render_pane_scrollbar(app, frame, info, rt);
@@ -695,25 +699,125 @@ pub(crate) fn pane_scrollbar_rect(info: &PaneInfo) -> Option<Rect> {
info.scrollbar_rect
}
-pub(crate) fn scrollbar_offset_from_row(
+fn should_show_scrollbar(
+ metrics: crate::pane::ScrollMetrics,
+ alternate_screen: bool,
+ detected_agent: Option<crate::detect::Agent>,
+) -> bool {
+ if metrics.max_offset_from_bottom == 0 {
+ return false;
+ }
+
+ if !alternate_screen {
+ return true;
+ }
+
+ metrics.offset_from_bottom > 0 || detected_agent.is_some()
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct ScrollbarThumb {
+ pub top: u16,
+ pub len: u16,
+}
+
+pub(crate) fn scrollbar_thumb(
+ metrics: crate::pane::ScrollMetrics,
+ track: Rect,
+) -> Option<ScrollbarThumb> {
+ if metrics.max_offset_from_bottom == 0 || track.height == 0 {
+ return None;
+ }
+
+ let track_height = track.height as usize;
+ let total_rows = metrics.max_offset_from_bottom + metrics.viewport_rows;
+ if total_rows == 0 {
+ return None;
+ }
+
+ let thumb_len = ((metrics.viewport_rows * track_height) as f32 / total_rows as f32)
+ .round()
+ .max(1.0)
+ .min(track_height as f32) as usize;
+ let max_thumb_top = track_height.saturating_sub(thumb_len);
+ let scrolled_from_top = metrics
+ .max_offset_from_bottom
+ .saturating_sub(metrics.offset_from_bottom);
+ let thumb_top = if max_thumb_top == 0 || metrics.max_offset_from_bottom == 0 {
+ 0
+ } else {
+ ((scrolled_from_top * max_thumb_top) as f32 / metrics.max_offset_from_bottom as f32)
+ .round()
+ .clamp(0.0, max_thumb_top as f32) as usize
+ };
+
+ Some(ScrollbarThumb {
+ top: track.y + thumb_top as u16,
+ len: thumb_len as u16,
+ })
+}
+
+pub(crate) fn scrollbar_thumb_grab_offset(
metrics: crate::pane::ScrollMetrics,
track: Rect,
row: u16,
+) -> Option<u16> {
+ let thumb = scrollbar_thumb(metrics, track)?;
+ (row >= thumb.top && row < thumb.top + thumb.len).then_some(row - thumb.top)
+}
+
+fn scrollbar_offset_from_thumb_top(
+ metrics: crate::pane::ScrollMetrics,
+ track: Rect,
+ thumb_top: usize,
) -> usize {
- if metrics.max_offset_from_bottom == 0 || track.height <= 1 {
- return metrics.max_offset_from_bottom;
+ if metrics.max_offset_from_bottom == 0 {
+ return 0;
}
- let clamped_row = row.clamp(track.y, track.y + track.height.saturating_sub(1));
- let row_offset = clamped_row.saturating_sub(track.y) as f32;
- let max_row = track.height.saturating_sub(1) as f32;
- let ratio = if max_row == 0.0 {
- 0.0
- } else {
- row_offset / max_row
+ let thumb_len = scrollbar_thumb(metrics, track)
+ .map(|thumb| thumb.len as usize)
+ .unwrap_or(1);
+ let max_thumb_top = track.height as usize - thumb_len.min(track.height as usize);
+ if max_thumb_top == 0 {
+ return 0;
+ }
+
+ let desired_top = thumb_top.min(max_thumb_top);
+ let scrolled_from_top = ((desired_top * metrics.max_offset_from_bottom) as f32
+ / max_thumb_top as f32)
+ .round() as usize;
+ metrics
+ .max_offset_from_bottom
+ .saturating_sub(scrolled_from_top)
+}
+
+pub(crate) fn scrollbar_offset_from_row(
+ metrics: crate::pane::ScrollMetrics,
+ track: Rect,
+ row: u16,
+) -> usize {
+ let thumb = match scrollbar_thumb(metrics, track) {
+ Some(thumb) => thumb,
+ None => return 0,
};
- let top_position = (ratio * metrics.max_offset_from_bottom as f32).round() as usize;
- metrics.max_offset_from_bottom.saturating_sub(top_position)
+ let clamped_row = row.clamp(track.y, track.y + track.height.saturating_sub(1));
+ let row_offset = clamped_row.saturating_sub(track.y) as usize;
+ let thumb_center = (thumb.len as usize) / 2;
+ let desired_top = row_offset.saturating_sub(thumb_center);
+ scrollbar_offset_from_thumb_top(metrics, track, desired_top)
+}
+
+pub(crate) fn scrollbar_offset_from_drag_row(
+ metrics: crate::pane::ScrollMetrics,
+ track: Rect,
+ row: u16,
+ grab_row_offset: u16,
+) -> usize {
+ let clamped_row = row.clamp(track.y, track.y + track.height.saturating_sub(1));
+ let row_offset = clamped_row.saturating_sub(track.y) as usize;
+ let desired_top = row_offset.saturating_sub(grab_row_offset as usize);
+ scrollbar_offset_from_thumb_top(metrics, track, desired_top)
}
fn render_pane_scrollbar(
@@ -732,34 +836,27 @@ fn render_pane_scrollbar(
return;
}
- let content_length = metrics.max_offset_from_bottom + metrics.viewport_rows;
- let position = metrics
- .max_offset_from_bottom
- .saturating_sub(metrics.offset_from_bottom);
- let mut scrollbar_state = ScrollbarState::new(content_length)
- .position(position)
- .viewport_content_length(metrics.viewport_rows);
+ let Some(thumb) = scrollbar_thumb(metrics, track) else {
+ return;
+ };
+
let (track_color, thumb_color, thumb_symbol) = if info.is_focused {
(app.palette.overlay0, app.palette.overlay1, "▐")
} else {
(app.palette.surface_dim, app.palette.overlay0, "▕")
};
- let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
- .begin_symbol(None)
- .end_symbol(None)
- .track_symbol(Some("▕"))
- .track_style(Style::default().fg(track_color))
- .thumb_symbol(thumb_symbol)
- .thumb_style(Style::default().fg(thumb_color));
-
- frame.render_stateful_widget(
- scrollbar,
- track.inner(Margin {
- vertical: 0,
- horizontal: 0,
- }),
- &mut scrollbar_state,
- );
+
+ let buf = frame.buffer_mut();
+ for y in track.y..track.y + track.height {
+ let cell = &mut buf[(track.x, y)];
+ cell.set_symbol("▕");
+ cell.set_style(Style::default().fg(track_color));
+ }
+ for y in thumb.top..thumb.top + thumb.len {
+ let cell = &mut buf[(track.x, y)];
+ cell.set_symbol(thumb_symbol);
+ cell.set_style(Style::default().fg(thumb_color));
+ }
}
fn render_selection_highlight(
@@ -1748,6 +1845,67 @@ mod tests {
}
#[test]
+ fn alternate_screen_scrollbar_stays_hidden_for_unidentified_live_bottom() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 0,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+
+ assert!(!should_show_scrollbar(metrics, true, None));
+ }
+
+ #[test]
+ fn alternate_screen_scrollbar_shows_for_agents_at_live_bottom() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 0,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+
+ assert!(should_show_scrollbar(
+ metrics,
+ true,
+ Some(crate::detect::Agent::Codex)
+ ));
+ }
+
+ #[test]
+ fn alternate_screen_scrollbar_shows_when_user_scrolled_up() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 3,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+
+ assert!(should_show_scrollbar(metrics, true, None));
+ }
+
+ #[test]
+ fn normal_screen_scrollbar_shows_with_scrollback() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 0,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+
+ assert!(should_show_scrollbar(metrics, false, None));
+ }
+
+ #[test]
+ fn scrollbar_thumb_reaches_bottom_when_scrolled_to_bottom() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 0,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+ let track = Rect::new(9, 4, 1, 5);
+
+ let thumb = scrollbar_thumb(metrics, track).expect("thumb");
+ assert_eq!(thumb.top + thumb.len, track.y + track.height);
+ }
+
+ #[test]
fn scrollbar_offset_mapping_hits_top_middle_and_bottom() {
let metrics = crate::pane::ScrollMetrics {
offset_from_bottom: 0,
@@ -1760,4 +1918,19 @@ mod tests {
assert_eq!(scrollbar_offset_from_row(metrics, track, 6), 10);
assert_eq!(scrollbar_offset_from_row(metrics, track, 8), 0);
}
+
+ #[test]
+ fn dragging_from_current_thumb_row_preserves_offset() {
+ let metrics = crate::pane::ScrollMetrics {
+ offset_from_bottom: 7,
+ max_offset_from_bottom: 20,
+ viewport_rows: 5,
+ };
+ let track = Rect::new(9, 4, 1, 8);
+ let thumb = scrollbar_thumb(metrics, track).expect("thumb");
+ let row = thumb.top + thumb.len / 2;
+ let grab = scrollbar_thumb_grab_offset(metrics, track, row).expect("grab");
+
+ assert_eq!(scrollbar_offset_from_drag_row(metrics, track, row, grab), 7);
+ }
}
diff --git a/vendor/vt100/.cargo-ok b/vendor/vt100/.cargo-ok
new file mode 100644
index 00000000..5f8b7958
--- /dev/null
+++ b/vendor/vt100/.cargo-ok
@@ -0,0 +1 @@
+{"v":1} \ No newline at end of file
diff --git a/vendor/vt100/.cargo_vcs_info.json b/vendor/vt100/.cargo_vcs_info.json
new file mode 100644
index 00000000..99725106
--- /dev/null
+++ b/vendor/vt100/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "eb66ffaf7d771c13303ef73b29f6f2a56fdacecf"
+ },
+ "path_in_vcs": ""
+} \ No newline at end of file
diff --git a/vendor/vt100/CHANGELOG.md b/vendor/vt100/CHANGELOG.md
new file mode 100644
index 00000000..d589ed7a
--- /dev/null
+++ b/vendor/vt100/CHANGELOG.md
@@ -0,0 +1,396 @@
+# Changelog
+
+## [0.16.2] - 2025-07-11
+
+### Fixed
+
+* Fixed potential cursor out of bounds when using decrc after resizing. (#13)
+
+## [0.16.1] - 2025-07-10
+
+### Changed
+
+* Reverted to the 2021 edition for now.
+
+## [0.16.0] - 2025-07-08
+
+### Added
+
+* `Parser::process_cb`, which works the same as `Parser::process` except that
+ it calls callbacks during parsing when it finds a terminal escape which is
+ potentially useful but not something that affects the screen itself.
+* Support for xterm window resize request escape codes, via the new callback
+ mechanism.
+* Support for dim formatting. (Daniel Faust, #9)
+* Support for CNL/CPL escape codes. (Danny Weinberg, #10)
+* Support for OSC 52 (clipboard manipulation).
+
+### Removed
+
+* These methods on `Screen` have been removed in favor of the new callback
+ API described above:
+ * `title_formatted`
+ * `title_diff`
+ * `title`
+ * `icon_name`
+ * `bells_diff`
+ * `audible_bell_count`
+ * `visual_bell_count`
+ * `errors`
+* Additionally, unhandled escape sequences no longer log to STDERR; they
+ instead call various callback methods which can be defined to log if
+ desired.
+* `Cell` no longer implements `Default`.
+* `Screen` no longer implements `vte::Perform`.
+
+### Changed
+
+* `Parser::set_size` and `Parser::set_scrollback` have been moved to methods
+ on `Screen`, and `Parser::screen_mut` was added to get a mutable reference
+ to the screen.
+* `Cell::contents` now returns `&str` instead of `String`, eliminating an
+ allocation in many cases. (Chris Olszewski, #14)
+
+### Fixed
+
+* Fixed some issues with calculating scrollback offsets correctly in
+ `Grid::visible_rows`. (rezigned, #11)
+
+## [0.15.2] - 2023-02-05
+
+### Changed
+
+* Bumped dependencies
+
+## [0.15.1] - 2021-12-21
+
+### Changed
+
+* Removed a lot of unnecessary test data from the packaged crate, making
+ downloads faster
+
+## [0.15.0] - 2021-12-15
+
+### Added
+
+* `Screen::errors` to track the number of parsing errors seen so far
+
+### Fixed
+
+* No longer generate spurious diffs in some cases where the cursor is past the
+ end of a row
+* Fix restoring the cursor position when scrolled back
+
+### Changed
+
+* Various internal refactorings
+
+## [0.14.0] - 2021-12-06
+
+### Changed
+
+* Unknown UTF-8 characters default to a width of 1, rather than 0 (except for
+ control characters, as mentioned below)
+
+### Fixed
+
+* Ignore C1 control characters rather than adding them to the cell data, since
+ they are non-printable
+
+## [0.13.2] - 2021-12-05
+
+### Changed
+
+* Delay allocation of the alternate screen until it is used (saves a bit of
+ memory in basic cases)
+
+## [0.13.1] - 2021-12-04
+
+### Fixed
+
+* Fixed various line wrapping state issues
+* Fixed cursor positioning after writing zero width characters at the end of
+ the line
+* Fixed `Screen::cursor_state_formatted` to draw the last character in a line
+ with the appropriate drawing attributes if it needs to redraw it
+
+## [0.13.0] - 2021-11-17
+
+### Added
+
+* `Screen::alternate_screen` to determine if the alternate screen is in use
+* `Screen::row_wrapped` to determine whether the row at the given index should
+ wrap its text
+* `Screen::cursor_state_formatted` to set the cursor position and hidden state
+ (including internal state like the one-past-the-end state which isn't visible
+ in the return value of `cursor_position`)
+
+### Fixed
+
+* `Screen::rows_formatted` now outputs correct escape codes in some edge cases
+ at the beginning of a row when the previous row was wrapped
+* VPA escape sequence can no longer position the cursor off the screen
+
+## [0.12.0] - 2021-03-09
+
+### Added
+
+* `Screen::state_formatted` and `Screen::state_diff` convenience wrappers
+
+### Fixed
+
+* `Screen::attributes_formatted` now correctly resets previously set attributes
+ where necessary
+
+### Removed
+
+* Removed `Screen::attributes_diff`, since I can't actually think of any
+ situation where it does a thing that makes sense.
+
+## [0.11.1] - 2021-03-07
+
+### Changed
+
+* Drop dependency on `enumset`
+
+## [0.11.0] - 2021-03-07
+
+### Added
+
+* `Screen::attributes_formatted` and `Screen::attributes_diff` to retrieve the
+ current state of the drawing attributes as escape sequences
+* `Screen::fgcolor`, `Screen::bgcolor`, `Screen::bold`, `Screen::italic`,
+ `Screen::underline`, and `Screen::inverse` to retrieve the current state of
+ the drawing attributes directly
+
+## [0.10.0] - 2021-03-06
+
+### Added
+
+* Implementation of `std::io::Write` for `Parser`
+
+## [0.9.0] - 2021-03-05
+
+### Added
+
+* `Screen::contents_between`, for returning the contents logically between two
+ given cells (for things like clipboard selection)
+* Support SGR subparameters (so `\e[38:2:255:0:0m` behaves the same way as
+ `\e[38;2;255;0;0m`)
+
+### Fixed
+
+* Bump `enumset` to fix a dependency which fails to build
+
+## [0.8.1] - 2020-02-09
+
+### Changed
+
+* Bumped `vte` dep to 0.6.
+
+## [0.8.0] - 2019-12-07
+
+### Removed
+
+* Removed the unicode-normalization feature altogether - it turns out that it
+ still has a couple edge cases where it causes incorrect behavior, and fixing
+ those would be a lot more effort.
+
+### Fixed
+
+* Fix a couple more end-of-line/wrapping bugs, especially around cursor
+ positioning.
+* Fix applying combining characters to wide characters.
+* Ensure cells can't have contents with width zero (to avoid ambiguity). If an
+ empty cell gets a combining character applied to it, default that cell to a
+ (normal-width) space first.
+
+## [0.7.0] - 2019-11-23
+
+### Added
+
+* New (default-on) cargo feature `unicode-normalization` which can be disabled
+ to disable normalizing cell contents to NFC - it's a pretty small edge case,
+ and the data tables required to support it are quite large, which affects
+ size-sensitive targets like wasm
+
+## [0.6.3] - 2019-11-20
+
+### Fixed
+
+* Fix output of `contents_formatted` and `contents_diff` when the cursor
+ position ends at one past the end of a row.
+* If the cursor position is one past the end of a row, any char, even a
+ combining char, needs to cause the cursor position to wrap.
+
+## [0.6.2] - 2019-11-13
+
+### Fixed
+
+* Fix zero-width characters when the cursor is at the end of a row.
+
+## [0.6.1] - 2019-11-13
+
+### Added
+
+* Add more debug logging for unhandled escape sequences.
+
+### Changed
+
+* Unhandled escape sequence warnings are now at the `debug` log level.
+
+## [0.6.0] - 2019-11-13
+
+### Added
+
+* `Screen::input_mode_formatted` and `Screen::input_mode_diff` give escape
+ codes to set the current terminal input modes.
+* `Screen::title_formatted` and `Screen::title_diff` give escape codes to set
+ the terminal window title.
+* `Screen::bells_diff` gives escape codes to trigger any audible or visual
+ bells which have been seen since the previous state.
+
+### Changed
+
+* `Screen::contents_diff` no longer includes audible or visual bells (see
+ `Screen::bells_diff` instead).
+
+## [0.5.1] - 2019-11-12
+
+### Fixed
+
+* `Screen::set_size` now actually resizes when requested (previously the
+ underlying storage was not being resized, leading to panics when writing
+ outside of the original screen).
+
+## [0.5.0] - 2019-11-12
+
+### Added
+
+* Scrollback support.
+* `Default` impl for `Parser` which creates an 80x24 terminal with no
+ scrollback.
+
+### Removed
+
+* `Parser::screen_mut` (and the `pub` `&mut self` methods on `Screen`). The few
+ things you can do to change the screen state directly are now exposed as
+ methods on `Parser` itself.
+
+### Changed
+
+* `Cell::contents` now returns a `String` instead of a `&str`.
+* `Screen::check_audible_bell` and `Screen::check_visual_bell` have been
+ replaced with `Screen::audible_bell_count` and `Screen::visual_bell_count`.
+ You should keep track of the "since the last method call" state yourself
+ instead of having the screen track it for you.
+
+### Fixed
+
+* Lots of performance and output optimizations.
+* Clearing a cell now sets all of that cell's attributes to the current
+ attribute set, since different terminals render different things for an empty
+ cell based on the attributes.
+* `Screen::contents_diff` now includes audible and visual bells when
+ appropriate.
+
+## [0.4.0] - 2019-11-08
+
+### Removed
+
+* `Screen::fgcolor`, `Screen::bgcolor`, `Screen::bold`, `Screen::italic`,
+ `Screen::underline`, `Screen::inverse`, and `Screen::alternate_screen`:
+ these are just implementation details that people shouldn't need to care
+ about.
+
+### Fixed
+
+* Fixed cursor movement when the cursor position is already outside of an
+ active scroll region.
+
+## [0.3.2] - 2019-11-08
+
+### Fixed
+
+* Clearing cells now correctly sets the cell background color.
+* Fixed a couple bugs in wide character handling in `contents_formatted` and
+ `contents_diff`.
+* Fixed RI when the cursor is at the top of the screen (fixes scrolling up in
+ `less`, for instance).
+* Fixed VPA incorrectly being clamped to the scroll region.
+* Stop treating soft hyphen specially (as far as i can tell, no other terminals
+ do this, and i'm not sure why i thought it was necessary to begin with).
+* `contents_formatted` now also resets attributes at the start, like
+ `contents_diff` does.
+
+## [0.3.1] - 2019-11-06
+
+### Fixed
+
+* Make `contents_formatted` explicitly show the cursor when necessary, in case
+ the cursor was previously hidden.
+
+## [0.3.0] - 2019-11-06
+
+### Added
+
+* `Screen::rows` which is like `Screen::contents` except that it returns the
+ data by row instead of all at once, and also allows you to restrict the
+ region returned to a subset of columns.
+* `Screen::rows_formatted` which is like `Screen::rows`, but returns escape
+ sequences sufficient to draw the requested subset of each row.
+* `Screen::contents_diff` and `Screen::rows_diff` which return escape sequences
+ sufficient to turn the visible state of one screen (or a subset of the screen
+ in the case of `rows_diff`) into another.
+
+### Changed
+
+* The screen is now exposed separately from the parser, and is cloneable.
+* `contents_formatted` now returns `Vec<u8>` instead of `String`.
+* `contents` and `contents_formatted` now only allow getting the contents of
+ the entire screen rather than a subset (but see the entry for `rows` and
+ `rows_formatted` above).
+
+### Removed
+
+* `Cell::new`, since there's not really any reason that this is useful for
+ someone to do from outside of the crate.
+
+### Fixed
+
+* `contents_formatted` now preserves the state of empty cells instead of
+ filling them with spaces.
+* We now clear the row wrapping state when the number of columns in the
+ terminal is changed.
+* `contents_formatted` now ensures that the cursor has the correct hidden state
+ and location.
+* `contents_formatted` now clears the screen before starting to draw.
+
+## [0.2.0] - 2019-11-04
+
+### Changed
+
+* Reimplemented in pure safe rust, with a much more accurate parser
+* A bunch of minor API tweaks, some backwards-incompatible
+
+## [0.1.2] - 2016-06-04
+
+### Fixed
+
+* Fix returning uninit memory in get_string_formatted/get_string_plaintext
+* Handle emoji and zero width unicode characters properly
+* Fix cursor positioning with regards to scroll regions and wrapping
+* Fix parsing of (ignored) character set escapes
+* Explicitly suppress status report escapes
+
+## [0.1.1] - 2016-04-28
+
+### Fixed
+
+* Fix builds
+
+## [0.1.0] - 2016-04-28
+
+### Added
+
+* Initial release
diff --git a/vendor/vt100/Cargo.lock b/vendor/vt100/Cargo.lock
new file mode 100644
index 00000000..b984ee78
--- /dev/null
+++ b/vendor/vt100/Cargo.lock
@@ -0,0 +1,540 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "bitflags"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "env_logger"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "libc"
+version = "0.2.174"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quickcheck"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
+dependencies = [
+ "env_logger",
+ "log",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+dependencies = [
+ "rand_chacha",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.3",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
+dependencies = [
+ "getrandom 0.3.3",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rustix"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
+dependencies = [
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
+
+[[package]]
+name = "vt100"
+version = "0.16.2"
+dependencies = [
+ "itoa",
+ "nix",
+ "quickcheck",
+ "rand 0.9.1",
+ "serde",
+ "serde_json",
+ "terminal_size",
+ "unicode-width",
+ "vte",
+]
+
+[[package]]
+name = "vte"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd"
+dependencies = [
+ "arrayvec",
+ "memchr",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
+dependencies = [
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/vendor/vt100/Cargo.toml b/vendor/vt100/Cargo.toml
new file mode 100644
index 00000000..098eea05
--- /dev/null
+++ b/vendor/vt100/Cargo.toml
@@ -0,0 +1,75 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.70"
+name = "vt100"
+version = "0.16.2"
+authors = ["Jesse Luehrs <doy@tozt.net>"]
+build = false
+include = [
+ "src/**/*",
+ "LICENSE",
+ "README.md",
+ "CHANGELOG.md",
+]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Library for parsing terminal data"
+homepage = "https://github.com/doy/vt100-rust"
+readme = "README.md"
+keywords = [
+ "terminal",
+ "vt100",
+]
+categories = [
+ "command-line-interface",
+ "encoding",
+]
+license = "MIT"
+repository = "https://github.com/doy/vt100-rust"
+
+[lib]
+name = "vt100"
+path = "src/lib.rs"
+
+[dependencies.itoa]
+version = "1.0.15"
+
+[dependencies.unicode-width]
+version = "0.2.1"
+
+[dependencies.vte]
+version = "0.15.0"
+
+[dev-dependencies.nix]
+version = "0.30.1"
+features = ["term"]
+
+[dev-dependencies.quickcheck]
+version = "1.0"
+
+[dev-dependencies.rand]
+version = "0.9"
+
+[dev-dependencies.serde]
+version = "1.0.219"
+features = ["derive"]
+
+[dev-dependencies.serde_json]
+version = "1.0.140"
+
+[dev-dependencies.terminal_size]
+version = "0.4.2"
diff --git a/vendor/vt100/Cargo.toml.orig b/vendor/vt100/Cargo.toml.orig
new file mode 100644
index 00000000..db35e207
--- /dev/null
+++ b/vendor/vt100/Cargo.toml.orig
@@ -0,0 +1,28 @@
+[package]
+name = "vt100"
+version = "0.16.2"
+authors = ["Jesse Luehrs <doy@tozt.net>"]
+edition = "2021"
+rust-version = "1.70"
+
+description = "Library for parsing terminal data"
+homepage = "https://github.com/doy/vt100-rust"
+repository = "https://github.com/doy/vt100-rust"
+readme = "README.md"
+keywords = ["terminal", "vt100"]
+categories = ["command-line-interface", "encoding"]
+license = "MIT"
+include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]
+
+[dependencies]
+itoa = "1.0.15"
+unicode-width = "0.2.1"
+vte = "0.15.0"
+
+[dev-dependencies]
+nix = { version = "0.30.1", features = ["term"] }
+quickcheck = "1.0"
+rand = "0.9"
+serde = { version = "1.0.219", features = ["derive"] }
+serde_json = "1.0.140"
+terminal_size = "0.4.2"
diff --git a/vendor/vt100/LICENSE b/vendor/vt100/LICENSE
new file mode 100644
index 00000000..a1dd5b0e
--- /dev/null
+++ b/vendor/vt100/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Jesse Luehrs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/vt100/README.md b/vendor/vt100/README.md
new file mode 100644
index 00000000..a8e1b8f6
--- /dev/null
+++ b/vendor/vt100/README.md
@@ -0,0 +1,36 @@
+# vt100
+
+This crate parses a terminal byte stream and provides an in-memory
+representation of the rendered contents.
+
+## Overview
+
+This is essentially the terminal parser component of a graphical terminal
+emulator pulled out into a separate crate. Although you can use this crate
+to build a graphical terminal emulator, it also contains functionality
+necessary for implementing terminal applications that want to run other
+terminal applications - programs like `screen` or `tmux` for example.
+
+## Synopsis
+
+```rust
+let mut parser = vt100::Parser::new(24, 80, 0);
+
+let screen = parser.screen().clone();
+parser.process(b"this text is \x1b[31mRED\x1b[m");
+assert_eq!(
+ parser.screen().cell(0, 13).unwrap().fgcolor(),
+ vt100::Color::Idx(1),
+);
+
+let screen = parser.screen().clone();
+parser.process(b"\x1b[3D\x1b[32mGREEN");
+assert_eq!(
+ parser.screen().contents_formatted(),
+ &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jthis text is \x1b[32mGREEN"[..],
+);
+assert_eq!(
+ parser.screen().contents_diff(&screen),
+ &b"\x1b[1;14H\x1b[32mGREEN"[..],
+);
+```
diff --git a/vendor/vt100/src/attrs.rs b/vendor/vt100/src/attrs.rs
new file mode 100644
index 00000000..e5044f2e
--- /dev/null
+++ b/vendor/vt100/src/attrs.rs
@@ -0,0 +1,144 @@
+use crate::term::BufWrite as _;
+
+/// Represents a foreground or background color for cells.
+#[derive(Eq, PartialEq, Debug, Copy, Clone, Default)]
+pub enum Color {
+ /// The default terminal color.
+ #[default]
+ Default,
+
+ /// An indexed terminal color.
+ Idx(u8),
+
+ /// An RGB terminal color. The parameters are (red, green, blue).
+ Rgb(u8, u8, u8),
+}
+
+const TEXT_MODE_INTENSITY: u8 = 0b0000_0011;
+const TEXT_MODE_BOLD: u8 = 0b0000_0001;
+const TEXT_MODE_DIM: u8 = 0b0000_0010;
+const TEXT_MODE_ITALIC: u8 = 0b0000_0100;
+const TEXT_MODE_UNDERLINE: u8 = 0b0000_1000;
+const TEXT_MODE_INVERSE: u8 = 0b0001_0000;
+
+#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
+pub struct Attrs {
+ pub fgcolor: Color,
+ pub bgcolor: Color,
+ pub mode: u8,
+}
+
+impl Attrs {
+ pub fn bold(&self) -> bool {
+ self.mode & TEXT_MODE_BOLD != 0
+ }
+
+ pub fn dim(&self) -> bool {
+ self.mode & TEXT_MODE_DIM != 0
+ }
+
+ fn intensity(&self) -> u8 {
+ self.mode & TEXT_MODE_INTENSITY
+ }
+
+ pub fn set_bold(&mut self) {
+ self.mode &= !TEXT_MODE_INTENSITY;
+ self.mode |= TEXT_MODE_BOLD;
+ }
+
+ pub fn set_dim(&mut self) {
+ self.mode &= !TEXT_MODE_INTENSITY;
+ self.mode |= TEXT_MODE_DIM;
+ }
+
+ pub fn set_normal_intensity(&mut self) {
+ self.mode &= !TEXT_MODE_INTENSITY;
+ }
+
+ pub fn italic(&self) -> bool {
+ self.mode & TEXT_MODE_ITALIC != 0
+ }
+
+ pub fn set_italic(&mut self, italic: bool) {
+ if italic {
+ self.mode |= TEXT_MODE_ITALIC;
+ } else {
+ self.mode &= !TEXT_MODE_ITALIC;
+ }
+ }
+
+ pub fn underline(&self) -> bool {
+ self.mode & TEXT_MODE_UNDERLINE != 0
+ }
+
+ pub fn set_underline(&mut self, underline: bool) {
+ if underline {
+ self.mode |= TEXT_MODE_UNDERLINE;
+ } else {
+ self.mode &= !TEXT_MODE_UNDERLINE;
+ }
+ }
+
+ pub fn inverse(&self) -> bool {
+ self.mode & TEXT_MODE_INVERSE != 0
+ }
+
+ pub fn set_inverse(&mut self, inverse: bool) {
+ if inverse {
+ self.mode |= TEXT_MODE_INVERSE;
+ } else {
+ self.mode &= !TEXT_MODE_INVERSE;
+ }
+ }
+
+ pub fn write_escape_code_diff(
+ &self,
+ contents: &mut Vec<u8>,
+ other: &Self,
+ ) {
+ if self != other && self == &Self::default() {
+ crate::term::ClearAttrs.write_buf(contents);
+ return;
+ }
+
+ let attrs = crate::term::Attrs::default();
+
+ let attrs = if self.fgcolor == other.fgcolor {
+ attrs
+ } else {
+ attrs.fgcolor(self.fgcolor)
+ };
+ let attrs = if self.bgcolor == other.bgcolor {
+ attrs
+ } else {
+ attrs.bgcolor(self.bgcolor)
+ };
+ let attrs = if self.intensity() == other.intensity() {
+ attrs
+ } else {
+ attrs.intensity(match self.intensity() {
+ 0 => crate::term::Intensity::Normal,
+ TEXT_MODE_BOLD => crate::term::Intensity::Bold,
+ TEXT_MODE_DIM => crate::term::Intensity::Dim,
+ _ => unreachable!(),
+ })
+ };
+ let attrs = if self.italic() == other.italic() {
+ attrs
+ } else {
+ attrs.italic(self.italic())
+ };
+ let attrs = if self.underline() == other.underline() {
+ attrs
+ } else {
+ attrs.underline(self.underline())
+ };
+ let attrs = if self.inverse() == other.inverse() {
+ attrs
+ } else {
+ attrs.inverse(self.inverse())
+ };
+
+ attrs.write_buf(contents);
+ }
+}
diff --git a/vendor/vt100/src/callbacks.rs b/vendor/vt100/src/callbacks.rs
new file mode 100644
index 00000000..cce6f6c0
--- /dev/null
+++ b/vendor/vt100/src/callbacks.rs
@@ -0,0 +1,69 @@
+/// This trait is used by the parser to handle extra escape sequences that
+/// don't have an impact on the terminal screen directly.
+pub trait Callbacks {
+ /// This callback is called when the terminal requests an audible bell
+ /// (typically with `^G`).
+ fn audible_bell(&mut self, _: &mut crate::Screen) {}
+ /// This callback is called when the terminal requests a visual bell
+ /// (typically with `\eg`).
+ fn visual_bell(&mut self, _: &mut crate::Screen) {}
+ /// This callback is called when the terminal requests a resize
+ /// (typically with `\e[8;<rows>;<cols>t`).
+ fn resize(&mut self, _: &mut crate::Screen, _request: (u16, u16)) {}
+ /// This callback is called when the terminal requests the window title
+ /// to be set (typically with `\e]1;<icon_name>\a`)
+ fn set_window_icon_name(
+ &mut self,
+ _: &mut crate::Screen,
+ _icon_name: &[u8],
+ ) {
+ }
+ /// This callback is called when the terminal requests the window title
+ /// to be set (typically with `\e]2;<title>\a`)
+ fn set_window_title(&mut self, _: &mut crate::Screen, _title: &[u8]) {}
+ /// This callback is called when the terminal requests data to be copied
+ /// to the system clipboard (typically with `\e]52;<ty>;<data>\a`). Note
+ /// that `data` will be encoded as base64.
+ fn copy_to_clipboard(
+ &mut self,
+ _: &mut crate::Screen,
+ _ty: &[u8],
+ _data: &[u8],
+ ) {
+ }
+ /// This callback is called when the terminal requests data to be pasted
+ /// from the system clipboard (typically with `\e]52;<ty>;?\a`).
+ fn paste_from_clipboard(&mut self, _: &mut crate::Screen, _ty: &[u8]) {}
+ /// This callback is called when the terminal receives an escape sequence
+ /// which is otherwise not implemented.
+ fn unhandled_char(&mut self, _: &mut crate::Screen, _c: char) {}
+ /// This callback is called when the terminal receives a control
+ /// character which is otherwise not implemented.
+ fn unhandled_control(&mut self, _: &mut crate::Screen, _b: u8) {}
+ /// This callback is called when the terminal receives an escape sequence
+ /// which is otherwise not implemented.
+ fn unhandled_escape(
+ &mut self,
+ _: &mut crate::Screen,
+ _i1: Option<u8>,
+ _i2: Option<u8>,
+ _b: u8,
+ ) {
+ }
+ /// This callback is called when the terminal receives a CSI sequence
+ /// (`\e[`) which is otherwise not implemented.
+ fn unhandled_csi(
+ &mut self,
+ _: &mut crate::Screen,
+ _i1: Option<u8>,
+ _i2: Option<u8>,
+ _params: &[&[u16]],
+ _c: char,
+ ) {
+ }
+ /// This callback is called when the terminal receives a OSC sequence
+ /// (`\e]`) which is otherwise not implemented.
+ fn unhandled_osc(&mut self, _: &mut crate::Screen, _params: &[&[u8]]) {}
+}
+
+impl Callbacks for () {}
diff --git a/vendor/vt100/src/cell.rs b/vendor/vt100/src/cell.rs
new file mode 100644
index 00000000..c000cf30
--- /dev/null
+++ b/vendor/vt100/src/cell.rs
@@ -0,0 +1,179 @@
+use unicode_width::UnicodeWidthChar as _;
+
+// chosen to make the size of the cell struct 32 bytes
+const CONTENT_BYTES: usize = 22;
+
+const IS_WIDE: u8 = 0b1000_0000;
+const IS_WIDE_CONTINUATION: u8 = 0b0100_0000;
+const LEN_BITS: u8 = 0b0001_1111;
+
+/// Represents a single terminal cell.
+#[derive(Clone, Debug, Eq)]
+pub struct Cell {
+ contents: [u8; CONTENT_BYTES],
+ len: u8,
+ attrs: crate::attrs::Attrs,
+}
+const _: () = assert!(std::mem::size_of::<Cell>() == 32);
+
+impl PartialEq<Self> for Cell {
+ fn eq(&self, other: &Self) -> bool {
+ if self.len != other.len {
+ return false;
+ }
+ if self.attrs != other.attrs {
+ return false;
+ }
+ let len = self.len();
+ self.contents[..len] == other.contents[..len]
+ }
+}
+
+impl Cell {
+ pub(crate) fn new() -> Self {
+ Self {
+ contents: Default::default(),
+ len: 0,
+ attrs: crate::attrs::Attrs::default(),
+ }
+ }
+
+ fn len(&self) -> usize {
+ usize::from(self.len & LEN_BITS)
+ }
+
+ pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) {
+ self.len = 0;
+ self.append_char(0, c);
+ // strings in this context should always be an arbitrary character
+ // followed by zero or more zero-width characters, so we should only
+ // have to look at the first character
+ self.set_wide(c.width().unwrap_or(1) > 1);
+ self.attrs = a;
+ }
+
+ pub(crate) fn append(&mut self, c: char) {
+ let len = self.len();
+ if len >= CONTENT_BYTES - 4 {
+ return;
+ }
+ if len == 0 {
+ self.contents[0] = b' ';
+ self.len += 1;
+ }
+
+ // we already checked that we have space for another codepoint
+ self.append_char(self.len(), c);
+ }
+
+ // Writes bytes representing c at start
+ // Requires caller to verify start <= CODEPOINTS_IN_CELL * 4
+ fn append_char(&mut self, start: usize, c: char) {
+ c.encode_utf8(&mut self.contents[start..]);
+ self.len += u8::try_from(c.len_utf8()).unwrap();
+ }
+
+ pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) {
+ self.len = 0;
+ self.attrs = attrs;
+ }
+
+ /// Returns the text contents of the cell.
+ ///
+ /// Can include multiple unicode characters if combining characters are
+ /// used, but will contain at most one character with a non-zero character
+ /// width.
+ // Since contents has been constructed by appending chars encoded as UTF-8 it will be valid UTF-8
+ #[allow(clippy::missing_panics_doc)]
+ #[must_use]
+ pub fn contents(&self) -> &str {
+ std::str::from_utf8(&self.contents[..self.len()]).unwrap()
+ }
+
+ /// Returns whether the cell contains any text data.
+ #[must_use]
+ pub fn has_contents(&self) -> bool {
+ self.len() > 0
+ }
+
+ /// Returns whether the text data in the cell represents a wide character.
+ #[must_use]
+ pub fn is_wide(&self) -> bool {
+ self.len & IS_WIDE != 0
+ }
+
+ /// Returns whether the cell contains the second half of a wide character
+ /// (in other words, whether the previous cell in the row contains a wide
+ /// character)
+ #[must_use]
+ pub fn is_wide_continuation(&self) -> bool {
+ self.len & IS_WIDE_CONTINUATION != 0
+ }
+
+ fn set_wide(&mut self, wide: bool) {
+ if wide {
+ self.len |= IS_WIDE;
+ } else {
+ self.len &= !IS_WIDE;
+ }
+ }
+
+ pub(crate) fn set_wide_continuation(&mut self, wide: bool) {
+ if wide {
+ self.len |= IS_WIDE_CONTINUATION;
+ } else {
+ self.len &= !IS_WIDE_CONTINUATION;
+ }
+ }
+
+ pub(crate) fn attrs(&self) -> &crate::attrs::Attrs {
+ &self.attrs
+ }
+
+ /// Returns the foreground color of the cell.
+ #[must_use]
+ pub fn fgcolor(&self) -> crate::Color {
+ self.attrs.fgcolor
+ }
+
+ /// Returns the background color of the cell.
+ #[must_use]
+ pub fn bgcolor(&self) -> crate::Color {
+ self.attrs.bgcolor
+ }
+
+ /// Returns whether the cell should be rendered with the bold text
+ /// attribute.
+ #[must_use]
+ pub fn bold(&self) -> bool {
+ self.attrs.bold()
+ }
+
+ /// Returns whether the cell should be rendered with the dim text
+ /// attribute.
+ #[must_use]
+ pub fn dim(&self) -> bool {
+ self.attrs.dim()
+ }
+
+ /// Returns whether the cell should be rendered with the italic text
+ /// attribute.
+ #[must_use]
+ pub fn italic(&self) -> bool {
+ self.attrs.italic()
+ }
+
+ /// Returns whether the cell should be rendered with the underlined text
+ /// attribute.
+ #[must_use]
+ pub fn underline(&self) -> bool {
+ self.attrs.underline()
+ }
+
+ /// Returns whether the cell should be rendered with the inverse text
+ /// attribute.
+ #[must_use]
+ pub fn inverse(&self) -> bool {
+ self.attrs.inverse()
+ }
+}
diff --git a/vendor/vt100/src/grid.rs b/vendor/vt100/src/grid.rs
new file mode 100644
index 00000000..b1c5d341
--- /dev/null
+++ b/vendor/vt100/src/grid.rs
@@ -0,0 +1,743 @@
+use crate::term::BufWrite as _;
+
+#[derive(Clone, Debug)]
+pub struct Grid {
+ size: Size,
+ pos: Pos,
+ saved_pos: Pos,
+ rows: Vec<crate::row::Row>,
+ scroll_top: u16,
+ scroll_bottom: u16,
+ origin_mode: bool,
+ saved_origin_mode: bool,
+ scrollback: std::collections::VecDeque<crate::row::Row>,
+ scrollback_len: usize,
+ scrollback_offset: usize,
+}
+
+impl Grid {
+ pub fn new(size: Size, scrollback_len: usize) -> Self {
+ Self {
+ size,
+ pos: Pos::default(),
+ saved_pos: Pos::default(),
+ rows: vec![],
+ scroll_top: 0,
+ scroll_bottom: size.rows - 1,
+ origin_mode: false,
+ saved_origin_mode: false,
+ scrollback: std::collections::VecDeque::new(),
+ scrollback_len,
+ scrollback_offset: 0,
+ }
+ }
+
+ pub fn allocate_rows(&mut self) {
+ if self.rows.is_empty() {
+ self.rows.extend(
+ std::iter::repeat_with(|| {
+ crate::row::Row::new(self.size.cols)
+ })
+ .take(usize::from(self.size.rows)),
+ );
+ }
+ }
+
+ fn new_row(&self) -> crate::row::Row {
+ crate::row::Row::new(self.size.cols)
+ }
+
+ pub fn clear(&mut self) {
+ self.pos = Pos::default();
+ self.saved_pos = Pos::default();
+ for row in self.drawing_rows_mut() {
+ row.clear(crate::attrs::Attrs::default());
+ }
+ self.scroll_top = 0;
+ self.scroll_bottom = self.size.rows - 1;
+ self.origin_mode = false;
+ self.saved_origin_mode = false;
+ }
+
+ pub fn clear_scrollback(&mut self) {
+ self.scrollback.clear();
+ self.scrollback_offset = 0;
+ }
+
+ pub fn size(&self) -> Size {
+ self.size
+ }
+
+ pub fn set_size(&mut self, size: Size) {
+ if size.cols != self.size.cols {
+ for row in &mut self.rows {
+ row.wrap(false);
+ }
+ }
+
+ if self.scroll_bottom == self.size.rows - 1 {
+ self.scroll_bottom = size.rows - 1;
+ }
+
+ self.size = size;
+ for row in &mut self.rows {
+ row.resize(size.cols, crate::Cell::new());
+ }
+ self.rows.resize(usize::from(size.rows), self.new_row());
+
+ if self.scroll_bottom >= size.rows {
+ self.scroll_bottom = size.rows - 1;
+ }
+ if self.scroll_bottom < self.scroll_top {
+ self.scroll_top = 0;
+ }
+
+ self.row_clamp_top(false);
+ self.row_clamp_bottom(false);
+ self.col_clamp();
+
+ if self.saved_pos.row > self.size.rows - 1 {
+ self.saved_pos.row = self.size.rows - 1;
+ }
+ if self.saved_pos.col > self.size.cols - 1 {
+ self.saved_pos.col = self.size.cols - 1;
+ }
+ }
+
+ pub fn pos(&self) -> Pos {
+ self.pos
+ }
+
+ pub fn set_pos(&mut self, mut pos: Pos) {
+ if self.origin_mode {
+ pos.row = pos.row.saturating_add(self.scroll_top);
+ }
+ self.pos = pos;
+ self.row_clamp_top(self.origin_mode);
+ self.row_clamp_bottom(self.origin_mode);
+ self.col_clamp();
+ }
+
+ pub fn save_cursor(&mut self) {
+ self.saved_pos = self.pos;
+ self.saved_origin_mode = self.origin_mode;
+ }
+
+ pub fn restore_cursor(&mut self) {
+ self.pos = self.saved_pos;
+ self.origin_mode = self.saved_origin_mode;
+ }
+
+ pub fn visible_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
+ let scrollback_len = self.scrollback.len();
+ let rows_len = self.rows.len();
+ self.scrollback
+ .iter()
+ .skip(scrollback_len - self.scrollback_offset)
+ // when scrollback_offset > rows_len (e.g. rows = 3,
+ // scrollback_len = 10, offset = 9) the skip(10 - 9)
+ // will take 9 rows instead of 3. we need to set
+ // the upper bound to rows_len (e.g. 3)
+ .take(rows_len)
+ // same for rows_len - scrollback_offset (e.g. 3 - 9).
+ // it'll panic with overflow. we have to saturate the subtraction.
+ .chain(
+ self.rows
+ .iter()
+ .take(rows_len.saturating_sub(self.scrollback_offset)),
+ )
+ }
+
+ pub fn drawing_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
+ self.rows.iter()
+ }
+
+ pub fn drawing_rows_mut(
+ &mut self,
+ ) -> impl Iterator<Item = &mut crate::row::Row> {
+ self.rows.iter_mut()
+ }
+
+ pub fn visible_row(&self, row: u16) -> Option<&crate::row::Row> {
+ self.visible_rows().nth(usize::from(row))
+ }
+
+ pub fn drawing_row(&self, row: u16) -> Option<&crate::row::Row> {
+ self.drawing_rows().nth(usize::from(row))
+ }
+
+ pub fn drawing_row_mut(
+ &mut self,
+ row: u16,
+ ) -> Option<&mut crate::row::Row> {
+ self.drawing_rows_mut().nth(usize::from(row))
+ }
+
+ pub fn current_row_mut(&mut self) -> &mut crate::row::Row {
+ self.drawing_row_mut(self.pos.row)
+ // we assume self.pos.row is always valid
+ .unwrap()
+ }
+
+ pub fn visible_cell(&self, pos: Pos) -> Option<&crate::Cell> {
+ self.visible_row(pos.row).and_then(|r| r.get(pos.col))
+ }
+
+ pub fn drawing_cell(&self, pos: Pos) -> Option<&crate::Cell> {
+ self.drawing_row(pos.row).and_then(|r| r.get(pos.col))
+ }
+
+ pub fn drawing_cell_mut(&mut self, pos: Pos) -> Option<&mut crate::Cell> {
+ self.drawing_row_mut(pos.row)
+ .and_then(|r| r.get_mut(pos.col))
+ }
+
+ pub fn scrollback_len(&self) -> usize {
+ self.scrollback_len
+ }
+
+ pub fn scrollback(&self) -> usize {
+ self.scrollback_offset
+ }
+
+ pub fn set_scrollback(&mut self, rows: usize) {
+ self.scrollback_offset = rows.min(self.scrollback.len());
+ }
+
+ pub fn write_contents(&self, contents: &mut String) {
+ let mut wrapping = false;
+ for row in self.visible_rows() {
+ row.write_contents(contents, 0, self.size.cols, wrapping);
+ if !row.wrapped() {
+ contents.push('\n');
+ }
+ wrapping = row.wrapped();
+ }
+
+ while contents.ends_with('\n') {
+ contents.truncate(contents.len() - 1);
+ }
+ }
+
+ pub fn write_contents_formatted(
+ &self,
+ contents: &mut Vec<u8>,
+ ) -> crate::attrs::Attrs {
+ crate::term::ClearAttrs.write_buf(contents);
+ crate::term::ClearScreen.write_buf(contents);
+
+ let mut prev_attrs = crate::attrs::Attrs::default();
+ let mut prev_pos = Pos::default();
+ let mut wrapping = false;
+ for (i, row) in self.visible_rows().enumerate() {
+ // we limit the number of cols to a u16 (see Size), so
+ // visible_rows() can never return more rows than will fit
+ let i = i.try_into().unwrap();
+ let (new_pos, new_attrs) = row.write_contents_formatted(
+ contents,
+ 0,
+ self.size.cols,
+ i,
+ wrapping,
+ Some(prev_pos),
+ Some(prev_attrs),
+ );
+ prev_pos = new_pos;
+ prev_attrs = new_attrs;
+ wrapping = row.wrapped();
+ }
+
+ self.write_cursor_position_formatted(
+ contents,
+ Some(prev_pos),
+ Some(prev_attrs),
+ );
+
+ prev_attrs
+ }
+
+ pub fn write_contents_diff(
+ &self,
+ contents: &mut Vec<u8>,
+ prev: &Self,
+ mut prev_attrs: crate::attrs::Attrs,
+ ) -> crate::attrs::Attrs {
+ let mut prev_pos = prev.pos;
+ let mut wrapping = false;
+ let mut prev_wrapping = false;
+ for (i, (row, prev_row)) in
+ self.visible_rows().zip(prev.visible_rows()).enumerate()
+ {
+ // we limit the number of cols to a u16 (see Size), so
+ // visible_rows() can never return more rows than will fit
+ let i = i.try_into().unwrap();
+ let (new_pos, new_attrs) = row.write_contents_diff(
+ contents,
+ prev_row,
+ 0,
+ self.size.cols,
+ i,
+ wrapping,
+ prev_wrapping,
+ prev_pos,
+ prev_attrs,
+ );
+ prev_pos = new_pos;
+ prev_attrs = new_attrs;
+ wrapping = row.wrapped();
+ prev_wrapping = prev_row.wrapped();
+ }
+
+ self.write_cursor_position_formatted(
+ contents,
+ Some(prev_pos),
+ Some(prev_attrs),
+ );
+
+ prev_attrs
+ }
+
+ pub fn write_cursor_position_formatted(
+ &self,
+ contents: &mut Vec<u8>,
+ prev_pos: Option<Pos>,
+ prev_attrs: Option<crate::attrs::Attrs>,
+ ) {
+ let prev_attrs = prev_attrs.unwrap_or_default();
+ // writing a character to the last column of a row doesn't wrap the
+ // cursor immediately - it waits until the next character is actually
+ // drawn. it is only possible for the cursor to have this kind of
+ // position after drawing a character though, so if we end in this
+ // position, we need to redraw the character at the end of the row.
+ if prev_pos != Some(self.pos) && self.pos.col >= self.size.cols {
+ let mut pos = Pos {
+ row: self.pos.row,
+ col: self.size.cols - 1,
+ };
+ if self
+ .drawing_cell(pos)
+ // we assume self.pos.row is always valid, and self.size.cols
+ // - 1 is always a valid column
+ .unwrap()
+ .is_wide_continuation()
+ {
+ pos.col = self.size.cols - 2;
+ }
+ let cell =
+ // we assume self.pos.row is always valid, and self.size.cols
+ // - 2 must be a valid column because self.size.cols - 1 is
+ // always valid and we just checked that the cell at
+ // self.size.cols - 1 is a wide continuation character, which
+ // means that the first half of the wide character must be
+ // before it
+ self.drawing_cell(pos).unwrap();
+ if cell.has_contents() {
+ if let Some(prev_pos) = prev_pos {
+ crate::term::MoveFromTo::new(prev_pos, pos)
+ .write_buf(contents);
+ } else {
+ crate::term::MoveTo::new(pos).write_buf(contents);
+ }
+ cell.attrs().write_escape_code_diff(contents, &prev_attrs);
+ contents.extend(cell.contents().as_bytes());
+ prev_attrs.write_escape_code_diff(contents, cell.attrs());
+ } else {
+ // if the cell doesn't have contents, we can't have gotten
+ // here by drawing a character in the last column. this means
+ // that as far as i'm aware, we have to have reached here from
+ // a newline when we were already after the end of an earlier
+ // row. in the case where we are already after the end of an
+ // earlier row, we can just write a few newlines, otherwise we
+ // also need to do the same as above to get ourselves to after
+ // the end of a row.
+ let mut found = false;
+ for i in (0..self.pos.row).rev() {
+ pos.row = i;
+ pos.col = self.size.cols - 1;
+ if self
+ .drawing_cell(pos)
+ // i is always less than self.pos.row, which we assume
+ // to be always valid, so it must also be valid.
+ // self.size.cols - 1 is always a valid col.
+ .unwrap()
+ .is_wide_continuation()
+ {
+ pos.col = self.size.cols - 2;
+ }
+ let cell = self
+ .drawing_cell(pos)
+ // i is always less than self.pos.row, which we assume
+ // to be always valid, so it must also be valid.
+ // self.size.cols - 2 is valid because self.size.cols
+ // - 1 is always valid, and col gets set to
+ // self.size.cols - 2 when the cell at self.size.cols
+ // - 1 is a wide continuation character, meaning that
+ // the first half of the wide character must be before
+ // it
+ .unwrap();
+ if cell.has_contents() {
+ if let Some(prev_pos) = prev_pos {
+ if prev_pos.row != i
+ || prev_pos.col < self.size.cols
+ {
+ crate::term::MoveFromTo::new(prev_pos, pos)
+ .write_buf(contents);
+ cell.attrs().write_escape_code_diff(
+ contents,
+ &prev_attrs,
+ );
+ contents.extend(cell.contents().as_bytes());
+ prev_attrs.write_escape_code_diff(
+ contents,
+ cell.attrs(),
+ );
+ }
+ } else {
+ crate::term::MoveTo::new(pos).write_buf(contents);
+ cell.attrs().write_escape_code_diff(
+ contents,
+ &prev_attrs,
+ );
+ contents.extend(cell.contents().as_bytes());
+ prev_attrs.write_escape_code_diff(
+ contents,
+ cell.attrs(),
+ );
+ }
+ contents.extend(
+ "\n".repeat(usize::from(self.pos.row - i))
+ .as_bytes(),
+ );
+ found = true;
+ break;
+ }
+ }
+
+ // this can happen if you get the cursor off the end of a row,
+ // and then do something to clear the end of the current row
+ // without moving the cursor (IL, DL, ED, EL, etc). we know
+ // there can't be something in the last column because we
+ // would have caught that above, so it should be safe to
+ // overwrite it.
+ if !found {
+ pos = Pos {
+ row: self.pos.row,
+ col: self.size.cols - 1,
+ };
+ if let Some(prev_pos) = prev_pos {
+ crate::term::MoveFromTo::new(prev_pos, pos)
+ .write_buf(contents);
+ } else {
+ crate::term::MoveTo::new(pos).write_buf(contents);
+ }
+ contents.push(b' ');
+ // we know that the cell has no contents, but it still may
+ // have drawing attributes (background color, etc)
+ let end_cell = self
+ .drawing_cell(pos)
+ // we assume self.pos.row is always valid, and
+ // self.size.cols - 1 is always a valid column
+ .unwrap();
+ end_cell
+ .attrs()
+ .write_escape_code_diff(contents, &prev_attrs);
+ crate::term::SaveCursor.write_buf(contents);
+ crate::term::Backspace.write_buf(contents);
+ crate::term::EraseChar::new(1).write_buf(contents);
+ crate::term::RestoreCursor.write_buf(contents);
+ prev_attrs
+ .write_escape_code_diff(contents, end_cell.attrs());
+ }
+ }
+ } else if let Some(prev_pos) = prev_pos {
+ crate::term::MoveFromTo::new(prev_pos, self.pos)
+ .write_buf(contents);
+ } else {
+ crate::term::MoveTo::new(self.pos).write_buf(contents);
+ }
+ }
+
+ pub fn erase_all(&mut self, attrs: crate::attrs::Attrs) {
+ for row in self.drawing_rows_mut() {
+ row.clear(attrs);
+ }
+ }
+
+ pub fn erase_all_forward(&mut self, attrs: crate::attrs::Attrs) {
+ let pos = self.pos;
+ for row in self.drawing_rows_mut().skip(usize::from(pos.row) + 1) {
+ row.clear(attrs);
+ }
+
+ self.erase_row_forward(attrs);
+ }
+
+ pub fn erase_all_backward(&mut self, attrs: crate::attrs::Attrs) {
+ let pos = self.pos;
+ for row in self.drawing_rows_mut().take(usize::from(pos.row)) {
+ row.clear(attrs);
+ }
+
+ self.erase_row_backward(attrs);
+ }
+
+ pub fn erase_row(&mut self, attrs: crate::attrs::Attrs) {
+ self.current_row_mut().clear(attrs);
+ }
+
+ pub fn erase_row_forward(&mut self, attrs: crate::attrs::Attrs) {
+ let size = self.size;
+ let pos = self.pos;
+ let row = self.current_row_mut();
+ for col in pos.col..size.cols {
+ row.erase(col, attrs);
+ }
+ }
+
+ pub fn erase_row_backward(&mut self, attrs: crate::attrs::Attrs) {
+ let size = self.size;
+ let pos = self.pos;
+ let row = self.current_row_mut();
+ for col in 0..=pos.col.min(size.cols - 1) {
+ row.erase(col, attrs);
+ }
+ }
+
+ pub fn insert_cells(&mut self, count: u16) {
+ let size = self.size;
+ let pos = self.pos;
+ let wide = pos.col < size.cols
+ && self
+ .drawing_cell(pos)
+ // we assume self.pos.row is always valid, and we know we are
+ // not off the end of a row because we just checked pos.col <
+ // size.cols
+ .unwrap()
+ .is_wide_continuation();
+ let row = self.current_row_mut();
+ for _ in 0..count {
+ if wide {
+ row.get_mut(pos.col).unwrap().set_wide_continuation(false);
+ }
+ row.insert(pos.col, crate::Cell::new());
+ if wide {
+ row.get_mut(pos.col).unwrap().set_wide_continuation(true);
+ }
+ }
+ row.truncate(size.cols);
+ }
+
+ pub fn delete_cells(&mut self, count: u16) {
+ let size = self.size;
+ let pos = self.pos;
+ let row = self.current_row_mut();
+ for _ in 0..(count.min(size.cols - pos.col)) {
+ row.remove(pos.col);
+ }
+ row.resize(size.cols, crate::Cell::new());
+ }
+
+ pub fn erase_cells(&mut self, count: u16, attrs: crate::attrs::Attrs) {
+ let size = self.size;
+ let pos = self.pos;
+ let row = self.current_row_mut();
+ for col in pos.col..((pos.col.saturating_add(count)).min(size.cols)) {
+ row.erase(col, attrs);
+ }
+ }
+
+ pub fn insert_lines(&mut self, count: u16) {
+ for _ in 0..count {
+ self.rows.remove(usize::from(self.scroll_bottom));
+ self.rows.insert(usize::from(self.pos.row), self.new_row());
+ // self.scroll_bottom is maintained to always be a valid row
+ self.rows[usize::from(self.scroll_bottom)].wrap(false);
+ }
+ }
+
+ pub fn delete_lines(&mut self, count: u16) {
+ for _ in 0..(count.min(self.size.rows - self.pos.row)) {
+ self.rows
+ .insert(usize::from(self.scroll_bottom) + 1, self.new_row());
+ self.rows.remove(usize::from(self.pos.row));
+ }
+ }
+
+ pub fn scroll_up(&mut self, count: u16) {
+ for _ in 0..(count.min(self.size.rows - self.scroll_top)) {
+ self.rows
+ .insert(usize::from(self.scroll_bottom) + 1, self.new_row());
+ let removed = self.rows.remove(usize::from(self.scroll_top));
+ if self.scrollback_len > 0 && self.scroll_top == 0 {
+ self.scrollback.push_back(removed);
+ while self.scrollback.len() > self.scrollback_len {
+ self.scrollback.pop_front();
+ }
+ if self.scrollback_offset > 0 {
+ self.scrollback_offset =
+ self.scrollback.len().min(self.scrollback_offset + 1);
+ }
+ }
+ }
+ }
+
+ pub fn scroll_down(&mut self, count: u16) {
+ for _ in 0..count {
+ self.rows.remove(usize::from(self.scroll_bottom));
+ self.rows
+ .insert(usize::from(self.scroll_top), self.new_row());
+ // self.scroll_bottom is maintained to always be a valid row
+ self.rows[usize::from(self.scroll_bottom)].wrap(false);
+ }
+ }
+
+ pub fn set_scroll_region(&mut self, top: u16, bottom: u16) {
+ let bottom = bottom.min(self.size().rows - 1);
+ if top < bottom {
+ self.scroll_top = top;
+ self.scroll_bottom = bottom;
+ } else {
+ self.scroll_top = 0;
+ self.scroll_bottom = self.size().rows - 1;
+ }
+ self.pos.row = self.scroll_top;
+ self.pos.col = 0;
+ }
+
+ fn in_scroll_region(&self) -> bool {
+ self.pos.row >= self.scroll_top && self.pos.row <= self.scroll_bottom
+ }
+
+ pub fn set_origin_mode(&mut self, mode: bool) {
+ self.origin_mode = mode;
+ self.set_pos(Pos { row: 0, col: 0 });
+ }
+
+ pub fn row_inc_clamp(&mut self, count: u16) {
+ let in_scroll_region = self.in_scroll_region();
+ self.pos.row = self.pos.row.saturating_add(count);
+ self.row_clamp_bottom(in_scroll_region);
+ }
+
+ pub fn row_inc_scroll(&mut self, count: u16) -> u16 {
+ let in_scroll_region = self.in_scroll_region();
+ self.pos.row = self.pos.row.saturating_add(count);
+ let lines = self.row_clamp_bottom(in_scroll_region);
+ if in_scroll_region {
+ self.scroll_up(lines);
+ lines
+ } else {
+ 0
+ }
+ }
+
+ pub fn row_dec_clamp(&mut self, count: u16) {
+ let in_scroll_region = self.in_scroll_region();
+ self.pos.row = self.pos.row.saturating_sub(count);
+ self.row_clamp_top(in_scroll_region);
+ }
+
+ pub fn row_dec_scroll(&mut self, count: u16) {
+ let in_scroll_region = self.in_scroll_region();
+ // need to account for clamping by both row_clamp_top and by
+ // saturating_sub
+ let extra_lines = count.saturating_sub(self.pos.row);
+ self.pos.row = self.pos.row.saturating_sub(count);
+ let lines = self.row_clamp_top(in_scroll_region);
+ self.scroll_down(lines + extra_lines);
+ }
+
+ pub fn row_set(&mut self, i: u16) {
+ self.pos.row = i;
+ self.row_clamp();
+ }
+
+ pub fn col_inc(&mut self, count: u16) {
+ self.pos.col = self.pos.col.saturating_add(count);
+ }
+
+ pub fn col_inc_clamp(&mut self, count: u16) {
+ self.pos.col = self.pos.col.saturating_add(count);
+ self.col_clamp();
+ }
+
+ pub fn col_dec(&mut self, count: u16) {
+ self.pos.col = self.pos.col.saturating_sub(count);
+ }
+
+ pub fn col_tab(&mut self) {
+ self.pos.col -= self.pos.col % 8;
+ self.pos.col += 8;
+ self.col_clamp();
+ }
+
+ pub fn col_set(&mut self, i: u16) {
+ self.pos.col = i;
+ self.col_clamp();
+ }
+
+ pub fn col_wrap(&mut self, width: u16, wrap: bool) {
+ if self.pos.col > self.size.cols - width {
+ let mut prev_pos = self.pos;
+ self.pos.col = 0;
+ let scrolled = self.row_inc_scroll(1);
+ prev_pos.row -= scrolled;
+ let new_pos = self.pos;
+ self.drawing_row_mut(prev_pos.row)
+ // we assume self.pos.row is always valid, and so prev_pos.row
+ // must be valid because it is always less than or equal to
+ // self.pos.row
+ .unwrap()
+ .wrap(wrap && prev_pos.row + 1 == new_pos.row);
+ }
+ }
+
+ fn row_clamp_top(&mut self, limit_to_scroll_region: bool) -> u16 {
+ if limit_to_scroll_region && self.pos.row < self.scroll_top {
+ let rows = self.scroll_top - self.pos.row;
+ self.pos.row = self.scroll_top;
+ rows
+ } else {
+ 0
+ }
+ }
+
+ fn row_clamp_bottom(&mut self, limit_to_scroll_region: bool) -> u16 {
+ let bottom = if limit_to_scroll_region {
+ self.scroll_bottom
+ } else {
+ self.size.rows - 1
+ };
+ if self.pos.row > bottom {
+ let rows = self.pos.row - bottom;
+ self.pos.row = bottom;
+ rows
+ } else {
+ 0
+ }
+ }
+
+ fn row_clamp(&mut self) {
+ if self.pos.row > self.size.rows - 1 {
+ self.pos.row = self.size.rows - 1;
+ }
+ }
+
+ fn col_clamp(&mut self) {
+ if self.pos.col > self.size.cols - 1 {
+ self.pos.col = self.size.cols - 1;
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub struct Size {
+ pub rows: u16,
+ pub cols: u16,
+}
+
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub struct Pos {
+ pub row: u16,
+ pub col: u16,
+}
diff --git a/vendor/vt100/src/lib.rs b/vendor/vt100/src/lib.rs
new file mode 100644
index 00000000..19746a94
--- /dev/null
+++ b/vendor/vt100/src/lib.rs
@@ -0,0 +1,64 @@
+//! This crate parses a terminal byte stream and provides an in-memory
+//! representation of the rendered contents.
+//!
+//! # Overview
+//!
+//! This is essentially the terminal parser component of a graphical terminal
+//! emulator pulled out into a separate crate. Although you can use this crate
+//! to build a graphical terminal emulator, it also contains functionality
+//! necessary for implementing terminal applications that want to run other
+//! terminal applications - programs like `screen` or `tmux` for example.
+//!
+//! # Synopsis
+//!
+//! ```
+//! let mut parser = vt100::Parser::new(24, 80, 0);
+//!
+//! let screen = parser.screen().clone();
+//! parser.process(b"this text is \x1b[31mRED\x1b[m");
+//! assert_eq!(
+//! parser.screen().cell(0, 13).unwrap().fgcolor(),
+//! vt100::Color::Idx(1),
+//! );
+//!
+//! let screen = parser.screen().clone();
+//! parser.process(b"\x1b[3D\x1b[32mGREEN");
+//! assert_eq!(
+//! parser.screen().contents_formatted(),
+//! &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jthis text is \x1b[32mGREEN"[..],
+//! );
+//! assert_eq!(
+//! parser.screen().contents_diff(&screen),
+//! &b"\x1b[1;14H\x1b[32mGREEN"[..],
+//! );
+//! ```
+
+#![warn(missing_docs)]
+#![warn(clippy::cargo)]
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+#![warn(clippy::as_conversions)]
+#![warn(clippy::get_unwrap)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::missing_const_for_fn)]
+#![allow(clippy::similar_names)]
+#![allow(clippy::struct_excessive_bools)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::type_complexity)]
+
+mod attrs;
+mod callbacks;
+mod cell;
+mod grid;
+mod parser;
+mod perform;
+mod row;
+mod screen;
+mod term;
+
+pub use attrs::Color;
+pub use callbacks::Callbacks;
+pub use cell::Cell;
+pub use parser::Parser;
+pub use screen::{MouseProtocolEncoding, MouseProtocolMode, Screen};
diff --git a/vendor/vt100/src/parser.rs b/vendor/vt100/src/parser.rs
new file mode 100644
index 00000000..c81495b8
--- /dev/null
+++ b/vendor/vt100/src/parser.rs
@@ -0,0 +1,96 @@
+/// A parser for terminal output which produces an in-memory representation of
+/// the terminal contents.
+pub struct Parser<CB: crate::callbacks::Callbacks = ()> {
+ parser: vte::Parser,
+ screen: crate::perform::WrappedScreen<CB>,
+}
+
+impl Parser {
+ /// Creates a new terminal parser of the given size and with the given
+ /// amount of scrollback.
+ #[must_use]
+ pub fn new(rows: u16, cols: u16, scrollback_len: usize) -> Self {
+ Self {
+ parser: vte::Parser::new(),
+ screen: crate::perform::WrappedScreen::new(
+ rows,
+ cols,
+ scrollback_len,
+ ),
+ }
+ }
+}
+
+impl<CB: crate::callbacks::Callbacks> Parser<CB> {
+ /// Creates a new terminal parser of the given size and with the given
+ /// amount of scrollback. Terminal events will be reported via method
+ /// calls on the provided [`Callbacks`](crate::callbacks::Callbacks)
+ /// implementation.
+ pub fn new_with_callbacks(
+ rows: u16,
+ cols: u16,
+ scrollback_len: usize,
+ callbacks: CB,
+ ) -> Self {
+ Self {
+ parser: vte::Parser::new(),
+ screen: crate::perform::WrappedScreen::new_with_callbacks(
+ rows,
+ cols,
+ scrollback_len,
+ callbacks,
+ ),
+ }
+ }
+
+ /// Processes the contents of the given byte string, and updates the
+ /// in-memory terminal state.
+ pub fn process(&mut self, bytes: &[u8]) {
+ self.parser.advance(&mut self.screen, bytes);
+ }
+
+ /// Returns a reference to a [`Screen`](crate::Screen) object containing
+ /// the terminal state.
+ #[must_use]
+ pub fn screen(&self) -> &crate::Screen {
+ &self.screen.screen
+ }
+
+ /// Returns a mutable reference to a [`Screen`](crate::Screen) object
+ /// containing the terminal state.
+ #[must_use]
+ pub fn screen_mut(&mut self) -> &mut crate::Screen {
+ &mut self.screen.screen
+ }
+
+ /// Returns a reference to the [`Callbacks`](crate::callbacks::Callbacks)
+ /// state object passed into the constructor.
+ pub fn callbacks(&self) -> &CB {
+ &self.screen.callbacks
+ }
+
+ /// Returns a mutable reference to the
+ /// [`Callbacks`](crate::callbacks::Callbacks) state object passed into
+ /// the constructor.
+ pub fn callbacks_mut(&mut self) -> &mut CB {
+ &mut self.screen.callbacks
+ }
+}
+
+impl Default for Parser {
+ /// Returns a parser with dimensions 80x24 and no scrollback.
+ fn default() -> Self {
+ Self::new(24, 80, 0)
+ }
+}
+
+impl std::io::Write for Parser {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.process(buf);
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ Ok(())
+ }
+}
diff --git a/vendor/vt100/src/perform.rs b/vendor/vt100/src/perform.rs
new file mode 100644
index 00000000..e36cfd83
--- /dev/null
+++ b/vendor/vt100/src/perform.rs
@@ -0,0 +1,277 @@
+const BASE64: &[u8] =
+ b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+const CLIPBOARD_SELECTOR: &[u8] = b"cpqs01234567";
+
+pub struct WrappedScreen<CB: crate::callbacks::Callbacks = ()> {
+ pub screen: crate::screen::Screen,
+ pub callbacks: CB,
+}
+
+impl WrappedScreen<()> {
+ pub fn new(rows: u16, cols: u16, scrollback_len: usize) -> Self {
+ Self::new_with_callbacks(rows, cols, scrollback_len, ())
+ }
+}
+
+impl<CB: crate::callbacks::Callbacks> WrappedScreen<CB> {
+ pub fn new_with_callbacks(
+ rows: u16,
+ cols: u16,
+ scrollback_len: usize,
+ callbacks: CB,
+ ) -> Self {
+ Self {
+ screen: crate::screen::Screen::new(
+ crate::grid::Size { rows, cols },
+ scrollback_len,
+ ),
+ callbacks,
+ }
+ }
+}
+
+impl<CB: crate::callbacks::Callbacks> vte::Perform for WrappedScreen<CB> {
+ fn print(&mut self, c: char) {
+ if c == '\u{fffd}' || ('\u{80}'..'\u{a0}').contains(&c) {
+ self.callbacks.unhandled_char(&mut self.screen, c);
+ } else {
+ self.screen.text(c);
+ }
+ }
+
+ fn execute(&mut self, b: u8) {
+ match b {
+ 7 => self.callbacks.audible_bell(&mut self.screen),
+ 8 => self.screen.bs(),
+ 9 => self.screen.tab(),
+ 10 => self.screen.lf(),
+ 11 => self.screen.vt(),
+ 12 => self.screen.ff(),
+ 13 => self.screen.cr(),
+ // we don't implement shift in/out alternate character sets, but
+ // it shouldn't count as an "error"
+ 14 | 15 => {}
+ _ => self.callbacks.unhandled_control(&mut self.screen, b),
+ }
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, b: u8) {
+ if let Some(i) = intermediates.first() {
+ self.callbacks.unhandled_escape(
+ &mut self.screen,
+ Some(*i),
+ intermediates.get(1).copied(),
+ b,
+ );
+ } else {
+ match b {
+ b'7' => self.screen.decsc(),
+ b'8' => self.screen.decrc(),
+ b'=' => self.screen.deckpam(),
+ b'>' => self.screen.deckpnm(),
+ b'M' => self.screen.ri(),
+ b'c' => self.screen.ris(),
+ b'g' => self.callbacks.visual_bell(&mut self.screen),
+ _ => {
+ self.callbacks.unhandled_escape(
+ &mut self.screen,
+ None,
+ None,
+ b,
+ );
+ }
+ }
+ }
+ }
+
+ fn csi_dispatch(
+ &mut self,
+ params: &vte::Params,
+ intermediates: &[u8],
+ _ignore: bool,
+ c: char,
+ ) {
+ let unhandled = |screen: &mut crate::screen::Screen| {
+ self.callbacks.unhandled_csi(
+ screen,
+ intermediates.first().copied(),
+ intermediates.get(1).copied(),
+ &params.iter().collect::<Vec<_>>(),
+ c,
+ );
+ };
+ match intermediates.first() {
+ None => match c {
+ '@' => self.screen.ich(canonicalize_params_1(params, 1)),
+ 'A' => self.screen.cuu(canonicalize_params_1(params, 1)),
+ 'B' => self.screen.cud(canonicalize_params_1(params, 1)),
+ 'C' => self.screen.cuf(canonicalize_params_1(params, 1)),
+ 'D' => self.screen.cub(canonicalize_params_1(params, 1)),
+ 'E' => self.screen.cnl(canonicalize_params_1(params, 1)),
+ 'F' => self.screen.cpl(canonicalize_params_1(params, 1)),
+ 'G' => self.screen.cha(canonicalize_params_1(params, 1)),
+ 'H' => self.screen.cup(canonicalize_params_2(params, 1, 1)),
+ 'J' => self
+ .screen
+ .ed(canonicalize_params_1(params, 0), unhandled),
+ 'K' => self
+ .screen
+ .el(canonicalize_params_1(params, 0), unhandled),
+ 'L' => self.screen.il(canonicalize_params_1(params, 1)),
+ 'M' => self.screen.dl(canonicalize_params_1(params, 1)),
+ 'P' => self.screen.dch(canonicalize_params_1(params, 1)),
+ 'S' => self.screen.su(canonicalize_params_1(params, 1)),
+ 'T' => self.screen.sd(canonicalize_params_1(params, 1)),
+ 'X' => self.screen.ech(canonicalize_params_1(params, 1)),
+ 'd' => self.screen.vpa(canonicalize_params_1(params, 1)),
+ 'm' => self.screen.sgr(params, unhandled),
+ 'r' => self.screen.decstbm(canonicalize_params_decstbm(
+ params,
+ self.screen.grid().size(),
+ )),
+ 't' => {
+ let mut params_iter = params.iter();
+ let op =
+ params_iter.next().and_then(|x| x.first().copied());
+ if op == Some(8) {
+ let (screen_rows, screen_cols) = self.screen.size();
+ let rows =
+ params_iter.next().map_or(screen_rows, |x| {
+ *x.first().unwrap_or(&screen_rows)
+ });
+ let cols =
+ params_iter.next().map_or(screen_cols, |x| {
+ *x.first().unwrap_or(&screen_cols)
+ });
+ self.callbacks.resize(&mut self.screen, (rows, cols));
+ } else {
+ self.callbacks.unhandled_csi(
+ &mut self.screen,
+ None,
+ None,
+ &params.iter().collect::<Vec<_>>(),
+ c,
+ );
+ }
+ }
+ _ => {
+ self.callbacks.unhandled_csi(
+ &mut self.screen,
+ None,
+ None,
+ &params.iter().collect::<Vec<_>>(),
+ c,
+ );
+ }
+ },
+ Some(b'?') => match c {
+ 'J' => self
+ .screen
+ .decsed(canonicalize_params_1(params, 0), unhandled),
+ 'K' => self
+ .screen
+ .decsel(canonicalize_params_1(params, 0), unhandled),
+ 'h' => self.screen.decset(params, unhandled),
+ 'l' => self.screen.decrst(params, unhandled),
+ _ => {
+ self.callbacks.unhandled_csi(
+ &mut self.screen,
+ Some(b'?'),
+ intermediates.get(1).copied(),
+ &params.iter().collect::<Vec<_>>(),
+ c,
+ );
+ }
+ },
+ Some(i) => {
+ self.callbacks.unhandled_csi(
+ &mut self.screen,
+ Some(*i),
+ intermediates.get(1).copied(),
+ &params.iter().collect::<Vec<_>>(),
+ c,
+ );
+ }
+ }
+ }
+
+ fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) {
+ match params {
+ [b"0", s] => {
+ self.callbacks.set_window_icon_name(&mut self.screen, s);
+ self.callbacks.set_window_title(&mut self.screen, s);
+ }
+ [b"1", s] => {
+ self.callbacks.set_window_icon_name(&mut self.screen, s);
+ }
+ [b"2", s] => {
+ self.callbacks.set_window_title(&mut self.screen, s);
+ }
+ [b"52", ty, data] => {
+ match (
+ ty.iter().all(|c| CLIPBOARD_SELECTOR.contains(c)),
+ *data,
+ ) {
+ (true, b"?") => {
+ self.callbacks
+ .paste_from_clipboard(&mut self.screen, ty);
+ }
+ (true, data)
+ if data.iter().all(|c| BASE64.contains(c)) =>
+ {
+ self.callbacks.copy_to_clipboard(
+ &mut self.screen,
+ ty,
+ data,
+ );
+ }
+ _ => {
+ self.callbacks
+ .unhandled_osc(&mut self.screen, params);
+ }
+ }
+ }
+ _ => {
+ self.callbacks.unhandled_osc(&mut self.screen, params);
+ }
+ }
+ }
+}
+
+fn canonicalize_params_1(params: &vte::Params, default: u16) -> u16 {
+ let first = params.iter().next().map_or(0, |x| *x.first().unwrap_or(&0));
+ if first == 0 {
+ default
+ } else {
+ first
+ }
+}
+
+fn canonicalize_params_2(
+ params: &vte::Params,
+ default1: u16,
+ default2: u16,
+) -> (u16, u16) {
+ let mut iter = params.iter();
+ let first = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
+ let first = if first == 0 { default1 } else { first };
+
+ let second = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
+ let second = if second == 0 { default2 } else { second };
+
+ (first, second)
+}
+
+fn canonicalize_params_decstbm(
+ params: &vte::Params,
+ size: crate::grid::Size,
+) -> (u16, u16) {
+ let mut iter = params.iter();
+ let top = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
+ let top = if top == 0 { 1 } else { top };
+
+ let bottom = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
+ let bottom = if bottom == 0 { size.rows } else { bottom };
+
+ (top, bottom)
+}
diff --git a/vendor/vt100/src/row.rs b/vendor/vt100/src/row.rs
new file mode 100644
index 00000000..4ae8d868
--- /dev/null
+++ b/vendor/vt100/src/row.rs
@@ -0,0 +1,474 @@
+use crate::term::BufWrite as _;
+
+#[derive(Clone, Debug)]
+pub struct Row {
+ cells: Vec<crate::Cell>,
+ wrapped: bool,
+}
+
+impl Row {
+ pub fn new(cols: u16) -> Self {
+ Self {
+ cells: vec![crate::Cell::new(); usize::from(cols)],
+ wrapped: false,
+ }
+ }
+
+ fn cols(&self) -> u16 {
+ self.cells
+ .len()
+ .try_into()
+ // we limit the number of cols to a u16 (see Size)
+ .unwrap()
+ }
+
+ pub fn clear(&mut self, attrs: crate::attrs::Attrs) {
+ for cell in &mut self.cells {
+ cell.clear(attrs);
+ }
+ self.wrapped = false;
+ }
+
+ fn cells(&self) -> impl Iterator<Item = &crate::Cell> {
+ self.cells.iter()
+ }
+
+ pub fn get(&self, col: u16) -> Option<&crate::Cell> {
+ self.cells.get(usize::from(col))
+ }
+
+ pub fn get_mut(&mut self, col: u16) -> Option<&mut crate::Cell> {
+ self.cells.get_mut(usize::from(col))
+ }
+
+ pub fn insert(&mut self, i: u16, cell: crate::Cell) {
+ self.cells.insert(usize::from(i), cell);
+ self.wrapped = false;
+ }
+
+ pub fn remove(&mut self, i: u16) {
+ self.clear_wide(i);
+ self.cells.remove(usize::from(i));
+ self.wrapped = false;
+ }
+
+ pub fn erase(&mut self, i: u16, attrs: crate::attrs::Attrs) {
+ let wide = self.cells[usize::from(i)].is_wide();
+ self.clear_wide(i);
+ self.cells[usize::from(i)].clear(attrs);
+ if i == self.cols() - if wide { 2 } else { 1 } {
+ self.wrapped = false;
+ }
+ }
+
+ pub fn truncate(&mut self, len: u16) {
+ self.cells.truncate(usize::from(len));
+ self.wrapped = false;
+ let last_cell = &mut self.cells[usize::from(len) - 1];
+ if last_cell.is_wide() {
+ last_cell.clear(*last_cell.attrs());
+ }
+ }
+
+ pub fn resize(&mut self, len: u16, cell: crate::Cell) {
+ self.cells.resize(usize::from(len), cell);
+ self.wrapped = false;
+ }
+
+ pub fn wrap(&mut self, wrap: bool) {
+ self.wrapped = wrap;
+ }
+
+ pub fn wrapped(&self) -> bool {
+ self.wrapped
+ }
+
+ pub fn clear_wide(&mut self, col: u16) {
+ let cell = &self.cells[usize::from(col)];
+ let other = if cell.is_wide() {
+ &mut self.cells[usize::from(col + 1)]
+ } else if cell.is_wide_continuation() {
+ &mut self.cells[usize::from(col - 1)]
+ } else {
+ return;
+ };
+ other.clear(*other.attrs());
+ }
+
+ pub fn write_contents(
+ &self,
+ contents: &mut String,
+ start: u16,
+ width: u16,
+ wrapping: bool,
+ ) {
+ let mut prev_was_wide = false;
+
+ let mut prev_col = start;
+ for (col, cell) in self
+ .cells()
+ .enumerate()
+ .skip(usize::from(start))
+ .take(usize::from(width))
+ {
+ if prev_was_wide {
+ prev_was_wide = false;
+ continue;
+ }
+ prev_was_wide = cell.is_wide();
+
+ // we limit the number of cols to a u16 (see Size)
+ let col: u16 = col.try_into().unwrap();
+ if cell.has_contents() {
+ for _ in 0..(col - prev_col) {
+ contents.push(' ');
+ }
+ prev_col += col - prev_col;
+
+ contents.push_str(cell.contents());
+ prev_col += if cell.is_wide() { 2 } else { 1 };
+ }
+ }
+ if prev_col == start && wrapping {
+ contents.push('\n');
+ }
+ }
+
+ pub fn write_contents_formatted(
+ &self,
+ contents: &mut Vec<u8>,
+ start: u16,
+ width: u16,
+ row: u16,
+ wrapping: bool,
+ prev_pos: Option<crate::grid::Pos>,
+ prev_attrs: Option<crate::attrs::Attrs>,
+ ) -> (crate::grid::Pos, crate::attrs::Attrs) {
+ let mut prev_was_wide = false;
+ let default_cell = crate::Cell::new();
+
+ let mut prev_pos = prev_pos.unwrap_or_else(|| {
+ if wrapping {
+ crate::grid::Pos {
+ row: row - 1,
+ col: self.cols(),
+ }
+ } else {
+ crate::grid::Pos { row, col: start }
+ }
+ });
+ let mut prev_attrs = prev_attrs.unwrap_or_default();
+
+ let first_cell = &self.cells[usize::from(start)];
+ if wrapping && first_cell == &default_cell {
+ let default_attrs = default_cell.attrs();
+ if &prev_attrs != default_attrs {
+ default_attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *default_attrs;
+ }
+ contents.push(b' ');
+ crate::term::Backspace.write_buf(contents);
+ crate::term::EraseChar::new(1).write_buf(contents);
+ prev_pos = crate::grid::Pos { row, col: 0 };
+ }
+
+ let mut erase: Option<(u16, &crate::attrs::Attrs)> = None;
+ for (col, cell) in self
+ .cells()
+ .enumerate()
+ .skip(usize::from(start))
+ .take(usize::from(width))
+ {
+ if prev_was_wide {
+ prev_was_wide = false;
+ continue;
+ }
+ prev_was_wide = cell.is_wide();
+
+ // we limit the number of cols to a u16 (see Size)
+ let col: u16 = col.try_into().unwrap();
+ let pos = crate::grid::Pos { row, col };
+
+ if let Some((prev_col, attrs)) = erase {
+ if cell.has_contents() || cell.attrs() != attrs {
+ let new_pos = crate::grid::Pos { row, col: prev_col };
+ if wrapping
+ && prev_pos.row + 1 == new_pos.row
+ && prev_pos.col >= self.cols()
+ {
+ if new_pos.col > 0 {
+ contents.extend(
+ " ".repeat(usize::from(new_pos.col))
+ .as_bytes(),
+ );
+ } else {
+ contents.extend(b" ");
+ crate::term::Backspace.write_buf(contents);
+ }
+ } else {
+ crate::term::MoveFromTo::new(prev_pos, new_pos)
+ .write_buf(contents);
+ }
+ prev_pos = new_pos;
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+ crate::term::EraseChar::new(pos.col - prev_col)
+ .write_buf(contents);
+ erase = None;
+ }
+ }
+
+ if cell != &default_cell {
+ let attrs = cell.attrs();
+ if cell.has_contents() {
+ if pos != prev_pos {
+ if !wrapping
+ || prev_pos.row + 1 != pos.row
+ || prev_pos.col
+ < self.cols() - u16::from(cell.is_wide())
+ || pos.col != 0
+ {
+ crate::term::MoveFromTo::new(prev_pos, pos)
+ .write_buf(contents);
+ }
+ prev_pos = pos;
+ }
+
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+
+ prev_pos.col += if cell.is_wide() { 2 } else { 1 };
+ let cell_contents = cell.contents();
+ contents.extend(cell_contents.as_bytes());
+ } else if erase.is_none() {
+ erase = Some((pos.col, attrs));
+ }
+ }
+ }
+ if let Some((prev_col, attrs)) = erase {
+ let new_pos = crate::grid::Pos { row, col: prev_col };
+ if wrapping
+ && prev_pos.row + 1 == new_pos.row
+ && prev_pos.col >= self.cols()
+ {
+ if new_pos.col > 0 {
+ contents.extend(
+ " ".repeat(usize::from(new_pos.col)).as_bytes(),
+ );
+ } else {
+ contents.extend(b" ");
+ crate::term::Backspace.write_buf(contents);
+ }
+ } else {
+ crate::term::MoveFromTo::new(prev_pos, new_pos)
+ .write_buf(contents);
+ }
+ prev_pos = new_pos;
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+ crate::term::ClearRowForward.write_buf(contents);
+ }
+
+ (prev_pos, prev_attrs)
+ }
+
+ // while it's true that most of the logic in this is identical to
+ // write_contents_formatted, i can't figure out how to break out the
+ // common parts without making things noticeably slower.
+ pub fn write_contents_diff(
+ &self,
+ contents: &mut Vec<u8>,
+ prev: &Self,
+ start: u16,
+ width: u16,
+ row: u16,
+ wrapping: bool,
+ prev_wrapping: bool,
+ mut prev_pos: crate::grid::Pos,
+ mut prev_attrs: crate::attrs::Attrs,
+ ) -> (crate::grid::Pos, crate::attrs::Attrs) {
+ let mut prev_was_wide = false;
+
+ let first_cell = &self.cells[usize::from(start)];
+ let prev_first_cell = &prev.cells[usize::from(start)];
+ if wrapping
+ && !prev_wrapping
+ && first_cell == prev_first_cell
+ && prev_pos.row + 1 == row
+ && prev_pos.col
+ >= self.cols() - u16::from(prev_first_cell.is_wide())
+ {
+ let first_cell_attrs = first_cell.attrs();
+ if &prev_attrs != first_cell_attrs {
+ first_cell_attrs
+ .write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *first_cell_attrs;
+ }
+ let mut cell_contents = prev_first_cell.contents();
+ let need_erase = if cell_contents.is_empty() {
+ cell_contents = " ";
+ true
+ } else {
+ false
+ };
+ contents.extend(cell_contents.as_bytes());
+ crate::term::Backspace.write_buf(contents);
+ if prev_first_cell.is_wide() {
+ crate::term::Backspace.write_buf(contents);
+ }
+ if need_erase {
+ crate::term::EraseChar::new(1).write_buf(contents);
+ }
+ prev_pos = crate::grid::Pos { row, col: 0 };
+ }
+
+ let mut erase: Option<(u16, &crate::attrs::Attrs)> = None;
+ for (col, (cell, prev_cell)) in self
+ .cells()
+ .zip(prev.cells())
+ .enumerate()
+ .skip(usize::from(start))
+ .take(usize::from(width))
+ {
+ if prev_was_wide {
+ prev_was_wide = false;
+ continue;
+ }
+ prev_was_wide = cell.is_wide();
+
+ // we limit the number of cols to a u16 (see Size)
+ let col: u16 = col.try_into().unwrap();
+ let pos = crate::grid::Pos { row, col };
+
+ if let Some((prev_col, attrs)) = erase {
+ if cell.has_contents() || cell.attrs() != attrs {
+ let new_pos = crate::grid::Pos { row, col: prev_col };
+ if wrapping
+ && prev_pos.row + 1 == new_pos.row
+ && prev_pos.col >= self.cols()
+ {
+ if new_pos.col > 0 {
+ contents.extend(
+ " ".repeat(usize::from(new_pos.col))
+ .as_bytes(),
+ );
+ } else {
+ contents.extend(b" ");
+ crate::term::Backspace.write_buf(contents);
+ }
+ } else {
+ crate::term::MoveFromTo::new(prev_pos, new_pos)
+ .write_buf(contents);
+ }
+ prev_pos = new_pos;
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+ crate::term::EraseChar::new(pos.col - prev_col)
+ .write_buf(contents);
+ erase = None;
+ }
+ }
+
+ if cell != prev_cell {
+ let attrs = cell.attrs();
+ if cell.has_contents() {
+ if pos != prev_pos {
+ if !wrapping
+ || prev_pos.row + 1 != pos.row
+ || prev_pos.col
+ < self.cols() - u16::from(cell.is_wide())
+ || pos.col != 0
+ {
+ crate::term::MoveFromTo::new(prev_pos, pos)
+ .write_buf(contents);
+ }
+ prev_pos = pos;
+ }
+
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+
+ prev_pos.col += if cell.is_wide() { 2 } else { 1 };
+ contents.extend(cell.contents().as_bytes());
+ } else if erase.is_none() {
+ erase = Some((pos.col, attrs));
+ }
+ }
+ }
+ if let Some((prev_col, attrs)) = erase {
+ let new_pos = crate::grid::Pos { row, col: prev_col };
+ if wrapping
+ && prev_pos.row + 1 == new_pos.row
+ && prev_pos.col >= self.cols()
+ {
+ if new_pos.col > 0 {
+ contents.extend(
+ " ".repeat(usize::from(new_pos.col)).as_bytes(),
+ );
+ } else {
+ contents.extend(b" ");
+ crate::term::Backspace.write_buf(contents);
+ }
+ } else {
+ crate::term::MoveFromTo::new(prev_pos, new_pos)
+ .write_buf(contents);
+ }
+ prev_pos = new_pos;
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+ crate::term::ClearRowForward.write_buf(contents);
+ }
+
+ // if this row is going from wrapped to not wrapped, we need to erase
+ // and redraw the last character to break wrapping. if this row is
+ // wrapped, we need to redraw the last character without erasing it to
+ // position the cursor after the end of the line correctly so that
+ // drawing the next line can just start writing and be wrapped.
+ if (!self.wrapped && prev.wrapped) || (!prev.wrapped && self.wrapped)
+ {
+ let end_pos = if self.cells[usize::from(self.cols() - 1)]
+ .is_wide_continuation()
+ {
+ crate::grid::Pos {
+ row,
+ col: self.cols() - 2,
+ }
+ } else {
+ crate::grid::Pos {
+ row,
+ col: self.cols() - 1,
+ }
+ };
+ crate::term::MoveFromTo::new(prev_pos, end_pos)
+ .write_buf(contents);
+ prev_pos = end_pos;
+ if !self.wrapped {
+ crate::term::EraseChar::new(1).write_buf(contents);
+ }
+ let end_cell = &self.cells[usize::from(end_pos.col)];
+ if end_cell.has_contents() {
+ let attrs = end_cell.attrs();
+ if &prev_attrs != attrs {
+ attrs.write_escape_code_diff(contents, &prev_attrs);
+ prev_attrs = *attrs;
+ }
+ contents.extend(end_cell.contents().as_bytes());
+ prev_pos.col += if end_cell.is_wide() { 2 } else { 1 };
+ }
+ }
+
+ (prev_pos, prev_attrs)
+ }
+}
diff --git a/vendor/vt100/src/screen.rs b/vendor/vt100/src/screen.rs
new file mode 100644
index 00000000..a11ed776
--- /dev/null
+++ b/vendor/vt100/src/screen.rs
@@ -0,0 +1,1356 @@
+use crate::term::BufWrite as _;
+use unicode_width::UnicodeWidthChar as _;
+
+const MODE_APPLICATION_KEYPAD: u8 = 0b0000_0001;
+const MODE_APPLICATION_CURSOR: u8 = 0b0000_0010;
+const MODE_HIDE_CURSOR: u8 = 0b0000_0100;
+const MODE_ALTERNATE_SCREEN: u8 = 0b0000_1000;
+const MODE_BRACKETED_PASTE: u8 = 0b0001_0000;
+
+/// The xterm mouse handling mode currently in use.
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
+pub enum MouseProtocolMode {
+ /// Mouse handling is disabled.
+ #[default]
+ None,
+
+ /// Mouse button events should be reported on button press. Also known as
+ /// X10 mouse mode.
+ Press,
+
+ /// Mouse button events should be reported on button press and release.
+ /// Also known as VT200 mouse mode.
+ PressRelease,
+
+ // Highlight,
+ /// Mouse button events should be reported on button press and release, as
+ /// well as when the mouse moves between cells while a button is held
+ /// down.
+ ButtonMotion,
+
+ /// Mouse button events should be reported on button press and release,
+ /// and mouse motion events should be reported when the mouse moves
+ /// between cells regardless of whether a button is held down or not.
+ AnyMotion,
+ // DecLocator,
+}
+
+/// The encoding to use for the enabled [`MouseProtocolMode`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
+pub enum MouseProtocolEncoding {
+ /// Default single-printable-byte encoding.
+ #[default]
+ Default,
+
+ /// UTF-8-based encoding.
+ Utf8,
+
+ /// SGR-like encoding.
+ Sgr,
+ // Urxvt,
+}
+
+/// Represents the overall terminal state.
+#[derive(Clone, Debug)]
+pub struct Screen {
+ grid: crate::grid::Grid,
+ alternate_grid: crate::grid::Grid,
+
+ attrs: crate::attrs::Attrs,
+ saved_attrs: crate::attrs::Attrs,
+
+ modes: u8,
+ mouse_protocol_mode: MouseProtocolMode,
+ mouse_protocol_encoding: MouseProtocolEncoding,
+}
+
+impl Screen {
+ pub(crate) fn new(
+ size: crate::grid::Size,
+ scrollback_len: usize,
+ ) -> Self {
+ let mut grid = crate::grid::Grid::new(size, scrollback_len);
+ grid.allocate_rows();
+ Self {
+ grid,
+ alternate_grid: crate::grid::Grid::new(size, scrollback_len),
+
+ attrs: crate::attrs::Attrs::default(),
+ saved_attrs: crate::attrs::Attrs::default(),
+
+ modes: 0,
+ mouse_protocol_mode: MouseProtocolMode::default(),
+ mouse_protocol_encoding: MouseProtocolEncoding::default(),
+ }
+ }
+
+ /// Resizes the terminal.
+ pub fn set_size(&mut self, rows: u16, cols: u16) {
+ self.grid.set_size(crate::grid::Size { rows, cols });
+ self.alternate_grid
+ .set_size(crate::grid::Size { rows, cols });
+ }
+
+ /// Returns the current size of the terminal.
+ ///
+ /// The return value will be (rows, cols).
+ #[must_use]
+ pub fn size(&self) -> (u16, u16) {
+ let size = self.grid().size();
+ (size.rows, size.cols)
+ }
+
+ /// Scrolls to the given position in the scrollback.
+ ///
+ /// This position indicates the offset from the top of the screen, and
+ /// should be `0` to put the normal screen in view.
+ ///
+ /// This affects the return values of methods called on the screen: for
+ /// instance, `screen.cell(0, 0)` will return the top left corner of the
+ /// screen after taking the scrollback offset into account.
+ ///
+ /// The value given will be clamped to the actual size of the scrollback.
+ pub fn set_scrollback(&mut self, rows: usize) {
+ self.grid_mut().set_scrollback(rows);
+ }
+
+ /// Returns the current position in the scrollback.
+ ///
+ /// This position indicates the offset from the top of the screen, and is
+ /// `0` when the normal screen is in view.
+ #[must_use]
+ pub fn scrollback(&self) -> usize {
+ self.grid().scrollback()
+ }
+
+ /// Returns the text contents of the terminal.
+ ///
+ /// This will not include any formatting information, and will be in plain
+ /// text format.
+ #[must_use]
+ pub fn contents(&self) -> String {
+ let mut contents = String::new();
+ self.write_contents(&mut contents);
+ contents
+ }
+
+ fn write_contents(&self, contents: &mut String) {
+ self.grid().write_contents(contents);
+ }
+
+ /// Returns the text contents of the terminal by row, restricted to the
+ /// given subset of columns.
+ ///
+ /// This will not include any formatting information, and will be in plain
+ /// text format.
+ ///
+ /// Newlines will not be included.
+ pub fn rows(
+ &self,
+ start: u16,
+ width: u16,
+ ) -> impl Iterator<Item = String> + '_ {
+ self.grid().visible_rows().map(move |row| {
+ let mut contents = String::new();
+ row.write_contents(&mut contents, start, width, false);
+ contents
+ })
+ }
+
+ /// Returns the text contents of the terminal logically between two cells.
+ /// This will include the remainder of the starting row after `start_col`,
+ /// followed by the entire contents of the rows between `start_row` and
+ /// `end_row`, followed by the beginning of the `end_row` up until
+ /// `end_col`. This is useful for things like determining the contents of
+ /// a clipboard selection.
+ #[must_use]
+ pub fn contents_between(
+ &self,
+ start_row: u16,
+ start_col: u16,
+ end_row: u16,
+ end_col: u16,
+ ) -> String {
+ match start_row.cmp(&end_row) {
+ std::cmp::Ordering::Less => {
+ let (_, cols) = self.size();
+ let mut contents = String::new();
+ for (i, row) in self
+ .grid()
+ .visible_rows()
+ .enumerate()
+ .skip(usize::from(start_row))
+ .take(usize::from(end_row) - usize::from(start_row) + 1)
+ {
+ if i == usize::from(start_row) {
+ row.write_contents(
+ &mut contents,
+ start_col,
+ cols - start_col,
+ false,
+ );
+ if !row.wrapped() {
+ contents.push('\n');
+ }
+ } else if i == usize::from(end_row) {
+ row.write_contents(&mut contents, 0, end_col, false);
+ } else {
+ row.write_contents(&mut contents, 0, cols, false);
+ if !row.wrapped() {
+ contents.push('\n');
+ }
+ }
+ }
+ contents
+ }
+ std::cmp::Ordering::Equal => {
+ if start_col < end_col {
+ self.rows(start_col, end_col - start_col)
+ .nth(usize::from(start_row))
+ .unwrap_or_default()
+ } else {
+ String::new()
+ }
+ }
+ std::cmp::Ordering::Greater => String::new(),
+ }
+ }
+
+ /// Return escape codes sufficient to reproduce the entire contents of the
+ /// current terminal state. This is a convenience wrapper around
+ /// [`contents_formatted`](Self::contents_formatted) and
+ /// [`input_mode_formatted`](Self::input_mode_formatted).
+ #[must_use]
+ pub fn state_formatted(&self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_contents_formatted(&mut contents);
+ self.write_input_mode_formatted(&mut contents);
+ contents
+ }
+
+ /// Return escape codes sufficient to turn the terminal state of the
+ /// screen `prev` into the current terminal state. This is a convenience
+ /// wrapper around [`contents_diff`](Self::contents_diff) and
+ /// [`input_mode_diff`](Self::input_mode_diff).
+ #[must_use]
+ pub fn state_diff(&self, prev: &Self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_contents_diff(&mut contents, prev);
+ self.write_input_mode_diff(&mut contents, prev);
+ contents
+ }
+
+ /// Returns the formatted visible contents of the terminal.
+ ///
+ /// Formatting information will be included inline as terminal escape
+ /// codes. The result will be suitable for feeding directly to a raw
+ /// terminal parser, and will result in the same visual output.
+ #[must_use]
+ pub fn contents_formatted(&self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_contents_formatted(&mut contents);
+ contents
+ }
+
+ fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
+ crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
+ let prev_attrs = self.grid().write_contents_formatted(contents);
+ self.attrs.write_escape_code_diff(contents, &prev_attrs);
+ }
+
+ /// Returns the formatted visible contents of the terminal by row,
+ /// restricted to the given subset of columns.
+ ///
+ /// Formatting information will be included inline as terminal escape
+ /// codes. The result will be suitable for feeding directly to a raw
+ /// terminal parser, and will result in the same visual output.
+ ///
+ /// You are responsible for positioning the cursor before printing each
+ /// row, and the final cursor position after displaying each row is
+ /// unspecified.
+ // the unwraps in this method shouldn't be reachable
+ #[allow(clippy::missing_panics_doc)]
+ pub fn rows_formatted(
+ &self,
+ start: u16,
+ width: u16,
+ ) -> impl Iterator<Item = Vec<u8>> + '_ {
+ let mut wrapping = false;
+ self.grid().visible_rows().enumerate().map(move |(i, row)| {
+ // number of rows in a grid is stored in a u16 (see Size), so
+ // visible_rows can never return enough rows to overflow here
+ let i = i.try_into().unwrap();
+ let mut contents = vec![];
+ row.write_contents_formatted(
+ &mut contents,
+ start,
+ width,
+ i,
+ wrapping,
+ None,
+ None,
+ );
+ if start == 0 && width == self.grid.size().cols {
+ wrapping = row.wrapped();
+ }
+ contents
+ })
+ }
+
+ /// Returns a terminal byte stream sufficient to turn the visible contents
+ /// of the screen described by `prev` into the visible contents of the
+ /// screen described by `self`.
+ ///
+ /// The result of rendering `prev.contents_formatted()` followed by
+ /// `self.contents_diff(prev)` should be equivalent to the result of
+ /// rendering `self.contents_formatted()`. This is primarily useful when
+ /// you already have a terminal parser whose state is described by `prev`,
+ /// since the diff will likely require less memory and cause less
+ /// flickering than redrawing the entire screen contents.
+ #[must_use]
+ pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_contents_diff(&mut contents, prev);
+ contents
+ }
+
+ fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
+ if self.hide_cursor() != prev.hide_cursor() {
+ crate::term::HideCursor::new(self.hide_cursor())
+ .write_buf(contents);
+ }
+ let prev_attrs = self.grid().write_contents_diff(
+ contents,
+ prev.grid(),
+ prev.attrs,
+ );
+ self.attrs.write_escape_code_diff(contents, &prev_attrs);
+ }
+
+ /// Returns a sequence of terminal byte streams sufficient to turn the
+ /// visible contents of the subset of each row from `prev` (as described
+ /// by `start` and `width`) into the visible contents of the corresponding
+ /// row subset in `self`.
+ ///
+ /// You are responsible for positioning the cursor before printing each
+ /// row, and the final cursor position after displaying each row is
+ /// unspecified.
+ // the unwraps in this method shouldn't be reachable
+ #[allow(clippy::missing_panics_doc)]
+ pub fn rows_diff<'a>(
+ &'a self,
+ prev: &'a Self,
+ start: u16,
+ width: u16,
+ ) -> impl Iterator<Item = Vec<u8>> + 'a {
+ self.grid()
+ .visible_rows()
+ .zip(prev.grid().visible_rows())
+ .enumerate()
+ .map(move |(i, (row, prev_row))| {
+ // number of rows in a grid is stored in a u16 (see Size), so
+ // visible_rows can never return enough rows to overflow here
+ let i = i.try_into().unwrap();
+ let mut contents = vec![];
+ row.write_contents_diff(
+ &mut contents,
+ prev_row,
+ start,
+ width,
+ i,
+ false,
+ false,
+ crate::grid::Pos { row: i, col: start },
+ crate::attrs::Attrs::default(),
+ );
+ contents
+ })
+ }
+
+ /// Returns terminal escape sequences sufficient to set the current
+ /// terminal's input modes.
+ ///
+ /// Supported modes are:
+ /// * application keypad
+ /// * application cursor
+ /// * bracketed paste
+ /// * xterm mouse support
+ #[must_use]
+ pub fn input_mode_formatted(&self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_input_mode_formatted(&mut contents);
+ contents
+ }
+
+ fn write_input_mode_formatted(&self, contents: &mut Vec<u8>) {
+ crate::term::ApplicationKeypad::new(
+ self.mode(MODE_APPLICATION_KEYPAD),
+ )
+ .write_buf(contents);
+ crate::term::ApplicationCursor::new(
+ self.mode(MODE_APPLICATION_CURSOR),
+ )
+ .write_buf(contents);
+ crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
+ .write_buf(contents);
+ crate::term::MouseProtocolMode::new(
+ self.mouse_protocol_mode,
+ MouseProtocolMode::None,
+ )
+ .write_buf(contents);
+ crate::term::MouseProtocolEncoding::new(
+ self.mouse_protocol_encoding,
+ MouseProtocolEncoding::Default,
+ )
+ .write_buf(contents);
+ }
+
+ /// Returns terminal escape sequences sufficient to change the previous
+ /// terminal's input modes to the input modes enabled in the current
+ /// terminal.
+ #[must_use]
+ pub fn input_mode_diff(&self, prev: &Self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_input_mode_diff(&mut contents, prev);
+ contents
+ }
+
+ fn write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
+ if self.mode(MODE_APPLICATION_KEYPAD)
+ != prev.mode(MODE_APPLICATION_KEYPAD)
+ {
+ crate::term::ApplicationKeypad::new(
+ self.mode(MODE_APPLICATION_KEYPAD),
+ )
+ .write_buf(contents);
+ }
+ if self.mode(MODE_APPLICATION_CURSOR)
+ != prev.mode(MODE_APPLICATION_CURSOR)
+ {
+ crate::term::ApplicationCursor::new(
+ self.mode(MODE_APPLICATION_CURSOR),
+ )
+ .write_buf(contents);
+ }
+ if self.mode(MODE_BRACKETED_PASTE) != prev.mode(MODE_BRACKETED_PASTE)
+ {
+ crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
+ .write_buf(contents);
+ }
+ crate::term::MouseProtocolMode::new(
+ self.mouse_protocol_mode,
+ prev.mouse_protocol_mode,
+ )
+ .write_buf(contents);
+ crate::term::MouseProtocolEncoding::new(
+ self.mouse_protocol_encoding,
+ prev.mouse_protocol_encoding,
+ )
+ .write_buf(contents);
+ }
+
+ /// Returns terminal escape sequences sufficient to set the current
+ /// terminal's drawing attributes.
+ ///
+ /// Supported drawing attributes are:
+ /// * fgcolor
+ /// * bgcolor
+ /// * bold
+ /// * dim
+ /// * italic
+ /// * underline
+ /// * inverse
+ ///
+ /// This is not typically necessary, since
+ /// [`contents_formatted`](Self::contents_formatted) will leave
+ /// the current active drawing attributes in the correct state, but this
+ /// can be useful in the case of drawing additional things on top of a
+ /// terminal output, since you will need to restore the terminal state
+ /// without the terminal contents necessarily being the same.
+ #[must_use]
+ pub fn attributes_formatted(&self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_attributes_formatted(&mut contents);
+ contents
+ }
+
+ fn write_attributes_formatted(&self, contents: &mut Vec<u8>) {
+ crate::term::ClearAttrs.write_buf(contents);
+ self.attrs.write_escape_code_diff(
+ contents,
+ &crate::attrs::Attrs::default(),
+ );
+ }
+
+ /// Returns the current cursor position of the terminal.
+ ///
+ /// The return value will be (row, col).
+ #[must_use]
+ pub fn cursor_position(&self) -> (u16, u16) {
+ let pos = self.grid().pos();
+ (pos.row, pos.col)
+ }
+
+ /// Returns terminal escape sequences sufficient to set the current
+ /// cursor state of the terminal.
+ ///
+ /// This is not typically necessary, since
+ /// [`contents_formatted`](Self::contents_formatted) will leave
+ /// the cursor in the correct state, but this can be useful in the case of
+ /// drawing additional things on top of a terminal output, since you will
+ /// need to restore the terminal state without the terminal contents
+ /// necessarily being the same.
+ ///
+ /// Note that the bytes returned by this function may alter the active
+ /// drawing attributes, because it may require redrawing existing cells in
+ /// order to position the cursor correctly (for instance, in the case
+ /// where the cursor is past the end of a row). Therefore, you should
+ /// ensure to reset the active drawing attributes if necessary after
+ /// processing this data, for instance by using
+ /// [`attributes_formatted`](Self::attributes_formatted).
+ #[must_use]
+ pub fn cursor_state_formatted(&self) -> Vec<u8> {
+ let mut contents = vec![];
+ self.write_cursor_state_formatted(&mut contents);
+ contents
+ }
+
+ fn write_cursor_state_formatted(&self, contents: &mut Vec<u8>) {
+ crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
+ self.grid()
+ .write_cursor_position_formatted(contents, None, None);
+
+ // we don't just call write_attributes_formatted here, because that
+ // would still be confusing - consider the case where the user sets
+ // their own unrelated drawing attributes (on a different parser
+ // instance) and then calls cursor_state_formatted. just documenting
+ // it and letting the user handle it on their own is more
+ // straightforward.
+ }
+
+ /// Returns the [`Cell`](crate::Cell) object at the given location in the
+ /// terminal, if it exists.
+ #[must_use]
+ pub fn cell(&self, row: u16, col: u16) -> Option<&crate::Cell> {
+ self.grid().visible_cell(crate::grid::Pos { row, col })
+ }
+
+ /// Returns whether the text in row `row` should wrap to the next line.
+ #[must_use]
+ pub fn row_wrapped(&self, row: u16) -> bool {
+ self.grid()
+ .visible_row(row)
+ .is_some_and(crate::row::Row::wrapped)
+ }
+
+ /// Returns whether the alternate screen is currently in use.
+ #[must_use]
+ pub fn alternate_screen(&self) -> bool {
+ self.mode(MODE_ALTERNATE_SCREEN)
+ }
+
+ /// Returns whether the terminal should be in application keypad mode.
+ #[must_use]
+ pub fn application_keypad(&self) -> bool {
+ self.mode(MODE_APPLICATION_KEYPAD)
+ }
+
+ /// Returns whether the terminal should be in application cursor mode.
+ #[must_use]
+ pub fn application_cursor(&self) -> bool {
+ self.mode(MODE_APPLICATION_CURSOR)
+ }
+
+ /// Returns whether the terminal should be in hide cursor mode.
+ #[must_use]
+ pub fn hide_cursor(&self) -> bool {
+ self.mode(MODE_HIDE_CURSOR)
+ }
+
+ /// Returns whether the terminal should be in bracketed paste mode.
+ #[must_use]
+ pub fn bracketed_paste(&self) -> bool {
+ self.mode(MODE_BRACKETED_PASTE)
+ }
+
+ /// Returns the currently active [`MouseProtocolMode`].
+ #[must_use]
+ pub fn mouse_protocol_mode(&self) -> MouseProtocolMode {
+ self.mouse_protocol_mode
+ }
+
+ /// Returns the currently active [`MouseProtocolEncoding`].
+ #[must_use]
+ pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding {
+ self.mouse_protocol_encoding
+ }
+
+ /// Returns the currently active foreground color.
+ #[must_use]
+ pub fn fgcolor(&self) -> crate::Color {
+ self.attrs.fgcolor
+ }
+
+ /// Returns the currently active background color.
+ #[must_use]
+ pub fn bgcolor(&self) -> crate::Color {
+ self.attrs.bgcolor
+ }
+
+ /// Returns whether newly drawn text should be rendered with the bold text
+ /// attribute.
+ #[must_use]
+ pub fn bold(&self) -> bool {
+ self.attrs.bold()
+ }
+
+ /// Returns whether newly drawn text should be rendered with the dim text
+ /// attribute.
+ #[must_use]
+ pub fn dim(&self) -> bool {
+ self.attrs.dim()
+ }
+
+ /// Returns whether newly drawn text should be rendered with the italic
+ /// text attribute.
+ #[must_use]
+ pub fn italic(&self) -> bool {
+ self.attrs.italic()
+ }
+
+ /// Returns whether newly drawn text should be rendered with the
+ /// underlined text attribute.
+ #[must_use]
+ pub fn underline(&self) -> bool {
+ self.attrs.underline()
+ }
+
+ /// Returns whether newly drawn text should be rendered with the inverse
+ /// text attribute.
+ #[must_use]
+ pub fn inverse(&self) -> bool {
+ self.attrs.inverse()
+ }
+
+ pub(crate) fn grid(&self) -> &crate::grid::Grid {
+ if self.mode(MODE_ALTERNATE_SCREEN) {
+ &self.alternate_grid
+ } else {
+ &self.grid
+ }
+ }
+
+ fn grid_mut(&mut self) -> &mut crate::grid::Grid {
+ if self.mode(MODE_ALTERNATE_SCREEN) {
+ &mut self.alternate_grid
+ } else {
+ &mut self.grid
+ }
+ }
+
+ fn enter_alternate_grid(&mut self) {
+ self.grid_mut().set_scrollback(0);
+ self.set_mode(MODE_ALTERNATE_SCREEN);
+ self.alternate_grid.allocate_rows();
+ self.grid_mut().set_scrollback(0);
+ }
+
+ fn exit_alternate_grid(&mut self) {
+ self.clear_mode(MODE_ALTERNATE_SCREEN);
+ }
+
+ fn save_cursor(&mut self) {
+ self.grid_mut().save_cursor();
+ self.saved_attrs = self.attrs;
+ }
+
+ fn restore_cursor(&mut self) {
+ self.grid_mut().restore_cursor();
+ self.attrs = self.saved_attrs;
+ }
+
+ fn set_mode(&mut self, mode: u8) {
+ self.modes |= mode;
+ }
+
+ fn clear_mode(&mut self, mode: u8) {
+ self.modes &= !mode;
+ }
+
+ fn mode(&self, mode: u8) -> bool {
+ self.modes & mode != 0
+ }
+
+ fn set_mouse_mode(&mut self, mode: MouseProtocolMode) {
+ self.mouse_protocol_mode = mode;
+ }
+
+ fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) {
+ if self.mouse_protocol_mode == mode {
+ self.mouse_protocol_mode = MouseProtocolMode::default();
+ }
+ }
+
+ fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
+ self.mouse_protocol_encoding = encoding;
+ }
+
+ fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
+ if self.mouse_protocol_encoding == encoding {
+ self.mouse_protocol_encoding = MouseProtocolEncoding::default();
+ }
+ }
+}
+
+impl Screen {
+ pub(crate) fn text(&mut self, c: char) {
+ let pos = self.grid().pos();
+ let size = self.grid().size();
+ let attrs = self.attrs;
+
+ let width = c.width();
+ if width.is_none() && (u32::from(c)) < 256 {
+ // don't even try to draw control characters
+ return;
+ }
+ let width = width
+ .unwrap_or(1)
+ .try_into()
+ // width() can only return 0, 1, or 2
+ .unwrap();
+
+ // it doesn't make any sense to wrap if the last column in a row
+ // didn't already have contents. don't try to handle the case where a
+ // character wraps because there was only one column left in the
+ // previous row - literally everything handles this case differently,
+ // and this is tmux behavior (and also the simplest). i'm open to
+ // reconsidering this behavior, but only with a really good reason
+ // (xterm handles this by introducing the concept of triple width
+ // cells, which i really don't want to do).
+ let mut wrap = false;
+ if pos.col > size.cols - width {
+ let last_cell = self
+ .grid()
+ .drawing_cell(crate::grid::Pos {
+ row: pos.row,
+ col: size.cols - 1,
+ })
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always have a valid
+ // row value. size.cols - 1 is also always a valid column.
+ .unwrap();
+ if last_cell.has_contents() || last_cell.is_wide_continuation() {
+ wrap = true;
+ }
+ }
+ self.grid_mut().col_wrap(width, wrap);
+ let pos = self.grid().pos();
+
+ if width == 0 {
+ if pos.col > 0 {
+ let mut prev_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row,
+ col: pos.col - 1,
+ })
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always have a
+ // valid row value. pos.col - 1 is valid because we just
+ // checked for pos.col > 0.
+ .unwrap();
+ if prev_cell.is_wide_continuation() {
+ prev_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row,
+ col: pos.col - 2,
+ })
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always have a
+ // valid row value. we know pos.col - 2 is valid
+ // because the cell at pos.col - 1 is a wide
+ // continuation character, which means there must be
+ // the first half of the wide character before it.
+ .unwrap();
+ }
+ prev_cell.append(c);
+ } else if pos.row > 0 {
+ let prev_row = self
+ .grid()
+ .drawing_row(pos.row - 1)
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always have a
+ // valid row value. pos.row - 1 is valid because we just
+ // checked for pos.row > 0.
+ .unwrap();
+ if prev_row.wrapped() {
+ let mut prev_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row - 1,
+ col: size.cols - 1,
+ })
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always have a
+ // valid row value. pos.row - 1 is valid because we
+ // just checked for pos.row > 0. col of size.cols - 1
+ // is always valid.
+ .unwrap();
+ if prev_cell.is_wide_continuation() {
+ prev_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row - 1,
+ col: size.cols - 2,
+ })
+ // pos.row is valid, since it comes directly from
+ // self.grid().pos() which we assume to always
+ // have a valid row value. pos.row - 1 is valid
+ // because we just checked for pos.row > 0. col of
+ // size.cols - 2 is valid because the cell at
+ // size.cols - 1 is a wide continuation character,
+ // so it must have the first half of the wide
+ // character before it.
+ .unwrap();
+ }
+ prev_cell.append(c);
+ }
+ }
+ } else {
+ if self
+ .grid()
+ .drawing_cell(pos)
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because we
+ // called col_wrap() immediately before this, which ensures
+ // that self.grid().pos().col has a valid value.
+ .unwrap()
+ .is_wide_continuation()
+ {
+ let prev_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row,
+ col: pos.col - 1,
+ })
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because
+ // we called col_wrap() immediately before this, which
+ // ensures that self.grid().pos().col has a valid value.
+ // pos.col - 1 is valid because the cell at pos.col is a
+ // wide continuation character, so it must have the first
+ // half of the wide character before it.
+ .unwrap();
+ prev_cell.clear(attrs);
+ }
+
+ if self
+ .grid()
+ .drawing_cell(pos)
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because we
+ // called col_wrap() immediately before this, which ensures
+ // that self.grid().pos().col has a valid value.
+ .unwrap()
+ .is_wide()
+ {
+ let next_cell = self
+ .grid_mut()
+ .drawing_cell_mut(crate::grid::Pos {
+ row: pos.row,
+ col: pos.col + 1,
+ })
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because
+ // we called col_wrap() immediately before this, which
+ // ensures that self.grid().pos().col has a valid value.
+ // pos.col + 1 is valid because the cell at pos.col is a
+ // wide character, so it must have the second half of the
+ // wide character after it.
+ .unwrap();
+ next_cell.set(' ', attrs);
+ }
+
+ let cell = self
+ .grid_mut()
+ .drawing_cell_mut(pos)
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because we
+ // called col_wrap() immediately before this, which ensures
+ // that self.grid().pos().col has a valid value.
+ .unwrap();
+ cell.set(c, attrs);
+ self.grid_mut().col_inc(1);
+ if width > 1 {
+ let pos = self.grid().pos();
+ if self
+ .grid()
+ .drawing_cell(pos)
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because
+ // we called col_wrap() earlier, which ensures that
+ // self.grid().pos().col has a valid value. this is true
+ // even though we just called col_inc, because this branch
+ // only happens if width > 1, and col_wrap takes width
+ // into account.
+ .unwrap()
+ .is_wide()
+ {
+ let next_next_pos = crate::grid::Pos {
+ row: pos.row,
+ col: pos.col + 1,
+ };
+ let next_next_cell = self
+ .grid_mut()
+ .drawing_cell_mut(next_next_pos)
+ // pos.row is valid because we assume
+ // self.grid().pos() to always have a valid row value.
+ // pos.col is valid because we called col_wrap()
+ // earlier, which ensures that self.grid().pos().col
+ // has a valid value. this is true even though we just
+ // called col_inc, because this branch only happens if
+ // width > 1, and col_wrap takes width into account.
+ // pos.col + 1 is valid because the cell at pos.col is
+ // wide, and so it must have the second half of the
+ // wide character after it.
+ .unwrap();
+ next_next_cell.clear(attrs);
+ if next_next_pos.col == size.cols - 1 {
+ self.grid_mut()
+ .drawing_row_mut(pos.row)
+ // we assume self.grid().pos().row is always valid
+ .unwrap()
+ .wrap(false);
+ }
+ }
+ let next_cell = self
+ .grid_mut()
+ .drawing_cell_mut(pos)
+ // pos.row is valid because we assume self.grid().pos() to
+ // always have a valid row value. pos.col is valid because
+ // we called col_wrap() earlier, which ensures that
+ // self.grid().pos().col has a valid value. this is true
+ // even though we just called col_inc, because this branch
+ // only happens if width > 1, and col_wrap takes width
+ // into account.
+ .unwrap();
+ next_cell.clear(crate::attrs::Attrs::default());
+ next_cell.set_wide_continuation(true);
+ self.grid_mut().col_inc(1);
+ }
+ }
+ }
+
+ // control codes
+
+ pub(crate) fn bs(&mut self) {
+ self.grid_mut().col_dec(1);
+ }
+
+ pub(crate) fn tab(&mut self) {
+ self.grid_mut().col_tab();
+ }
+
+ pub(crate) fn lf(&mut self) {
+ self.grid_mut().row_inc_scroll(1);
+ }
+
+ pub(crate) fn vt(&mut self) {
+ self.lf();
+ }
+
+ pub(crate) fn ff(&mut self) {
+ self.lf();
+ }
+
+ pub(crate) fn cr(&mut self) {
+ self.grid_mut().col_set(0);
+ }
+
+ // escape codes
+
+ // ESC 7
+ pub(crate) fn decsc(&mut self) {
+ self.save_cursor();
+ }
+
+ // ESC 8
+ pub(crate) fn decrc(&mut self) {
+ self.restore_cursor();
+ }
+
+ // ESC =
+ pub(crate) fn deckpam(&mut self) {
+ self.set_mode(MODE_APPLICATION_KEYPAD);
+ }
+
+ // ESC >
+ pub(crate) fn deckpnm(&mut self) {
+ self.clear_mode(MODE_APPLICATION_KEYPAD);
+ }
+
+ // ESC M
+ pub(crate) fn ri(&mut self) {
+ self.grid_mut().row_dec_scroll(1);
+ }
+
+ // ESC c
+ pub(crate) fn ris(&mut self) {
+ *self = Self::new(self.grid.size(), self.grid.scrollback_len());
+ }
+
+ // csi codes
+
+ // CSI @
+ pub(crate) fn ich(&mut self, count: u16) {
+ self.grid_mut().insert_cells(count);
+ }
+
+ // CSI A
+ pub(crate) fn cuu(&mut self, offset: u16) {
+ self.grid_mut().row_dec_clamp(offset);
+ }
+
+ // CSI B
+ pub(crate) fn cud(&mut self, offset: u16) {
+ self.grid_mut().row_inc_clamp(offset);
+ }
+
+ // CSI C
+ pub(crate) fn cuf(&mut self, offset: u16) {
+ self.grid_mut().col_inc_clamp(offset);
+ }
+
+ // CSI D
+ pub(crate) fn cub(&mut self, offset: u16) {
+ self.grid_mut().col_dec(offset);
+ }
+
+ // CSI E
+ pub(crate) fn cnl(&mut self, offset: u16) {
+ self.grid_mut().col_set(0);
+ self.grid_mut().row_inc_clamp(offset);
+ }
+
+ // CSI F
+ pub(crate) fn cpl(&mut self, offset: u16) {
+ self.grid_mut().col_set(0);
+ self.grid_mut().row_dec_clamp(offset);
+ }
+
+ // CSI G
+ pub(crate) fn cha(&mut self, col: u16) {
+ self.grid_mut().col_set(col - 1);
+ }
+
+ // CSI H
+ pub(crate) fn cup(&mut self, (row, col): (u16, u16)) {
+ self.grid_mut().set_pos(crate::grid::Pos {
+ row: row - 1,
+ col: col - 1,
+ });
+ }
+
+ // CSI J
+ pub(crate) fn ed(
+ &mut self,
+ mode: u16,
+ mut unhandled: impl FnMut(&mut Self),
+ ) {
+ let attrs = self.attrs;
+ match mode {
+ 0 => self.grid_mut().erase_all_forward(attrs),
+ 1 => self.grid_mut().erase_all_backward(attrs),
+ 2 => self.grid_mut().erase_all(attrs),
+ _ => unhandled(self),
+ }
+ }
+
+ // CSI ? J
+ pub(crate) fn decsed(
+ &mut self,
+ mode: u16,
+ unhandled: impl FnMut(&mut Self),
+ ) {
+ self.ed(mode, unhandled);
+ }
+
+ // CSI K
+ pub(crate) fn el(
+ &mut self,
+ mode: u16,
+ mut unhandled: impl FnMut(&mut Self),
+ ) {
+ let attrs = self.attrs;
+ match mode {
+ 0 => self.grid_mut().erase_row_forward(attrs),
+ 1 => self.grid_mut().erase_row_backward(attrs),
+ 2 => self.grid_mut().erase_row(attrs),
+ _ => unhandled(self),
+ }
+ }
+
+ // CSI ? K
+ pub(crate) fn decsel(
+ &mut self,
+ mode: u16,
+ unhandled: impl FnMut(&mut Self),
+ ) {
+ self.el(mode, unhandled);
+ }
+
+ // CSI L
+ pub(crate) fn il(&mut self, count: u16) {
+ self.grid_mut().insert_lines(count);
+ }
+
+ // CSI M
+ pub(crate) fn dl(&mut self, count: u16) {
+ self.grid_mut().delete_lines(count);
+ }
+
+ // CSI P
+ pub(crate) fn dch(&mut self, count: u16) {
+ self.grid_mut().delete_cells(count);
+ }
+
+ // CSI S
+ pub(crate) fn su(&mut self, count: u16) {
+ self.grid_mut().scroll_up(count);
+ }
+
+ // CSI T
+ pub(crate) fn sd(&mut self, count: u16) {
+ self.grid_mut().scroll_down(count);
+ }
+
+ // CSI X
+ pub(crate) fn ech(&mut self, count: u16) {
+ let attrs = self.attrs;
+ self.grid_mut().erase_cells(count, attrs);
+ }
+
+ // CSI d
+ pub(crate) fn vpa(&mut self, row: u16) {
+ self.grid_mut().row_set(row - 1);
+ }
+
+ // CSI ? h
+ pub(crate) fn decset(
+ &mut self,
+ params: &vte::Params,
+ mut unhandled: impl FnMut(&mut Self),
+ ) {
+ for param in params {
+ match param {
+ [1] => self.set_mode(MODE_APPLICATION_CURSOR),
+ [6] => self.grid_mut().set_origin_mode(true),
+ [9] => self.set_mouse_mode(MouseProtocolMode::Press),
+ [25] => self.clear_mode(MODE_HIDE_CURSOR),
+ [47] => self.enter_alternate_grid(),
+ [1000] => {
+ self.set_mouse_mode(MouseProtocolMode::PressRelease);
+ }
+ [1002] => {
+ self.set_mouse_mode(MouseProtocolMode::ButtonMotion);
+ }
+ [1003] => self.set_mouse_mode(MouseProtocolMode::AnyMotion),
+ [1005] => {
+ self.set_mouse_encoding(MouseProtocolEncoding::Utf8);
+ }
+ [1006] => {
+ self.set_mouse_encoding(MouseProtocolEncoding::Sgr);
+ }
+ [1049] => {
+ self.decsc();
+ self.alternate_grid.clear();
+ self.alternate_grid.clear_scrollback();
+ self.enter_alternate_grid();
+ }
+ [2004] => self.set_mode(MODE_BRACKETED_PASTE),
+ _ => unhandled(self),
+ }
+ }
+ }
+
+ // CSI ? l
+ pub(crate) fn decrst(
+ &mut self,
+ params: &vte::Params,
+ mut unhandled: impl FnMut(&mut Self),
+ ) {
+ for param in params {
+ match param {
+ [1] => self.clear_mode(MODE_APPLICATION_CURSOR),
+ [6] => self.grid_mut().set_origin_mode(false),
+ [9] => self.clear_mouse_mode(MouseProtocolMode::Press),
+ [25] => self.set_mode(MODE_HIDE_CURSOR),
+ [47] => {
+ self.exit_alternate_grid();
+ }
+ [1000] => {
+ self.clear_mouse_mode(MouseProtocolMode::PressRelease);
+ }
+ [1002] => {
+ self.clear_mouse_mode(MouseProtocolMode::ButtonMotion);
+ }
+ [1003] => {
+ self.clear_mouse_mode(MouseProtocolMode::AnyMotion);
+ }
+ [1005] => {
+ self.clear_mouse_encoding(MouseProtocolEncoding::Utf8);
+ }
+ [1006] => {
+ self.clear_mouse_encoding(MouseProtocolEncoding::Sgr);
+ }
+ [1049] => {
+ self.exit_alternate_grid();
+ self.decrc();
+ }
+ [2004] => self.clear_mode(MODE_BRACKETED_PASTE),
+ _ => unhandled(self),
+ }
+ }
+ }
+
+ // CSI m
+ pub(crate) fn sgr(
+ &mut self,
+ params: &vte::Params,
+ mut unhandled: impl FnMut(&mut Self),
+ ) {
+ // XXX really i want to just be able to pass in a default Params
+ // instance with a 0 in it, but vte doesn't allow creating new Params
+ // instances
+ if params.is_empty() {
+ self.attrs = crate::attrs::Attrs::default();
+ return;
+ }
+
+ let mut iter = params.iter();
+
+ macro_rules! next_param {
+ () => {
+ match iter.next() {
+ Some(n) => n,
+ _ => return,
+ }
+ };
+ }
+
+ macro_rules! to_u8 {
+ ($n:expr) => {
+ if let Some(n) = u16_to_u8($n) {
+ n
+ } else {
+ return;
+ }
+ };
+ }
+
+ macro_rules! next_param_u8 {
+ () => {
+ if let &[n] = next_param!() {
+ to_u8!(n)
+ } else {
+ return;
+ }
+ };
+ }
+
+ loop {
+ match next_param!() {
+ [0] => self.attrs = crate::attrs::Attrs::default(),
+ [1] => self.attrs.set_bold(),
+ [2] => self.attrs.set_dim(),
+ [3] => self.attrs.set_italic(true),
+ [4] => self.attrs.set_underline(true),
+ [7] => self.attrs.set_inverse(true),
+ [22] => self.attrs.set_normal_intensity(),
+ [23] => self.attrs.set_italic(false),
+ [24] => self.attrs.set_underline(false),
+ [27] => self.attrs.set_inverse(false),
+ [n] if (30..=37).contains(n) => {
+ self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 30);
+ }
+ [38, 2, r, g, b] => {
+ self.attrs.fgcolor =
+ crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
+ }
+ [38, 5, i] => {
+ self.attrs.fgcolor = crate::Color::Idx(to_u8!(*i));
+ }
+ [38] => match next_param!() {
+ [2] => {
+ let r = next_param_u8!();
+ let g = next_param_u8!();
+ let b = next_param_u8!();
+ self.attrs.fgcolor = crate::Color::Rgb(r, g, b);
+ }
+ [5] => {
+ self.attrs.fgcolor =
+ crate::Color::Idx(next_param_u8!());
+ }
+ _ => {
+ unhandled(self);
+ return;
+ }
+ },
+ [39] => {
+ self.attrs.fgcolor = crate::Color::Default;
+ }
+ [n] if (40..=47).contains(n) => {
+ self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 40);
+ }
+ [48, 2, r, g, b] => {
+ self.attrs.bgcolor =
+ crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
+ }
+ [48, 5, i] => {
+ self.attrs.bgcolor = crate::Color::Idx(to_u8!(*i));
+ }
+ [48] => match next_param!() {
+ [2] => {
+ let r = next_param_u8!();
+ let g = next_param_u8!();
+ let b = next_param_u8!();
+ self.attrs.bgcolor = crate::Color::Rgb(r, g, b);
+ }
+ [5] => {
+ self.attrs.bgcolor =
+ crate::Color::Idx(next_param_u8!());
+ }
+ _ => {
+ unhandled(self);
+ return;
+ }
+ },
+ [49] => {
+ self.attrs.bgcolor = crate::Color::Default;
+ }
+ [n] if (90..=97).contains(n) => {
+ self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 82);
+ }
+ [n] if (100..=107).contains(n) => {
+ self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 92);
+ }
+ _ => unhandled(self),
+ }
+ }
+ }
+
+ // CSI r
+ pub(crate) fn decstbm(&mut self, (top, bottom): (u16, u16)) {
+ self.grid_mut().set_scroll_region(top - 1, bottom - 1);
+ }
+}
+
+fn u16_to_u8(i: u16) -> Option<u8> {
+ if i > u16::from(u8::MAX) {
+ None
+ } else {
+ // safe because we just ensured that the value fits in a u8
+ Some(i.try_into().unwrap())
+ }
+}
diff --git a/vendor/vt100/src/term.rs b/vendor/vt100/src/term.rs
new file mode 100644
index 00000000..b6b898c6
--- /dev/null
+++ b/vendor/vt100/src/term.rs
@@ -0,0 +1,551 @@
+// TODO: read all of this from terminfo
+
+pub trait BufWrite {
+ fn write_buf(&self, buf: &mut Vec<u8>);
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct ClearScreen;
+
+impl BufWrite for ClearScreen {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x1b[H\x1b[J");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct ClearRowForward;
+
+impl BufWrite for ClearRowForward {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x1b[K");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct Crlf;
+
+impl BufWrite for Crlf {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\r\n");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct Backspace;
+
+impl BufWrite for Backspace {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x08");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct SaveCursor;
+
+impl BufWrite for SaveCursor {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x1b7");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct RestoreCursor;
+
+impl BufWrite for RestoreCursor {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x1b8");
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct MoveTo {
+ row: u16,
+ col: u16,
+}
+
+impl MoveTo {
+ pub fn new(pos: crate::grid::Pos) -> Self {
+ Self {
+ row: pos.row,
+ col: pos.col,
+ }
+ }
+}
+
+impl BufWrite for MoveTo {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.row == 0 && self.col == 0 {
+ buf.extend_from_slice(b"\x1b[H");
+ } else {
+ buf.extend_from_slice(b"\x1b[");
+ extend_itoa(buf, self.row + 1);
+ buf.push(b';');
+ extend_itoa(buf, self.col + 1);
+ buf.push(b'H');
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct ClearAttrs;
+
+impl BufWrite for ClearAttrs {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ buf.extend_from_slice(b"\x1b[m");
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Intensity {
+ Normal,
+ Bold,
+ Dim,
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct Attrs {
+ fgcolor: Option<crate::Color>,
+ bgcolor: Option<crate::Color>,
+ intensity: Option<Intensity>,
+ italic: Option<bool>,
+ underline: Option<bool>,
+ inverse: Option<bool>,
+}
+
+impl Attrs {
+ pub fn fgcolor(mut self, fgcolor: crate::Color) -> Self {
+ self.fgcolor = Some(fgcolor);
+ self
+ }
+
+ pub fn bgcolor(mut self, bgcolor: crate::Color) -> Self {
+ self.bgcolor = Some(bgcolor);
+ self
+ }
+
+ pub fn intensity(mut self, intensity: Intensity) -> Self {
+ self.intensity = Some(intensity);
+ self
+ }
+
+ pub fn italic(mut self, italic: bool) -> Self {
+ self.italic = Some(italic);
+ self
+ }
+
+ pub fn underline(mut self, underline: bool) -> Self {
+ self.underline = Some(underline);
+ self
+ }
+
+ pub fn inverse(mut self, inverse: bool) -> Self {
+ self.inverse = Some(inverse);
+ self
+ }
+}
+
+impl BufWrite for Attrs {
+ #[allow(unused_assignments)]
+ #[allow(clippy::branches_sharing_code)]
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.fgcolor.is_none()
+ && self.bgcolor.is_none()
+ && self.intensity.is_none()
+ && self.italic.is_none()
+ && self.underline.is_none()
+ && self.inverse.is_none()
+ {
+ return;
+ }
+
+ buf.extend_from_slice(b"\x1b[");
+ let mut first = true;
+
+ macro_rules! write_param {
+ ($i:expr) => {{
+ if first {
+ first = false;
+ } else {
+ buf.push(b';');
+ }
+ extend_itoa(buf, $i);
+ }};
+ }
+
+ if let Some(fgcolor) = self.fgcolor {
+ match fgcolor {
+ crate::Color::Default => {
+ write_param!(39);
+ }
+ crate::Color::Idx(i) => {
+ if i < 8 {
+ write_param!(i + 30);
+ } else if i < 16 {
+ write_param!(i + 82);
+ } else {
+ write_param!(38);
+ write_param!(5);
+ write_param!(i);
+ }
+ }
+ crate::Color::Rgb(r, g, b) => {
+ write_param!(38);
+ write_param!(2);
+ write_param!(r);
+ write_param!(g);
+ write_param!(b);
+ }
+ }
+ }
+
+ if let Some(bgcolor) = self.bgcolor {
+ match bgcolor {
+ crate::Color::Default => {
+ write_param!(49);
+ }
+ crate::Color::Idx(i) => {
+ if i < 8 {
+ write_param!(i + 40);
+ } else if i < 16 {
+ write_param!(i + 92);
+ } else {
+ write_param!(48);
+ write_param!(5);
+ write_param!(i);
+ }
+ }
+ crate::Color::Rgb(r, g, b) => {
+ write_param!(48);
+ write_param!(2);
+ write_param!(r);
+ write_param!(g);
+ write_param!(b);
+ }
+ }
+ }
+
+ if let Some(intensity) = self.intensity {
+ match intensity {
+ Intensity::Normal => write_param!(22),
+ Intensity::Bold => write_param!(1),
+ Intensity::Dim => write_param!(2),
+ }
+ }
+
+ if let Some(italic) = self.italic {
+ if italic {
+ write_param!(3);
+ } else {
+ write_param!(23);
+ }
+ }
+
+ if let Some(underline) = self.underline {
+ if underline {
+ write_param!(4);
+ } else {
+ write_param!(24);
+ }
+ }
+
+ if let Some(inverse) = self.inverse {
+ if inverse {
+ write_param!(7);
+ } else {
+ write_param!(27);
+ }
+ }
+
+ buf.push(b'm');
+ }
+}
+
+#[derive(Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct MoveRight {
+ count: u16,
+}
+
+impl MoveRight {
+ pub fn new(count: u16) -> Self {
+ Self { count }
+ }
+}
+
+impl Default for MoveRight {
+ fn default() -> Self {
+ Self { count: 1 }
+ }
+}
+
+impl BufWrite for MoveRight {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ match self.count {
+ 0 => {}
+ 1 => buf.extend_from_slice(b"\x1b[C"),
+ n => {
+ buf.extend_from_slice(b"\x1b[");
+ extend_itoa(buf, n);
+ buf.push(b'C');
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct EraseChar {
+ count: u16,
+}
+
+impl EraseChar {
+ pub fn new(count: u16) -> Self {
+ Self { count }
+ }
+}
+
+impl Default for EraseChar {
+ fn default() -> Self {
+ Self { count: 1 }
+ }
+}
+
+impl BufWrite for EraseChar {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ match self.count {
+ 0 => {}
+ 1 => buf.extend_from_slice(b"\x1b[X"),
+ n => {
+ buf.extend_from_slice(b"\x1b[");
+ extend_itoa(buf, n);
+ buf.push(b'X');
+ }
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct HideCursor {
+ state: bool,
+}
+
+impl HideCursor {
+ pub fn new(state: bool) -> Self {
+ Self { state }
+ }
+}
+
+impl BufWrite for HideCursor {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.state {
+ buf.extend_from_slice(b"\x1b[?25l");
+ } else {
+ buf.extend_from_slice(b"\x1b[?25h");
+ }
+ }
+}
+
+#[derive(Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct MoveFromTo {
+ from: crate::grid::Pos,
+ to: crate::grid::Pos,
+}
+
+impl MoveFromTo {
+ pub fn new(from: crate::grid::Pos, to: crate::grid::Pos) -> Self {
+ Self { from, to }
+ }
+}
+
+impl BufWrite for MoveFromTo {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.to.row == self.from.row + 1 && self.to.col == 0 {
+ crate::term::Crlf.write_buf(buf);
+ } else if self.from.row == self.to.row && self.from.col < self.to.col
+ {
+ crate::term::MoveRight::new(self.to.col - self.from.col)
+ .write_buf(buf);
+ } else if self.to != self.from {
+ crate::term::MoveTo::new(self.to).write_buf(buf);
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct ApplicationKeypad {
+ state: bool,
+}
+
+impl ApplicationKeypad {
+ pub fn new(state: bool) -> Self {
+ Self { state }
+ }
+}
+
+impl BufWrite for ApplicationKeypad {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.state {
+ buf.extend_from_slice(b"\x1b=");
+ } else {
+ buf.extend_from_slice(b"\x1b>");
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct ApplicationCursor {
+ state: bool,
+}
+
+impl ApplicationCursor {
+ pub fn new(state: bool) -> Self {
+ Self { state }
+ }
+}
+
+impl BufWrite for ApplicationCursor {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.state {
+ buf.extend_from_slice(b"\x1b[?1h");
+ } else {
+ buf.extend_from_slice(b"\x1b[?1l");
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct BracketedPaste {
+ state: bool,
+}
+
+impl BracketedPaste {
+ pub fn new(state: bool) -> Self {
+ Self { state }
+ }
+}
+
+impl BufWrite for BracketedPaste {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.state {
+ buf.extend_from_slice(b"\x1b[?2004h");
+ } else {
+ buf.extend_from_slice(b"\x1b[?2004l");
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct MouseProtocolMode {
+ mode: crate::MouseProtocolMode,
+ prev: crate::MouseProtocolMode,
+}
+
+impl MouseProtocolMode {
+ pub fn new(
+ mode: crate::MouseProtocolMode,
+ prev: crate::MouseProtocolMode,
+ ) -> Self {
+ Self { mode, prev }
+ }
+}
+
+impl BufWrite for MouseProtocolMode {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.mode == self.prev {
+ return;
+ }
+
+ match self.mode {
+ crate::MouseProtocolMode::None => match self.prev {
+ crate::MouseProtocolMode::None => {}
+ crate::MouseProtocolMode::Press => {
+ buf.extend_from_slice(b"\x1b[?9l");
+ }
+ crate::MouseProtocolMode::PressRelease => {
+ buf.extend_from_slice(b"\x1b[?1000l");
+ }
+ crate::MouseProtocolMode::ButtonMotion => {
+ buf.extend_from_slice(b"\x1b[?1002l");
+ }
+ crate::MouseProtocolMode::AnyMotion => {
+ buf.extend_from_slice(b"\x1b[?1003l");
+ }
+ },
+ crate::MouseProtocolMode::Press => {
+ buf.extend_from_slice(b"\x1b[?9h");
+ }
+ crate::MouseProtocolMode::PressRelease => {
+ buf.extend_from_slice(b"\x1b[?1000h");
+ }
+ crate::MouseProtocolMode::ButtonMotion => {
+ buf.extend_from_slice(b"\x1b[?1002h");
+ }
+ crate::MouseProtocolMode::AnyMotion => {
+ buf.extend_from_slice(b"\x1b[?1003h");
+ }
+ }
+ }
+}
+
+#[derive(Default, Debug)]
+#[must_use = "this struct does nothing unless you call write_buf"]
+pub struct MouseProtocolEncoding {
+ encoding: crate::MouseProtocolEncoding,
+ prev: crate::MouseProtocolEncoding,
+}
+
+impl MouseProtocolEncoding {
+ pub fn new(
+ encoding: crate::MouseProtocolEncoding,
+ prev: crate::MouseProtocolEncoding,
+ ) -> Self {
+ Self { encoding, prev }
+ }
+}
+
+impl BufWrite for MouseProtocolEncoding {
+ fn write_buf(&self, buf: &mut Vec<u8>) {
+ if self.encoding == self.prev {
+ return;
+ }
+
+ match self.encoding {
+ crate::MouseProtocolEncoding::Default => match self.prev {
+ crate::MouseProtocolEncoding::Default => {}
+ crate::MouseProtocolEncoding::Utf8 => {
+ buf.extend_from_slice(b"\x1b[?1005l");
+ }
+ crate::MouseProtocolEncoding::Sgr => {
+ buf.extend_from_slice(b"\x1b[?1006l");
+ }
+ },
+ crate::MouseProtocolEncoding::Utf8 => {
+ buf.extend_from_slice(b"\x1b[?1005h");
+ }
+ crate::MouseProtocolEncoding::Sgr => {
+ buf.extend_from_slice(b"\x1b[?1006h");
+ }
+ }
+ }
+}
+
+fn extend_itoa<I: itoa::Integer>(buf: &mut Vec<u8>, i: I) {
+ let mut itoa_buf = itoa::Buffer::new();
+ buf.extend_from_slice(itoa_buf.format(i).as_bytes());
+}