1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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,
}
}
|