Me porting Runestone from UIKit to AppKit.

Yay! Runestone now works with... *Checks notes*... UIKit?? 🤨

In order to prepare for AppKit support, I had to rip the internals of Runestone apart and put it together again. For the first time in 36 hours, Runestone now works with UIKit again.

So I now have an NSWindow with an NSView that receives keystrokes. That's like step 0 in building a text editor, right?

There's such a long way to go still before Runestone is rewritten in AppKit. I hope I'll eventually turn a corner where all my work from UIKit can be reused and I ✨magically✨ have an AppKit implementation.

Runestone just rendered its first text using AppKit. Baby steps, y'all.

The AppKit version of Runestone now supports scrolling the content.

This involves a bit more than just wrapping everything in an NSScrollView because Runestone only renders the lines within the viewport.

Baby steps, y'all.

Time to add a caret that shows where characters will be inserted. I'm a little bummed that I have to implement this myself. We get that for free in UIKit.
Runestone for AppKit now has a caret. Baby steps, y'all.

I need to implement all moving within lines myself 😑

In the screenshot I'm logging the selectors that I don't handle but that AppKit expects me to handle. This is something we get (almost) for free in UIKit. Honestly, I really don't want to write this logic.

Got navigation with the arrow keys working in Runestone for AppKit.

I figured out how to reuse some of the code from the UIKit implementation so this turned out to be much easier than anticipated.

Next up is adding support for jumping between words with Option+Left/Right arrow keys.

Baby steps, y'all.

In order to move from word to word, UIKit relies on an implementation of UITextInputStringTokenizer. I managed to replicate UIKit's calls to my string tokenizer and reuse a lot of logic for moving between words. Baby steps, y'all.

While working on moving between words in Runestone for AppKit I found that the UIKit version had an incorrect behavior when moving between words followed by an emoji. The caret would always jump all the way to the end of the document which isn't correct, obviously. Fortunately, that was easy to fix and the fix works in both UIKit and AppKit.

And yes, it is supposed to jump all the way from the word "emoji" to the word "cool". That's how TextEdit does it too. Baby steps, y'all.

And now Runestone for AppKit supports moving to the line and document boundaries as well as clicking with the mouse to move to the closest location.

Maybe the next step is to support text selection. Or something more fun like line numbers.

Baby steps, y'all.

Fortunately, invisible characters, line height, kerning and theming works with no changes needed 😃

Line numbers and highlighting the selected line now works in Runestone for AppKit.

This one was a bit tricky because the view hierarchy is different between AppKit and UIKit and there's some important layering going on here to make it look the way I want it to.

On the other hand, disabling line wrapping worked without any changes 😃

Baby steps, y'all.

I quite like this look where the title bar is big and transparent ✨

Taking a break from this thread tonight*. I did a few minor things that aren’t worth showing off but I’ll prioritize playing with the Quest 2 and watching Slow Horses for the rest of the evening. I need a short break 🤗

* Since I’m posting this I guess I’m not really taking a break.

As I'm working more and more on Runestone for AppKit, the codebase feels increasingly off-putting. It's growing large and complex and some duplicated code between UIKit and AppKit seems inevitable. If I am to continue working on this (and I hope I am!) I need three things:

1. Find peace in the codebase becoming a bit more complex.
2. Figure out which parts I can clean up.
3. Set time aside to add more unit tests.

@simonbs Maybe the more you work on it, the more commonalities appear between the two, and it can be extracted to some utility code which is shared between them.
@pasi That's what I'm hoping too 😊

@simonbs are there pieces you can break apart into their own packages? Smaller isolated things are more fun to work on.

And yes. Tests. Can’t recommend them enough. Tests and watching the coverage in Xcode gives such zen and peace of mind.

Just tested syntax highlighting in Runestone for AppKit for the first time. Was happy to discover that it just works 😃

Baby steps, y'all.

Mostly got text selection, copy, paste, and cut working in Runestone for AppKit today 😃

There are still a couple of bugs in the text selection that needs to be fixed but it's getting there.

Baby steps, y'all.

Still polishing the text selection in Runestone for AppKit. Getting all keyboard shortcuts working as expected is extremely tricky but I'm getting closer. However, I've just got text selection working with the mouse so that's something 😄

Baby steps, y'all.

In case you would like to start playing around with Runestone for AppKit, you can do so already now. It’s available in the GitHub repository: https://github.com/simonbs/Runestone/tree/mac

I’m still working on this, so bugs should be expected. Don’t waste too much time reporting issues. At this point I likely know they’re there but haven’t gotten around to fixing them yet 😊

And in case you are using Runestone in your project, remember that I have GitHub sponsors setup 🫶 https://github.com/sponsors/simonbs

GitHub - simonbs/Runestone at mac

📝 Performant plain text editor for iOS with syntax highlighting, line numbers, invisible characters and much more. - GitHub - simonbs/Runestone at mac

GitHub

Got double and triple clicking to select words and lines working in Runestone for AppKit 😃

Notice that it's even possible to double click an opening or closing bracket to select everything within the brackets 🤓

Baby steps, y'all.

Word selection was a prerequisite to support right-clicking to cut, copy, and paste and with word selection in place, it was trivial to the right-click menu 😃

Baby steps, y'all.

And now Runestone for AppKit supports undo and redo too 😃

Text selection, the right-click menu, and undo/redo are things I have missed while working on other features, so it is great to finally have those in place.

Baby steps, y'all.

Hoping to fix this difference between Runestone and UITextView as part of bringing Runestone to the Mac.

TextKit, and as a result UITextView, will remove leading spaces one line fragments when wrapping lines. This ensures that line fragments align vertically.

It's going to be tricky though 🤔

@simonbs can I ask why that would be difficult? Form an outside perspective it looks easy enough, but I’m sure I’m missing multiple parts of the story here
@Mister_Eel I'll need to add a concept of "hiding" characters that are in the text. The spaces are still in the text but I shouldn't show them. Also, the caret should be placed at the end of the previous line fragment.
@simonbs thanks for the clarification!
@simonbs Every time I read the “Baby steps, y’all” line, I think of Tony Stark in the first Iron Man telling Jarvis, “Sometimes you gotta run, before you can walk.”
@simonbs I love seeing this come to life step by step!
@simonbs I’ve always known runestone was a really cool project, and watching you develop it has been a joy. But man, that in-bracket selection is simply amazing
@simonbs didn’t realize Runestone was open source 🤯 it’s great to see how things are done in well crafted apps. I would be nervous of someone ripping my app off though. Seems like open sourcing projects is becoming bigger though? Pocketcast and Dashlane as 2 big examples of recent.
@JediMax I’ve only open-sourced the core of Runestone, not the entire app. Although the core is like 95% of the app 😅

@simonbs completely unrelated, I've been meaning to ask if there's a way to set text selection (in Runestone) back to a more "traditional," unidirectional method.

I use Runestone often and ⇧+[arrow keys] is a very ingrained habit. I often find myself unexpectedly moving the side of the selection opposite the cursor.

(hope I articulated that aptly. can also formalize this ask if that'd help other users.)

@DavidBlue Hmm.. I’m not sure I understand this. Do you find that Runestone behaves differently than other iOS apps?
@simonbs realized that I was wrong! it's actually when using ⌥ *and* ⇧ with the arrow keys. hope this video clears things up instead of further confusing lol.
@DavidBlue Oh, I see! That should not happen. Thanks for the video. I can probably fix that one of these days as I’m currently revisiting the implementation of text selection.
@simonbs well thanks for responding so promptly! I assumed it was some advanced new methodology and beyond me lol.

@simonbs Replaced NSTextView with Runestone in my Proxygen app and it’s already super promising. Rendering works great, layout of text on window resizing is fast, and scrolling long text (that 800kB response) is buttery smooth on an M1 Mini.

You’ve done great work!

@pasi Wow! You’re fast! Thanks for testing 🙏

Please excuse the bugs. Hopefully I’ll get more squashed over the coming weeks 😊

@simonbs A minor hiccup in the beginning was Xcode not liking a different branch of the same Swift package in one project, even if they’re used in different targets (iOS and Mac app). Some error about the package already being imported.
@pasi Interesting. I haven’t tried that. I’m unsure if that’s an edge case for now that it doesn’t really make sense to address. I guess that whenever this goes to 1.0.0 or whatever then people will use the same branch on iOS and macOS.
@simonbs Yeah don’t worry about it all. So I guess on iOS TextView will refer to the UIKit class and on Mac the AppKit one?
@pasi Yep, that’s the plan. The plan is to make them as close to each other as possible but it’s not a strict requirement. And if everything goes well, I’ll wrap them in a SwiftUI type so they can be used in a multi-platform project.
@simonbs Okay I was an idiot. Of course building the mac branch for iOS target just works. I don’t know why I expected otherwise. This is excellent!
@simonbs This looks fantastic! Do you have any plans for supporting code completion? Something like a data source delegate providing the completion values?
@simonbs Are you interested in contributions for missing commands like deleteToEndOfParagraph: and deleteBackward:? Or is it too early?
@ls Would love it! deleteBackward: should be supported though 🤔
@simonbs It is! I meant deleteForward: 🤦‍♂️
@ls Oh yeah, that would be great to support! 😃
@simonbs nice! I’m working in a new macOS app and going to need this soon :)

@zachwaugh Exciting! I’ looking forward to see what you are working on 😃

Technically, you can start using Runestone for AppKit already now. It’s on a branch in the repository on GitHub and there’s an example project to go with it. Do expect bugs though 😅

@simonbs oh awesome, I’m not quite there yet, but great to know!

@simonbs Lovely!

(Hope my recorded issue report video came across, sent to runestone email acct)

@simonbs what’s your opinion on textkit 2? (Seems like you are implementing it on your own.
@unclex It wasn’t available when I started my work and TextKit 1 had performance issues that I couldn’t live with.

@simonbs Aaamaaazing! 🤩 This is just the best thread

(I was wondering what would happen with the syntax highlighting thing)

@simonbs and it’s pretty fluid
@simonbs seems to be more than a baby step if it just works :-)
@simonbs excellent news, desperate to use this on the Mac. Great work. Will it work with SwiftUI on the Mac also?
@simonbs by the way the Runestone documentation is wild. Really impressive.
@camsoft2000 Thank you for the kind words! 🙏
@simonbs it’s so awesome watching this evolve in real time!
@simonbs
Slow Horses is totally worth taking the break for... :)
@simonbs But it's hard to separate the content from the chrome.
@okla I agree. I guess that's what I like about it 😄
@simonbs It looks nice until the scrolling starts, then the text becomes clipped by an invisible border which is kinda ugly)
@simonbs Awesome progress. I’m looking forward to use it.