The Rake and Its Prongs

Teaser image

When you type in a text editor, you always type out of some range. When your insertion point or cursor is blinking at the end of a word you just typed, you expect to still be “in touch” with the word, and that the next key you press will for example add a character to that word. This is a useful deception for us human users. It’s not actually part of the technological underpinnings.

Continue reading …

ChatGPT: Publish NSTextView Changes to ReSwift, but Also Use a Lamport Clock

Me: How can you wrap NSTextView events to go through a ReSwift store? ChatGPT: It’s not clear what you’re asking. Could you please provide more information or context? I’m a large language model trained by OpenAI, so I don’t have the ability to browse the internet or access information outside of what I was trained on. I can provide general information and advice based on that training, but I may not be able to answer specific questions about events in NSTextView or ReSwift stores.

Continue reading …

NSTextView (Plain Text) and the Pasteboard: PasteboardType.string Is Not Handled

For a plain text (not rich text/RTF) NSTextView, I found that: Since NSTextView doesn’t understand the NSPasteboard.PasteboardType.string pasteboard type for reading or writing, I tried two approaches to handle plain text input (pasting) and output (cut/copy): The backport seems to work, but extending supported types sounds like more robust solution.

Continue reading …

How to Fix When Some Text Changes Don't Come with Automatic Undo?

When you work with NSTextView and happen to use insertText(_:) to programmatically insert text, you get an undoable action for free. This might give the impression you get undo/redo functionality for free. Eventually, you’ll notice how other changes don’t have an affordance in the “Edit” menu. While it’s possible to get “Undo Typing” and “Undo Set Color” from some function calls, it’s not possible to get “Undo Change Text Attributes” when you use NSTextStorage.addAttributes(_:range:).

Continue reading …

STTextView: A TextKit 2 Text Editor without NSTextView

Teaser image

In my recent post about the TextKit 2 sample app, commenter Frizlab pointed out that Marcin Krzyzanowski (@krzyzanowskim on Twitter) is doing TextKit 2 stuff. I didn’t notice that in my Twitter timeline even though I follow him for years now, so I was confused, nay, angry about the state of my Twitter timeline since Musk’s takeover /s.

And yes, Marcin does have a very extensive open source sample project that explores TextKit 2! He’s working on Swift Studio, a pure Swift IDE and his STTextView is a part of that.

So meet STTextView, a “TextKit2 text view without NSTextView baggage”.

Check out the video demo!

One thing I love about the code comments for the UI compoents is the ASCII diagrams of layer contents, e.g. in STTextView.swift:

//  STTextView
//      |---selectionLayer (CALayer)
//      |---contentLayer (CALAyer)
//              |---(STInsertionPointLayer | TextLayoutFragmentLayer)
//

Am totally going to adopt this :)

What Do You Get When You Drag and Drop a PNG File From Finder Into an NSTextView?

In short: Image file drag and drop does only produce file URLs, either with security scope-able bookmarks or plain file paths. Dragging an image file from Finder onto an NSTextView will trigger performDragOperation(_:). You get access to NSDraggingInfo there and can inspect available content. Its draggingPasteboard (shortened to pb here) contains the following data when executed on macOS 12 Monterey:

Continue reading …

NSTextView Performance May Degrade for Large Plain Text Documents When usesFontPanel Is Active

I was profiling performance bottlenecks in the The Archive and noticed that no matter how much highlighting functionality I removed/commented-out, the apparent slowness was all due to … Touch Bar API?! I have been “pruning” the Touch Bar related calls from the profiling stack to focus on what I though would be the real bottlenecks. But, as often, it turns out this was stupid and the instruments did point out the true problem. Something indeed was causing trouble here, it turned out.

Continue reading …

Retry Imperative Conditions with RxSwift Using a Delay

In The Archive, people relying on character composition to enter their text noticed that the auto-saving routing got in the way and aborted the composable editing mode. This affects e.g. Chinese or Japanese character input on macOS, but also when you hit a composable accent like ´ after which the text editor waits for another character to put underneath the accent.

Continue reading …

Disable NSTextAttachment Action and Sharing Services Menu Drop-Down

Teaser image

By default, NSTextView will show the NSSharingServicePicker button when you hover over an image inside the text view. That’s true even for custom image-based NSTextAttachments in the attributed string. The default menu item is limited to “Markup” and a “Services” submenu, I believe. Apps can register to be shown in this menu, and users can customize the menu in System Preferences.

Continue reading …

Follow Link at Insertion Point in NSTextView

Teaser image

If you ever wondered how to programmatically trigger a click on a link in a NSTextView, here’s one way to do so. This assumes that clickable links are not stored as temporary attributes in the NSLayoutManager, but permanently as part of the “model” in your NSTextStorage. You can then ask the storage for the attribute at the cursor/insertion point location:

Continue reading …

Fix Missing Font Fallbacks for NSTextView

Rich Siegel recently wondered on Slack why NSTextView would suddenly display empty placeholders for some glyphs when the font does not support them, instead of falling back to a safe font like it usually does. Chinese characters never got displayed. Michel Fortin remembered a similar problem, and the potential fix was quite simple:

In summary, if you have to change the font after the text storage was edited, do it in willProcessEditing and it’ll do the right thing. Don’t do it in didProcessEditing.

That turned out to be what tripped up Mr Siegel’s text view, which now happily displays CJK/CJKV again. For more details and some background about how you can detect this problem in your apps, read Michel’s post.

NSTextField usesSingleLineMode Stops Working When You Implement NSTextViewDelegate Methods

Today I learned why my NSTextField permits pasting of newline characters even though I set usesSingleLineMode properly. It’s because I made it conform to NSTextViewDelegate to cache changes. When you edit text inside of an NSTextField, you actually type inside a field editor of the window. That’s a shared NSTextView instance. Most of the hard work of an NSTextField is done by its cell, which is an NSTextCell. NSTextCells implement at least the delegate method NSTextViewDelegate.textView(_:shouldChangeTextIn:replacementText:) – and when you set usesSingleLineMode, this is actually set for the cell, not the view itself. You can use textView(_:shouldChangeTextIn:replacementText:) to sanitize input text, and I suspect that’s where the usesSingleLineMode implementation happens. If your NSTextField subclass implements this method, the NSTextCell implementation isn’t called. And since that one isn’t public (it was called “implicit protocol conformance” back in the day), you cannot delegate up in Swift because the compiler knows it isn’t there.

Continue reading …

RxSwift: Typing/Idle Event Stream for NSTextView

To improve my note editing app The Archive’s responsiveness, I want to find out when the user is actively editing a note. Here’s how to create the idle switch in RxSwift/RxCocoa. A simple enum of .typing and .idle will do for a start. Of course, NSTextViewDelegate provides a textDidChange(_:) callback to hook into; it’s based on a notification, so you can also subscribe to the NSText.didChangeNotification directly if you don’t want to use a delegate. That’s the input signal I’m going to use.

Continue reading …

NSTextView's Default Insertion Point and Selected Text Colors

Teaser image

NSTextView can be customized to display different colors for the insertion point (aka caret, aka text cursor) and for selected text. This is especially useful when you add themes to your editor and the default settings don’t fit anymore. The default values are not exposed anywhere, so I had to log them and reconstruct convenient accessors to reset a text view if needed:

Continue reading …

NSTextView: When Did the Find Bar Disappear?

For whatever reason, my current app project’s find bar does not make the text view firstResponder again when you hit Escape or click the “Done” button to close it. This is very uncomfortable for users: they type away, hit ⌘F to find a phrase, then hit Esc – and now they’re in limbo. To my astonishment, the NSTextFinderAction called hideFindInterface is not triggered when you make the find bar disappear. Its opposite, showFindInterface, is triggered when the find bar slides back in, though. Intercepting in NSTextView.performTextFinderAction(_:) does not help, then.

Continue reading …

Why the Selection Changes When You Do Syntax Highlighting in a NSTextView and What You Can Do About It

Teaser image

On iOS, this does maybe not happen at all, but when you want to write syntax highlighting code for macOS apps, copying together stuff from around the web, you’ll end up with broken application behavior. In short: when you type and the attributes of the line change, the insertion point is moved to the end of the line. That sucks.

Continue reading …

Setting the NSTextView Line Height in a Beautiful Way

In the original post about a cheap way to set the line height in a text view to, say, 150%, the result kind of worked but didn’t look that cool. One issue is that the extra line spacing was exclusively added at the bottom. With the following solution, you’ll get a proper line height with tastefully aligned insertion point and baseline and all.

Continue reading …

Typewriter Mode: Adding Overscrolling to the Text View

Typewriter modes depend on the feature that you can scroll farther up and down than usual. You need extra whitespace, most of the time in both directions. Let’s start with “overscrolling” to understand what we need. Regular text views show additional bottom whitespace only until you fill it with text. Take TextEdit, for example. You can start to type at the topmost edge of the text view and the rest of the window is blank.

Continue reading …

Setting the Line Height of a NSTextView

NSTextView (and UITextView for that matter) have a defaultParagraphStyle attribute where you can set the text’s line height. That works swell – if you display text statically. Once the user can enter something, you can run into trouble: Update 2017-07: I posted a better version without paragraph style attributes that hooks into the NSLayoutManager delegate callbacks for a more consistent and speedy experience!

Continue reading …