The two main options for a buffer struct is a gap buffer, or a rope. They both come with different tradeoffs. I really like this comment from the author of a Rust rope crate.
I don't think the tradeoff between gap buffers and ropes is really about file size. At small file sizes anything will work. You could just use a plain contiguous string if you wanted, and it would be totally fine. The whole point of using something like a gap buffer or rope or piece table or whatever is to avoid the shortcomings of something like a contiguous string as files get larger.
Gap buffers are actually great for large files, as long as the editing patterns are favorable. Specifically, for localized edits gap buffers are O(1), which is amazing. And for single-cursor editors, localized edits are pretty much the only case that needs to be handled interactively. So gap buffers are great (even with huge files) for such editors. However, gap buffers degrade to O(N) as edits get less and less localized. So if you want your editor to support non-localized editing patterns, gap buffers probably aren't great.
Ropes make a different performance trade-off, being a sort of "jack of all trades". They're not amazing at anything, but they're always solidly good with O(log N) performance. They're not the best choice for an editor that only supports local editing patterns, since they leave a lot of performance on the table compared to gap buffers in that case (again, even for huge documents). But for an editor that encourages non-localized edits, or just wants flexibility in that regard, they're a great choice because they always have good performance, whereas gap buffers degrade poorly with unfavorable editing patterns.
In other words, regardless of file size, gap buffers have both a better best case and worse worst case compared to ropes.
It's worth noting, however, that even multiple cursors are frequently quite localized. Most of the time I use them, for example, all cursors are still in view (or nearly so). It's really only with editors that are built around multiple cursors as the way to edit things that you commonly do interactive non-local edits.
So for something like emacs, I suspect any real arguments in favor of ropes or piece tables (or whatever else) aren't going to be about editing performance, but rather are going to be about secondary features like cheaply taking "snapshots" of a document to save it asynchronously, etc.
Like the author said, gap buffers have great performance. And they are much simpler then other data structures like ropes. It also makes things like regex much more performant.
Given all that, I think sticking with a gap buffer is the right choice. Most of complaints about Gap buffers is just misunderstanding.
edit: fixed misquote
The two main options for a buffer struct is a gap buffer, or a rope. They both come with different tradeoffs. I really like this comment from the author of a Rust rope crate.
Like the author said, gap buffers have great performance. And they are much simpler then other data structures like ropes. It also makes things like regex much more performant.
Given all that, I think sticking with a gap buffer is the right choice. Most of complaints about Gap buffers is just misunderstanding.
edit: fixed misquote