ui/embed: fix scrolling area issues
parent
f1588f6002
commit
0cea6368d9
|
@ -268,6 +268,14 @@ fn run_app() -> Result<()> {
|
||||||
state.render();
|
state.render();
|
||||||
state.redraw();
|
state.redraw();
|
||||||
},
|
},
|
||||||
|
ThreadEvent::InputRaw(raw_input @ (Key::Ctrl('l'), _)) => {
|
||||||
|
/* Manual screen redraw */
|
||||||
|
state.update_size();
|
||||||
|
state.render();
|
||||||
|
state.redraw();
|
||||||
|
state.rcv_event(UIEvent::EmbedInput(raw_input));
|
||||||
|
state.redraw();
|
||||||
|
},
|
||||||
ThreadEvent::Input(k) => {
|
ThreadEvent::Input(k) => {
|
||||||
match state.mode {
|
match state.mode {
|
||||||
UIMode::Normal => {
|
UIMode::Normal => {
|
||||||
|
|
|
@ -687,15 +687,6 @@ impl Component for Composer {
|
||||||
match *event {
|
match *event {
|
||||||
UIEvent::Resize => {
|
UIEvent::Resize => {
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
if let Some(ref mut embed_pty) = self.embed {
|
|
||||||
match embed_pty {
|
|
||||||
EmbedStatus::Running(_, _) => {
|
|
||||||
let mut guard = embed_pty.lock().unwrap();
|
|
||||||
guard.grid.clear(Cell::default());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
/* Switch e-mail From: field to the `left` configured account. */
|
/* Switch e-mail From: field to the `left` configured account. */
|
||||||
|
@ -873,7 +864,8 @@ impl Component for Composer {
|
||||||
if settings.composing.embed {
|
if settings.composing.embed {
|
||||||
self.embed = Some(EmbedStatus::Running(
|
self.embed = Some(EmbedStatus::Running(
|
||||||
crate::terminal::embed::create_pty(
|
crate::terminal::embed::create_pty(
|
||||||
self.embed_area,
|
width!(self.embed_area),
|
||||||
|
height!(self.embed_area),
|
||||||
[editor, f.path().display().to_string()].join(" "),
|
[editor, f.path().display().to_string()].join(" "),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -478,6 +478,10 @@ impl Cell {
|
||||||
pub fn empty(&self) -> bool {
|
pub fn empty(&self) -> bool {
|
||||||
self.empty
|
self.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_empty(&mut self, new_val: bool) {
|
||||||
|
self.empty = new_val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cell {
|
impl Default for Cell {
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
use crate::split_command;
|
use crate::split_command;
|
||||||
use crate::terminal::position::Area;
|
|
||||||
use crate::terminal::position::*;
|
use crate::terminal::position::*;
|
||||||
use melib::log;
|
use melib::log;
|
||||||
use melib::ERROR;
|
use melib::ERROR;
|
||||||
|
|
||||||
use nix::fcntl::{open, OFlag};
|
use nix::fcntl::{open, OFlag};
|
||||||
use nix::ioctl_write_ptr_bad;
|
|
||||||
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||||
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
|
use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
|
||||||
use nix::sys::{stat, wait::waitpid};
|
use nix::sys::{stat, wait::waitpid};
|
||||||
use nix::unistd::{dup2, fork, ForkResult};
|
use nix::unistd::{dup2, fork, ForkResult};
|
||||||
|
use nix::{ioctl_none_bad, ioctl_write_ptr_bad};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
|
@ -17,7 +16,9 @@ mod grid;
|
||||||
|
|
||||||
pub use grid::EmbedGrid;
|
pub use grid::EmbedGrid;
|
||||||
|
|
||||||
// ioctl command to set window size of pty:
|
// ioctl request code to "Make the given terminal the controlling terminal of the calling process"
|
||||||
|
use libc::TIOCSCTTY;
|
||||||
|
// ioctl request code to set window size of pty:
|
||||||
use libc::TIOCSWINSZ;
|
use libc::TIOCSWINSZ;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
@ -29,7 +30,13 @@ use std::sync::{Arc, Mutex};
|
||||||
// Macro generated function that calls ioctl to set window size of slave pty end
|
// Macro generated function that calls ioctl to set window size of slave pty end
|
||||||
ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
|
ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
|
||||||
|
|
||||||
pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
|
ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY);
|
||||||
|
|
||||||
|
pub fn create_pty(
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
command: String,
|
||||||
|
) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
|
||||||
// Open a new PTY master
|
// Open a new PTY master
|
||||||
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
||||||
|
|
||||||
|
@ -44,8 +51,8 @@ pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGri
|
||||||
//let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
|
//let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?;
|
||||||
{
|
{
|
||||||
let winsize = Winsize {
|
let winsize = Winsize {
|
||||||
ws_row: <u16>::try_from(height!(area)).unwrap(),
|
ws_row: <u16>::try_from(height).unwrap(),
|
||||||
ws_col: <u16>::try_from(width!(area)).unwrap(),
|
ws_col: <u16>::try_from(width).unwrap(),
|
||||||
ws_xpixel: 0,
|
ws_xpixel: 0,
|
||||||
ws_ypixel: 0,
|
ws_ypixel: 0,
|
||||||
};
|
};
|
||||||
|
@ -65,6 +72,19 @@ pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGri
|
||||||
dup2(slave_fd, STDIN_FILENO).unwrap();
|
dup2(slave_fd, STDIN_FILENO).unwrap();
|
||||||
dup2(slave_fd, STDOUT_FILENO).unwrap();
|
dup2(slave_fd, STDOUT_FILENO).unwrap();
|
||||||
dup2(slave_fd, STDERR_FILENO).unwrap();
|
dup2(slave_fd, STDERR_FILENO).unwrap();
|
||||||
|
/* Become session leader */
|
||||||
|
nix::unistd::setsid().unwrap();
|
||||||
|
match unsafe { set_controlling_terminal(slave_fd) } {
|
||||||
|
Ok(c) if c < 0 => {
|
||||||
|
log(format!("Could not execute `{}`: ioctl(fd, TIOCSCTTY, NULL) returned {}", command, c,), ERROR);
|
||||||
|
std::process::exit(c);
|
||||||
|
}
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log(format!("Could not execute `{}`: ioctl(fd, TIOCSCTTY, NULL) returned {}", command, err,), ERROR);
|
||||||
|
std::process::exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
let parts = split_command!(command);
|
let parts = split_command!(command);
|
||||||
let (cmd, _) = (parts[0], &parts[1..]);
|
let (cmd, _) = (parts[0], &parts[1..]);
|
||||||
if let Err(e) = nix::unistd::execv(
|
if let Err(e) = nix::unistd::execv(
|
||||||
|
@ -78,7 +98,7 @@ pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGri
|
||||||
std::process::exit(-1);
|
std::process::exit(-1);
|
||||||
}
|
}
|
||||||
/* This path shouldn't be executed. */
|
/* This path shouldn't be executed. */
|
||||||
std::process::exit(0);
|
std::process::exit(-1);
|
||||||
}
|
}
|
||||||
Ok(ForkResult::Parent { child }) => child,
|
Ok(ForkResult::Parent { child }) => child,
|
||||||
Err(e) => panic!(e),
|
Err(e) => panic!(e),
|
||||||
|
@ -92,7 +112,7 @@ pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGri
|
||||||
|
|
||||||
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
||||||
let mut embed_grid = EmbedGrid::new(stdin, child_pid);
|
let mut embed_grid = EmbedGrid::new(stdin, child_pid);
|
||||||
embed_grid.set_terminal_size((width!(area), height!(area)));
|
embed_grid.set_terminal_size((width, height));
|
||||||
let grid = Arc::new(Mutex::new(embed_grid));
|
let grid = Arc::new(Mutex::new(embed_grid));
|
||||||
let grid_ = grid.clone();
|
let grid_ = grid.clone();
|
||||||
|
|
||||||
|
@ -110,7 +130,8 @@ fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc<Mutex<Emb
|
||||||
let mut bytes_iter = pty_fd.bytes();
|
let mut bytes_iter = pty_fd.bytes();
|
||||||
debug!("waiting for bytes");
|
debug!("waiting for bytes");
|
||||||
while let Some(Ok(byte)) = bytes_iter.next() {
|
while let Some(Ok(byte)) = bytes_iter.next() {
|
||||||
debug!("got byte {}", byte as char);
|
debug!("got a byte? {:?}", byte as char);
|
||||||
|
/* Drink deep, and descend. */
|
||||||
grid.lock().unwrap().process_byte(byte);
|
grid.lock().unwrap().process_byte(byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +188,9 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
unsafestr!(buf2),
|
unsafestr!(buf2),
|
||||||
*c as char
|
*c as char
|
||||||
),
|
),
|
||||||
|
EscCode(ExpectingControlChar, b'D') => write!(
|
||||||
|
f, "ESC D Linefeed"
|
||||||
|
),
|
||||||
EscCode(Csi, b'm') => write!(
|
EscCode(Csi, b'm') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[m\t\tCSI Character Attributes | Set Attr and Color to Normal (default)"
|
"ESC[m\t\tCSI Character Attributes | Set Attr and Color to Normal (default)"
|
||||||
|
@ -175,12 +199,26 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
f,
|
f,
|
||||||
"ESC[K\t\tCSI Erase from the cursor to the end of the line"
|
"ESC[K\t\tCSI Erase from the cursor to the end of the line"
|
||||||
),
|
),
|
||||||
|
EscCode(Csi, b'L') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[L\t\tCSI Insert one blank line"
|
||||||
|
),
|
||||||
|
EscCode(Csi, b'M') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[M\t\tCSI delete line"
|
||||||
|
),
|
||||||
EscCode(Csi, b'J') => write!(
|
EscCode(Csi, b'J') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[J\t\tCSI Erase from the cursor to the end of the screen"
|
"ESC[J\t\tCSI Erase from the cursor to the end of the screen"
|
||||||
),
|
),
|
||||||
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position."),
|
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position."),
|
||||||
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
|
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
|
||||||
|
EscCode(Csi1(ref buf), b'L') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{}L\t\tCSI Insert {} blank lines",
|
||||||
|
unsafestr!(buf),
|
||||||
|
unsafestr!(buf)
|
||||||
|
),
|
||||||
EscCode(Csi1(ref buf), b'm') => write!(
|
EscCode(Csi1(ref buf), b'm') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[{}m\t\tCSI Character Attributes | Set fg, bg color",
|
"ESC[{}m\t\tCSI Character Attributes | Set fg, bg color",
|
||||||
|
@ -230,7 +268,11 @@ impl std::fmt::Display for EscCode<'_> {
|
||||||
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
|
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
|
||||||
buf = unsafestr!(buf)
|
buf = unsafestr!(buf)
|
||||||
),
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'M') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}M\t\tDelete P s Lines(s) (default = 1) (DCH). ",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
EscCode(Csi1(ref buf), b'P') => write!(
|
EscCode(Csi1(ref buf), b'P') => write!(
|
||||||
f,
|
f,
|
||||||
"ESC[{buf}P\t\tDelete P s Character(s) (default = 1) (DCH). ",
|
"ESC[{buf}P\t\tDelete P s Character(s) (default = 1) (DCH). ",
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::terminal::cells::*;
|
||||||
use melib::error::{MeliError, Result};
|
use melib::error::{MeliError, Result};
|
||||||
use nix::sys::wait::WaitStatus;
|
use nix::sys::wait::WaitStatus;
|
||||||
use nix::sys::wait::{waitpid, WaitPidFlag};
|
use nix::sys::wait::{waitpid, WaitPidFlag};
|
||||||
|
use text_processing::wcwidth;
|
||||||
/**
|
/**
|
||||||
* `EmbedGrid` manages the terminal grid state of the embed process.
|
* `EmbedGrid` manages the terminal grid state of the embed process.
|
||||||
*
|
*
|
||||||
|
@ -41,6 +42,11 @@ pub struct EmbedGrid {
|
||||||
prev_bg_color: Option<Color>,
|
prev_bg_color: Option<Color>,
|
||||||
|
|
||||||
show_cursor: bool,
|
show_cursor: bool,
|
||||||
|
origin_mode: bool,
|
||||||
|
auto_wrap_mode: bool,
|
||||||
|
/// If next grapheme should be placed in the next line
|
||||||
|
/// This should be reset whenever the cursor value changes
|
||||||
|
wrap_next: bool,
|
||||||
/// Store state in case a multi-byte character is encountered
|
/// Store state in case a multi-byte character is encountered
|
||||||
codepoints: CodepointBuf,
|
codepoints: CodepointBuf,
|
||||||
}
|
}
|
||||||
|
@ -68,6 +74,9 @@ impl EmbedGrid {
|
||||||
prev_fg_color: None,
|
prev_fg_color: None,
|
||||||
prev_bg_color: None,
|
prev_bg_color: None,
|
||||||
show_cursor: true,
|
show_cursor: true,
|
||||||
|
auto_wrap_mode: true,
|
||||||
|
wrap_next: false,
|
||||||
|
origin_mode: false,
|
||||||
codepoints: CodepointBuf::None,
|
codepoints: CodepointBuf::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,13 +86,14 @@ impl EmbedGrid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug!("resizing to {:?}", new_val);
|
debug!("resizing to {:?}", new_val);
|
||||||
if self.scroll_region.top == 0 && self.scroll_region.bottom == self.terminal_size.1 {
|
self.scroll_region.top = 0;
|
||||||
self.scroll_region.bottom = new_val.1;
|
self.scroll_region.bottom = new_val.1.saturating_sub(1);
|
||||||
}
|
|
||||||
self.terminal_size = new_val;
|
self.terminal_size = new_val;
|
||||||
self.grid.resize(new_val.0, new_val.1, Cell::default());
|
self.grid.resize(new_val.0, new_val.1, Cell::default());
|
||||||
self.grid.clear(Cell::default());
|
self.grid.clear(Cell::default());
|
||||||
self.cursor = (0, 0);
|
self.cursor = (0, 0);
|
||||||
|
self.wrap_next = false;
|
||||||
let winsize = Winsize {
|
let winsize = Winsize {
|
||||||
ws_row: <u16>::try_from(new_val.1).unwrap(),
|
ws_row: <u16>::try_from(new_val.1).unwrap(),
|
||||||
ws_col: <u16>::try_from(new_val.0).unwrap(),
|
ws_col: <u16>::try_from(new_val.0).unwrap(),
|
||||||
|
@ -124,6 +134,9 @@ impl EmbedGrid {
|
||||||
ref mut prev_bg_color,
|
ref mut prev_bg_color,
|
||||||
ref mut codepoints,
|
ref mut codepoints,
|
||||||
ref mut show_cursor,
|
ref mut show_cursor,
|
||||||
|
ref mut auto_wrap_mode,
|
||||||
|
ref mut wrap_next,
|
||||||
|
ref mut origin_mode,
|
||||||
child_pid: _,
|
child_pid: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -131,6 +144,8 @@ impl EmbedGrid {
|
||||||
() => {
|
() => {
|
||||||
if cursor.0 + 1 < terminal_size.0 {
|
if cursor.0 + 1 < terminal_size.0 {
|
||||||
cursor.0 += 1;
|
cursor.0 += 1;
|
||||||
|
} else if *auto_wrap_mode {
|
||||||
|
*wrap_next = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -172,6 +187,17 @@ impl EmbedGrid {
|
||||||
(b'(', State::ExpectingControlChar) => {
|
(b'(', State::ExpectingControlChar) => {
|
||||||
*state = State::G0;
|
*state = State::G0;
|
||||||
}
|
}
|
||||||
|
(b'D', State::ExpectingControlChar) => {
|
||||||
|
// ESCD Linefeed
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
if cursor.1 == scroll_region.bottom {
|
||||||
|
scroll_up(grid, scroll_region, scroll_region.top, 1);
|
||||||
|
} else {
|
||||||
|
cursor.1 += 1;
|
||||||
|
}
|
||||||
|
*wrap_next = false;
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
(b'J', State::ExpectingControlChar) => {
|
(b'J', State::ExpectingControlChar) => {
|
||||||
// ESCJ Erase from the cursor to the end of the screen
|
// ESCJ Erase from the cursor to the end of the screen
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)));
|
debug!("sending {}", EscCode::from((&(*state), byte)));
|
||||||
|
@ -214,26 +240,25 @@ impl EmbedGrid {
|
||||||
(c, State::Osc2(_, ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => {
|
(c, State::Osc2(_, ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(_, State::Osc1(_)) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(_, State::Osc2(_, _)) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
/* Normal */
|
/* Normal */
|
||||||
(b'\r', State::Normal) => {
|
(b'\r', State::Normal) => {
|
||||||
debug!("carriage return x-> 0, cursor was: {:?}", cursor);
|
debug!("carriage return x-> 0, cursor was: {:?}", cursor);
|
||||||
cursor.0 = 0;
|
cursor.0 = 0;
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
}
|
}
|
||||||
(b'\n', State::Normal) => {
|
(b'\n', State::Normal) => {
|
||||||
//debug!("setting cell {:?} char '{}'", cursor, c as char);
|
//debug!("setting cell {:?} char '{}'", cursor, c as char);
|
||||||
debug!("newline y-> y+1, cursor was: {:?}", cursor);
|
debug!("newline y-> y+1, cursor was: {:?}", cursor);
|
||||||
|
|
||||||
if cursor.1 + 1 < terminal_size.1 {
|
if cursor.1 + 1 < terminal_size.1 {
|
||||||
|
if cursor.1 == scroll_region.bottom {
|
||||||
|
scroll_up(grid, scroll_region, cursor.1, 1);
|
||||||
|
} else {
|
||||||
cursor.1 += 1;
|
cursor.1 += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
}
|
}
|
||||||
(b'', State::Normal) => {
|
(b'', State::Normal) => {
|
||||||
|
@ -249,9 +274,9 @@ impl EmbedGrid {
|
||||||
}
|
}
|
||||||
(c, State::Normal) => {
|
(c, State::Normal) => {
|
||||||
/* Character to be printed. */
|
/* Character to be printed. */
|
||||||
if *codepoints == CodepointBuf::None && c & 0x80 == 0 {
|
let c = if *codepoints == CodepointBuf::None && c & 0x80 == 0 {
|
||||||
/* This is a one byte char */
|
/* This is a one byte char */
|
||||||
grid[cursor_val!()].set_ch(c as char);
|
c as char
|
||||||
} else {
|
} else {
|
||||||
match codepoints {
|
match codepoints {
|
||||||
CodepointBuf::None if c & 0b1110_0000 == 0b1100_0000 => {
|
CodepointBuf::None if c & 0b1110_0000 == 0b1100_0000 => {
|
||||||
|
@ -267,37 +292,29 @@ impl EmbedGrid {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CodepointBuf::TwoCodepoints(buf) => {
|
CodepointBuf::TwoCodepoints(buf) => {
|
||||||
grid[cursor_val!()].set_ch(
|
debug!("two byte char = ");
|
||||||
unsafe { std::str::from_utf8_unchecked(&[buf[0], c]) }
|
unsafe { std::str::from_utf8_unchecked(&[buf[0], c]) }
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.unwrap(),
|
.unwrap()
|
||||||
);
|
|
||||||
*codepoints = CodepointBuf::None;
|
|
||||||
}
|
}
|
||||||
CodepointBuf::ThreeCodepoints(buf) if buf.len() == 2 => {
|
CodepointBuf::ThreeCodepoints(buf) if buf.len() == 2 => {
|
||||||
grid[cursor_val!()].set_ch(
|
debug!("three byte char = ",);
|
||||||
unsafe { std::str::from_utf8_unchecked(&[buf[0], buf[1], c]) }
|
unsafe { std::str::from_utf8_unchecked(&[buf[0], buf[1], c]) }
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.unwrap(),
|
.unwrap()
|
||||||
);
|
|
||||||
*codepoints = CodepointBuf::None;
|
|
||||||
}
|
}
|
||||||
CodepointBuf::ThreeCodepoints(buf) => {
|
CodepointBuf::ThreeCodepoints(buf) => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CodepointBuf::FourCodepoints(buf) if buf.len() == 3 => {
|
CodepointBuf::FourCodepoints(buf) if buf.len() == 3 => {
|
||||||
grid[cursor_val!()].set_ch(
|
debug!("four byte char = ",);
|
||||||
unsafe {
|
unsafe { std::str::from_utf8_unchecked(&[buf[0], buf[1], buf[2], c]) }
|
||||||
std::str::from_utf8_unchecked(&[buf[0], buf[1], buf[2], c])
|
|
||||||
}
|
|
||||||
.chars()
|
.chars()
|
||||||
.next()
|
.next()
|
||||||
.unwrap(),
|
.unwrap()
|
||||||
);
|
|
||||||
*codepoints = CodepointBuf::None;
|
|
||||||
}
|
}
|
||||||
CodepointBuf::FourCodepoints(buf) => {
|
CodepointBuf::FourCodepoints(buf) => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
|
@ -309,12 +326,45 @@ impl EmbedGrid {
|
||||||
codepoints, c
|
codepoints, c
|
||||||
);
|
);
|
||||||
*codepoints = CodepointBuf::None;
|
*codepoints = CodepointBuf::None;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
debug!("c = {:?}\tcursor={:?}", c, cursor);
|
||||||
|
*codepoints = CodepointBuf::None;
|
||||||
|
if *auto_wrap_mode && *wrap_next {
|
||||||
|
*wrap_next = false;
|
||||||
|
if cursor.1 == scroll_region.bottom {
|
||||||
|
scroll_up(grid, scroll_region, scroll_region.top, 1);
|
||||||
|
} else {
|
||||||
|
cursor.1 += 1;
|
||||||
|
}
|
||||||
|
cursor.0 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c == '↪' {
|
||||||
|
debug!("↪ cursor is {:?}", cursor_val!());
|
||||||
|
}
|
||||||
|
grid[cursor_val!()].set_ch(c);
|
||||||
grid[cursor_val!()].set_fg(*fg_color);
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
grid[cursor_val!()].set_bg(*bg_color);
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
match wcwidth(u32::from(c)) {
|
||||||
|
Some(0) | None => {
|
||||||
|
/* Skip drawing zero width characters */
|
||||||
|
grid[cursor_val!()].set_empty(true);
|
||||||
|
}
|
||||||
|
Some(1) => {}
|
||||||
|
Some(n) => {
|
||||||
|
/* Grapheme takes more than one column, so the next cell will be
|
||||||
|
* drawn over. Set it as empty to skip drawing it. */
|
||||||
|
for _ in 1..n {
|
||||||
|
increase_cursor_x!();
|
||||||
|
grid[cursor_val!()].set_empty(true);
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
increase_cursor_x!();
|
increase_cursor_x!();
|
||||||
}
|
}
|
||||||
(b'u', State::Csi) => {
|
(b'u', State::Csi) => {
|
||||||
|
@ -332,24 +382,11 @@ impl EmbedGrid {
|
||||||
grid[cursor_val!()].set_bg(Color::Default);
|
grid[cursor_val!()].set_bg(Color::Default);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'H', State::Csi) => {
|
|
||||||
/* move cursor to (1,1) */
|
|
||||||
debug!("{}", EscCode::from((&(*state), byte)),);
|
|
||||||
debug!("move cursor to (1,1) cursor before: {:?}", *cursor);
|
|
||||||
*cursor = (0, 0);
|
|
||||||
debug!("cursor after: {:?}", *cursor);
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b'P', State::Csi) => {
|
|
||||||
/* delete one character */
|
|
||||||
debug!("{}", EscCode::from((&(*state), byte)),);
|
|
||||||
grid[cursor_val!()].set_ch(' ');
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b'C', State::Csi) => {
|
(b'C', State::Csi) => {
|
||||||
// ESC[C CSI Cursor Forward one Time
|
// ESC[C CSI Cursor Forward one Time
|
||||||
debug!("cursor forward one time, cursor was: {:?}", cursor);
|
debug!("cursor forward one time, cursor was: {:?}", cursor);
|
||||||
cursor.0 = std::cmp::min(cursor.0 + 1, terminal_size.0.saturating_sub(1));
|
cursor.0 = std::cmp::min(cursor.0 + 1, terminal_size.0.saturating_sub(1));
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -359,6 +396,12 @@ impl EmbedGrid {
|
||||||
}
|
}
|
||||||
(b'h', State::CsiQ(ref buf)) => {
|
(b'h', State::CsiQ(ref buf)) => {
|
||||||
match buf.as_slice() {
|
match buf.as_slice() {
|
||||||
|
b"6" => {
|
||||||
|
*origin_mode = true;
|
||||||
|
}
|
||||||
|
b"7" => {
|
||||||
|
*auto_wrap_mode = true;
|
||||||
|
}
|
||||||
b"25" => {
|
b"25" => {
|
||||||
*show_cursor = true;
|
*show_cursor = true;
|
||||||
*prev_fg_color = Some(grid[cursor_val!()].fg());
|
*prev_fg_color = Some(grid[cursor_val!()].fg());
|
||||||
|
@ -374,6 +417,12 @@ impl EmbedGrid {
|
||||||
}
|
}
|
||||||
(b'l', State::CsiQ(ref mut buf)) => {
|
(b'l', State::CsiQ(ref mut buf)) => {
|
||||||
match buf.as_slice() {
|
match buf.as_slice() {
|
||||||
|
b"6" => {
|
||||||
|
*origin_mode = false;
|
||||||
|
}
|
||||||
|
b"7" => {
|
||||||
|
*auto_wrap_mode = false;
|
||||||
|
}
|
||||||
b"25" => {
|
b"25" => {
|
||||||
*show_cursor = false;
|
*show_cursor = false;
|
||||||
if let Some(fg_color) = prev_fg_color.take() {
|
if let Some(fg_color) = prev_fg_color.take() {
|
||||||
|
@ -392,10 +441,6 @@ impl EmbedGrid {
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(_, State::CsiQ(_)) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
/* END OF CSI ? stuff */
|
/* END OF CSI ? stuff */
|
||||||
(c, State::Csi) if c >= b'0' && c <= b'9' => {
|
(c, State::Csi) if c >= b'0' && c <= b'9' => {
|
||||||
let mut buf1 = Vec::new();
|
let mut buf1 = Vec::new();
|
||||||
|
@ -429,16 +474,38 @@ impl EmbedGrid {
|
||||||
/* Erase to right (Default) */
|
/* Erase to right (Default) */
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
for x in cursor.0..terminal_size.0 {
|
for x in cursor.0..terminal_size.0 {
|
||||||
grid[(x, cursor.1 + scroll_region.top)] = Cell::default();
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
}
|
}
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'M', State::Csi) => {
|
(b'L', State::Csi) | (b'L', State::Csi1(_)) => {
|
||||||
/* Delete line */
|
/* Insert n blank lines (default 1) */
|
||||||
|
let n = if let State::Csi1(ref buf1) = state {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(buf1) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
scroll_down(grid, scroll_region, cursor.1, n);
|
||||||
|
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
for x in 0..terminal_size.0 {
|
*state = State::Normal;
|
||||||
grid[(x, cursor.1 + scroll_region.top)] = Cell::default();
|
|
||||||
}
|
}
|
||||||
|
(b'M', State::Csi) | (b'M', State::Csi1(_)) => {
|
||||||
|
/* Delete n lines (default 1) */
|
||||||
|
let n = if let State::Csi1(ref buf1) = state {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(buf1) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
scroll_up(grid, scroll_region, cursor.1, n);
|
||||||
|
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'A', State::Csi) => {
|
(b'A', State::Csi) => {
|
||||||
|
@ -449,34 +516,24 @@ impl EmbedGrid {
|
||||||
} else {
|
} else {
|
||||||
debug!("cursor.1 == 0");
|
debug!("cursor.1 == 0");
|
||||||
}
|
}
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'r', State::Csi) => {
|
|
||||||
// Set scrolling region to default (size of window)
|
|
||||||
scroll_region.top = 0;
|
|
||||||
scroll_region.bottom = terminal_size.1.saturating_sub(1);
|
|
||||||
*cursor = (0, 0);
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(_, State::Csi) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b'K', State::Csi1(buf)) if buf == b"0" => {
|
(b'K', State::Csi1(buf)) if buf == b"0" => {
|
||||||
/* Erase in Line (ED), VT100.*/
|
/* Erase in Line (ED), VT100.*/
|
||||||
/* Erase to right (Default) */
|
/* Erase to right (Default) */
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
for x in cursor.0..terminal_size.0 {
|
for x in cursor.0..terminal_size.0 {
|
||||||
grid[(x, cursor.1 + scroll_region.top)] = Cell::default();
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
}
|
}
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'K', State::Csi1(buf)) if buf == b"1" => {
|
(b'K', State::Csi1(buf)) if buf == b"1" => {
|
||||||
/* Erase in Line (ED), VT100.*/
|
/* Erase in Line (ED), VT100.*/
|
||||||
/* Erase to left (Default) */
|
/* Erase to left (Default) */
|
||||||
for x in cursor.0..=0 {
|
for x in 0..=cursor.0 {
|
||||||
grid[(x, cursor.1 + scroll_region.top)] = Cell::default();
|
grid[(x, cursor.1)] = Cell::default();
|
||||||
}
|
}
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
|
@ -538,12 +595,6 @@ impl EmbedGrid {
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'J', State::Csi1(ref buf)) if buf == b"3" => {
|
|
||||||
/* Erase in Display (ED), VT100.*/
|
|
||||||
/* Erase saved lines (What?) */
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b'X', State::Csi1(ref buf)) => {
|
(b'X', State::Csi1(ref buf)) => {
|
||||||
/* Erase Ps Character(s) (default = 1) (ECH)..*/
|
/* Erase Ps Character(s) (default = 1) (ECH)..*/
|
||||||
let ps = unsafe { std::str::from_utf8_unchecked(buf) }
|
let ps = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
@ -569,7 +620,7 @@ impl EmbedGrid {
|
||||||
}
|
}
|
||||||
(b't', State::Csi1(buf)) => {
|
(b't', State::Csi1(buf)) => {
|
||||||
/* Window manipulation */
|
/* Window manipulation */
|
||||||
if buf == b"18" {
|
if buf == b"18" || buf == b"19" {
|
||||||
// Ps = 18 → Report the size of the text area in characters as CSI 8 ; height ; width t
|
// Ps = 18 → Report the size of the text area in characters as CSI 8 ; height ; width t
|
||||||
debug!("report size of the text area");
|
debug!("report size of the text area");
|
||||||
debug!("got {}", EscCode::from((&(*state), byte)));
|
debug!("got {}", EscCode::from((&(*state), byte)));
|
||||||
|
@ -582,6 +633,7 @@ impl EmbedGrid {
|
||||||
.write_all((terminal_size.0).to_string().as_bytes())
|
.write_all((terminal_size.0).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b't']).unwrap();
|
stdin.write_all(&[b't']).unwrap();
|
||||||
|
stdin.flush();
|
||||||
} else {
|
} else {
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
}
|
}
|
||||||
|
@ -601,6 +653,7 @@ impl EmbedGrid {
|
||||||
.write_all((cursor.0 + 1).to_string().as_bytes())
|
.write_all((cursor.0 + 1).to_string().as_bytes())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
stdin.write_all(&[b'R']).unwrap();
|
stdin.write_all(&[b'R']).unwrap();
|
||||||
|
stdin.flush();
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'A', State::Csi1(buf)) => {
|
(b'A', State::Csi1(buf)) => {
|
||||||
|
@ -609,16 +662,7 @@ impl EmbedGrid {
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("cursor up {} times, cursor was: {:?}", offset, cursor);
|
debug!("cursor up {} times, cursor was: {:?}", offset, cursor);
|
||||||
if cursor.1 == scroll_region.top {
|
if cursor.1 >= offset {
|
||||||
for y in scroll_region.top..scroll_region.bottom {
|
|
||||||
for x in 0..terminal_size.1 {
|
|
||||||
grid[(x, y)] = grid[(x, y + 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for x in 0..terminal_size.1 {
|
|
||||||
grid[(x, scroll_region.bottom)] = Cell::default();
|
|
||||||
}
|
|
||||||
} else if cursor.1 >= offset {
|
|
||||||
cursor.1 -= offset;
|
cursor.1 -= offset;
|
||||||
} else {
|
} else {
|
||||||
debug!("offset > cursor.1");
|
debug!("offset > cursor.1");
|
||||||
|
@ -632,12 +676,23 @@ impl EmbedGrid {
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("cursor down {} times, cursor was: {:?}", offset, cursor);
|
debug!("cursor down {} times, cursor was: {:?}", offset, cursor);
|
||||||
if offset + cursor.1 < terminal_size.1 {
|
if cursor.1 == scroll_region.bottom {
|
||||||
|
/* scroll down */
|
||||||
|
for y in scroll_region.top..scroll_region.bottom {
|
||||||
|
for x in 0..terminal_size.1 {
|
||||||
|
grid[(x, y)] = grid[(x, y + 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for x in 0..terminal_size.1 {
|
||||||
|
grid[(x, scroll_region.bottom)] = Cell::default();
|
||||||
|
}
|
||||||
|
} else if offset + cursor.1 < terminal_size.1 {
|
||||||
cursor.1 += offset;
|
cursor.1 += offset;
|
||||||
}
|
}
|
||||||
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
cursor.1 = terminal_size.1.saturating_sub(1);
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -646,8 +701,13 @@ impl EmbedGrid {
|
||||||
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cursor.0 = cursor.0.saturating_sub(offset);
|
if cursor.0 >= offset {
|
||||||
debug!("cursor became: {:?}", cursor);
|
cursor.0 -= offset;
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"ESC[ {} D cursor backwards cursor became: {:?}",
|
||||||
|
offset, cursor
|
||||||
|
);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'E', State::Csi1(buf)) => {
|
(b'E', State::Csi1(buf)) => {
|
||||||
|
@ -666,6 +726,7 @@ impl EmbedGrid {
|
||||||
cursor.1 = terminal_size.1.saturating_sub(1);
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
}
|
}
|
||||||
cursor.0 = 0;
|
cursor.0 = 0;
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -675,19 +736,24 @@ impl EmbedGrid {
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!(
|
debug!(
|
||||||
"cursor next line {} times, cursor was: {:?}",
|
"cursor previous line {} times, cursor was: {:?}",
|
||||||
offset, cursor
|
offset, cursor
|
||||||
);
|
);
|
||||||
cursor.1 = cursor.1.saturating_sub(offset);
|
cursor.1 = cursor.1.saturating_sub(offset);
|
||||||
cursor.0 = 0;
|
cursor.0 = 0;
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'G', State::Csi1(buf)) => {
|
(b'G', State::Csi1(_)) | (b'G', State::Csi) => {
|
||||||
// ESC[{buf}G Cursor Character Absolute [column={buf}] (default = [row,1])
|
// ESC[{buf}G Cursor Character Absolute [column={buf}] (default = [row,1])
|
||||||
let new_col = unsafe { std::str::from_utf8_unchecked(buf) }
|
let new_col = if let State::Csi1(buf) = state {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
|
debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
|
||||||
if new_col < terminal_size.0 {
|
if new_col < terminal_size.0 {
|
||||||
cursor.0 = new_col.saturating_sub(1);
|
cursor.0 = new_col.saturating_sub(1);
|
||||||
|
@ -697,47 +763,53 @@ impl EmbedGrid {
|
||||||
new_col, terminal_size.0, terminal_size
|
new_col, terminal_size.0, terminal_size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'C', State::Csi1(buf)) => {
|
(b'C', State::Csi1(buf)) => {
|
||||||
// ESC[{buf}C CSI Cursor Preceding Line {buf} Times
|
// ESC[{buf}C CSI Cursor Forward {buf} Times
|
||||||
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!(
|
debug!("cursor forward {} times, cursor was: {:?}", offset, cursor);
|
||||||
"cursor preceding {} times, cursor was: {:?}",
|
if cursor.0 + offset < terminal_size.0 {
|
||||||
offset, cursor
|
cursor.0 += offset;
|
||||||
);
|
|
||||||
if cursor.1 >= offset {
|
|
||||||
cursor.1 -= offset;
|
|
||||||
}
|
|
||||||
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
|
||||||
cursor.1 = terminal_size.1.saturating_sub(1);
|
|
||||||
}
|
}
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'P', State::Csi1(buf)) => {
|
(b'P', State::Csi1(_)) | (b'P', State::Csi) => {
|
||||||
// ESC[{buf}P CSI Delete {buf} characters, default = 1
|
// ESC[{buf}P CSI Delete {buf} characters, default = 1
|
||||||
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
let offset = if let State::Csi1(buf) = state {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..(terminal_size.0 - cursor.0 - offset) {
|
||||||
|
grid[(cursor.0 + i, cursor.1)] = grid[(cursor.0 + i + offset, cursor.1)];
|
||||||
|
}
|
||||||
|
for x in (terminal_size.0 - offset)..terminal_size.0 {
|
||||||
|
grid[(x, cursor.1)].set_ch(' ');
|
||||||
|
}
|
||||||
debug!(
|
debug!(
|
||||||
"Delete {} Character(s) with cursor at {:?} ",
|
"Delete {} Character(s) with cursor at {:?} ",
|
||||||
offset, cursor
|
offset, cursor
|
||||||
);
|
);
|
||||||
for x in (cursor.0 - std::cmp::min(offset, cursor.0))..cursor.0 {
|
|
||||||
grid[(x, cursor.1 + scroll_region.top)].set_ch(' ');
|
|
||||||
}
|
|
||||||
debug!("cursor became: {:?}", cursor);
|
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'd', State::Csi1(buf)) => {
|
(b'd', State::Csi1(_)) | (b'd', State::Csi) => {
|
||||||
/* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
|
/* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
|
||||||
let row = unsafe { std::str::from_utf8_unchecked(buf) }
|
let row = if let State::Csi1(buf) = state {
|
||||||
|
unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
debug!(
|
debug!(
|
||||||
"Line position absolute row {} with cursor at {:?}",
|
"Line position absolute row {} with cursor at {:?}",
|
||||||
row, cursor
|
row, cursor
|
||||||
|
@ -746,6 +818,7 @@ impl EmbedGrid {
|
||||||
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
cursor.1 = terminal_size.1.saturating_sub(1);
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
*wrap_next = false;
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
@ -757,6 +830,23 @@ impl EmbedGrid {
|
||||||
(b'm', State::Csi1(ref buf1)) => {
|
(b'm', State::Csi1(ref buf1)) => {
|
||||||
// Character Attributes.
|
// Character Attributes.
|
||||||
match buf1.as_slice() {
|
match buf1.as_slice() {
|
||||||
|
b"0" => {
|
||||||
|
*fg_color = Color::Default;
|
||||||
|
*bg_color = Color::Default;
|
||||||
|
}
|
||||||
|
b"1" => { /* bold */ }
|
||||||
|
b"7" => {
|
||||||
|
/* Inverse */
|
||||||
|
let temp = *fg_color;
|
||||||
|
*fg_color = *bg_color;
|
||||||
|
*bg_color = temp;
|
||||||
|
}
|
||||||
|
b"27" => {
|
||||||
|
/* Inverse off */
|
||||||
|
let temp = *fg_color;
|
||||||
|
*fg_color = *bg_color;
|
||||||
|
*bg_color = temp;
|
||||||
|
}
|
||||||
b"30" => *fg_color = Color::Black,
|
b"30" => *fg_color = Color::Black,
|
||||||
b"31" => *fg_color = Color::Red,
|
b"31" => *fg_color = Color::Red,
|
||||||
b"32" => *fg_color = Color::Green,
|
b"32" => *fg_color = Color::Green,
|
||||||
|
@ -777,19 +867,98 @@ impl EmbedGrid {
|
||||||
b"47" => *bg_color = Color::White,
|
b"47" => *bg_color = Color::White,
|
||||||
|
|
||||||
b"49" => *bg_color = Color::Default,
|
b"49" => *bg_color = Color::Default,
|
||||||
|
|
||||||
|
b"90" => *fg_color = Color::Black,
|
||||||
|
b"91" => *fg_color = Color::Red,
|
||||||
|
b"92" => *fg_color = Color::Green,
|
||||||
|
b"93" => *fg_color = Color::Yellow,
|
||||||
|
b"94" => *fg_color = Color::Blue,
|
||||||
|
b"95" => *fg_color = Color::Magenta,
|
||||||
|
b"96" => *fg_color = Color::Cyan,
|
||||||
|
b"97" => *fg_color = Color::White,
|
||||||
|
|
||||||
|
b"100" => *bg_color = Color::Black,
|
||||||
|
b"101" => *bg_color = Color::Red,
|
||||||
|
b"102" => *bg_color = Color::Green,
|
||||||
|
b"103" => *bg_color = Color::Yellow,
|
||||||
|
b"104" => *bg_color = Color::Blue,
|
||||||
|
b"105" => *bg_color = Color::Magenta,
|
||||||
|
b"106" => *bg_color = Color::Cyan,
|
||||||
|
b"107" => *bg_color = Color::White,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
grid[cursor_val!()].set_fg(*fg_color);
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
grid[cursor_val!()].set_bg(*bg_color);
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
(b'm', State::Csi2(ref buf1, ref buf2)) => {
|
||||||
|
for b in &[buf1, buf2] {
|
||||||
|
match b.as_slice() {
|
||||||
|
b"0" => {
|
||||||
|
*fg_color = Color::Default;
|
||||||
|
*bg_color = Color::Default;
|
||||||
|
}
|
||||||
|
b"1" => { /* bold */ }
|
||||||
|
b"7" => {
|
||||||
|
/* Inverse */
|
||||||
|
let temp = *fg_color;
|
||||||
|
*fg_color = *bg_color;
|
||||||
|
*bg_color = temp;
|
||||||
|
}
|
||||||
|
b"27" => {
|
||||||
|
/* Inverse off */
|
||||||
|
let temp = *fg_color;
|
||||||
|
*fg_color = *bg_color;
|
||||||
|
*bg_color = temp;
|
||||||
|
}
|
||||||
|
b"30" => *fg_color = Color::Black,
|
||||||
|
b"31" => *fg_color = Color::Red,
|
||||||
|
b"32" => *fg_color = Color::Green,
|
||||||
|
b"33" => *fg_color = Color::Yellow,
|
||||||
|
b"34" => *fg_color = Color::Blue,
|
||||||
|
b"35" => *fg_color = Color::Magenta,
|
||||||
|
b"36" => *fg_color = Color::Cyan,
|
||||||
|
b"37" => *fg_color = Color::White,
|
||||||
|
|
||||||
|
b"39" => *fg_color = Color::Default,
|
||||||
|
b"40" => *bg_color = Color::Black,
|
||||||
|
b"41" => *bg_color = Color::Red,
|
||||||
|
b"42" => *bg_color = Color::Green,
|
||||||
|
b"43" => *bg_color = Color::Yellow,
|
||||||
|
b"44" => *bg_color = Color::Blue,
|
||||||
|
b"45" => *bg_color = Color::Magenta,
|
||||||
|
b"46" => *bg_color = Color::Cyan,
|
||||||
|
b"47" => *bg_color = Color::White,
|
||||||
|
|
||||||
|
b"49" => *bg_color = Color::Default,
|
||||||
|
|
||||||
|
b"90" => *fg_color = Color::Black,
|
||||||
|
b"91" => *fg_color = Color::Red,
|
||||||
|
b"92" => *fg_color = Color::Green,
|
||||||
|
b"93" => *fg_color = Color::Yellow,
|
||||||
|
b"94" => *fg_color = Color::Blue,
|
||||||
|
b"95" => *fg_color = Color::Magenta,
|
||||||
|
b"96" => *fg_color = Color::Cyan,
|
||||||
|
b"97" => *fg_color = Color::White,
|
||||||
|
|
||||||
|
b"100" => *bg_color = Color::Black,
|
||||||
|
b"101" => *bg_color = Color::Red,
|
||||||
|
b"102" => *bg_color = Color::Green,
|
||||||
|
b"103" => *bg_color = Color::Yellow,
|
||||||
|
b"104" => *bg_color = Color::Blue,
|
||||||
|
b"105" => *bg_color = Color::Magenta,
|
||||||
|
b"106" => *bg_color = Color::Cyan,
|
||||||
|
b"107" => *bg_color = Color::White,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
(c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => {
|
(c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(_, State::Csi1(_)) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => {
|
(b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => {
|
||||||
let buf1 = std::mem::replace(buf1_p, Vec::new());
|
let buf1 = std::mem::replace(buf1_p, Vec::new());
|
||||||
let buf2 = std::mem::replace(buf2_p, Vec::new());
|
let buf2 = std::mem::replace(buf2_p, Vec::new());
|
||||||
|
@ -801,58 +970,69 @@ impl EmbedGrid {
|
||||||
// Window manipulation, skip it
|
// Window manipulation, skip it
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(b'H', State::Csi2(ref y, ref x)) => {
|
(b'H', State::Csi2(_, _)) | (b'H', State::Csi) => {
|
||||||
//Cursor Position [row;column] (default = [1,1]) (CUP).
|
//Cursor Position [row;column] (default = [1,1]) (CUP).
|
||||||
let orig_x = unsafe { std::str::from_utf8_unchecked(x) }
|
let (orig_x, mut orig_y) = if let State::Csi2(ref y, ref x) = state {
|
||||||
|
(
|
||||||
|
unsafe { std::str::from_utf8_unchecked(x) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1),
|
||||||
let orig_y = unsafe { std::str::from_utf8_unchecked(y) }
|
unsafe { std::str::from_utf8_unchecked(y) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1),
|
||||||
debug!("sending {}", EscCode::from((&(*state), byte)),);
|
)
|
||||||
|
} else {
|
||||||
|
(1, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (min_y, max_y) = if *origin_mode {
|
||||||
|
debug!(*origin_mode);
|
||||||
|
orig_y += scroll_region.top;
|
||||||
|
(scroll_region.top, scroll_region.bottom)
|
||||||
|
} else {
|
||||||
|
(0, terminal_size.1.saturating_sub(1))
|
||||||
|
};
|
||||||
|
|
||||||
|
cursor.0 = std::cmp::min(orig_x - 1, terminal_size.0.saturating_sub(1));
|
||||||
|
cursor.1 = std::cmp::max(min_y, std::cmp::min(max_y, orig_y - 1));
|
||||||
|
*wrap_next = false;
|
||||||
|
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)),);
|
||||||
debug!(
|
debug!(
|
||||||
"cursor set to ({},{}), cursor was: {:?}",
|
"cursor set to ({},{}), cursor was: {:?}",
|
||||||
orig_x, orig_y, cursor
|
orig_x, orig_y, cursor
|
||||||
);
|
);
|
||||||
if orig_x - 1 <= terminal_size.0 && orig_y - 1 <= terminal_size.1 {
|
|
||||||
cursor.0 = orig_x - 1;
|
|
||||||
cursor.1 = orig_y - 1;
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"[error] terminal_size = {:?}, cursor = {:?} but given [{},{}]",
|
|
||||||
terminal_size, cursor, orig_x, orig_y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
|
||||||
cursor.1 = terminal_size.1.saturating_sub(1);
|
|
||||||
}
|
|
||||||
debug!("cursor became: {:?}", cursor);
|
debug!("cursor became: {:?}", cursor);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => {
|
(c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => {
|
||||||
buf.push(c);
|
buf.push(c);
|
||||||
}
|
}
|
||||||
(b'r', State::Csi2(ref top, ref bottom)) => {
|
(b'r', State::Csi2(_, _)) | (b'r', State::Csi) => {
|
||||||
/* CSI Ps ; Ps r Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). */
|
/* CSI Ps ; Ps r Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). */
|
||||||
let top = unsafe { std::str::from_utf8_unchecked(top) }
|
let (top, bottom) = if let State::Csi2(ref top, ref bottom) = state {
|
||||||
|
(
|
||||||
|
unsafe { std::str::from_utf8_unchecked(top) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1),
|
||||||
let bottom = unsafe { std::str::from_utf8_unchecked(bottom) }
|
unsafe { std::str::from_utf8_unchecked(bottom) }
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap_or(1);
|
.unwrap_or(1),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(1, terminal_size.1)
|
||||||
|
};
|
||||||
|
|
||||||
if bottom > top {
|
if bottom > top {
|
||||||
scroll_region.top = top - 1;
|
scroll_region.top = top - 1;
|
||||||
scroll_region.bottom = bottom - 1;
|
scroll_region.bottom = bottom - 1;
|
||||||
*cursor = (0, 0);
|
*cursor = (0, 0);
|
||||||
|
*wrap_next = false;
|
||||||
}
|
}
|
||||||
debug!("set scrolling region to {:?}", scroll_region);
|
debug!("set scrolling region to {:?}", scroll_region);
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
(_, State::Csi2(_, _)) => {
|
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
|
||||||
*state = State::Normal;
|
|
||||||
}
|
|
||||||
(b't', State::Csi3(_, _, _)) => {
|
(b't', State::Csi3(_, _, _)) => {
|
||||||
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
// Window manipulation, skip it
|
// Window manipulation, skip it
|
||||||
|
@ -890,10 +1070,34 @@ impl EmbedGrid {
|
||||||
debug!("{}", EscCode::from((&(*state), byte)));
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
(_, State::Csi) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Csi1(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Csi2(_, _)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
(_, State::Csi3(_, _, _)) => {
|
(_, State::Csi3(_, _, _)) => {
|
||||||
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
*state = State::Normal;
|
*state = State::Normal;
|
||||||
}
|
}
|
||||||
|
(_, State::Osc1(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Osc2(_, _)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::CsiQ(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
/* other stuff */
|
/* other stuff */
|
||||||
(_, State::G0) => {
|
(_, State::G0) => {
|
||||||
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
|
@ -902,3 +1106,106 @@ impl EmbedGrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
/// Performs the normal scroll up motion:
|
||||||
|
///
|
||||||
|
/// First clear offset number of lines:
|
||||||
|
///
|
||||||
|
/// For offset = 1, top = 1:
|
||||||
|
///
|
||||||
|
/// | 111111111111 | | |
|
||||||
|
/// | 222222222222 | | 222222222222 |
|
||||||
|
/// | 333333333333 | | 333333333333 |
|
||||||
|
/// | 444444444444 | --> | 444444444444 |
|
||||||
|
/// | 555555555555 | | 555555555555 |
|
||||||
|
/// | 666666666666 | | 666666666666 |
|
||||||
|
///
|
||||||
|
/// In each step, swap the current line with the next by offset:
|
||||||
|
///
|
||||||
|
/// | | | 222222222222 |
|
||||||
|
/// | 222222222222 | | |
|
||||||
|
/// | 333333333333 | | 333333333333 |
|
||||||
|
/// | 444444444444 | --> | 444444444444 |
|
||||||
|
/// | 555555555555 | | 555555555555 |
|
||||||
|
/// | 666666666666 | | 666666666666 |
|
||||||
|
///
|
||||||
|
/// Result:
|
||||||
|
/// Before After
|
||||||
|
/// | 111111111111 | | 222222222222 |
|
||||||
|
/// | 222222222222 | | 333333333333 |
|
||||||
|
/// | 333333333333 | | 444444444444 |
|
||||||
|
/// | 444444444444 | | 555555555555 |
|
||||||
|
/// | 555555555555 | | 666666666666 |
|
||||||
|
/// | 666666666666 | | |
|
||||||
|
///
|
||||||
|
fn scroll_up(grid: &mut CellBuffer, scroll_region: &ScrollRegion, top: usize, offset: usize) {
|
||||||
|
//debug!(
|
||||||
|
// "scroll_up scroll_region {:?}, top: {} offset {}",
|
||||||
|
// scroll_region, top, offset
|
||||||
|
//);
|
||||||
|
for y in top..=(top + offset - 1) {
|
||||||
|
for x in 0..grid.size().0 {
|
||||||
|
grid[(x, y)] = Cell::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y in top..=(scroll_region.bottom - offset) {
|
||||||
|
for x in 0..grid.size().0 {
|
||||||
|
let temp = grid[(x, y)];
|
||||||
|
grid[(x, y)] = grid[(x, y + offset)];
|
||||||
|
grid[(x, y + offset)] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
/// Performs the normal scroll down motion:
|
||||||
|
///
|
||||||
|
/// First clear offset number of lines:
|
||||||
|
///
|
||||||
|
/// For offset = 1, top = 1:
|
||||||
|
///
|
||||||
|
/// | 111111111111 | | 111111111111 |
|
||||||
|
/// | 222222222222 | | 222222222222 |
|
||||||
|
/// | 333333333333 | | 333333333333 |
|
||||||
|
/// | 444444444444 | --> | 444444444444 |
|
||||||
|
/// | 555555555555 | | 555555555555 |
|
||||||
|
/// | 666666666666 | | |
|
||||||
|
///
|
||||||
|
/// In each step, swap the current line with the prev by offset:
|
||||||
|
///
|
||||||
|
/// | 111111111111 | | 111111111111 |
|
||||||
|
/// | 222222222222 | | 222222222222 |
|
||||||
|
/// | 333333333333 | | 333333333333 |
|
||||||
|
/// | 444444444444 | --> | 444444444444 |
|
||||||
|
/// | 555555555555 | | |
|
||||||
|
/// | | | 555555555555 |
|
||||||
|
///
|
||||||
|
/// Result:
|
||||||
|
/// Before After
|
||||||
|
/// | 111111111111 | | |
|
||||||
|
/// | 222222222222 | | 111111111111 |
|
||||||
|
/// | 333333333333 | | 222222222222 |
|
||||||
|
/// | 444444444444 | | 333333333333 |
|
||||||
|
/// | 555555555555 | | 444444444444 |
|
||||||
|
/// | 666666666666 | | 555555555555 |
|
||||||
|
///
|
||||||
|
fn scroll_down(grid: &mut CellBuffer, scroll_region: &ScrollRegion, top: usize, offset: usize) {
|
||||||
|
//debug!(
|
||||||
|
// "scroll_down scroll_region {:?}, top: {} offset {}",
|
||||||
|
// scroll_region, top, offset
|
||||||
|
//);
|
||||||
|
for y in (scroll_region.bottom - offset + 1)..=scroll_region.bottom {
|
||||||
|
for x in 0..grid.size().0 {
|
||||||
|
grid[(x, y)] = Cell::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in ((top + offset)..=scroll_region.bottom).rev() {
|
||||||
|
for x in 0..grid.size().0 {
|
||||||
|
let temp = grid[(x, y)];
|
||||||
|
grid[(x, y)] = grid[(x, y - offset)];
|
||||||
|
grid[(x, y - offset)] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue