diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 83bab5e3020f0..b70d9ee6402af 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -222,9 +222,12 @@ impl Range { // groupAt + /// This returns a RopeSlice since returning a string might imply copying + /// the entire content within the range. If you really need the text in a + /// continuous slice, you can call Cow::.from(...). #[inline] - pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> { - text.slice(self.from()..self.to()).into() + pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> RopeSlice<'b> { + text.slice(self.from()..self.to()) } //-------------------------------- @@ -544,7 +547,7 @@ impl Selection { self.transform(|range| Range::point(range.cursor(text))) } - pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator> + 'a { + pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator + 'a { self.ranges.iter().map(move |range| range.fragment(text)) } @@ -611,7 +614,7 @@ pub fn keep_or_remove_matches( ) -> Option { let result: SmallVec<_> = selection .iter() - .filter(|range| regex.is_match(&range.fragment(text)) ^ remove) + .filter(|range| regex.is_match(&Cow::from(range.fragment(text))) ^ remove) .copied() .collect(); @@ -636,7 +639,7 @@ pub fn select_on_matches( let sel_start = sel.from(); let start_byte = text.char_to_byte(sel_start); - for mat in regex.find_iter(&fragment) { + for mat in regex.find_iter(&Cow::from(fragment)) { // TODO: retain range direction let start = text.byte_to_char(start_byte + mat.start()); @@ -678,7 +681,7 @@ pub fn split_on_matches( let mut start = sel_start; - for mat in regex.find_iter(&fragment) { + for mat in regex.find_iter(&Cow::from(fragment)) { // TODO: retain range direction let end = text.byte_to_char(start_byte + mat.start()); result.push(Range::new(start, end)); @@ -1024,7 +1027,10 @@ mod test { ); assert_eq!( - result.fragments(text.slice(..)).collect::>(), + result + .fragments(text.slice(..)) + .map(Cow::from) + .collect::>(), &["", "abcd", "efg", "rs", "xyz"] ); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3ee75f6a392f2..9d224f839ae62 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1287,7 +1287,7 @@ fn replace(cx: &mut Context) { fn switch_case_impl(cx: &mut Context, change_fn: F) where - F: Fn(Cow) -> Tendril, + F: Fn(RopeSlice) -> Tendril, { let (view, doc) = current!(cx.editor); let selection = doc.selection(view.id); @@ -1318,11 +1318,15 @@ fn switch_case(cx: &mut Context) { } fn switch_to_uppercase(cx: &mut Context) { - switch_case_impl(cx, |string| string.to_uppercase().into()); + switch_case_impl(cx, |string| { + string.chunks().map(|chunk| chunk.to_uppercase()).collect() + }); } fn switch_to_lowercase(cx: &mut Context) { - switch_case_impl(cx, |string| string.to_lowercase().into()); + switch_case_impl(cx, |string| { + string.chunks().map(|chunk| chunk.to_lowercase()).collect() + }); } pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { @@ -1748,7 +1752,7 @@ fn search_selection(cx: &mut Context) { let (view, doc) = current!(cx.editor); let contents = doc.text().slice(..); let query = doc.selection(view.id).primary().fragment(contents); - let regex = regex::escape(&query); + let regex = regex::escape(&Cow::from(query)); cx.editor.registers.get_mut('/').push(regex); let msg = format!("register '{}' set to '{}'", '/', query); cx.editor.set_status(msg); @@ -2044,7 +2048,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { if cx.register != Some('_') { // first yank the selection - let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); + let values: Vec = selection.fragments(text).map(String::from).collect(); let reg_name = cx.register.unwrap_or('"'); let registers = &mut cx.editor.registers; let reg = registers.get_mut(reg_name); @@ -3152,7 +3156,7 @@ fn yank(cx: &mut Context) { let values: Vec = doc .selection(view.id) .fragments(text) - .map(Cow::into_owned) + .map(String::from) .collect(); let msg = format!( @@ -3180,7 +3184,7 @@ fn yank_joined_to_clipboard_impl( let values: Vec = doc .selection(view.id) .fragments(text) - .map(Cow::into_owned) + .map(String::from) .collect(); let msg = format!( @@ -3218,7 +3222,7 @@ fn yank_main_selection_to_clipboard_impl( if let Err(e) = editor .clipboard_provider - .set_contents(value.into_owned(), clipboard_type) + .set_contents(String::from(value), clipboard_type) { bail!("Couldn't set system clipboard content: {}", e); } @@ -3789,7 +3793,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) { let selection = doc.selection(view.id); let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from(fragment.as_ref())) + .map(|fragment| fragment.chunks().collect()) .collect(); let group = count @@ -4398,13 +4402,14 @@ fn shell_keep_pipe(cx: &mut Context) { for (i, range) in selection.ranges().iter().enumerate() { let fragment = range.fragment(text); - let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) { - Ok(result) => result, - Err(err) => { - cx.editor.set_error(err.to_string()); - return; - } - }; + let (_output, success) = + match shell_impl(shell, input, Some(fragment.chunks().map(str::as_bytes))) { + Ok(result) => result, + Err(err) => { + cx.editor.set_error(err.to_string()); + return; + } + }; // if the process exits successfully, keep the selection if success { @@ -4426,11 +4431,14 @@ fn shell_keep_pipe(cx: &mut Context) { ); } -fn shell_impl( +fn shell_impl<'a, I>( shell: &[String], cmd: &str, - input: Option<&[u8]>, -) -> anyhow::Result<(Tendril, bool)> { + input: Option, +) -> anyhow::Result<(Tendril, bool)> +where + I: Iterator, +{ use std::io::Write; use std::process::{Command, Stdio}; ensure!(!shell.is_empty(), "No shell set"); @@ -4451,7 +4459,9 @@ fn shell_impl( }; if let Some(input) = input { let mut stdin = process.stdin.take().unwrap(); - stdin.write_all(input)?; + for chunk in input { + stdin.write_all(chunk)?; + } } let output = process.wait_with_output()?; @@ -4481,7 +4491,11 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) { for range in selection.ranges() { let fragment = range.fragment(text); - let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) { + let (output, success) = match shell_impl( + shell, + cmd, + pipe.then(|| fragment.chunks().map(str::as_bytes)), + ) { Ok(result) => result, Err(err) => { cx.editor.set_error(err.to_string()); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index d6db117e67e4e..254760cb4392a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1292,7 +1292,7 @@ fn sort_impl( let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from(fragment.as_ref())) + .map(|fragment| fragment.chunks().collect()) .collect(); fragments.sort_by(match reverse { @@ -1346,7 +1346,7 @@ fn reflow( let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(rope, selection, |range| { let fragment = range.fragment(rope.slice(..)); - let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, max_line_len); + let reflowed_text = helix_core::wrap::reflow_hard_wrap(&Cow::from(fragment), max_line_len); (range.from(), range.to(), Some(reflowed_text)) }); @@ -1485,7 +1485,7 @@ fn run_shell_command( } let shell = &cx.editor.config().shell; - let (output, success) = shell_impl(shell, &args.join(" "), None)?; + let (output, success) = shell_impl::>(shell, &args.join(" "), None)?; if success { cx.editor.set_status("Command succeed"); } else { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 6ed9799b4ae39..4c26e14e2d404 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -12,7 +12,7 @@ use helix_core::{ }, movement::Direction, syntax::{self, HighlightEvent}, - unicode::width::UnicodeWidthStr, + unicode::width::{UnicodeWidthChar, UnicodeWidthStr}, LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ @@ -1030,8 +1030,10 @@ impl EditorView { .selection(view.id) .primary() .fragment(doc.text().slice(..)) - .width() - <= 1 + .chars() + .map(|c| c.width().unwrap_or(0)) + .try_fold(0, |acc, w| if acc + w <= 1 { Some(acc + w) } else { None }) + .is_some() { return EventResult::Ignored(None); }