feat(settings): add bell, notify-threshold, mouse-reporting, and session-restore controls (#68)

Expose four terminal preferences in Settings that previously had no knob (or
were hardcoded):

- Terminal → Bell: Off / Visual / Audible. Audible rings the system bell
  (NSBeep on macOS), falling back to the visual flash where no system bell
  exists so an opted-in bell is never silent.
- Terminal → Notifications: configurable "long command" threshold
  (5s/10s/30s/1m), replacing the hardcoded 10s floor.
- Terminal → Mouse: "Report mouse to apps" toggle. Off keeps the mouse local
  (native selection + scrollback) regardless of what a full-screen app
  requests; Shift still bypasses per gesture. Cached per view and pushed on
  config hot-reload.
- Window & Tabs: "Restore previous session" toggle. When off, the daemon is
  restarted on launch so the previous session's shells are hung up instead of
  left running orphaned (this launch never re-attaches to them).

Config gains a BellMode enum plus bell / notify_threshold_secs /
mouse_reporting / restore_session fields, each defaulting to the prior
behavior.

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
This commit is contained in:
l0ng-ai
2026-07-13 20:06:53 +08:00
committed by GitHub
co-authored by l0ng-ai
parent 7606c19a51
commit 61da15dc1b
6 changed files with 361 additions and 47 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ winresource = "0.1"
# so the native traffic-light buttons render in the right light/dark style.
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSResponder", "NSAppearance"] }
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSResponder", "NSAppearance", "NSGraphics"] }
# x11/wayland are the Linux windowing backends; only pull them on Linux. The
# Windows backend (`gpui_windows`) and macOS backend are selected by gpui_platform
+107
View File
@@ -91,6 +91,22 @@ pub struct Config {
/// self-updates — it only links to the Releases page. On by default; set to
/// `false` to skip the network call entirely (offline / privacy).
pub check_for_updates: bool,
/// Seconds a foreground command must run before it's eligible for a
/// "command finished" notification (further gated by
/// `notify_on_command_finish`). Defaults to 10; clamped in `sanitize` so a
/// hand-edit can't set a degenerate value.
#[serde(default = "default_notify_threshold_secs")]
pub notify_threshold_secs: u64,
/// Restore the previous session (tab/split layout + each pane's cwd) on
/// launch. On by default; when off, every launch starts with a single fresh
/// terminal instead of the last window's layout. The session is still saved
/// on quit — it's just ignored at startup.
#[serde(default = "default_true")]
pub restore_session: bool,
/// How the terminal bell (BEL / `^G`) is signalled. Defaults to a brief
/// visual flash (the current behavior).
#[serde(default, deserialize_with = "de_lenient")]
pub bell: BellMode,
// ── Appearance ──────────────────────────────────────────────────────────
/// The shape drawn for the terminal cursor.
@@ -114,6 +130,13 @@ pub struct Config {
/// Multiplier applied to mouse-wheel scroll distance. 1.0 = one row per wheel
/// line (the raw amount). Clamped to a sane band in `sanitize`.
pub mouse_scroll_multiplier: f32,
/// Report mouse events (click / drag / wheel) to full-screen apps that ask
/// for them (vim, tmux, htop). On by default. When off, the mouse always
/// stays local — native selection and scrollback — regardless of what the
/// app requested. Holding Shift already forces local behavior for a single
/// gesture even while this is on.
#[serde(default = "default_true")]
pub mouse_reporting: bool,
/// Drop trailing whitespace from each copied line. Off by default.
pub clipboard_trim_trailing_spaces: bool,
/// Copy a mouse selection to the clipboard as soon as the gesture ends,
@@ -215,6 +238,20 @@ pub enum NotifyMode {
Always,
}
/// How the terminal bell (BEL / `^G`) is signalled (see [`Config::bell`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum BellMode {
/// Ignore the bell entirely — no flash, no sound.
None,
/// A brief visual flash of the terminal (the current behavior).
#[default]
Visual,
/// Ring the system bell. On platforms without one, falls back to a flash so
/// an opted-in bell is never silent.
Audible,
}
/// A shell program plus its launch arguments. Mirrors `alacritty_terminal`'s
/// `tty::Shell`, but lives here so config has no dependency on the PTY crate and
/// the daemon can read it straight from `config.json`.
@@ -276,6 +313,11 @@ impl Default for Config {
// Opt-out, not opt-in: a stale terminal that never tells you it's
// outdated is the status quo we're fixing. One cheap GET at startup.
check_for_updates: true,
notify_threshold_secs: default_notify_threshold_secs(),
restore_session: true,
// Visual flash preserves the pre-config behavior (the bell always
// flashed); opting into None/Audible is a deliberate change.
bell: BellMode::Visual,
cursor_style: CursorStyle::Block,
// Input/mouse defaults preserve today's behavior: Option composes
// characters as macOS ships it (opt into Option-as-Meta); GPUI
@@ -286,6 +328,7 @@ impl Default for Config {
mouse_hide_while_typing: true,
focus_follows_mouse: false,
mouse_scroll_multiplier: 1.0,
mouse_reporting: true,
clipboard_trim_trailing_spaces: false,
copy_on_select: false,
startup_mode: StartupMode::Normal,
@@ -344,6 +387,10 @@ impl Config {
self.mouse_scroll_multiplier = Config::default().mouse_scroll_multiplier;
}
self.mouse_scroll_multiplier = self.mouse_scroll_multiplier.clamp(0.1, 10.0);
// Keep the notify threshold in a usable band: a 1s floor so it can't fire
// on every trivial command, and a 1-hour ceiling above which "long
// command" stops meaning anything.
self.notify_threshold_secs = self.notify_threshold_secs.clamp(1, 3600);
}
/// Write the current config back to disk, creating the parent directory if
@@ -509,6 +556,19 @@ fn default_preset() -> String {
"default".to_string()
}
/// Serde default for the several `bool` fields that default to `true` (so a
/// config predating them, or one omitting them, keeps the on-by-default
/// behavior instead of deserializing to `false`).
fn default_true() -> bool {
true
}
/// Serde default for [`Config::notify_threshold_secs`]: the 10-second floor a
/// command had to cross before this was configurable.
fn default_notify_threshold_secs() -> u64 {
10
}
/// Serde default for [`Config::prefix`]: tmux's classic `C-b`.
fn default_prefix() -> String {
"ctrl-b".to_string()
@@ -709,6 +769,53 @@ mod tests {
assert_eq!(clamp(usize::MAX), MAX_SCROLLBACK); // ceiling
}
#[test]
fn new_terminal_prefs_default_and_parse_leniently() {
// Defaults preserve the pre-config behavior: restore on, mouse reporting
// on, a 10s notify floor, and a visual bell.
let cfg = Config::default();
assert!(cfg.restore_session);
assert!(cfg.mouse_reporting);
assert_eq!(cfg.notify_threshold_secs, 10);
assert_eq!(cfg.bell, BellMode::Visual);
// A config predating these fields keeps the on-by-default booleans (not
// `false`) and the 10s floor.
let cfg: Config = serde_json::from_str(r#"{"font_size": 15.0}"#).unwrap();
assert!(cfg.restore_session);
assert!(cfg.mouse_reporting);
assert_eq!(cfg.notify_threshold_secs, 10);
assert_eq!(cfg.bell, BellMode::Visual);
// Valid values round-trip; a bad bell string falls back without failing
// the whole parse.
let cfg: Config = serde_json::from_str(
r#"{"restore_session": false, "mouse_reporting": false, "bell": "audible"}"#,
)
.unwrap();
assert!(!cfg.restore_session);
assert!(!cfg.mouse_reporting);
assert_eq!(cfg.bell, BellMode::Audible);
let cfg: Config = serde_json::from_str(r#"{"bell": "loud"}"#).unwrap();
assert_eq!(cfg.bell, BellMode::Visual);
}
#[test]
fn sanitize_clamps_notify_threshold_into_band() {
let clamp = |n: u64| {
let mut cfg = Config {
notify_threshold_secs: n,
..Config::default()
};
cfg.sanitize();
cfg.notify_threshold_secs
};
assert_eq!(clamp(0), 1); // floor
assert_eq!(clamp(10), 10); // untouched in-band
assert_eq!(clamp(100_000), 3600); // ceiling
}
#[test]
fn keybinding_preset_and_prefix_default_and_round_trip() {
// Missing fields fall back to the no-op preset and the tmux-classic prefix.
+14 -1
View File
@@ -271,7 +271,20 @@ fn main() {
// daemon if none is running (sharing our config dir). Failure is non-fatal —
// we log and continue; a still-absent daemon will surface later when a
// RemoteTerminal fails to connect, rather than blocking startup here.
if let Err(e) = crate::daemon::spawn::ensure_running() {
//
// When session restore is off, start the daemon *fresh* instead of reusing a
// live one: this launch won't re-attach to the previous session's panes, so
// reusing the daemon would leave those shells running orphaned (unreachable,
// never hung up). `restart()` hangs up every old shell then spawns a clean
// daemon — and is safe (equivalent to a plain spawn) when none is running.
// Read straight off disk; the `Config` global isn't set until inside `run`.
let restore_session = crate::core::config::Config::load().restore_session;
let daemon_result = if restore_session {
crate::daemon::spawn::ensure_running()
} else {
crate::daemon::spawn::restart()
};
if let Err(e) = daemon_result {
log::error!("failed to ensure daemon is running: {e}");
}
+97 -41
View File
@@ -28,7 +28,7 @@ use super::typeahead::{RawInput, Typeahead};
use crate::core::actions::{
CloseActiveTab, NewTab, SendBackTab, SendTab, SplitDown, SplitRight, ToggleMaximizePane,
};
use crate::core::config::{Config, NotifyMode};
use crate::core::config::{BellMode, Config, NotifyMode};
use crate::daemon::protocol::{RemoteContext, ShellSpec};
/// Inset (px) between the terminal-surface edge and the cell grid. The prompt
@@ -165,6 +165,13 @@ pub struct TerminalView {
/// True for a brief window after a bell event; drives a momentary visual
/// flash painted in place of an audible beep.
pub bell_flash: bool,
/// Whether mouse events are reported to full-screen apps that request it
/// (`Config::mouse_reporting`). Cached from the global at construction and
/// refreshed on config hot-reload (`Tty7App::reload_from_config`) so the
/// mouse-report gates — which run in `&self`/`&mut self` methods without a
/// `cx` — can consult it. When `false`, every mouse-tracking mode reads as
/// clear, keeping the mouse local (selection + scrollback).
pub report_mouse: bool,
/// Last observed "shell is idle at its prompt" state, tracked so a change can
/// trigger a redraw (showing/hiding the line editor) even when the shell
/// produced no output to repaint on its own.
@@ -336,9 +343,9 @@ enum CmdKey {
FallThrough,
}
/// A foreground command must run at least this long for its completion to be
/// worth a background notification.
const LONG_COMMAND: std::time::Duration = std::time::Duration::from_secs(10);
// The minimum foreground-command duration worth a "finished" notification is
// configurable (`Config::notify_threshold_secs`, default 10s); read live where
// the notification is posted rather than pinned to a const here.
/// How long gap input may be held client-side before it must be released to
/// the PTY (see the `hold` module). Long enough for a fast command's full
@@ -399,6 +406,24 @@ fn notify_command_finished(label: &str, elapsed: std::time::Duration) {
super::remote::notify_desktop(Some("tty7"), &body);
}
/// Ring the OS system bell for the `Audible` bell mode. Returns `true` if a
/// sound was actually requested, `false` on platforms without a portable beep
/// (the caller then falls back to the visual flash so the bell is never silent).
fn ring_system_bell() -> bool {
#[cfg(target_os = "macos")]
{
// A parameter-less AppKit call that just asks the system to play the
// user's alert sound; invoked on the main (gpui app) thread, where every
// `AlacEvent` is handled.
objc2_app_kit::NSBeep();
true
}
#[cfg(not(target_os = "macos"))]
{
false
}
}
/// Build the byte sequence written to the PTY for a paste. Under bracketed paste
/// the content is wrapped in the `ESC[200~` / `ESC[201~` markers, and every ESC
/// (`0x1b`) byte is stripped from the content first. Without that strip, clipboard
@@ -590,6 +615,7 @@ impl TerminalView {
let font_size = px(config.font_size);
let line_height_mul = config.line_height;
let font_features = config.font_features.clone();
let report_mouse = config.mouse_reporting;
let mut font = gpui::font(font_family);
font.fallbacks = Some(gpui::FontFallbacks::from_fonts(fallbacks.clone()));
if let Some(features) = &font_features {
@@ -762,6 +788,7 @@ impl TerminalView {
title: "tty7".to_string(),
marked_text: String::new(),
last_mouse_cell: None,
report_mouse,
last_hover_cell: None,
link_modifier_down: false,
scroll_debt: 0.,
@@ -887,22 +914,20 @@ impl TerminalView {
};
self.terminal.write(fmt(rgb).into_bytes());
}
AlacEvent::Bell => {
// Visual bell: a brief flash instead of an audible beep. Turn it
// on now, then schedule a one-shot task to clear it ~150ms later.
self.bell_flash = true;
cx.notify();
cx.spawn(async move |this, cx| {
cx.background_executor()
.timer(std::time::Duration::from_millis(150))
.await;
let _ = this.update(cx, |view, cx| {
view.bell_flash = false;
cx.notify();
});
})
.detach();
}
AlacEvent::Bell => match cx.global::<Config>().bell {
// Silenced: neither flash nor sound.
BellMode::None => {}
// Visual bell: a brief flash instead of an audible beep.
BellMode::Visual => self.flash_bell(cx),
// Audible bell: ring the system bell. Where none exists (non-mac
// today), fall back to the flash so an opted-in bell is never
// silent.
BellMode::Audible => {
if !ring_system_bell() {
self.flash_bell(cx);
}
}
},
AlacEvent::TextAreaSizeRequest(fmt) => {
// CSI 14 t: the text area size in pixels. Image-preview TUIs
// (yazi, ranger's chafa/sixel backends) size their graphics
@@ -1693,12 +1718,32 @@ impl TerminalView {
// ---- Mouse tracking (so vim / tmux / zellij get clicks & drags) ----
/// True when the application has enabled any mouse-reporting mode.
/// Drive the momentary visual bell flash: turn it on now, then schedule a
/// one-shot task to clear it ~150ms later. Shared by the `Visual` bell mode
/// and the `Audible` fallback on platforms without a system bell.
fn flash_bell(&mut self, cx: &mut Context<Self>) {
self.bell_flash = true;
cx.notify();
cx.spawn(async move |this, cx| {
cx.background_executor()
.timer(std::time::Duration::from_millis(150))
.await;
let _ = this.update(cx, |view, cx| {
view.bell_flash = false;
cx.notify();
});
})
.detach();
}
pub fn mouse_mode(&self) -> bool {
self.terminal
.term
.lock()
.mode()
.intersects(TermMode::MOUSE_MODE)
self.report_mouse
&& self
.terminal
.term
.lock()
.mode()
.intersects(TermMode::MOUSE_MODE)
}
/// Encode and send a single mouse event to the PTY. `base` is the raw button
@@ -1743,12 +1788,13 @@ impl TerminalView {
if self.last_mouse_cell == Some((col, row)) {
return;
}
let wants = self
.terminal
.term
.lock()
.mode()
.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION);
let wants = self.report_mouse
&& self
.terminal
.term
.lock()
.mode()
.intersects(TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION);
if !wants {
return;
}
@@ -1771,12 +1817,13 @@ impl TerminalView {
if self.last_mouse_cell == Some((col, row)) {
return;
}
if !self
.terminal
.term
.lock()
.mode()
.contains(TermMode::MOUSE_MOTION)
if !self.report_mouse
|| !self
.terminal
.term
.lock()
.mode()
.contains(TermMode::MOUSE_MOTION)
{
return;
}
@@ -1790,7 +1837,13 @@ impl TerminalView {
if lines == 0 {
return;
}
let mode = *self.terminal.term.lock().mode();
let mut mode = *self.terminal.term.lock().mode();
// "Mouse reporting off" also silences the wheel: drop the report mode so
// the tick falls through to alternate-scroll / local scrollback, exactly
// as if the app had never asked for wheel reporting.
if !self.report_mouse {
mode.remove(TermMode::MOUSE_MODE);
}
match wheel_route(mode, mods.shift, lines > 0) {
// Mouse-wheel reporting: one report per line, at the last mouse cell.
WheelRoute::Report { base } => {
@@ -2005,13 +2058,16 @@ impl TerminalView {
let title = std::mem::take(&mut self.running_title);
self.running_since = None;
// Gate on the configured policy: never / only-when-unfocused /
// always. The long-command floor still applies regardless.
let notify = match cx.global::<Config>().notify_on_command_finish {
// always. The configured long-command floor still applies
// regardless.
let cfg = cx.global::<Config>();
let notify = match cfg.notify_on_command_finish {
NotifyMode::Never => false,
NotifyMode::Unfocused => !window.is_window_active(),
NotifyMode::Always => true,
};
if elapsed >= LONG_COMMAND && notify {
let threshold = std::time::Duration::from_secs(cfg.notify_threshold_secs);
if elapsed >= threshold && notify {
notify_command_finished(&title, elapsed);
}
}
+61 -2
View File
@@ -204,8 +204,15 @@ pub struct Tty7App {
impl Tty7App {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
// Restore the previous session (tab/split layout + each pane's cwd).
Self::with_session(Session::load(), window, cx)
// Restore the previous session (tab/split layout + each pane's cwd),
// unless the user turned restore off — then start fresh. `None` takes the
// first-run path in `with_session`, spawning a single default terminal.
let session = if cx.global::<Config>().restore_session {
Session::load()
} else {
None
};
Self::with_session(session, window, cx)
}
/// The whole constructor behind `new`, with the saved session injected
@@ -990,6 +997,29 @@ impl Tty7App {
self.update_config(cx, |cfg| cfg.notify_on_command_finish = mode);
}
/// Set the "long command" floor (seconds) a foreground command must exceed
/// to be eligible for a completion notification. Read live where the alert
/// is posted, so nothing needs pushing to open panes.
pub(crate) fn set_notify_threshold(&mut self, secs: u64, cx: &mut Context<Self>) {
self.update_config(cx, |cfg| cfg.notify_threshold_secs = secs.clamp(1, 3600));
}
/// Switch how the terminal bell is signalled. Read live in each pane's bell
/// handler, so there's nothing to push.
pub(crate) fn set_bell_mode(
&mut self,
mode: crate::core::config::BellMode,
cx: &mut Context<Self>,
) {
self.update_config(cx, |cfg| cfg.bell = mode);
}
/// Toggle session restore. Takes effect on the next launch (this only
/// persists the preference); the current window is untouched.
pub(crate) fn set_restore_session(&mut self, on: bool, cx: &mut Context<Self>) {
self.update_config(cx, |cfg| cfg.restore_session = on);
}
// ── Input / Mouse setters ───────────────────────────────────────────────
/// Takes effect on the next keystroke — the terminal reads the flag per
@@ -1008,6 +1038,21 @@ impl Tty7App {
self.update_config(cx, |cfg| cfg.focus_follows_mouse = on);
}
/// Toggle whether mouse events reach full-screen apps. The gates are cached
/// per view, so this pushes the new value into every open pane (like the
/// font setters) in addition to persisting it.
pub(crate) fn set_mouse_reporting(&mut self, on: bool, cx: &mut Context<Self>) {
self.update_config(cx, |cfg| cfg.mouse_reporting = on);
for tab in &self.tabs {
for leaf in tab.pane.leaves() {
leaf.update(cx, |v, cx| {
v.report_mouse = on;
cx.notify();
});
}
}
}
pub(crate) fn set_mouse_scroll_multiplier(&mut self, mult: f32, cx: &mut Context<Self>) {
self.update_config(cx, |cfg| {
cfg.mouse_scroll_multiplier = mult.clamp(0.1, 10.0)
@@ -1992,6 +2037,20 @@ impl Tty7App {
}
}
}
// Mouse-reporting is cached per view (the gates run without a `cx`), so a
// hot-reload must push it into every open pane. Diffed per leaf so an
// unrelated config edit doesn't churn panes that already agree.
let report_mouse = cx.global::<Config>().mouse_reporting;
for tab in &self.tabs {
for leaf in tab.pane.leaves() {
leaf.update(cx, |v, cx| {
if v.report_mouse != report_mouse {
v.report_mouse = report_mouse;
cx.notify();
}
});
}
}
cx.notify();
}
+81 -2
View File
@@ -20,7 +20,7 @@ use gpui_component::switch::Switch;
use gpui_component::{ActiveTheme as _, Icon, IconName, Sizable as _, h_flex, v_flex};
use std::sync::Arc;
use crate::core::config::{Config, CursorStyle, NewTabPosition, NotifyMode};
use crate::core::config::{BellMode, Config, CursorStyle, NewTabPosition, NotifyMode};
use crate::ui::app::{FONT_SIZE_STEP, LINE_HEIGHT_STEP, ThemeEdit, Tty7App};
use crate::ui::presets;
@@ -832,6 +832,16 @@ impl Tty7App {
let scroll_mult = cfg.mouse_scroll_multiplier;
let clip_trim = cfg.clipboard_trim_trailing_spaces;
let copy_on_select = cfg.copy_on_select;
let mouse_reporting = cfg.mouse_reporting;
let bell = cfg.bell;
// Map the persisted threshold onto its preset radio index (nearest slot
// for any off-preset value a hand-edit might leave).
let threshold_idx = match cfg.notify_threshold_secs {
n if n <= 5 => 0,
n if n <= 10 => 1,
n if n <= 30 => 2,
_ => 3,
};
// Map the persisted scrollback depth onto its preset radio index (default
// to 10k's slot for any off-preset value a hand-edit might leave).
let scrollback_idx = match cfg.scrollback_limit {
@@ -904,6 +914,44 @@ impl Tty7App {
.checked(copy_on_select)
.on_click(cx.listener(|this, on: &bool, _w, cx| this.set_copy_on_select(*on, cx)))
.into_any_element();
let mouse_report_switch = Switch::new("term-mouse-report")
.checked(mouse_reporting)
.on_click(cx.listener(|this, on: &bool, _w, cx| this.set_mouse_reporting(*on, cx)))
.into_any_element();
let bell_idx = match bell {
BellMode::None => 0,
BellMode::Visual => 1,
BellMode::Audible => 2,
};
let bell_control = self.segmented(
"term-bell",
&["Off", "Visual", "Audible"],
bell_idx,
cx,
|this, ix, _w, cx| {
let mode = match ix {
0 => BellMode::None,
1 => BellMode::Visual,
_ => BellMode::Audible,
};
this.set_bell_mode(mode, cx);
},
);
let threshold_radio = self.segmented(
"term-notify-threshold",
&["5s", "10s", "30s", "1m"],
threshold_idx,
cx,
|this, ix, _w, cx| {
let secs = match ix {
0 => 5,
1 => 10,
2 => 30,
_ => 60,
};
this.set_notify_threshold(secs, cx);
},
);
// macOS only: the Option/special-character split this toggle resolves
// doesn't exist on other platforms, where Alt always carries Meta.
let option_alt_row = cfg!(target_os = "macos").then(|| {
@@ -964,6 +1012,12 @@ impl Tty7App {
mouse_hide_switch,
cx,
))
.child(self.settings_row(
"Report mouse to apps",
"Let full-screen apps (vim, tmux) handle clicks and scrolling. Off keeps the mouse local; Shift always does for one gesture.",
mouse_report_switch,
cx,
))
.when_some(option_alt_row, |v, row| {
v.child(self.section_rule(cx))
.child(self.section_header("Keyboard", cx))
@@ -998,13 +1052,27 @@ impl Tty7App {
cx,
))
.child(self.section_rule(cx))
.child(self.section_header("Bell", cx))
.child(self.settings_row(
"Terminal bell",
"How a bell (^G) is signalled: silenced, a brief flash, or the system sound.",
bell_control,
cx,
))
.child(self.section_rule(cx))
.child(self.section_header("Notifications", cx))
.child(self.settings_row(
"Notify on command finish",
"Desktop alert after a long (≥10s) command completes.",
"Desktop alert after a long foreground command completes.",
notify_radio,
cx,
))
.child(self.settings_row(
"Notify threshold",
"How long a command must run to qualify as \"long\".",
threshold_radio,
cx,
))
.into_any_element()
}
@@ -1020,7 +1088,12 @@ impl Tty7App {
NewTabPosition::AfterCurrent => 0,
NewTabPosition::End => 1,
};
let restore_session = cfg.restore_session;
let restore_switch = Switch::new("wt-restore-session")
.checked(restore_session)
.on_click(cx.listener(|this, on: &bool, _w, cx| this.set_restore_session(*on, cx)))
.into_any_element();
let startup_radio = self.segmented(
"wt-startup",
&["Normal", "Maximized", "Fullscreen"],
@@ -1058,6 +1131,12 @@ impl Tty7App {
startup_radio,
cx,
))
.child(self.settings_row(
"Restore previous session",
"Reopen the last window's tabs, splits, and directories on launch. Off starts with a single fresh terminal.",
restore_switch,
cx,
))
.child(self.section_rule(cx))
.child(self.section_header("Tabs", cx))
.child(self.settings_row(