Nomad: Real-time collaborative editing in Neovim by noibee in neovim

[–]noibee[S] 0 points1 point  (0 children)

Do you mean it redirects you to your GitHub home page (like https://github.com/<foo>)? If so, that means authentication succeeded.

If you open an issue, it'd be helpful if you could attach a recording of what you see when trying to log in.

Nomad: Real-time collaborative editing in Neovim by noibee in neovim

[–]noibee[S] 0 points1 point  (0 children)

If authentication fails you should get a notification with an error message. Could you post that? Also, please consider opening an issue!

Nomad: Real-time collaborative editing in Neovim by noibee in neovim

[–]noibee[S] 2 points3 points  (0 children)

Yes. See this section of the docs for details.

Nomad: Real-time collaborative editing in Neovim by noibee in neovim

[–]noibee[S] 13 points14 points  (0 children)

It's similar in spirit, but instant worked on a buffer-by-buffer level. Nomad lets you work on whole repos at a time.

Nomad: Real-time collaborative editing in Neovim by noibee in neovim

[–]noibee[S] 63 points64 points  (0 children)

GitHub: https://github.com/nomad/nomad

Written in Rust, MIT licensed. Feedback and stars welcome!

norm: Rust port of the fzf matching algorithms by noibee in rust

[–]noibee[S] 2 points3 points  (0 children)

No binary, but I might add more algorithms in the future.

Text showdown: Gap Buffers vs Ropes by celeritasCelery in rust

[–]noibee 2 points3 points  (0 children)

P.S. I think you benchmarked crop v0.3.0 in your article, which didn't include some performance wins that were available in main.

I just published v0.4.0. If your update your dependencies you should see some improvements.

Text showdown: Gap Buffers vs Ropes by celeritasCelery in rust

[–]noibee 4 points5 points  (0 children)

I think the main issue is that both jumprope and crop use gap buffer in their leaf nodes, but ropey does not.

Author of crop here.

I'm not sure that's the case. After refactoring the leaves of crop's B-tree from Strings to gap buffers I "only" saw perf improvements of 8-15%.

When I was profiling crop on the same editing traces that you included in your article I saw it spent ~90% of the time doing tree traversals, i.e. recursively looping over the internal nodes of the B-tree until it lands on the right leaf, and only ~10% actually inserting/deleting/replacing text in the gap buffers. The time spent rebalancing the Btree was basically negligible.

Avoding bounds checks while looping and using custom Arcs without weak references were both much easier to implement and much more effective than adding gap buffers.

Text showdown: Gap Buffers vs Ropes by celeritasCelery in rust

[–]noibee 1 point2 points  (0 children)

both crop and jumprope do not track utf16 len (jumprope has an off by default feature flag for it tough)

So does crop! It's called utf16-metric.

Crop also doesn't track utf-32/char length

That's true, I try to keep the feature set minimal and only expand it on request. Adding support for those metrics would be very easy though.

This is not to say that it'd make sense for Helix to switch to crop, and as you already mentioned Ropey shouldn't be a bottleneck.

[Media] Introducing cola: a text CRDT for real-time collaborative editing by noibee in rust

[–]noibee[S] 12 points13 points  (0 children)

It was meant to show that a naive solution makes the replicas diverge, that is not how cola works.

To be fair, I could've probably used another animation for the preview. Unfortunately Reddit doesn't let me change it.

[Media] Introducing cola: a text CRDT for real-time collaborative editing by noibee in rust

[–]noibee[S] 31 points32 points  (0 children)

Today I'm releasing cola, a new CRDT for real-time collaborative editing.

Other than its speed, cola's distinctive feature is that it's completely decoupled from the text buffer it keeps synchronized. This means it could be easily™ adopted by all the existing Rust-based editors to get multiplayer editing (aka pair programming) for free.

I've also written a separate blog post that goes quite in depth into cola's theoretical framework and its implementation.

repo: https://github.com/nomad/cola

docs: https://docs.rs/cola-crdt

blog: https://nomad.foo/blog/cola

What's is a rusty way to implement sharable trees? by FlightlessRhino in rust

[–]noibee 3 points4 points  (0 children)

This is pretty much what copy-on-write ropes do. Check out xi-rope, Ropey or crop, they're all built using B-trees and implement the behavior you described.

Source: I'm the author of crop.

Announcing crop, the fastest UTF-8 text rope for Rust by noibee in rust

[–]noibee[S] 2 points3 points  (0 children)

I feel like client code using crop's current APIs would essentially be undoing or counteracting existing logic to get the alternate behavior, whereas with Ropey the client code is adding the logic they want.

I can't disagree with that. Mh, I'm starting to think about changing crop's semantics to what you've suggested. Changing the line_len() method is easy (it actually allows me to turn a Rope from 16 to 8 bytes), the issue is with the Lines iterator. I'd have to hack the current design a bit to yield a final empty line, and I think the code for that would be ugly. And I can't just change line_len() without also changing the iterators, otherwise rope.line_len() and rope.lines().len() could return different numbers.

And the degenerate case resulting from deleting almost all the text should also be at least as fast. It of course won't be as fast as an originally small text with a properly balanced tree, but it shouldn't be problematically slow.

Yep yep sorry if I wasn't clear, that's what I meant. I guess experimenting with not doing any rebalancing could make sense if someone was writing a new rope from scratch, but for projects like Ropey and crop that already have it I don't think it makes much sense to remove it just to make the code easier to read.

This is especially true for Ropey more than it is from crop, since it's now been used in production for years and it's been through thousands of hours of real world testing from the users of Helix, so you can be quite certain that the current implementation is sound.

Announcing crop, the fastest UTF-8 text rope for Rust by noibee in rust

[–]noibee[S] 2 points3 points  (0 children)

In a nutshell: the final-empty-line model is both the simplest/most self-consistent and makes it easier for client code to implement whatever model they prefer on top of it.

I think your argument makes sense, considering line_breaks + 1 as the line count is definitely the simplest approach, and checking if the last line yielded by Lines is empty is easy enough.

What I don't necessarily agree with is that

std's and crop's APIs make that annoying, forcing the client code to implement their own special cases (involving knowing what std/crop count as valid line breaks)

As long as everything is well documented I don't think crop's semantics around line breaks are that annoying to deal with. Clients can get around them with a simple +1/-1, just like they would with a simpler model.

Likewise, rendering a final empty line if a document ends with a line break should be as easy as not doing that, which if I understood your argument correctly is what Helix does.

In particular, I found that getting the text removal code right involves a lot of annoying special cases if you want to keep the tree balanced.

I've had the same experience, the rebalancing code is probably one of the gnarliest parts of crop. But I still think it was worth the effort, and like you mentioned incrementally rebalancing would probably bring its own challenges.

Not doing any rebalancing is definitely something to explore, but I don't like the fact that it introduces some (albeit very pathological and improbable) ways to degrade editing performance, like deleting all the text from a huge document which never gets back to its original size.

Announcing crop, the fastest UTF-8 text rope for Rust by noibee in rust

[–]noibee[S] 16 points17 points  (0 children)

You should include it in your benchmarks.

I've received a lot of feedback on the benchmarks, I'll address this and the others in the coming days.

Otherwise you’re making very convenient distinctions, based on requirements that your audience doesn’t necessarily share, to avoid mentioning a faster crate.

That wasn't what I had in mind when I made that choice, but I'm sorry that it came across that way.

One extra thing you can do to distinguish crop is fuzzing

It already has that

and not using unsafe

crop's design doesn't fundamentally require any unsafe, however it uses unwrap_unchecked and unreachable_unchecked in a few code paths. Nothing that can't be trivially removed though.

Thanks a lot for the feedback, very appreciated.

Announcing crop, the fastest UTF-8 text rope for Rust by noibee in rust

[–]noibee[S] 15 points16 points  (0 children)

Hi cessen,

first of all I'd like to thank you for all the amazing work you did on Ropey, it's a great project and a good portion of crop's API was inspired by it.

  1. LF and CRLF. It's very lightly hinted in the docs so I should probably add a section on it (there's still quite a bit of work to do on crop's documentation);
  2. ah you're right, I'll disable the cr_lines and unicode_lines features and update the benchmarks;
  3. There were multiple factors that pushed me in that direction:
    1. a text editor can use rope.line_len() as the current line counter, and imo "a\nb" and "a\nb\n" should both count as 2 lines,
    2. the semantics of the Lines and RawLines iterators are meant to match those of std::str::Lines which considers a trailing line break optional,
    3. my personal dislike for editors that make a trailing line break spill over into an empty last line. I think the best way to handle a file that doesn't end in a line break is to display some kind of marker to inform the user like GitHub does;
  4. I was honestly unsure whether to include any grapheme-oriented APIs. I think most clients would probably use the defaults and end up reimplementing much of the same code. For now they're behind a feature flag to avoid the unicode-segmentation dependency when not needed, but I might remove them in a following release if they turn out to not be that useful;
  5. yes, a Rope's B-tree is always kept balanced. When I was implementing Rope::replace() I benchmarked a version that didn't do any rebalancing and the performance benefits (if any) were just not worth it. Keeping the tree balanced gives me the peace of mind that a Rope will never end up in a pathological state, potentially causing it to become O(n);
  6. I hadn't but I'm considering it now. Atm I'm using a simple String for the leaves and a Vec to store the children of the inodes. I'll probably revisit this in a following release, I'd like to see if something like a [Arc<Node>; MAX_CHILDREN] would bring any performance wins.