diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-01 15:48:22 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-01 15:48:22 +0300 |
| commit | 207be3c771d281baae6e5fa0fb74be9a056e97a2 (patch) | |
| tree | d7b037ecdf629224a048c841d11659efdc23507d /src/copy_mode.rs | |
| parent | 8a6d697308162874a236c84aac0b0f8c7ac01f83 (diff) | |
refactor: render the shell in the client (#3487)
* feat: add opt-in client-rendered shell
* feat: add client-owned shell mouse controls
* feat: complete client-owned shell chrome
* feat: add client-owned shell settings and notifications
* feat: run client shell custom commands
* feat: render popup terminals in client shell
* feat: add client-owned scrollback and copy mode
* fix: preserve client worktree trust defaults
* feat: render startup diagnostics in client shell
* feat: render onboarding in client shell
* feat: render product announcements in client shell
* feat: render release notes in client shell
* feat: render mobile switcher in client shell
* feat: render kitty graphics in client shell
* fix: preserve client shell lifecycle coherence
* refactor: route client shell commands through endpoint connections
* refactor: make the client-rendered shell the default
* refactor: remove monolithic launch mode
* refactor: complete client-rendered shell cutover
* perf: retain client pane surface updates
* perf: scale retained pane surface updates
* perf: remove remote pane input latency
* test: make client shell checks platform-independent
* fix: prevent client timer starvation
* test: fix client shell platform gates
* test: accept retained surfaces in cross-area checks
* test: make client mode assertions platform-neutral
* fix: badge only outdated integrations
Diffstat (limited to 'src/copy_mode.rs')
| -rw-r--r-- | src/copy_mode.rs | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/src/copy_mode.rs b/src/copy_mode.rs new file mode 100644 index 00000000..8f84b9a8 --- /dev/null +++ b/src/copy_mode.rs @@ -0,0 +1,86 @@ +use crossterm::event::{KeyCode, KeyModifiers}; + +use crate::input::TerminalKey; + +pub(crate) fn first_non_blank_col(text: &str) -> Option<u16> { + let mut col = 0u16; + for ch in text.chars() { + if !ch.is_whitespace() { + return Some(col); + } + col = col.saturating_add(char_cell_width(ch)); + } + None +} + +pub(crate) fn last_character_col(text: &str) -> Option<u16> { + let mut col = 0u16; + let mut last_col = None; + for ch in text.chars() { + let width = u16::from(crate::ghostty::unicode_codepoint_width(ch as u32)); + if width > 0 { + last_col = Some(col); + col = col.saturating_add(width); + } + } + last_col +} + +fn char_cell_width(ch: char) -> u16 { + u16::from(crate::ghostty::unicode_codepoint_width(ch as u32)).max(1) +} + +pub(crate) fn copy_mode_page_lines(height: u16, half_page: bool) -> usize { + if height <= 2 { + 1 + } else if half_page { + usize::from(height / 2) + } else { + usize::from(height - 2) + } +} + +pub(crate) fn copy_mode_command_char(key: TerminalKey) -> Option<char> { + if !key.modifiers.difference(KeyModifiers::SHIFT).is_empty() { + return None; + } + if let Some(ch) = key.shifted_codepoint.and_then(char::from_u32) { + return Some(ch); + } + let KeyCode::Char(ch) = key.code else { + return None; + }; + if key.modifiers.contains(KeyModifiers::SHIFT) { + Some(shifted_ascii_char(ch).unwrap_or(ch)) + } else { + Some(ch) + } +} + +fn shifted_ascii_char(ch: char) -> Option<char> { + match ch { + 'a'..='z' => Some(ch.to_ascii_uppercase()), + '1' => Some('!'), + '2' => Some('@'), + '3' => Some('#'), + '4' => Some('$'), + '5' => Some('%'), + '6' => Some('^'), + '7' => Some('&'), + '8' => Some('*'), + '9' => Some('('), + '0' => Some(')'), + '-' => Some('_'), + '=' => Some('+'), + '[' => Some('{'), + ']' => Some('}'), + '\\' => Some('|'), + ';' => Some(':'), + '\'' => Some('"'), + ',' => Some('<'), + '.' => Some('>'), + '/' => Some('?'), + '`' => Some('~'), + _ => None, + } +} |
