aboutsummaryrefslogtreecommitdiff
path: root/src/copy_mode.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-01 15:48:22 +0300
committerGitHub <noreply@github.com>2026-09-01 15:48:22 +0300
commit207be3c771d281baae6e5fa0fb74be9a056e97a2 (patch)
treed7b037ecdf629224a048c841d11659efdc23507d /src/copy_mode.rs
parent8a6d697308162874a236c84aac0b0f8c7ac01f83 (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.rs86
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,
+ }
+}