1 use crate::term::BufWrite as _;
2 use std::convert::TryInto as _;
3 use unicode_width::UnicodeWidthChar as _;
4 
5 const DEFAULT_MULTI_PARAMS: &[i64] = &[0];
6 
7 #[derive(enumset::EnumSetType, Debug)]
8 enum Mode {
9     ApplicationKeypad,
10     ApplicationCursor,
11     HideCursor,
12     AlternateScreen,
13     BracketedPaste,
14 }
15 
16 /// The xterm mouse handling mode currently in use.
17 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
18 pub enum MouseProtocolMode {
19     /// Mouse handling is disabled.
20     None,
21 
22     /// Mouse button events should be reported on button press. Also known as
23     /// X10 mouse mode.
24     Press,
25 
26     /// Mouse button events should be reported on button press and release.
27     /// Also known as VT200 mouse mode.
28     PressRelease,
29 
30     // Highlight,
31     /// Mouse button events should be reported on button press and release, as
32     /// well as when the mouse moves between cells while a button is held
33     /// down.
34     ButtonMotion,
35 
36     /// Mouse button events should be reported on button press and release,
37     /// and mouse motion events should be reported when the mouse moves
38     /// between cells regardless of whether a button is held down or not.
39     AnyMotion,
40     // DecLocator,
41 }
42 
43 impl Default for MouseProtocolMode {
default() -> Self44     fn default() -> Self {
45         Self::None
46     }
47 }
48 
49 /// The encoding to use for the enabled `MouseProtocolMode`.
50 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
51 pub enum MouseProtocolEncoding {
52     /// Default single-printable-byte encoding.
53     Default,
54 
55     /// UTF-8-based encoding.
56     Utf8,
57 
58     /// SGR-like encoding.
59     Sgr,
60     // Urxvt,
61 }
62 
63 impl Default for MouseProtocolEncoding {
default() -> Self64     fn default() -> Self {
65         Self::Default
66     }
67 }
68 
69 /// Represents the overall terminal state.
70 #[derive(Clone, Debug)]
71 pub struct Screen {
72     grid: crate::grid::Grid,
73     alternate_grid: crate::grid::Grid,
74 
75     attrs: crate::attrs::Attrs,
76     saved_attrs: crate::attrs::Attrs,
77 
78     title: String,
79     icon_name: String,
80 
81     modes: enumset::EnumSet<Mode>,
82     mouse_protocol_mode: MouseProtocolMode,
83     mouse_protocol_encoding: MouseProtocolEncoding,
84 
85     audible_bell_count: usize,
86     visual_bell_count: usize,
87 }
88 
89 impl Screen {
new( size: crate::grid::Size, scrollback_len: usize, ) -> Self90     pub(crate) fn new(
91         size: crate::grid::Size,
92         scrollback_len: usize,
93     ) -> Self {
94         Self {
95             grid: crate::grid::Grid::new(size, scrollback_len),
96             alternate_grid: crate::grid::Grid::new(size, 0),
97 
98             attrs: crate::attrs::Attrs::default(),
99             saved_attrs: crate::attrs::Attrs::default(),
100 
101             title: String::default(),
102             icon_name: String::default(),
103 
104             modes: enumset::EnumSet::default(),
105             mouse_protocol_mode: MouseProtocolMode::default(),
106             mouse_protocol_encoding: MouseProtocolEncoding::default(),
107 
108             audible_bell_count: 0,
109             visual_bell_count: 0,
110         }
111     }
112 
set_size(&mut self, rows: u16, cols: u16)113     pub(crate) fn set_size(&mut self, rows: u16, cols: u16) {
114         self.grid.set_size(crate::grid::Size { rows, cols });
115         self.alternate_grid
116             .set_size(crate::grid::Size { rows, cols });
117     }
118 
119     /// Returns the current size of the terminal.
120     ///
121     /// The return value will be (rows, cols).
122     #[must_use]
size(&self) -> (u16, u16)123     pub fn size(&self) -> (u16, u16) {
124         let size = self.grid().size();
125         (size.rows, size.cols)
126     }
127 
128     /// Returns the current position in the scrollback.
129     ///
130     /// This position indicates the offset from the top of the screen, and is
131     /// `0` when the normal screen is in view.
132     #[must_use]
scrollback(&self) -> usize133     pub fn scrollback(&self) -> usize {
134         self.grid().scrollback()
135     }
136 
set_scrollback(&mut self, rows: usize)137     pub(crate) fn set_scrollback(&mut self, rows: usize) {
138         self.grid_mut().set_scrollback(rows);
139     }
140 
141     /// Returns the text contents of the terminal.
142     ///
143     /// This will not include any formatting information, and will be in plain
144     /// text format.
145     #[must_use]
contents(&self) -> String146     pub fn contents(&self) -> String {
147         let mut contents = String::new();
148         self.write_contents(&mut contents);
149         contents
150     }
151 
write_contents(&self, contents: &mut String)152     fn write_contents(&self, contents: &mut String) {
153         self.grid().write_contents(contents);
154     }
155 
156     /// Returns the text contents of the terminal by row, restricted to the
157     /// given subset of columns.
158     ///
159     /// This will not include any formatting information, and will be in plain
160     /// text format.
161     ///
162     /// Newlines will not be included.
rows( &self, start: u16, width: u16, ) -> impl Iterator<Item = String> + '_163     pub fn rows(
164         &self,
165         start: u16,
166         width: u16,
167     ) -> impl Iterator<Item = String> + '_ {
168         self.grid().visible_rows().map(move |row| {
169             let mut contents = String::new();
170             row.write_contents(&mut contents, start, width, false);
171             contents
172         })
173     }
174 
175     /// Returns the formatted visible contents of the terminal.
176     ///
177     /// Formatting information will be included inline as terminal escape
178     /// codes. The result will be suitable for feeding directly to a raw
179     /// terminal parser, and will result in the same visual output.
180     #[must_use]
contents_formatted(&self) -> Vec<u8>181     pub fn contents_formatted(&self) -> Vec<u8> {
182         let mut contents = vec![];
183         self.write_contents_formatted(&mut contents);
184         contents
185     }
186 
write_contents_formatted(&self, contents: &mut Vec<u8>)187     fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
188         crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
189         let prev_attrs = self.grid().write_contents_formatted(contents);
190         self.attrs.write_escape_code_diff(contents, &prev_attrs);
191     }
192 
193     /// Returns the formatted visible contents of the terminal by row,
194     /// restricted to the given subset of columns.
195     ///
196     /// Formatting information will be included inline as terminal escape
197     /// codes. The result will be suitable for feeding directly to a raw
198     /// terminal parser, and will result in the same visual output.
199     ///
200     /// You are responsible for positioning the cursor before printing each
201     /// row, and the final cursor position after displaying each row is
202     /// unspecified.
rows_formatted( &self, start: u16, width: u16, ) -> impl Iterator<Item = Vec<u8>> + '_203     pub fn rows_formatted(
204         &self,
205         start: u16,
206         width: u16,
207     ) -> impl Iterator<Item = Vec<u8>> + '_ {
208         self.grid().visible_rows().enumerate().map(move |(i, row)| {
209             let i = i.try_into().unwrap();
210             let mut contents = vec![];
211             row.write_contents_formatted(
212                 &mut contents,
213                 start,
214                 width,
215                 i,
216                 false,
217                 crate::grid::Pos { row: i, col: start },
218                 crate::attrs::Attrs::default(),
219             );
220             contents
221         })
222     }
223 
224     /// Returns a terminal byte stream sufficient to turn the visible contents
225     /// of the screen described by `prev` into the visible contents of the
226     /// screen described by `self`.
227     ///
228     /// The result of rendering `prev.contents_formatted()` followed by
229     /// `self.contents_diff(prev)` should be equivalent to the result of
230     /// rendering `self.contents_formatted()`. This is primarily useful when
231     /// you already have a terminal parser whose state is described by `prev`,
232     /// since the diff will likely require less memory and cause less
233     /// flickering than redrawing the entire screen contents.
234     #[must_use]
contents_diff(&self, prev: &Self) -> Vec<u8>235     pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
236         let mut contents = vec![];
237         self.write_contents_diff(&mut contents, prev);
238         contents
239     }
240 
write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self)241     fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
242         if self.hide_cursor() != prev.hide_cursor() {
243             crate::term::HideCursor::new(self.hide_cursor())
244                 .write_buf(contents);
245         }
246         let prev_attrs = self.grid().write_contents_diff(
247             contents,
248             prev.grid(),
249             prev.attrs,
250         );
251         self.attrs.write_escape_code_diff(contents, &prev_attrs);
252     }
253 
254     /// Returns a sequence of terminal byte streams sufficient to turn the
255     /// visible contents of the subset of each row from `prev` (as described
256     /// by `start` and `width`) into the visible contents of the corresponding
257     /// row subset in `self`.
258     ///
259     /// You are responsible for positioning the cursor before printing each
260     /// row, and the final cursor position after displaying each row is
261     /// unspecified.
rows_diff<'a>( &'a self, prev: &'a Self, start: u16, width: u16, ) -> impl Iterator<Item = Vec<u8>> + 'a262     pub fn rows_diff<'a>(
263         &'a self,
264         prev: &'a Self,
265         start: u16,
266         width: u16,
267     ) -> impl Iterator<Item = Vec<u8>> + 'a {
268         self.grid()
269             .visible_rows()
270             .zip(prev.grid().visible_rows())
271             .enumerate()
272             .map(move |(i, (row, prev_row))| {
273                 let i = i.try_into().unwrap();
274                 let mut contents = vec![];
275                 row.write_contents_diff(
276                     &mut contents,
277                     prev_row,
278                     start,
279                     width,
280                     i,
281                     false,
282                     crate::grid::Pos { row: i, col: start },
283                     crate::attrs::Attrs::default(),
284                 );
285                 contents
286             })
287     }
288 
289     /// Returns terminal escape sequences sufficient to set the current
290     /// terminal's input modes.
291     ///
292     /// Supported modes are:
293     /// * application keypad
294     /// * application cursor
295     /// * bracketed paste
296     /// * xterm mouse support
297     #[must_use]
input_mode_formatted(&self) -> Vec<u8>298     pub fn input_mode_formatted(&self) -> Vec<u8> {
299         let mut contents = vec![];
300         self.write_input_mode_formatted(&mut contents);
301         contents
302     }
303 
write_input_mode_formatted(&self, contents: &mut Vec<u8>)304     fn write_input_mode_formatted(&self, contents: &mut Vec<u8>) {
305         crate::term::ApplicationKeypad::new(
306             self.mode(Mode::ApplicationKeypad),
307         )
308         .write_buf(contents);
309         crate::term::ApplicationCursor::new(
310             self.mode(Mode::ApplicationCursor),
311         )
312         .write_buf(contents);
313         crate::term::BracketedPaste::new(self.mode(Mode::BracketedPaste))
314             .write_buf(contents);
315         crate::term::MouseProtocolMode::new(
316             self.mouse_protocol_mode,
317             MouseProtocolMode::None,
318         )
319         .write_buf(contents);
320         crate::term::MouseProtocolEncoding::new(
321             self.mouse_protocol_encoding,
322             MouseProtocolEncoding::Default,
323         )
324         .write_buf(contents);
325     }
326 
327     /// Returns terminal escape sequences sufficient to change the previous
328     /// terminal's input modes to the input modes enabled in the current
329     /// terminal.
330     #[must_use]
input_mode_diff(&self, prev: &Self) -> Vec<u8>331     pub fn input_mode_diff(&self, prev: &Self) -> Vec<u8> {
332         let mut contents = vec![];
333         self.write_input_mode_diff(&mut contents, prev);
334         contents
335     }
336 
write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self)337     fn write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
338         if self.mode(Mode::ApplicationKeypad)
339             != prev.mode(Mode::ApplicationKeypad)
340         {
341             crate::term::ApplicationKeypad::new(
342                 self.mode(Mode::ApplicationKeypad),
343             )
344             .write_buf(contents);
345         }
346         if self.mode(Mode::ApplicationCursor)
347             != prev.mode(Mode::ApplicationCursor)
348         {
349             crate::term::ApplicationCursor::new(
350                 self.mode(Mode::ApplicationCursor),
351             )
352             .write_buf(contents);
353         }
354         if self.mode(Mode::BracketedPaste) != prev.mode(Mode::BracketedPaste)
355         {
356             crate::term::BracketedPaste::new(self.mode(Mode::BracketedPaste))
357                 .write_buf(contents);
358         }
359         crate::term::MouseProtocolMode::new(
360             self.mouse_protocol_mode,
361             prev.mouse_protocol_mode,
362         )
363         .write_buf(contents);
364         crate::term::MouseProtocolEncoding::new(
365             self.mouse_protocol_encoding,
366             prev.mouse_protocol_encoding,
367         )
368         .write_buf(contents);
369     }
370 
371     /// Returns terminal escape sequences sufficient to set the current
372     /// terminal's window title.
373     #[must_use]
title_formatted(&self) -> Vec<u8>374     pub fn title_formatted(&self) -> Vec<u8> {
375         let mut contents = vec![];
376         self.write_title_formatted(&mut contents);
377         contents
378     }
379 
write_title_formatted(&self, contents: &mut Vec<u8>)380     fn write_title_formatted(&self, contents: &mut Vec<u8>) {
381         crate::term::ChangeTitle::new(&self.icon_name, &self.title, "", "")
382             .write_buf(contents);
383     }
384 
385     /// Returns terminal escape sequences sufficient to change the previous
386     /// terminal's window title to the window title set in the current
387     /// terminal.
388     #[must_use]
title_diff(&self, prev: &Self) -> Vec<u8>389     pub fn title_diff(&self, prev: &Self) -> Vec<u8> {
390         let mut contents = vec![];
391         self.write_title_diff(&mut contents, prev);
392         contents
393     }
394 
write_title_diff(&self, contents: &mut Vec<u8>, prev: &Self)395     fn write_title_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
396         crate::term::ChangeTitle::new(
397             &self.icon_name,
398             &self.title,
399             &prev.icon_name,
400             &prev.title,
401         )
402         .write_buf(contents);
403     }
404 
405     /// Returns terminal escape sequences sufficient to cause audible and
406     /// visual bells to occur if they have been received since the terminal
407     /// described by `prev`.
408     #[must_use]
bells_diff(&self, prev: &Self) -> Vec<u8>409     pub fn bells_diff(&self, prev: &Self) -> Vec<u8> {
410         let mut contents = vec![];
411         self.write_bells_diff(&mut contents, prev);
412         contents
413     }
414 
write_bells_diff(&self, contents: &mut Vec<u8>, prev: &Self)415     fn write_bells_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
416         if self.audible_bell_count != prev.audible_bell_count {
417             crate::term::AudibleBell::default().write_buf(contents);
418         }
419         if self.visual_bell_count != prev.visual_bell_count {
420             crate::term::VisualBell::default().write_buf(contents);
421         }
422     }
423 
424     /// Returns the `Cell` object at the given location in the terminal, if it
425     /// exists.
426     #[must_use]
cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell>427     pub fn cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell> {
428         self.grid().visible_cell(crate::grid::Pos { row, col })
429     }
430 
431     /// Returns the current cursor position of the terminal.
432     ///
433     /// The return value will be (row, col).
434     #[must_use]
cursor_position(&self) -> (u16, u16)435     pub fn cursor_position(&self) -> (u16, u16) {
436         let pos = self.grid().pos();
437         (pos.row, pos.col)
438     }
439 
440     /// Returns the terminal's window title.
441     #[must_use]
title(&self) -> &str442     pub fn title(&self) -> &str {
443         &self.title
444     }
445 
446     /// Returns the terminal's icon name.
447     #[must_use]
icon_name(&self) -> &str448     pub fn icon_name(&self) -> &str {
449         &self.icon_name
450     }
451 
452     /// Returns a value which changes every time an audible bell is received.
453     ///
454     /// Typically you would store this number after each call to `process`,
455     /// and trigger an audible bell whenever it changes.
456     ///
457     /// You shouldn't rely on the exact value returned here, since the exact
458     /// value will not be maintained by `contents_formatted` or
459     /// `contents_diff`.
460     #[must_use]
audible_bell_count(&self) -> usize461     pub fn audible_bell_count(&self) -> usize {
462         self.audible_bell_count
463     }
464 
465     /// Returns a value which changes every time an visual bell is received.
466     ///
467     /// Typically you would store this number after each call to `process`,
468     /// and trigger an visual bell whenever it changes.
469     ///
470     /// You shouldn't rely on the exact value returned here, since the exact
471     /// value will not be maintained by `contents_formatted` or
472     /// `contents_diff`.
473     #[must_use]
visual_bell_count(&self) -> usize474     pub fn visual_bell_count(&self) -> usize {
475         self.visual_bell_count
476     }
477 
478     /// Returns whether the terminal should be in application keypad mode.
479     #[must_use]
application_keypad(&self) -> bool480     pub fn application_keypad(&self) -> bool {
481         self.mode(Mode::ApplicationKeypad)
482     }
483 
484     /// Returns whether the terminal should be in application cursor mode.
485     #[must_use]
application_cursor(&self) -> bool486     pub fn application_cursor(&self) -> bool {
487         self.mode(Mode::ApplicationCursor)
488     }
489 
490     /// Returns whether the terminal should be in hide cursor mode.
491     #[must_use]
hide_cursor(&self) -> bool492     pub fn hide_cursor(&self) -> bool {
493         self.mode(Mode::HideCursor)
494     }
495 
496     /// Returns whether the terminal should be in bracketed paste mode.
497     #[must_use]
bracketed_paste(&self) -> bool498     pub fn bracketed_paste(&self) -> bool {
499         self.mode(Mode::BracketedPaste)
500     }
501 
502     /// Returns the currently active `MouseProtocolMode`
503     #[must_use]
mouse_protocol_mode(&self) -> MouseProtocolMode504     pub fn mouse_protocol_mode(&self) -> MouseProtocolMode {
505         self.mouse_protocol_mode
506     }
507 
508     /// Returns the currently active `MouseProtocolEncoding`
509     #[must_use]
mouse_protocol_encoding(&self) -> MouseProtocolEncoding510     pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding {
511         self.mouse_protocol_encoding
512     }
513 
grid(&self) -> &crate::grid::Grid514     fn grid(&self) -> &crate::grid::Grid {
515         if self.mode(Mode::AlternateScreen) {
516             &self.alternate_grid
517         } else {
518             &self.grid
519         }
520     }
521 
grid_mut(&mut self) -> &mut crate::grid::Grid522     fn grid_mut(&mut self) -> &mut crate::grid::Grid {
523         if self.mode(Mode::AlternateScreen) {
524             &mut self.alternate_grid
525         } else {
526             &mut self.grid
527         }
528     }
529 
drawing_row(&self, pos: crate::grid::Pos) -> Option<&crate::row::Row>530     fn drawing_row(&self, pos: crate::grid::Pos) -> Option<&crate::row::Row> {
531         self.grid().drawing_row(pos)
532     }
533 
drawing_cell( &self, pos: crate::grid::Pos, ) -> Option<&crate::cell::Cell>534     fn drawing_cell(
535         &self,
536         pos: crate::grid::Pos,
537     ) -> Option<&crate::cell::Cell> {
538         self.grid().drawing_cell(pos)
539     }
540 
drawing_cell_mut( &mut self, pos: crate::grid::Pos, ) -> Option<&mut crate::cell::Cell>541     fn drawing_cell_mut(
542         &mut self,
543         pos: crate::grid::Pos,
544     ) -> Option<&mut crate::cell::Cell> {
545         self.grid_mut().drawing_cell_mut(pos)
546     }
547 
current_cell(&self) -> &crate::cell::Cell548     fn current_cell(&self) -> &crate::cell::Cell {
549         self.grid().current_cell()
550     }
551 
current_cell_mut(&mut self) -> &mut crate::cell::Cell552     fn current_cell_mut(&mut self) -> &mut crate::cell::Cell {
553         self.grid_mut().current_cell_mut()
554     }
555 
enter_alternate_grid(&mut self)556     fn enter_alternate_grid(&mut self) {
557         self.grid_mut().set_scrollback(0);
558         self.set_mode(Mode::AlternateScreen);
559     }
560 
exit_alternate_grid(&mut self)561     fn exit_alternate_grid(&mut self) {
562         self.clear_mode(Mode::AlternateScreen);
563     }
564 
save_cursor(&mut self)565     fn save_cursor(&mut self) {
566         self.grid_mut().save_cursor();
567         self.saved_attrs = self.attrs;
568     }
569 
restore_cursor(&mut self)570     fn restore_cursor(&mut self) {
571         self.grid_mut().restore_cursor();
572         self.attrs = self.saved_attrs;
573     }
574 
set_mode(&mut self, mode: Mode)575     fn set_mode(&mut self, mode: Mode) {
576         self.modes.insert(mode);
577     }
578 
clear_mode(&mut self, mode: Mode)579     fn clear_mode(&mut self, mode: Mode) {
580         self.modes.remove(mode);
581     }
582 
mode(&self, mode: Mode) -> bool583     fn mode(&self, mode: Mode) -> bool {
584         self.modes.contains(mode)
585     }
586 
set_mouse_mode(&mut self, mode: MouseProtocolMode)587     fn set_mouse_mode(&mut self, mode: MouseProtocolMode) {
588         self.mouse_protocol_mode = mode;
589     }
590 
clear_mouse_mode(&mut self, mode: MouseProtocolMode)591     fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) {
592         if self.mouse_protocol_mode == mode {
593             self.mouse_protocol_mode = MouseProtocolMode::default();
594         }
595     }
596 
set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding)597     fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
598         self.mouse_protocol_encoding = encoding;
599     }
600 
clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding)601     fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
602         if self.mouse_protocol_encoding == encoding {
603             self.mouse_protocol_encoding = MouseProtocolEncoding::default();
604         }
605     }
606 }
607 
608 // unclear why putting this attribute on the individual methods doesn't work
609 #[allow(clippy::unused_self)]
610 impl Screen {
611     #[allow(clippy::too_many_lines)]
text(&mut self, c: char)612     fn text(&mut self, c: char) {
613         let pos = self.grid().pos();
614         let size = self.grid().size();
615         let attrs = self.attrs;
616 
617         let width = c.width().unwrap_or(0).try_into().unwrap();
618 
619         // zero width characters still cause the cursor to wrap - this doesn't
620         // affect which cell they go into (the "previous cell" for both (row,
621         // max_col + 1) and (row + 1, 0) is (row, max_col)), but does affect
622         // further movement afterwards - writing an `a` at (row, max_col)
623         // followed by a crlf puts the cursor at (row + 1,
624         // 0), but writing a `à` (specifically `a` followed by a combining
625         // grave accent - the normalized U+00E0 "latin small letter a with
626         // grave" behaves the same as `a`) at (row, max_col) followed by a
627         // crlf puts the cursor at (row + 2, 0)
628         let wrap_width = if width == 0 { 1 } else { width };
629 
630         // it doesn't make any sense to wrap if the last column in a row
631         // didn't already have contents. don't try to handle the case where a
632         // character wraps because there was only one column left in the
633         // previous row - literally everything handles this case differently,
634         // and this is tmux behavior (and also the simplest). i'm open to
635         // reconsidering this behavior, but only with a really good reason
636         // (xterm handles this by introducing the concept of triple width
637         // cells, which i really don't want to do).
638         let mut wrap = false;
639         if pos.col > size.cols - wrap_width {
640             let last_cell = self
641                 .drawing_cell(crate::grid::Pos {
642                     row: pos.row,
643                     col: size.cols - 1,
644                 })
645                 .unwrap();
646             if last_cell.has_contents() || last_cell.is_wide_continuation() {
647                 wrap = true;
648             }
649         }
650         self.grid_mut().col_wrap(wrap_width, wrap);
651         let pos = self.grid().pos();
652 
653         if width == 0 {
654             if pos.col > 0 {
655                 let mut prev_cell = self
656                     .drawing_cell_mut(crate::grid::Pos {
657                         row: pos.row,
658                         col: pos.col - 1,
659                     })
660                     .unwrap();
661                 if prev_cell.is_wide_continuation() {
662                     prev_cell = self
663                         .drawing_cell_mut(crate::grid::Pos {
664                             row: pos.row,
665                             col: pos.col - 2,
666                         })
667                         .unwrap();
668                 }
669                 prev_cell.append(c);
670             } else if pos.row > 0 {
671                 let prev_row = self
672                     .drawing_row(crate::grid::Pos {
673                         row: pos.row - 1,
674                         col: 0,
675                     })
676                     .unwrap();
677                 if prev_row.wrapped() {
678                     let mut prev_cell = self
679                         .drawing_cell_mut(crate::grid::Pos {
680                             row: pos.row - 1,
681                             col: size.cols - 1,
682                         })
683                         .unwrap();
684                     if prev_cell.is_wide_continuation() {
685                         prev_cell = self
686                             .drawing_cell_mut(crate::grid::Pos {
687                                 row: pos.row - 1,
688                                 col: size.cols - 2,
689                             })
690                             .unwrap();
691                     }
692                     prev_cell.append(c);
693                 }
694             }
695         } else {
696             let drawing_pos = if pos.col < size.cols {
697                 pos
698             } else {
699                 crate::grid::Pos {
700                     row: pos.row + 1,
701                     col: 0,
702                 }
703             };
704 
705             if self
706                 .drawing_cell(drawing_pos)
707                 .unwrap()
708                 .is_wide_continuation()
709             {
710                 let prev_cell = self
711                     .drawing_cell_mut(crate::grid::Pos {
712                         row: drawing_pos.row,
713                         col: drawing_pos.col - 1,
714                     })
715                     .unwrap();
716                 prev_cell.clear(attrs);
717             }
718 
719             if self.drawing_cell(drawing_pos).unwrap().is_wide() {
720                 let next_cell = self
721                     .drawing_cell_mut(crate::grid::Pos {
722                         row: drawing_pos.row,
723                         col: drawing_pos.col + 1,
724                     })
725                     .unwrap();
726                 next_cell.clear(attrs);
727             }
728 
729             let cell = self.current_cell_mut();
730             cell.set(c, attrs);
731             self.grid_mut().col_inc(1);
732             if width > 1 {
733                 let pos = self.grid().pos();
734                 if self.current_cell().is_wide() {
735                     let next_next_cell = self
736                         .drawing_cell_mut(crate::grid::Pos {
737                             row: pos.row,
738                             col: pos.col + 1,
739                         })
740                         .unwrap();
741                     next_next_cell.clear(attrs);
742                 }
743                 let next_cell = self.current_cell_mut();
744                 next_cell.clear(attrs);
745                 next_cell.set_wide_continuation(true);
746                 self.grid_mut().col_inc(1);
747             }
748         }
749     }
750 
751     // control codes
752 
bel(&mut self)753     fn bel(&mut self) {
754         self.audible_bell_count += 1;
755     }
756 
bs(&mut self)757     fn bs(&mut self) {
758         self.grid_mut().col_dec(1);
759     }
760 
tab(&mut self)761     fn tab(&mut self) {
762         self.grid_mut().col_tab();
763     }
764 
lf(&mut self)765     fn lf(&mut self) {
766         self.grid_mut().row_inc_scroll(1);
767     }
768 
vt(&mut self)769     fn vt(&mut self) {
770         self.lf();
771     }
772 
ff(&mut self)773     fn ff(&mut self) {
774         self.lf();
775     }
776 
cr(&mut self)777     fn cr(&mut self) {
778         self.grid_mut().col_set(0);
779     }
780 
781     // escape codes
782 
783     // ESC 7
decsc(&mut self)784     fn decsc(&mut self) {
785         self.save_cursor();
786     }
787 
788     // ESC 8
decrc(&mut self)789     fn decrc(&mut self) {
790         self.restore_cursor();
791     }
792 
793     // ESC =
deckpam(&mut self)794     fn deckpam(&mut self) {
795         self.set_mode(Mode::ApplicationKeypad);
796     }
797 
798     // ESC >
deckpnm(&mut self)799     fn deckpnm(&mut self) {
800         self.clear_mode(Mode::ApplicationKeypad);
801     }
802 
803     // ESC M
ri(&mut self)804     fn ri(&mut self) {
805         self.grid_mut().row_dec_scroll(1);
806     }
807 
808     // ESC c
ris(&mut self)809     fn ris(&mut self) {
810         let title = self.title.clone();
811         let icon_name = self.icon_name.clone();
812         let audible_bell_count = self.audible_bell_count;
813         let visual_bell_count = self.visual_bell_count;
814 
815         *self = Self::new(self.grid.size(), self.grid.scrollback_len());
816 
817         self.title = title;
818         self.icon_name = icon_name;
819         self.audible_bell_count = audible_bell_count;
820         self.visual_bell_count = visual_bell_count;
821     }
822 
823     // ESC g
vb(&mut self)824     fn vb(&mut self) {
825         self.visual_bell_count += 1;
826     }
827 
828     // csi codes
829 
830     // CSI @
ich(&mut self, count: u16)831     fn ich(&mut self, count: u16) {
832         self.grid_mut().insert_cells(count);
833     }
834 
835     // CSI A
cuu(&mut self, offset: u16)836     fn cuu(&mut self, offset: u16) {
837         self.grid_mut().row_dec_clamp(offset);
838     }
839 
840     // CSI B
cud(&mut self, offset: u16)841     fn cud(&mut self, offset: u16) {
842         self.grid_mut().row_inc_clamp(offset);
843     }
844 
845     // CSI C
cuf(&mut self, offset: u16)846     fn cuf(&mut self, offset: u16) {
847         self.grid_mut().col_inc_clamp(offset);
848     }
849 
850     // CSI D
cub(&mut self, offset: u16)851     fn cub(&mut self, offset: u16) {
852         self.grid_mut().col_dec(offset);
853     }
854 
855     // CSI G
cha(&mut self, col: u16)856     fn cha(&mut self, col: u16) {
857         self.grid_mut().col_set(col - 1);
858     }
859 
860     // CSI H
cup(&mut self, (row, col): (u16, u16))861     fn cup(&mut self, (row, col): (u16, u16)) {
862         self.grid_mut().set_pos(crate::grid::Pos {
863             row: row - 1,
864             col: col - 1,
865         });
866     }
867 
868     // CSI J
ed(&mut self, mode: u16)869     fn ed(&mut self, mode: u16) {
870         let attrs = self.attrs;
871         match mode {
872             0 => self.grid_mut().erase_all_forward(attrs),
873             1 => self.grid_mut().erase_all_backward(attrs),
874             2 => self.grid_mut().erase_all(attrs),
875             n => {
876                 log::debug!("unhandled ED mode: {}", n);
877             }
878         }
879     }
880 
881     // CSI ? J
decsed(&mut self, mode: u16)882     fn decsed(&mut self, mode: u16) {
883         self.ed(mode);
884     }
885 
886     // CSI K
el(&mut self, mode: u16)887     fn el(&mut self, mode: u16) {
888         let attrs = self.attrs;
889         match mode {
890             0 => self.grid_mut().erase_row_forward(attrs),
891             1 => self.grid_mut().erase_row_backward(attrs),
892             2 => self.grid_mut().erase_row(attrs),
893             n => {
894                 log::debug!("unhandled EL mode: {}", n);
895             }
896         }
897     }
898 
899     // CSI ? K
decsel(&mut self, mode: u16)900     fn decsel(&mut self, mode: u16) {
901         self.el(mode);
902     }
903 
904     // CSI L
il(&mut self, count: u16)905     fn il(&mut self, count: u16) {
906         self.grid_mut().insert_lines(count);
907     }
908 
909     // CSI M
dl(&mut self, count: u16)910     fn dl(&mut self, count: u16) {
911         self.grid_mut().delete_lines(count);
912     }
913 
914     // CSI P
dch(&mut self, count: u16)915     fn dch(&mut self, count: u16) {
916         self.grid_mut().delete_cells(count);
917     }
918 
919     // CSI S
su(&mut self, count: u16)920     fn su(&mut self, count: u16) {
921         self.grid_mut().scroll_up(count);
922     }
923 
924     // CSI T
sd(&mut self, count: u16)925     fn sd(&mut self, count: u16) {
926         self.grid_mut().scroll_down(count);
927     }
928 
929     // CSI X
ech(&mut self, count: u16)930     fn ech(&mut self, count: u16) {
931         let attrs = self.attrs;
932         self.grid_mut().erase_cells(count, attrs);
933     }
934 
935     // CSI d
vpa(&mut self, row: u16)936     fn vpa(&mut self, row: u16) {
937         self.grid_mut().row_set(row - 1);
938     }
939 
940     // CSI h
sm(&mut self, params: &[i64])941     fn sm(&mut self, params: &[i64]) {
942         // nothing, i think?
943         if log::log_enabled!(log::Level::Debug) {
944             log::debug!("unhandled SM mode: {}", param_str(params))
945         }
946     }
947 
948     // CSI ? h
decset(&mut self, params: &[i64])949     fn decset(&mut self, params: &[i64]) {
950         for param in params {
951             match param {
952                 1 => self.set_mode(Mode::ApplicationCursor),
953                 6 => self.grid_mut().set_origin_mode(true),
954                 9 => self.set_mouse_mode(MouseProtocolMode::Press),
955                 25 => self.clear_mode(Mode::HideCursor),
956                 47 => self.enter_alternate_grid(),
957                 1000 => self.set_mouse_mode(MouseProtocolMode::PressRelease),
958                 1002 => self.set_mouse_mode(MouseProtocolMode::ButtonMotion),
959                 1003 => self.set_mouse_mode(MouseProtocolMode::AnyMotion),
960                 1005 => self.set_mouse_encoding(MouseProtocolEncoding::Utf8),
961                 1006 => self.set_mouse_encoding(MouseProtocolEncoding::Sgr),
962                 1049 => {
963                     self.decsc();
964                     self.alternate_grid.clear();
965                     self.enter_alternate_grid();
966                 }
967                 2004 => self.set_mode(Mode::BracketedPaste),
968                 n => {
969                     log::debug!("unhandled DECSET mode: {}", n);
970                 }
971             }
972         }
973     }
974 
975     // CSI l
rm(&mut self, params: &[i64])976     fn rm(&mut self, params: &[i64]) {
977         // nothing, i think?
978         if log::log_enabled!(log::Level::Debug) {
979             log::debug!("unhandled RM mode: {}", param_str(params))
980         }
981     }
982 
983     // CSI ? l
decrst(&mut self, params: &[i64])984     fn decrst(&mut self, params: &[i64]) {
985         for param in params {
986             match param {
987                 1 => self.clear_mode(Mode::ApplicationCursor),
988                 6 => self.grid_mut().set_origin_mode(false),
989                 9 => self.clear_mouse_mode(MouseProtocolMode::Press),
990                 25 => self.set_mode(Mode::HideCursor),
991                 47 => {
992                     self.exit_alternate_grid();
993                 }
994                 1000 => {
995                     self.clear_mouse_mode(MouseProtocolMode::PressRelease)
996                 }
997                 1002 => {
998                     self.clear_mouse_mode(MouseProtocolMode::ButtonMotion)
999                 }
1000                 1003 => self.clear_mouse_mode(MouseProtocolMode::AnyMotion),
1001                 1005 => {
1002                     self.clear_mouse_encoding(MouseProtocolEncoding::Utf8)
1003                 }
1004                 1006 => self.clear_mouse_encoding(MouseProtocolEncoding::Sgr),
1005                 1049 => {
1006                     self.exit_alternate_grid();
1007                     self.decrc();
1008                 }
1009                 2004 => self.clear_mode(Mode::BracketedPaste),
1010                 n => {
1011                     log::debug!("unhandled DECRST mode: {}", n);
1012                 }
1013             }
1014         }
1015     }
1016 
1017     // CSI m
sgr(&mut self, params: &[i64])1018     fn sgr(&mut self, params: &[i64]) {
1019         let mut i = 0;
1020 
1021         macro_rules! next_param {
1022             () => {
1023                 if i >= params.len() {
1024                     return;
1025                 } else if let Some(n) = i64_to_u8(params[i]) {
1026                     i += 1;
1027                     n
1028                 } else {
1029                     return;
1030                 }
1031             };
1032         }
1033 
1034         loop {
1035             match next_param!() {
1036                 0 => self.attrs = crate::attrs::Attrs::default(),
1037                 1 => self.attrs.set_bold(true),
1038                 3 => self.attrs.set_italic(true),
1039                 4 => self.attrs.set_underline(true),
1040                 7 => self.attrs.set_inverse(true),
1041                 22 => self.attrs.set_bold(false),
1042                 23 => self.attrs.set_italic(false),
1043                 24 => self.attrs.set_underline(false),
1044                 27 => self.attrs.set_inverse(false),
1045                 n if n >= 30 && n <= 37 => {
1046                     self.attrs.fgcolor = crate::attrs::Color::Idx(n - 30);
1047                 }
1048                 38 => match next_param!() {
1049                     2 => {
1050                         let r = next_param!();
1051                         let g = next_param!();
1052                         let b = next_param!();
1053                         self.attrs.fgcolor =
1054                             crate::attrs::Color::Rgb(r, g, b);
1055                     }
1056                     5 => {
1057                         self.attrs.fgcolor =
1058                             crate::attrs::Color::Idx(next_param!());
1059                     }
1060                     n => {
1061                         log::debug!("unhandled SGR mode: 38 {}", n);
1062                         return;
1063                     }
1064                 },
1065                 39 => {
1066                     self.attrs.fgcolor = crate::attrs::Color::Default;
1067                 }
1068                 n if n >= 40 && n <= 47 => {
1069                     self.attrs.bgcolor = crate::attrs::Color::Idx(n - 40);
1070                 }
1071                 48 => match next_param!() {
1072                     2 => {
1073                         let r = next_param!();
1074                         let g = next_param!();
1075                         let b = next_param!();
1076                         self.attrs.bgcolor =
1077                             crate::attrs::Color::Rgb(r, g, b);
1078                     }
1079                     5 => {
1080                         self.attrs.bgcolor =
1081                             crate::attrs::Color::Idx(next_param!());
1082                     }
1083                     n => {
1084                         log::debug!("unhandled SGR mode: 48 {}", n);
1085                         return;
1086                     }
1087                 },
1088                 49 => {
1089                     self.attrs.bgcolor = crate::attrs::Color::Default;
1090                 }
1091                 n if n >= 90 && n <= 97 => {
1092                     self.attrs.fgcolor = crate::attrs::Color::Idx(n - 82);
1093                 }
1094                 n if n >= 100 && n <= 107 => {
1095                     self.attrs.bgcolor = crate::attrs::Color::Idx(n - 92);
1096                 }
1097                 n => {
1098                     log::debug!("unhandled SGR mode: {}", n);
1099                 }
1100             }
1101         }
1102     }
1103 
1104     // CSI r
decstbm(&mut self, (top, bottom): (u16, u16))1105     fn decstbm(&mut self, (top, bottom): (u16, u16)) {
1106         self.grid_mut().set_scroll_region(top - 1, bottom - 1);
1107     }
1108 
1109     // osc codes
1110 
osc0(&mut self, s: &[u8])1111     fn osc0(&mut self, s: &[u8]) {
1112         self.osc1(s);
1113         self.osc2(s);
1114     }
1115 
osc1(&mut self, s: &[u8])1116     fn osc1(&mut self, s: &[u8]) {
1117         if let Ok(s) = std::str::from_utf8(s) {
1118             self.icon_name = s.to_string();
1119         }
1120     }
1121 
osc2(&mut self, s: &[u8])1122     fn osc2(&mut self, s: &[u8]) {
1123         if let Ok(s) = std::str::from_utf8(s) {
1124             self.title = s.to_string();
1125         }
1126     }
1127 }
1128 
1129 impl vte::Perform for Screen {
print(&mut self, c: char)1130     fn print(&mut self, c: char) {
1131         self.text(c)
1132     }
1133 
execute(&mut self, b: u8)1134     fn execute(&mut self, b: u8) {
1135         match b {
1136             7 => self.bel(),
1137             8 => self.bs(),
1138             9 => self.tab(),
1139             10 => self.lf(),
1140             11 => self.vt(),
1141             12 => self.ff(),
1142             13 => self.cr(),
1143             _ => {
1144                 log::debug!("unhandled control character: {}", b);
1145             }
1146         }
1147     }
1148 
esc_dispatch( &mut self, _params: &[i64], intermediates: &[u8], _ignore: bool, b: u8, )1149     fn esc_dispatch(
1150         &mut self,
1151         _params: &[i64],
1152         intermediates: &[u8],
1153         _ignore: bool,
1154         b: u8,
1155     ) {
1156         match intermediates.get(0) {
1157             None => match b {
1158                 b'7' => self.decsc(),
1159                 b'8' => self.decrc(),
1160                 b'=' => self.deckpam(),
1161                 b'>' => self.deckpnm(),
1162                 b'M' => self.ri(),
1163                 b'c' => self.ris(),
1164                 b'g' => self.vb(),
1165                 _ => {
1166                     log::debug!("unhandled escape code: ESC {}", b);
1167                 }
1168             },
1169             Some(i) => {
1170                 log::debug!("unhandled escape code: ESC {} {}", i, b);
1171             }
1172         }
1173     }
1174 
csi_dispatch( &mut self, params: &[i64], intermediates: &[u8], _ignore: bool, c: char, )1175     fn csi_dispatch(
1176         &mut self,
1177         params: &[i64],
1178         intermediates: &[u8],
1179         _ignore: bool,
1180         c: char,
1181     ) {
1182         match intermediates.get(0) {
1183             None => match c {
1184                 '@' => self.ich(canonicalize_params_1(params, 1)),
1185                 'A' => self.cuu(canonicalize_params_1(params, 1)),
1186                 'B' => self.cud(canonicalize_params_1(params, 1)),
1187                 'C' => self.cuf(canonicalize_params_1(params, 1)),
1188                 'D' => self.cub(canonicalize_params_1(params, 1)),
1189                 'G' => self.cha(canonicalize_params_1(params, 1)),
1190                 'H' => self.cup(canonicalize_params_2(params, 1, 1)),
1191                 'J' => self.ed(canonicalize_params_1(params, 0)),
1192                 'K' => self.el(canonicalize_params_1(params, 0)),
1193                 'L' => self.il(canonicalize_params_1(params, 1)),
1194                 'M' => self.dl(canonicalize_params_1(params, 1)),
1195                 'P' => self.dch(canonicalize_params_1(params, 1)),
1196                 'S' => self.su(canonicalize_params_1(params, 1)),
1197                 'T' => self.sd(canonicalize_params_1(params, 1)),
1198                 'X' => self.ech(canonicalize_params_1(params, 1)),
1199                 'd' => self.vpa(canonicalize_params_1(params, 1)),
1200                 'h' => self.sm(canonicalize_params_multi(params)),
1201                 'l' => self.rm(canonicalize_params_multi(params)),
1202                 'm' => self.sgr(canonicalize_params_multi(params)),
1203                 'r' => self.decstbm(canonicalize_params_decstbm(
1204                     params,
1205                     self.grid().size(),
1206                 )),
1207                 _ => {
1208                     if log::log_enabled!(log::Level::Debug) {
1209                         log::debug!(
1210                             "unhandled csi sequence: CSI {} {}",
1211                             param_str(params),
1212                             c
1213                         )
1214                     }
1215                 }
1216             },
1217             Some(b'?') => match c {
1218                 'J' => self.decsed(canonicalize_params_1(params, 0)),
1219                 'K' => self.decsel(canonicalize_params_1(params, 0)),
1220                 'h' => self.decset(canonicalize_params_multi(params)),
1221                 'l' => self.decrst(canonicalize_params_multi(params)),
1222                 _ => {
1223                     if log::log_enabled!(log::Level::Debug) {
1224                         log::debug!(
1225                             "unhandled csi sequence: CSI ? {} {}",
1226                             param_str(params),
1227                             c
1228                         )
1229                     }
1230                 }
1231             },
1232             Some(i) => {
1233                 if log::log_enabled!(log::Level::Debug) {
1234                     log::debug!(
1235                         "unhandled csi sequence: CSI {} {} {}",
1236                         i,
1237                         param_str(params),
1238                         c
1239                     )
1240                 }
1241             }
1242         }
1243     }
1244 
osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool)1245     fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) {
1246         match (params.get(0), params.get(1)) {
1247             (Some(&b"0"), Some(s)) => self.osc0(s),
1248             (Some(&b"1"), Some(s)) => self.osc1(s),
1249             (Some(&b"2"), Some(s)) => self.osc2(s),
1250             _ => {
1251                 if log::log_enabled!(log::Level::Debug) {
1252                     log::debug!(
1253                         "unhandled osc sequence: OSC {}",
1254                         osc_param_str(params),
1255                     )
1256                 }
1257             }
1258         }
1259     }
1260 
hook( &mut self, params: &[i64], intermediates: &[u8], _ignore: bool, action: char, )1261     fn hook(
1262         &mut self,
1263         params: &[i64],
1264         intermediates: &[u8],
1265         _ignore: bool,
1266         action: char,
1267     ) {
1268         if log::log_enabled!(log::Level::Debug) {
1269             match intermediates.get(0) {
1270                 None => log::debug!(
1271                     "unhandled dcs sequence: DCS {} {}",
1272                     param_str(params),
1273                     action,
1274                 ),
1275                 Some(i) => log::debug!(
1276                     "unhandled dcs sequence: DCS {} {} {}",
1277                     i,
1278                     param_str(params),
1279                     action,
1280                 ),
1281             }
1282         }
1283     }
put(&mut self, _: u8)1284     fn put(&mut self, _: u8) {}
unhook(&mut self)1285     fn unhook(&mut self) {}
1286 }
1287 
canonicalize_params_1(params: &[i64], default: u16) -> u161288 fn canonicalize_params_1(params: &[i64], default: u16) -> u16 {
1289     let first = params.get(0).copied().unwrap_or(0);
1290     if first == 0 {
1291         default
1292     } else {
1293         i64_to_u16(first)
1294     }
1295 }
1296 
canonicalize_params_2( params: &[i64], default1: u16, default2: u16, ) -> (u16, u16)1297 fn canonicalize_params_2(
1298     params: &[i64],
1299     default1: u16,
1300     default2: u16,
1301 ) -> (u16, u16) {
1302     let first = params.get(0).copied().unwrap_or(0);
1303     let first = if first == 0 {
1304         default1
1305     } else {
1306         i64_to_u16(first)
1307     };
1308 
1309     let second = params.get(1).copied().unwrap_or(0);
1310     let second = if second == 0 {
1311         default2
1312     } else {
1313         i64_to_u16(second)
1314     };
1315 
1316     (first, second)
1317 }
1318 
canonicalize_params_multi(params: &[i64]) -> &[i64]1319 fn canonicalize_params_multi(params: &[i64]) -> &[i64] {
1320     if params.is_empty() {
1321         DEFAULT_MULTI_PARAMS
1322     } else {
1323         params
1324     }
1325 }
1326 
canonicalize_params_decstbm( params: &[i64], size: crate::grid::Size, ) -> (u16, u16)1327 fn canonicalize_params_decstbm(
1328     params: &[i64],
1329     size: crate::grid::Size,
1330 ) -> (u16, u16) {
1331     let top = params.get(0).copied().unwrap_or(0);
1332     let top = if top == 0 { 1 } else { i64_to_u16(top) };
1333 
1334     let bottom = params.get(1).copied().unwrap_or(0);
1335     let bottom = if bottom == 0 {
1336         size.rows
1337     } else {
1338         i64_to_u16(bottom)
1339     };
1340 
1341     (top, bottom)
1342 }
1343 
i64_to_u16(i: i64) -> u161344 fn i64_to_u16(i: i64) -> u16 {
1345     if i < 0 {
1346         0
1347     } else if i > i64::from(u16::max_value()) {
1348         u16::max_value()
1349     } else {
1350         i.try_into().unwrap()
1351     }
1352 }
1353 
i64_to_u8(i: i64) -> Option<u8>1354 fn i64_to_u8(i: i64) -> Option<u8> {
1355     if i < 0 || i > i64::from(u8::max_value()) {
1356         None
1357     } else {
1358         Some(i.try_into().unwrap())
1359     }
1360 }
1361 
param_str(params: &[i64]) -> String1362 fn param_str(params: &[i64]) -> String {
1363     let strs: Vec<_> = params
1364         .iter()
1365         .map(std::string::ToString::to_string)
1366         .collect();
1367     strs.join(" ; ")
1368 }
1369 
osc_param_str(params: &[&[u8]]) -> String1370 fn osc_param_str(params: &[&[u8]]) -> String {
1371     let strs: Vec<_> = params
1372         .iter()
1373         .map(|b| format!("\"{}\"", std::string::String::from_utf8_lossy(*b)))
1374         .collect();
1375     strs.join(" ; ")
1376 }
1377