Skip to content

Commit

Permalink
Avoid copying fragments
Browse files Browse the repository at this point in the history
  • Loading branch information
MDeiml committed Jul 21, 2022
1 parent 8b2a141 commit f071e5b
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 34 deletions.
20 changes: 13 additions & 7 deletions helix-core/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<str>.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())
}

//--------------------------------
Expand Down Expand Up @@ -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<Item = Cow<str>> + 'a {
pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
self.ranges.iter().map(move |range| range.fragment(text))
}

Expand Down Expand Up @@ -611,7 +614,7 @@ pub fn keep_or_remove_matches(
) -> Option<Selection> {
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();

Expand All @@ -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());
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1024,7 +1027,10 @@ mod test {
);

assert_eq!(
result.fragments(text.slice(..)).collect::<Vec<_>>(),
result
.fragments(text.slice(..))
.map(Cow::from)
.collect::<Vec<_>>(),
&["", "abcd", "efg", "rs", "xyz"]
);
}
Expand Down
56 changes: 35 additions & 21 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ fn replace(cx: &mut Context) {

fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
where
F: Fn(Cow<str>) -> Tendril,
F: Fn(RopeSlice) -> Tendril,
{
let (view, doc) = current!(cx.editor);
let selection = doc.selection(view.id);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2044,7 +2048,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {

if cx.register != Some('_') {
// first yank the selection
let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
let values: Vec<String> = 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);
Expand Down Expand Up @@ -3152,7 +3156,7 @@ fn yank(cx: &mut Context) {
let values: Vec<String> = doc
.selection(view.id)
.fragments(text)
.map(Cow::into_owned)
.map(String::from)
.collect();

let msg = format!(
Expand Down Expand Up @@ -3180,7 +3184,7 @@ fn yank_joined_to_clipboard_impl(
let values: Vec<String> = doc
.selection(view.id)
.fragments(text)
.map(Cow::into_owned)
.map(String::from)
.collect();

let msg = format!(
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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<I>,
) -> anyhow::Result<(Tendril, bool)>
where
I: Iterator<Item = &'a [u8]>,
{
use std::io::Write;
use std::process::{Command, Stdio};
ensure!(!shell.is_empty(), "No shell set");
Expand All @@ -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()?;

Expand Down Expand Up @@ -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());
Expand Down
6 changes: 3 additions & 3 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
});
Expand Down Expand Up @@ -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::<std::iter::Empty<_>>(shell, &args.join(" "), None)?;
if success {
cx.editor.set_status("Command succeed");
} else {
Expand Down
8 changes: 5 additions & 3 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit f071e5b

Please sign in to comment.