aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/mod.rs1
-rw-r--r--src/api/schema.rs2
-rw-r--r--src/api/server.rs1
-rw-r--r--src/app/api.rs1
-rw-r--r--src/app/api/panes.rs36
-rw-r--r--src/client/shell/actions.rs3
-rw-r--r--src/client/shell/tests/popup_focus_projection.rs19
-rw-r--r--src/config/keybinds.rs3
-rw-r--r--src/config/model.rs5
-rw-r--r--src/ghostty/bindings.rs4
-rw-r--r--src/ghostty/mod.rs4
-rw-r--r--src/input/keybind_help.rs1
-rw-r--r--src/input/keybindings.rs36
-rw-r--r--src/main.rs1
-rw-r--r--src/pane.rs64
-rw-r--r--src/pane/terminal.rs19
-rw-r--r--src/server/client_commands.rs7
-rw-r--r--src/server/headless/client_views.rs1
-rw-r--r--src/terminal/runtime.rs4
19 files changed, 211 insertions, 1 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index d28a82ae..bd2983e4 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -59,6 +59,7 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool {
| Method::PaneFocusDirection(_)
| Method::PaneResize(_)
| Method::PaneScroll(_)
+ | Method::PaneClear(_)
| Method::PaneEditScrollback(_)
| Method::PaneFocus(_)
| Method::PaneInputSet(_)
diff --git a/src/api/schema.rs b/src/api/schema.rs
index 7c58fb69..e6c678dc 100644
--- a/src/api/schema.rs
+++ b/src/api/schema.rs
@@ -165,6 +165,8 @@ pub enum Method {
PaneResize(PaneResizeParams),
#[serde(rename = "pane.scroll")]
PaneScroll(PaneScrollParams),
+ #[serde(rename = "pane.clear")]
+ PaneClear(PaneTarget),
#[serde(rename = "pane.edit_scrollback")]
PaneEditScrollback(PaneTarget),
#[serde(rename = "pane.selection.read")]
diff --git a/src/api/server.rs b/src/api/server.rs
index 55571d75..03724813 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -457,6 +457,7 @@ pub(crate) fn api_method_name(method: &Method) -> &'static str {
Method::PaneFocusDirection(_) => "pane.focus_direction",
Method::PaneResize(_) => "pane.resize",
Method::PaneScroll(_) => "pane.scroll",
+ Method::PaneClear(_) => "pane.clear",
Method::PaneEditScrollback(_) => "pane.edit_scrollback",
Method::PaneSelectionRead(_) => "pane.selection.read",
Method::PaneCopyMotion(_) => "pane.copy_motion",
diff --git a/src/app/api.rs b/src/app/api.rs
index 1270feff..0900a09d 100644
--- a/src/app/api.rs
+++ b/src/app/api.rs
@@ -1101,6 +1101,7 @@ impl App {
}
Method::PaneResize(params) => return self.handle_pane_resize(request.id, params),
Method::PaneScroll(params) => return self.handle_pane_scroll(request.id, params),
+ Method::PaneClear(target) => return self.handle_pane_clear(request.id, target),
Method::PaneEditScrollback(target) => {
return self.handle_pane_edit_scrollback(request.id, target);
}
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs
index a025f0b9..119d9298 100644
--- a/src/app/api/panes.rs
+++ b/src/app/api/panes.rs
@@ -167,6 +167,22 @@ impl App {
encode_success(id, ResponseResult::PaneInfo { pane })
}
+ pub(super) fn handle_pane_clear(&mut self, id: String, target: PaneTarget) -> String {
+ let Some((ws_idx, pane_id)) = self.parse_pane_id(&target.pane_id) else {
+ return pane_not_found(id, &target.pane_id);
+ };
+ let Some(runtime) =
+ self.state
+ .runtime_for_pane_in_workspace(&self.terminal_runtimes, ws_idx, pane_id)
+ else {
+ return pane_not_found(id, &target.pane_id);
+ };
+ match runtime.clear_screen() {
+ Ok(()) => encode_success(id, ResponseResult::Ok {}),
+ Err(err) => encode_error(id, "pane_clear_failed", err.to_string()),
+ }
+ }
+
pub(super) fn handle_pane_scroll(&mut self, id: String, params: PaneScrollParams) -> String {
let Some((ws_idx, pane_id)) = self.parse_pane_id(&params.pane_id) else {
return pane_not_found(id, &params.pane_id);
@@ -2350,6 +2366,26 @@ mod tests {
}
#[tokio::test]
+ async fn api_clear_pane_mutates_endpoint_owned_history() {
+ let (mut app, public_pane_id, pane_id) = app_with_scrollback_runtime();
+ let request = crate::api::schema::Request {
+ id: "clear".into(),
+ method: crate::api::schema::Method::PaneClear(PaneTarget {
+ pane_id: public_pane_id,
+ }),
+ };
+ assert!(crate::api::request_changes_ui(&request));
+ let response = app.handle_api_request(request);
+ let success: SuccessResponse = serde_json::from_str(&response).unwrap();
+ assert_eq!(success.result, ResponseResult::Ok {});
+ let runtime = app
+ .state
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .unwrap();
+ assert_eq!(runtime.scroll_metrics().unwrap().max_offset_from_bottom, 0);
+ }
+
+ #[tokio::test]
async fn api_pane_get_exposes_scroll_metrics() {
let (mut app, public_pane_id, pane_id) = app_with_scrollback_runtime();
let runtime = app
diff --git a/src/client/shell/actions.rs b/src/client/shell/actions.rs
index 12d8cc16..e3885626 100644
--- a/src/client/shell/actions.rs
+++ b/src/client/shell/actions.rs
@@ -1064,6 +1064,9 @@ impl ClientShellState {
pane_id: focused_pane,
mode: PaneZoomMode::Toggle,
})),
+ KeybindAction::ClearPane => Some(Method::PaneClear(PaneTarget {
+ pane_id: focused_pane?,
+ })),
KeybindAction::EditScrollback => Some(Method::PaneEditScrollback(PaneTarget {
pane_id: focused_pane?,
})),
diff --git a/src/client/shell/tests/popup_focus_projection.rs b/src/client/shell/tests/popup_focus_projection.rs
index 25d3b334..7f3ea137 100644
--- a/src/client/shell/tests/popup_focus_projection.rs
+++ b/src/client/shell/tests/popup_focus_projection.rs
@@ -885,6 +885,25 @@ fn pane_scrollbar_track_and_thumb_use_stable_endpoint_scroll_requests() {
}
#[test]
+fn clear_pane_binding_targets_the_focused_endpoint_pane() {
+ let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
+ state.set_snapshot(Box::new(snapshot()));
+ state.set_pane_surface(surface());
+ let mut input = ClientShellInput::default();
+ state.record_binding(
+ crate::input::KeybindMatch::Action(crate::input::KeybindAction::ClearPane),
+ &mut input,
+ );
+ assert!(input.requests.is_empty());
+ assert!(matches!(
+ &input.actions[..],
+ [ClientShellAction::Endpoint { request, .. }]
+ if matches!(&request.method, crate::api::schema::Method::PaneClear(target)
+ if target.pane_id == "pane_1")
+ ));
+}
+
+#[test]
fn edit_scrollback_binding_targets_the_focused_endpoint_pane() {
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
state.set_snapshot(Box::new(snapshot()));
diff --git a/src/config/keybinds.rs b/src/config/keybinds.rs
index bec1af66..5f4b46de 100644
--- a/src/config/keybinds.rs
+++ b/src/config/keybinds.rs
@@ -347,6 +347,7 @@ pub struct Keybinds {
pub close_tab: ActionKeybinds,
pub rename_pane: ActionKeybinds,
pub edit_scrollback: ActionKeybinds,
+ pub clear_pane: ActionKeybinds,
pub copy_mode: ActionKeybinds,
pub focus_pane_left: ActionKeybinds,
pub focus_pane_down: ActionKeybinds,
@@ -515,6 +516,7 @@ impl Config {
close_tab: empty_action!(),
rename_pane: empty_action!(),
edit_scrollback: empty_action!(),
+ clear_pane: empty_action!(),
copy_mode: empty_action!(),
focus_pane_left: empty_action!(),
focus_pane_down: empty_action!(),
@@ -662,6 +664,7 @@ impl Config {
apply_action!(keybinds.close_tab, close_tab, source);
apply_action!(keybinds.rename_pane, rename_pane, source);
apply_action!(keybinds.edit_scrollback, edit_scrollback, source);
+ apply_action!(keybinds.clear_pane, clear_pane, source);
apply_action!(keybinds.copy_mode, copy_mode, source);
apply_action!(keybinds.focus_pane_left, focus_pane_left, source);
apply_action!(keybinds.focus_pane_down, focus_pane_down, source);
diff --git a/src/config/model.rs b/src/config/model.rs
index 6ef6274d..930df026 100644
--- a/src/config/model.rs
+++ b/src/config/model.rs
@@ -405,6 +405,7 @@ pub struct KeysConfig {
pub rename_pane: BindingConfig,
/// Open the focused pane scrollback in $EDITOR. Default: "prefix+e".
pub edit_scrollback: BindingConfig,
+ pub clear_pane: BindingConfig,
/// Enter keyboard copy mode for the focused pane. Default: "prefix+[".
pub copy_mode: BindingConfig,
/// Focus the pane to the left. Default: "prefix+h".
@@ -536,6 +537,7 @@ pub(crate) struct KeysConfigOverlay {
rename_pane: Option<BindingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
edit_scrollback: Option<BindingConfig>,
+ clear_pane: Option<BindingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
copy_mode: Option<BindingConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -646,6 +648,7 @@ impl<'de> Deserialize<'de> for KeysConfig {
apply_field!(close_tab);
apply_field!(rename_pane);
apply_field!(edit_scrollback);
+ apply_field!(clear_pane);
apply_field!(copy_mode);
apply_field!(focus_pane_left);
apply_field!(focus_pane_down);
@@ -750,6 +753,7 @@ impl KeysConfig {
copy_effective_action_field!(close_tab, keybinds.close_tab);
copy_effective_action_field!(rename_pane, keybinds.rename_pane);
copy_effective_action_field!(edit_scrollback, keybinds.edit_scrollback);
+ copy_effective_action_field!(clear_pane, keybinds.clear_pane);
copy_effective_action_field!(copy_mode, keybinds.copy_mode);
copy_effective_action_field!(focus_pane_left, keybinds.focus_pane_left);
copy_effective_action_field!(focus_pane_down, keybinds.focus_pane_down);
@@ -1118,6 +1122,7 @@ impl Default for KeysConfig {
close_tab: BindingConfig::one("prefix+shift+x"),
rename_pane: BindingConfig::one("prefix+shift+p"),
edit_scrollback: BindingConfig::one("prefix+e"),
+ clear_pane: BindingConfig::default(),
copy_mode: BindingConfig::one("prefix+["),
focus_pane_left: BindingConfig::one("prefix+h"),
focus_pane_down: BindingConfig::one("prefix+j"),
diff --git a/src/ghostty/bindings.rs b/src/ghostty/bindings.rs
index bfa71825..ceb2d699 100644
--- a/src/ghostty/bindings.rs
+++ b/src/ghostty/bindings.rs
@@ -3237,6 +3237,10 @@ unsafe extern "C" {
pub fn ghostty_terminal_reset(terminal: GhosttyTerminal);
}
unsafe extern "C" {
+ #[doc = " Clear screen and history, retaining the cursor's soft-wrapped active line.\n Does not alter the VT parser or write to the child process. Returns false\n without changing the terminal on the alternate screen or for a NULL handle.\n Otherwise returns true and moves the retained line to the top of the screen."]
+ pub fn ghostty_terminal_clear_screen(terminal: GhosttyTerminal) -> bool;
+}
+unsafe extern "C" {
#[doc = " Resize the terminal to the given dimensions.\n\n Changes the number of columns and rows in the terminal. The primary\n screen will reflow content if wraparound mode is enabled; the alternate\n screen does not reflow. If the dimensions are unchanged, this is a no-op.\n\n This also updates the terminal's pixel dimensions (used for image\n protocols and size reports), disables synchronized output mode (allowed\n by the spec so that resize results are shown immediately), and sends an\n in-band size report if mode 2048 is enabled.\n\n @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)\n @param cols New width in cells (must be greater than zero)\n @param rows New height in cells (must be greater than zero)\n @param cell_width_px Width of a single cell in pixels\n @param cell_height_px Height of a single cell in pixels\n @return GHOSTTY_SUCCESS on success, or an error code on failure\n\n @ingroup terminal"]
pub fn ghostty_terminal_resize(
terminal: GhosttyTerminal,
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index d1748aad..82714118 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -1619,6 +1619,10 @@ impl Terminal {
Ok(text)
}
+ pub fn clear_screen(&mut self) -> bool {
+ unsafe { ffi::ghostty_terminal_clear_screen(self.raw) }
+ }
+
pub fn scroll_viewport_bottom(&mut self) {
let viewport = ffi::GhosttyTerminalScrollViewport {
tag: ffi::GhosttyTerminalScrollViewportTag_GHOSTTY_SCROLL_VIEWPORT_BOTTOM,
diff --git a/src/input/keybind_help.rs b/src/input/keybind_help.rs
index e9462b9d..2f89b861 100644
--- a/src/input/keybind_help.rs
+++ b/src/input/keybind_help.rs
@@ -160,6 +160,7 @@ pub(crate) fn keybind_help_groups(
entry(binding_label(&keybinds.close_pane), "close pane"),
entry(binding_label(&keybinds.rename_pane), "rename pane"),
entry(binding_label(&keybinds.edit_scrollback), "edit scrollback"),
+ entry(binding_label(&keybinds.clear_pane), "clear pane"),
entry(binding_label(&keybinds.copy_mode), "copy mode"),
entry(binding_label(&keybinds.zoom), "zoom pane"),
entry(binding_label(&keybinds.resize_mode), "resize mode"),
diff --git a/src/input/keybindings.rs b/src/input/keybindings.rs
index d56de80c..9ffce2ee 100644
--- a/src/input/keybindings.rs
+++ b/src/input/keybindings.rs
@@ -52,6 +52,7 @@ pub(crate) enum KeybindAction {
SplitHorizontal,
ClosePane,
EditScrollback,
+ ClearPane,
CopyMode,
Zoom,
EnterResizeMode,
@@ -120,6 +121,7 @@ pub(crate) fn resolve_non_indexed_action(
(&keybinds.close_tab, KeybindAction::CloseTab),
(&keybinds.rename_pane, KeybindAction::RenamePane),
(&keybinds.edit_scrollback, KeybindAction::EditScrollback),
+ (&keybinds.clear_pane, KeybindAction::ClearPane),
(&keybinds.copy_mode, KeybindAction::CopyMode),
(&keybinds.focus_pane_left, KeybindAction::FocusPaneLeft),
(&keybinds.focus_pane_down, KeybindAction::FocusPaneDown),
@@ -259,6 +261,40 @@ mod tests {
use super::*;
#[test]
+ fn clear_pane_is_unbound_by_default_and_configurable() {
+ assert!(crate::config::Config::default()
+ .keybinds()
+ .clear_pane
+ .bindings
+ .is_empty());
+ let config: crate::config::Config =
+ toml::from_str("[keys]\nclear_pane = [\"super+k\", \"prefix+ctrl+k\"]").unwrap();
+ assert!(config.collect_diagnostics().is_empty());
+ let keybinds = config.keybinds();
+ assert!(matches!(
+ resolve_direct_binding(
+ &keybinds,
+ &TerminalKey::new(KeyCode::Char('k'), KeyModifiers::SUPER)
+ ),
+ Some(KeybindMatch::Action(KeybindAction::ClearPane))
+ ));
+ assert!(matches!(
+ resolve_prefix_binding(
+ &keybinds,
+ &TerminalKey::new(KeyCode::Char('k'), KeyModifiers::CONTROL)
+ ),
+ Some(KeybindMatch::Action(KeybindAction::ClearPane))
+ ));
+ assert!(matches!(
+ resolve_prefix_binding(
+ &keybinds,
+ &TerminalKey::new(KeyCode::Char('k'), KeyModifiers::SHIFT)
+ ),
+ Some(KeybindMatch::Action(KeybindAction::SwapPaneUp))
+ ));
+ }
+
+ #[test]
fn one_shared_resolver_handles_direct_prefix_and_indexed_bindings() {
let keybinds = Keybinds {
next_tab: crate::config::ActionKeybinds::direct("ctrl+n"),
diff --git a/src/main.rs b/src/main.rs
index 8cfe9b0b..5d7582cc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -172,6 +172,7 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# close_tab = "prefix+shift+x"
# rename_pane = "prefix+shift+p"
# edit_scrollback = "prefix+e"
+# clear_pane = "" # unbound; e.g. "prefix+ctrl+k"
# focus_pane_left = "prefix+h"
# focus_pane_down = "prefix+j"
# focus_pane_up = "prefix+k"
diff --git a/src/pane.rs b/src/pane.rs
index 29cbad2a..3e1c52a4 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -3081,6 +3081,20 @@ impl PaneRuntime {
self.compression.wake();
}
+ pub fn clear_screen(&self) -> Result<(), String> {
+ let guard = match self.content_write_lock.lock() {
+ Ok(guard) => guard,
+ Err(poisoned) => poisoned.into_inner(),
+ };
+ self.content_seq.fetch_add(1, Ordering::AcqRel);
+ let result = self.terminal.clear_screen();
+ self.content_seq.fetch_add(1, Ordering::Release);
+ drop(guard);
+ self.compression.wake();
+ mark_detection_content_changed(&self.detection_content_seq);
+ result
+ }
+
/// Reset scroll to live view (offset = 0).
pub fn scroll_reset(&self) {
self.terminal.scroll_reset();
@@ -3669,6 +3683,56 @@ mod tests {
use super::*;
#[tokio::test]
+ async fn clear_pane_preserves_wrapped_input_and_unfinished_vt_sequence() {
+ let (runtime, mut rx) = PaneRuntime::test_with_channel_and_scrollback_bytes(
+ 10,
+ 5,
+ 100_000,
+ b"old\r\nold\r\nold\r\nold\r\nold\r\n\x1b[32m$ abcdefghijklmnop\x1b[1A\x1b[4G\x1b[",
+ 4,
+ );
+ let before = runtime.content_seq();
+ runtime.scroll_up(1);
+ runtime.clear_screen().unwrap();
+ let snapshot = runtime.collect_dirty_patch_snapshot(10, 5).unwrap();
+ assert!(snapshot.content_revision > before);
+ assert!(!matches!(snapshot.patch, TerminalDirtyPatchOutcome::Clean));
+ let metrics = runtime.scroll_metrics().unwrap();
+ assert_eq!(metrics.max_offset_from_bottom, 0);
+ assert_eq!(metrics.offset_from_bottom, 0);
+ let text = runtime.recent_unwrapped_text_snapshot(100).text;
+ assert!(text.contains("$ abcdefghijklmnop"), "{text:?}");
+ assert!(!text.contains("old"), "{text:?}");
+ runtime.test_process_pty_bytes(b"5 q");
+ assert!(!runtime.visible_text().contains("5 q"));
+ assert!(rx.try_recv().is_err(), "clear must not send child input");
+ }
+
+ #[tokio::test]
+ async fn clear_pane_preserves_alternate_screen_and_primary_history() {
+ let runtime = PaneRuntime::test_with_scrollback_bytes(
+ 20,
+ 4,
+ 100_000,
+ b"one\r\ntwo\r\nthree\r\nfour\r\nfive\x1b[?1049halt app",
+ );
+ let before = runtime.visible_text();
+ runtime.clear_screen().unwrap();
+ assert_eq!(runtime.visible_text(), before);
+ runtime.test_process_pty_bytes(b"\x1b[?1049l");
+ assert!(runtime
+ .recent_unwrapped_text_snapshot(100)
+ .text
+ .contains("one"));
+ runtime.clear_screen().unwrap();
+ assert!(!runtime
+ .recent_unwrapped_text_snapshot(100)
+ .text
+ .contains("one"));
+ assert!(runtime.visible_text().contains("five"));
+ }
+
+ #[tokio::test]
async fn dirty_patch_snapshot_keeps_clean_metadata_and_terminal_fallback() {
let (runtime, _rx) = PaneRuntime::test_with_channel(20, 4);
runtime
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs
index 4a6bc173..4d7f07b1 100644
--- a/src/pane/terminal.rs
+++ b/src/pane/terminal.rs
@@ -258,6 +258,10 @@ impl PaneTerminal {
self.ghostty.scroll_reset();
}
+ pub fn clear_screen(&self) -> Result<(), String> {
+ self.ghostty.clear_screen()
+ }
+
pub fn set_scroll_offset_from_bottom(&self, lines: usize) {
self.ghostty.set_scroll_offset_from_bottom(lines);
}
@@ -1773,6 +1777,21 @@ impl GhosttyPaneTerminal {
}
}
+ pub fn clear_screen(&self) -> Result<(), String> {
+ let mut core = self
+ .core
+ .lock()
+ .map_err(|_| "terminal lock poisoned".to_owned())?;
+ if core.terminal.clear_screen() {
+ #[cfg(windows)]
+ {
+ core.recent_fallback = windows_recent_fallback::Cache::default();
+ windows_recent_fallback::update(&mut core);
+ }
+ }
+ Ok(())
+ }
+
pub fn set_scroll_offset_from_bottom(&self, lines: usize) {
if let Ok(mut core) = self.core.lock() {
#[cfg(windows)]
diff --git a/src/server/client_commands.rs b/src/server/client_commands.rs
index 4494f053..87824c85 100644
--- a/src/server/client_commands.rs
+++ b/src/server/client_commands.rs
@@ -18,6 +18,7 @@ const CLIENT_SHELL_METHODS: &[&str] = &[
"integration.install",
"integration.list",
"layout.set_split_ratio",
+ "pane.clear",
"pane.close",
"pane.copy_motion",
"pane.copy_search",
@@ -287,7 +288,11 @@ mod tests {
)))
.expect("endpoint method shape fixture");
let mut actual = endpoint_method_shape_digests();
- // Freeze the additive method separately without rewriting the published fixture.
+ // Freeze additive methods separately without rewriting the published fixture.
+ assert_eq!(
+ actual.remove("pane.clear").as_deref(),
+ Some("0301d288ba198ddaa427dd7421c71911cccaf4ea03544531efa8b67ca21b08f6")
+ );
assert_eq!(
actual.remove("pane.link.resolve").as_deref(),
Some("f5e4a3e01453ae7b188f127ce951c12c20e0bebcc17cc364eeb6d1a01fd5bf81")
diff --git a/src/server/headless/client_views.rs b/src/server/headless/client_views.rs
index 4e0bd2e2..62fd04e4 100644
--- a/src/server/headless/client_views.rs
+++ b/src/server/headless/client_views.rs
@@ -262,6 +262,7 @@ impl HeadlessServer {
| Method::PaneRename(_)
| Method::PaneResize(_)
| Method::PaneScroll(_)
+ | Method::PaneClear(_)
| Method::PaneSplit(_)
| Method::PaneSwap(_)
| Method::PaneZoom(_)
diff --git a/src/terminal/runtime.rs b/src/terminal/runtime.rs
index 46795791..a10bec78 100644
--- a/src/terminal/runtime.rs
+++ b/src/terminal/runtime.rs
@@ -270,6 +270,10 @@ impl TerminalRuntime {
self.0.scroll_reset();
}
+ pub fn clear_screen(&self) -> Result<(), String> {
+ self.0.clear_screen()
+ }
+
pub fn set_scroll_offset_from_bottom(&self, lines: usize) {
self.0.set_scroll_offset_from_bottom(lines);
}