A basic graphical text editor written in Haskell

I wanted to share a personal project that I’ve been working on for some time now: https://github.com/ssddq/editor

At the start, my goal was to learn how Vulkan and GPU programming/rendering worked in general; over time, this slowly morphed into trying to write a streaming-based text editor. Even though it currently lacks too many (indeed, any) features required to be useable, I thought it might be interesting enough to share in its present state.

The things that are most exciting about it (in my opinion) are:

  • Files are never loaded into memory; edits are instead tracked in an IntMap keyed on byte positions in the base file, which is used to generate a Stream (Of Char) IO () on demand.

  • With a streaming-based approach, line traversal requires knowing the positions of newline ('\n') characters in the base file, and typically you want to scan these into an array on load – this is surprisingly fast, but can still be a noticeable delay on very large files.

    Here, this is done asynchronously, and functions only block when they attempt to force a value in a part of the array that has not been initialized yet. This means that startup (and even editing near the start of the file!) is instant even on extremely large (> 1 GB) files, as long as you have at least 2 CPU cores.

12 Likes

Seems like an interesting concept, do you know of other visual editors that adopt this approach?

Also, how do you handle the possibility of very long lines?

I wonder what made you pick vulkan-api and wrapping VMA over vulkan and its stuff.

@danidiaz:

Seems like an interesting concept, do you know of other visual editors that adopt this approach?

I looked around (not thoroughly by any means!), but didn’t see anything that used quite the same approach. I did recently hear about some commercial software called UltraEdit that can handle large files, but I think it’s closed source and I haven’t tried it.

Also, how do you handle the possibility of very long lines?

Great question! I currently don’t, which means very long lines will cause line operations to block until the end of the file is reached in the initial scan. I did think about this, and my solution will likely be to just treat files as having a virtual newline character on every 32 KB boundary, if the block does not contain '\n'. This ends up being an extremely simple change code-wise, though I want to test it to see how it performs.

I do also want to eventually separate visual line traversal from actual line traversal, but this is a little more involved since information about where lines wrapped visually needs to flow back from the renderer.

@wiz:

I wonder what made you pick vulkan-api and wrapping VMA over vulkan and its stuff.

This is also a great question! I really didn’t have any good reason here. When I began, I reasoned I’d pick the library that was a 1:1 binding so I could follow traditional Vulkan tutorials more closely. At the point where I started finding manual memory management annoying, I was already fairly committed and figured I could just generate the VMA bindings I needed.

I’m definitely aware vulkan already has bindings to VMA (in fact, I used them as a reference!) and was probably the better choice to start with. That said, I certainly don’t regret the decision – I learned quite a bit doing it, and the FFI was very pleasant to work with once I got off the ground.

2 Likes