diff options
author | Nicolas Goaziou <mail@nicolasgoaziou.fr> | 2017-08-19 18:45:41 +0200 |
---|---|---|
committer | Nicolas Goaziou <mail@nicolasgoaziou.fr> | 2017-09-06 15:21:20 +0200 |
commit | 70d2b3c96f84bfad2ccecb69ba35b2d6f425e16b (patch) | |
tree | 3dc80a655b744c15ac85254f8c075ea6a8771ffc | |
parent | 6e5598dc316146f50b2503a9f18dcf7d25e873db (diff) | |
download | org-mode-70d2b3c96f84bfad2ccecb69ba35b2d6f425e16b.tar.gz |
org-table: Obey <c> cookie when aligning a table
* lisp/org-table.el (org-table--align-field): New function.
(org-table-align): Use new function. Refactor code.
(org-table-justify-field-maybe): Use new function.
(org-table-get-remote-range): Remove duplicate bindings.
* doc/org.texi (Column width and alignment): Remove footnote.
* testing/lisp/test-org-table.el (test-org-table/align): New test.
-rw-r--r-- | doc/org.texi | 5 | ||||
-rw-r--r-- | lisp/org-table.el | 202 | ||||
-rw-r--r-- | testing/lisp/test-org-table.el | 79 |
3 files changed, 182 insertions, 104 deletions
diff --git a/doc/org.texi b/doc/org.texi index ef54ea2..784eeee 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -2375,9 +2375,8 @@ set this option on a per-file basis with: If you would like to overrule the automatic alignment of number-rich columns to the right and of string-rich columns to the left, you can use @samp{<r>}, -@samp{<c>}@footnote{Centering does not work inside Emacs, but it does have an -effect when exporting to HTML.} or @samp{<l>} in a similar fashion. You may -also combine alignment and field width like this: @samp{<r10>}. +@samp{<c>} or @samp{<l>} in a similar fashion. You may also combine +alignment and field width like this: @samp{<r10>}. Lines which only contain these formatting cookies are removed automatically upon exporting the document. diff --git a/lisp/org-table.el b/lisp/org-table.el index e0fbe7a..ee88407 100644 --- a/lisp/org-table.el +++ b/lisp/org-table.el @@ -772,6 +772,18 @@ When nil, simply write \"#ERROR\" in corrupted fields.") (defvar org-last-recalc-line nil) +(defun org-table--align-field (field width align) + "Format FIELD according to column WIDTH and alignement ALIGN. +FIELD is a string. WIDTH is a number. ALIGN is either \"c\", +\"l\" or\"r\"." + (let* ((spaces (- width (org-string-width field))) + (prefix (pcase align + ("l" "") + ("r" (make-string spaces ?\s)) + ("c" (make-string (/ spaces 2) ?\s)))) + (suffix (make-string (- spaces (length prefix)) ?\s))) + (concat " " prefix field suffix " "))) + ;;;###autoload (defun org-table-align () "Align the table at point by aligning all vertical bars." @@ -791,100 +803,83 @@ When nil, simply write \"#ERROR\" in corrupted fields.") (save-excursion (re-search-forward "|[ \t]*<[lrc][0-9]*>[ \t]*\\(?:|\\|$\\)" end t))) - ;; Table's rows. Rules are replaced by nil. Trailing - ;; spaces are removed. - (lines (mapcar - (lambda (l) - (and (not (string-match-p org-table-hline-regexp l)) - l)) - (split-string (buffer-substring beg end) "\n" t "[ \t]"))) - ;; List of lists of data fields. - (fields (mapcar (lambda (l) (org-split-string l "[ \t]*|[ \t]*")) - (remq nil lines))) - ;; Compute number of fields in the longest line. If the - ;; table contains no field, create a default table. - (maxfields (if fields (apply #'max (mapcar #'length fields)) - (kill-region beg end) - (org-table-create org-table-default-size) - (user-error "Empty table - created default table"))) - ;; A list of empty strings to fill any short rows on output. - (emptycells (make-list maxfields "")) - lengths typenums) + ;; Table's rows as lists of fields. Rules are replaced + ;; by nil. Trailing spaces are removed. + (fields (mapcar + (lambda (l) + (and (not (string-match-p org-table-hline-regexp l)) + (org-split-string l "[ \t]*|[ \t]*"))) + (split-string (buffer-substring beg end) "\n" t "[ \t]"))) + ;; Compute number of columns. If the table contains no + ;; field, create a default table and bail out. + (columns-number + (if fields (apply #'max (mapcar #'length fields)) + (kill-region beg end) + (org-table-create org-table-default-size) + (user-error "Empty table - created default table"))) + (widths nil) + (alignments nil)) ;; Compute alignment and width for each column. - (dotimes (i maxfields) + (dotimes (i columns-number) (let* ((column (mapcar (lambda (x) (or (nth i x) "")) fields)) - (falign - (and align-cookie? - (cl-some (lambda (cell) - (and (string-match "\\`<\\([lrc]\\)[0-9]*>\\'" - cell) - (match-string 1 cell))) - column)))) - ;; Get the maximum width for each column. - (push (apply #'max 1 (mapcar #'org-string-width column)) - lengths) - ;; If there is no alignment cookie, get the fraction of + (width (apply #'max 1 (mapcar #'org-string-width column)))) + ;; Store the maximum width for the column. + (push width widths) + ;; If there is no alignment cookie get the fraction of ;; numbers among non-empty cells to decide about alignment ;; of the column. - (if falign (push (equal (downcase falign) "r") typenums) - (let ((cnt 0) - (frac 0.0)) - (dolist (x column) - (unless (equal x "") - (setq frac - (/ (+ (* frac cnt) - (if (string-match-p org-table-number-regexp x) - 1 - 0)) - (cl-incf cnt))))) - (push (>= frac org-table-number-fraction) typenums))))) - (setq lengths (nreverse lengths)) - (setq typenums (nreverse typenums)) + (push (cond + ((= width 1) "r") ;doesn't matter + ((and align-cookie? + (cl-some + (lambda (f) + (and (string-match "\\`<\\([lrc]\\)[0-9]*>\\'" f) + (match-string-no-properties 1 f))) + column))) + ((let ((numbers 0) + (non-empty 0)) + (dolist (field column) + (unless (equal "" field) + (cl-incf non-empty) + (when (string-match-p org-table-number-regexp field) + (cl-incf numbers)))) + (>= numbers (* org-table-number-fraction non-empty))) + "r") + (t "l")) + alignments))) + (setq widths (nreverse widths)) + (setq alignments (nreverse alignments)) ;; Store alignment of this table, for later editing of single ;; fields. - (setq org-table-last-alignment typenums) - (setq org-table-last-column-widths lengths) - ;; With invisible characters, `format' does not get the field - ;; width right So we need to make these fields wide by hand. - ;; Invisible characters may be introduced by fontified links, - ;; emphasis, macros or sub/superscripts. - (when (or (text-property-any beg end 'invisible 'org-link) - (text-property-any beg end 'invisible t)) - (dotimes (i maxfields) - (let ((len (nth i lengths))) - (dotimes (j (length fields)) - (let* ((c (nthcdr i (nth j fields))) - (cell (car c))) - (when (and - (stringp cell) - (let ((l (length cell))) - (or (text-property-any 0 l 'invisible 'org-link cell) - (text-property-any beg end 'invisible t))) - (< (org-string-width cell) len)) - (let ((s (make-string (- len (org-string-width cell)) ?\s))) - (setcar c (if (nth i typenums) (concat s cell) - (concat cell s)))))))))) - - ;; Compute the formats needed for output of the table. - (let ((hfmt (concat indent "|")) - (rfmt (concat indent "|")) - (rfmt1 " %%%s%ds |") - (hfmt1 "-%s-+")) - (dolist (l lengths (setq hfmt (concat (substring hfmt 0 -1) "|"))) - (let ((ty (if (pop typenums) "" "-"))) ; Flush numbers right. - (setq rfmt (concat rfmt (format rfmt1 ty l))) - (setq hfmt (concat hfmt (format hfmt1 (make-string l ?-)))))) - ;; Replace modified lines only. - (dolist (l lines) - (let ((line - (if l (apply #'format rfmt (append (pop fields) emptycells)) - hfmt)) - (previous (buffer-substring (point) (line-end-position)))) - (if (equal previous line) - (forward-line) - (insert line "\n") - (delete-region (point) (line-beginning-position 2)))))) + (setq org-table-last-alignment alignments) + (setq org-table-last-column-widths widths) + ;; Build new table rows. Only replace rows that actually + ;; changed. + (dolist (row fields) + (let ((previous (buffer-substring (point) (line-end-position))) + (new + (format "%s|%s|" + indent + (if (null row) ;horizontal rule + (mapconcat (lambda (w) (make-string (+ 2 w) ?-)) + widths + "+") + (let ((cells ;add missing fields + (append row + (make-list (- columns-number + (length row)) + "")))) + (mapconcat #'identity + (cl-mapcar #'org-table--align-field + cells + widths + alignments) + "|")))))) + (if (equal new previous) + (forward-line) + (insert new "\n") + (delete-region (point) (line-beginning-position 2))))) (set-marker end nil) (when org-table-overlay-coordinates (org-table-overlay-coordinates)) (setq org-table-may-need-update nil)))))) @@ -946,22 +941,27 @@ Optional argument NEW may specify text to replace the current field content." (skip-chars-backward "^|") (if (not (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)")) (setq org-table-may-need-update t) - (let* ((numbers? (nth (1- col) org-table-last-alignment)) + (let* ((align (nth (1- col) org-table-last-alignment)) + (width (nth (1- col) org-table-last-column-widths)) (cell (match-string 0)) (field (match-string 1)) - (len (max 1 (- (org-string-width cell) 3))) (properly-closed? (/= (match-beginning 2) (match-end 2))) - (fmt (format (if numbers? " %%%ds %s" " %%-%ds %s") - len - (if properly-closed? "|" - (setq org-table-may-need-update t) - ""))) (new-cell - (cond ((not new) (format fmt field)) - ((<= (org-string-width new) len) (format fmt new)) - (t - (setq org-table-may-need-update t) - (format " %s |" new))))) + (save-match-data + (cond (org-table-may-need-update + (format " %s |" (or new field))) + ((not properly-closed?) + (setq org-table-may-need-update t) + (format " %s |" (or new field))) + ((not new) + (concat (org-table--align-field field width align) + "|")) + ((<= (org-string-width new) width) + (concat (org-table--align-field new width align) + "|")) + (t + (setq org-table-may-need-update t) + (format " %s |" new)))))) (unless (equal new-cell cell) (let (org-table-may-need-update) (replace-match new-cell t t))) @@ -5756,9 +5756,9 @@ list of the fields in the rectangle." org-table-current-line-types org-table-current-begin-pos org-table-dlines org-table-current-ncol - org-table-hlines org-table-last-alignment - org-table-last-column-widths org-table-last-alignment + org-table-hlines org-table-last-column-widths + org-table-last-alignment buffer loc) (setq form (org-table-convert-refs-to-rc form)) (org-with-wide-buffer diff --git a/testing/lisp/test-org-table.el b/testing/lisp/test-org-table.el index 1f322d8..b29ed88 100644 --- a/testing/lisp/test-org-table.el +++ b/testing/lisp/test-org-table.el @@ -1614,6 +1614,85 @@ See also `test-org-table/copy-field'." (search-forward "| a |" nil t 3)))) +;;; Align + +(ert-deftest test-org-table/align () + "Test `org-table-align' specifications." + ;; Regular test. + (should + (equal "| a |\n" + (org-test-with-temp-text "| a |" + (org-table-align) + (buffer-string)))) + ;; Preserve alignment. + (should + (equal " | a |\n" + (org-test-with-temp-text " | a |" + (org-table-align) + (buffer-string)))) + ;; Handle horizontal lines. + (should + (equal "| 123 |\n|-----|\n" + (org-test-with-temp-text "| 123 |\n|-|" + (org-table-align) + (buffer-string)))) + (should + (equal "| a | b |\n|---+---|\n" + (org-test-with-temp-text "| a | b |\n|-+-|" + (org-table-align) + (buffer-string)))) + ;; Handle empty fields. + (should + (equal "| a | bc |\n| bcd | |\n" + (org-test-with-temp-text "| a | bc |\n| bcd | |" + (org-table-align) + (buffer-string)))) + (should + (equal "| abc | bc |\n| | bcd |\n" + (org-test-with-temp-text "| abc | bc |\n| | bcd |" + (org-table-align) + (buffer-string)))) + ;; Handle missing fields. + (should + (equal "| a | b |\n| c | |\n" + (org-test-with-temp-text "| a | b |\n| c |" + (org-table-align) + (buffer-string)))) + (should + (equal "| a | b |\n|---+---|\n" + (org-test-with-temp-text "| a | b |\n|---|" + (org-table-align) + (buffer-string)))) + ;; Alignment is done to the right when the ratio of numbers in the + ;; column is superior to `org-table-number-fraction'. + (should + (equal "| 1 |\n| 12 |\n| abc |" + (org-test-with-temp-text "| 1 |\n| 12 |\n| abc |" + (let ((org-table-number-fraction 0.5)) (org-table-align)) + (buffer-string)))) + (should + (equal "| 1 |\n| ab |\n| abc |" + (org-test-with-temp-text "| 1 |\n| ab |\n| abc |" + (let ((org-table-number-fraction 0.5)) (org-table-align)) + (buffer-string)))) + ;; Obey to alignment cookies. + (should + (equal "| <r> |\n| ab |\n| abc |" + (org-test-with-temp-text "| <r> |\n| ab |\n| abc |" + (let ((org-table-number-fraction 0.5)) (org-table-align)) + (buffer-string)))) + (should + (equal "| <l> |\n| 12 |\n| 123 |" + (org-test-with-temp-text "| <l> |\n| 12 |\n| 123 |" + (let ((org-table-number-fraction 0.5)) (org-table-align)) + (buffer-string)))) + (should + (equal "| <c> |\n| 1 |\n| 123 |" + (org-test-with-temp-text "| <c> |\n| 1 |\n| 123 |" + (let ((org-table-number-fraction 0.5)) (org-table-align)) + (buffer-string))))) + + ;;; Sorting (ert-deftest test-org-table/sort-lines () |