1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-21 03:39:16 +03:00

add scrolling of regions

Add two new `Change` variants: `ScrollRegionUp` and `ScrollRegionDown`, which
scroll part of the screen up or down by a number of lines.

On Unix, these are implemented using the `change_scroll_region` and
`parm_index`/`parm_rindex` terminfo capabilities if available.  If `parm_index`
or `parm_rindex` are not available, but `scroll_forward` or `scroll_reverse`
are, then these are used repeatedly to get the same effect.

On Windows, these are implemented using `ScrollConsoleScreenBuffer`.
This commit is contained in:
Mark Thomas 2019-04-21 00:19:55 +01:00 committed by Wez Furlong
parent 4e783b3920
commit 7c6cf297a6
5 changed files with 227 additions and 6 deletions

View File

@ -604,6 +604,66 @@ impl TerminfoRenderer {
self.cursor_up(image.height as u32, out)?;
}
}
Change::ScrollRegionUp {
first_row,
region_size,
scroll_count,
} => {
if *region_size > 0 {
if let Some(csr) = self.get_capability::<cap::ChangeScrollRegion>() {
let top = *first_row as u32;
let bottom = (*first_row + *region_size - 1) as u32;
let scroll_count = *scroll_count as u32;
csr.expand().top(top).bottom(bottom).to(out.by_ref())?;
if scroll_count > 0 {
if let Some(scroll) = self.get_capability::<cap::ParmIndex>() {
scroll.expand().count(scroll_count).to(out.by_ref())?
} else {
let scroll = self.get_capability::<cap::ScrollForward>();
let set_position = self.get_capability::<cap::CursorAddress>();
if let (Some(scroll), Some(set_position)) =
(scroll, set_position)
{
set_position.expand().x(0).y(bottom).to(out.by_ref())?;
for _ in 0..scroll_count {
scroll.expand().to(out.by_ref())?
}
}
}
}
}
}
}
Change::ScrollRegionDown {
first_row,
region_size,
scroll_count,
} => {
if *region_size > 0 {
if let Some(csr) = self.get_capability::<cap::ChangeScrollRegion>() {
let top = *first_row as u32;
let bottom = (*first_row + *region_size - 1) as u32;
let scroll_count = *scroll_count as u32;
csr.expand().top(top).bottom(bottom).to(out.by_ref())?;
if scroll_count > 0 {
if let Some(scroll) = self.get_capability::<cap::ParmRindex>() {
scroll.expand().count(scroll_count).to(out.by_ref())?
} else {
let scroll = self.get_capability::<cap::ScrollReverse>();
let set_position = self.get_capability::<cap::CursorAddress>();
if let (Some(scroll), Some(set_position)) =
(scroll, set_position)
{
set_position.expand().x(0).y(top).to(out.by_ref())?;
for _ in 0..scroll_count {
scroll.expand().to(out.by_ref())?
}
}
}
}
}
}
}
}
}

View File

@ -280,6 +280,42 @@ impl WindowsConsoleRenderer {
info.dwCursorPosition.Y,
)?;
}
Change::ScrollRegionUp {
first_row,
region_size,
scroll_count,
} => {
if *region_size > 0 {
let info = out.get_buffer_info()?;
out.scroll_region(
info.srWindow.Left,
info.srWindow.Top + *first_row as i16,
info.srWindow.Right,
info.srWindow.Top + *first_row as i16 + *region_size as i16,
0,
-(*scroll_count as i16),
to_attr_word(&self.current_attr),
)?;
}
}
Change::ScrollRegionDown {
first_row,
region_size,
scroll_count,
} => {
if *region_size > 0 {
let info = out.get_buffer_info()?;
out.scroll_region(
info.srWindow.Left,
info.srWindow.Top + *first_row as i16,
info.srWindow.Right,
info.srWindow.Top + *first_row as i16 + *region_size as i16,
0,
*scroll_count as i16,
to_attr_word(&self.current_attr),
)?;
}
}
}
}
out.flush()?;

View File

@ -41,7 +41,6 @@ pub enum Change {
CursorColor(ColorAttribute),
/// Change the cursor shape
CursorShape(CursorShape),
/* ChangeScrollRegion{top: usize, bottom: usize}, */
/// Place an image at the current cursor position.
/// The image defines the dimensions in cells.
/// TODO: check iterm rendering behavior when the image is larger than the width of the screen.
@ -50,6 +49,38 @@ pub enum Change {
/// The cursor Y position is unchanged by rendering the Image.
/// The cursor X position will be incremented by `Image::width` cells.
Image(Image),
/// Scroll the `region_size` lines starting at `first_row` upwards
/// by `scroll_count` lines. The `scroll_count` lines at the top of
/// the region are overwritten. The `scroll_count` lines at the
/// bottom of the region will become blank.
///
/// After a region is scrolled, the cursor position is undefined,
/// and the terminal's scroll region is set to the range specified.
/// To restore scrolling behaviour to the full terminal window, an
/// additional `Change::ScrollRegionUp { first_row: 0, region_size:
/// height, scroll_count: 0 }`, where `height` is the height of the
/// terminal, should be emitted.
ScrollRegionUp {
first_row: usize,
region_size: usize,
scroll_count: usize,
},
/// Scroll the `region_size` lines starting at `first_row` downwards
/// by `scroll_count` lines. The `scroll_count` lines at the bottom
/// the region are overwritten. The `scroll_count` lines at the top
/// of the region will become blank.
///
/// After a region is scrolled, the cursor position is undefined,
/// and the terminal's scroll region is set to the range specified.
/// To restore scrolling behaviour to the full terminal window, an
/// additional `Change::ScrollRegionDown { first_row: 0,
/// region_size: height, scroll_count: 0 }`, where `height` is the
/// height of the terminal, should be emitted.
ScrollRegionDown {
first_row: usize,
region_size: usize,
scroll_count: usize,
},
}
impl Change {

View File

@ -251,6 +251,16 @@ impl Surface {
Change::CursorColor(color) => self.cursor_color = *color,
Change::CursorShape(shape) => self.cursor_shape = *shape,
Change::Image(image) => self.add_image(image),
Change::ScrollRegionUp {
first_row,
region_size,
scroll_count,
} => self.scroll_region_up(*first_row, *region_size, *scroll_count),
Change::ScrollRegionDown {
first_row,
region_size,
scroll_count,
} => self.scroll_region_down(*first_row, *region_size, *scroll_count),
}
}
@ -329,6 +339,28 @@ impl Surface {
self.lines.push(Line::with_width(self.width));
}
fn scroll_region_up(&mut self, start: usize, size: usize, count: usize) {
// Replace the first lines with empty lines
for index in start..start + min(count, size) {
self.lines[index] = Line::with_width(self.width);
}
// Rotate the remaining lines up the surface.
if 0 < count && count < size {
self.lines[start..start + size].rotate_left(count);
}
}
fn scroll_region_down(&mut self, start: usize, size: usize, count: usize) {
// Replace the last lines with empty lines
for index in start + size - min(count, size)..start + size {
self.lines[index] = Line::with_width(self.width);
}
// Rotate the remaining lines down the surface.
if 0 < count && count < size {
self.lines[start..start + size].rotate_right(count);
}
}
fn print_text(&mut self, text: &str) {
for g in UnicodeSegmentation::graphemes(text, true) {
if g == "\r\n" {

View File

@ -1,5 +1,6 @@
use crate::istty::IsTty;
use failure::Error;
use std::cmp::{max, min};
use std::collections::VecDeque;
use std::fs::OpenOptions;
use std::io::{stdin, stdout, Error as IoError, Read, Result as IoResult, Write};
@ -11,11 +12,11 @@ use winapi::um::handleapi::*;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::wincon::{
FillConsoleOutputAttribute, FillConsoleOutputCharacterW, GetConsoleScreenBufferInfo,
SetConsoleCursorPosition, SetConsoleScreenBufferSize, SetConsoleTextAttribute,
SetConsoleWindowInfo, CONSOLE_SCREEN_BUFFER_INFO, COORD, DISABLE_NEWLINE_AUTO_RETURN,
ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_MOUSE_INPUT, ENABLE_PROCESSED_INPUT,
ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_WINDOW_INPUT,
INPUT_RECORD, SMALL_RECT,
ScrollConsoleScreenBufferW, SetConsoleCursorPosition, SetConsoleScreenBufferSize,
SetConsoleTextAttribute, SetConsoleWindowInfo, CHAR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD,
DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_MOUSE_INPUT,
ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
ENABLE_WINDOW_INPUT, INPUT_RECORD, SMALL_RECT,
};
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
@ -43,6 +44,16 @@ pub trait ConsoleOutputHandle {
fn set_cursor_position(&mut self, x: i16, y: i16) -> Result<(), Error>;
fn get_buffer_info(&mut self) -> Result<CONSOLE_SCREEN_BUFFER_INFO, Error>;
fn set_viewport(&mut self, left: i16, top: i16, right: i16, bottom: i16) -> Result<(), Error>;
fn scroll_region(
&mut self,
left: i16,
top: i16,
right: i16,
bottom: i16,
dx: i16,
dy: i16,
attr: u16,
) -> Result<(), Error>;
}
struct InputHandle {
@ -308,6 +319,57 @@ impl ConsoleOutputHandle for OutputHandle {
}
Ok(())
}
fn scroll_region(
&mut self,
left: i16,
top: i16,
right: i16,
bottom: i16,
dx: i16,
dy: i16,
attr: u16,
) -> Result<(), Error> {
let scroll_rect = SMALL_RECT {
Left: max(left, left - dx),
Top: max(top, top - dy),
Right: min(right, right - dx),
Bottom: min(bottom, bottom - dy),
};
let clip_rect = SMALL_RECT {
Left: left,
Top: top,
Right: right,
Bottom: bottom,
};
let fill = unsafe {
let mut fill = CHAR_INFO {
Char: mem::zeroed(),
Attributes: attr,
};
*fill.Char.UnicodeChar_mut() = ' ' as u16;
fill
};
if unsafe {
ScrollConsoleScreenBufferW(
self.handle as *mut _,
&scroll_rect,
&clip_rect,
COORD {
X: max(left, left + dx),
Y: max(left, top + dy),
},
&fill,
)
} == 0
{
bail!(
"ScrollConsoleScreenBufferW failed: {}",
IoError::last_os_error()
);
}
Ok(())
}
}
pub struct WindowsTerminal {