use crossterm::event::{KeyCode, KeyModifiers}; use crate::input::TerminalKey; pub(crate) fn first_non_blank_col(text: &str) -> Option { 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 { 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 { 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 { 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, } }