Mac Text Editing: mark, kill, yank
I’ve been implementing the mark, kill, and yank actions for Bike Outliner. These actions are obscure and under documented. This post documents what I’ve found and includes code I used to implement them.
My understanding is that these features were originally implemented in terminal based editors. Later used by NeXTSTEP. Now many years later they are still with us on macOS.
There isn’t much documentation of the macOS implementation. Much of the following behavior was discovered by experimenting with TextEdit. Probably I’ve missed some things and got some things wrong.
Corrections welcome!
Mark
Marks are from terminal editors where it wasn’t possible to visually select a range of text. When you needed to define a range you would “mark” one end and use the insertion point for the other.
Actions to manipulate the mark:
setMark:
Save the selection to the mark.deleteToMark:
Union the mark and selection and delete that union.selectToMark:
Union the mark and selection and select that union.swapWithMark:
Swap the selection with the mark.
Note that on macOS the mark is a range, not a single point. The mark shouldn’t be updated to account for text changes in the editor. When reading the the mark always clamp it to the editor’s text length.
I don’t think there are standard keybindings for mark actions. I think they are mostly used when building up more complex multi-action keybindings. See the keybinding links below.
Kill
When text is deleted by certain actions it gets added to the kill ring. By default the kill ring contains a single text entry. If you change the NSTextKillRingSize
system default the kill ring can contain multiple entries.
Actions that kill text:
delete:
deleteToMark:
deleteToBeginningOfLine:
deleteToEndOfLine:
deleteToBeginningOfParagraph:
deleteToEndOfParagraph:
Generally killed text it is added to the ring as a new entry. Once the ring is filled it will wrap and new kills replace older kills.
The exception is when you repeat the same kill action. In that case the killed text is combined with the current kill entry. This coalescing stops when you edit text or change the selection.
To quickly try out kill use:
- Control-k: To kill using
deleteToEndOfParagraph:
.
Yank
Text can be retrieved from the kill ring by yank:
-
yank:
Replace the selection with the most recent kill text. -
yankAndSelect:
Replace the selection with the most recent kill text and select the inserted text. Repeat calls toyankAndSelect:
will cycle through the kill ring. Cycling pauses when you make an edit.
Each app maintains a private kill ring. You can kill text in one view and then yank it into another view within the same app. You can’t kill or yank between apps.
To quickly try out yank use:
- Control-y: To yank text from the kill ring using
yank:
. - To
yankAndSelect:
you will need to add your own binding.
Why would you use these actions?
I have seen these actions used most often in custom multi-action keybindings. Marks are useful for saving and restoring the selection. Kill and yank are useful for moving text without overwriting the pasteboard.
To learn more about keybindings:
- Cocoa Text System | Jacob Rus
- Customizing KeyBindings | Brett Terpstra
- Text System Defaults and Key Bindings | Apple
How to implement in your own text editor?
I’ve created KillRing.swift
class to get you started.
The kill ring needs to maintain some state. Your editor should call selectionChanged()
and textChanged()
anytime they change in your editor. When killing and yanking your editor should perform all related changes within the callback block.