Let org-columns correctly detect string-widths in code

TODO: maybe I should also make a test directly on
`org-columns-add-ellipses'.  Will do in next iteration unless
objections.

* lisp/org-colview.el (org-columns--truncate-below-width): add a
helper function that will trim off just enough data from string to
fit into expected width.
(org-columns-add-ellipses): make sure to do truncation correctly
even in CJK locales (where an ellipsis character takes two
spaces).

* testing/lisp/test-org-colview.el
(test-org-colview/substring-below-width): add test to make sure
helper function is correct.
(test-org-colview/columns-width): fix incorrect expectations for
CJK locales about ellipses.
This commit is contained in:
Ruijie Yu 2023-04-25 22:56:02 +08:00 committed by Ihor Radchenko
parent ab9c9732ea
commit 8739a95782
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
2 changed files with 53 additions and 20 deletions

View File

@ -452,14 +452,30 @@ DATELINE is non-nil when the face used should be
"Type \\<org-columns-map>`\\[org-columns-edit-value]' \
to edit property")))))))
(defun org-columns--truncate-below-width (string width)
"Return a substring of STRING no wider than WIDTH.
This substring must start at 0, and must be the longest possible
substring whose `string-width' does not exceed WIDTH."
(declare (side-effect-free t))
(let ((end (min width (length string))) res)
(while (and end (>= end 0))
(let* ((curr (string-width string 0 end))
(excess (- curr width)))
(if (> excess 0)
(cl-decf end (max 1 (/ excess 2)))
(setq res (substring string 0 end) end nil))))
res))
(defun org-columns-add-ellipses (string width)
"Truncate STRING with WIDTH characters, with ellipses."
(cond
((<= (length string) width) string)
((<= width (length org-columns-ellipses))
(substring org-columns-ellipses 0 width))
(t (concat (substring string 0 (- width (length org-columns-ellipses)))
org-columns-ellipses))))
((<= (string-width string) width) string)
((<= width (string-width org-columns-ellipses))
(org-columns--truncate-below-width org-columns-ellipses width))
(t (concat
(org-columns--truncate-below-width
string (- width (string-width org-columns-ellipses)))
org-columns-ellipses))))
(defvar org-columns-full-header-line-format nil
"The full header line format, will be shifted by horizontal scrolling." )

View File

@ -92,47 +92,64 @@
(org-columns-compile-format
"%ITEM{+;%.1f}"))))
(ert-deftest test-org-colview/substring-below-width ()
"Test `org-columns--truncate-below-width'."
(cl-flet ((check (string width expect)
(string= expect (org-columns--truncate-below-width
string width))))
(if (= (char-width ?…) 2)
(progn (should (check "12…" 3 "12"))
(should (check "1…2" 1 "1"))
(should (check "1…2" 2 "1"))
(should (check "1…2" 3 "1…"))
(should (check "……………………" 7 "………")))
(progn (should (check "12…" 4 "12…"))
(should (check "1…2" 1 "1"))
(should (check "1…2" 2 "1…"))
(should (check "1…2" 3 "1…2"))
(should (check "……………………" 7 "…………………"))))))
(ert-deftest test-org-colview/get-format ()
"Test `org-columns-get-format' specifications."
;; Without any clue, use `org-columns-default-format'.
(should
(equal "%A"
(org-test-with-temp-text "* H"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
;; If COLUMNS keyword is set, use it.
(should
(equal "%B"
(org-test-with-temp-text "#+COLUMNS: %B\n* H"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(should
(equal "%B"
(org-test-with-temp-text "#+columns: %B\n* H"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(should
(equal "%B"
(org-test-with-temp-text "* H\n#+COLUMNS: %B"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
;; When :COLUMNS: property is set somewhere in the tree, use it over
;; the previous ways.
(should
(equal
"%C"
(org-test-with-temp-text
"#+COLUMNS: %B\n* H\n:PROPERTIES:\n:COLUMNS: %C\n:END:\n** S\n<point>"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
"#+COLUMNS: %B\n* H\n:PROPERTIES:\n:COLUMNS: %C\n:END:\n** S\n<point>"
(let ((org-columns-default-format "%A"))
(org-columns-get-format)))))
;; When optional argument is provided, prefer it.
(should
(equal
"%D"
(org-test-with-temp-text
"#+COLUMNS: %B\n* H\n:PROPERTIES:\n:COLUMNS: %C\n:END:\n** S\n<point>"
(let ((org-columns-default-format "%A"))
(org-columns-get-format "%D"))))))
"#+COLUMNS: %B\n* H\n:PROPERTIES:\n:COLUMNS: %C\n:END:\n** S\n<point>"
(let ((org-columns-default-format "%A"))
(org-columns-get-format "%D"))))))
(ert-deftest test-org-colview/columns-scope ()
"Test `org-columns' scope."
@ -226,7 +243,7 @@
(org-columns))
(org-trim (get-char-property (point) 'display)))))
(should
(equal "1234… |"
(equal (if (= 1 (char-width ?…)) "1234… |" "123… |")
(org-test-with-temp-text "* H\n:PROPERTIES:\n:P: 123456\n:END:"
(let ((org-columns-default-format "%5P")
(org-columns-ellipses ""))