mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-25 07:07:06 +03:00
fix ctrl+R, use unicode-width to manage 2-wide graphemes
This commit is contained in:
parent
47d025b3a2
commit
5180e24387
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3586,6 +3586,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.21.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"url",
|
||||
"walkdir",
|
||||
"warp",
|
||||
|
@ -85,6 +85,7 @@ thiserror = "1.0"
|
||||
tokio = { version = "1.28", features = ["fs", "macros", "rt-multi-thread", "signal", "sync"] }
|
||||
tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] }
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-width = "0.1.13"
|
||||
url = "2.4.1"
|
||||
warp = "0.3.5"
|
||||
wasi-common = "19.0.1"
|
||||
|
@ -70,8 +70,7 @@ impl State {
|
||||
let search_prompt = format!("{} *", our_name);
|
||||
let search_query = &self.current_line.line;
|
||||
if let Some(result) = self.command_history.search(search_query, self.search_depth) {
|
||||
let (result_underlined, u_end) = utils::underline(result, search_query);
|
||||
let search_cursor_col = u_end + search_prompt.graphemes(true).count() as u16;
|
||||
let (result_underlined, search_cursor_col) = utils::underline(result, search_query);
|
||||
execute!(
|
||||
self.stdout,
|
||||
cursor::MoveTo(0, self.win_rows),
|
||||
@ -134,6 +133,18 @@ impl CurrentLine {
|
||||
.unwrap_or_else(|| self.line.len())
|
||||
}
|
||||
|
||||
fn current_char_left(&self) -> Option<&str> {
|
||||
if self.line_col == 0 {
|
||||
None
|
||||
} else {
|
||||
self.line.graphemes(true).nth(self.line_col - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_char_right(&self) -> Option<&str> {
|
||||
self.line.graphemes(true).nth(self.line_col)
|
||||
}
|
||||
|
||||
fn insert_char(&mut self, c: char) {
|
||||
let byte_index = self.byte_index();
|
||||
self.line.insert(byte_index, c);
|
||||
@ -144,14 +155,17 @@ impl CurrentLine {
|
||||
self.line.insert_str(byte_index, s);
|
||||
}
|
||||
|
||||
fn delete_char(&mut self) {
|
||||
/// returns the deleted character
|
||||
fn delete_char(&mut self) -> String {
|
||||
let byte_index = self.byte_index();
|
||||
let next_grapheme = self.line[byte_index..]
|
||||
.graphemes(true)
|
||||
.next()
|
||||
.map(|g| g.len())
|
||||
.unwrap_or(0);
|
||||
self.line.drain(byte_index..byte_index + next_grapheme);
|
||||
self.line
|
||||
.drain(byte_index..byte_index + next_grapheme)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,7 +417,7 @@ async fn handle_event(
|
||||
.filter(|c| !c.is_control() && !c.is_ascii_control())
|
||||
.collect::<String>();
|
||||
current_line.insert_str(&pasted);
|
||||
current_line.line_col = current_line.line_col + pasted.graphemes(true).count();
|
||||
current_line.line_col = current_line.line_col + utils::display_width(&pasted);
|
||||
current_line.cursor_col = std::cmp::min(
|
||||
current_line.line_col.try_into().unwrap_or(*win_cols),
|
||||
*win_cols - current_line.prompt_len as u16,
|
||||
@ -545,18 +559,17 @@ async fn handle_event(
|
||||
// go up one command in history
|
||||
match command_history.get_prev(¤t_line.line) {
|
||||
Some(line) => {
|
||||
current_line.line_col = line.graphemes(true).count();
|
||||
let width = utils::display_width(&line);
|
||||
current_line.line_col = width;
|
||||
current_line.line = line;
|
||||
current_line.cursor_col =
|
||||
std::cmp::min(width as u16, *win_cols - current_line.prompt_len as u16);
|
||||
}
|
||||
None => {
|
||||
// the "no-no" ding
|
||||
print!("\x07");
|
||||
}
|
||||
}
|
||||
current_line.cursor_col = std::cmp::min(
|
||||
current_line.line.graphemes(true).count() as u16,
|
||||
*win_cols - current_line.prompt_len as u16,
|
||||
);
|
||||
state.display_current_input_line(true)?;
|
||||
return Ok(false);
|
||||
}
|
||||
@ -578,18 +591,17 @@ async fn handle_event(
|
||||
// go down one command in history
|
||||
match command_history.get_next() {
|
||||
Some(line) => {
|
||||
current_line.line_col = line.graphemes(true).count();
|
||||
let width = utils::display_width(&line);
|
||||
current_line.line_col = width;
|
||||
current_line.line = line;
|
||||
current_line.cursor_col =
|
||||
std::cmp::min(width as u16, *win_cols - current_line.prompt_len as u16);
|
||||
}
|
||||
None => {
|
||||
// the "no-no" ding
|
||||
print!("\x07");
|
||||
}
|
||||
}
|
||||
current_line.cursor_col = std::cmp::min(
|
||||
current_line.line.graphemes(true).count() as u16,
|
||||
*win_cols - current_line.prompt_len as u16,
|
||||
);
|
||||
state.display_current_input_line(true)?;
|
||||
return Ok(false);
|
||||
}
|
||||
@ -618,11 +630,10 @@ async fn handle_event(
|
||||
if state.search_mode {
|
||||
return Ok(false);
|
||||
}
|
||||
current_line.line_col = current_line.line.graphemes(true).count();
|
||||
current_line.cursor_col = std::cmp::min(
|
||||
current_line.line.graphemes(true).count() as u16,
|
||||
*win_cols - current_line.prompt_len as u16,
|
||||
);
|
||||
let width = utils::display_width(¤t_line.line);
|
||||
current_line.line_col = width;
|
||||
current_line.cursor_col =
|
||||
std::cmp::min(width as u16, *win_cols - current_line.prompt_len as u16);
|
||||
}
|
||||
//
|
||||
// CTRL+R: enter search mode
|
||||
@ -661,7 +672,7 @@ async fn handle_event(
|
||||
KeyCode::Char(c) => {
|
||||
current_line.insert_char(c);
|
||||
if (current_line.cursor_col + current_line.prompt_len as u16) < *win_cols {
|
||||
current_line.cursor_col += 1;
|
||||
current_line.cursor_col += utils::display_width(&c.to_string()) as u16;
|
||||
}
|
||||
current_line.line_col += 1;
|
||||
}
|
||||
@ -671,18 +682,17 @@ async fn handle_event(
|
||||
KeyCode::Backspace => {
|
||||
if current_line.line_col == 0 {
|
||||
return Ok(false);
|
||||
} else {
|
||||
current_line.line_col -= 1;
|
||||
let c = current_line.delete_char();
|
||||
current_line.cursor_col -= utils::display_width(&c) as u16;
|
||||
}
|
||||
if current_line.cursor_col as usize > 0 {
|
||||
current_line.cursor_col -= 1;
|
||||
}
|
||||
current_line.line_col -= 1;
|
||||
current_line.delete_char();
|
||||
}
|
||||
//
|
||||
// DELETE: delete a single character at right of cursor
|
||||
//
|
||||
KeyCode::Delete => {
|
||||
if current_line.line_col == current_line.line.graphemes(true).count() {
|
||||
if current_line.line_col == utils::display_width(¤t_line.line) {
|
||||
return Ok(false);
|
||||
}
|
||||
current_line.delete_char();
|
||||
@ -702,7 +712,10 @@ async fn handle_event(
|
||||
} else {
|
||||
// simply move cursor and line position left
|
||||
execute!(stdout, cursor::MoveLeft(1))?;
|
||||
current_line.cursor_col -= 1;
|
||||
current_line.cursor_col -= current_line
|
||||
.current_char_left()
|
||||
.map_or_else(|| 1, |c| utils::display_width(&c))
|
||||
as u16;
|
||||
current_line.line_col -= 1;
|
||||
return Ok(false);
|
||||
}
|
||||
@ -711,7 +724,7 @@ async fn handle_event(
|
||||
// RIGHT: move cursor one spot right
|
||||
//
|
||||
KeyCode::Right => {
|
||||
if current_line.line_col == current_line.line.graphemes(true).count() {
|
||||
if current_line.line_col == utils::display_width(¤t_line.line) {
|
||||
// at the very end of the current typed line
|
||||
return Ok(false);
|
||||
};
|
||||
@ -719,7 +732,10 @@ async fn handle_event(
|
||||
{
|
||||
// simply move cursor and line position right
|
||||
execute!(stdout, cursor::MoveRight(1))?;
|
||||
current_line.cursor_col += 1;
|
||||
current_line.cursor_col += current_line
|
||||
.current_char_right()
|
||||
.map_or_else(|| 1, |c| utils::display_width(&c))
|
||||
as u16;
|
||||
current_line.line_col += 1;
|
||||
return Ok(false);
|
||||
} else {
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
io::{BufWriter, Stdout, Write},
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
pub struct RawMode;
|
||||
impl RawMode {
|
||||
@ -124,10 +125,14 @@ pub fn splash(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn display_width(s: &str) -> usize {
|
||||
UnicodeWidthStr::width(s)
|
||||
}
|
||||
|
||||
/// produce command line prompt and its length
|
||||
pub fn make_prompt(our_name: &str) -> (&'static str, usize) {
|
||||
let prompt = Box::leak(format!("{} > ", our_name).into_boxed_str());
|
||||
(prompt, prompt.graphemes(true).count())
|
||||
(prompt, display_width(prompt))
|
||||
}
|
||||
|
||||
pub fn cleanup(quit_msg: &str) {
|
||||
@ -234,10 +239,11 @@ pub fn underline(s: &str, to_underline: &str) -> (String, u16) {
|
||||
// format result string to have query portion underlined
|
||||
let mut result = s.to_string();
|
||||
let u_start = s.find(to_underline).unwrap();
|
||||
let u_end = u_start + to_underline.graphemes(true).count();
|
||||
let u_end = u_start + to_underline.len();
|
||||
result.insert_str(u_end, "\x1b[24m");
|
||||
result.insert_str(u_start, "\x1b[4m");
|
||||
(result, u_end as u16)
|
||||
let cursor_end = display_width(&result[..u_end]);
|
||||
(result, cursor_end as u16)
|
||||
}
|
||||
|
||||
/// if line is wider than the terminal, truncate it intelligently,
|
||||
@ -249,8 +255,8 @@ pub fn truncate_in_place(
|
||||
cursor_col: u16,
|
||||
show_end: bool,
|
||||
) -> String {
|
||||
let graphemes_count = s.graphemes(true).count();
|
||||
if graphemes_count <= term_width as usize {
|
||||
let width = display_width(s);
|
||||
if width <= term_width as usize {
|
||||
// no adjustment to be made
|
||||
return s.to_string();
|
||||
}
|
||||
@ -260,7 +266,7 @@ pub fn truncate_in_place(
|
||||
if show_end {
|
||||
// show end of line, truncate everything before
|
||||
s.graphemes(true)
|
||||
.skip(graphemes_count - term_width as usize)
|
||||
.skip(width - term_width as usize)
|
||||
.collect::<String>()
|
||||
} else if (cursor_col as usize) == line_col {
|
||||
// beginning of line is placed at left end, truncate everything past term_width
|
||||
|
Loading…
Reference in New Issue
Block a user