fix alt-screen scrollback handling

This commit is contained in:
Ogulcan Celik 2026-03-31 20:49:05 +03:00
parent f826fb78dc
commit 41e009f9b7
24 changed files with 5383 additions and 72 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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,
))
}
}

View File

@ -397,6 +397,7 @@ pub(crate) enum DragTarget {
},
PaneScrollbar {
pane_id: crate::layout::PaneId,
grab_row_offset: u16,
},
SidebarDivider,
}

View File

@ -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();
@ -636,6 +658,28 @@ mod tests {
assert_eq!(rows, vec!["hello".to_string()]);
}
#[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();

271
src/ui.rs
View File

@ -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
}
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 {
return 0;
}
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 {
if metrics.max_offset_from_bottom == 0 || track.height <= 1 {
return metrics.max_offset_from_bottom;
}
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 = 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(
@ -1747,6 +1844,67 @@ mod tests {
assert_eq!(pane_scrollbar_rect(&info), Some(Rect::new(10, 1, 1, 6)));
}
#[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 {
@ -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);
}
}

1
vendor/vt100/.cargo-ok vendored Normal file
View File

@ -0,0 +1 @@
{"v":1}

6
vendor/vt100/.cargo_vcs_info.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"git": {
"sha1": "eb66ffaf7d771c13303ef73b29f6f2a56fdacecf"
},
"path_in_vcs": ""
}

396
vendor/vt100/CHANGELOG.md vendored Normal file
View File

@ -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

540
vendor/vt100/Cargo.lock generated vendored Normal file
View File

@ -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",
]

75
vendor/vt100/Cargo.toml vendored Normal file
View File

@ -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"

28
vendor/vt100/Cargo.toml.orig vendored Normal file
View File

@ -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"

21
vendor/vt100/LICENSE vendored Normal file
View File

@ -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.

36
vendor/vt100/README.md vendored Normal file
View File

@ -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"[..],
);
```

144
vendor/vt100/src/attrs.rs vendored Normal file
View File

@ -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);
}
}

69
vendor/vt100/src/callbacks.rs vendored Normal file
View File

@ -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 () {}

179
vendor/vt100/src/cell.rs vendored Normal file
View File

@ -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()
}
}

743
vendor/vt100/src/grid.rs vendored Normal file
View File

@ -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,
}

64
vendor/vt100/src/lib.rs vendored Normal file
View File

@ -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};

96
vendor/vt100/src/parser.rs vendored Normal file
View File

@ -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(())
}
}

277
vendor/vt100/src/perform.rs vendored Normal file
View File

@ -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)
}

474
vendor/vt100/src/row.rs vendored Normal file
View File

@ -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)
}
}

1356
vendor/vt100/src/screen.rs vendored Normal file

File diff suppressed because it is too large Load Diff

551
vendor/vt100/src/term.rs vendored Normal file
View File

@ -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());
}