@akiojin/gwt 9.7.0 → 9.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ //! This crate parses a terminal byte stream and provides an in-memory
2
+ //! representation of the rendered contents.
3
+ //!
4
+ //! # Overview
5
+ //!
6
+ //! This is essentially the terminal parser component of a graphical terminal
7
+ //! emulator pulled out into a separate crate. Although you can use this crate
8
+ //! to build a graphical terminal emulator, it also contains functionality
9
+ //! necessary for implementing terminal applications that want to run other
10
+ //! terminal applications - programs like `screen` or `tmux` for example.
11
+ //!
12
+ //! # Synopsis
13
+ //!
14
+ //! ```
15
+ //! let mut parser = vt100::Parser::new(24, 80, 0);
16
+ //!
17
+ //! let screen = parser.screen().clone();
18
+ //! parser.process(b"this text is \x1b[31mRED\x1b[m");
19
+ //! assert_eq!(
20
+ //! parser.screen().cell(0, 13).unwrap().fgcolor(),
21
+ //! vt100::Color::Idx(1),
22
+ //! );
23
+ //!
24
+ //! let screen = parser.screen().clone();
25
+ //! parser.process(b"\x1b[3D\x1b[32mGREEN");
26
+ //! assert_eq!(
27
+ //! parser.screen().contents_formatted(),
28
+ //! &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jthis text is \x1b[32mGREEN"[..],
29
+ //! );
30
+ //! assert_eq!(
31
+ //! parser.screen().contents_diff(&screen),
32
+ //! &b"\x1b[1;14H\x1b[32mGREEN"[..],
33
+ //! );
34
+ //! ```
35
+
36
+ #![warn(missing_docs)]
37
+ #![warn(clippy::cargo)]
38
+ #![warn(clippy::pedantic)]
39
+ #![warn(clippy::nursery)]
40
+ #![warn(clippy::as_conversions)]
41
+ #![warn(clippy::get_unwrap)]
42
+ #![allow(clippy::cognitive_complexity)]
43
+ #![allow(clippy::missing_const_for_fn)]
44
+ #![allow(clippy::similar_names)]
45
+ #![allow(clippy::struct_excessive_bools)]
46
+ #![allow(clippy::too_many_arguments)]
47
+ #![allow(clippy::too_many_lines)]
48
+ #![allow(clippy::type_complexity)]
49
+
50
+ mod attrs;
51
+ mod callbacks;
52
+ mod cell;
53
+ mod grid;
54
+ mod parser;
55
+ mod perform;
56
+ mod row;
57
+ mod screen;
58
+ mod term;
59
+
60
+ pub use attrs::Color;
61
+ pub use callbacks::Callbacks;
62
+ pub use cell::Cell;
63
+ pub use parser::Parser;
64
+ pub use screen::{MouseProtocolEncoding, MouseProtocolMode, Screen};
@@ -0,0 +1,96 @@
1
+ /// A parser for terminal output which produces an in-memory representation of
2
+ /// the terminal contents.
3
+ pub struct Parser<CB: crate::callbacks::Callbacks = ()> {
4
+ parser: vte::Parser,
5
+ screen: crate::perform::WrappedScreen<CB>,
6
+ }
7
+
8
+ impl Parser {
9
+ /// Creates a new terminal parser of the given size and with the given
10
+ /// amount of scrollback.
11
+ #[must_use]
12
+ pub fn new(rows: u16, cols: u16, scrollback_len: usize) -> Self {
13
+ Self {
14
+ parser: vte::Parser::new(),
15
+ screen: crate::perform::WrappedScreen::new(
16
+ rows,
17
+ cols,
18
+ scrollback_len,
19
+ ),
20
+ }
21
+ }
22
+ }
23
+
24
+ impl<CB: crate::callbacks::Callbacks> Parser<CB> {
25
+ /// Creates a new terminal parser of the given size and with the given
26
+ /// amount of scrollback. Terminal events will be reported via method
27
+ /// calls on the provided [`Callbacks`](crate::callbacks::Callbacks)
28
+ /// implementation.
29
+ pub fn new_with_callbacks(
30
+ rows: u16,
31
+ cols: u16,
32
+ scrollback_len: usize,
33
+ callbacks: CB,
34
+ ) -> Self {
35
+ Self {
36
+ parser: vte::Parser::new(),
37
+ screen: crate::perform::WrappedScreen::new_with_callbacks(
38
+ rows,
39
+ cols,
40
+ scrollback_len,
41
+ callbacks,
42
+ ),
43
+ }
44
+ }
45
+
46
+ /// Processes the contents of the given byte string, and updates the
47
+ /// in-memory terminal state.
48
+ pub fn process(&mut self, bytes: &[u8]) {
49
+ self.parser.advance(&mut self.screen, bytes);
50
+ }
51
+
52
+ /// Returns a reference to a [`Screen`](crate::Screen) object containing
53
+ /// the terminal state.
54
+ #[must_use]
55
+ pub fn screen(&self) -> &crate::Screen {
56
+ &self.screen.screen
57
+ }
58
+
59
+ /// Returns a mutable reference to a [`Screen`](crate::Screen) object
60
+ /// containing the terminal state.
61
+ #[must_use]
62
+ pub fn screen_mut(&mut self) -> &mut crate::Screen {
63
+ &mut self.screen.screen
64
+ }
65
+
66
+ /// Returns a reference to the [`Callbacks`](crate::callbacks::Callbacks)
67
+ /// state object passed into the constructor.
68
+ pub fn callbacks(&self) -> &CB {
69
+ &self.screen.callbacks
70
+ }
71
+
72
+ /// Returns a mutable reference to the
73
+ /// [`Callbacks`](crate::callbacks::Callbacks) state object passed into
74
+ /// the constructor.
75
+ pub fn callbacks_mut(&mut self) -> &mut CB {
76
+ &mut self.screen.callbacks
77
+ }
78
+ }
79
+
80
+ impl Default for Parser {
81
+ /// Returns a parser with dimensions 80x24 and no scrollback.
82
+ fn default() -> Self {
83
+ Self::new(24, 80, 0)
84
+ }
85
+ }
86
+
87
+ impl std::io::Write for Parser {
88
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
89
+ self.process(buf);
90
+ Ok(buf.len())
91
+ }
92
+
93
+ fn flush(&mut self) -> std::io::Result<()> {
94
+ Ok(())
95
+ }
96
+ }
@@ -0,0 +1,277 @@
1
+ const BASE64: &[u8] =
2
+ b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
3
+ const CLIPBOARD_SELECTOR: &[u8] = b"cpqs01234567";
4
+
5
+ pub struct WrappedScreen<CB: crate::callbacks::Callbacks = ()> {
6
+ pub screen: crate::screen::Screen,
7
+ pub callbacks: CB,
8
+ }
9
+
10
+ impl WrappedScreen<()> {
11
+ pub fn new(rows: u16, cols: u16, scrollback_len: usize) -> Self {
12
+ Self::new_with_callbacks(rows, cols, scrollback_len, ())
13
+ }
14
+ }
15
+
16
+ impl<CB: crate::callbacks::Callbacks> WrappedScreen<CB> {
17
+ pub fn new_with_callbacks(
18
+ rows: u16,
19
+ cols: u16,
20
+ scrollback_len: usize,
21
+ callbacks: CB,
22
+ ) -> Self {
23
+ Self {
24
+ screen: crate::screen::Screen::new(
25
+ crate::grid::Size { rows, cols },
26
+ scrollback_len,
27
+ ),
28
+ callbacks,
29
+ }
30
+ }
31
+ }
32
+
33
+ impl<CB: crate::callbacks::Callbacks> vte::Perform for WrappedScreen<CB> {
34
+ fn print(&mut self, c: char) {
35
+ if c == '\u{fffd}' || ('\u{80}'..'\u{a0}').contains(&c) {
36
+ self.callbacks.unhandled_char(&mut self.screen, c);
37
+ } else {
38
+ self.screen.text(c);
39
+ }
40
+ }
41
+
42
+ fn execute(&mut self, b: u8) {
43
+ match b {
44
+ 7 => self.callbacks.audible_bell(&mut self.screen),
45
+ 8 => self.screen.bs(),
46
+ 9 => self.screen.tab(),
47
+ 10 => self.screen.lf(),
48
+ 11 => self.screen.vt(),
49
+ 12 => self.screen.ff(),
50
+ 13 => self.screen.cr(),
51
+ // we don't implement shift in/out alternate character sets, but
52
+ // it shouldn't count as an "error"
53
+ 14 | 15 => {}
54
+ _ => self.callbacks.unhandled_control(&mut self.screen, b),
55
+ }
56
+ }
57
+
58
+ fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, b: u8) {
59
+ if let Some(i) = intermediates.first() {
60
+ self.callbacks.unhandled_escape(
61
+ &mut self.screen,
62
+ Some(*i),
63
+ intermediates.get(1).copied(),
64
+ b,
65
+ );
66
+ } else {
67
+ match b {
68
+ b'7' => self.screen.decsc(),
69
+ b'8' => self.screen.decrc(),
70
+ b'=' => self.screen.deckpam(),
71
+ b'>' => self.screen.deckpnm(),
72
+ b'M' => self.screen.ri(),
73
+ b'c' => self.screen.ris(),
74
+ b'g' => self.callbacks.visual_bell(&mut self.screen),
75
+ _ => {
76
+ self.callbacks.unhandled_escape(
77
+ &mut self.screen,
78
+ None,
79
+ None,
80
+ b,
81
+ );
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ fn csi_dispatch(
88
+ &mut self,
89
+ params: &vte::Params,
90
+ intermediates: &[u8],
91
+ _ignore: bool,
92
+ c: char,
93
+ ) {
94
+ let unhandled = |screen: &mut crate::screen::Screen| {
95
+ self.callbacks.unhandled_csi(
96
+ screen,
97
+ intermediates.first().copied(),
98
+ intermediates.get(1).copied(),
99
+ &params.iter().collect::<Vec<_>>(),
100
+ c,
101
+ );
102
+ };
103
+ match intermediates.first() {
104
+ None => match c {
105
+ '@' => self.screen.ich(canonicalize_params_1(params, 1)),
106
+ 'A' => self.screen.cuu(canonicalize_params_1(params, 1)),
107
+ 'B' => self.screen.cud(canonicalize_params_1(params, 1)),
108
+ 'C' => self.screen.cuf(canonicalize_params_1(params, 1)),
109
+ 'D' => self.screen.cub(canonicalize_params_1(params, 1)),
110
+ 'E' => self.screen.cnl(canonicalize_params_1(params, 1)),
111
+ 'F' => self.screen.cpl(canonicalize_params_1(params, 1)),
112
+ 'G' => self.screen.cha(canonicalize_params_1(params, 1)),
113
+ 'H' => self.screen.cup(canonicalize_params_2(params, 1, 1)),
114
+ 'J' => self
115
+ .screen
116
+ .ed(canonicalize_params_1(params, 0), unhandled),
117
+ 'K' => self
118
+ .screen
119
+ .el(canonicalize_params_1(params, 0), unhandled),
120
+ 'L' => self.screen.il(canonicalize_params_1(params, 1)),
121
+ 'M' => self.screen.dl(canonicalize_params_1(params, 1)),
122
+ 'P' => self.screen.dch(canonicalize_params_1(params, 1)),
123
+ 'S' => self.screen.su(canonicalize_params_1(params, 1)),
124
+ 'T' => self.screen.sd(canonicalize_params_1(params, 1)),
125
+ 'X' => self.screen.ech(canonicalize_params_1(params, 1)),
126
+ 'd' => self.screen.vpa(canonicalize_params_1(params, 1)),
127
+ 'm' => self.screen.sgr(params, unhandled),
128
+ 'r' => self.screen.decstbm(canonicalize_params_decstbm(
129
+ params,
130
+ self.screen.grid().size(),
131
+ )),
132
+ 't' => {
133
+ let mut params_iter = params.iter();
134
+ let op =
135
+ params_iter.next().and_then(|x| x.first().copied());
136
+ if op == Some(8) {
137
+ let (screen_rows, screen_cols) = self.screen.size();
138
+ let rows =
139
+ params_iter.next().map_or(screen_rows, |x| {
140
+ *x.first().unwrap_or(&screen_rows)
141
+ });
142
+ let cols =
143
+ params_iter.next().map_or(screen_cols, |x| {
144
+ *x.first().unwrap_or(&screen_cols)
145
+ });
146
+ self.callbacks.resize(&mut self.screen, (rows, cols));
147
+ } else {
148
+ self.callbacks.unhandled_csi(
149
+ &mut self.screen,
150
+ None,
151
+ None,
152
+ &params.iter().collect::<Vec<_>>(),
153
+ c,
154
+ );
155
+ }
156
+ }
157
+ _ => {
158
+ self.callbacks.unhandled_csi(
159
+ &mut self.screen,
160
+ None,
161
+ None,
162
+ &params.iter().collect::<Vec<_>>(),
163
+ c,
164
+ );
165
+ }
166
+ },
167
+ Some(b'?') => match c {
168
+ 'J' => self
169
+ .screen
170
+ .decsed(canonicalize_params_1(params, 0), unhandled),
171
+ 'K' => self
172
+ .screen
173
+ .decsel(canonicalize_params_1(params, 0), unhandled),
174
+ 'h' => self.screen.decset(params, unhandled),
175
+ 'l' => self.screen.decrst(params, unhandled),
176
+ _ => {
177
+ self.callbacks.unhandled_csi(
178
+ &mut self.screen,
179
+ Some(b'?'),
180
+ intermediates.get(1).copied(),
181
+ &params.iter().collect::<Vec<_>>(),
182
+ c,
183
+ );
184
+ }
185
+ },
186
+ Some(i) => {
187
+ self.callbacks.unhandled_csi(
188
+ &mut self.screen,
189
+ Some(*i),
190
+ intermediates.get(1).copied(),
191
+ &params.iter().collect::<Vec<_>>(),
192
+ c,
193
+ );
194
+ }
195
+ }
196
+ }
197
+
198
+ fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) {
199
+ match params {
200
+ [b"0", s] => {
201
+ self.callbacks.set_window_icon_name(&mut self.screen, s);
202
+ self.callbacks.set_window_title(&mut self.screen, s);
203
+ }
204
+ [b"1", s] => {
205
+ self.callbacks.set_window_icon_name(&mut self.screen, s);
206
+ }
207
+ [b"2", s] => {
208
+ self.callbacks.set_window_title(&mut self.screen, s);
209
+ }
210
+ [b"52", ty, data] => {
211
+ match (
212
+ ty.iter().all(|c| CLIPBOARD_SELECTOR.contains(c)),
213
+ *data,
214
+ ) {
215
+ (true, b"?") => {
216
+ self.callbacks
217
+ .paste_from_clipboard(&mut self.screen, ty);
218
+ }
219
+ (true, data)
220
+ if data.iter().all(|c| BASE64.contains(c)) =>
221
+ {
222
+ self.callbacks.copy_to_clipboard(
223
+ &mut self.screen,
224
+ ty,
225
+ data,
226
+ );
227
+ }
228
+ _ => {
229
+ self.callbacks
230
+ .unhandled_osc(&mut self.screen, params);
231
+ }
232
+ }
233
+ }
234
+ _ => {
235
+ self.callbacks.unhandled_osc(&mut self.screen, params);
236
+ }
237
+ }
238
+ }
239
+ }
240
+
241
+ fn canonicalize_params_1(params: &vte::Params, default: u16) -> u16 {
242
+ let first = params.iter().next().map_or(0, |x| *x.first().unwrap_or(&0));
243
+ if first == 0 {
244
+ default
245
+ } else {
246
+ first
247
+ }
248
+ }
249
+
250
+ fn canonicalize_params_2(
251
+ params: &vte::Params,
252
+ default1: u16,
253
+ default2: u16,
254
+ ) -> (u16, u16) {
255
+ let mut iter = params.iter();
256
+ let first = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
257
+ let first = if first == 0 { default1 } else { first };
258
+
259
+ let second = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
260
+ let second = if second == 0 { default2 } else { second };
261
+
262
+ (first, second)
263
+ }
264
+
265
+ fn canonicalize_params_decstbm(
266
+ params: &vte::Params,
267
+ size: crate::grid::Size,
268
+ ) -> (u16, u16) {
269
+ let mut iter = params.iter();
270
+ let top = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
271
+ let top = if top == 0 { 1 } else { top };
272
+
273
+ let bottom = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
274
+ let bottom = if bottom == 0 { size.rows } else { bottom };
275
+
276
+ (top, bottom)
277
+ }