From dea4abacaa14e8ad628ff81ce9ab7946860bd315 Mon Sep 17 00:00:00 2001 From: Tom Grushka Date: Sun, 17 May 2026 04:16:11 -0600 Subject: [PATCH] feat: add clear buffer action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new action that clears both the visible screen and scrollback buffer while preserving any wrapped lines above the cursor. Default key-binding: Ctrl+Alt+K. I used AI to guide the walk‑back logic and research `WRAPLINE` flag placement. All final code, design decisions, and testing are mine. --- i18n/en/cosmic_term.ftl | 1 + src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++++- src/menu.rs | 2 ++ src/shortcuts.rs | 7 ++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/i18n/en/cosmic_term.ftl b/i18n/en/cosmic_term.ftl index e2e459f9..3d917b75 100644 --- a/i18n/en/cosmic_term.ftl +++ b/i18n/en/cosmic_term.ftl @@ -117,6 +117,7 @@ copy = Copy paste = Paste select-all = Select all find = Find +clear-buffer = Clear buffer clear-scrollback = Clear scrollback ## Open diff --git a/src/main.rs b/src/main.rs index 231e9675..9492754a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,13 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use alacritty_terminal::{event::Event as TermEvent, term, term::color::Colors as TermColors, tty}; +use alacritty_terminal::{ + event::Event as TermEvent, + grid::{Dimensions, GridCell}, + index::Line, + term::{self, color::Colors as TermColors}, + tty, +}; use cosmic::iced::clipboard::dnd::DndAction; use cosmic::iced::core::keyboard::key::Named; use cosmic::widget::menu::action::MenuAction; @@ -238,6 +244,7 @@ pub struct Flags { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { About, + ClearBuffer, ClearScrollback, ColorSchemes(ColorSchemeKind), Copy, @@ -289,6 +296,7 @@ impl Action { fn message(&self, entity_opt: Option) -> Message { match self { Self::About => Message::ToggleContextPage(ContextPage::About), + Self::ClearBuffer => Message::ClearBuffer(entity_opt), Self::ClearScrollback => Message::ClearScrollback(entity_opt), Self::ColorSchemes(color_scheme_kind) => { Message::ToggleContextPage(ContextPage::ColorSchemes(*color_scheme_kind)) @@ -352,6 +360,7 @@ impl MenuAction for Action { #[derive(Clone, Debug)] pub enum Message { AppTheme(AppTheme), + ClearBuffer(Option), ClearScrollback(Option), ColorSchemeCollapse, ColorSchemeDelete(ColorSchemeKind, ColorSchemeId), @@ -1960,6 +1969,38 @@ impl Application for App { config_set!(app_theme, app_theme); return self.update_config(); } + Message::ClearBuffer(entity_opt) => { + if let Some(tab_model) = self.pane_model.active() { + let entity = entity_opt.unwrap_or_else(|| tab_model.active()); + if let Some(terminal) = tab_model.data::>(entity) { + let mut terminal = terminal.lock().unwrap(); + let mut term = terminal.term.lock(); + let grid = term.grid_mut(); + let point = grid.cursor.point; + let mut line = point.line; + // Include any wrapped lines above the cursor. + while line > grid.topmost_line() + && grid[line - Line(1)] + .last() + .is_some_and(|c| c.flags().contains(term::cell::Flags::WRAPLINE)) + { + line -= 1; + } + let saved_rows: Vec<_> = (line.0..=grid.cursor.point.line.0) + .map(|i| grid[Line(i)].clone()) + .collect(); + grid.reset(); + let new_line = saved_rows.len() - 1; + for (i, row) in saved_rows.into_iter().enumerate() { + grid[Line(i as i32)] = row; + } + grid.cursor.point.line = Line(new_line as i32); + grid.cursor.point.column = point.column; + drop(term); + terminal.needs_update = true; + } + } + } Message::ClearScrollback(entity_opt) => { if let Some(tab_model) = self.pane_model.active() { let entity = entity_opt.unwrap_or_else(|| tab_model.active()); diff --git a/src/menu.rs b/src/menu.rs index a10ca759..d50a7029 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -81,6 +81,7 @@ pub fn context_menu<'a>( Element::from(menu_item(fl!("paste"), Action::Paste)), Element::from(menu_item(fl!("select-all"), Action::SelectAll)), Element::from(divider::horizontal::light()), + Element::from(menu_item(fl!("clear-buffer"), Action::ClearBuffer)), Element::from(menu_item(fl!("clear-scrollback"), Action::ClearScrollback)), Element::from(divider::horizontal::light()), Element::from(menu_item( @@ -236,6 +237,7 @@ pub fn menu_bar<'a>( MenuItem::Button(fl!("paste"), None, Action::Paste), MenuItem::Button(fl!("select-all"), None, Action::SelectAll), MenuItem::Divider, + MenuItem::Button(fl!("clear-buffer"), None, Action::ClearBuffer), MenuItem::Button(fl!("clear-scrollback"), None, Action::ClearScrollback), MenuItem::Divider, MenuItem::Button(fl!("find"), None, Action::Find), diff --git a/src/shortcuts.rs b/src/shortcuts.rs index 503c8de5..f356a22c 100644 --- a/src/shortcuts.rs +++ b/src/shortcuts.rs @@ -57,6 +57,7 @@ impl Binding { #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum KeyBindAction { Disable, + ClearBuffer, ClearScrollback, Copy, CopyOrSigint, @@ -99,6 +100,7 @@ impl KeyBindAction { fn to_action(self) -> Option { match self { Self::Disable => None, + Self::ClearBuffer => Some(Action::ClearBuffer), Self::ClearScrollback => Some(Action::ClearScrollback), Self::Copy => Some(Action::Copy), Self::CopyOrSigint => Some(Action::CopyOrSigint), @@ -259,6 +261,7 @@ impl ShortcutsConfig { pub fn action_label(action: KeyBindAction) -> String { match action { KeyBindAction::Disable => fl!("disable"), + KeyBindAction::ClearBuffer => fl!("clear-buffer"), KeyBindAction::ClearScrollback => fl!("clear-scrollback"), KeyBindAction::Copy => fl!("copy"), KeyBindAction::CopyOrSigint => fl!("copy-or-sigint"), @@ -362,7 +365,7 @@ pub fn shortcut_groups() -> Vec { KeyBindAction::ZoomReset, ], }); - let mut other_actions = vec![KeyBindAction::ClearScrollback]; + let mut other_actions = vec![KeyBindAction::ClearBuffer, KeyBindAction::ClearScrollback]; #[cfg(feature = "password_manager")] other_actions.push(KeyBindAction::PasswordManager); groups.push(ShortcutGroup { @@ -497,6 +500,8 @@ fn fallback_shortcuts() -> Shortcuts { bind!([Ctrl, Shift], "ArrowRight", PaneFocusRight); bind!([Ctrl, Shift], "L", PaneFocusRight); + // CTRL+Alt+K clears the screen and entire buffer. + bind!([Ctrl, Alt], "K", ClearBuffer); // CTRL+Alt+L clears the scrollback. bind!([Ctrl, Alt], "L", ClearScrollback);