| 450 |
450 |
|
pub wrap_next: bool,
|
| 451 |
451 |
|
}
|
| 452 |
452 |
|
|
|
453 |
+ |
/// How much of the mouse a program has asked to be told about.
|
|
454 |
+ |
///
|
|
455 |
+ |
/// Strictly increasing: each level includes everything below it, which is why
|
|
456 |
+ |
/// one field holds all of them rather than a flag per DECSET number. Setting
|
|
457 |
+ |
/// any level replaces the previous one, matching xterm — the modes are not
|
|
458 |
+ |
/// composable there either, however much the separate numbers suggest it.
|
|
459 |
+ |
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
460 |
+ |
pub enum MouseTracking {
|
|
461 |
+ |
/// The pointer belongs to the user: shop selects text with it.
|
|
462 |
+ |
#[default]
|
|
463 |
+ |
Off,
|
|
464 |
+ |
/// DECSET 9, X10 compatibility. Presses only, and no modifier bits.
|
|
465 |
+ |
Press,
|
|
466 |
+ |
/// DECSET 1000. Presses and releases.
|
|
467 |
+ |
Click,
|
|
468 |
+ |
/// DECSET 1002. Adds motion, but only while a button is held.
|
|
469 |
+ |
Drag,
|
|
470 |
+ |
/// DECSET 1003. Adds motion with no button down, which is a report per
|
|
471 |
+ |
/// cell crossed for as long as the pointer is over the window.
|
|
472 |
+ |
Motion,
|
|
473 |
+ |
}
|
|
474 |
+ |
|
|
475 |
+ |
/// How a mouse report is spelled on the wire.
|
|
476 |
+ |
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
477 |
+ |
pub enum MouseEncoding {
|
|
478 |
+ |
/// The original `CSI M Cb Cx Cy`, each field a byte biased by 32.
|
|
479 |
+ |
///
|
|
480 |
+ |
/// Two consequences worth knowing, and both are why 1006 exists: a
|
|
481 |
+ |
/// coordinate past 223 has no byte to land in and is dropped, and a
|
|
482 |
+ |
/// release does not say which button was let go.
|
|
483 |
+ |
#[default]
|
|
484 |
+ |
X10,
|
|
485 |
+ |
/// DECSET 1006. `CSI < b ; x ; y M` for a press, `m` for a release —
|
|
486 |
+ |
/// decimal, so no coordinate ceiling, and the release keeps its button.
|
|
487 |
+ |
Sgr,
|
|
488 |
+ |
}
|
|
489 |
+ |
|
|
490 |
+ |
/// Which button a mouse report is about.
|
|
491 |
+ |
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
492 |
+ |
pub enum MouseButton {
|
|
493 |
+ |
Left,
|
|
494 |
+ |
Middle,
|
|
495 |
+ |
Right,
|
|
496 |
+ |
WheelUp,
|
|
497 |
+ |
WheelDown,
|
|
498 |
+ |
/// Motion with nothing held. Only [`MouseTracking::Motion`] asks for it.
|
|
499 |
+ |
None,
|
|
500 |
+ |
}
|
|
501 |
+ |
|
|
502 |
+ |
impl MouseButton {
|
|
503 |
+ |
/// The low bits the wire spells this button with. Wheel buttons set 64,
|
|
504 |
+ |
/// which is the bit that distinguishes them from a real press.
|
|
505 |
+ |
fn code(self) -> u8 {
|
|
506 |
+ |
match self {
|
|
507 |
+ |
Self::Left => 0,
|
|
508 |
+ |
Self::Middle => 1,
|
|
509 |
+ |
Self::Right => 2,
|
|
510 |
+ |
Self::WheelUp => 64,
|
|
511 |
+ |
Self::WheelDown => 65,
|
|
512 |
+ |
// The same 3 a release uses. Unambiguous in context: this one
|
|
513 |
+ |
// always arrives with the motion bit set.
|
|
514 |
+ |
Self::None => 3,
|
|
515 |
+ |
}
|
|
516 |
+ |
}
|
|
517 |
+ |
|
|
518 |
+ |
fn is_wheel(self) -> bool {
|
|
519 |
+ |
matches!(self, Self::WheelUp | Self::WheelDown)
|
|
520 |
+ |
}
|
|
521 |
+ |
}
|
|
522 |
+ |
|
|
523 |
+ |
/// What the pointer did.
|
|
524 |
+ |
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
525 |
+ |
pub enum MouseAction {
|
|
526 |
+ |
Press,
|
|
527 |
+ |
Release,
|
|
528 |
+ |
/// The pointer crossed into another cell. Whether a button is held is read
|
|
529 |
+ |
/// from the report's button, not from here.
|
|
530 |
+ |
Motion,
|
|
531 |
+ |
}
|
|
532 |
+ |
|
|
533 |
+ |
/// Modifiers held while the pointer did it.
|
|
534 |
+ |
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
535 |
+ |
pub struct MouseMods {
|
|
536 |
+ |
pub shift: bool,
|
|
537 |
+ |
pub alt: bool,
|
|
538 |
+ |
pub ctrl: bool,
|
|
539 |
+ |
}
|
|
540 |
+ |
|
|
541 |
+ |
impl MouseMods {
|
|
542 |
+ |
fn bits(self) -> u8 {
|
|
543 |
+ |
u8::from(self.shift) * 4 + u8::from(self.alt) * 8 + u8::from(self.ctrl) * 16
|
|
544 |
+ |
}
|
|
545 |
+ |
}
|
|
546 |
+ |
|
|
547 |
+ |
/// One thing the pointer did, in grid coordinates, ready to be encoded.
|
|
548 |
+ |
///
|
|
549 |
+ |
/// Cells, 0-based, as the rest of this crate counts them. The +1 the wire
|
|
550 |
+ |
/// wants is applied at encoding time and nowhere else.
|
|
551 |
+ |
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
552 |
+ |
pub struct MouseReport {
|
|
553 |
+ |
pub button: MouseButton,
|
|
554 |
+ |
pub action: MouseAction,
|
|
555 |
+ |
pub col: u16,
|
|
556 |
+ |
pub row: u16,
|
|
557 |
+ |
pub mods: MouseMods,
|
|
558 |
+ |
}
|
|
559 |
+ |
|
|
560 |
+ |
/// The largest coordinate X10's byte-per-field encoding can carry.
|
|
561 |
+ |
///
|
|
562 |
+ |
/// A field is `32 + 1 + n` in one byte, so n stops at 222. Past that the
|
|
563 |
+ |
/// report is dropped rather than truncated: a wrong coordinate tells the
|
|
564 |
+ |
/// program the click was somewhere it wasn't, and a missing one tells it
|
|
565 |
+ |
/// nothing, which is the smaller lie.
|
|
566 |
+ |
const X10_COORD_MAX: u16 = 222;
|
|
567 |
+ |
|
| 453 |
568 |
|
/// One row that has scrolled off the top of the main screen.
|
| 454 |
569 |
|
///
|
| 455 |
570 |
|
/// Always exactly the grid's current width: `resize` rewraps the whole buffer
|
| 527 |
642 |
|
region_origin: u16,
|
| 528 |
643 |
|
cursor: Cursor,
|
| 529 |
644 |
|
cursor_shape: CursorShape,
|
| 530 |
|
- |
saved_main_cursor: Cursor,
|
| 531 |
|
- |
saved_alt_cursor: Cursor,
|
|
645 |
+ |
/// Where each screen's cursor was when the other took over, indexed by
|
|
646 |
+ |
/// `on_alt`. The alt-screen swap's own bookkeeping, and nothing else's.
|
|
647 |
+ |
swap_saved: [Cursor; 2],
|
|
648 |
+ |
/// DECSC's slot, one per screen, indexed by `on_alt`.
|
|
649 |
+ |
///
|
|
650 |
+ |
/// Separate from `swap_saved` because they answer to different owners and
|
|
651 |
+ |
/// sharing one slot loses saves. They shared one until this was written:
|
|
652 |
+ |
/// `ESC 7` on the alt screen wrote the slot that leaving alt restored
|
|
653 |
+ |
/// from, so a full-screen program that saved its cursor moved the shell's
|
|
654 |
+ |
/// on the way out. Per-screen because that is what xterm does, and an
|
|
655 |
+ |
/// `ESC 7` in vim has nothing to say about where the shell was.
|
|
656 |
+ |
dec_saved: [Cursor; 2],
|
| 532 |
657 |
|
scroll_top: u16, // 0-indexed, inclusive
|
| 533 |
658 |
|
scroll_bottom: u16, // 0-indexed, inclusive
|
| 534 |
659 |
|
pending_fg: Color,
|
| 564 |
689 |
|
// program that wants the wheel to mean something else clears it. Same
|
| 565 |
690 |
|
// division as 2004: the grid tracks the mode, the binary acts on it.
|
| 566 |
691 |
|
alternate_scroll: bool,
|
|
692 |
+ |
// DECSET 9/1000/1002/1003 and 1006: how much of the mouse the program
|
|
693 |
+ |
// wants, and in which encoding it wants it. Same division again — the grid
|
|
694 |
+ |
// records what was asked for and the binary, which is the only half that
|
|
695 |
+ |
// sees a pointer, does the sending.
|
|
696 |
+ |
mouse_tracking: MouseTracking,
|
|
697 |
+ |
mouse_encoding: MouseEncoding,
|
| 567 |
698 |
|
pending_title: Option<String>,
|
| 568 |
699 |
|
identity: Identity,
|
| 569 |
700 |
|
// Bytes the terminal owes the program, from queries it answered. The grid
|
| 609 |
740 |
|
..Cursor::default()
|
| 610 |
741 |
|
},
|
| 611 |
742 |
|
cursor_shape: CursorShape::Block,
|
| 612 |
|
- |
saved_main_cursor: Cursor::default(),
|
| 613 |
|
- |
saved_alt_cursor: Cursor::default(),
|
|
743 |
+ |
swap_saved: [Cursor::default(); 2],
|
|
744 |
+ |
dec_saved: [Cursor::default(); 2],
|
| 614 |
745 |
|
scroll_top: 0,
|
| 615 |
746 |
|
scroll_bottom: rows - 1,
|
| 616 |
747 |
|
pending_fg: Color::Default,
|
| 624 |
755 |
|
cursor_keys_application: false,
|
| 625 |
756 |
|
keypad_application: false,
|
| 626 |
757 |
|
alternate_scroll: true,
|
|
758 |
+ |
mouse_tracking: MouseTracking::Off,
|
|
759 |
+ |
mouse_encoding: MouseEncoding::X10,
|
| 627 |
760 |
|
pending_title: None,
|
| 628 |
761 |
|
identity: Identity::default(),
|
| 629 |
762 |
|
pending_replies: Vec::new(),
|
| 748 |
881 |
|
self.alternate_scroll
|
| 749 |
882 |
|
}
|
| 750 |
883 |
|
|
|
884 |
+ |
/// How much of the mouse the program has asked for. [`MouseTracking::Off`]
|
|
885 |
+ |
/// means the pointer is the user's, for selecting text.
|
|
886 |
+ |
pub fn mouse_tracking(&self) -> MouseTracking {
|
|
887 |
+ |
self.mouse_tracking
|
|
888 |
+ |
}
|
|
889 |
+ |
|
|
890 |
+ |
/// Which spelling a mouse report should use.
|
|
891 |
+ |
pub fn mouse_encoding(&self) -> MouseEncoding {
|
|
892 |
+ |
self.mouse_encoding
|
|
893 |
+ |
}
|
|
894 |
+ |
|
|
895 |
+ |
/// The bytes this pointer event owes the program, or `None` when the
|
|
896 |
+ |
/// program did not ask for it.
|
|
897 |
+ |
///
|
|
898 |
+ |
/// Filtering lives here rather than at the call site because the levels
|
|
899 |
+ |
/// are what decides it, and the levels are this crate's business. A
|
|
900 |
+ |
/// caller reports everything the pointer does and lets the answer decide.
|
|
901 |
+ |
pub fn encode_mouse(&self, r: MouseReport) -> Option<Vec<u8>> {
|
|
902 |
+ |
if self.mouse_tracking == MouseTracking::Off || !self.mouse_coords_fit(r.col, r.row) {
|
|
903 |
+ |
return None;
|
|
904 |
+ |
}
|
|
905 |
+ |
// The wheel has no release and no drag; it is a press or it is
|
|
906 |
+ |
// nothing, at every level that reports the mouse at all.
|
|
907 |
+ |
if r.button.is_wheel() {
|
|
908 |
+ |
return (r.action == MouseAction::Press).then(|| self.spell_mouse(r));
|
|
909 |
+ |
}
|
|
910 |
+ |
let wanted = match r.action {
|
|
911 |
+ |
MouseAction::Press => true,
|
|
912 |
+ |
MouseAction::Release => self.mouse_tracking >= MouseTracking::Click,
|
|
913 |
+ |
MouseAction::Motion if r.button == MouseButton::None => {
|
|
914 |
+ |
self.mouse_tracking == MouseTracking::Motion
|
|
915 |
+ |
}
|
|
916 |
+ |
MouseAction::Motion => self.mouse_tracking >= MouseTracking::Drag,
|
|
917 |
+ |
};
|
|
918 |
+ |
wanted.then(|| self.spell_mouse(r))
|
|
919 |
+ |
}
|
|
920 |
+ |
|
|
921 |
+ |
fn spell_mouse(&self, r: MouseReport) -> Vec<u8> {
|
|
922 |
+ |
let mut cb = r.button.code();
|
|
923 |
+ |
if r.action == MouseAction::Motion {
|
|
924 |
+ |
cb += 32;
|
|
925 |
+ |
}
|
|
926 |
+ |
// X10 compatibility mode predates modifier reporting, and a program
|
|
927 |
+ |
// that asked for it is parsing three fixed bytes.
|
|
928 |
+ |
if self.mouse_tracking != MouseTracking::Press {
|
|
929 |
+ |
cb += r.mods.bits();
|
|
930 |
+ |
}
|
|
931 |
+ |
match self.mouse_encoding {
|
|
932 |
+ |
MouseEncoding::Sgr => {
|
|
933 |
+ |
let end = if r.action == MouseAction::Release {
|
|
934 |
+ |
'm'
|
|
935 |
+ |
} else {
|
|
936 |
+ |
'M'
|
|
937 |
+ |
};
|
|
938 |
+ |
format!("\x1b[<{};{};{}{end}", cb, r.col + 1, r.row + 1).into_bytes()
|
|
939 |
+ |
}
|
|
940 |
+ |
MouseEncoding::X10 => {
|
|
941 |
+ |
// The button a release let go of has nowhere to be spelled
|
|
942 |
+ |
// here; 3 is "some button came up" and it is all the program
|
|
943 |
+ |
// gets. This is the limitation 1006 exists to lift.
|
|
944 |
+ |
if r.action == MouseAction::Release {
|
|
945 |
+ |
cb = 3 + if self.mouse_tracking == MouseTracking::Press {
|
|
946 |
+ |
0
|
|
947 |
+ |
} else {
|
|
948 |
+ |
r.mods.bits()
|
|
949 |
+ |
};
|
|
950 |
+ |
}
|
|
951 |
+ |
let mut out = vec![0x1b, b'[', b'M', 32 + cb];
|
|
952 |
+ |
out.push(32 + 1 + r.col as u8);
|
|
953 |
+ |
out.push(32 + 1 + r.row as u8);
|
|
954 |
+ |
out
|
|
955 |
+ |
}
|
|
956 |
+ |
}
|
|
957 |
+ |
}
|
|
958 |
+ |
|
|
959 |
+ |
/// Whether a report at these coordinates can be spelled at all.
|
|
960 |
+ |
///
|
|
961 |
+ |
/// Only X10 can fail, and only past its byte ceiling. Checked separately
|
|
962 |
+ |
/// from encoding so a caller can drop the event before doing the work.
|
|
963 |
+ |
pub fn mouse_coords_fit(&self, col: u16, row: u16) -> bool {
|
|
964 |
+ |
self.mouse_encoding == MouseEncoding::Sgr || (col <= X10_COORD_MAX && row <= X10_COORD_MAX)
|
|
965 |
+ |
}
|
|
966 |
+ |
|
| 751 |
967 |
|
/// Whether the alt screen is the active one.
|
| 752 |
968 |
|
///
|
| 753 |
969 |
|
/// The alt screen keeps no history, which is why anything deciding what a
|
| 1769 |
1985 |
|
}
|
| 1770 |
1986 |
|
}
|
| 1771 |
1987 |
|
|
|
1988 |
+ |
/// DECSC. Per-screen, so the alt screen's save cannot reach the main
|
|
1989 |
+ |
/// screen's slot.
|
|
1990 |
+ |
fn save_cursor(&mut self) {
|
|
1991 |
+ |
self.dec_saved[self.on_alt as usize] = self.cursor;
|
|
1992 |
+ |
}
|
|
1993 |
+ |
|
|
1994 |
+ |
/// DECRC. A restore with no matching save puts the cursor home, which is
|
|
1995 |
+ |
/// what the default slot holds.
|
|
1996 |
+ |
fn restore_cursor(&mut self) {
|
|
1997 |
+ |
let mut c = self.dec_saved[self.on_alt as usize];
|
|
1998 |
+ |
// The screen may have shrunk since the save. A cursor off the end of
|
|
1999 |
+ |
// it is not a position anything can draw at.
|
|
2000 |
+ |
c.row = c.row.min(self.rows - 1);
|
|
2001 |
+ |
c.col = c.col.min(self.cols - 1);
|
|
2002 |
+ |
self.cursor = c;
|
|
2003 |
+ |
}
|
|
2004 |
+ |
|
|
2005 |
+ |
/// Whether logical row `r` of the active screen runs onto the next.
|
|
2006 |
+ |
///
|
|
2007 |
+ |
/// Screen-relative, unlike the public [`row_wrapped`](Self::row_wrapped),
|
|
2008 |
+ |
/// which takes a viewport row and may answer out of history. The row
|
|
2009 |
+ |
/// movers below deal in screen rows, and reading the viewport's numbering
|
|
2010 |
+ |
/// here would move the wrong flags whenever the user had scrolled back.
|
|
2011 |
+ |
fn screen_row_wrapped(&self, r: u16) -> bool {
|
|
2012 |
+ |
let phys = self.phys_row(r) as usize;
|
|
2013 |
+ |
let flags = if self.on_alt {
|
|
2014 |
+ |
&self.alt_wrapped
|
|
2015 |
+ |
} else {
|
|
2016 |
+ |
&self.main_wrapped
|
|
2017 |
+ |
};
|
|
2018 |
+ |
flags.get(phys).copied().unwrap_or(false)
|
|
2019 |
+ |
}
|
|
2020 |
+ |
|
|
2021 |
+ |
/// Copy one whole screen row onto another, contents and wrap flag both.
|
|
2022 |
+ |
///
|
|
2023 |
+ |
/// Goes through `row_start` per row rather than moving a span: logical
|
|
2024 |
+ |
/// rows are a ring, so two rows adjacent on screen need not be adjacent in
|
|
2025 |
+ |
/// memory, and a bulk move would shuffle the ring instead of the screen.
|
|
2026 |
+ |
fn copy_row(&mut self, src: u16, dst: u16) {
|
|
2027 |
+ |
if src == dst {
|
|
2028 |
+ |
return;
|
|
2029 |
+ |
}
|
|
2030 |
+ |
let wrapped = self.screen_row_wrapped(src);
|
|
2031 |
+ |
let (s, d) = (self.row_start(src), self.row_start(dst));
|
|
2032 |
+ |
let cols = self.cols as usize;
|
|
2033 |
+ |
self.active_cells_mut().copy_within(s..s + cols, d);
|
|
2034 |
+ |
self.set_row_wrapped(dst, wrapped);
|
|
2035 |
+ |
}
|
|
2036 |
+ |
|
|
2037 |
+ |
fn blank_screen_row(&mut self, r: u16) {
|
|
2038 |
+ |
self.erase_line_range(r, 0, self.cols);
|
|
2039 |
+ |
self.set_row_wrapped(r, false);
|
|
2040 |
+ |
}
|
|
2041 |
+ |
|
|
2042 |
+ |
/// Blank any half of a wide pair whose other half is gone.
|
|
2043 |
+ |
///
|
|
2044 |
+ |
/// The column movers shift a run of cells sideways, and a shift can cut a
|
|
2045 |
+ |
/// pair in two: the lead of a wide character can be pushed off the right
|
|
2046 |
+ |
/// edge, or a spacer can be pulled away from its lead. Either half left
|
|
2047 |
+ |
/// alone is a cell lying about what it holds — the same reasoning
|
|
2048 |
+ |
/// `erase_line_range` applies at its ends, applied to the whole row
|
|
2049 |
+ |
/// because a shift can break a pair anywhere along it.
|
|
2050 |
+ |
fn heal_wide_pairs(&mut self, row: u16) {
|
|
2051 |
+ |
let start = self.row_start(row);
|
|
2052 |
+ |
let cols = self.cols as usize;
|
|
2053 |
+ |
let cells = &mut self.active_cells_mut()[start..start + cols];
|
|
2054 |
+ |
for i in 0..cols {
|
|
2055 |
+ |
let orphan_lead = cells[i].is_wide() && !cells.get(i + 1).is_some_and(Cell::is_spacer);
|
|
2056 |
+ |
let orphan_spacer = cells[i].is_spacer() && !(i > 0 && cells[i - 1].is_wide());
|
|
2057 |
+ |
if orphan_lead || orphan_spacer {
|
|
2058 |
+ |
cells[i] = Cell::default();
|
|
2059 |
+ |
}
|
|
2060 |
+ |
}
|
|
2061 |
+ |
}
|
|
2062 |
+ |
|
|
2063 |
+ |
/// IL. Open `n` blank lines at the cursor, pushing what follows down and
|
|
2064 |
+ |
/// off the bottom of the scrolling region.
|
|
2065 |
+ |
///
|
|
2066 |
+ |
/// Ignored when the cursor sits outside the region: the region is the part
|
|
2067 |
+ |
/// of the screen the program has claimed, and an insert from outside it
|
|
2068 |
+ |
/// would move rows it does not own.
|
|
2069 |
+ |
fn insert_lines(&mut self, n: u16) {
|
|
2070 |
+ |
if self.cursor.row < self.scroll_top || self.cursor.row > self.scroll_bottom {
|
|
2071 |
+ |
return;
|
|
2072 |
+ |
}
|
|
2073 |
+ |
let top = self.cursor.row;
|
|
2074 |
+ |
let n = n.min(self.scroll_bottom - top + 1);
|
|
2075 |
+ |
if n == 0 {
|
|
2076 |
+ |
return;
|
|
2077 |
+ |
}
|
|
2078 |
+ |
// Downward, so a row is read before the copy that overwrites it.
|
|
2079 |
+ |
for r in (top + n..=self.scroll_bottom).rev() {
|
|
2080 |
+ |
self.copy_row(r - n, r);
|
|
2081 |
+ |
}
|
|
2082 |
+ |
for r in top..top + n {
|
|
2083 |
+ |
self.blank_screen_row(r);
|
|
2084 |
+ |
}
|
|
2085 |
+ |
// The row above the opening no longer runs into what is now a blank.
|
|
2086 |
+ |
if top > 0 {
|
|
2087 |
+ |
self.set_row_wrapped(top - 1, false);
|
|
2088 |
+ |
}
|
|
2089 |
+ |
for r in top..=self.scroll_bottom {
|
|
2090 |
+ |
self.mark_row_dirty(r);
|
|
2091 |
+ |
}
|
|
2092 |
+ |
// DEC puts the cursor at the left margin, and enough programs rely on
|
|
2093 |
+ |
// it that leaving the column alone is the surprising choice.
|
|
2094 |
+ |
self.cursor.col = 0;
|
|
2095 |
+ |
self.cursor.wrap_next = false;
|
|
2096 |
+ |
}
|
|
2097 |
+ |
|
|
2098 |
+ |
/// DL. Remove `n` lines at the cursor, pulling the rest of the scrolling
|
|
2099 |
+ |
/// region up and blanking what it vacates at the bottom.
|
|
2100 |
+ |
fn delete_lines(&mut self, n: u16) {
|
|
2101 |
+ |
if self.cursor.row < self.scroll_top || self.cursor.row > self.scroll_bottom {
|
|
2102 |
+ |
return;
|
|
2103 |
+ |
}
|
|
2104 |
+ |
let top = self.cursor.row;
|
|
2105 |
+ |
let n = n.min(self.scroll_bottom - top + 1);
|
|
2106 |
+ |
if n == 0 {
|
|
2107 |
+ |
return;
|
|
2108 |
+ |
}
|
|
2109 |
+ |
for r in top..=self.scroll_bottom - n {
|
|
2110 |
+ |
self.copy_row(r + n, r);
|
|
2111 |
+ |
}
|
|
2112 |
+ |
for r in self.scroll_bottom + 1 - n..=self.scroll_bottom {
|
|
2113 |
+ |
self.blank_screen_row(r);
|
|
2114 |
+ |
}
|
|
2115 |
+ |
if top > 0 {
|
|
2116 |
+ |
self.set_row_wrapped(top - 1, false);
|
|
2117 |
+ |
}
|
|
2118 |
+ |
for r in top..=self.scroll_bottom {
|
|
2119 |
+ |
self.mark_row_dirty(r);
|
|
2120 |
+ |
}
|
|
2121 |
+ |
self.cursor.col = 0;
|
|
2122 |
+ |
self.cursor.wrap_next = false;
|
|
2123 |
+ |
}
|
|
2124 |
+ |
|
|
2125 |
+ |
/// ICH. Open `n` blank cells at the cursor, pushing the rest of the line
|
|
2126 |
+ |
/// right and off the edge. The cursor does not move.
|
|
2127 |
+ |
fn insert_chars(&mut self, n: u16) {
|
|
2128 |
+ |
let row = self.cursor.row;
|
|
2129 |
+ |
let col = self.cursor.col;
|
|
2130 |
+ |
let cols = self.cols;
|
|
2131 |
+ |
let n = n.min(cols - col);
|
|
2132 |
+ |
if n == 0 {
|
|
2133 |
+ |
return;
|
|
2134 |
+ |
}
|
|
2135 |
+ |
let start = self.row_start(row);
|
|
2136 |
+ |
let (c, k, w) = (col as usize, n as usize, cols as usize);
|
|
2137 |
+ |
let cells = &mut self.active_cells_mut()[start..start + w];
|
|
2138 |
+ |
cells.copy_within(c..w - k, c + k);
|
|
2139 |
+ |
for cell in &mut cells[c..c + k] {
|
|
2140 |
+ |
*cell = Cell::default();
|
|
2141 |
+ |
}
|
|
2142 |
+ |
self.heal_wide_pairs(row);
|
|
2143 |
+ |
// Whatever ran off the right edge is gone, so the line stops here.
|
|
2144 |
+ |
self.set_row_wrapped(row, false);
|
|
2145 |
+ |
self.mark_row_dirty(row);
|
|
2146 |
+ |
}
|
|
2147 |
+ |
|
|
2148 |
+ |
/// DCH. Remove `n` cells at the cursor, pulling the rest of the line left
|
|
2149 |
+ |
/// and blanking the tail it vacates.
|
|
2150 |
+ |
fn delete_chars(&mut self, n: u16) {
|
|
2151 |
+ |
let row = self.cursor.row;
|
|
2152 |
+ |
let col = self.cursor.col;
|
|
2153 |
+ |
let cols = self.cols;
|
|
2154 |
+ |
let n = n.min(cols - col);
|
|
2155 |
+ |
if n == 0 {
|
|
2156 |
+ |
return;
|
|
2157 |
+ |
}
|
|
2158 |
+ |
let start = self.row_start(row);
|
|
2159 |
+ |
let (c, k, w) = (col as usize, n as usize, cols as usize);
|
|
2160 |
+ |
let cells = &mut self.active_cells_mut()[start..start + w];
|
|
2161 |
+ |
cells.copy_within(c + k..w, c);
|
|
2162 |
+ |
for cell in &mut cells[w - k..] {
|
|
2163 |
+ |
*cell = Cell::default();
|
|
2164 |
+ |
}
|
|
2165 |
+ |
self.heal_wide_pairs(row);
|
|
2166 |
+ |
self.set_row_wrapped(row, false);
|
|
2167 |
+ |
self.mark_row_dirty(row);
|
|
2168 |
+ |
}
|
|
2169 |
+ |
|
|
2170 |
+ |
/// ECH. Blank `n` cells from the cursor without moving anything. The
|
|
2171 |
+ |
/// difference from DCH is the whole point: the tail of the line stays
|
|
2172 |
+ |
/// where it is.
|
|
2173 |
+ |
fn erase_chars(&mut self, n: u16) {
|
|
2174 |
+ |
let row = self.cursor.row;
|
|
2175 |
+ |
let col = self.cursor.col;
|
|
2176 |
+ |
let end = col.saturating_add(n).min(self.cols);
|
|
2177 |
+ |
self.erase_line_range(row, col, end);
|
|
2178 |
+ |
self.mark_row_dirty(row);
|
|
2179 |
+ |
}
|
|
2180 |
+ |
|
| 1772 |
2181 |
|
fn erase_line(&mut self, mode: u16) {
|
| 1773 |
2182 |
|
let row = self.cursor.row;
|
| 1774 |
2183 |
|
let col = self.cursor.col;
|
| 1848 |
2257 |
|
// they were reading before vim opened.
|
| 1849 |
2258 |
|
self.view_offset = 0;
|
| 1850 |
2259 |
|
if to_alt {
|
| 1851 |
|
- |
self.saved_main_cursor = self.cursor;
|
|
2260 |
+ |
self.swap_saved[0] = self.cursor;
|
| 1852 |
2261 |
|
self.on_alt = true;
|
| 1853 |
2262 |
|
for cell in &mut self.alt {
|
| 1854 |
2263 |
|
*cell = Cell::default();
|
| 1855 |
2264 |
|
}
|
| 1856 |
2265 |
|
self.alt_wrapped.fill(false);
|
| 1857 |
|
- |
self.cursor = self.saved_alt_cursor;
|
|
2266 |
+ |
self.cursor = self.swap_saved[1];
|
| 1858 |
2267 |
|
} else {
|
| 1859 |
|
- |
self.saved_alt_cursor = self.cursor;
|
|
2268 |
+ |
self.swap_saved[1] = self.cursor;
|
| 1860 |
2269 |
|
self.on_alt = false;
|
| 1861 |
|
- |
self.cursor = self.saved_main_cursor;
|
|
2270 |
+ |
self.cursor = self.swap_saved[0];
|
| 1862 |
2271 |
|
}
|
| 1863 |
2272 |
|
self.pending_screen_swap = true;
|
| 1864 |
2273 |
|
self.mark_all_rows_dirty();
|
| 2088 |
2497 |
|
('K', false) => {
|
| 2089 |
2498 |
|
self.erase_line(param1(params, 0));
|
| 2090 |
2499 |
|
}
|
|
2500 |
+ |
('L', false) => self.insert_lines(param1(params, 1)),
|
|
2501 |
+ |
('M', false) => self.delete_lines(param1(params, 1)),
|
|
2502 |
+ |
('@', false) => self.insert_chars(param1(params, 1)),
|
|
2503 |
+ |
('P', false) => self.delete_chars(param1(params, 1)),
|
|
2504 |
+ |
('X', false) => self.erase_chars(param1(params, 1)),
|
|
2505 |
+ |
// DECSC/DECRC in their CSI spelling, the same pair as `ESC 7` and
|
|
2506 |
+ |
// `ESC 8`. `CSI s` is DECSLRM under DECLRMM, which shop does not
|
|
2507 |
+ |
// implement and no program can have turned on, so there is nothing
|
|
2508 |
+ |
// for it to be mistaken for here.
|
|
2509 |
+ |
('s', false) if intermediates.is_empty() => self.save_cursor(),
|
|
2510 |
+ |
('u', false) if intermediates.is_empty() => self.restore_cursor(),
|
| 2091 |
2511 |
|
// DA1, "what are you". Guarded on empty intermediates because
|
| 2092 |
2512 |
|
// `CSI > c` is DA2, a different question, and the private-flag
|
| 2093 |
2513 |
|
// check above only screens for `?`.
|
| 2097 |
2517 |
|
// sixel, and claiming it means a client picks sixel over kitty
|
| 2098 |
2518 |
|
// graphics and draws nothing.
|
| 2099 |
2519 |
|
('c', false) if intermediates.is_empty() => self.reply(b"\x1b[?62;22c"),
|