T22: the macOS menu bar holds the PRD's menus, each item showing its key; Quit, Select All, Bigger, Smaller, Reset Size and Minimize take Ghostty's keys, Ctrl-Shift off macOS where Ctrl with a letter types a control byte; the font's size moves for every session, unsaved; Select All runs from the first character in the history to the last, as Ghostty's; shot lists the menus and says whether the app was active at each key
CI / platforms (windows-latest) (push) Failing after 8s
CI / macos (push) Failing after 8m29s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-15 01:51:46 -04:00
1 parent 149c1b5f02
commit 5ee67bd406
15 files changed
+770 -495

No files matched your search

+11 -5
View File
@@ -11,7 +11,7 @@ use super::{
view::TerminalView,
};
actions!(elyterminal, [Copy, Paste, Find, FindNewer]);
actions!(elyterminal, [Copy, Paste, Find, FindNewer, SelectAll]);
/// The key context a terminal's root sets.
pub const CONTEXT: &str = "Terminal";
@@ -19,16 +19,22 @@ pub const CONTEXT: &str = "Terminal";
/// The key context of a terminal's find box.
pub const FIND: &str = "TerminalFind";
/// Copy, paste and find; Ely's focus keys go to the terminal's listeners instead.
/// Copy, paste, select all and find; Ely's focus keys go to the terminal's listeners instead.
pub fn bind_keys(cx: &mut App) {
let (copy, paste, find) = if cfg!(target_os = "macos") {
("cmd-c", "cmd-v", "cmd-f")
let (copy, paste, all, find) = if cfg!(target_os = "macos") {
("cmd-c", "cmd-v", "cmd-a", "cmd-f")
} else {
("ctrl-shift-c", "ctrl-shift-v", "ctrl-shift-f")
(
"ctrl-shift-c",
"ctrl-shift-v",
"ctrl-shift-a",
"ctrl-shift-f",
)
};
cx.bind_keys([
KeyBinding::new(copy, Copy, Some(CONTEXT)),
KeyBinding::new(paste, Paste, Some(CONTEXT)),
KeyBinding::new(all, SelectAll, Some(CONTEXT)),
KeyBinding::new(find, Find, Some(CONTEXT)),
KeyBinding::new(
"shift-enter",
+1
View File
@@ -16,6 +16,7 @@ mod tap;
#[cfg(unix)]
mod unix;
mod view;
pub mod zoom;
pub use pty::{Events, Launch, Listener, Pty, PtyEvent, config, fit, spawn};
pub use view::{BLINK, ICONS, MONO, TerminalEvent, TerminalView, load_font};
+57 -2
View File
@@ -1,8 +1,8 @@
use alacritty_terminal::{
grid::Scroll,
grid::{Dimensions, Scroll},
index::{Column, Line, Point as Cell, Side},
selection::{Selection, SelectionType},
term::TermMode,
term::{Term, TermMode},
};
use gpui::{
ClipboardItem, Context, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
@@ -298,6 +298,25 @@ impl TerminalView {
(!text.is_empty()).then_some(text)
}
/// Selects the history and the screen, blanks around them left out; copy on select takes the text.
pub(super) fn select_all(&mut self, cx: &mut Context<Self>) {
{
let mut term = self.pty.term.lock();
let Some(all) = everything(&*term) else {
log::debug!("terminal {}: nothing to select", self.pty.pid);
return;
};
term.selection = Some(all);
}
let selected = self.selected();
let count = selected.as_ref().map_or(0, |text| text.chars().count());
log::debug!("terminal {}: all {count} characters selected", self.pty.pid);
if let Some(text) = selected.filter(|_| SettingsStore::get(cx).copy_on_select) {
self.copy_text(text, true, cx);
}
cx.notify();
}
/// The selection to the clipboard.
pub(super) fn copy(&mut self, cx: &mut Context<Self>) {
match self.selected() {
@@ -347,3 +366,39 @@ impl TerminalView {
self.input(keys::paste(&text, bracketed), cx);
}
}
/// Ghostty's select all: the first cell holding more than a space or tab, through history and screen, to the last.
fn everything<T>(term: &Term<T>) -> Option<Selection> {
let grid = term.grid();
let cells = (grid.topmost_line().0..=grid.bottommost_line().0).flat_map(|line| {
(0..grid.columns()).map(move |column| Cell::new(Line(line), Column(column)))
});
let inked = |cell: &Cell| !matches!(grid[*cell].c, ' ' | '\t');
let first = cells.clone().find(inked)?;
let last = cells.rev().find(inked)?;
let mut all = Selection::new(SelectionType::Simple, first, Side::Left);
all.update(last, Side::Right);
Some(all)
}
#[cfg(test)]
mod tests {
use alacritty_terminal::{
event::VoidListener,
term::{Config, test::TermSize},
vte::ansi::Processor,
};
use super::*;
#[test]
fn everything_runs_from_the_first_character_in_the_history_to_the_last() {
let mut term = Term::new(Config::default(), &TermSize::new(10, 3), VoidListener);
assert!(everything(&term).is_none(), "a blank grid selects nothing");
let mut parser: Processor = Processor::new();
parser.advance(&mut term, b"\r\n one\r\n\r\ntwo six\t\r\n\r\n");
term.selection = everything(&term);
let text = term.selection_to_string().expect("a selection");
assert_eq!(text, "one\n\ntwo six");
}
}
+9 -5
View File
@@ -18,11 +18,12 @@ use super::{
find::Finder,
frame::frame,
ink::Ink,
input::{self, Copy, Find, Paste},
input::{self, Copy, Find, Paste, SelectAll},
mouse::MouseMode,
paint::{self, Look},
pointer::Pointer,
pty::{Events, Pty, PtyEvent, config, fit},
zoom,
};
use crate::{
settings::{Settings, SettingsStore},
@@ -71,6 +72,7 @@ pub struct TerminalView {
_blink: Task<()>,
_focus: [Subscription; 3],
_settings: Subscription,
_zoom: Subscription,
}
impl EventEmitter<TerminalEvent> for TerminalView {}
@@ -120,6 +122,7 @@ impl TerminalView {
view.search(false, cx);
cx.notify();
}),
_zoom: cx.observe_global::<zoom::Enlarged>(|_, cx| cx.notify()),
}
}
@@ -266,8 +269,8 @@ pub fn load_font(font: &Font, system: &TextSystem) -> anyhow::Result<FontId> {
Ok(id)
}
/// The font, cell and metrics the settings give at this window's scale.
fn look(settings: &Settings, ink: &Ink, window: &Window) -> anyhow::Result<Look> {
/// The font, cell and metrics the settings give at `points` and this window's scale.
fn look(settings: &Settings, points: f32, ink: &Ink, window: &Window) -> anyhow::Result<Look> {
let font = Font {
family: settings.font_family.clone().into(),
features: if settings.ligatures {
@@ -279,7 +282,7 @@ fn look(settings: &Settings, ink: &Ink, window: &Window) -> anyhow::Result<Look>
weight: FontWeight::NORMAL,
style: FontStyle::Normal,
};
let text = px(settings.font_size);
let text = px(points);
let system = window.text_system();
let id = load_font(&font, system)?;
let advance = system
@@ -349,6 +352,7 @@ impl Render for TerminalView {
.on_key_up(cx.listener(Self::key_up))
.on_action(cx.listener(|view, _: &Copy, _, cx| view.copy(cx)))
.on_action(cx.listener(|view, _: &Paste, _, cx| view.paste(cx)))
.on_action(cx.listener(|view, _: &SelectAll, _, cx| view.select_all(cx)))
.on_action(cx.listener(|view, _: &Find, window, cx| view.find(window, cx)))
.on_any_mouse_down(cx.listener(Self::mouse_down))
.on_mouse_move(cx.listener(Self::mouse_move))
@@ -367,7 +371,7 @@ impl Render for TerminalView {
root.on_mouse_up(button, cx.listener(Self::mouse_up))
.on_mouse_up_out(button, cx.listener(Self::mouse_up))
});
let root = match look(&settings, &ink, window) {
let root = match look(&settings, zoom::size(cx), &ink, window) {
Ok(look) => {
self.cell = (
f32::from(look.cell.width).round() as u16,
+54
View File
@@ -0,0 +1,54 @@
use gpui::{App, Global};
use crate::settings::{FONT_SIZE, SettingsStore};
/// Points the terminal's font stands above the Size setting, below it when negative; Bigger and Smaller move it, nothing saves it.
#[derive(Default)]
pub struct Enlarged(f32);
impl Global for Enlarged {}
/// The size the terminal draws its font at: the setting, moved, inside the range.
pub fn size(cx: &App) -> f32 {
shown(SettingsStore::get(cx).font_size, cx.global::<Enlarged>().0)
}
/// Moves the font `by` points, or back to the setting when `by` is none.
pub fn step(by: Option<f32>, cx: &mut App) {
let setting = SettingsStore::get(cx).font_size;
let enlarged = by.map_or(0.0, |by| moved(setting, cx.global::<Enlarged>().0, by));
cx.set_global(Enlarged(enlarged));
log::info!(
"zoom: the terminal's font is {} points, {enlarged:+} on the setting",
shown(setting, enlarged)
);
}
fn shown(setting: f32, enlarged: f32) -> f32 {
(setting + enlarged).clamp(*FONT_SIZE.start(), *FONT_SIZE.end())
}
/// What `enlarged` becomes once the font moves `by` points from where it shows.
fn moved(setting: f32, enlarged: f32, by: f32) -> f32 {
shown(shown(setting, enlarged) + by, 0.0) - setting
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_step_moves_the_font_inside_the_range() {
assert_eq!(moved(13.0, 0.0, 1.0), 1.0);
assert_eq!(moved(13.0, 2.0, -1.0), 1.0);
assert_eq!(moved(71.0, 0.0, 1.0), 1.0);
assert_eq!(moved(72.0, 0.0, 1.0), 0.0, "the top holds");
assert_eq!(moved(6.0, 0.0, -1.0), 0.0, "the bottom holds");
assert_eq!(
shown(70.0, 5.0),
72.0,
"a larger setting under a zoom stays in range"
);
assert_eq!(moved(70.0, 5.0, -1.0), 1.0, "a step starts from what shows");
}
}