{"id":42945757,"url":"https://github.com/noib3/crop","last_synced_at":"2026-03-27T02:44:42.631Z","repository":{"id":84212574,"uuid":"556004294","full_name":"noib3/crop","owner":"noib3","description":"🌾 A pretty fast text rope","archived":false,"fork":false,"pushed_at":"2026-01-13T11:00:14.000Z","size":1056,"stargazers_count":307,"open_issues_count":7,"forks_count":18,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-20T05:52:03.765Z","etag":null,"topics":["data-structures","ropes","text-editing","text-editors"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/crop","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noib3.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-22T21:05:05.000Z","updated_at":"2026-01-13T11:01:45.000Z","dependencies_parsed_at":"2023-11-12T02:24:26.313Z","dependency_job_id":"28a62bb4-ba16-407f-8b27-973a2bc8d589","html_url":"https://github.com/noib3/crop","commit_stats":null,"previous_names":["nomad/crop","noib3/crop"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/noib3/crop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noib3%2Fcrop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noib3%2Fcrop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noib3%2Fcrop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noib3%2Fcrop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noib3","download_url":"https://codeload.github.com/noib3/crop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noib3%2Fcrop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28918532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T20:25:28.696Z","status":"ssl_error","status_checked_at":"2026-01-30T20:25:13.426Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-structures","ropes","text-editing","text-editors"],"created_at":"2026-01-30T20:35:48.330Z","updated_at":"2026-01-30T20:35:48.475Z","avatar_url":"https://github.com/noib3.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🌾 crop\n\n[![Latest version]](https://crates.io/crates/crop)\n[![Docs badge]](https://docs.rs/crop)\n[![CI]](https://github.com/noib3/crop/actions)\n\n[Latest version]: https://img.shields.io/crates/v/crop.svg\n[Docs badge]: https://docs.rs/crop/badge.svg\n[CI]: https://github.com/noib3/crop/actions/workflows/ci.yml/badge.svg\n\ncrop is an implementation of a text rope, a data structure designed to be used\nin applications that need to handle frequent edits to arbitrarily large\nbuffers, such as text editors.\n\ncrop's `Rope` is backed by a [B-tree](https://en.wikipedia.org/wiki/B-tree),\nensuring that the time complexity of inserting, deleting or replacing a piece\nof text is always logarithmic in the size of the `Rope`.\n\ncrop places an extreme focus on performance: check out [the\nbenchmarks][synthetic-benches] to see how it stacks up against similar\nprojects.\n\n## Built with parallelism in mind\n\n`Rope`s use thread-safe reference counting to share data between threads.\nCloning a `Rope` takes up only 16 extra bytes of memory, and its copy-on-write\nsemantics allow the actual text contents to be cloned incrementally as\ndifferent clones diverge due to user edits.\n\nThis allows to cheaply snapshot a `Rope` and send it to a background thread to\nperform any IO or CPU-intensive computations, while the main thread is kept\nresponsive and always ready for the next batch of edits.\n\n## Example usage\n\n```rust\n// A `Rope` can be created either directly from a string or incrementally\n// using the `RopeBuilder`.\n\nlet mut builder = RopeBuilder::new();\n\nbuilder\n    .append(\"I am a 🦀\\n\")\n    .append(\"Who walks the shore\\n\")\n    .append(\"And pinches toes all day.\\n\")\n    .append(\"\\n\")\n    .append(\"If I were you\\n\")\n    .append(\"I'd wear some 👟\\n\")\n    .append(\"And not get in my way.\\n\");\n\nlet mut rope: Rope = builder.build();\n\n// `Rope`s can be sliced to obtain `RopeSlice`s.\n//\n// A `RopeSlice` is to a `Rope` as a `\u0026str` is to a `String`: the former in\n// each pair is a borrowed reference of the latter.\n\n// A `Rope` can be sliced using either byte offsets:\n\nlet byte_slice: RopeSlice = rope.byte_slice(..32);\n\nassert_eq!(byte_slice, \"I am a 🦀\\nWho walks the shore\\n\");\n\n// or line offsets:\n\nlet line_slice: RopeSlice = rope.line_slice(..2);\n\nassert_eq!(line_slice, byte_slice);\n\n// We can also get a `RopeSlice` by asking the `Rope` for a specific line\n// index:\n\nassert_eq!(rope.line(5), \"I'd wear some 👟\");\n\n// We can modify that line by getting its start/end byte offsets:\n\nlet start: usize = rope.byte_of_line(5);\n\nlet end: usize = rope.byte_of_line(6);\n\n// and replacing that byte range with some other text:\n\nrope.replace(start..end, \"I'd rock some 👠\\n\");\n\nassert_eq!(rope.line(5), \"I'd rock some 👠\");\n\n// `Rope`s use `Arc`s to share data between threads, so cloning them is\n// extremely cheap.\n\nlet snapshot: Rope = rope.clone();\n\n// This allows to save a `Rope` to disk in a background thread while\n// keeping the main thread responsive.\n\nthread::spawn(move || {\n    let mut file =\n        BufWriter::new(File::create(\"my_little_poem.txt\").unwrap());\n\n    // The text content is stored as separate chunks in the leaves of the\n    // B-tree.\n    //\n    // We can iterate over them using the `Chunks` iterator which yields the\n    // chunks of the `Rope` as string slices.\n\n    for chunk in snapshot.chunks() {\n        file.write_all(chunk.as_bytes()).unwrap();\n    }\n})\n.join()\n.unwrap();\n```\n\nCheck out [the docs](https://docs.rs/crop) for a more in-depth overview of the\ncrate.\n\n## Comparison with other ropes\n\nAs of April 2023 there are (to my knowledge) 3 rope crates that are still\nactively maintained: crop, [Jumprope][jumprope] and [Ropey][ropey]. The\nfollowing is a quick (and incomplete) overview of their features and tradeoffs\nto help you decide which one is best suited for your specific use case.\n\n### Speed\n\nThe following results were obtained by running the real world,\ncharacter-by-character editing traces provided by [crdt-benchmarks] on a 2018\nMacBook Pro with an Intel Core i7.\n\n| Dataset         | crop (ms) | Jumprope (ms) | Ropey (ms) | `std::string::String` (ms) |\n|-----------------|-----------|---------------|------------|----------------------------|\n| automerge-paper | 12.39     | 12.52         | 44.14      | 108.57                     |\n| rustcode        | 2.67      | 2.86          | 7.96       | 13.40                      |\n| sveltecomponent | 0.95      | 1.08          | 3.65       | 1.22                       |\n| seph-blog1      | 6.47      | 6.94          | 23.46      | 22.26                      |\n\n### Cheap clones\n\nBoth crop and Ropey allow their `Rope`s to be cloned in `O(1)` in time and\nspace by sharing data between clones, whereas cloning a `JumpRope` involves\nre-allocating the actual text contents, just like it would with a regular\n`String`.\n\n### Indexing metric\n\nJumprope and Ropey both use Unicode codepoint offsets (`char`s in Rust) as\ntheir primary indexing metric. crop uses UTF-8 code unit (aka byte) offsets,\njust like Rust's `String`s.\n\n### Line breaks\n\nBoth crop and Ropey track line breaks, allowing you to convert between line and\nbyte offsets and to iterate over the lines of their `Rope`s and `RopeSlice`s.\nRopey can be configured to recognize all Unicode line breaks, while crop only\nrecognizes LF and CRLF as line terminators.\n\nJumprope doesn't currently have any line-based APIs.\n\n\n## Acknowledgements\n\n- A significant portion of crop's public API was inspired by the excellent\n  Ropey crate (from which I also borrowed some test vectors). Unlike crop,\n  Ropey uses code points (`char`s in Rust-speak) as its primary indexing\n  metric. If you'd prefer to work with `char` offsets rather than byte offsets\n  Ropey might be a great alternative;\n\n- Even though the implementations are quite different, crop's\n  [`Metric`][crop-metric] trait was inspired by the [homonymous trait in\n  xi_rope][xi-rope-metric]. Check out the [second blog post][rope-science-2] in\n  the \"Rope science\" series by Raph Levien for more infos.\n\n[crdt-benchmarks]: https://github.com/josephg/crdt-benchmarks\n[crop-metric]: https://github.com/noib3/crop/blob/21638ed46864b140ad52f41449f1274b15ca3eb2/src/tree/traits.rs#L71-L92\n[jumprope]: https://github.com/josephg/jumprope-rs\n[rope-science-2]: https://xi-editor.io/docs/rope_science_02.html\n[ropey]: https://github.com/cessen/ropey\n[synthetic-benches]: https://github.com/noib3/crop/blob/main/BENCHMARKS.md\n[xi-rope-metric]: https://docs.rs/xi-rope/latest/xi_rope/tree/trait.Metric.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoib3%2Fcrop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoib3%2Fcrop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoib3%2Fcrop/lists"}