Skip to content

Commit 0ee2061

Browse files
authored
Avoid copying fragments (#3136)
* Avoid copying fragments * Add slice / slices method * Better documentation for fragment and slice methods
1 parent afd292e commit 0ee2061

File tree

4 files changed

+42
-18
lines changed

4 files changed

+42
-18
lines changed

helix-core/src/selection.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,23 @@ impl Range {
222222

223223
// groupAt
224224

225+
/// Returns the text inside this range given the text of the whole buffer.
226+
///
227+
/// The returned `Cow` is a reference if the range of text is inside a single
228+
/// chunk of the rope. Otherwise a copy of the text is returned. Consider
229+
/// using `slice` instead if you do not need a `Cow` or `String` to avoid copying.
225230
#[inline]
226231
pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
227-
text.slice(self.from()..self.to()).into()
232+
self.slice(text).into()
233+
}
234+
235+
/// Returns the text inside this range given the text of the whole buffer.
236+
///
237+
/// The returned value is a reference to the passed slice. This method never
238+
/// copies any contents.
239+
#[inline]
240+
pub fn slice<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> RopeSlice<'b> {
241+
text.slice(self.from()..self.to())
228242
}
229243

230244
//--------------------------------
@@ -548,6 +562,10 @@ impl Selection {
548562
self.ranges.iter().map(move |range| range.fragment(text))
549563
}
550564

565+
pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
566+
self.ranges.iter().map(move |range| range.slice(text))
567+
}
568+
551569
#[inline(always)]
552570
pub fn iter(&self) -> std::slice::Iter<'_, Range> {
553571
self.ranges.iter()

helix-term/src/commands.rs

+19-13
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ fn trim_selections(cx: &mut Context) {
766766
.selection(view.id)
767767
.iter()
768768
.filter_map(|range| {
769-
if range.is_empty() || range.fragment(text).chars().all(|ch| ch.is_whitespace()) {
769+
if range.is_empty() || range.slice(text).chars().all(|ch| ch.is_whitespace()) {
770770
return None;
771771
}
772772
let mut start = range.from();
@@ -1289,12 +1289,12 @@ fn replace(cx: &mut Context) {
12891289

12901290
fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
12911291
where
1292-
F: Fn(Cow<str>) -> Tendril,
1292+
F: Fn(RopeSlice) -> Tendril,
12931293
{
12941294
let (view, doc) = current!(cx.editor);
12951295
let selection = doc.selection(view.id);
12961296
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1297-
let text: Tendril = change_fn(range.fragment(doc.text().slice(..)));
1297+
let text: Tendril = change_fn(range.slice(doc.text().slice(..)));
12981298

12991299
(range.from(), range.to(), Some(text))
13001300
});
@@ -1320,11 +1320,15 @@ fn switch_case(cx: &mut Context) {
13201320
}
13211321

13221322
fn switch_to_uppercase(cx: &mut Context) {
1323-
switch_case_impl(cx, |string| string.to_uppercase().into());
1323+
switch_case_impl(cx, |string| {
1324+
string.chunks().map(|chunk| chunk.to_uppercase()).collect()
1325+
});
13241326
}
13251327

13261328
fn switch_to_lowercase(cx: &mut Context) {
1327-
switch_case_impl(cx, |string| string.to_lowercase().into());
1329+
switch_case_impl(cx, |string| {
1330+
string.chunks().map(|chunk| chunk.to_lowercase()).collect()
1331+
});
13281332
}
13291333

13301334
pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
@@ -3903,8 +3907,8 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
39033907

39043908
let selection = doc.selection(view.id);
39053909
let mut fragments: Vec<_> = selection
3906-
.fragments(text)
3907-
.map(|fragment| Tendril::from(fragment.as_ref()))
3910+
.slices(text)
3911+
.map(|fragment| fragment.chunks().collect())
39083912
.collect();
39093913

39103914
let group = count
@@ -4510,8 +4514,8 @@ fn shell_keep_pipe(cx: &mut Context) {
45104514
let text = doc.text().slice(..);
45114515

45124516
for (i, range) in selection.ranges().iter().enumerate() {
4513-
let fragment = range.fragment(text);
4514-
let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) {
4517+
let fragment = range.slice(text);
4518+
let (_output, success) = match shell_impl(shell, input, Some(fragment)) {
45154519
Ok(result) => result,
45164520
Err(err) => {
45174521
cx.editor.set_error(err.to_string());
@@ -4542,7 +4546,7 @@ fn shell_keep_pipe(cx: &mut Context) {
45424546
fn shell_impl(
45434547
shell: &[String],
45444548
cmd: &str,
4545-
input: Option<&[u8]>,
4549+
input: Option<RopeSlice>,
45464550
) -> anyhow::Result<(Tendril, bool)> {
45474551
use std::io::Write;
45484552
use std::process::{Command, Stdio};
@@ -4564,7 +4568,9 @@ fn shell_impl(
45644568
};
45654569
if let Some(input) = input {
45664570
let mut stdin = process.stdin.take().unwrap();
4567-
stdin.write_all(input)?;
4571+
for chunk in input.chunks() {
4572+
stdin.write_all(chunk.as_bytes())?;
4573+
}
45684574
}
45694575
let output = process.wait_with_output()?;
45704576

@@ -4593,8 +4599,8 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
45934599
let text = doc.text().slice(..);
45944600

45954601
for range in selection.ranges() {
4596-
let fragment = range.fragment(text);
4597-
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) {
4602+
let fragment = range.slice(text);
4603+
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment)) {
45984604
Ok(result) => result,
45994605
Err(err) => {
46004606
cx.editor.set_error(err.to_string());

helix-term/src/commands/typed.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1291,8 +1291,8 @@ fn sort_impl(
12911291
let selection = doc.selection(view.id);
12921292

12931293
let mut fragments: Vec<_> = selection
1294-
.fragments(text)
1295-
.map(|fragment| Tendril::from(fragment.as_ref()))
1294+
.slices(text)
1295+
.map(|fragment| fragment.chunks().collect())
12961296
.collect();
12971297

12981298
fragments.sort_by(match reverse {

helix-term/src/ui/editor.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1029,8 +1029,8 @@ impl EditorView {
10291029
if doc
10301030
.selection(view.id)
10311031
.primary()
1032-
.fragment(doc.text().slice(..))
1033-
.width()
1032+
.slice(doc.text().slice(..))
1033+
.len_chars()
10341034
<= 1
10351035
{
10361036
return EventResult::Ignored(None);

0 commit comments

Comments
 (0)