krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Commit 441daa361a

441daa361a949bb87a5b9785a776ead68e716f8f

parent: 4245fe5852

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-08 03:45 UTC

Make ZZ :wq and ZQ :q!

ZZ saves and closes the window, ZQ closes it, both through the ex
commands.

Layout: unified · split

Sources/OrgCore/Keymap/Vim.swift +4 −2
@@ -32,6 +32,8 @@ public struct Vim: Sendable {
3232 case setClipboard(String)
3333 /// Ask for a search pattern (`/`, `?`) or an ex command (`:`).
3434 case prompt(String)
35 /// An ex command to run, as if typed after `:` (`ZZ` is `:wq`, `ZQ` is `:q!`).
36 case ex(String)
3537 case message(String)
3638 /// `m`: a mark at this offset, for the editor to keep through edits.
3739 case setMark(Character, Int)
@@ -463,8 +465,8 @@ public struct Vim: Sendable {
463465 case ("Z", []):
464466 guard rest.count >= 2 else { return .incomplete }
465467 switch rest[rest.startIndex + 1].key {
466 case "Z": return .done([.command("app.save")])
467 case "Q": return .done([.message("ZQ isn't available; use the window's close button")])
468 case "Z": return .done([.ex("wq")])
469 case "Q": return .done([.ex("q!")])
468470 default: return .invalid
469471 }
470472 default:
Sources/OrgEditorAppKit/Modal.swift +2
@@ -170,6 +170,8 @@ extension OrgEditor {
170170 NSPasteboard.general.setString(text, forType: .string)
171171 case .prompt(let kind):
172172 prompt(kind)
173 case .ex(let command):
174 ex(command)
173175 case .message(let text):
174176 onMessage?(text)
175177 case .setMark(let name, let offset):
Tests/OrgCoreTests/VimTests.swift +9
@@ -196,6 +196,15 @@ struct VimTests {
196196 return driver.caret
197197 }
198198
199 @Test func zzAndZqRunExCommands() {
200 var vim = Vim()
201 let context = Vim.Context(text: "a\n", caret: 0)
202 #expect(vim.feed(KeyChord("Z"), context) == [])
203 #expect(vim.feed(KeyChord("Z"), context) == [.ex("wq")])
204 _ = vim.feed(KeyChord("Z"), context)
205 #expect(vim.feed(KeyChord("Q"), context) == [.ex("q!")])
206 }
207
199208 @Test func halfPageScrollsFollowTheWindow() {
200209 #expect(driven(0, "C-d", halfPage: 5) == 35)
201210 #expect(driven(70, "C-u", halfPage: 5) == 35)
Tests/OrgEditorAppKitTests/ModalTests.swift +11
@@ -72,6 +72,17 @@ struct ModalTests {
7272 #expect(h.editor.vim.mode == .normal)
7373 }
7474
75 @Test func zzSavesAndClosesAsWq() {
76 let h = doom("text\n")
77 var app: [String] = []
78 h.editor.onAppCommand = { app.append($0) }
79 h.caret(at: 0)
80 type(h, "Z Q")
81 #expect(app.isEmpty)
82 type(h, "Z Z")
83 #expect(app == ["app.save"])
84 }
85
7586 @Test func windowLinesInTheEditor() {
7687 let h = doom("one\ntwo\nthree\nfour\nfive\n")
7788 h.caret(at: 0)