krz/orgstar

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

Commit f71bf44f5c

f71bf44f5c03b7b0037fde38a1159b50186b8159

parent: 14aff41909

Verified · cmc

cmc <hello@cleberg.net> · 2026-10-08 04:36 UTC

Keep spaces when filling with gq and gw

Doom advises evil-fill and evil-fill-and-move to call fill-region with NOSQUEEZE, so runs of spaces and trailing spaces stay. The evil oracle loads the same advice.

Layout: unified · split

Sources/OrgCore/Commands/Fill.swift +17 −13
@@ -302,8 +302,9 @@ extension EmacsBuffer {
302302 }
303303 }
304304
305 /// `fill-region-as-paragraph` with left justification and adaptive fill.
306 func fillRegionAsParagraph(_ fromOffset: Int, _ toOffset: Int, prefix fixedPrefix: String? = nil) {
305 /// `fill-region-as-paragraph` with left justification and adaptive fill. With `nosqueeze`,
306 /// spaces between words and at the end stay.
307 func fillRegionAsParagraph(_ fromOffset: Int, _ toOffset: Int, prefix fixedPrefix: String? = nil, nosqueeze: Bool = false) {
307308 point = min(fromOffset, toOffset)
308309 let to = max(fromOffset, toOffset)
309310 skipForward(Self.whitespace)
@@ -349,13 +350,15 @@ extension EmacsBuffer {
349350 if character(at: at) == 10 { replace(at..<(at + 1), with: " ") }
350351 at += 1
351352 }
352 point = from
353 skipForward(Self.blanks)
354 canonicallySpace(point, toMarker.position)
355 point = toMarker.position
356 let end = point
357 skipBackward(Self.blanks)
358 if point < end { deleteRegion(point, end) }
353 if !nosqueeze {
354 point = from
355 skipForward(Self.blanks)
356 canonicallySpace(point, toMarker.position)
357 point = toMarker.position
358 let end = point
359 skipBackward(Self.blanks)
360 if point < end { deleteRegion(point, end) }
361 }
359362 point = from
360363 while point < toMarker.position {
361364 let lineBegin = point
@@ -469,7 +472,7 @@ extension EmacsBuffer {
469472
470473 /// `fill-region` over whole lines: each paragraph's part of `from..<to` filled as a
471474 /// paragraph. Other elements stay.
472 func fillRegion(_ from: Int, _ to: Int) {
475 func fillRegion(_ from: Int, _ to: Int, nosqueeze: Bool = false) {
473476 var spans: [Range<Int>] = []
474477 var line = lineStart(from)
475478 while line < to, line < length {
@@ -486,7 +489,7 @@ extension EmacsBuffer {
486489 line = next
487490 }
488491 // Back to front, so the spans before stay where they are.
489 for span in spans.reversed() { fillRegionAsParagraph(span.lowerBound, span.upperBound) }
492 for span in spans.reversed() { fillRegionAsParagraph(span.lowerBound, span.upperBound, nosqueeze: nosqueeze) }
490493 }
491494
492495 /// Where a paragraph's text starts: past affiliated keywords, and for a paragraph that
@@ -507,7 +510,8 @@ public struct FillParagraph: OrgCommand {
507510
508511/// evil's `gq` and `gw` operators: the paragraphs in `range`'s lines filled, as `fill-region`.
509512/// With `keep` (`gw`) the caret stays there; without (`gq`) it goes to the first non-blank
510/// of the range's last line.
513/// of the range's last line. Spaces are not squeezed, as Doom's `+evil--no-squeeze-on-fill-a`
514/// has it.
511515public struct FillRegion: OrgCommand {
512516 public let range: Range<Int>
513517 public let keep: Int?
@@ -524,7 +528,7 @@ public struct FillRegion: OrgCommand {
524528 commitBuffer(context) { buffer in
525529 let target = buffer.marker(keep ?? max(range.lowerBound, range.upperBound - 1))
526530 defer { buffer.release(target) }
527 buffer.fillRegion(range.lowerBound, range.upperBound)
531 buffer.fillRegion(range.lowerBound, range.upperBound, nosqueeze: true)
528532 buffer.point = target.position
529533 if keep == nil {
530534 buffer.beginningOfLine()
Tests/OrgCoreTests/VimExtrasTests.swift +8
@@ -165,6 +165,8 @@ struct VimExtrasTests {
165165 ("a\nb\nc\nd\n", 2, "g q j"),
166166 ("a\nb\nc\n", 0, "V j g q"),
167167 ("a\nb\n\nc\nd\n", 0, "g q j j ."),
168 ("one two three\nfour five \nsix\n", 0, "g q G"),
169 ("End. Next word, " + long + " \nmore\n", 3, "g w j"),
168170 ]
169171 }()
170172
@@ -201,6 +203,12 @@ struct VimExtrasTests {
201203 (dolist (d '("evil" "goto-chg" "evil-surround" "evil-org-mode")) (add-to-list 'load-path (concat \(lispString(repos.path)) "/" d)))
202204 (require 'evil)
203205 (require 'evil-surround)
206 (defun oracle-no-squeeze (fn &rest args)
207 (cl-letf* ((fill (symbol-function 'fill-region))
208 ((symbol-function 'fill-region) (lambda (from to &optional justify _nosqueeze to-eop) (funcall fill from to justify t to-eop))))
209 (apply fn args)))
210 (advice-add 'evil-fill :around #'oracle-no-squeeze)
211 (advice-add 'evil-fill-and-move :around #'oracle-no-squeeze)
204212 (setq kill-ring nil)
205213 (switch-to-buffer (current-buffer))
206214 \(org ? "(org-mode) (require 'evil-org) (evil-org-set-key-theme '(textobjects navigation)) (evil-org-mode 1)" : "")