Fix(scroll): Prevent scroll buffer overwrite (#655)

* fix #306: Buffer new vte events when pane is scrolled up

* Exit scroll mode after pressing [Enter] once

* clear scroll of active pane when changing from scroll mode to normal or locked mode

* Increase render interval
This commit is contained in:
Kunal Mohan 2021-10-01 22:20:13 +05:30 committed by GitHub
parent 79fa3594e3
commit 3c05f26802
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 5 deletions

View file

@ -361,6 +361,7 @@ pub struct Grid {
pub pending_messages_to_pty: Vec<Vec<u8>>,
pub selection: Selection,
pub title: Option<String>,
pub is_scrolled: bool,
}
impl Debug for Grid {
@ -405,6 +406,7 @@ impl Grid {
title_stack: vec![],
title: None,
changed_colors: None,
is_scrolled: false,
}
}
pub fn render_full_viewport(&mut self) {
@ -537,6 +539,7 @@ impl Grid {
}
pub fn scroll_up_one_line(&mut self) {
if !self.lines_above.is_empty() && self.viewport.len() == self.height {
self.is_scrolled = true;
let line_to_push_down = self.viewport.pop().unwrap();
self.lines_below.insert(0, line_to_push_down);
@ -572,6 +575,9 @@ impl Grid {
self.selection.move_up(1);
self.output_buffer.update_all_lines();
}
if self.lines_below.is_empty() {
self.is_scrolled = false;
}
}
fn force_change_size(&mut self, new_rows: usize, new_columns: usize) {
// this is an ugly hack - it's here because sometimes we need to change_size to the

View file

@ -263,6 +263,9 @@ impl Pane for PluginPane {
fn clear_scroll(&mut self) {
//unimplemented!()
}
fn is_scrolled(&self) -> bool {
false
}
fn active_at(&self) -> Instant {
self.active_at

View file

@ -320,6 +320,9 @@ impl Pane for TerminalPane {
self.grid.reset_viewport();
self.set_should_render(true);
}
fn is_scrolled(&self) -> bool {
self.grid.is_scrolled
}
fn active_at(&self) -> Instant {
self.active_at

View file

@ -16,7 +16,7 @@ use crate::{
wasm_vm::PluginInstruction,
ServerInstruction, SessionState,
};
use zellij_tile::data::{Event, ModeInfo, Palette, PluginCapabilities, TabInfo};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo};
use zellij_utils::{
errors::{ContextType, ScreenContext},
input::{get_mode_info, options::Options},
@ -205,6 +205,12 @@ impl Screen {
new_tab.visible(true);
let old_active_index = self.active_tab_index.replace(new_tab_index);
if let Some(ref i) = old_active_index {
if let Some(t) = self.tabs.get_mut(i) {
t.is_active = false;
}
}
self.tabs.get_mut(&new_tab_index).unwrap().is_active = true;
self.tab_history.retain(|&e| e != Some(new_tab_pos));
self.tab_history.push(old_active_index);
@ -264,6 +270,7 @@ impl Screen {
for t in self.tabs.values_mut() {
if t.index == self.active_tab_index.unwrap() {
t.set_force_render();
t.is_active = true;
t.visible(true);
}
if t.position > active_tab.position {
@ -351,8 +358,9 @@ impl Screen {
self.draw_pane_frames,
);
tab.apply_layout(layout, new_pids, tab_index);
if let Some(active_tab) = self.get_active_tab() {
if let Some(active_tab) = self.get_active_tab_mut() {
active_tab.visible(false);
active_tab.is_active = false;
}
self.tab_history
.push(self.active_tab_index.replace(tab_index));
@ -398,6 +406,13 @@ impl Screen {
self.update_tabs();
}
pub fn change_mode(&mut self, mode_info: ModeInfo) {
if self.mode_info.mode == InputMode::Scroll
&& (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked)
{
self.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
self.colors = mode_info.palette;
self.mode_info = mode_info;
for tab in self.tabs.values_mut() {

View file

@ -16,7 +16,7 @@ use std::sync::{mpsc::channel, Arc, RwLock};
use std::time::Instant;
use std::{
cmp::Reverse,
collections::{BTreeMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor};
use zellij_utils::input::layout::Direction;
@ -39,6 +39,9 @@ const MIN_TERMINAL_WIDTH: usize = 5;
const RESIZE_PERCENT: f64 = 5.0;
const MAX_PENDING_VTE_EVENTS: usize = 7000;
const PENDING_EVENTS_SET_SIZE: usize = 200;
type BorderAndPaneIds = (usize, Vec<PaneId>);
fn split(direction: Direction, rect: &PaneGeom) -> Option<(PaneGeom, PaneGeom)> {
@ -112,7 +115,9 @@ pub(crate) struct Tab {
session_state: Arc<RwLock<SessionState>>,
pub mode_info: ModeInfo,
pub colors: Palette,
pub is_active: bool,
draw_pane_frames: bool,
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -162,6 +167,7 @@ pub trait Pane {
fn scroll_up(&mut self, count: usize);
fn scroll_down(&mut self, count: usize);
fn clear_scroll(&mut self);
fn is_scrolled(&self) -> bool;
fn active_at(&self) -> Instant;
fn set_active_at(&mut self, instant: Instant);
fn set_frame(&mut self, frame: bool);
@ -292,6 +298,8 @@ impl Tab {
colors,
session_state,
draw_pane_frames,
pending_vte_events: HashMap::new(),
is_active: true,
}
}
@ -542,6 +550,44 @@ impl Tab {
self.panes.contains_key(&PaneId::Terminal(pid))
}
pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) {
if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) {
// If the pane is scrolled buffer the vte events
if terminal_output.is_scrolled() {
self.pending_vte_events.entry(pid).or_default().push(bytes);
if let Some(evs) = self.pending_vte_events.get(&pid) {
// Reset scroll and play the pending events if the buufer size exceeds the limit
if evs.len() >= MAX_PENDING_VTE_EVENTS {
terminal_output.clear_scroll();
self.play_pending_vte_events(pid);
}
}
return;
}
}
self.process_pty_bytes(pid, bytes);
}
fn play_pending_vte_events(&mut self, pid: RawFd) {
if self.pending_vte_events.get(&pid).is_some() {
if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) {
if terminal_output.is_scrolled() {
return;
}
}
let mut events = self.pending_vte_events.remove(&pid).unwrap();
while events.len() >= PENDING_EVENTS_SET_SIZE {
events
.drain(..PENDING_EVENTS_SET_SIZE)
.for_each(|bytes| self.process_pty_bytes(pid, bytes));
// Render at regular intervals
self.render();
}
events
.drain(..)
.for_each(|bytes| self.process_pty_bytes(pid, bytes));
self.render();
}
}
fn process_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) {
// if we don't have the terminal in self.terminals it's probably because
// of a race condition where the terminal was created in pty but has not
// yet been created in Screen. These events are currently not buffered, so
@ -553,7 +599,6 @@ impl Tab {
for message in messages_to_pty {
self.write_to_pane_id(message, PaneId::Terminal(pid));
}
// self.render();
}
}
pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec<u8>) {
@ -728,7 +773,10 @@ impl Tab {
}
}
pub fn render(&mut self) {
if self.active_terminal.is_none() || self.session_state.read().unwrap().clients.is_empty() {
if self.active_terminal.is_none()
|| self.session_state.read().unwrap().clients.is_empty()
|| !self.is_active
{
// we might not have an active terminal if we closed the last pane
// in that case, we should not render as the app is exiting
// or if there are no attached clients to this session
@ -2218,6 +2266,7 @@ impl Tab {
.get_mut(&PaneId::Terminal(active_terminal_id))
.unwrap();
active_terminal.scroll_down(1);
self.play_pending_vte_events(active_terminal_id);
self.render();
}
}
@ -2242,6 +2291,7 @@ impl Tab {
// prevent overflow when row == 0
let scroll_columns = active_terminal.rows().max(1) - 1;
active_terminal.scroll_down(scroll_columns);
self.play_pending_vte_events(active_terminal_id);
self.render();
}
}
@ -2252,6 +2302,7 @@ impl Tab {
.get_mut(&PaneId::Terminal(active_terminal_id))
.unwrap();
active_terminal.clear_scroll();
self.play_pending_vte_events(active_terminal_id);
self.render();
}
}
@ -2262,6 +2313,7 @@ impl Tab {
.get_mut(&PaneId::Terminal(active_terminal_id))
.unwrap();
active_terminal.clear_scroll();
self.play_pending_vte_events(active_terminal_id);
}
}
pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize) {
@ -2273,6 +2325,9 @@ impl Tab {
pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize) {
if let Some(pane) = self.get_pane_at(point) {
pane.scroll_down(lines);
if let PaneId::Terminal(id) = pane.pid() {
self.play_pending_vte_events(id);
}
self.render();
}
}