docs(comments): rewrite comments across all crates to the guidelines

Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
This commit is contained in:
ZacharyZhang-NY committed 2026-07-23 16:55:39 -04:00
1 parent ff0fb56c67
commit a02b555e66
1458 files changed
+10729 -21750

No files matched your search

@@ -22,7 +22,7 @@ use super::render_mermaid::RenderMermaid;
use super::scroll_mode::ScrollMode;
use super::text_selection::TextSelection;
// -- Defaults (asserted in tests to match UiConfig::default()) --------------
// Defaults (asserted in tests to match UiConfig::default())
const COMPACT_DEFAULT: bool = false;
const TIMESTAMPS_DEFAULT: bool = true;
@@ -52,7 +52,7 @@ const SCROLL_LINES_UNSET: u8 = 0;
const SCROLL_LINES_MIN: u8 = 1;
const SCROLL_LINES_MAX: u8 = 10;
// -- Compact mode ------------------------------------------------------------
// Compact mode
thread_local! {
static COMPACT_CURRENT: Cell<bool> = const { Cell::new(COMPACT_DEFAULT) };
@@ -75,13 +75,13 @@ pub fn load() -> bool {
COMPACT_CURRENT.with(|c| c.get())
}
/// Replace cached `compact_mode` (optimistic update or rollback).
/// Replace cached `compact_mode` (optimistic write or rollback).
pub fn set(enabled: bool) {
COMPACT_CURRENT.with(|c| c.set(enabled));
COMPACT_LOADED.with(|l| l.set(true));
}
// -- Timestamps --------------------------------------------------------------
// Timestamps
thread_local! {
static TIMESTAMPS_CURRENT: Cell<bool> = const { Cell::new(TIMESTAMPS_DEFAULT) };
@@ -108,7 +108,7 @@ pub fn set_timestamps(enabled: bool) {
TIMESTAMPS_LOADED.with(|l| l.set(true));
}
// -- Timeline sidebar ----------------------------------------------------------
// Timeline sidebar
thread_local! {
static TIMELINE_CURRENT: Cell<bool> = const { Cell::new(TIMELINE_DEFAULT) };
@@ -135,7 +135,7 @@ pub fn set_show_timeline(enabled: bool) {
TIMELINE_LOADED.with(|l| l.set(true));
}
// -- Simple mode --------------------------------------------------------------
// Simple mode
thread_local! {
static SIMPLE_MODE_CURRENT: Cell<bool> = const { Cell::new(SIMPLE_MODE_DEFAULT) };
@@ -162,7 +162,7 @@ pub fn set_simple_mode(enabled: bool) {
SIMPLE_MODE_LOADED.with(|l| l.set(true));
}
// -- Vim mode (scrollback) ---------------------------------------------------
// Vim mode (scrollback)
thread_local! {
static VIM_MODE_CURRENT: Cell<bool> = const { Cell::new(VIM_MODE_DEFAULT) };
@@ -197,7 +197,7 @@ pub fn set_vim_mode(enabled: bool) {
VIM_MODE_LOADED.with(|l| l.set(true));
}
// -- Show thinking blocks ----------------------------------------------------
// Show thinking blocks
thread_local! {
static SHOW_THINKING_BLOCKS_CURRENT: Cell<bool> =
@@ -228,7 +228,7 @@ pub fn set_show_thinking_blocks(enabled: bool) {
SHOW_THINKING_BLOCKS_LOADED.with(|l| l.set(true));
}
// -- Group tool verbs ---------------------------------------------------------
// Group tool verbs
thread_local! {
static GROUP_TOOL_VERBS_CURRENT: Cell<bool> =
@@ -259,7 +259,7 @@ pub fn set_group_tool_verbs(enabled: bool) {
GROUP_TOOL_VERBS_LOADED.with(|l| l.set(true));
}
// -- Collapsed edit blocks -----------------------------------------------------
// Collapsed edit blocks
thread_local! {
static COLLAPSED_EDIT_BLOCKS_CURRENT: Cell<bool> =
@@ -292,7 +292,7 @@ pub fn set_collapsed_edit_blocks(enabled: bool) {
COLLAPSED_EDIT_BLOCKS_LOADED.with(|l| l.set(true));
}
// -- Prompt suggestions (tab autocomplete) -----------------------------------
// Prompt suggestions (tab autocomplete)
thread_local! {
static PROMPT_SUGGESTIONS_CURRENT: Cell<bool> =
@@ -324,7 +324,7 @@ pub fn set_prompt_suggestions(enabled: bool) {
PROMPT_SUGGESTIONS_LOADED.with(|l| l.set(true));
}
// -- keep_text_selection (`flash` | `hold`) ----------------------------------
// keep_text_selection (`flash` | `hold`)
thread_local! {
static KEEP_TEXT_SELECTION_CURRENT: Cell<TextSelection> =
@@ -350,7 +350,7 @@ pub fn set_keep_text_selection(value: TextSelection) {
KEEP_TEXT_SELECTION_LOADED.with(|l| l.set(true));
}
// -- Scroll speed ------------------------------------------------------------
// Scroll speed
thread_local! {
static SCROLL_SPEED_CURRENT: Cell<u8> = const { Cell::new(SCROLL_SPEED_DEFAULT) };
@@ -383,7 +383,7 @@ pub fn set_scroll_speed(speed: u8) {
SCROLL_SPEED_LOADED.with(|l| l.set(true));
}
// -- Scroll mode (auto | wheel | trackpad) -----------------------------------
// Scroll mode (auto | wheel | trackpad)
thread_local! {
static SCROLL_MODE_CURRENT: Cell<ScrollMode> = const { Cell::new(SCROLL_MODE_DEFAULT) };
@@ -418,7 +418,7 @@ pub fn set_scroll_mode(value: ScrollMode) {
SCROLL_MODE_LOADED.with(|l| l.set(true));
}
// -- Invert scroll ------------------------------------------------------------
// Invert scroll
thread_local! {
static INVERT_SCROLL_CURRENT: Cell<bool> = const { Cell::new(INVERT_SCROLL_DEFAULT) };
@@ -453,7 +453,7 @@ pub fn set_invert_scroll(enabled: bool) {
INVERT_SCROLL_LOADED.with(|l| l.set(true));
}
// -- Scroll lines ------------------------------------------------------------
// Scroll lines
thread_local! {
static SCROLL_LINES_CURRENT: Cell<u8> = const { Cell::new(SCROLL_LINES_UNSET) };
@@ -494,7 +494,7 @@ pub fn set_scroll_lines(lines: u8) {
SCROLL_LINES_LOADED.with(|l| l.set(true));
}
// -- Render mermaid (auto | on | off) ---------------------------------------
// Render mermaid (auto | on | off)
thread_local! {
static RENDER_MERMAID_CURRENT: Cell<RenderMermaid> = const { Cell::new(RenderMermaid::Auto) };
@@ -531,13 +531,13 @@ fn render_mermaid_from_config_str(value: Option<&str>) -> RenderMermaid {
.unwrap_or_default()
}
/// Replace cached `render_mermaid` (optimistic update from the settings modal).
/// Replace cached `render_mermaid` (optimistic write from the settings modal).
pub fn set_render_mermaid(value: RenderMermaid) {
RENDER_MERMAID_CURRENT.with(|c| c.set(value));
RENDER_MERMAID_LOADED.with(|l| l.set(true));
}
// -- Prime + read path ------------------------------------------------------
// Prime + read path
/// Seed all caches from the live `UiConfig` at startup so subsequent
/// `load*()` calls never hit disk on the render hot path.
@@ -643,7 +643,7 @@ fn load_str_from_effective_config(key: &str) -> Option<String> {
.map(str::to_string)
}
// -- Tests -------------------------------------------------------------------
// Tests
#[cfg(test)]
mod tests {
@@ -915,7 +915,7 @@ mod tests {
#[test]
fn caches_are_independent() {
std::thread::spawn(|| {
// ── compact independent (the other two stay true) ──
// compact independent (the other two stay true)
set(false);
set_timestamps(true);
set_simple_mode(true);
@@ -929,7 +929,7 @@ mod tests {
"simple_mode must NOT toggle when compact changed"
);
// ── timestamps independent ──
// timestamps independent
set(true);
set_timestamps(false);
set_simple_mode(true);
@@ -940,7 +940,7 @@ mod tests {
"simple_mode must NOT toggle when timestamps changed"
);
// ── simple_mode independent ──
// simple_mode independent
set(true);
set_timestamps(true);
set_simple_mode(false);
@@ -10,9 +10,7 @@ use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use toml_edit::{DocumentMut, Item, RawString};
// ============================================================================
// Runtime Config (used by render code)
// ============================================================================
/// Background style for block content area.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
@@ -173,7 +171,8 @@ impl Default for ScrollbackDisplayConfig {
line_under_last_entry: false,
collapsed_accent_char: crate::glyphs::collapsed_accent().to_string(),
dim_accent: 0.5,
group_selection_split: true, // Mode B by default
// Mode B by default
group_selection_split: true,
highlight_overlays_border: false,
expandable_indicator: true,
expandable_indicator_running: true,
@@ -208,7 +207,8 @@ impl Default for LayoutConfig {
outer_hpad_left: 2,
outer_hpad_right: 2,
block_pad_left: 2,
block_pad_right: 2, // Match left padding for symmetry
// Match left padding for symmetry
block_pad_right: 2,
}
}
}
@@ -289,8 +289,10 @@ impl Default for ScrollbarConfig {
fn default() -> Self {
Self {
enabled: true,
gap_left: 0, // Content adjacent to scrollbar
gap_right: 0, // Scrollbar at screen edge
// Content adjacent to scrollbar
gap_left: 0,
// Scrollbar at screen edge
gap_right: 0,
scrollbar_bg: None,
scrollbar_fg: None,
}
@@ -587,8 +589,8 @@ pub struct ToolConfig {
pub dim_details: bool,
/// Bullet/icon character rendered before tool call headers.
pub bullet: ToolBullet,
// Note: bullet_accent and bullet_color were removed in the scrollback-v2 refactor.
// Bullet color is now determined by BlockContent::bullet() — each block type
// Note: bullet_accent and bullet_color are not configurable here.
// Bullet color is determined by BlockContent::bullet() — each block type
// decides its own bullet color based on state (accent color, error, default).
// Dimming for collapsed+groupable blocks is handled by EntryRenderer.
// TODO(dim_muted): add a dim factor for collapsed text styling (not just bullet/accent).
@@ -658,7 +660,8 @@ pub struct ListDirConfig {
impl Default for ListDirConfig {
fn default() -> Self {
Self {
terminal_bg: true, // Default: dark background for output
// Default: dark background for output
terminal_bg: true,
}
}
}
@@ -705,25 +708,12 @@ impl Default for ExecuteConfig {
}
}
// ============================================================================
// Raw Config (for TOML serde)
// ============================================================================
//
// ╔═══════════════════════════════════════════════════════════════════════════╗
// ║ MAINTAINER NOTE: When adding/changing fields or sections: ║
// ║ ║
// ║ 1. Add doc comments (///) to ALL fields in Raw* structs - they become ║
// ║ TOML comments via the `DocumentedFields` derive macro. ║
// ║ ║
// ║ 2. If adding a new section (e.g., RawNewBlockConfig): ║
// ║ - Add it to RawBlocksConfig (or appropriate parent) ║
// ║ - Add corresponding runtime config (NewBlockConfig) ║
// ║ - Add From<RawNewBlockConfig> for NewBlockConfig conversion ║
// ║ - Add annotate_table call in to_toml_with_comments() below! ║
// ║ ║
// ║ 3. The to_toml_with_comments() method generates the default config file ║
// ║ with comments. Update it when adding new sections. ║
// ╚═══════════════════════════════════════════════════════════════════════════╝
// MAINTAINER NOTE — when adding or changing fields or sections:
// 1. Put `///` on every Raw* field (DocumentedFields emits TOML comments).
// 2. For a new section: Raw* + runtime config + From impl + annotate_table.
// 3. Keep to_toml_with_comments() in sync so the default file stays documented.
/// Root appearance configuration (TOML format).
#[derive(Debug, Clone, Default, Serialize, Deserialize, Documented, DocumentedFields)]
@@ -1259,7 +1249,7 @@ pub struct RawToolConfig {
/// "none", "dot" (·), "small-circle" (•), "circle" (●),
/// "small-triangle" (▸), "triangle" (▶), "diamond" (◆).
pub bullet: RawToolBullet,
// Note: bullet_accent and bullet_color removed — see ToolConfig comment.
// Note: bullet_accent and bullet_color are not configurable — see ToolConfig comment.
}
impl Default for RawToolConfig {
@@ -1284,7 +1274,8 @@ pub struct RawListDirConfig {
impl Default for RawListDirConfig {
fn default() -> Self {
Self {
terminal_bg: true, // Default: dark background for output
// Default: dark background for output
terminal_bg: true,
}
}
}
@@ -1364,9 +1355,7 @@ impl From<RawBlockBackground> for BlockBackground {
}
}
// ============================================================================
// Raw → Runtime Conversion
// ============================================================================
impl From<RawAppearanceConfig> for AppearanceConfig {
fn from(raw: RawAppearanceConfig) -> Self {
@@ -1376,7 +1365,8 @@ impl From<RawAppearanceConfig> for AppearanceConfig {
collapse_unfocused: raw.prompt.collapse_unfocused,
mouse_hover: raw.prompt.mouse_hover,
show_prefix: raw.prompt.show_prefix,
compact: false, // runtime-only, not persisted in TOML
// runtime-only, not persisted in TOML
compact: false,
},
scrollback: ScrollbackConfig {
layout: raw.scrollback.layout.into(),
@@ -1426,7 +1416,8 @@ impl From<RawAppearanceConfig> for AppearanceConfig {
badge_format: raw.todo.badge_format.into(),
},
turn_status: TurnStatusConfig::default(),
show_timestamps: true, // runtime-only, loaded from config.toml via persist
// runtime-only, loaded from config.toml via persist
show_timestamps: true,
// Single source: UiConfig::SHOW_TIMELINE_DEFAULT (loaded from config.toml via persist).
show_timeline: UiConfig::SHOW_TIMELINE_DEFAULT,
disable_plugins: raw.disable_plugins,
@@ -1607,9 +1598,7 @@ impl From<RawThinkingConfig> for ThinkingConfig {
}
}
// ============================================================================
// Color Parsing
// ============================================================================
/// An optional color that can be "none" or a color value.
/// This allows TOML to represent None values explicitly.
@@ -1719,47 +1708,48 @@ fn lookup_named_color(name: &str) -> Result<Color, String> {
// `parse_color_string` → `quantize()` to match the terminal's capabilities.
let color = match name.to_uppercase().as_str() {
// Background colors
"BG" | "BG_BASE" => Color::Rgb(20, 20, 20), // #141414
"BG_LIGHT" | "BG_HIGHLIGHT" => Color::Rgb(30, 30, 30), // #1e1e1e
"BG_DARK" => Color::Rgb(17, 17, 17), // #111111
"BG_TERMINAL" | "BG_NIGHT" => Color::Rgb(10, 10, 10), // #0a0a0a
"BG_VISUAL" => Color::Rgb(30, 32, 45), // blue-tinted selection
"BG_SEARCH" => Color::Rgb(48, 48, 52), // #303034
"BG" | "BG_BASE" => Color::Rgb(20, 20, 20),
"BG_LIGHT" | "BG_HIGHLIGHT" => Color::Rgb(30, 30, 30),
"BG_DARK" => Color::Rgb(17, 17, 17),
"BG_TERMINAL" | "BG_NIGHT" => Color::Rgb(10, 10, 10),
// blue-tinted selection
"BG_VISUAL" => Color::Rgb(30, 32, 45),
"BG_SEARCH" => Color::Rgb(48, 48, 52),
// Accent colors (TokyoNight Night)
"BLUE" => Color::Rgb(77, 121, 255), // #4D79FF
"BLUE0" => Color::Rgb(61, 89, 161), // #3d59a1
"BLUE1" => Color::Rgb(42, 195, 222), // #2ac3de
"BLUE2" => Color::Rgb(13, 185, 215), // #0db9d7
"BLUE5" => Color::Rgb(137, 221, 255), // #89ddff
"BLUE6" => Color::Rgb(180, 249, 248), // #b4f9f8
"BLUE7" => Color::Rgb(57, 75, 112), // #394b70
"CYAN" => Color::Rgb(125, 207, 255), // #7dcfff
"GREEN" => Color::Rgb(36, 196, 116), // #24C474
"GREEN1" => Color::Rgb(115, 218, 202), // #73daca
"GREEN2" => Color::Rgb(65, 166, 181), // #41a6b5
"YELLOW" => Color::Rgb(255, 219, 141), // #FFDB8D
"ORANGE" => Color::Rgb(255, 158, 100), // #ff9e64
"RED" => Color::Rgb(248, 114, 122), // #F8727A
"RED1" => Color::Rgb(219, 75, 75), // #db4b4b
"MAGENTA" => Color::Rgb(187, 154, 247), // #bb9af7
"PURPLE" => Color::Rgb(131, 113, 211), // #8371D3
"MAGENTA2" => Color::Rgb(255, 0, 124), // #ff007c
"TEAL" | "HINT" => Color::Rgb(26, 188, 156), // #1abc9c
"BLUE" => Color::Rgb(77, 121, 255),
"BLUE0" => Color::Rgb(61, 89, 161),
"BLUE1" => Color::Rgb(42, 195, 222),
"BLUE2" => Color::Rgb(13, 185, 215),
"BLUE5" => Color::Rgb(137, 221, 255),
"BLUE6" => Color::Rgb(180, 249, 248),
"BLUE7" => Color::Rgb(57, 75, 112),
"CYAN" => Color::Rgb(125, 207, 255),
"GREEN" => Color::Rgb(36, 196, 116),
"GREEN1" => Color::Rgb(115, 218, 202),
"GREEN2" => Color::Rgb(65, 166, 181),
"YELLOW" => Color::Rgb(255, 219, 141),
"ORANGE" => Color::Rgb(255, 158, 100),
"RED" => Color::Rgb(248, 114, 122),
"RED1" => Color::Rgb(219, 75, 75),
"MAGENTA" => Color::Rgb(187, 154, 247),
"PURPLE" => Color::Rgb(131, 113, 211),
"MAGENTA2" => Color::Rgb(255, 0, 124),
"TEAL" | "HINT" => Color::Rgb(26, 188, 156),
// Text colors
"FG" | "TEXT" | "TEXT_PRIMARY" => Color::Rgb(243, 243, 243), // #f3f3f3
"FG_DARK" | "TEXT_SECONDARY" => Color::Rgb(200, 200, 200), // #c8c8c8
"FG_GUTTER" => Color::Rgb(65, 65, 65), // #414141
"COMMENT" | "MUTED" | "TEXT_MUTED" => Color::Rgb(98, 98, 98), // #626262
"DARK3" => Color::Rgb(90, 90, 90), // #5a5a5a
"DARK5" | "TOOL" => Color::Rgb(120, 120, 120), // #787878
"FG" | "TEXT" | "TEXT_PRIMARY" => Color::Rgb(243, 243, 243),
"FG_DARK" | "TEXT_SECONDARY" => Color::Rgb(200, 200, 200),
"FG_GUTTER" => Color::Rgb(65, 65, 65),
"COMMENT" | "MUTED" | "TEXT_MUTED" => Color::Rgb(98, 98, 98),
"DARK3" => Color::Rgb(90, 90, 90),
"DARK5" | "TOOL" => Color::Rgb(120, 120, 120),
// Semantic colors
"ERROR" => Color::Rgb(247, 118, 142), // RED
"SUCCESS" => Color::Rgb(158, 206, 106), // GREEN
"WARNING" => Color::Rgb(224, 175, 104), // YELLOW
"INFO" => Color::Rgb(125, 207, 255), // CYAN
"ERROR" => Color::Rgb(247, 118, 142),
"SUCCESS" => Color::Rgb(158, 206, 106),
"WARNING" => Color::Rgb(224, 175, 104),
"INFO" => Color::Rgb(125, 207, 255),
// Basic colors
"BLACK" => Color::Black,
@@ -1771,9 +1761,7 @@ fn lookup_named_color(name: &str) -> Result<Color, String> {
Ok(color)
}
// ============================================================================
// TOML Generation with Comments
// ============================================================================
impl RawAppearanceConfig {
pub fn to_toml_with_comments() -> String {
@@ -2025,9 +2013,7 @@ fn annotate_table<T: DocumentedFields>(table: &mut toml_edit::Table) {
}
}
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
@@ -2414,7 +2400,7 @@ gutter_bg = true
);
}
// ── Terminal config (alt_screen) parsing ─────────────────────
// Terminal config (alt_screen) parsing
#[test]
fn terminal_alt_screen_auto_default() {
@@ -33,7 +33,7 @@ pub use scroll_mode::ScrollMode;
pub use text_selection::TextSelection;
pub use watcher::ConfigWatcher;
// -- Global tab_width --------------------------------------------------------
// Global tab_width
//
// Stored as an atomic so MarkdownContent can read the current value
// without needing the AppearanceConfig threaded through its API.
@@ -62,7 +62,7 @@ impl DefaultSelectedPermission {
}
}
/// Display label for the settings picker and the change toast.
/// Display label for the settings picker and the confirmation toast.
/// `AllowCommandAlways` preselects the prompt-specific always-allow row
/// (per-command / per-tool / per-domain / per-edit-session), never a
/// global allow-everything — that is `AlwaysAllowAllSessions`.
@@ -132,7 +132,7 @@ impl DefaultSelectedPermission {
}
}
// ── Configured value cache: `[ui].default_selected_permission` ──────────────
// Configured value cache: `[ui].default_selected_permission`
//
// Read when queueing the first prompt of a session. Seeded by `prime` at
// startup (and lazily on first read) so the path never hits disk mid-session.
@@ -174,7 +174,7 @@ pub fn load_default_selected_permission() -> DefaultSelectedPermission {
CONFIG_CURRENT.with(Cell::get)
}
/// Replace the cached configured value (optimistic update from the settings
/// Replace the cached configured value (optimistic write from the settings
/// modal, or rollback on persist failure). The next prompt sees it without a
/// restart.
pub fn set_default_selected_permission(value: DefaultSelectedPermission) {
@@ -188,7 +188,7 @@ pub fn prime() {
let _ = load_default_selected_permission();
}
// ── Sticky "last used" cursor target ────────────────────────────────────────
// Sticky "last used" cursor target
//
// Process-wide ephemeral state: the kind the user most recently confirmed.
// After the first prompt, `resolve_initial_cursor` prefers this over the
@@ -215,7 +215,7 @@ pub fn set_last_used_permission(kind: DefaultSelectedPermission) {
LAST_USED.with(|c| c.set(kind));
}
// ── Resolution ──────────────────────────────────────────────────────────────
// Resolution
/// Pick the initially-highlighted row for a freshly-queued permission prompt.
///
@@ -252,7 +252,7 @@ fn load_string_from_effective_config(key: &str) -> Option<String> {
.map(std::string::ToString::to_string)
}
// -- Tests -------------------------------------------------------------------
// Tests
#[cfg(test)]
mod tests {
@@ -2,7 +2,7 @@
//!
//! Fenced ` ```mermaid ` blocks are rendered inline as Unicode box-drawing art
//! by the markdown renderer. This setting controls the full-fidelity affordance
//! row layered beneath that art: `auto`/`on` add the clickable row
//! row layered beneath that art: `auto`/`on` include the clickable row
//! (`◇ mermaid [Open Image] [Copy Image Path] [Copy Source]`); `off` shows the
//! inline art alone. The PNG render engine is always compiled in, and the PNG is
//! never drawn as an inline image (it opens in the OS viewer), so the treatment
@@ -6,10 +6,9 @@
//! `ScrollInputMode` when building its scroll config; this crate only owns
//! the persisted value type and its cache.
/// Scroll input classification preference: auto-detect or force one kind.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum ScrollMode {
/// Detect wheel vs trackpad per stream from event timing. Default.
/// Detect wheel vs trackpad per stream from event timing.
#[default]
Auto,
/// Always treat scroll input as a mouse wheel (fixed lines per tick).
@@ -28,7 +27,6 @@ impl ScrollMode {
}
}
/// Parse a canonical string, returning `None` for unrecognized input.
pub fn from_canonical(value: &str) -> Option<Self> {
match value {
"auto" => Some(Self::Auto),
@@ -52,8 +50,8 @@ mod tests {
#[test]
fn junk_and_case_variants_are_rejected() {
// Strict parse: unknown disk/env values must fall back to the default
// at the caller (cache seed), never panic or mis-map.
// Parsing is strict so a stray disk/env value falls back to the
// default at the caller (cache seed) rather than mis-mapping.
for junk in ["", "Auto", "WHEEL", "track pad", "mouse", "1"] {
assert_eq!(ScrollMode::from_canonical(junk), None, "{junk:?}");
}
@@ -1,18 +1,12 @@
//! The `keep_text_selection` user setting (`flash` | `hold` | `word_select`).
//!
//! This is the single, unified control for scrollback text-selection behavior.
//! It governs both how long an in-app selection highlight stays on screen and
//! what a double/triple-click does, so the two can never drift out of sync:
//!
//! - `flash` — brief highlight on mouse-up, then clear; double-click toggles fold.
//! - `hold` — selection stays until dismissed; double-click toggles fold.
//! - `word_select` — selection stays until dismissed; double-click selects &
//! copies a word, triple-click a line (terminal-like). Implies `hold`.
//! One setting governs both how long an in-app selection highlight stays on
//! screen and what a double/triple-click does, so the two can never drift out
//! of sync.
/// Scrollback text-selection behavior: highlight lifetime + double-click action.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum TextSelection {
/// Brief highlight on mouse-up, then clear; double-click toggles fold. Default.
/// Brief highlight on mouse-up, then clear; double-click toggles fold.
#[default]
Flash,
/// Stay visible until Esc/click/scroll; double-click toggles fold.
@@ -32,7 +26,6 @@ impl TextSelection {
}
}
/// Parse a canonical string, returning `None` for unrecognized input.
pub fn from_canonical(value: &str) -> Option<Self> {
match value {
"flash" => Some(Self::Flash),
@@ -78,16 +71,14 @@ mod tests {
assert_eq!(TextSelection::default().as_canonical(), "flash");
}
/// The unified invariant: `word_select` always implies `holds()` (persistent
/// highlight) and is the only mode that turns on double-click word select.
/// `word_select` always implies `holds()`, and is the only mode that turns
/// on double-click word select.
#[test]
fn word_select_implies_hold_and_word_select() {
assert!(TextSelection::WordSelect.holds());
assert!(TextSelection::WordSelect.selects_word());
// Hold persists but leaves double-click as fold-toggle.
assert!(TextSelection::Hold.holds());
assert!(!TextSelection::Hold.selects_word());
// Flash neither persists nor word-selects.
assert!(!TextSelection::Flash.holds());
assert!(!TextSelection::Flash.selects_word());
}