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
This commit is contained in:
15 files changed
+770
-495
No files matched your search
+11
-5
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user