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 {
302 } 302 }
303 } 303 }
304 304
305 /// `fill-region-as-paragraph` with left justification and adaptive fill. 305 /// `fill-region-as-paragraph` with left justification and adaptive fill. With `nosqueeze`,
306 func fillRegionAsParagraph(_ fromOffset: Int, _ toOffset: Int, prefix fixedPrefix: String? = nil) { 306 /// spaces between words and at the end stay.
307 func fillRegionAsParagraph(_ fromOffset: Int, _ toOffset: Int, prefix fixedPrefix: String? = nil, nosqueeze: Bool = false) {
307 point = min(fromOffset, toOffset) 308 point = min(fromOffset, toOffset)
308 let to = max(fromOffset, toOffset) 309 let to = max(fromOffset, toOffset)
309 skipForward(Self.whitespace) 310 skipForward(Self.whitespace)
@@ -349,13 +350,15 @@ extension EmacsBuffer {
349 if character(at: at) == 10 { replace(at..<(at + 1), with: " ") } 350 if character(at: at) == 10 { replace(at..<(at + 1), with: " ") }
350 at += 1 351 at += 1
351 } 352 }
352 point = from 353 if !nosqueeze {
353 skipForward(Self.blanks) 354 point = from
354 canonicallySpace(point, toMarker.position) 355 skipForward(Self.blanks)
355 point = toMarker.position 356 canonicallySpace(point, toMarker.position)
356 let end = point 357 point = toMarker.position
357 skipBackward(Self.blanks) 358 let end = point
358 if point < end { deleteRegion(point, end) } 359 skipBackward(Self.blanks)
360 if point < end { deleteRegion(point, end) }
361 }
359 point = from 362 point = from
360 while point < toMarker.position { 363 while point < toMarker.position {
361 let lineBegin = point 364 let lineBegin = point
@@ -469,7 +472,7 @@ extension EmacsBuffer {
469 472
470 /// `fill-region` over whole lines: each paragraph's part of `from..<to` filled as a 473 /// `fill-region` over whole lines: each paragraph's part of `from..<to` filled as a
471 /// paragraph. Other elements stay. 474 /// paragraph. Other elements stay.
472 func fillRegion(_ from: Int, _ to: Int) { 475 func fillRegion(_ from: Int, _ to: Int, nosqueeze: Bool = false) {
473 var spans: [Range<Int>] = [] 476 var spans: [Range<Int>] = []
474 var line = lineStart(from) 477 var line = lineStart(from)
475 while line < to, line < length { 478 while line < to, line < length {
@@ -486,7 +489,7 @@ extension EmacsBuffer {
486 line = next 489 line = next
487 } 490 }
488 // Back to front, so the spans before stay where they are. 491 // 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) }
490 } 493 }
491 494
492 /// Where a paragraph's text starts: past affiliated keywords, and for a paragraph that 495 /// Where a paragraph's text starts: past affiliated keywords, and for a paragraph that
@@ -507,7 +510,8 @@ public struct FillParagraph: OrgCommand {
507 510
508/// evil's `gq` and `gw` operators: the paragraphs in `range`'s lines filled, as `fill-region`. 511/// evil's `gq` and `gw` operators: the paragraphs in `range`'s lines filled, as `fill-region`.
509/// With `keep` (`gw`) the caret stays there; without (`gq`) it goes to the first non-blank 512/// 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.
511public struct FillRegion: OrgCommand { 515public struct FillRegion: OrgCommand {
512 public let range: Range<Int> 516 public let range: Range<Int>
513 public let keep: Int? 517 public let keep: Int?
@@ -524,7 +528,7 @@ public struct FillRegion: OrgCommand {
524 commitBuffer(context) { buffer in 528 commitBuffer(context) { buffer in
525 let target = buffer.marker(keep ?? max(range.lowerBound, range.upperBound - 1)) 529 let target = buffer.marker(keep ?? max(range.lowerBound, range.upperBound - 1))
526 defer { buffer.release(target) } 530 defer { buffer.release(target) }
527 buffer.fillRegion(range.lowerBound, range.upperBound) 531 buffer.fillRegion(range.lowerBound, range.upperBound, nosqueeze: true)
528 buffer.point = target.position 532 buffer.point = target.position
529 if keep == nil { 533 if keep == nil {
530 buffer.beginningOfLine() 534 buffer.beginningOfLine()
Tests/OrgCoreTests/VimExtrasTests.swift +8
@@ -165,6 +165,8 @@ struct VimExtrasTests {
165 ("a\nb\nc\nd\n", 2, "g q j"), 165 ("a\nb\nc\nd\n", 2, "g q j"),
166 ("a\nb\nc\n", 0, "V j g q"), 166 ("a\nb\nc\n", 0, "V j g q"),
167 ("a\nb\n\nc\nd\n", 0, "g q j j ."), 167 ("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"),
168 ] 170 ]
169 }() 171 }()
170 172
@@ -201,6 +203,12 @@ struct VimExtrasTests {
201 (dolist (d '("evil" "goto-chg" "evil-surround" "evil-org-mode")) (add-to-list 'load-path (concat \(lispString(repos.path)) "/" d))) 203 (dolist (d '("evil" "goto-chg" "evil-surround" "evil-org-mode")) (add-to-list 'load-path (concat \(lispString(repos.path)) "/" d)))
202 (require 'evil) 204 (require 'evil)
203 (require 'evil-surround) 205 (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)
204 (setq kill-ring nil) 212 (setq kill-ring nil)
205 (switch-to-buffer (current-buffer)) 213 (switch-to-buffer (current-buffer))
206 \(org ? "(org-mode) (require 'evil-org) (evil-org-set-key-theme '(textobjects navigation)) (evil-org-mode 1)" : "") 214 \(org ? "(org-mode) (require 'evil-org) (evil-org-set-key-theme '(textobjects navigation)) (evil-org-mode 1)" : "")