SIGN IN SIGN UP

input: Rewrap lines without copying them out of the rope (#2783)

## Description

Previously the code copied each line twice, once at
`Rope::from(changed_text.slice(new_range))` and then again at
`line.to_string()`. Now it is doing essentially the same as the rope
iter_lines(), but without copying it into a new rope, and instead of
copying the line it is borrowing it when it can.
This affects all re-wrapping, like opening files, resizing and editing.

  
  | branch                                |     bytes      | blocks   |
  |-------------------------------------|---------------- |------------|
  | main                                   | 31,602,003 | 82,470 |
  | reduce-wrapper-alloc      | 20,632,839 | 8,342   |
  
## How to Test

I'm using this test with dhat, maybe it's worth committing?
```rust
use gpui::{
    AppContext as _, Bounds, Context, Entity, IntoElement, ParentElement as _, Render, Styled as _,
    TestAppContext, VisualTestContext, Window, WindowBounds, WindowOptions, div, px, size,
};
use gpui_component::input::{Editor, EditorState};

#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;

const FRAMES: usize = 50;
const FIXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../story/examples/fixtures");

struct Root(Entity<EditorState>);

impl Render for Root {
    fn render(&mut self, _: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div().size_full().child(Editor::new(&self.0))
    }
}

fn language_for(ext: &str) -> &'static str {
    match ext {
        "rs" => "rust",
        "md" => "markdown",
        "js" => "javascript",
        "ts" => "typescript",
        "py" => "python",
        "rb" => "ruby",
        "kt" => "kotlin",
        "c" => "c",
        "go" => "go",
        "html" => "html",
        "json" => "json",
        "sql" => "sql",
        "zig" => "zig",
        "lua" => "lua",
        "php" => "php",
        _ => "",
    }
}

#[gpui::test]
fn measure_editor_allocations_per_fixture(cx: &mut TestAppContext) {
    let mut fixtures: Vec<(String, String, String)> = std::fs::read_dir(FIXTURES)
        .expect("fixtures dir")
        .filter_map(|entry| {
            let path = entry.ok()?.path();
            let ext = path.extension()?.to_str()?.to_string();
            let language = language_for(&ext);
            if language.is_empty() {
                return None;
            }
            let name = path.file_name()?.to_str()?.to_string();
            let content = std::fs::read_to_string(&path).ok()?;
            Some((name, language.to_string(), content))
        })
        .collect();
    // A synthetic large file (test.rs repeated, ~20k lines)
    if let Some(rust) = fixtures.iter().find(|(name, _, _)| name == "test.rs") {
        fixtures.push(("test.rs x20".into(), "rust".into(), rust.2.repeat(20)));
    }
    // Largest first so the headline number is up top.
    fixtures.sort_by_key(|(_, _, content)| std::cmp::Reverse(content.len()));

    cx.update(gpui_component::init);
    println!("== alloc_dhat: per-fixture, load + {FRAMES} draw frames, 800x600, soft wrap ==");

    for (name, language, content) in fixtures {
        let lines = content.lines().count();
        let mut state = None;
        let window = cx.update(|cx| {
            cx.open_window(
                WindowOptions {
                    window_bounds: Some(WindowBounds::Windowed(Bounds {
                        origin: Default::default(),
                        size: size(px(800.), px(600.)),
                    })),
                    ..Default::default()
                },
                |window, cx| {
                    let editor = cx.new(|cx| {
                        EditorState::new(window, cx)
                            .language(language.clone())
                            .soft_wrap(true)
                    });
                    state = Some(editor.clone());
                    cx.new(|_| Root(editor))
                },
            )
            .unwrap()
        });
        let state = state.unwrap();
        let mut cx = VisualTestContext::from_window(window.into(), cx);

        // Warm up an empty draw so window/theme one-time costs stay out.
        cx.update(|window, cx| window.draw(cx).clear(cx));

        let _profiler = dhat::Profiler::builder().testing().build();
        let before_load = dhat::HeapStats::get();
        cx.update(|window, cx| {
            state.update(cx, |state, cx| state.set_value(content.clone(), window, cx));
            window.draw(cx).clear(cx);
        });
        let after_load = dhat::HeapStats::get();

        let step = (content.len() / FRAMES).max(1);
        for frame in 0..FRAMES {
            cx.update(|_, cx| {
                state.update(cx, |state, cx| {
                    let offset = (frame * step).min(content.len());
                    state.set_selected_range(offset..offset, cx);
                    cx.notify();
                });
            });
            cx.update(|window, cx| window.draw(cx).clear(cx));
        }
        let after_draws = dhat::HeapStats::get();

        println!(
            "{name:>12} ({language}, {lines} lines): load {:>10} bytes / {:>6} blocks, {FRAMES} draws {:>10} bytes / {:>6} blocks",
            after_load.total_bytes - before_load.total_bytes,
            after_load.total_blocks - before_load.total_blocks,
            after_draws.total_bytes - after_load.total_bytes,
            after_draws.total_blocks - after_load.total_blocks,
        );
        drop(_profiler);
    }
}
```

## Checklist

- [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [x] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)
A
Andreas Johansson committed
16274ece3203f06d73637ce6873bdccae9b8eb12
Parent: 06a5b0e
Committed by GitHub <noreply@github.com> on 8/20/2026, 10:34:41 AM