fix cursor tracking: use char indices instead of byte for multi-byte utf-8 support
This commit is contained in:
parent
66ca52c187
commit
b161924bbc
1 changed files with 30 additions and 34 deletions
|
|
@ -124,12 +124,17 @@ fn follow_log(path: &str, tx: mpsc::Sender<String>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn byte_at(s: &str, char_idx: usize) -> usize {
|
||||
s.char_indices().nth(char_idx).map(|(i, _)| i).unwrap_or(s.len())
|
||||
}
|
||||
|
||||
fn adjust_scroll(input: &str, cursor: usize, scroll: &mut usize, cols: usize) {
|
||||
let max_visible = cols.saturating_sub(2);
|
||||
if max_visible == 0 {
|
||||
return;
|
||||
}
|
||||
let threshold = (max_visible as f64 * 0.9) as usize;
|
||||
let nchars = input.chars().count();
|
||||
|
||||
if cursor > *scroll + threshold {
|
||||
*scroll = cursor - threshold;
|
||||
|
|
@ -137,8 +142,8 @@ fn adjust_scroll(input: &str, cursor: usize, scroll: &mut usize, cols: usize) {
|
|||
if cursor < *scroll {
|
||||
*scroll = cursor;
|
||||
}
|
||||
if *scroll + max_visible > input.len() {
|
||||
*scroll = input.len().saturating_sub(max_visible);
|
||||
if *scroll + max_visible > nchars {
|
||||
*scroll = nchars.saturating_sub(max_visible);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,18 +183,12 @@ fn draw_all(
|
|||
|
||||
let prompt = "> ";
|
||||
let max_visible = cols.saturating_sub(prompt.len());
|
||||
let visible_start = scroll;
|
||||
let visible_end = (scroll + max_visible).min(input.len());
|
||||
let visible = if scroll < input.len() {
|
||||
&input[visible_start..visible_end]
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let visible: String = input.chars().skip(scroll).take(max_visible).collect();
|
||||
|
||||
out.push_str(&format!("\x1b[{};1H", rows));
|
||||
out.push_str(GRAY_BG);
|
||||
out.push_str(prompt);
|
||||
out.push_str(visible);
|
||||
out.push_str(&visible);
|
||||
out.push_str("\x1b[K");
|
||||
out.push_str(RESET);
|
||||
|
||||
|
|
@ -389,8 +388,8 @@ pub fn run_attach(name: &str, full: bool, stream: UnixStream) {
|
|||
}
|
||||
if let Some(idx) = history_idx {
|
||||
input = history[idx].clone();
|
||||
cursor = input.len();
|
||||
scroll = input.len().saturating_sub(cols.saturating_sub(2));
|
||||
cursor = input.chars().count();
|
||||
scroll = input.chars().count().saturating_sub(cols.saturating_sub(2));
|
||||
}
|
||||
adjust_scroll(&input, cursor, &mut scroll, cols);
|
||||
}
|
||||
|
|
@ -399,7 +398,7 @@ pub fn run_attach(name: &str, full: bool, stream: UnixStream) {
|
|||
if idx + 1 < history.len() {
|
||||
history_idx = Some(idx + 1);
|
||||
input = history[idx + 1].clone();
|
||||
cursor = input.len();
|
||||
cursor = input.chars().count();
|
||||
} else {
|
||||
history_idx = None;
|
||||
input.clear();
|
||||
|
|
@ -449,13 +448,6 @@ pub fn run_attach(name: &str, full: bool, stream: UnixStream) {
|
|||
}
|
||||
}
|
||||
|
||||
fn prev_char_boundary(s: &str, cursor: usize) -> usize {
|
||||
if cursor == 0 {
|
||||
return 0;
|
||||
}
|
||||
s.floor_char_boundary(cursor - 1)
|
||||
}
|
||||
|
||||
fn process_input_byte(
|
||||
b: u8,
|
||||
input: &mut String,
|
||||
|
|
@ -473,9 +465,11 @@ fn process_input_byte(
|
|||
0x0e => return InputAction::HistoryNext,
|
||||
0x08 | 0x7f => {
|
||||
if *cursor > 0 {
|
||||
let start = prev_char_boundary(input, *cursor);
|
||||
input.drain(start..*cursor);
|
||||
*cursor = start;
|
||||
*cursor -= 1;
|
||||
let byte_start = byte_at(input, *cursor);
|
||||
let c = input[byte_start..].chars().next().unwrap();
|
||||
let n = c.len_utf8();
|
||||
input.drain(byte_start..byte_start + n);
|
||||
}
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
return InputAction::None;
|
||||
|
|
@ -486,7 +480,7 @@ fn process_input_byte(
|
|||
return InputAction::None;
|
||||
}
|
||||
0x05 => {
|
||||
*cursor = input.len();
|
||||
*cursor = input.chars().count();
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
return InputAction::None;
|
||||
}
|
||||
|
|
@ -497,7 +491,7 @@ fn process_input_byte(
|
|||
return InputAction::None;
|
||||
}
|
||||
0x0b => {
|
||||
input.truncate(*cursor);
|
||||
input.truncate(byte_at(input, *cursor));
|
||||
return InputAction::None;
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -514,9 +508,9 @@ fn process_input_byte(
|
|||
if Some(utf8_buf.len()) == *utf8_expected {
|
||||
if let Ok(s) = std::str::from_utf8(&utf8_buf) {
|
||||
for c in s.chars() {
|
||||
let n = c.len_utf8();
|
||||
input.insert(*cursor, c);
|
||||
*cursor += n;
|
||||
let byte_pos = byte_at(input, *cursor);
|
||||
input.insert(byte_pos, c);
|
||||
*cursor += 1;
|
||||
}
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
}
|
||||
|
|
@ -528,7 +522,8 @@ fn process_input_byte(
|
|||
*utf8_expected = None;
|
||||
}
|
||||
} else if b >= 0x20 {
|
||||
input.insert(*cursor, b as char);
|
||||
let byte_pos = byte_at(input, *cursor);
|
||||
input.insert(byte_pos, b as char);
|
||||
*cursor += 1;
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
}
|
||||
|
|
@ -558,7 +553,7 @@ fn process_escape(
|
|||
}
|
||||
if let Some(idx) = *history_idx {
|
||||
*input = history[idx].clone();
|
||||
*cursor = input.len();
|
||||
*cursor = input.chars().count();
|
||||
}
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
InputAction::None
|
||||
|
|
@ -569,7 +564,7 @@ fn process_escape(
|
|||
if idx + 1 < history.len() {
|
||||
*history_idx = Some(idx + 1);
|
||||
*input = history[idx + 1].clone();
|
||||
*cursor = input.len();
|
||||
*cursor = input.chars().count();
|
||||
} else {
|
||||
*history_idx = None;
|
||||
input.clear();
|
||||
|
|
@ -581,7 +576,8 @@ fn process_escape(
|
|||
}
|
||||
b'C' => {
|
||||
// Right arrow
|
||||
if *cursor < input.len() {
|
||||
let nchars = input.chars().count();
|
||||
if *cursor < nchars {
|
||||
*cursor += 1;
|
||||
}
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
|
|
@ -589,7 +585,7 @@ fn process_escape(
|
|||
}
|
||||
b'D' => {
|
||||
// Left arrow
|
||||
*cursor = prev_char_boundary(input, *cursor);
|
||||
*cursor = cursor.saturating_sub(1);
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
InputAction::None
|
||||
}
|
||||
|
|
@ -601,7 +597,7 @@ fn process_escape(
|
|||
}
|
||||
b'F' => {
|
||||
// End
|
||||
*cursor = input.len();
|
||||
*cursor = input.chars().count();
|
||||
adjust_scroll(input, *cursor, scroll, cols);
|
||||
InputAction::None
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue