summaryrefslogtreecommitdiff
path: root/lisp/org/org-table.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org/org-table.el')
-rw-r--r--lisp/org/org-table.el3849
1 files changed, 2272 insertions, 1577 deletions
diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el
index a65629b302c..a21587acbe0 100644
--- a/lisp/org/org-table.el
+++ b/lisp/org/org-table.el
@@ -35,51 +35,66 @@
;;; Code:
(require 'cl-lib)
-(require 'org)
+(require 'org-macs)
+(require 'org-compat)
+(require 'org-keys)
+(declare-function calc-eval "calc" (str &optional separator &rest args))
+(declare-function org-at-timestamp-p "org" (&optional extended))
+(declare-function org-delete-backward-char "org" (N))
(declare-function org-element-at-point "org-element" ())
(declare-function org-element-contents "org-element" (element))
(declare-function org-element-extract-element "org-element" (element))
(declare-function org-element-interpret-data "org-element" (data))
-(declare-function org-element-lineage "org-element"
- (blob &optional types with-self))
-(declare-function org-element-map "org-element"
- (data types fun
- &optional info first-match no-recursion with-affiliated))
-(declare-function org-element-parse-buffer "org-element"
- (&optional granularity visible-only))
+(declare-function org-element-lineage "org-element" (blob &optional types with-self))
+(declare-function org-element-map "org-element" (data types fun &optional info first-match no-recursion with-affiliated))
+(declare-function org-element-parse-buffer "org-element" (&optional granularity visible-only))
(declare-function org-element-property "org-element" (property element))
(declare-function org-element-type "org-element" (element))
-
+(declare-function org-entry-get "org" (pom property &optional inherit literal-nil))
(declare-function org-export-create-backend "ox" (&rest rest) t)
(declare-function org-export-data-with-backend "ox" (data backend info))
-(declare-function org-export-filter-apply-functions "ox"
- (filters value info))
+(declare-function org-export-filter-apply-functions "ox" (filters value info))
(declare-function org-export-first-sibling-p "ox" (blob info))
(declare-function org-export-get-backend "ox" (name))
-(declare-function org-export-get-environment "ox"
- (&optional backend subtreep ext-plist))
+(declare-function org-export-get-environment "ox" (&optional backend subtreep ext-plist))
(declare-function org-export-install-filters "ox" (info))
(declare-function org-export-table-has-special-column-p "ox" (table))
(declare-function org-export-table-row-is-special-p "ox" (table-row info))
-
-(declare-function calc-eval "calc" (str &optional separator &rest args))
+(declare-function org-id-find "org-id" (id &optional markerp))
+(declare-function org-indent-line "org" ())
+(declare-function org-load-modules-maybe "org" (&optional force))
+(declare-function org-restart-font-lock "org" ())
+(declare-function org-sort-remove-invisible "org" (s))
+(declare-function org-time-stamp-format "org" (&optional long inactive))
+(declare-function org-time-string-to-absolute "org" (s &optional daynr prefer buffer pos))
+(declare-function org-time-string-to-time "org" (s))
+(declare-function org-timestamp-up-day "org" (&optional arg))
(defvar constants-unit-system)
+(defvar org-M-RET-may-split-line)
(defvar org-element-use-cache)
(defvar org-export-filters-alist)
-(defvar org-table-follow-field-mode)
-(defvar orgtbl-mode) ; defined below
-(defvar orgtbl-mode-menu) ; defined when orgtbl mode get initialized
+(defvar org-finish-function)
+(defvar org-inhibit-highlight-removal)
+(defvar org-inhibit-startup)
+(defvar org-selected-window)
+(defvar org-self-insert-cluster-for-undo)
+(defvar org-self-insert-command-undo-counter)
+(defvar org-ts-regexp)
+(defvar org-ts-regexp-both)
+(defvar org-ts-regexp-inactive)
+(defvar org-ts-regexp3)
+(defvar org-window-configuration)
(defvar sort-fold-case)
-(defvar orgtbl-after-send-table-hook nil
- "Hook for functions attaching to `C-c C-c', if the table is sent.
-This can be used to add additional functionality after the table is sent
-to the receiver position, otherwise, if table is not sent, the functions
-are not run.")
+
+;;; Customizables
-(defvar org-table-TBLFM-begin-regexp "^[ \t]*|.*\n[ \t]*#\\+TBLFM: ")
+(defgroup org-table nil
+ "Options concerning tables in Org mode."
+ :tag "Org Table"
+ :group 'org)
(defcustom orgtbl-optimized t
"Non-nil means use the optimized table editor version for `orgtbl-mode'.
@@ -193,6 +208,15 @@ alignment to the right border applies."
:group 'org-table-settings
:type 'number)
+(defcustom org-table-formula-field-format "%s"
+ "Format for fields which contain the result of a formula.
+For example, using \"~%s~\" will display the result within tilde
+characters. Beware that modifying the display can prevent the
+field from being used in another formula."
+ :group 'org-table-settings
+ :version "24.1"
+ :type 'string)
+
(defgroup org-table-editing nil
"Behavior of tables during editing in Org mode."
:tag "Org Table Editing"
@@ -231,9 +255,6 @@ fields."
(const :tag "with yes-or-no" yes-or-no-p)
(const :tag "with y-or-n" y-or-n-p)
(const :tag "no confirmation" nil)))
-(put 'org-table-fix-formulas-confirm
- 'safe-local-variable
- #'(lambda (x) (member x '(yes-or-no-p y-or-n-p))))
(defcustom org-table-tab-jumps-over-hlines t
"Non-nil means tab in the last column of a table with jump over a hline.
@@ -244,6 +265,13 @@ this line."
:group 'org-table-editing
:type 'boolean)
+(defcustom org-table-shrunk-column-indicator "…"
+ "String to be displayed in a shrunk column."
+ :group 'org-table-editing
+ :type 'string
+ :package-version '(Org . "9.2")
+ :safe (lambda (v) (and (stringp v) (not (equal v "")))))
+
(defgroup org-table-calculation nil
"Options concerning tables in Org mode."
:tag "Org Table Calculation"
@@ -279,8 +307,7 @@ t accept as input and present for editing"
calc-prefer-frac nil
calc-symbolic-mode nil
calc-date-format (YYYY "-" MM "-" DD " " Www (" " hh ":" mm))
- calc-display-working-message t
- )
+ calc-display-working-message t)
"List with Calc mode settings for use in `calc-eval' for table formulas.
The list must contain alternating symbols (Calc modes variables and values).
Don't remove any of the default settings, just change the values. Org mode
@@ -313,15 +340,6 @@ So this is about 08:32:34 versus 8:33:34."
:type 'boolean
:safe #'booleanp)
-(defcustom org-table-formula-field-format "%s"
- "Format for fields which contain the result of a formula.
-For example, using \"~%s~\" will display the result within tilde
-characters. Beware that modifying the display can prevent the
-field from being used in another formula."
- :group 'org-table-settings
- :version "24.1"
- :type 'string)
-
(defcustom org-table-formula-evaluate-inline t
"Non-nil means TAB and RET evaluate a formula in current table field.
If the current field starts with an equal sign, it is assumed to be a formula
@@ -393,7 +411,6 @@ many columns as needed. When set to `warn', issue a warning when
doing so. When set to `prompt', ask user before creating a new
column. Otherwise, throw an error."
:group 'org-table-calculation
- :version "26.1"
:package-version '(Org . "8.3")
:type '(choice
(const :tag "Out-of-bounds field generates an error (default)" nil)
@@ -419,12 +436,38 @@ available parameters."
"Max lines that `org-table-convert-region' will attempt to process.
The function can be slow on larger regions; this safety feature
-prevents it from hanging emacs."
+prevents it from hanging Emacs."
:group 'org-table-import-export
:type 'integer
- :version "26.1"
:package-version '(Org . "8.3"))
+
+;;; Regexps Constants
+
+(defconst org-table-any-line-regexp "^[ \t]*\\(|\\|\\+-[-+]\\)"
+ "Detect an org-type or table-type table.")
+
+(defconst org-table-line-regexp "^[ \t]*|"
+ "Detect an org-type table line.")
+
+(defconst org-table-dataline-regexp "^[ \t]*|[^-]"
+ "Detect an org-type table line.")
+
+(defconst org-table-hline-regexp "^[ \t]*|-"
+ "Detect an org-type table hline.")
+
+(defconst org-table1-hline-regexp "^[ \t]*\\+-[-+]"
+ "Detect a table-type table hline.")
+
+(defconst org-table-any-border-regexp "^[ \t]*[^|+ \t]"
+ "Detect the first line outside a table when searching from within it.
+This works for both table types.")
+
+(defconst org-TBLFM-regexp "^[ \t]*#\\+TBLFM: "
+ "Detect a #+TBLFM line.")
+
+(defvar org-table-TBLFM-begin-regexp "^[ \t]*|.*\n[ \t]*#\\+TBLFM: ")
+
(defconst org-table-auto-recalculate-regexp "^[ \t]*| *# *\\(|\\|$\\)"
"Regexp matching a line marked for automatic recalculation.")
@@ -437,10 +480,52 @@ prevents it from hanging emacs."
(defconst org-table-border-regexp "^[ \t]*[^| \t]"
"Regexp matching any line outside an Org table.")
+(defconst org-table-range-regexp
+ "@\\([-+]?I*[-+]?[0-9]*\\)\\(\\$[-+]?[0-9]+\\)?\\(\\.\\.@?\\([-+]?I*[-+]?[0-9]*\\)\\(\\$[-+]?[0-9]+\\)?\\)?"
+ ;; 1 2 3 4 5
+ "Regular expression for matching ranges in formulas.")
+
+(defconst org-table-range-regexp2
+ (concat
+ "\\(" "@[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)"
+ "\\.\\."
+ "\\(" "@?[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)")
+ "Match a range for reference display.")
+
+(defconst org-table-translate-regexp
+ (concat "\\(" "@[-0-9I$]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\)")
+ "Match a reference that needs translation, for reference display.")
+
+(defconst org-table-separator-space
+ (propertize " " 'display '(space :relative-width 1))
+ "Space used around fields when aligning the table.
+This space serves as a segment separator for the purposes of the
+bidirectional reordering.")
+
+
+;;; Internal Variables
+
(defvar org-table-last-highlighted-reference nil)
(defvar org-table-formula-history nil)
+(defvar org-field-marker nil)
+(defvar org-table-buffer-is-an nil)
+
+(defvar-local org-table-formula-constants-local nil
+ "Local version of `org-table-formula-constants'.")
+
+(defvar org-table-may-need-update t
+ "Indicates that a table might need an update.
+This variable is set by `org-before-change-function'.
+`org-table-align' sets it back to nil.")
+
+(defvar orgtbl-after-send-table-hook nil
+ "Hook for functions attaching to `C-c C-c', if the table is sent.
+This can be used to add additional functionality after the table is sent
+to the receiver position, otherwise, if table is not sent, the functions
+are not run.")
+
(defvar org-table-column-names nil
"Alist with column names, derived from the `!' line.
This variable is initialized with `org-table-analyze'.")
@@ -483,21 +568,84 @@ variable is initialized with `org-table-analyze'.")
Line numbers are counted from the beginning of the table. This
variable is initialized with `org-table-analyze'.")
-(defconst org-table-range-regexp
- "@\\([-+]?I*[-+]?[0-9]*\\)\\(\\$[-+]?[0-9]+\\)?\\(\\.\\.@?\\([-+]?I*[-+]?[0-9]*\\)\\(\\$[-+]?[0-9]+\\)?\\)?"
- ;; 1 2 3 4 5
- "Regular expression for matching ranges in formulas.")
+(defvar org-table-aligned-begin-marker (make-marker)
+ "Marker at the beginning of the table last aligned.
+Used to check if cursor still is in that table, to minimize realignment.")
-(defconst org-table-range-regexp2
- (concat
- "\\(" "@[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)"
- "\\.\\."
- "\\(" "@?[-0-9I$&]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\|" "\\$[a-zA-Z0-9]+" "\\)")
- "Match a range for reference display.")
+(defvar org-table-aligned-end-marker (make-marker)
+ "Marker at the end of the table last aligned.
+Used to check if cursor still is in that table, to minimize realignment.")
-(defconst org-table-translate-regexp
- (concat "\\(" "@[-0-9I$]+" "\\|" "[a-zA-Z]\\{1,2\\}\\([0-9]+\\|&\\)" "\\)")
- "Match a reference that needs translation, for reference display.")
+(defvar org-table-last-alignment nil
+ "List of flags for flushright alignment, from the last re-alignment.
+This is being used to correctly align a single field after TAB or RET.")
+
+(defvar org-table-last-column-widths nil
+ "List of max width of fields in each column.
+This is being used to correctly align a single field after TAB or RET.")
+
+(defvar-local org-table-formula-debug nil
+ "Non-nil means debug table formulas.
+When nil, simply write \"#ERROR\" in corrupted fields.")
+
+(defvar-local org-table-overlay-coordinates nil
+ "Overlay coordinates after each align of a table.")
+
+(defvar org-last-recalc-line nil)
+
+(defvar org-show-positions nil)
+
+(defvar org-table-rectangle-overlays nil)
+
+(defvar org-table-clip nil
+ "Clipboard for table regions.")
+
+(defvar org-timecnt nil)
+
+(defvar org-recalc-commands nil
+ "List of commands triggering the recalculation of a line.
+Will be filled automatically during use.")
+
+(defvar org-recalc-marks
+ '((" " . "Unmarked: no special line, no automatic recalculation")
+ ("#" . "Automatically recalculate this line upon TAB, RET, and C-c C-c in the line")
+ ("*" . "Recalculate only when entire table is recalculated with `C-u C-c *'")
+ ("!" . "Column name definition line. Reference in formula as $name.")
+ ("$" . "Parameter definition line name=value. Reference in formula as $name.")
+ ("_" . "Names for values in row below this one.")
+ ("^" . "Names for values in row above this one.")))
+
+(defvar org-tbl-calc-modes nil)
+
+(defvar org-pos nil)
+
+
+;;; Macros and Inlined Functions
+
+(defmacro org-table-with-shrunk-columns (&rest body)
+ "Expand all columns before executing BODY, then shrink them again."
+ (declare (debug (body)))
+ (org-with-gensyms (shrunk-columns begin end)
+ `(let ((,begin (copy-marker (org-table-begin)))
+ (,end (copy-marker (org-table-end) t))
+ (,shrunk-columns (org-table--list-shrunk-columns)))
+ (org-with-point-at ,begin (org-table-expand ,begin ,end))
+ (unwind-protect
+ (progn ,@body)
+ (org-table--shrink-columns ,shrunk-columns ,begin ,end)
+ (set-marker ,begin nil)
+ (set-marker ,end nil)))))
+
+(defmacro org-table-with-shrunk-field (&rest body)
+ "Save field shrunk state, execute BODY and restore state."
+ (declare (debug (body)))
+ (org-with-gensyms (end shrunk size)
+ `(let* ((,shrunk (save-match-data (org-table--shrunk-field)))
+ (,end (and ,shrunk (copy-marker (overlay-end ,shrunk) t)))
+ (,size (and ,shrunk (- ,end (overlay-start ,shrunk)))))
+ (when ,shrunk (delete-overlay ,shrunk))
+ (unwind-protect (progn ,@body)
+ (when ,shrunk (move-overlay ,shrunk (- ,end ,size) ,end))))))
(defmacro org-table-save-field (&rest body)
"Save current field; execute BODY; restore field.
@@ -512,6 +660,66 @@ Field is restored even in case of abnormal exit."
(org-table-goto-column ,column)
(set-marker ,line nil)))))
+(defsubst org-table--set-calc-mode (var &optional value)
+ (if (stringp var)
+ (setq var (assoc var '(("D" calc-angle-mode deg)
+ ("R" calc-angle-mode rad)
+ ("F" calc-prefer-frac t)
+ ("S" calc-symbolic-mode t)))
+ value (nth 2 var) var (nth 1 var)))
+ (if (memq var org-tbl-calc-modes)
+ (setcar (cdr (memq var org-tbl-calc-modes)) value)
+ (cons var (cons value org-tbl-calc-modes)))
+ org-tbl-calc-modes)
+
+
+;;; Predicates
+
+(defun org-at-TBLFM-p (&optional pos)
+ "Non-nil when point (or POS) is in #+TBLFM line."
+ (save-excursion
+ (goto-char (or pos (point)))
+ (beginning-of-line)
+ (and (let ((case-fold-search t)) (looking-at org-TBLFM-regexp))
+ (eq (org-element-type (org-element-at-point)) 'table))))
+
+(defun org-at-table-p (&optional table-type)
+ "Non-nil if the cursor is inside an Org table.
+If TABLE-TYPE is non-nil, also check for table.el-type tables."
+ (and (org-match-line (if table-type "[ \t]*[|+]" "[ \t]*|"))
+ (or (not (derived-mode-p 'org-mode))
+ (let ((e (org-element-lineage (org-element-at-point) '(table) t)))
+ (and e (or table-type
+ (eq 'org (org-element-property :type e))))))))
+
+(defun org-at-table.el-p ()
+ "Non-nil when point is at a table.el table."
+ (and (org-match-line "[ \t]*[|+]")
+ (let ((element (org-element-at-point)))
+ (and (eq (org-element-type element) 'table)
+ (eq (org-element-property :type element) 'table.el)))))
+
+(defun org-at-table-hline-p ()
+ "Non-nil when point is inside a hline in a table.
+Assume point is already in a table."
+ (org-match-line org-table-hline-regexp))
+
+(defun org-table-check-inside-data-field (&optional noerror assume-table)
+ "Non-nil when point is inside a table data field.
+Raise an error otherwise, unless NOERROR is non-nil. In that
+case, return nil if point is not inside a data field. When
+optional argument ASSUME-TABLE is non-nil, assume point is within
+a table."
+ (cond ((and (or assume-table (org-at-table-p))
+ (not (save-excursion (skip-chars-backward " \t") (bolp)))
+ (not (org-at-table-hline-p))
+ (not (looking-at-p "[ \t]*$"))))
+ (noerror nil)
+ (t (user-error "Not in table data field"))))
+
+
+;;; Create, Import, and Convert Tables
+
;;;###autoload
(defun org-table-create-with-table.el ()
"Use the table.el package to insert a new table.
@@ -520,13 +728,13 @@ and table.el tables."
(interactive)
(require 'table)
(cond
- ((org-at-table.el-p)
- (if (y-or-n-p "Convert table to Org table? ")
- (org-table-convert)))
- ((org-at-table-p)
- (when (y-or-n-p "Convert table to table.el table? ")
- (org-table-align)
- (org-table-convert)))
+ ((and (org-at-table.el-p)
+ (y-or-n-p "Convert table to Org table? "))
+ (org-table-convert))
+ ((and (org-at-table-p)
+ (y-or-n-p "Convert table to table.el table? "))
+ (org-table-align)
+ (org-table-convert))
(t (call-interactively 'table-insert))))
;;;###autoload
@@ -567,12 +775,11 @@ SIZE is a string Columns x Rows like for example \"3x2\"."
;; (mapcar (lambda (x) (insert line)) (make-list rows t))
(dotimes (_ rows) (insert line))
(goto-char pos)
- (if (> rows 1)
- ;; Insert a hline after the first row.
- (progn
- (end-of-line 1)
- (insert "\n|-")
- (goto-char pos)))
+ (when (> rows 1)
+ ;; Insert a hline after the first row.
+ (end-of-line 1)
+ (insert "\n|-")
+ (goto-char pos))
(org-table-align)))
;;;###autoload
@@ -602,8 +809,8 @@ nil When nil, the command tries to be smart and figure out the
(if (> (count-lines beg end) org-table-convert-region-max-lines)
(user-error "Region is longer than `org-table-convert-region-max-lines' (%s) lines; not converting"
org-table-convert-region-max-lines)
- (if (equal separator '(64))
- (setq separator (read-regexp "Regexp for field separator")))
+ (when (equal separator '(64))
+ (setq separator (read-regexp "Regexp for field separator")))
(goto-char beg)
(beginning-of-line 1)
(setq beg (point-marker))
@@ -672,264 +879,53 @@ regexp When a regular expression, use it to match the separator."
(insert-file-contents file)
(org-table-convert-region beg (+ (point) (- (point-max) pm)) separator)))
-
-;;;###autoload
-(defun org-table-export (&optional file format)
- "Export table to a file, with configurable format.
-Such a file can be imported into usual spreadsheet programs.
-
-FILE can be the output file name. If not given, it will be taken
-from a TABLE_EXPORT_FILE property in the current entry or higher
-up in the hierarchy, or the user will be prompted for a file
-name. FORMAT can be an export format, of the same kind as it
-used when `orgtbl-mode' sends a table in a different format.
-
-The command suggests a format depending on TABLE_EXPORT_FORMAT,
-whether it is set locally or up in the hierarchy, then on the
-extension of the given file name, and finally on the variable
-`org-table-export-default-format'."
+(defun org-table-convert ()
+ "Convert from Org table to table.el and back.
+Obviously, this only works within limits. When an Org table is converted
+to table.el, all horizontal separator lines get lost, because table.el uses
+these as cell boundaries and has no notion of horizontal lines. A table.el
+table can be converted to an Org table only if it does not do row or column
+spanning. Multiline cells will become multiple cells. Beware, Org mode
+does not test if the table can be successfully converted - it blindly
+applies a recipe that works for simple tables."
(interactive)
- (unless (org-at-table-p) (user-error "No table at point"))
- (org-table-align) ; Make sure we have everything we need.
- (let ((file (or file (org-entry-get (point) "TABLE_EXPORT_FILE" t))))
- (unless file
- (setq file (read-file-name "Export table to: "))
- (unless (or (not (file-exists-p file))
- (y-or-n-p (format "Overwrite file %s? " file)))
- (user-error "File not written")))
- (when (file-directory-p file)
- (user-error "This is a directory path, not a file"))
- (when (and (buffer-file-name (buffer-base-buffer))
- (file-equal-p
- (file-truename file)
- (file-truename (buffer-file-name (buffer-base-buffer)))))
- (user-error "Please specify a file name that is different from current"))
- (let ((fileext (concat (file-name-extension file) "$"))
- (format (or format (org-entry-get (point) "TABLE_EXPORT_FORMAT" t))))
- (unless format
- (let* ((formats '("orgtbl-to-tsv" "orgtbl-to-csv" "orgtbl-to-latex"
- "orgtbl-to-html" "orgtbl-to-generic"
- "orgtbl-to-texinfo" "orgtbl-to-orgtbl"
- "orgtbl-to-unicode"))
- (deffmt-readable
- (replace-regexp-in-string
- "\t" "\\t"
- (replace-regexp-in-string
- "\n" "\\n"
- (or (car (delq nil
- (mapcar
- (lambda (f)
- (and (string-match-p fileext f) f))
- formats)))
- org-table-export-default-format)
- t t) t t)))
- (setq format
- (org-completing-read
- "Format: " formats nil nil deffmt-readable))))
- (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
- (let ((transform (intern (match-string 1 format)))
- (params (and (match-end 2)
- (read (concat "(" (match-string 2 format) ")"))))
- (table (org-table-to-lisp
- (buffer-substring-no-properties
- (org-table-begin) (org-table-end)))))
- (unless (fboundp transform)
- (user-error "No such transformation function %s" transform))
- (let (buf)
- (with-current-buffer (find-file-noselect file)
- (setq buf (current-buffer))
- (erase-buffer)
- (fundamental-mode)
- (insert (funcall transform table params) "\n")
- (save-buffer))
- (kill-buffer buf))
- (message "Export done."))
- (user-error "TABLE_EXPORT_FORMAT invalid")))))
-
-(defvar org-table-aligned-begin-marker (make-marker)
- "Marker at the beginning of the table last aligned.
-Used to check if cursor still is in that table, to minimize realignment.")
-(defvar org-table-aligned-end-marker (make-marker)
- "Marker at the end of the table last aligned.
-Used to check if cursor still is in that table, to minimize realignment.")
-(defvar org-table-last-alignment nil
- "List of flags for flushright alignment, from the last re-alignment.
-This is being used to correctly align a single field after TAB or RET.")
-(defvar org-table-last-column-widths nil
- "List of max width of fields in each column.
-This is being used to correctly align a single field after TAB or RET.")
-(defvar-local org-table-formula-debug nil
- "Non-nil means debug table formulas.
-When nil, simply write \"#ERROR\" in corrupted fields.")
-(defvar-local org-table-overlay-coordinates nil
- "Overlay coordinates after each align of a table.")
-
-(defvar org-last-recalc-line nil)
-(defvar org-table-do-narrow t) ; for dynamic scoping
-(defconst org-narrow-column-arrow "=>"
- "Used as display property in narrowed table columns.")
+ (require 'table)
+ (if (org-at-table.el-p)
+ ;; convert to Org table
+ (let ((beg (copy-marker (org-table-begin t)))
+ (end (copy-marker (org-table-end t))))
+ (table-unrecognize-region beg end)
+ (goto-char beg)
+ (while (re-search-forward "^\\([ \t]*\\)\\+-.*\n" end t)
+ (replace-match ""))
+ (goto-char beg))
+ (if (org-at-table-p)
+ ;; convert to table.el table
+ (let ((beg (copy-marker (org-table-begin)))
+ (end (copy-marker (org-table-end))))
+ ;; first, get rid of all horizontal lines
+ (goto-char beg)
+ (while (re-search-forward "^\\([ \t]*\\)|-.*\n" end t)
+ (replace-match ""))
+ ;; insert a hline before first
+ (goto-char beg)
+ (org-table-insert-hline 'above)
+ (beginning-of-line -1)
+ ;; insert a hline after each line
+ (while (progn (beginning-of-line 3) (< (point) end))
+ (org-table-insert-hline))
+ (goto-char beg)
+ (setq end (move-marker end (org-table-end)))
+ ;; replace "+" at beginning and ending of hlines
+ (while (re-search-forward "^\\([ \t]*\\)|-" end t)
+ (replace-match "\\1+-"))
+ (goto-char beg)
+ (while (re-search-forward "-|[ \t]*$" end t)
+ (replace-match "-+"))
+ (goto-char beg)))))
-;;;###autoload
-(defun org-table-align ()
- "Align the table at point by aligning all vertical bars."
- (interactive)
- (let* ((beg (org-table-begin))
- (end (copy-marker (org-table-end))))
- (org-table-save-field
- ;; Make sure invisible characters in the table are at the right
- ;; place since column widths take them into account.
- (font-lock-fontify-region beg end)
- (move-marker org-table-aligned-begin-marker beg)
- (move-marker org-table-aligned-end-marker end)
- (goto-char beg)
- (let* ((indent (progn (looking-at "[ \t]*") (match-string 0)))
- ;; Table's rows. Separators are replaced by nil. Trailing
- ;; spaces are also removed.
- (lines (mapcar (lambda (l)
- (and (not (string-match-p "\\`[ \t]*|-" l))
- (let ((l (org-trim l)))
- (remove-text-properties
- 0 (length l) '(display t org-cwidth t) l)
- l)))
- (org-split-string (buffer-substring beg end) "\n")))
- ;; Get the data fields by splitting the lines.
- (fields (mapcar (lambda (l) (org-split-string l " *| *"))
- (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)
- ;; Check for special formatting.
- (dotimes (i maxfields)
- (let ((column (mapcar (lambda (x) (or (nth i x) "")) fields))
- fmax falign)
- ;; Look for an explicit width or alignment.
- (when (save-excursion
- (or (re-search-forward "| *<[lrc][0-9]*> *\\(|\\|$\\)" end t)
- (and org-table-do-narrow
- (re-search-forward
- "| *<[lrc]?[0-9]+> *\\(|\\|$\\)" end t))))
- (catch :exit
- (dolist (cell column)
- (when (string-match "\\`<\\([lrc]\\)?\\([0-9]+\\)?>\\'" cell)
- (when (match-end 1) (setq falign (match-string 1 cell)))
- (when (and org-table-do-narrow (match-end 2))
- (setq fmax (string-to-number (match-string 2 cell))))
- (when (or falign fmax) (throw :exit nil)))))
- ;; Find fields that are wider than FMAX, and shorten them.
- (when fmax
- (dolist (x column)
- (when (> (string-width x) fmax)
- (org-add-props x nil
- 'help-echo
- (concat
- "Clipped table field, use `\\[org-table-edit-field]' to \
-edit. Full value is:\n"
- (substring-no-properties x)))
- (let ((l (length x))
- (f1 (min fmax
- (or (string-match org-bracket-link-regexp x)
- fmax)))
- (f2 1))
- (unless (> f1 1)
- (user-error
- "Cannot narrow field starting with wide link \"%s\""
- (match-string 0 x)))
- (if (= (org-string-width x) l) (setq f2 f1)
- (setq f2 1)
- (while (< (org-string-width (substring x 0 f2)) f1)
- (cl-incf f2)))
- (add-text-properties f2 l (list 'org-cwidth t) x)
- (add-text-properties
- (if (>= (string-width (substring x (1- f2) f2)) 2) (1- f2)
- (- f2 2))
- f2
- (list 'display org-narrow-column-arrow)
- x))))))
- ;; Get the maximum width for each column
- (push (or fmax (apply #'max 1 (mapcar #'org-string-width column)))
- lengths)
- ;; 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))
- ;; 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. Check not only contents, but
- ;; also columns' width.
- (dolist (l lines)
- (let ((line
- (if l (apply #'format rfmt (append (pop fields) emptycells))
- hfmt))
- (previous (buffer-substring (point) (line-end-position))))
- (if (and (equal previous line)
- (let ((a 0)
- (b 0))
- (while (and (progn
- (setq a (next-single-property-change
- a 'org-cwidth previous))
- (setq b (next-single-property-change
- b 'org-cwidth line)))
- (eq a b)))
- (eq a b)))
- (forward-line)
- (insert line "\n")
- (delete-region (point) (line-beginning-position 2))))))
- (when (and orgtbl-mode (not (derived-mode-p 'org-mode)))
- (goto-char org-table-aligned-begin-marker)
- (while (org-hide-wide-columns org-table-aligned-end-marker)))
- (set-marker end nil)
- (when org-table-overlay-coordinates (org-table-overlay-coordinates))
- (setq org-table-may-need-update nil)))))
+
+;;; Navigation and Structure Editing
;;;###autoload
(defun org-table-begin (&optional table-type)
@@ -967,58 +963,15 @@ a table."
(if (bolp) (point) (line-end-position))))))
;;;###autoload
-(defun org-table-justify-field-maybe (&optional new)
- "Justify the current field, text to left, number to right.
-Optional argument NEW may specify text to replace the current field content."
- (cond
- ((and (not new) org-table-may-need-update)) ; Realignment will happen anyway
- ((org-at-table-hline-p))
- ((and (not new)
- (or (not (eq (marker-buffer org-table-aligned-begin-marker)
- (current-buffer)))
- (< (point) org-table-aligned-begin-marker)
- (>= (point) org-table-aligned-end-marker)))
- ;; This is not the same table, force a full re-align.
- (setq org-table-may-need-update t))
- (t
- ;; Realign the current field, based on previous full realign.
- (let ((pos (point))
- (col (org-table-current-column)))
- (when (> col 0)
- (skip-chars-backward "^|")
- (if (not (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)"))
- (setq org-table-may-need-update t)
- (let* ((numbers? (nth (1- col) org-table-last-alignment))
- (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)))))
- (unless (equal new-cell cell)
- (let (org-table-may-need-update)
- (replace-match new-cell t t)))
- (goto-char pos))))))))
-
-;;;###autoload
(defun org-table-next-field ()
"Go to the next field in the current table, creating new lines as needed.
Before doing so, re-align the table if necessary."
(interactive)
(org-table-maybe-eval-formula)
(org-table-maybe-recalculate-line)
- (if (and org-table-automatic-realign
- org-table-may-need-update)
- (org-table-align))
+ (when (and org-table-automatic-realign
+ org-table-may-need-update)
+ (org-table-align))
(let ((end (org-table-end)))
(if (org-at-table-hline-p)
(end-of-line 1))
@@ -1078,7 +1031,7 @@ With numeric argument N, move N-1 fields backward first."
(user-error "No more table fields before the current")
(goto-char (match-end 0))
(and (looking-at " ") (forward-char 1)))
- (if (>= (point) pos) (org-table-beginning-of-field 2))))
+ (when (>= (point) pos) (org-table-beginning-of-field 2))))
(defun org-table-end-of-field (&optional n)
"Move to the end of the current table field.
@@ -1092,9 +1045,9 @@ With numeric argument N, move N-1 fields forward first."
(when (re-search-forward "|" (point-at-eol 1) t)
(backward-char 1)
(skip-chars-backward " ")
- (if (and (equal (char-before (point)) ?|) (looking-at " "))
- (forward-char 1)))
- (if (<= (point) pos) (org-table-end-of-field 2))))
+ (when (and (equal (char-before (point)) ?|) (equal (char-after (point)) ?\s))
+ (forward-char 1)))
+ (when (<= (point) pos) (org-table-end-of-field 2))))
;;;###autoload
(defun org-table-next-row ()
@@ -1108,6 +1061,7 @@ Before doing so, re-align the table if necessary."
(org-table-align))
(let ((col (org-table-current-column)))
(beginning-of-line 2)
+ (unless (bolp) (insert "\n")) ;missing newline at eob
(when (or (not (org-at-table-p))
(org-at-table-hline-p))
(beginning-of-line 0)
@@ -1116,106 +1070,6 @@ Before doing so, re-align the table if necessary."
(skip-chars-backward "^|\n\r")
(when (looking-at " ") (forward-char))))
-;;;###autoload
-(defun org-table-copy-down (n)
- "Copy the value of the current field one row below.
-
-If the field at the cursor is empty, copy the content of the
-nearest non-empty field above. With argument N, use the Nth
-non-empty field.
-
-If the current field is not empty, it is copied down to the next
-row, and the cursor is moved with it. Therefore, repeating this
-command causes the column to be filled row-by-row.
-
-If the variable `org-table-copy-increment' is non-nil and the
-field is an integer or a timestamp, it will be incremented while
-copying. By default, increment by the difference between the
-value in the current field and the one in the field above. To
-increment using a fixed integer, set `org-table-copy-increment'
-to a number. In the case of a timestamp, increment by days."
- (interactive "p")
- (let* ((colpos (org-table-current-column))
- (col (current-column))
- (field (save-excursion (org-table-get-field)))
- (field-up (or (save-excursion
- (org-table-get (1- (org-table-current-line))
- (org-table-current-column))) ""))
- (non-empty (string-match "[^ \t]" field))
- (non-empty-up (string-match "[^ \t]" field-up))
- (beg (org-table-begin))
- (orig-n n)
- txt txt-up inc)
- (org-table-check-inside-data-field)
- (if (not non-empty)
- (save-excursion
- (setq txt
- (catch 'exit
- (while (progn (beginning-of-line 1)
- (re-search-backward org-table-dataline-regexp
- beg t))
- (org-table-goto-column colpos t)
- (if (and (looking-at
- "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|")
- (<= (setq n (1- n)) 0))
- (throw 'exit (match-string 1))))))
- (setq field-up
- (catch 'exit
- (while (progn (beginning-of-line 1)
- (re-search-backward org-table-dataline-regexp
- beg t))
- (org-table-goto-column colpos t)
- (if (and (looking-at
- "|[ \t]*\\([^| \t][^|]*?\\)[ \t]*|")
- (<= (setq n (1- n)) 0))
- (throw 'exit (match-string 1))))))
- (setq non-empty-up (and field-up (string-match "[^ \t]" field-up))))
- ;; Above field was not empty, go down to the next row
- (setq txt (org-trim field))
- (org-table-next-row)
- (org-table-blank-field))
- (if non-empty-up (setq txt-up (org-trim field-up)))
- (setq inc (cond
- ((numberp org-table-copy-increment) org-table-copy-increment)
- (txt-up (cond ((and (string-match org-ts-regexp3 txt-up)
- (string-match org-ts-regexp3 txt))
- (- (org-time-string-to-absolute txt)
- (org-time-string-to-absolute txt-up)))
- ((string-match org-ts-regexp3 txt) 1)
- ((string-match "\\([-+]\\)?[0-9]*\\(?:\\.[0-9]+\\)?" txt-up)
- (- (string-to-number txt)
- (string-to-number (match-string 0 txt-up))))
- (t 1)))
- (t 1)))
- (if (not txt)
- (user-error "No non-empty field found")
- (if (and org-table-copy-increment
- (not (equal orig-n 0))
- (string-match-p "^[-+^/*0-9eE.]+$" txt)
- (< (string-to-number txt) 100000000))
- (setq txt (calc-eval (concat txt "+" (number-to-string inc)))))
- (insert txt)
- (org-move-to-column col)
- (if (and org-table-copy-increment (org-at-timestamp-p 'lax))
- (org-timestamp-up-day inc)
- (org-table-maybe-recalculate-line))
- (org-table-align)
- (org-move-to-column col))))
-
-(defun org-table-check-inside-data-field (&optional noerror)
- "Is point inside a table data field?
-I.e. not on a hline or before the first or after the last column?
-This actually throws an error, so it aborts the current command."
- (cond ((and (org-at-table-p)
- (not (save-excursion (skip-chars-backward " \t") (bolp)))
- (not (org-at-table-hline-p))
- (not (looking-at "[ \t]*$"))))
- (noerror nil)
- (t (user-error "Not in table data field"))))
-
-(defvar org-table-clip nil
- "Clipboard for table regions.")
-
(defun org-table-get (line column)
"Get the field in table line LINE, column COLUMN.
If LINE is larger than the number of data lines in the table, the function
@@ -1248,6 +1102,30 @@ When ALIGN is set, also realign the table."
(< (point-at-eol) pos))))
cnt))
+(defun org-table-current-column ()
+ "Return current column number."
+ (interactive)
+ (save-excursion
+ (let ((pos (point)))
+ (beginning-of-line)
+ (if (not (search-forward "|" pos t)) 0
+ (let ((column 1)
+ (separator (if (org-at-table-hline-p) "[+|]" "|")))
+ (while (re-search-forward separator pos t) (cl-incf column))
+ column)))))
+
+(defun org-table-current-dline ()
+ "Find out what table data line we are in.
+Only data lines count for this."
+ (save-excursion
+ (let ((c 0)
+ (pos (line-beginning-position)))
+ (goto-char (org-table-begin))
+ (while (<= (point) pos)
+ (when (looking-at org-table-dataline-regexp) (cl-incf c))
+ (forward-line))
+ c)))
+
(defun org-table-goto-line (N)
"Go to the Nth data line in the current table.
Return t when the line exists, nil if it does not exist."
@@ -1289,7 +1167,8 @@ value."
(let* ((pos (match-beginning 0))
(val (buffer-substring pos (match-end 0))))
(when replace
- (replace-match (if (equal replace "") " " replace) t t))
+ (org-table-with-shrunk-field
+ (replace-match (if (equal replace "") " " replace) t t)))
(goto-char (min (line-end-position) (1+ pos)))
val)))
@@ -1341,26 +1220,36 @@ value."
(car eqn) "=" (cdr eqn))))
"")))))
-(defun org-table-current-column ()
- "Find out which column we are in."
- (interactive)
- (save-excursion
- (let ((column 0) (pos (point)))
- (beginning-of-line)
- (while (search-forward "|" pos t) (cl-incf column))
- column)))
+(defun org-table-goto-field (ref &optional create-column-p)
+ "Move point to a specific field in the current table.
-(defun org-table-current-dline ()
- "Find out what table data line we are in.
-Only data lines count for this."
- (save-excursion
- (let ((c 0)
- (pos (line-beginning-position)))
- (goto-char (org-table-begin))
- (while (<= (point) pos)
- (when (looking-at org-table-dataline-regexp) (cl-incf c))
- (forward-line))
- c)))
+REF is either the name of a field its absolute reference, as
+a string. No column is created unless CREATE-COLUMN-P is
+non-nil. If it is a function, it is called with the column
+number as its argument as is used as a predicate to know if the
+column can be created.
+
+This function assumes the table is already analyzed (i.e., using
+`org-table-analyze')."
+ (let* ((coordinates
+ (cond
+ ((cdr (assoc ref org-table-named-field-locations)))
+ ((string-match "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'" ref)
+ (list (condition-case nil
+ (aref org-table-dlines
+ (string-to-number (match-string 1 ref)))
+ (error (user-error "Invalid row number in %s" ref)))
+ (string-to-number (match-string 2 ref))))
+ (t (user-error "Unknown field: %s" ref))))
+ (line (car coordinates))
+ (column (nth 1 coordinates))
+ (create-new-column (if (functionp create-column-p)
+ (funcall create-column-p column)
+ create-column-p)))
+ (when coordinates
+ (goto-char org-table-current-begin-pos)
+ (forward-line line)
+ (org-table-goto-column column nil create-new-column))))
;;;###autoload
(defun org-table-goto-column (n &optional on-delim force)
@@ -1391,41 +1280,50 @@ However, when FORCE is non-nil, create new columns if necessary."
(interactive)
(unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
- (let* ((col (max 1 (org-table-current-column)))
- (beg (org-table-begin))
- (end (copy-marker (org-table-end))))
- (org-table-save-field
- (goto-char beg)
- (while (< (point) end)
- (unless (org-at-table-hline-p)
- (org-table-goto-column col t)
- (insert "| "))
- (forward-line)))
- (set-marker end nil)
+ (let ((col (max 1 (org-table-current-column)))
+ (beg (org-table-begin))
+ (end (copy-marker (org-table-end)))
+ (shrunk-columns (org-table--list-shrunk-columns)))
+ (org-table-expand beg end)
+ (save-excursion
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (org-at-table-hline-p)
+ (org-table-goto-column col t)
+ (unless (search-forward "|" (line-end-position) t 2)
+ ;; Add missing vertical bar at the end of the row.
+ (end-of-line)
+ (insert "|"))
+ (insert " |"))
+ (forward-line)))
+ (org-table-goto-column (1+ col))
(org-table-align)
+ ;; Shift appropriately stored shrunk column numbers, then hide the
+ ;; columns again.
+ (org-table--shrink-columns (mapcar (lambda (c) (if (<= c col) c (1+ c)))
+ shrunk-columns)
+ beg end)
+ (set-marker end nil)
+ ;; Fix TBLFM formulas, if desirable.
(when (or (not org-table-fix-formulas-confirm)
(funcall org-table-fix-formulas-confirm "Fix formulas? "))
(org-table-fix-formulas "$" nil (1- col) 1)
(org-table-fix-formulas "$LR" nil (1- col) 1))))
(defun org-table-find-dataline ()
- "Find a data line in the current table, which is needed for column commands."
- (if (and (org-at-table-p)
- (not (org-at-table-hline-p)))
- t
- (let ((col (current-column))
- (end (org-table-end)))
- (org-move-to-column col)
- (while (and (< (point) end)
- (or (not (= (current-column) col))
- (org-at-table-hline-p)))
- (beginning-of-line 2)
- (org-move-to-column col))
- (if (and (org-at-table-p)
- (not (org-at-table-hline-p)))
- t
- (user-error
- "Please position cursor in a data line for column operations")))))
+ "Find a data line in the current table, which is needed for column commands.
+This function assumes point is in a table. Raise an error when
+there is no data row below."
+ (or (not (org-at-table-hline-p))
+ (let ((col (current-column))
+ (end (org-table-end)))
+ (forward-line)
+ (while (and (< (point) end) (org-at-table-hline-p))
+ (forward-line))
+ (when (>= (point) end)
+ (user-error "Cannot find data row for column operation"))
+ (org-move-to-column col)
+ t)))
(defun org-table-line-to-dline (line &optional above)
"Turn a buffer line number into a data line number.
@@ -1440,7 +1338,7 @@ non-nil, the one above is used."
(cond ((or (> (aref org-table-dlines min) line)
(< (aref org-table-dlines max) line))
nil)
- ((= (aref org-table-dlines max) line) max)
+ ((= line (aref org-table-dlines max)) max)
(t (catch 'exit
(while (> (- max min) 1)
(let* ((mean (/ (+ max min) 2))
@@ -1448,7 +1346,84 @@ non-nil, the one above is used."
(cond ((= v line) (throw 'exit mean))
((> v line) (setq max mean))
(t (setq min mean)))))
- (if above min max))))))
+ (cond ((= line (aref org-table-dlines max)) max)
+ ((= line (aref org-table-dlines min)) min)
+ (above min)
+ (t max)))))))
+
+(defun org-table--swap-cells (row1 col1 row2 col2)
+ "Swap two cells indicated by the coordinates provided.
+ROW1, COL1, ROW2, COL2 are integers indicating the row/column
+position of the two cells that will be swapped in the table."
+ (let ((content1 (org-table-get row1 col1))
+ (content2 (org-table-get row2 col2)))
+ (org-table-put row1 col1 content2)
+ (org-table-put row2 col2 content1)))
+
+(defun org-table--move-cell (direction)
+ "Move the current cell in a cardinal direction.
+DIRECTION is a symbol among `up', `down', `left', and `right'.
+The contents the current cell are swapped with cell in the
+indicated direction. Raise an error if the move cannot be done."
+ (let ((row-shift (pcase direction (`up -1) (`down 1) (_ 0)))
+ (column-shift (pcase direction (`left -1) (`right 1) (_ 0))))
+ (when (and (= 0 row-shift) (= 0 column-shift))
+ (error "Invalid direction: %S" direction))
+ ;; Initialize `org-table-current-ncol' and `org-table-dlines'.
+ (org-table-analyze)
+ (let* ((row (org-table-current-line))
+ (column (org-table-current-column))
+ (target-row (+ row row-shift))
+ (target-column (+ column column-shift))
+ (org-table-current-nrow (1- (length org-table-dlines))))
+ (when (or (< target-column 1)
+ (< target-row 1)
+ (> target-column org-table-current-ncol)
+ (> target-row org-table-current-nrow))
+ (user-error "Cannot move cell further"))
+ (org-table--swap-cells row column target-row target-column)
+ (org-table-goto-line target-row)
+ (org-table-goto-column target-column))))
+
+;;;###autoload
+(defun org-table-move-cell-up ()
+ "Move a single cell up in a table.
+Swap with anything in target cell."
+ (interactive)
+ (unless (org-table-check-inside-data-field)
+ (error "No table at point"))
+ (org-table--move-cell 'up)
+ (org-table-align))
+
+;;;###autoload
+(defun org-table-move-cell-down ()
+ "Move a single cell down in a table.
+Swap with anything in target cell."
+ (interactive)
+ (unless (org-table-check-inside-data-field)
+ (error "No table at point"))
+ (org-table--move-cell 'down)
+ (org-table-align))
+
+;;;###autoload
+(defun org-table-move-cell-left ()
+ "Move a single cell left in a table.
+Swap with anything in target cell."
+ (interactive)
+ (unless (org-table-check-inside-data-field)
+ (error "No table at point"))
+ (org-table--move-cell 'left)
+ (org-table-align))
+
+;;;###autoload
+(defun org-table-move-cell-right ()
+ "Move a single cell right in a table.
+Swap with anything in target cell."
+ (interactive)
+ (unless (org-table-check-inside-data-field)
+ (error "No table at point"))
+ (org-table--move-cell 'right)
+ (org-table-align))
;;;###autoload
(defun org-table-delete-column ()
@@ -1456,10 +1431,12 @@ non-nil, the one above is used."
(interactive)
(unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
- (org-table-check-inside-data-field)
- (let ((col (org-table-current-column))
- (beg (org-table-begin))
- (end (copy-marker (org-table-end))))
+ (org-table-check-inside-data-field nil t)
+ (let* ((col (org-table-current-column))
+ (beg (org-table-begin))
+ (end (copy-marker (org-table-end)))
+ (shrunk-columns (remq col (org-table--list-shrunk-columns))))
+ (org-table-expand beg end)
(org-table-save-field
(goto-char beg)
(while (< (point) end)
@@ -1469,9 +1446,15 @@ non-nil, the one above is used."
(and (looking-at "|[^|\n]+|")
(replace-match "|")))
(forward-line)))
- (set-marker end nil)
(org-table-goto-column (max 1 (1- col)))
(org-table-align)
+ ;; Shift appropriately stored shrunk column numbers, then hide the
+ ;; columns again.
+ (org-table--shrink-columns (mapcar (lambda (c) (if (< c col) c (1- c)))
+ shrunk-columns)
+ beg end)
+ (set-marker end nil)
+ ;; Fix TBLFM formulas, if desirable.
(when (or (not org-table-fix-formulas-confirm)
(funcall org-table-fix-formulas-confirm "Fix formulas? "))
(org-table-fix-formulas
@@ -1484,6 +1467,7 @@ non-nil, the one above is used."
"Move column to the right."
(interactive)
(org-table-move-column nil))
+
;;;###autoload
(defun org-table-move-column-left ()
"Move column to the left."
@@ -1496,7 +1480,7 @@ non-nil, the one above is used."
(interactive "P")
(unless (org-at-table-p) (user-error "Not at a table"))
(org-table-find-dataline)
- (org-table-check-inside-data-field)
+ (org-table-check-inside-data-field nil t)
(let* ((col (org-table-current-column))
(col1 (if left (1- col) col))
(colpos (if left (1- col) (1+ col)))
@@ -1506,33 +1490,49 @@ non-nil, the one above is used."
(user-error "Cannot move column further left"))
(when (and (not left) (looking-at "[^|\n]*|[^|\n]*$"))
(user-error "Cannot move column further right"))
- (org-table-save-field
- (goto-char beg)
- (while (< (point) end)
- (unless (org-at-table-hline-p)
- (org-table-goto-column col1 t)
- (when (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|")
- (transpose-regions
- (match-beginning 1) (match-end 1)
- (match-beginning 2) (match-end 2))))
- (forward-line)))
- (set-marker end nil)
- (org-table-goto-column colpos)
- (org-table-align)
- (when (or (not org-table-fix-formulas-confirm)
- (funcall org-table-fix-formulas-confirm "Fix formulas? "))
- (org-table-fix-formulas
- "$" (list (cons (number-to-string col) (number-to-string colpos))
- (cons (number-to-string colpos) (number-to-string col))))
- (org-table-fix-formulas
- "$LR" (list (cons (number-to-string col) (number-to-string colpos))
- (cons (number-to-string colpos) (number-to-string col)))))))
+ (let ((shrunk-columns (org-table--list-shrunk-columns)))
+ (org-table-expand beg end)
+ (org-table-save-field
+ (goto-char beg)
+ (while (< (point) end)
+ (unless (org-at-table-hline-p)
+ (org-table-goto-column col1 t)
+ (when (looking-at "|\\([^|\n]+\\)|\\([^|\n]+\\)|")
+ (transpose-regions
+ (match-beginning 1) (match-end 1)
+ (match-beginning 2) (match-end 2))))
+ (forward-line)))
+ (org-table-goto-column colpos)
+ (org-table-align)
+ ;; Shift appropriately stored shrunk column numbers, then shrink
+ ;; the columns again.
+ (org-table--shrink-columns
+ (mapcar (lambda (c)
+ (cond ((and (= col c) left) (1- c))
+ ((= col c) (1+ c))
+ ((and (= col (1+ c)) left) (1+ c))
+ ((and (= col (1- c)) (not left) (1- c)))
+ (t c)))
+ shrunk-columns)
+ beg end)
+ (set-marker end nil)
+ ;; Fix TBLFM formulas, if desirable.
+ (when (or (not org-table-fix-formulas-confirm)
+ (funcall org-table-fix-formulas-confirm "Fix formulas? "))
+ (org-table-fix-formulas
+ "$" (list (cons (number-to-string col) (number-to-string colpos))
+ (cons (number-to-string colpos) (number-to-string col))))
+ (org-table-fix-formulas
+ "$LR" (list
+ (cons (number-to-string col) (number-to-string colpos))
+ (cons (number-to-string colpos) (number-to-string col))))))))
;;;###autoload
(defun org-table-move-row-down ()
"Move table row down."
(interactive)
(org-table-move-row nil))
+
;;;###autoload
(defun org-table-move-row-up ()
"Move table row up."
@@ -1557,24 +1557,25 @@ non-nil, the one above is used."
(when (or (and (not up) (eobp)) (not (org-at-table-p)))
(goto-char pos)
(user-error "Cannot move row further"))
- (setq hline2p (looking-at org-table-hline-regexp))
- (goto-char pos)
- (let ((row (delete-and-extract-region (line-beginning-position)
- (line-beginning-position 2))))
- (beginning-of-line tonew)
- (unless (bolp) (insert "\n")) ;at eob without a newline
- (insert row)
- (unless (bolp) (insert "\n")) ;missing final newline in ROW
- (beginning-of-line 0)
- (org-move-to-column col)
- (unless (or hline1p hline2p
- (not (or (not org-table-fix-formulas-confirm)
- (funcall org-table-fix-formulas-confirm
- "Fix formulas? "))))
- (org-table-fix-formulas
- "@" (list
- (cons (number-to-string dline1) (number-to-string dline2))
- (cons (number-to-string dline2) (number-to-string dline1))))))))
+ (org-table-with-shrunk-columns
+ (setq hline2p (looking-at org-table-hline-regexp))
+ (goto-char pos)
+ (let ((row (delete-and-extract-region (line-beginning-position)
+ (line-beginning-position 2))))
+ (beginning-of-line tonew)
+ (unless (bolp) (insert "\n")) ;at eob without a newline
+ (insert row)
+ (unless (bolp) (insert "\n")) ;missing final newline in ROW
+ (beginning-of-line 0)
+ (org-move-to-column col)
+ (unless (or hline1p hline2p
+ (not (or (not org-table-fix-formulas-confirm)
+ (funcall org-table-fix-formulas-confirm
+ "Fix formulas? "))))
+ (org-table-fix-formulas
+ "@" (list
+ (cons (number-to-string dline1) (number-to-string dline2))
+ (cons (number-to-string dline2) (number-to-string dline1)))))))))
;;;###autoload
(defun org-table-insert-row (&optional arg)
@@ -1582,47 +1583,48 @@ non-nil, the one above is used."
With prefix ARG, insert below the current line."
(interactive "P")
(unless (org-at-table-p) (user-error "Not at a table"))
- (let* ((line (buffer-substring (line-beginning-position) (line-end-position)))
- (new (org-table-clean-line line)))
- ;; Fix the first field if necessary
- (if (string-match "^[ \t]*| *[#$] *|" line)
- (setq new (replace-match (match-string 0 line) t t new)))
- (beginning-of-line (if arg 2 1))
- ;; Buffer may not end of a newline character, so ensure
- ;; (beginning-of-line 2) moves point to a new line.
- (unless (bolp) (insert "\n"))
- (let (org-table-may-need-update) (insert-before-markers new "\n"))
- (beginning-of-line 0)
- (re-search-forward "| ?" (line-end-position) t)
- (when (or org-table-may-need-update org-table-overlay-coordinates)
- (org-table-align))
- (when (or (not org-table-fix-formulas-confirm)
- (funcall org-table-fix-formulas-confirm "Fix formulas? "))
- (org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1))))
+ (org-table-with-shrunk-columns
+ (let* ((line (buffer-substring (line-beginning-position) (line-end-position)))
+ (new (org-table-clean-line line)))
+ ;; Fix the first field if necessary
+ (when (string-match "^[ \t]*| *[#*$] *|" line)
+ (setq new (replace-match (match-string 0 line) t t new)))
+ (beginning-of-line (if arg 2 1))
+ ;; Buffer may not end of a newline character, so ensure
+ ;; (beginning-of-line 2) moves point to a new line.
+ (unless (bolp) (insert "\n"))
+ (let (org-table-may-need-update) (insert-before-markers new "\n"))
+ (beginning-of-line 0)
+ (re-search-forward "| ?" (line-end-position) t)
+ (when (or org-table-may-need-update org-table-overlay-coordinates)
+ (org-table-align))
+ (when (or (not org-table-fix-formulas-confirm)
+ (funcall org-table-fix-formulas-confirm "Fix formulas? "))
+ (org-table-fix-formulas "@" nil (1- (org-table-current-dline)) 1)))))
;;;###autoload
(defun org-table-insert-hline (&optional above)
"Insert a horizontal-line below the current line into the table.
With prefix ABOVE, insert above the current line."
(interactive "P")
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
- (when (eobp) (insert "\n") (backward-char 1))
- (if (not (string-match-p "|[ \t]*$" (org-current-line-string)))
- (org-table-align))
- (let ((line (org-table-clean-line
- (buffer-substring (point-at-bol) (point-at-eol))))
- (col (current-column)))
- (while (string-match "|\\( +\\)|" line)
- (setq line (replace-match
- (concat "+" (make-string (- (match-end 1) (match-beginning 1))
- ?-) "|") t t line)))
- (and (string-match "\\+" line) (setq line (replace-match "|" t t line)))
- (beginning-of-line (if above 1 2))
- (insert line "\n")
- (beginning-of-line (if above 1 -1))
- (org-move-to-column col)
- (and org-table-overlay-coordinates (org-table-align))))
+ (unless (org-at-table-p) (user-error "Not at a table"))
+ (when (eobp) (save-excursion (insert "\n")))
+ (unless (string-match-p "|[ \t]*$" (org-current-line-string))
+ (org-table-align))
+ (org-table-with-shrunk-columns
+ (let ((line (org-table-clean-line
+ (buffer-substring (point-at-bol) (point-at-eol))))
+ (col (current-column)))
+ (while (string-match "|\\( +\\)|" line)
+ (setq line (replace-match
+ (concat "+" (make-string (- (match-end 1) (match-beginning 1))
+ ?-) "|") t t line)))
+ (and (string-match "\\+" line) (setq line (replace-match "|" t t line)))
+ (beginning-of-line (if above 1 2))
+ (insert line "\n")
+ (beginning-of-line (if above 1 -1))
+ (org-move-to-column col)
+ (when org-table-overlay-coordinates (org-table-align)))))
;;;###autoload
(defun org-table-hline-and-move (&optional same-column)
@@ -1655,142 +1657,19 @@ In particular, this does handle wide and invisible characters."
(defun org-table-kill-row ()
"Delete the current row or horizontal line from the table."
(interactive)
- (if (not (org-at-table-p))
- (user-error "Not at a table"))
+ (unless (org-at-table-p) (user-error "Not at a table"))
(let ((col (current-column))
(dline (and (not (org-match-line org-table-hline-regexp))
(org-table-current-dline))))
- (kill-region (point-at-bol) (min (1+ (point-at-eol)) (point-max)))
- (if (not (org-at-table-p)) (beginning-of-line 0))
- (org-move-to-column col)
- (when (and dline
- (or (not org-table-fix-formulas-confirm)
- (funcall org-table-fix-formulas-confirm "Fix formulas? ")))
- (org-table-fix-formulas "@" (list (cons (number-to-string dline) "INVALID"))
- dline -1 dline))))
-
-;;;###autoload
-(defun org-table-sort-lines
- (&optional with-case sorting-type getkey-func compare-func interactive?)
- "Sort table lines according to the column at point.
-
-The position of point indicates the column to be used for
-sorting, and the range of lines is the range between the nearest
-horizontal separator lines, or the entire table of no such lines
-exist. If point is before the first column, you will be prompted
-for the sorting column. If there is an active region, the mark
-specifies the first line and the sorting column, while point
-should be in the last line to be included into the sorting.
-
-The command then prompts for the sorting type which can be
-alphabetically, numerically, or by time (as given in a time stamp
-in the field, or as a HH:MM value). Sorting in reverse order is
-also possible.
-
-With prefix argument WITH-CASE, alphabetic sorting will be case-sensitive.
-
-If SORTING-TYPE is specified when this function is called from a Lisp
-program, no prompting will take place. SORTING-TYPE must be a character,
-any of (?a ?A ?n ?N ?t ?T ?f ?F) where the capital letters indicate that
-sorting should be done in reverse order.
-
-If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies
-a function to be called to extract the key. It must return a value
-that is compatible with COMPARE-FUNC, the function used to compare
-entries.
-
-A non-nil value for INTERACTIVE? is used to signal that this
-function is being called interactively."
- (interactive (list current-prefix-arg nil nil nil t))
- (when (org-region-active-p) (goto-char (region-beginning)))
- ;; Point must be either within a field or before a data line.
- (save-excursion
- (skip-chars-backward " \t")
- (when (bolp) (search-forward "|" (line-end-position) t))
- (org-table-check-inside-data-field))
- ;; Set appropriate case sensitivity and column used for sorting.
- (let ((column (let ((c (org-table-current-column)))
- (cond ((> c 0) c)
- (interactive?
- (read-number "Use column N for sorting: "))
- (t 1))))
- (sorting-type
- (or sorting-type
- (read-char-exclusive "Sort Table: [a]lphabetic, [n]umeric, \
-\[t]ime, [f]unc. A/N/T/F means reversed: "))))
- (save-restriction
- ;; Narrow buffer to appropriate sorting area.
- (if (org-region-active-p)
- (progn (goto-char (region-beginning))
- (narrow-to-region
- (point)
- (save-excursion (goto-char (region-end))
- (line-beginning-position 2))))
- (let ((start (org-table-begin))
- (end (org-table-end)))
- (narrow-to-region
- (save-excursion
- (if (re-search-backward org-table-hline-regexp start t)
- (line-beginning-position 2)
- start))
- (if (save-excursion (re-search-forward org-table-hline-regexp end t))
- (match-beginning 0)
- end))))
- ;; Determine arguments for `sort-subr'. Also record original
- ;; position. `org-table-save-field' cannot help here since
- ;; sorting is too much destructive.
- (let* ((sort-fold-case (not with-case))
- (coordinates
- (cons (count-lines (point-min) (line-beginning-position))
- (current-column)))
- (extract-key-from-field
- ;; Function to be called on the contents of the field
- ;; used for sorting in the current row.
- (cl-case sorting-type
- ((?n ?N) #'string-to-number)
- ((?a ?A) #'org-sort-remove-invisible)
- ((?t ?T)
- (lambda (f)
- (cond ((string-match org-ts-regexp-both f)
- (float-time
- (org-time-string-to-time (match-string 0 f))))
- ((org-duration-p f) (org-duration-to-minutes f))
- ((string-match "\\<[0-9]+:[0-9]\\{2\\}\\>" f)
- (org-duration-to-minutes (match-string 0 f)))
- (t 0))))
- ((?f ?F)
- (or getkey-func
- (and interactive?
- (org-read-function "Function for extracting keys: "))
- (error "Missing key extractor to sort rows")))
- (t (user-error "Invalid sorting type `%c'" sorting-type))))
- (predicate
- (cl-case sorting-type
- ((?n ?N ?t ?T) #'<)
- ((?a ?A) #'string<)
- ((?f ?F)
- (or compare-func
- (and interactive?
- (org-read-function
- (concat "Function for comparing keys "
- "(empty for default `sort-subr' predicate): ")
- 'allow-empty)))))))
- (goto-char (point-min))
- (sort-subr (memq sorting-type '(?A ?N ?T ?F))
- (lambda ()
- (forward-line)
- (while (and (not (eobp))
- (not (looking-at org-table-dataline-regexp)))
- (forward-line)))
- #'end-of-line
- (lambda ()
- (funcall extract-key-from-field
- (org-trim (org-table-get-field column))))
- nil
- predicate)
- ;; Move back to initial field.
- (forward-line (car coordinates))
- (move-to-column (cdr coordinates))))))
+ (org-table-with-shrunk-columns
+ (kill-region (point-at-bol) (min (1+ (point-at-eol)) (point-max)))
+ (if (not (org-at-table-p)) (beginning-of-line 0))
+ (org-move-to-column col)
+ (when (and dline
+ (or (not org-table-fix-formulas-confirm)
+ (funcall org-table-fix-formulas-confirm "Fix formulas? ")))
+ (org-table-fix-formulas
+ "@" (list (cons (number-to-string dline) "INVALID")) dline -1 dline)))))
;;;###autoload
(defun org-table-cut-region (beg end)
@@ -1801,11 +1680,177 @@ If there is no active region, use just the field at point."
(if (org-region-active-p) (region-end) (point))))
(org-table-copy-region beg end 'cut))
+(defun org-table--increment-field (field previous)
+ "Increment string FIELD according to PREVIOUS field.
+
+Increment FIELD only if it is a string representing a number, per
+Emacs Lisp syntax, a timestamp, or is either prefixed or suffixed
+with a number. In any other case, return FIELD as-is.
+
+If PREVIOUS has the same structure as FIELD, e.g.,
+a number-prefixed string with the same pattern, the increment
+step is the difference between numbers (or timestamps, measured
+in days) in PREVIOUS and FIELD. Otherwise, it uses
+`org-table-copy-increment', if the variable contains a number, or
+default to 1.
+
+The function assumes `org-table-copy-increment' is non-nil."
+ (let* ((default-step (if (numberp org-table-copy-increment)
+ org-table-copy-increment
+ 1))
+ (number-regexp ;Lisp read syntax for numbers
+ (rx (and string-start
+ (opt (any "+-"))
+ (or (and (one-or-more digit) (opt "."))
+ (and (zero-or-more digit) "." (one-or-more digit)))
+ (opt (any "eE") (opt (opt (any "+-")) (one-or-more digit)))
+ string-end)))
+ (number-prefix-regexp (rx (and string-start (one-or-more digit))))
+ (number-suffix-regexp (rx (and (one-or-more digit) string-end)))
+ (analyze
+ (lambda (field)
+ ;; Analyse string FIELD and return information related to
+ ;; increment or nil. When non-nil, return value has the
+ ;; following scheme: (TYPE VALUE PATTERN) where
+ ;; - TYPE is a symbol among `number', `prefix', `suffix'
+ ;; and `timestamp',
+ ;; - VALUE is a timestamp if TYPE is `timestamp', or
+ ;; a number otherwise,
+ ;; - PATTERN is the field without its prefix, or suffix if
+ ;; TYPE is either `prefix' or `suffix' , or nil
+ ;; otherwise.
+ (cond ((not (org-string-nw-p field)) nil)
+ ((string-match-p number-regexp field)
+ (list 'number
+ (string-to-number field)
+ nil))
+ ((string-match number-prefix-regexp field)
+ (list 'prefix
+ (string-to-number (match-string 0 field))
+ (substring field (match-end 0))))
+ ((string-match number-suffix-regexp field)
+ (list 'suffix
+ (string-to-number (match-string 0 field))
+ (substring field 0 (match-beginning 0))))
+ ((string-match-p org-ts-regexp3 field)
+ (list 'timestamp field nil))
+ (t nil))))
+ (next-number-string
+ (lambda (n1 &optional n2)
+ ;; Increment number N1 and return it as a string. If N2
+ ;; is also a number, deduce increment step from the
+ ;; difference between N1 and N2. Otherwise, increment
+ ;; step is `default-step'.
+ (number-to-string (if n2 (+ n1 (- n1 n2)) (+ n1 default-step)))))
+ (shift-timestamp
+ (lambda (t1 &optional t2)
+ ;; Increment timestamp T1 and return it. If T2 is also
+ ;; a timestamp, deduce increment step from the difference,
+ ;; in days, between T1 and T2. Otherwise, increment by
+ ;; `default-step' days.
+ (with-temp-buffer
+ (insert t1)
+ (org-timestamp-up-day (if (not t2) default-step
+ (- (org-time-string-to-absolute t1)
+ (org-time-string-to-absolute t2))))
+ (buffer-string)))))
+ ;; Check if both PREVIOUS and FIELD have the same type. Also, if
+ ;; the case of prefixed or suffixed numbers, make sure their
+ ;; pattern, i.e., the part of the string without the prefix or the
+ ;; suffix, is the same.
+ (pcase (cons (funcall analyze field) (funcall analyze previous))
+ (`((number ,n1 ,_) . (number ,n2 ,_))
+ (funcall next-number-string n1 n2))
+ (`((number ,n ,_) . ,_)
+ (funcall next-number-string n))
+ (`((prefix ,n1 ,p1) . (prefix ,n2 ,p2))
+ (concat (funcall next-number-string n1 (and (equal p1 p2) n2)) p1))
+ (`((prefix ,n ,p) . ,_)
+ (concat (funcall next-number-string n) p))
+ (`((suffix ,n1 ,p1) . (suffix ,n2 ,p2))
+ (concat p1 (funcall next-number-string n1 (and (equal p1 p2) n2))))
+ (`((suffix ,n ,p) . ,_)
+ (concat p (funcall next-number-string n)))
+ (`((timestamp ,t1 ,_) . (timestamp ,t2 ,_))
+ (funcall shift-timestamp t1 t2))
+ (`((timestamp ,t1 ,_) . ,_)
+ (funcall shift-timestamp t1))
+ (_ field))))
+
+;;;###autoload
+(defun org-table-copy-down (n)
+ "Copy the value of the current field one row below.
+
+If the field at the cursor is empty, copy the content of the
+nearest non-empty field above. With argument N, use the Nth
+non-empty field.
+
+If the current field is not empty, it is copied down to the next
+row, and the cursor is moved with it. Therefore, repeating this
+command causes the column to be filled row-by-row.
+
+If the variable `org-table-copy-increment' is non-nil and the
+field is a number, a timestamp, or is either prefixed or suffixed
+with a number, it will be incremented while copying. By default,
+increment by the difference between the value in the current
+field and the one in the field above, if any. To increment using
+a fixed integer, set `org-table-copy-increment' to a number. In
+the case of a timestamp, increment by days.
+
+However, when N is 0, do not increment the field at all."
+ (interactive "p")
+ (org-table-check-inside-data-field)
+ (let* ((beg (org-table-begin))
+ (column (org-table-current-column))
+ (initial-field (save-excursion
+ (let ((f (org-string-nw-p (org-table-get-field))))
+ (and f (org-trim f)))))
+ field field-above next-field)
+ (save-excursion
+ ;; Get reference field.
+ (if initial-field (setq field initial-field)
+ (beginning-of-line)
+ (setq field
+ (catch :exit
+ (while (re-search-backward org-table-dataline-regexp beg t)
+ (let ((f (org-string-nw-p (org-table-get-field column))))
+ (cond ((and (> n 1) f) (cl-decf n))
+ (f (throw :exit (org-trim f)))
+ (t nil))
+ (beginning-of-line)))
+ (user-error "No non-empty field found"))))
+ ;; Check if increment is appropriate, and how it should be done.
+ (when (and org-table-copy-increment (/= n 0))
+ ;; If increment step is not explicit, get non-empty field just
+ ;; above the field being incremented to guess it.
+ (unless (numberp org-table-copy-increment)
+ (setq field-above
+ (let ((f (unless (= beg (line-beginning-position))
+ (forward-line -1)
+ (not (org-at-table-hline-p))
+ (org-table-get-field column))))
+ (and (org-string-nw-p f)
+ (org-trim f)))))
+ ;; Compute next field.
+ (setq next-field (org-table--increment-field field field-above))))
+ ;; Since initial field in not empty, we modify row below instead.
+ ;; Skip alignment since we do it at the end of the process anyway.
+ (when initial-field
+ (let ((org-table-may-need-update nil)) (org-table-next-row))
+ (org-table-blank-field))
+ ;; Insert the new field. NEW-FIELD may be nil if
+ ;; `org-table-increment' is nil, or N = 0. In that case, copy
+ ;; FIELD.
+ (insert (or next-field field))
+ (org-table-maybe-recalculate-line)
+ (org-table-align)))
+
;;;###autoload
(defun org-table-copy-region (beg end &optional cut)
"Copy rectangular region in table to clipboard.
-A special clipboard is used which can only be accessed
-with `org-table-paste-rectangle'."
+A special clipboard is used which can only be accessed with
+`org-table-paste-rectangle'. Return the region copied, as a list
+of lists of fields."
(interactive (list
(if (org-region-active-p) (region-beginning) (point))
(if (org-region-active-p) (region-end) (point))
@@ -1816,7 +1861,7 @@ with `org-table-paste-rectangle'."
(c01 (org-table-current-column))
region)
(goto-char (max beg end))
- (org-table-check-inside-data-field)
+ (org-table-check-inside-data-field nil t)
(let* ((end (copy-marker (line-end-position)))
(c02 (org-table-current-column))
(column-start (min c01 c02))
@@ -1834,6 +1879,8 @@ with `org-table-paste-rectangle'."
(forward-line))
(set-marker end nil))
(when cut (org-table-align))
+ (message (substitute-command-keys "Cells in the region copied, use \
+\\[org-table-paste-rectangle] to paste them in a table."))
(setq org-table-clip (nreverse region))))
;;;###autoload
@@ -1864,160 +1911,26 @@ lines."
(forward-line)))
(org-table-align)))
-;;;###autoload
-(defun org-table-convert ()
- "Convert from `org-mode' table to table.el and back.
-Obviously, this only works within limits. When an Org table is converted
-to table.el, all horizontal separator lines get lost, because table.el uses
-these as cell boundaries and has no notion of horizontal lines. A table.el
-table can be converted to an Org table only if it does not do row or column
-spanning. Multiline cells will become multiple cells. Beware, Org mode
-does not test if the table can be successfully converted - it blindly
-applies a recipe that works for simple tables."
- (interactive)
- (require 'table)
- (if (org-at-table.el-p)
- ;; convert to Org table
- (let ((beg (copy-marker (org-table-begin t)))
- (end (copy-marker (org-table-end t))))
- (table-unrecognize-region beg end)
- (goto-char beg)
- (while (re-search-forward "^\\([ \t]*\\)\\+-.*\n" end t)
- (replace-match ""))
- (goto-char beg))
- (if (org-at-table-p)
- ;; convert to table.el table
- (let ((beg (copy-marker (org-table-begin)))
- (end (copy-marker (org-table-end))))
- ;; first, get rid of all horizontal lines
- (goto-char beg)
- (while (re-search-forward "^\\([ \t]*\\)|-.*\n" end t)
- (replace-match ""))
- ;; insert a hline before first
- (goto-char beg)
- (org-table-insert-hline 'above)
- (beginning-of-line -1)
- ;; insert a hline after each line
- (while (progn (beginning-of-line 3) (< (point) end))
- (org-table-insert-hline))
- (goto-char beg)
- (setq end (move-marker end (org-table-end)))
- ;; replace "+" at beginning and ending of hlines
- (while (re-search-forward "^\\([ \t]*\\)|-" end t)
- (replace-match "\\1+-"))
- (goto-char beg)
- (while (re-search-forward "-|[ \t]*$" end t)
- (replace-match "-+"))
- (goto-char beg)))))
-
-(defun org-table-transpose-table-at-point ()
- "Transpose Org table at point and eliminate hlines.
-So a table like
-
-| 1 | 2 | 4 | 5 |
-|---+---+---+---|
-| a | b | c | d |
-| e | f | g | h |
-
-will be transposed as
-
-| 1 | a | e |
-| 2 | b | f |
-| 4 | c | g |
-| 5 | d | h |
-
-Note that horizontal lines disappear."
- (interactive)
- (let* ((table (delete 'hline (org-table-to-lisp)))
- (dline_old (org-table-current-line))
- (col_old (org-table-current-column))
- (contents (mapcar (lambda (_)
- (let ((tp table))
- (mapcar
- (lambda (_)
- (prog1
- (pop (car tp))
- (setq tp (cdr tp))))
- table)))
- (car table))))
- (goto-char (org-table-begin))
- (re-search-forward "|")
- (backward-char)
- (delete-region (point) (org-table-end))
- (insert (mapconcat
- (lambda(x)
- (concat "| " (mapconcat 'identity x " | " ) " |\n" ))
- contents ""))
- (org-table-goto-line col_old)
- (org-table-goto-column dline_old))
- (org-table-align))
-
-;;;###autoload
-(defun org-table-wrap-region (arg)
- "Wrap several fields in a column like a paragraph.
-This is useful if you'd like to spread the contents of a field over several
-lines, in order to keep the table compact.
+
+;;; Follow Field minor mode
-If there is an active region, and both point and mark are in the same column,
-the text in the column is wrapped to minimum width for the given number of
-lines. Generally, this makes the table more compact. A prefix ARG may be
-used to change the number of desired lines. For example, \
-`C-2 \\[org-table-wrap-region]'
-formats the selected text to two lines. If the region was longer than two
-lines, the remaining lines remain empty. A negative prefix argument reduces
-the current number of lines by that amount. The wrapped text is pasted back
-into the table. If you formatted it to more lines than it was before, fields
-further down in the table get overwritten - so you might need to make space in
-the table first.
-
-If there is no region, the current field is split at the cursor position and
-the text fragment to the right of the cursor is prepended to the field one
-line down.
-
-If there is no region, but you specify a prefix ARG, the current field gets
-blank, and the content is appended to the field above."
- (interactive "P")
- (org-table-check-inside-data-field)
- (if (org-region-active-p)
- ;; There is a region: fill as a paragraph.
- (let ((start (region-beginning)))
- (org-table-cut-region (region-beginning) (region-end))
- (when (> (length (car org-table-clip)) 1)
- (user-error "Region must be limited to single column"))
- (let ((nlines (cond ((not arg) (length org-table-clip))
- ((< arg 1) (+ (length org-table-clip) arg))
- (t arg))))
- (setq org-table-clip
- (mapcar #'list
- (org-wrap (mapconcat #'car org-table-clip " ")
- nil
- nlines))))
- (goto-char start)
- (org-table-paste-rectangle))
- ;; No region, split the current field at point.
- (unless (org-get-alist-option org-M-RET-may-split-line 'table)
- (skip-chars-forward "^\r\n|"))
- (cond
- (arg ; Combine with field above.
- (let ((s (org-table-blank-field))
- (col (org-table-current-column)))
- (forward-line -1)
- (while (org-at-table-hline-p) (forward-line -1))
- (org-table-goto-column col)
- (skip-chars-forward "^|")
- (skip-chars-backward " ")
- (insert " " (org-trim s))
- (org-table-align)))
- ((looking-at "\\([^|]+\\)+|") ; Split field.
- (let ((s (match-string 1)))
- (replace-match " |")
- (goto-char (match-beginning 0))
- (org-table-next-row)
- (insert (org-trim s) " ")
- (org-table-align)))
- (t (org-table-next-row)))))
-
-(defvar org-field-marker nil)
+(define-minor-mode org-table-follow-field-mode
+ "Minor mode to make the table field editor window follow the cursor.
+When this mode is active, the field editor window will always show the
+current field. The mode exits automatically when the cursor leaves the
+table (but see `org-table-exit-follow-field-mode-when-leaving-table')."
+ nil " TblFollow" nil
+ (if org-table-follow-field-mode
+ (add-hook 'post-command-hook 'org-table-follow-fields-with-editor
+ 'append 'local)
+ (remove-hook 'post-command-hook 'org-table-follow-fields-with-editor 'local)
+ (let* ((buf (get-buffer "*Org Table Edit Field*"))
+ (win (and buf (get-buffer-window buf))))
+ (when win (delete-window win))
+ (when buf
+ (with-current-buffer buf
+ (move-marker org-field-marker nil))
+ (kill-buffer buf)))))
;;;###autoload
(defun org-table-edit-field (arg)
@@ -2037,8 +1950,7 @@ toggle `org-table-follow-field-mode'."
(arg
(let ((b (save-excursion (skip-chars-backward "^|") (point)))
(e (save-excursion (skip-chars-forward "^|\r\n") (point))))
- (remove-text-properties b e '(org-cwidth t invisible t
- display t intangible t))
+ (remove-text-properties b e '(invisible t intangible t))
(if (and (boundp 'font-lock-mode) font-lock-mode)
(font-lock-fontify-block))))
(t
@@ -2065,15 +1977,24 @@ toggle `org-table-follow-field-mode'."
(setq word-wrap t)
(goto-char (setq p (point-max)))
(insert (org-trim field))
- (remove-text-properties p (point-max)
- '(invisible t org-cwidth t display t
- intangible t))
+ (remove-text-properties p (point-max) '(invisible t intangible t))
(goto-char p)
(setq-local org-finish-function 'org-table-finish-edit-field)
(setq-local org-window-configuration cw)
(setq-local org-field-marker pos)
(message "Edit and finish with C-c C-c")))))
+(defun org-table-follow-fields-with-editor ()
+ (if (and org-table-exit-follow-field-mode-when-leaving-table
+ (not (org-at-table-p)))
+ ;; We have left the table, exit the follow mode
+ (org-table-follow-field-mode -1)
+ (when (org-table-check-inside-data-field 'noerror)
+ (let ((win (selected-window)))
+ (org-table-edit-field nil)
+ (org-fit-window-to-buffer)
+ (select-window win)))))
+
(defun org-table-finish-edit-field ()
"Finish editing a table data field.
Remove all newline characters, insert the result into the table, realign
@@ -2097,114 +2018,8 @@ the table and kill the editing buffer."
(org-table-align)
(message "New field value inserted")))
-(define-minor-mode org-table-follow-field-mode
- "Minor mode to make the table field editor window follow the cursor.
-When this mode is active, the field editor window will always show the
-current field. The mode exits automatically when the cursor leaves the
-table (but see `org-table-exit-follow-field-mode-when-leaving-table')."
- nil " TblFollow" nil
- (if org-table-follow-field-mode
- (add-hook 'post-command-hook 'org-table-follow-fields-with-editor
- 'append 'local)
- (remove-hook 'post-command-hook 'org-table-follow-fields-with-editor 'local)
- (let* ((buf (get-buffer "*Org Table Edit Field*"))
- (win (and buf (get-buffer-window buf))))
- (when win (delete-window win))
- (when buf
- (with-current-buffer buf
- (move-marker org-field-marker nil))
- (kill-buffer buf)))))
-
-(defun org-table-follow-fields-with-editor ()
- (if (and org-table-exit-follow-field-mode-when-leaving-table
- (not (org-at-table-p)))
- ;; We have left the table, exit the follow mode
- (org-table-follow-field-mode -1)
- (when (org-table-check-inside-data-field 'noerror)
- (let ((win (selected-window)))
- (org-table-edit-field nil)
- (org-fit-window-to-buffer)
- (select-window win)))))
-
-(defvar org-timecnt) ; dynamically scoped parameter
-
-;;;###autoload
-(defun org-table-sum (&optional beg end nlast)
- "Sum numbers in region of current table column.
-The result will be displayed in the echo area, and will be available
-as kill to be inserted with \\[yank].
-
-If there is an active region, it is interpreted as a rectangle and all
-numbers in that rectangle will be summed. If there is no active
-region and point is located in a table column, sum all numbers in that
-column.
-
-If at least one number looks like a time HH:MM or HH:MM:SS, all other
-numbers are assumed to be times as well (in decimal hours) and the
-numbers are added as such.
-
-If NLAST is a number, only the NLAST fields will actually be summed."
- (interactive)
- (save-excursion
- (let (col (org-timecnt 0) diff h m s org-table-clip)
- (cond
- ((and beg end)) ; beg and end given explicitly
- ((org-region-active-p)
- (setq beg (region-beginning) end (region-end)))
- (t
- (setq col (org-table-current-column))
- (goto-char (org-table-begin))
- (unless (re-search-forward "^[ \t]*|[^-]" nil t)
- (user-error "No table data"))
- (org-table-goto-column col)
- (setq beg (point))
- (goto-char (org-table-end))
- (unless (re-search-backward "^[ \t]*|[^-]" nil t)
- (user-error "No table data"))
- (org-table-goto-column col)
- (setq end (point))))
- (let* ((items (apply 'append (org-table-copy-region beg end)))
- (items1 (cond ((not nlast) items)
- ((>= nlast (length items)) items)
- (t (setq items (reverse items))
- (setcdr (nthcdr (1- nlast) items) nil)
- (nreverse items))))
- (numbers (delq nil (mapcar 'org-table-get-number-for-summing
- items1)))
- (res (apply '+ numbers))
- (sres (if (= org-timecnt 0)
- (number-to-string res)
- (setq diff (* 3600 res)
- h (floor diff 3600) diff (mod diff 3600)
- m (floor diff 60) diff (mod diff 60)
- s diff)
- (format "%.0f:%02.0f:%02.0f" h m s))))
- (kill-new sres)
- (when (called-interactively-p 'interactive)
- (message "%s" (substitute-command-keys
- (format "Sum of %d items: %-20s \
-\(\\[yank] will insert result into buffer)" (length numbers) sres))))
- sres))))
-
-(defun org-table-get-number-for-summing (s)
- (let (n)
- (if (string-match "^ *|? *" s)
- (setq s (replace-match "" nil nil s)))
- (if (string-match " *|? *$" s)
- (setq s (replace-match "" nil nil s)))
- (setq n (string-to-number s))
- (cond
- ((and (string-match "0" s)
- (string-match "\\`[-+ \t0.edED]+\\'" s)) 0)
- ((string-match "\\`[ \t]+\\'" s) nil)
- ((string-match "\\`\\([0-9]+\\):\\([0-9]+\\)\\(:\\([0-9]+\\)\\)?\\'" s)
- (let ((h (string-to-number (or (match-string 1 s) "0")))
- (m (string-to-number (or (match-string 2 s) "0")))
- (s (string-to-number (or (match-string 4 s) "0"))))
- (if (boundp 'org-timecnt) (setq org-timecnt (1+ org-timecnt)))
- (* 1.0 (+ h (/ m 60.0) (/ s 3600.0)))))
- ((equal n 0) nil)
- (t n))))
+
+;;; Formulas
(defun org-table-current-field-formula (&optional key noerror)
"Return the formula active for the current field.
@@ -2253,34 +2068,31 @@ When NAMED is non-nil, look for a named equation."
(eq (cond
((and stored equation (string-match-p "^ *=? *$" equation))
stored)
- ((stringp equation)
- equation)
- (t (org-table-formula-from-user
- (read-string
- (org-table-formula-to-user
- (format "%s formula %s="
- (if named "Field" "Column")
- scol))
- (if stored (org-table-formula-to-user stored) "")
- 'org-table-formula-history
- )))))
+ ((stringp equation) equation)
+ (t
+ (org-table-formula-from-user
+ (read-string
+ (org-table-formula-to-user
+ (format "%s formula %s=" (if named "Field" "Column") scol))
+ (if stored (org-table-formula-to-user stored) "")
+ 'org-table-formula-history)))))
mustsave)
- (when (not (string-match "\\S-" eq))
- ;; remove formula
+ (unless (org-string-nw-p eq)
+ ;; Remove formula.
(setq stored-list (delq (assoc scol stored-list) stored-list))
(org-table-store-formulas stored-list)
(user-error "Formula removed"))
- (if (string-match "^ *=?" eq) (setq eq (replace-match "" t t eq)))
- (if (string-match " *$" eq) (setq eq (replace-match "" t t eq)))
- (if (and name (not named))
- ;; We set the column equation, delete the named one.
- (setq stored-list (delq (assoc name stored-list) stored-list)
- mustsave t))
+ (when (string-match "^ *=?" eq) (setq eq (replace-match "" t t eq)))
+ (when (string-match " *$" eq) (setq eq (replace-match "" t t eq)))
+ (when (and name (not named))
+ ;; We set the column equation, delete the named one.
+ (setq stored-list (delq (assoc name stored-list) stored-list)
+ mustsave t))
(if stored
(setcdr (assoc scol stored-list) eq)
(setq stored-list (cons (cons scol eq) stored-list)))
- (if (or mustsave (not (equal stored eq)))
- (org-table-store-formulas stored-list))
+ (when (or mustsave (not (equal stored eq)))
+ (org-table-store-formulas stored-list))
eq))
(defun org-table-store-formulas (alist &optional location)
@@ -2348,7 +2160,7 @@ LOCATION is a buffer position, consider the formulas there."
eq-alist seen)
(dolist (string strings (nreverse eq-alist))
(when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|\\$\\([_a-zA-Z0-9]+\\|\
-[<>]+\\)\\) *= *\\(.*[^ \t]\\)"
+\[<>]+\\)\\) *= *\\(.*[^ \t]\\)"
string)
(let ((lhs
(let ((m (match-string 1 string)))
@@ -2425,19 +2237,6 @@ If yes, store the formula and apply it."
(org-table-eval-formula (and named '(4))
(org-table-formula-from-user eq))))))
-(defvar org-recalc-commands nil
- "List of commands triggering the recalculation of a line.
-Will be filled automatically during use.")
-
-(defvar org-recalc-marks
- '((" " . "Unmarked: no special line, no automatic recalculation")
- ("#" . "Automatically recalculate this line upon TAB, RET, and C-c C-c in the line")
- ("*" . "Recalculate only when entire table is recalculated with `C-u C-c *'")
- ("!" . "Column name definition line. Reference in formula as $name.")
- ("$" . "Parameter definition line name=value. Reference in formula as $name.")
- ("_" . "Names for values in row below this one.")
- ("^" . "Names for values in row above this one.")))
-
;;;###autoload
(defun org-table-rotate-recalc-marks (&optional newchar)
"Rotate the recalculation mark in the first column.
@@ -2509,141 +2308,6 @@ of the new mark."
(message "%s" (cdr (assoc newchar org-recalc-marks))))))
;;;###autoload
-(defun org-table-analyze ()
- "Analyze table at point and store results.
-
-This function sets up the following dynamically scoped variables:
-
- `org-table-column-name-regexp',
- `org-table-column-names',
- `org-table-current-begin-pos',
- `org-table-current-line-types',
- `org-table-current-ncol',
- `org-table-dlines',
- `org-table-hlines',
- `org-table-local-parameters',
- `org-table-named-field-locations'."
- (let ((beg (org-table-begin))
- (end (org-table-end)))
- (save-excursion
- (goto-char beg)
- ;; Extract column names.
- (setq org-table-column-names nil)
- (when (save-excursion
- (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t))
- (let ((c 1))
- (dolist (name (org-split-string (match-string 1) " *| *"))
- (cl-incf c)
- (when (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" name)
- (push (cons name (int-to-string c)) org-table-column-names)))))
- (setq org-table-column-names (nreverse org-table-column-names))
- (setq org-table-column-name-regexp
- (format "\\$\\(%s\\)\\>"
- (regexp-opt (mapcar #'car org-table-column-names) t)))
- ;; Extract local parameters.
- (setq org-table-local-parameters nil)
- (save-excursion
- (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t)
- (dolist (field (org-split-string (match-string 1) " *| *"))
- (when (string-match
- "\\`\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field)
- (push (cons (match-string 1 field) (match-string 2 field))
- org-table-local-parameters)))))
- ;; Update named fields locations. We minimize `count-lines'
- ;; processing by storing last known number of lines in LAST.
- (setq org-table-named-field-locations nil)
- (save-excursion
- (let ((last (cons (point) 0)))
- (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t)
- (let ((c (match-string 1))
- (fields (org-split-string (match-string 2) " *| *")))
- (save-excursion
- (forward-line (if (equal c "_") 1 -1))
- (let ((fields1
- (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)")
- (org-split-string (match-string 1) " *| *")))
- (line (cl-incf (cdr last) (count-lines (car last) (point))))
- (col 1))
- (setcar last (point)) ; Update last known position.
- (while (and fields fields1)
- (let ((field (pop fields))
- (v (pop fields1)))
- (cl-incf col)
- (when (and (stringp field)
- (stringp v)
- (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'"
- field))
- (push (cons field v) org-table-local-parameters)
- (push (list field line col)
- org-table-named-field-locations))))))))))
- ;; Re-use existing markers when possible.
- (if (markerp org-table-current-begin-pos)
- (move-marker org-table-current-begin-pos (point))
- (setq org-table-current-begin-pos (point-marker)))
- ;; Analyze the line types.
- (let ((l 0) hlines dlines types)
- (while (looking-at "[ \t]*|\\(-\\)?")
- (push (if (match-end 1) 'hline 'dline) types)
- (if (match-end 1) (push l hlines) (push l dlines))
- (forward-line)
- (cl-incf l))
- (push 'hline types) ; Add an imaginary extra hline to the end.
- (setq org-table-current-line-types (apply #'vector (nreverse types)))
- (setq org-table-dlines (apply #'vector (cons nil (nreverse dlines))))
- (setq org-table-hlines (apply #'vector (cons nil (nreverse hlines)))))
- ;; Get the number of columns from the first data line in table.
- (goto-char beg)
- (forward-line (aref org-table-dlines 1))
- (let* ((fields
- (org-split-string
- (buffer-substring (line-beginning-position) (line-end-position))
- "[ \t]*|[ \t]*"))
- (nfields (length fields))
- al al2)
- (setq org-table-current-ncol nfields)
- (let ((last-dline
- (aref org-table-dlines (1- (length org-table-dlines)))))
- (dotimes (i nfields)
- (let ((column (1+ i)))
- (push (list (format "LR%d" column) last-dline column) al)
- (push (cons (format "LR%d" column) (nth i fields)) al2))))
- (setq org-table-named-field-locations
- (append org-table-named-field-locations al))
- (setq org-table-local-parameters
- (append org-table-local-parameters al2))))))
-
-(defun org-table-goto-field (ref &optional create-column-p)
- "Move point to a specific field in the current table.
-
-REF is either the name of a field its absolute reference, as
-a string. No column is created unless CREATE-COLUMN-P is
-non-nil. If it is a function, it is called with the column
-number as its argument as is used as a predicate to know if the
-column can be created.
-
-This function assumes the table is already analyzed (i.e., using
-`org-table-analyze')."
- (let* ((coordinates
- (cond
- ((cdr (assoc ref org-table-named-field-locations)))
- ((string-match "\\`@\\([1-9][0-9]*\\)\\$\\([1-9][0-9]*\\)\\'" ref)
- (list (condition-case nil
- (aref org-table-dlines
- (string-to-number (match-string 1 ref)))
- (error (user-error "Invalid row number in %s" ref)))
- (string-to-number (match-string 2 ref))))
- (t (user-error "Unknown field: %s" ref))))
- (line (car coordinates))
- (column (nth 1 coordinates))
- (create-new-column (if (functionp create-column-p)
- (funcall create-column-p column)
- create-column-p)))
- (when coordinates
- (goto-char org-table-current-begin-pos)
- (forward-line line)
- (org-table-goto-column column nil create-new-column))))
-
-;;;###autoload
(defun org-table-maybe-recalculate-line ()
"Recompute the current line if marked for it, and if we haven't just done it."
(interactive)
@@ -2654,19 +2318,6 @@ This function assumes the table is already analyzed (i.e., using
(looking-at org-table-auto-recalculate-regexp))
(org-table-recalculate) t))
-(defvar org-tbl-calc-modes) ;; Dynamically bound in `org-table-eval-formula'
-(defsubst org-set-calc-mode (var &optional value)
- (if (stringp var)
- (setq var (assoc var '(("D" calc-angle-mode deg)
- ("R" calc-angle-mode rad)
- ("F" calc-prefer-frac t)
- ("S" calc-symbolic-mode t)))
- value (nth 2 var) var (nth 1 var)))
- (if (memq var org-tbl-calc-modes)
- (setcar (cdr (memq var org-tbl-calc-modes)) value)
- (cons var (cons value org-tbl-calc-modes)))
- org-tbl-calc-modes)
-
;;;###autoload
(defun org-table-eval-formula (&optional arg equation
suppress-align suppress-const
@@ -2714,7 +2365,7 @@ SUPPRESS-ANALYSIS prevents analyzing the table and checking
location of point."
(interactive "P")
(unless suppress-analysis
- (org-table-check-inside-data-field)
+ (org-table-check-inside-data-field nil t)
(org-table-analyze))
(if (equal arg '(16))
(let ((eq (org-table-current-field-formula)))
@@ -2746,9 +2397,10 @@ location of point."
(setq c (string-to-char (match-string 1 fmt))
n (string-to-number (match-string 2 fmt)))
(if (= c ?p)
- (setq org-tbl-calc-modes (org-set-calc-mode 'calc-internal-prec n))
+ (setq org-tbl-calc-modes
+ (org-table--set-calc-mode 'calc-internal-prec n))
(setq org-tbl-calc-modes
- (org-set-calc-mode
+ (org-table--set-calc-mode
'calc-float-format
(list (cdr (assoc c '((?n . float) (?f . fix)
(?s . sci) (?e . eng))))
@@ -2772,7 +2424,8 @@ location of point."
(setq keep-empty t
fmt (replace-match "" t t fmt)))
(while (string-match "[DRFS]" fmt)
- (setq org-tbl-calc-modes (org-set-calc-mode (match-string 0 fmt)))
+ (setq org-tbl-calc-modes
+ (org-table--set-calc-mode (match-string 0 fmt)))
(setq fmt (replace-match "" t t fmt)))
(unless (string-match "\\S-" fmt)
(setq fmt nil))))
@@ -2909,8 +2562,8 @@ location of point."
(format-time-string
(org-time-stamp-format
(string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}" ts))
- (encode-time
- (save-match-data (org-parse-time-string ts))))))
+ (apply #'encode-time
+ (save-match-data (org-parse-time-string ts))))))
form t t))
(setq ev (if (and duration (string-match "^[0-9]+:[0-9]+\\(?::[0-9]+\\)?$" form))
@@ -3169,7 +2822,7 @@ ARGS are passed as arguments to the `message' function. Returns
current time if a message is printed, otherwise returns T1. If
T1 is nil, always messages."
(let ((curtime (current-time)))
- (if (or (not t1) (time-less-p 1 (time-subtract curtime t1)))
+ (if (or (not t1) (org-time-less-p 1 (org-time-subtract curtime t1)))
(progn (apply 'message args)
curtime)
t1)))
@@ -3204,139 +2857,139 @@ known that the table will be realigned a little later anyway."
beg end eqlcol eqlfield)
;; Insert constants in all formulas.
(when eqlist
- (org-table-save-field
- ;; Expand equations, then split the equation list between
- ;; column formulas and field formulas.
- (dolist (eq eqlist)
- (let* ((rhs (org-table-formula-substitute-names
- (org-table-formula-handle-first/last-rc (cdr eq))))
- (old-lhs (car eq))
- (lhs
- (org-table-formula-handle-first/last-rc
- (cond
- ((string-match "\\`@-?I+" old-lhs)
- (user-error "Can't assign to hline relative reference"))
- ((string-match "\\`\\$[<>]" old-lhs)
- (let ((new (org-table-formula-handle-first/last-rc
- old-lhs)))
- (when (assoc new eqlist)
- (user-error "\"%s=\" formula tries to overwrite \
+ (org-table-with-shrunk-columns
+ (org-table-save-field
+ ;; Expand equations, then split the equation list between
+ ;; column formulas and field formulas.
+ (dolist (eq eqlist)
+ (let* ((rhs (org-table-formula-substitute-names
+ (org-table-formula-handle-first/last-rc (cdr eq))))
+ (old-lhs (car eq))
+ (lhs
+ (org-table-formula-handle-first/last-rc
+ (cond
+ ((string-match "\\`@-?I+" old-lhs)
+ (user-error "Can't assign to hline relative reference"))
+ ((string-match "\\`\\$[<>]" old-lhs)
+ (let ((new (org-table-formula-handle-first/last-rc
+ old-lhs)))
+ (when (assoc new eqlist)
+ (user-error "\"%s=\" formula tries to overwrite \
existing formula for column %s"
- old-lhs
- new))
- new))
- (t old-lhs)))))
- (if (string-match-p "\\`\\$[0-9]+\\'" lhs)
- (push (cons lhs rhs) eqlcol)
- (push (cons lhs rhs) eqlfield))))
- (setq eqlcol (nreverse eqlcol))
- ;; Expand ranges in lhs of formulas
- (setq eqlfield (org-table-expand-lhs-ranges (nreverse eqlfield)))
- ;; Get the correct line range to process.
- (if all
- (progn
- (setq end (copy-marker (org-table-end)))
- (goto-char (setq beg org-table-current-begin-pos))
- (cond
- ((re-search-forward org-table-calculate-mark-regexp end t)
- ;; This is a table with marked lines, compute selected
- ;; lines.
- (setq line-re org-table-recalculate-regexp))
- ;; Move forward to the first non-header line.
- ((and (re-search-forward org-table-dataline-regexp end t)
- (re-search-forward org-table-hline-regexp end t)
- (re-search-forward org-table-dataline-regexp end t))
- (setq beg (match-beginning 0)))
- ;; Just leave BEG at the start of the table.
- (t nil)))
- (setq beg (line-beginning-position)
- end (copy-marker (line-beginning-position 2))))
- (goto-char beg)
- ;; Mark named fields untouchable. Also check if several
- ;; field/range formulas try to set the same field.
- (remove-text-properties beg end '(:org-untouchable t))
- (let ((current-line (count-lines org-table-current-begin-pos
- (line-beginning-position)))
- seen-fields)
- (dolist (eq eqlfield)
- (let* ((name (car eq))
- (location (assoc name org-table-named-field-locations))
- (eq-line (or (nth 1 location)
- (and (string-match "\\`@\\([0-9]+\\)" name)
- (aref org-table-dlines
- (string-to-number
- (match-string 1 name))))))
- (reference
- (if location
- ;; Turn field coordinates associated to NAME
- ;; into an absolute reference.
- (format "@%d$%d"
- (org-table-line-to-dline eq-line)
- (nth 2 location))
- name)))
- (when (member reference seen-fields)
- (user-error "Several field/range formulas try to set %s"
- reference))
- (push reference seen-fields)
- (when (or all (eq eq-line current-line))
- (org-table-goto-field name)
- (org-table-put-field-property :org-untouchable t)))))
- ;; Evaluate the column formulas, but skip fields covered by
- ;; field formulas.
- (goto-char beg)
- (while (re-search-forward line-re end t)
- (unless (string-match "\\` *[_^!$/] *\\'" (org-table-get-field 1))
- ;; Unprotected line, recalculate.
- (cl-incf cnt)
- (when all
- (setq log-last-time
- (org-table-message-once-per-second
- log-last-time
- "Re-applying formulas to full table...(line %d)" cnt)))
- (if (markerp org-last-recalc-line)
- (move-marker org-last-recalc-line (line-beginning-position))
- (setq org-last-recalc-line
- (copy-marker (line-beginning-position))))
- (dolist (entry eqlcol)
- (goto-char org-last-recalc-line)
- (org-table-goto-column
- (string-to-number (substring (car entry) 1)) nil 'force)
- (unless (get-text-property (point) :org-untouchable)
- (org-table-eval-formula
- nil (cdr entry) 'noalign 'nocst 'nostore 'noanalysis)))))
- ;; Evaluate the field formulas.
- (dolist (eq eqlfield)
- (let ((reference (car eq))
- (formula (cdr eq)))
- (setq log-last-time
- (org-table-message-once-per-second
- (and all log-last-time)
- "Re-applying formula to field: %s" (car eq)))
- (org-table-goto-field
- reference
- ;; Possibly create a new column, as long as
- ;; `org-table-formula-create-columns' allows it.
- (let ((column-count (progn (end-of-line)
- (1- (org-table-current-column)))))
- (lambda (column)
- (when (> column 1000)
- (user-error "Formula column target too large"))
- (and (> column column-count)
- (or (eq org-table-formula-create-columns t)
- (and (eq org-table-formula-create-columns 'warn)
- (progn
- (org-display-warning
- "Out-of-bounds formula added columns")
- t))
- (and (eq org-table-formula-create-columns 'prompt)
- (yes-or-no-p
- "Out-of-bounds formula. Add columns? "))
- (user-error
- "Missing columns in the table. Aborting"))))))
- (org-table-eval-formula nil formula t t t t))))
- ;; Clean up markers and internal text property.
- (remove-text-properties (point-min) (point-max) '(org-untouchable t))
- (set-marker end nil)
+ old-lhs
+ new))
+ new))
+ (t old-lhs)))))
+ (if (string-match-p "\\`\\$[0-9]+\\'" lhs)
+ (push (cons lhs rhs) eqlcol)
+ (push (cons lhs rhs) eqlfield))))
+ (setq eqlcol (nreverse eqlcol))
+ ;; Expand ranges in lhs of formulas
+ (setq eqlfield (org-table-expand-lhs-ranges (nreverse eqlfield)))
+ ;; Get the correct line range to process.
+ (if all
+ (progn
+ (setq end (copy-marker (org-table-end)))
+ (goto-char (setq beg org-table-current-begin-pos))
+ (cond
+ ((re-search-forward org-table-calculate-mark-regexp end t)
+ ;; This is a table with marked lines, compute selected
+ ;; lines.
+ (setq line-re org-table-recalculate-regexp))
+ ;; Move forward to the first non-header line.
+ ((and (re-search-forward org-table-dataline-regexp end t)
+ (re-search-forward org-table-hline-regexp end t)
+ (re-search-forward org-table-dataline-regexp end t))
+ (setq beg (match-beginning 0)))
+ ;; Just leave BEG at the start of the table.
+ (t nil)))
+ (setq beg (line-beginning-position)
+ end (copy-marker (line-beginning-position 2))))
+ (goto-char beg)
+ ;; Mark named fields untouchable. Also check if several
+ ;; field/range formulas try to set the same field.
+ (remove-text-properties beg end '(:org-untouchable t))
+ (let ((current-line (count-lines org-table-current-begin-pos
+ (line-beginning-position)))
+ seen-fields)
+ (dolist (eq eqlfield)
+ (let* ((name (car eq))
+ (location (assoc name org-table-named-field-locations))
+ (eq-line (or (nth 1 location)
+ (and (string-match "\\`@\\([0-9]+\\)" name)
+ (aref org-table-dlines
+ (string-to-number
+ (match-string 1 name))))))
+ (reference
+ (if location
+ ;; Turn field coordinates associated to NAME
+ ;; into an absolute reference.
+ (format "@%d$%d"
+ (org-table-line-to-dline eq-line)
+ (nth 2 location))
+ name)))
+ (when (member reference seen-fields)
+ (user-error "Several field/range formulas try to set %s"
+ reference))
+ (push reference seen-fields)
+ (when (or all (eq eq-line current-line))
+ (org-table-goto-field name)
+ (org-table-put-field-property :org-untouchable t)))))
+ ;; Evaluate the column formulas, but skip fields covered by
+ ;; field formulas.
+ (goto-char beg)
+ (while (re-search-forward line-re end t)
+ (unless (string-match "\\` *[_^!$/] *\\'" (org-table-get-field 1))
+ ;; Unprotected line, recalculate.
+ (cl-incf cnt)
+ (when all
+ (setq log-last-time
+ (org-table-message-once-per-second
+ log-last-time
+ "Re-applying formulas to full table...(line %d)" cnt)))
+ (if (markerp org-last-recalc-line)
+ (move-marker org-last-recalc-line (line-beginning-position))
+ (setq org-last-recalc-line
+ (copy-marker (line-beginning-position))))
+ (dolist (entry eqlcol)
+ (goto-char org-last-recalc-line)
+ (org-table-goto-column
+ (string-to-number (substring (car entry) 1)) nil 'force)
+ (unless (get-text-property (point) :org-untouchable)
+ (org-table-eval-formula
+ nil (cdr entry) 'noalign 'nocst 'nostore 'noanalysis)))))
+ ;; Evaluate the field formulas.
+ (dolist (eq eqlfield)
+ (let ((reference (car eq))
+ (formula (cdr eq)))
+ (setq log-last-time
+ (org-table-message-once-per-second
+ (and all log-last-time)
+ "Re-applying formula to field: %s" (car eq)))
+ (org-table-goto-field
+ reference
+ ;; Possibly create a new column, as long as
+ ;; `org-table-formula-create-columns' allows it.
+ (let ((column-count (progn (end-of-line)
+ (1- (org-table-current-column)))))
+ (lambda (column)
+ (when (> column 1000)
+ (user-error "Formula column target too large"))
+ (and (> column column-count)
+ (or (eq org-table-formula-create-columns t)
+ (and (eq org-table-formula-create-columns 'warn)
+ (progn
+ (org-display-warning
+ "Out-of-bounds formula added columns")
+ t))
+ (and (eq org-table-formula-create-columns 'prompt)
+ (yes-or-no-p
+ "Out-of-bounds formula. Add columns? "))
+ (user-error
+ "Missing columns in the table. Aborting"))))))
+ (org-table-eval-formula nil formula t t t t)))
+ ;; Clean up marker.
+ (set-marker end nil)))
(unless noalign
(when org-table-may-need-update (org-table-align))
(when all
@@ -3597,7 +3250,6 @@ Parameters get priority."
["Standard Refs (B3 instead of @3$2)" org-table-fedit-toggle-ref-type
:style toggle :selected org-table-buffer-is-an]))
-(defvar org-pos)
(defvar org-table--fedit-source nil
"Position of the TBLFM line being edited.")
@@ -3830,44 +3482,47 @@ minutes or seconds."
"Shift the reference at point one row/hline up."
(interactive)
(org-table-fedit-shift-reference 'up))
+
(defun org-table-fedit-ref-down ()
"Shift the reference at point one row/hline down."
(interactive)
(org-table-fedit-shift-reference 'down))
+
(defun org-table-fedit-ref-left ()
"Shift the reference at point one field to the left."
(interactive)
(org-table-fedit-shift-reference 'left))
+
(defun org-table-fedit-ref-right ()
"Shift the reference at point one field to the right."
(interactive)
(org-table-fedit-shift-reference 'right))
+(defun org-table--rematch-and-replace (n &optional decr hline)
+ "Re-match the group N, and replace it with the shifted reference."
+ (or (match-end n) (user-error "Cannot shift reference in this direction"))
+ (goto-char (match-beginning n))
+ (and (looking-at (regexp-quote (match-string n)))
+ (replace-match (org-table-shift-refpart (match-string 0) decr hline)
+ t t)))
+
(defun org-table-fedit-shift-reference (dir)
(cond
((org-in-regexp "\\(\\<[a-zA-Z]\\)&")
(if (memq dir '(left right))
- (org-rematch-and-replace 1 (eq dir 'left))
+ (org-table--rematch-and-replace 1 (eq dir 'left))
(user-error "Cannot shift reference in this direction")))
((org-in-regexp "\\(\\<[a-zA-Z]\\{1,2\\}\\)\\([0-9]+\\)")
;; A B3-like reference
(if (memq dir '(up down))
- (org-rematch-and-replace 2 (eq dir 'up))
- (org-rematch-and-replace 1 (eq dir 'left))))
+ (org-table--rematch-and-replace 2 (eq dir 'up))
+ (org-table--rematch-and-replace 1 (eq dir 'left))))
((org-in-regexp
"\\(@\\|\\.\\.\\)\\([-+]?\\(I+\\>\\|[0-9]+\\)\\)\\(\\$\\([-+]?[0-9]+\\)\\)?")
;; An internal reference
(if (memq dir '(up down))
- (org-rematch-and-replace 2 (eq dir 'up) (match-end 3))
- (org-rematch-and-replace 5 (eq dir 'left))))))
-
-(defun org-rematch-and-replace (n &optional decr hline)
- "Re-match the group N, and replace it with the shifted reference."
- (or (match-end n) (user-error "Cannot shift reference in this direction"))
- (goto-char (match-beginning n))
- (and (looking-at (regexp-quote (match-string n)))
- (replace-match (org-table-shift-refpart (match-string 0) decr hline)
- t t)))
+ (org-table--rematch-and-replace 2 (eq dir 'up) (match-end 3))
+ (org-table--rematch-and-replace 5 (eq dir 'left))))))
(defun org-table-shift-refpart (ref &optional decr hline)
"Shift a reference part REF.
@@ -3995,7 +3650,1207 @@ With prefix ARG, apply the new formulas to the table."
(goto-char beg))
(t nil))))
-(defvar org-show-positions nil)
+(defun org-table-fedit-line-up ()
+ "Move cursor one line up in the window showing the table."
+ (interactive)
+ (org-table-fedit-move 'previous-line))
+
+(defun org-table-fedit-line-down ()
+ "Move cursor one line down in the window showing the table."
+ (interactive)
+ (org-table-fedit-move 'next-line))
+
+(defun org-table-fedit-move (command)
+ "Move the cursor in the window showing the table.
+Use COMMAND to do the motion, repeat if necessary to end up in a data line."
+ (let ((org-table-allow-automatic-line-recalculation nil)
+ (pos org-pos) (win (selected-window)) p)
+ (select-window (get-buffer-window (marker-buffer org-pos)))
+ (setq p (point))
+ (call-interactively command)
+ (while (and (org-at-table-p)
+ (org-at-table-hline-p))
+ (call-interactively command))
+ (or (org-at-table-p) (goto-char p))
+ (move-marker pos (point))
+ (select-window win)))
+
+(defun org-table-fedit-scroll (N)
+ (interactive "p")
+ (let ((other-window-scroll-buffer (marker-buffer org-pos)))
+ (scroll-other-window N)))
+
+(defun org-table-fedit-scroll-down (N)
+ (interactive "p")
+ (org-table-fedit-scroll (- N)))
+
+(defun org-table-add-rectangle-overlay (beg end &optional face)
+ "Add a new overlay."
+ (let ((ov (make-overlay beg end)))
+ (overlay-put ov 'face (or face 'secondary-selection))
+ (push ov org-table-rectangle-overlays)))
+
+(defun org-table-highlight-rectangle (&optional beg end face)
+ "Highlight rectangular region in a table.
+When buffer positions BEG and END are provided, use them to
+delimit the region to highlight. Otherwise, refer to point. Use
+FACE, when non-nil, for the highlight."
+ (let* ((beg (or beg (point)))
+ (end (or end (point)))
+ (b (min beg end))
+ (e (max beg end))
+ (start-coordinates
+ (save-excursion
+ (goto-char b)
+ (cons (line-beginning-position) (org-table-current-column))))
+ (end-coordinates
+ (save-excursion
+ (goto-char e)
+ (cons (line-beginning-position) (org-table-current-column)))))
+ (when (boundp 'org-show-positions)
+ (setq org-show-positions (cons b (cons e org-show-positions))))
+ (goto-char (car start-coordinates))
+ (let ((column-start (min (cdr start-coordinates) (cdr end-coordinates)))
+ (column-end (max (cdr start-coordinates) (cdr end-coordinates)))
+ (last-row (car end-coordinates)))
+ (while (<= (point) last-row)
+ (when (looking-at org-table-dataline-regexp)
+ (org-table-goto-column column-start)
+ (skip-chars-backward "^|\n")
+ (let ((p (point)))
+ (org-table-goto-column column-end)
+ (skip-chars-forward "^|\n")
+ (org-table-add-rectangle-overlay p (point) face)))
+ (forward-line)))
+ (goto-char (car start-coordinates)))
+ (add-hook 'before-change-functions #'org-table-remove-rectangle-highlight))
+
+(defun org-table-remove-rectangle-highlight (&rest _ignore)
+ "Remove the rectangle overlays."
+ (unless org-inhibit-highlight-removal
+ (remove-hook 'before-change-functions 'org-table-remove-rectangle-highlight)
+ (mapc 'delete-overlay org-table-rectangle-overlays)
+ (setq org-table-rectangle-overlays nil)))
+
+(defvar-local org-table-coordinate-overlays nil
+ "Collects the coordinate grid overlays, so that they can be removed.")
+
+(defun org-table-overlay-coordinates ()
+ "Add overlays to the table at point, to show row/column coordinates."
+ (interactive)
+ (mapc 'delete-overlay org-table-coordinate-overlays)
+ (setq org-table-coordinate-overlays nil)
+ (save-excursion
+ (let ((id 0) (ih 0) hline eol str ov)
+ (goto-char (org-table-begin))
+ (while (org-at-table-p)
+ (setq eol (point-at-eol))
+ (setq ov (make-overlay (point-at-bol) (1+ (point-at-bol))))
+ (push ov org-table-coordinate-overlays)
+ (setq hline (looking-at org-table-hline-regexp))
+ (setq str (if hline (format "I*%-2d" (setq ih (1+ ih)))
+ (format "%4d" (setq id (1+ id)))))
+ (org-overlay-before-string ov str 'org-special-keyword 'evaporate)
+ (when hline
+ (let ((ic 0))
+ (while (re-search-forward "[+|]\\(-+\\)" eol t)
+ (cl-incf ic)
+ (let* ((beg (1+ (match-beginning 0)))
+ (s1 (format "$%d" ic))
+ (s2 (org-number-to-letters ic))
+ (str (if (eq t org-table-use-standard-references) s2 s1))
+ (ov (make-overlay beg (+ beg (length str)))))
+ (push ov org-table-coordinate-overlays)
+ (org-overlay-display ov str 'org-special-keyword 'evaporate)))))
+ (forward-line)))))
+
+;;;###autoload
+(defun org-table-toggle-coordinate-overlays ()
+ "Toggle the display of Row/Column numbers in tables."
+ (interactive)
+ (setq org-table-overlay-coordinates (not org-table-overlay-coordinates))
+ (message "Tables Row/Column numbers display turned %s"
+ (if org-table-overlay-coordinates "on" "off"))
+ (when (and (org-at-table-p) org-table-overlay-coordinates)
+ (org-table-align))
+ (unless org-table-overlay-coordinates
+ (mapc 'delete-overlay org-table-coordinate-overlays)
+ (setq org-table-coordinate-overlays nil)))
+
+;;;###autoload
+(defun org-table-toggle-formula-debugger ()
+ "Toggle the formula debugger in tables."
+ (interactive)
+ (setq org-table-formula-debug (not org-table-formula-debug))
+ (message "Formula debugging has been turned %s"
+ (if org-table-formula-debug "on" "off")))
+
+
+;;; Columns Shrinking
+
+(defun org-table--shrunk-field ()
+ "Non-nil if current field is narrowed.
+When non-nil, return the overlay narrowing the field."
+ (cl-some (lambda (o)
+ (and (eq 'table-column-hide (overlay-get o 'org-overlay-type))
+ o))
+ (overlays-at (save-excursion
+ (skip-chars-forward (if (org-at-table-hline-p) "^+|"
+ "^|")
+ (line-end-position))
+ (1- (point))))))
+
+(defun org-table--list-shrunk-columns ()
+ "List currently shrunk columns in table at point."
+ (save-excursion
+ ;; We really check shrunk columns in current row only. It could
+ ;; be wrong if all rows do not contain the same number of columns
+ ;; (i.e. the table is not properly aligned). As a consequence,
+ ;; some columns may not be shrunk again upon aligning the table.
+ ;;
+ ;; For example, in the following table, cursor is on first row and
+ ;; "<>" indicates a shrunk column.
+ ;;
+ ;; | |
+ ;; | | <> |
+ ;;
+ ;; Aligning table from the first row will not shrink again the
+ ;; second row, which was not visible initially.
+ ;;
+ ;; However, fixing it requires to check every row, which may be
+ ;; slow on large tables. Moreover, the hindrance of this
+ ;; pathological case is very limited.
+ (beginning-of-line)
+ (search-forward "|")
+ (let ((separator (if (org-at-table-hline-p) "+" "|"))
+ (column 1)
+ (shrunk (and (org-table--shrunk-field) (list 1)))
+ (end (line-end-position)))
+ (while (search-forward separator end t)
+ (cl-incf column)
+ (when (org-table--shrunk-field) (push column shrunk)))
+ (nreverse shrunk))))
+
+(defun org-table--make-shrinking-overlay (start end display field &optional pre)
+ "Create an overlay to shrink text between START and END.
+
+Use string DISPLAY instead of the real text between the two
+buffer positions. FIELD is the real contents of the field, as
+a string, or nil. It is meant to be displayed upon moving the
+mouse onto the overlay.
+
+When optional argument PRE is non-nil, assume the overlay is
+located at the beginning of the field, and prepend
+`org-table-separator-space' to it. Otherwise, concatenate
+`org-table-shrunk-column-indicator' at its end.
+
+Return the overlay."
+ (let ((show-before-edit
+ (lambda (o &rest _)
+ ;; Removing one overlay removes all other overlays in the
+ ;; same column.
+ (mapc #'delete-overlay
+ (cdr (overlay-get o 'org-table-column-overlays)))))
+ (o (make-overlay start end)))
+ (overlay-put o 'insert-behind-hooks (list show-before-edit))
+ (overlay-put o 'insert-in-front-hooks (list show-before-edit))
+ (overlay-put o 'modification-hooks (list show-before-edit))
+ (overlay-put o 'org-overlay-type 'table-column-hide)
+ (when (stringp field) (overlay-put o 'help-echo field))
+ ;; Make sure overlays stays on top of table coordinates overlays.
+ ;; See `org-table-overlay-coordinates'.
+ (overlay-put o 'priority 1)
+ (let ((d (if pre (concat org-table-separator-space display)
+ (concat display org-table-shrunk-column-indicator))))
+ (org-overlay-display o d 'org-table t))
+ o))
+
+(defun org-table--shrink-field (width align start end contents)
+ "Shrink a table field to a specified width.
+
+WIDTH is an integer representing the number of characters to
+display, in addition to `org-table-shrunk-column-indicator'.
+ALIGN is the alignment of the current column, as either \"l\",
+\"c\" or \"r\". START and END are, respectively, the beginning
+and ending positions of the field. CONTENTS is its trimmed
+contents, as a string, or `hline' for table rules.
+
+Real field is hidden under one or two overlays. They have the
+following properties:
+
+ `org-overlay-type'
+
+ Set to `table-column-hide'. Used to identify overlays
+ responsible for shrinking columns in a table.
+
+ `org-table-column-overlays'
+
+ It is a list with the pattern (siblings . COLUMN-OVERLAYS)
+ where COLUMN-OVERLAYS is the list of all overlays hiding the
+ same column.
+
+Whenever the text behind or next to the overlay is modified, all
+the overlays in the column are deleted, effectively displaying
+the column again.
+
+Return a list of overlays hiding the field, or nil if field is
+already hidden."
+ (cond
+ ((= start end) nil) ;no field to narrow
+ ((org-table--shrunk-field) nil) ;already shrunk
+ ((= 0 width) ;shrink to one character
+ (list (org-table--make-shrinking-overlay
+ start end "" (if (eq 'hline contents) "" contents))))
+ ((eq contents 'hline)
+ (list (org-table--make-shrinking-overlay
+ start end (make-string (1+ width) ?-) "")))
+ ((equal contents "") ;no contents to hide
+ (list
+ (let ((w (org-string-width (buffer-substring start end)))
+ ;; We really want WIDTH + 2 whitespace, to include blanks
+ ;; around fields.
+ (full (+ 2 width)))
+ (if (<= w full)
+ (org-table--make-shrinking-overlay
+ (1- end) end (make-string (- full w) ?\s) "")
+ (org-table--make-shrinking-overlay (- end (- w full) 1) end "" "")))))
+ (t
+ ;; If the field is not empty, display exactly WIDTH characters.
+ ;; It can mean to partly hide the field, or extend it with virtual
+ ;; blanks. To that effect, we use one or two overlays. The
+ ;; first, optional, one may add or hide white spaces before the
+ ;; contents of the field. The other, mandatory, one cuts the
+ ;; field or displays white spaces at the end of the field. It
+ ;; also always displays `org-table-shrunk-column-indicator'.
+ (let* ((lead (org-with-point-at start (skip-chars-forward " ")))
+ (trail (org-with-point-at end (abs (skip-chars-backward " "))))
+ (contents-width (org-string-width
+ (buffer-substring (+ start lead) (- end trail)))))
+ (cond
+ ;; Contents are too large to fit in WIDTH character. Limit, if
+ ;; possible, blanks at the beginning of the field to a single
+ ;; white space, and cut the field at an appropriate location.
+ ((<= width contents-width)
+ (let ((pre
+ (and (> lead 0)
+ (org-table--make-shrinking-overlay
+ start (+ start lead) "" contents t)))
+ (post
+ (org-table--make-shrinking-overlay
+ ;; Find cut location so that WIDTH characters are
+ ;; visible using dichotomy.
+ (let* ((begin (+ start lead))
+ (lower begin)
+ (upper (1- end))
+ ;; Compensate the absence of leading space,
+ ;; thus preserving alignment.
+ (width (if (= lead 0) (1+ width) width)))
+ (catch :exit
+ (while (> (- upper lower) 1)
+ (let ((mean (+ (ash lower -1)
+ (ash upper -1)
+ (logand lower upper 1))))
+ (pcase (org-string-width (buffer-substring begin mean))
+ ((pred (= width)) (throw :exit mean))
+ ((pred (< width)) (setq upper mean))
+ (_ (setq lower mean)))))
+ upper))
+ end "" contents)))
+ (if pre (list pre post) (list post))))
+ ;; Contents fit it WIDTH characters. First compute number of
+ ;; white spaces needed on each side of contents, then expand or
+ ;; compact blanks on each side of the field in order to
+ ;; preserve width and obey to alignment constraints.
+ (t
+ (let* ((required (- width contents-width))
+ (before
+ (pcase align
+ ;; Compensate the absence of leading space, thus
+ ;; preserving alignment.
+ ((guard (= lead 0)) -1)
+ ("l" 0)
+ ("r" required)
+ ("c" (/ required 2))))
+ (after (- required before))
+ (pre
+ (pcase (1- lead)
+ ((or (guard (= lead 0)) (pred (= before))) nil)
+ ((pred (< before))
+ (org-table--make-shrinking-overlay
+ start (+ start (- lead before)) "" contents t))
+ (_
+ (org-table--make-shrinking-overlay
+ start (1+ start)
+ (make-string (- before (1- lead)) ?\s)
+ contents t))))
+ (post
+ (pcase (1- trail)
+ ((pred (= after))
+ (org-table--make-shrinking-overlay (1- end) end "" contents))
+ ((pred (< after))
+ (org-table--make-shrinking-overlay
+ (+ after (- end trail)) end "" contents))
+ (_
+ (org-table--make-shrinking-overlay
+ (1- end) end
+ (make-string (- after (1- trail)) ?\s)
+ contents)))))
+ (if pre (list pre post) (list post)))))))))
+
+(defun org-table--read-column-selection (select max)
+ "Read column selection select as a list of numbers.
+
+SELECT is a string containing column ranges, separated by white
+space characters, see `org-table-hide-column' for details. MAX
+is the maximum column number.
+
+Return value is a sorted list of numbers. Ignore any number
+outside of the [1;MAX] range."
+ (catch :all
+ (sort
+ (delete-dups
+ (cl-mapcan
+ (lambda (s)
+ (cond
+ ((member s '("-" "1-")) (throw :all (number-sequence 1 max)))
+ ((string-match-p "\\`[0-9]+\\'" s)
+ (let ((n (string-to-number s)))
+ (and (> n 0) (<= n max) (list n))))
+ ((string-match "\\`\\([0-9]+\\)?-\\([0-9]+\\)?\\'" s)
+ (let ((n (match-string 1 s))
+ (m (match-string 2 s)))
+ (number-sequence (if n (max 1 (string-to-number n))
+ 1)
+ (if m (min max (string-to-number m))
+ max))))
+ (t nil))) ;invalid specification
+ (split-string select)))
+ #'<)))
+
+(defun org-table--shrink-columns (columns beg end)
+ "Shrink COLUMNS in a table.
+COLUMNS is a sorted list of column numbers. BEG and END are,
+respectively, the beginning position and the end position of the
+table."
+ (org-with-wide-buffer
+ (org-font-lock-ensure beg end)
+ (dolist (c columns)
+ (goto-char beg)
+ (let ((align nil)
+ (width nil)
+ (fields nil))
+ (while (< (point) end)
+ (catch :continue
+ (let* ((hline? (org-at-table-hline-p))
+ (separator (if hline? "+" "|")))
+ ;; Move to COLUMN.
+ (search-forward "|")
+ (or (= c 1) ;already there
+ (search-forward separator (line-end-position) t (1- c))
+ (throw :continue nil)) ;skip invalid columns
+ ;; Extract boundaries and contents from current field.
+ ;; Also set the column's width if we encounter a width
+ ;; cookie for the first time.
+ (let* ((start (point))
+ (end (progn
+ (skip-chars-forward (concat "^|" separator)
+ (line-end-position))
+ (point)))
+ (contents (if hline? 'hline
+ (org-trim (buffer-substring start end)))))
+ (push (list start end contents) fields)
+ (when (and (not hline?)
+ (string-match "\\`<\\([lrc]\\)?\\([0-9]+\\)>\\'"
+ contents))
+ (unless align (setq align (match-string 1 contents)))
+ (unless width
+ (setq width (string-to-number (match-string 2 contents))))))))
+ (forward-line))
+ ;; Link overlays for current field to the other overlays in the
+ ;; same column.
+ (let ((chain (list 'siblings)))
+ (dolist (field fields)
+ (dolist (new (apply #'org-table--shrink-field
+ (or width 0) (or align "l") field))
+ (push new (cdr chain))
+ (overlay-put new 'org-table-column-overlays chain))))))))
+
+;;;###autoload
+(defun org-table-toggle-column-width (&optional arg)
+ "Shrink or expand current column in an Org table.
+
+If a width cookie specifies a width W for the column, the first
+W visible characters are displayed. Otherwise, the column is
+shrunk to a single character.
+
+When point is before the first column or after the last one, ask
+for the columns to shrink or expand, as a list of ranges.
+A column range can be one of the following patterns:
+
+ N column N only
+ N-M every column between N and M (both inclusive)
+ N- every column between N (inclusive) and the last column
+ -M every column between the first one and M (inclusive)
+ - every column
+
+When optional argument ARG is a string, use it as white space
+separated list of column ranges.
+
+When called with `\\[universal-argument]' prefix, call \
+`org-table-shrink', i.e.,
+shrink columns with a width cookie and expand the others.
+
+When called with `\\[universal-argument] \\[universal-argument]' \
+prefix, expand all columns."
+ (interactive "P")
+ (unless (org-at-table-p) (user-error "Not in a table"))
+ (let* ((begin (org-table-begin))
+ (end (org-table-end))
+ ;; Compute an upper bound for the number of columns.
+ ;; Nonexistent columns are ignored anyway.
+ (max-columns (/ (- (line-end-position) (line-beginning-position)) 2))
+ (shrunk (org-table--list-shrunk-columns))
+ (columns
+ (pcase arg
+ (`nil
+ (if (save-excursion
+ (skip-chars-backward "^|" (line-beginning-position))
+ (or (bolp) (looking-at-p "[ \t]*$")))
+ ;; Point is either before first column or past last
+ ;; one. Ask for columns to operate on.
+ (org-table--read-column-selection
+ (read-string "Column ranges (e.g. 2-4 6-): ")
+ max-columns)
+ (list (org-table-current-column))))
+ ((pred stringp) (org-table--read-column-selection arg max-columns))
+ ((or `(4) `(16)) nil)
+ (_ (user-error "Invalid argument: %S" arg)))))
+ (pcase arg
+ (`(4) (org-table-shrink begin end))
+ (`(16) (org-table-expand begin end))
+ (_
+ (org-table-expand begin end)
+ (org-table--shrink-columns
+ (cl-set-exclusive-or columns shrunk) begin end)))))
+
+;;;###autoload
+(defun org-table-shrink (&optional begin end)
+ "Shrink all columns with a width cookie in the table at point.
+
+Columns without a width cookie are expanded.
+
+Optional arguments BEGIN and END, when non-nil, specify the
+beginning and end position of the current table."
+ (interactive)
+ (unless (or begin (org-at-table-p)) (user-error "Not at a table"))
+ (org-with-wide-buffer
+ (let ((begin (or begin (org-table-begin)))
+ (end (or end (org-table-end)))
+ (regexp "|[ \t]*<[lrc]?[0-9]+>[ \t]*\\(|\\|$\\)")
+ (columns))
+ (goto-char begin)
+ (while (re-search-forward regexp end t)
+ (goto-char (match-beginning 1))
+ (cl-pushnew (org-table-current-column) columns))
+ (org-table-expand begin end)
+ ;; Make sure invisible characters in the table are at the right
+ ;; place since column widths take them into account.
+ (org-font-lock-ensure begin end)
+ (org-table--shrink-columns (sort columns #'<) begin end))))
+
+;;;###autoload
+(defun org-table-expand (&optional begin end)
+ "Expand all columns in the table at point.
+Optional arguments BEGIN and END, when non-nil, specify the
+beginning and end position of the current table."
+ (interactive)
+ (unless (or begin (org-at-table-p)) (user-error "Not at a table"))
+ (org-with-wide-buffer
+ (let ((begin (or begin (org-table-begin)))
+ (end (or end (org-table-end))))
+ (remove-overlays begin end 'org-overlay-type 'table-column-hide))))
+
+
+;;; Generic Tools
+
+;;;###autoload
+(defun org-table-map-tables (f &optional quietly)
+ "Apply function F to the start of all tables in the buffer."
+ (org-with-point-at 1
+ (while (re-search-forward org-table-line-regexp nil t)
+ (let ((table (org-element-lineage (org-element-at-point) '(table) t)))
+ (when table
+ (unless quietly
+ (message "Mapping tables: %d%%"
+ (floor (* 100.0 (point)) (buffer-size))))
+ (goto-char (org-element-property :post-affiliated table))
+ (let ((end (copy-marker (org-element-property :end table))))
+ (unwind-protect
+ (progn (funcall f) (goto-char end))
+ (set-marker end nil)))))))
+ (unless quietly (message "Mapping tables: done")))
+
+;;;###autoload
+(defun org-table-export (&optional file format)
+ "Export table to a file, with configurable format.
+Such a file can be imported into usual spreadsheet programs.
+
+FILE can be the output file name. If not given, it will be taken
+from a TABLE_EXPORT_FILE property in the current entry or higher
+up in the hierarchy, or the user will be prompted for a file
+name. FORMAT can be an export format, of the same kind as it
+used when `-mode' sends a table in a different format.
+
+The command suggests a format depending on TABLE_EXPORT_FORMAT,
+whether it is set locally or up in the hierarchy, then on the
+extension of the given file name, and finally on the variable
+`org-table-export-default-format'."
+ (interactive)
+ (unless (org-at-table-p) (user-error "No table at point"))
+ (org-table-align) ; Make sure we have everything we need.
+ (let ((file (or file (org-entry-get (point) "TABLE_EXPORT_FILE" t))))
+ (unless file
+ (setq file (read-file-name "Export table to: "))
+ (unless (or (not (file-exists-p file))
+ (y-or-n-p (format "Overwrite file %s? " file)))
+ (user-error "File not written")))
+ (when (file-directory-p file)
+ (user-error "This is a directory path, not a file"))
+ (when (and (buffer-file-name (buffer-base-buffer))
+ (file-equal-p
+ (file-truename file)
+ (file-truename (buffer-file-name (buffer-base-buffer)))))
+ (user-error "Please specify a file name that is different from current"))
+ (let ((fileext (concat (file-name-extension file) "$"))
+ (format (or format (org-entry-get (point) "TABLE_EXPORT_FORMAT" t))))
+ (unless format
+ (let* ((formats '("orgtbl-to-tsv" "orgtbl-to-csv" "orgtbl-to-latex"
+ "orgtbl-to-html" "orgtbl-to-generic"
+ "orgtbl-to-texinfo" "orgtbl-to-orgtbl"
+ "orgtbl-to-unicode"))
+ (deffmt-readable
+ (replace-regexp-in-string
+ "\t" "\\t"
+ (replace-regexp-in-string
+ "\n" "\\n"
+ (or (car (delq nil
+ (mapcar
+ (lambda (f)
+ (and (string-match-p fileext f) f))
+ formats)))
+ org-table-export-default-format)
+ t t) t t)))
+ (setq format
+ (org-completing-read
+ "Format: " formats nil nil deffmt-readable))))
+ (if (string-match "\\([^ \t\r\n]+\\)\\( +.*\\)?" format)
+ (let ((transform (intern (match-string 1 format)))
+ (params (and (match-end 2)
+ (read (concat "(" (match-string 2 format) ")"))))
+ (table (org-table-to-lisp
+ (buffer-substring-no-properties
+ (org-table-begin) (org-table-end)))))
+ (unless (fboundp transform)
+ (user-error "No such transformation function %s" transform))
+ (let (buf)
+ (with-current-buffer (find-file-noselect file)
+ (setq buf (current-buffer))
+ (erase-buffer)
+ (fundamental-mode)
+ (insert (funcall transform table params) "\n")
+ (save-buffer))
+ (kill-buffer buf))
+ (message "Export done."))
+ (user-error "TABLE_EXPORT_FORMAT invalid")))))
+
+;;;###autoload
+(defun org-table--align-field (field width align)
+ "Format FIELD according to column WIDTH and alignment 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 org-table-separator-space
+ prefix
+ field
+ suffix
+ org-table-separator-space)))
+
+(defun org-table-align ()
+ "Align the table at point by aligning all vertical bars."
+ (interactive)
+ (let ((beg (org-table-begin))
+ (end (copy-marker (org-table-end))))
+ (org-table-save-field
+ ;; Make sure invisible characters in the table are at the right
+ ;; place since column widths take them into account.
+ (org-font-lock-ensure beg end)
+ (move-marker org-table-aligned-begin-marker beg)
+ (move-marker org-table-aligned-end-marker end)
+ (goto-char beg)
+ (org-table-with-shrunk-columns
+ (let* ((indent (progn (looking-at "[ \t]*") (match-string 0)))
+ ;; 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)))
+ ;; 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 columns-number)
+ (let* ((max-width 1)
+ (fixed-align? nil)
+ (numbers 0)
+ (non-empty 0))
+ (dolist (row fields)
+ (let ((cell (or (nth i row) "")))
+ (setq max-width (max max-width (org-string-width cell)))
+ (cond (fixed-align? nil)
+ ((equal cell "") nil)
+ ((string-match "\\`<\\([lrc]\\)[0-9]*>\\'" cell)
+ (setq fixed-align? (match-string 1 cell)))
+ (t
+ (cl-incf non-empty)
+ (when (string-match-p org-table-number-regexp cell)
+ (cl-incf numbers))))))
+ (push max-width widths)
+ (push (cond
+ (fixed-align?)
+ ((>= 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 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))))))
+
+;;;###autoload
+(defun org-table-justify-field-maybe (&optional new)
+ "Justify the current field, text to left, number to right.
+Optional argument NEW may specify text to replace the current field content."
+ (cond
+ ((and (not new) org-table-may-need-update)) ; Realignment will happen anyway
+ ((org-at-table-hline-p))
+ ((and (not new)
+ (or (not (eq (marker-buffer org-table-aligned-begin-marker)
+ (current-buffer)))
+ (< (point) org-table-aligned-begin-marker)
+ (>= (point) org-table-aligned-end-marker)))
+ ;; This is not the same table, force a full re-align.
+ (setq org-table-may-need-update t))
+ (t
+ ;; Realign the current field, based on previous full realign.
+ (let ((pos (point))
+ (col (org-table-current-column)))
+ (when (> col 0)
+ (skip-chars-backward "^|")
+ (if (not (looking-at " *\\([^|\n]*?\\) *\\(|\\|$\\)"))
+ (setq org-table-may-need-update t)
+ (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))
+ (properly-closed? (/= (match-beginning 2) (match-end 2)))
+ (new-cell
+ (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)))
+ (goto-char pos))))))))
+
+;;;###autoload
+(defun org-table-sort-lines
+ (&optional with-case sorting-type getkey-func compare-func interactive?)
+ "Sort table lines according to the column at point.
+
+The position of point indicates the column to be used for
+sorting, and the range of lines is the range between the nearest
+horizontal separator lines, or the entire table of no such lines
+exist. If point is before the first column, you will be prompted
+for the sorting column. If there is an active region, the mark
+specifies the first line and the sorting column, while point
+should be in the last line to be included into the sorting.
+
+The command then prompts for the sorting type which can be
+alphabetically, numerically, or by time (as given in a time stamp
+in the field, or as a HH:MM value). Sorting in reverse order is
+also possible.
+
+With prefix argument WITH-CASE, alphabetic sorting will be case-sensitive
+if the locale allows for it.
+
+If SORTING-TYPE is specified when this function is called from a Lisp
+program, no prompting will take place. SORTING-TYPE must be a character,
+any of (?a ?A ?n ?N ?t ?T ?f ?F) where the capital letters indicate that
+sorting should be done in reverse order.
+
+If the SORTING-TYPE is ?f or ?F, then GETKEY-FUNC specifies
+a function to be called to extract the key. It must return a value
+that is compatible with COMPARE-FUNC, the function used to compare
+entries.
+
+A non-nil value for INTERACTIVE? is used to signal that this
+function is being called interactively."
+ (interactive (list current-prefix-arg nil nil nil t))
+ (when (org-region-active-p) (goto-char (region-beginning)))
+ ;; Point must be either within a field or before a data line.
+ (save-excursion
+ (skip-chars-backward " \t")
+ (when (bolp) (search-forward "|" (line-end-position) t))
+ (org-table-check-inside-data-field))
+ ;; Set appropriate case sensitivity and column used for sorting.
+ (let ((column (let ((c (org-table-current-column)))
+ (cond ((> c 0) c)
+ (interactive?
+ (read-number "Use column N for sorting: "))
+ (t 1))))
+ (sorting-type
+ (or sorting-type
+ (read-char-exclusive "Sort Table: [a]lphabetic, [n]umeric, \
+\[t]ime, [f]unc. A/N/T/F means reversed: ")))
+ (start (org-table-begin))
+ (end (org-table-end)))
+ (save-restriction
+ ;; Narrow buffer to appropriate sorting area.
+ (if (org-region-active-p)
+ (progn (goto-char (region-beginning))
+ (narrow-to-region
+ (point)
+ (save-excursion (goto-char (region-end))
+ (line-beginning-position 2))))
+ (narrow-to-region
+ (save-excursion
+ (if (re-search-backward org-table-hline-regexp start t)
+ (line-beginning-position 2)
+ start))
+ (if (save-excursion (re-search-forward org-table-hline-regexp end t))
+ (match-beginning 0)
+ end)))
+ ;; Determine arguments for `sort-subr'. Also record original
+ ;; position. `org-table-save-field' cannot help here since
+ ;; sorting is too much destructive.
+ (let* ((coordinates
+ (cons (count-lines (point-min) (line-beginning-position))
+ (current-column)))
+ (extract-key-from-field
+ ;; Function to be called on the contents of the field
+ ;; used for sorting in the current row.
+ (cl-case sorting-type
+ ((?n ?N) #'string-to-number)
+ ((?a ?A) #'org-sort-remove-invisible)
+ ((?t ?T)
+ (lambda (f)
+ (cond ((string-match org-ts-regexp-both f)
+ (float-time
+ (org-time-string-to-time (match-string 0 f))))
+ ((org-duration-p f) (org-duration-to-minutes f))
+ ((string-match "\\<[0-9]+:[0-9]\\{2\\}\\>" f)
+ (org-duration-to-minutes (match-string 0 f)))
+ (t 0))))
+ ((?f ?F)
+ (or getkey-func
+ (and interactive?
+ (org-read-function "Function for extracting keys: "))
+ (error "Missing key extractor to sort rows")))
+ (t (user-error "Invalid sorting type `%c'" sorting-type))))
+ (predicate
+ (cl-case sorting-type
+ ((?n ?N ?t ?T) #'<)
+ ((?a ?A) (if with-case #'org-string-collate-lessp
+ (lambda (s1 s2) (org-string-collate-lessp s1 s2 nil t))))
+ ((?f ?F)
+ (or compare-func
+ (and interactive?
+ (org-read-function
+ "Function for comparing keys (empty for default \
+`sort-subr' predicate): "
+ 'allow-empty))))))
+ (shrunk-columns (remq column (org-table--list-shrunk-columns))))
+ (goto-char (point-min))
+ (sort-subr (memq sorting-type '(?A ?N ?T ?F))
+ (lambda ()
+ (forward-line)
+ (while (and (not (eobp))
+ (not (looking-at org-table-dataline-regexp)))
+ (forward-line)))
+ #'end-of-line
+ (lambda ()
+ (funcall extract-key-from-field
+ (org-trim (org-table-get-field column))))
+ nil
+ predicate)
+ ;; Hide all columns but the one being sorted.
+ (org-table--shrink-columns shrunk-columns start end)
+ ;; Move back to initial field.
+ (forward-line (car coordinates))
+ (move-to-column (cdr coordinates))))))
+
+(defun org-table-transpose-table-at-point ()
+ "Transpose Org table at point and eliminate hlines.
+So a table like
+
+| 1 | 2 | 4 | 5 |
+|---+---+---+---|
+| a | b | c | d |
+| e | f | g | h |
+
+will be transposed as
+
+| 1 | a | e |
+| 2 | b | f |
+| 4 | c | g |
+| 5 | d | h |
+
+Note that horizontal lines disappear."
+ (interactive)
+ (let* ((table (delete 'hline (org-table-to-lisp)))
+ (dline_old (org-table-current-line))
+ (col_old (org-table-current-column))
+ (contents (mapcar (lambda (_)
+ (let ((tp table))
+ (mapcar
+ (lambda (_)
+ (prog1
+ (pop (car tp))
+ (setq tp (cdr tp))))
+ table)))
+ (car table))))
+ (goto-char (org-table-begin))
+ (re-search-forward "|")
+ (backward-char)
+ (delete-region (point) (org-table-end))
+ (insert (mapconcat
+ (lambda(x)
+ (concat "| " (mapconcat 'identity x " | " ) " |\n" ))
+ contents ""))
+ (org-table-goto-line col_old)
+ (org-table-goto-column dline_old))
+ (org-table-align))
+
+;;;###autoload
+(defun org-table-wrap-region (arg)
+ "Wrap several fields in a column like a paragraph.
+This is useful if you'd like to spread the contents of a field over several
+lines, in order to keep the table compact.
+
+If there is an active region, and both point and mark are in the same column,
+the text in the column is wrapped to minimum width for the given number of
+lines. Generally, this makes the table more compact. A prefix ARG may be
+used to change the number of desired lines. For example, \
+`C-2 \\[org-table-wrap-region]'
+formats the selected text to two lines. If the region was longer than two
+lines, the remaining lines remain empty. A negative prefix argument reduces
+the current number of lines by that amount. The wrapped text is pasted back
+into the table. If you formatted it to more lines than it was before, fields
+further down in the table get overwritten - so you might need to make space in
+the table first.
+
+If there is no region, the current field is split at the cursor position and
+the text fragment to the right of the cursor is prepended to the field one
+line down.
+
+If there is no region, but you specify a prefix ARG, the current field gets
+blank, and the content is appended to the field above."
+ (interactive "P")
+ (org-table-check-inside-data-field)
+ (if (org-region-active-p)
+ ;; There is a region: fill as a paragraph.
+ (let ((start (region-beginning)))
+ (org-table-cut-region (region-beginning) (region-end))
+ (when (> (length (car org-table-clip)) 1)
+ (user-error "Region must be limited to single column"))
+ (let ((nlines (cond ((not arg) (length org-table-clip))
+ ((< arg 1) (+ (length org-table-clip) arg))
+ (t arg))))
+ (setq org-table-clip
+ (mapcar #'list
+ (org-wrap (mapconcat #'car org-table-clip " ")
+ nil
+ nlines))))
+ (goto-char start)
+ (org-table-paste-rectangle))
+ ;; No region, split the current field at point.
+ (unless (org-get-alist-option org-M-RET-may-split-line 'table)
+ (skip-chars-forward "^\r\n|"))
+ (cond
+ (arg ; Combine with field above.
+ (let ((s (org-table-blank-field))
+ (col (org-table-current-column)))
+ (forward-line -1)
+ (while (org-at-table-hline-p) (forward-line -1))
+ (org-table-goto-column col)
+ (skip-chars-forward "^|")
+ (skip-chars-backward " ")
+ (insert " " (org-trim s))
+ (org-table-align)))
+ ((looking-at "\\([^|]+\\)+|") ; Split field.
+ (let ((s (match-string 1)))
+ (replace-match " |")
+ (goto-char (match-beginning 0))
+ (org-table-next-row)
+ (insert (org-trim s) " ")
+ (org-table-align)))
+ (t (org-table-next-row)))))
+
+(defun org-table--number-for-summing (s)
+ (let (n)
+ (if (string-match "^ *|? *" s)
+ (setq s (replace-match "" nil nil s)))
+ (if (string-match " *|? *$" s)
+ (setq s (replace-match "" nil nil s)))
+ (setq n (string-to-number s))
+ (cond
+ ((and (string-match "0" s)
+ (string-match "\\`[-+ \t0.edED]+\\'" s)) 0)
+ ((string-match "\\`[ \t]+\\'" s) nil)
+ ((string-match "\\`\\([0-9]+\\):\\([0-9]+\\)\\(:\\([0-9]+\\)\\)?\\'" s)
+ (let ((h (string-to-number (or (match-string 1 s) "0")))
+ (m (string-to-number (or (match-string 2 s) "0")))
+ (s (string-to-number (or (match-string 4 s) "0"))))
+ (if (boundp 'org-timecnt) (setq org-timecnt (1+ org-timecnt)))
+ (* 1.0 (+ h (/ m 60.0) (/ s 3600.0)))))
+ ((equal n 0) nil)
+ (t n))))
+
+;;;###autoload
+(defun org-table-sum (&optional beg end nlast)
+ "Sum numbers in region of current table column.
+The result will be displayed in the echo area, and will be available
+as kill to be inserted with \\[yank].
+
+If there is an active region, it is interpreted as a rectangle and all
+numbers in that rectangle will be summed. If there is no active
+region and point is located in a table column, sum all numbers in that
+column.
+
+If at least one number looks like a time HH:MM or HH:MM:SS, all other
+numbers are assumed to be times as well (in decimal hours) and the
+numbers are added as such.
+
+If NLAST is a number, only the NLAST fields will actually be summed."
+ (interactive)
+ (save-excursion
+ (let (col (org-timecnt 0) diff h m s org-table-clip)
+ (cond
+ ((and beg end)) ; beg and end given explicitly
+ ((org-region-active-p)
+ (setq beg (region-beginning) end (region-end)))
+ (t
+ (setq col (org-table-current-column))
+ (goto-char (org-table-begin))
+ (unless (re-search-forward "^[ \t]*|[^-]" nil t)
+ (user-error "No table data"))
+ (org-table-goto-column col)
+ (setq beg (point))
+ (goto-char (org-table-end))
+ (unless (re-search-backward "^[ \t]*|[^-]" nil t)
+ (user-error "No table data"))
+ (org-table-goto-column col)
+ (setq end (point))))
+ (let* ((items (apply 'append (org-table-copy-region beg end)))
+ (items1 (cond ((not nlast) items)
+ ((>= nlast (length items)) items)
+ (t (setq items (reverse items))
+ (setcdr (nthcdr (1- nlast) items) nil)
+ (nreverse items))))
+ (numbers (delq nil (mapcar #'org-table--number-for-summing
+ items1)))
+ (res (apply '+ numbers))
+ (sres (if (= org-timecnt 0)
+ (number-to-string res)
+ (setq diff (* 3600 res)
+ h (floor diff 3600) diff (mod diff 3600)
+ m (floor diff 60) diff (mod diff 60)
+ s diff)
+ (format "%.0f:%02.0f:%02.0f" h m s))))
+ (kill-new sres)
+ (when (called-interactively-p 'interactive)
+ (message (substitute-command-keys
+ (format "Sum of %d items: %-20s \
+\(\\[yank] will insert result into buffer)"
+ (length numbers)
+ sres))))
+ sres))))
+
+;;;###autoload
+(defun org-table-analyze ()
+ "Analyze table at point and store results.
+
+This function sets up the following dynamically scoped variables:
+
+ `org-table-column-name-regexp',
+ `org-table-column-names',
+ `org-table-current-begin-pos',
+ `org-table-current-line-types',
+ `org-table-current-ncol',
+ `org-table-dlines',
+ `org-table-hlines',
+ `org-table-local-parameters',
+ `org-table-named-field-locations'."
+ (let ((beg (org-table-begin))
+ (end (org-table-end)))
+ (save-excursion
+ (goto-char beg)
+ ;; Extract column names.
+ (setq org-table-column-names nil)
+ (when (save-excursion
+ (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t))
+ (let ((c 1))
+ (dolist (name (org-split-string (match-string 1) " *| *"))
+ (cl-incf c)
+ (when (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'" name)
+ (push (cons name (int-to-string c)) org-table-column-names)))))
+ (setq org-table-column-names (nreverse org-table-column-names))
+ (setq org-table-column-name-regexp
+ (format "\\$\\(%s\\)\\>"
+ (regexp-opt (mapcar #'car org-table-column-names) t)))
+ ;; Extract local parameters.
+ (setq org-table-local-parameters nil)
+ (save-excursion
+ (while (re-search-forward "^[ \t]*| *\\$ *\\(|.*\\)" end t)
+ (dolist (field (org-split-string (match-string 1) " *| *"))
+ (when (string-match
+ "\\`\\([a-zA-Z][_a-zA-Z0-9]*\\|%\\) *= *\\(.*\\)" field)
+ (push (cons (match-string 1 field) (match-string 2 field))
+ org-table-local-parameters)))))
+ ;; Update named fields locations. We minimize `count-lines'
+ ;; processing by storing last known number of lines in LAST.
+ (setq org-table-named-field-locations nil)
+ (save-excursion
+ (let ((last (cons (point) 0)))
+ (while (re-search-forward "^[ \t]*| *\\([_^]\\) *\\(|.*\\)" end t)
+ (let ((c (match-string 1))
+ (fields (org-split-string (match-string 2) " *| *")))
+ (save-excursion
+ (forward-line (if (equal c "_") 1 -1))
+ (let ((fields1
+ (and (looking-at "^[ \t]*|[^|]*\\(|.*\\)")
+ (org-split-string (match-string 1) " *| *")))
+ (line (cl-incf (cdr last) (count-lines (car last) (point))))
+ (col 1))
+ (setcar last (point)) ; Update last known position.
+ (while (and fields fields1)
+ (let ((field (pop fields))
+ (v (pop fields1)))
+ (cl-incf col)
+ (when (and (stringp field)
+ (stringp v)
+ (string-match "\\`[a-zA-Z][_a-zA-Z0-9]*\\'"
+ field))
+ (push (cons field v) org-table-local-parameters)
+ (push (list field line col)
+ org-table-named-field-locations))))))))))
+ ;; Re-use existing markers when possible.
+ (if (markerp org-table-current-begin-pos)
+ (move-marker org-table-current-begin-pos (point))
+ (setq org-table-current-begin-pos (point-marker)))
+ ;; Analyze the line types.
+ (let ((l 0) hlines dlines types)
+ (while (looking-at "[ \t]*|\\(-\\)?")
+ (push (if (match-end 1) 'hline 'dline) types)
+ (if (match-end 1) (push l hlines) (push l dlines))
+ (forward-line)
+ (cl-incf l))
+ (push 'hline types) ; Add an imaginary extra hline to the end.
+ (setq org-table-current-line-types (apply #'vector (nreverse types)))
+ (setq org-table-dlines (apply #'vector (cons nil (nreverse dlines))))
+ (setq org-table-hlines (apply #'vector (cons nil (nreverse hlines)))))
+ ;; Get the number of columns from the first data line in table.
+ (goto-char beg)
+ (forward-line (aref org-table-dlines 1))
+ (let* ((fields
+ (org-split-string
+ (buffer-substring (line-beginning-position) (line-end-position))
+ "[ \t]*|[ \t]*"))
+ (nfields (length fields))
+ al al2)
+ (setq org-table-current-ncol nfields)
+ (let ((last-dline
+ (aref org-table-dlines (1- (length org-table-dlines)))))
+ (dotimes (i nfields)
+ (let ((column (1+ i)))
+ (push (list (format "LR%d" column) last-dline column) al)
+ (push (cons (format "LR%d" column) (nth i fields)) al2))))
+ (setq org-table-named-field-locations
+ (append org-table-named-field-locations al))
+ (setq org-table-local-parameters
+ (append org-table-local-parameters al2))))))
+
+(defun org-table--force-dataline ()
+ "Move point to the closest data line in a table.
+Raise an error if the table contains no data line. Preserve
+column when moving point."
+ (unless (org-match-line org-table-dataline-regexp)
+ (let* ((re org-table-dataline-regexp)
+ (column (current-column))
+ (p1 (save-excursion (re-search-forward re (org-table-end) t)))
+ (p2 (save-excursion (re-search-backward re (org-table-begin) t))))
+ (cond ((and p1 p2)
+ (goto-char (if (< (abs (- p1 (point))) (abs (- p2 (point))))
+ p1
+ p2)))
+ ((or p1 p2) (goto-char (or p1 p2)))
+ (t (user-error "No table data line around here")))
+ (org-move-to-column column))))
(defun org-table-show-reference (&optional local)
"Show the location/value of the $ expression at point.
@@ -4056,7 +4911,7 @@ When LOCAL is non-nil, show references for the table at point."
(org-switch-to-buffer-other-window (get-buffer-window
(marker-buffer pos)))))
(goto-char pos)
- (org-table-force-dataline)
+ (org-table--force-dataline)
(let ((table-start
(if local org-table-current-begin-pos (org-table-begin))))
(when dest
@@ -4128,158 +4983,8 @@ When LOCAL is non-nil, show references for the table at point."
(set-window-start (selected-window) max)))))
(select-window win))))
-(defun org-table-force-dataline ()
- "Make sure the cursor is in a dataline in a table."
- (unless (save-excursion
- (beginning-of-line 1)
- (looking-at org-table-dataline-regexp))
- (let* ((re org-table-dataline-regexp)
- (p1 (save-excursion (re-search-forward re nil 'move)))
- (p2 (save-excursion (re-search-backward re nil 'move))))
- (cond ((and p1 p2)
- (goto-char (if (< (abs (- p1 (point))) (abs (- p2 (point))))
- p1 p2)))
- ((or p1 p2) (goto-char (or p1 p2)))
- (t (user-error "No table dataline around here"))))))
-
-(defun org-table-fedit-line-up ()
- "Move cursor one line up in the window showing the table."
- (interactive)
- (org-table-fedit-move 'previous-line))
-
-(defun org-table-fedit-line-down ()
- "Move cursor one line down in the window showing the table."
- (interactive)
- (org-table-fedit-move 'next-line))
-
-(defun org-table-fedit-move (command)
- "Move the cursor in the window showing the table.
-Use COMMAND to do the motion, repeat if necessary to end up in a data line."
- (let ((org-table-allow-automatic-line-recalculation nil)
- (pos org-pos) (win (selected-window)) p)
- (select-window (get-buffer-window (marker-buffer org-pos)))
- (setq p (point))
- (call-interactively command)
- (while (and (org-at-table-p)
- (org-at-table-hline-p))
- (call-interactively command))
- (or (org-at-table-p) (goto-char p))
- (move-marker pos (point))
- (select-window win)))
-
-(defun org-table-fedit-scroll (N)
- (interactive "p")
- (let ((other-window-scroll-buffer (marker-buffer org-pos)))
- (scroll-other-window N)))
-
-(defun org-table-fedit-scroll-down (N)
- (interactive "p")
- (org-table-fedit-scroll (- N)))
-
-(defvar org-table-rectangle-overlays nil)
-
-(defun org-table-add-rectangle-overlay (beg end &optional face)
- "Add a new overlay."
- (let ((ov (make-overlay beg end)))
- (overlay-put ov 'face (or face 'secondary-selection))
- (push ov org-table-rectangle-overlays)))
-
-(defun org-table-highlight-rectangle (&optional beg end face)
- "Highlight rectangular region in a table.
-When buffer positions BEG and END are provided, use them to
-delimit the region to highlight. Otherwise, refer to point. Use
-FACE, when non-nil, for the highlight."
- (let* ((beg (or beg (point)))
- (end (or end (point)))
- (b (min beg end))
- (e (max beg end))
- (start-coordinates
- (save-excursion
- (goto-char b)
- (cons (line-beginning-position) (org-table-current-column))))
- (end-coordinates
- (save-excursion
- (goto-char e)
- (cons (line-beginning-position) (org-table-current-column)))))
- (when (boundp 'org-show-positions)
- (setq org-show-positions (cons b (cons e org-show-positions))))
- (goto-char (car start-coordinates))
- (let ((column-start (min (cdr start-coordinates) (cdr end-coordinates)))
- (column-end (max (cdr start-coordinates) (cdr end-coordinates)))
- (last-row (car end-coordinates)))
- (while (<= (point) last-row)
- (when (looking-at org-table-dataline-regexp)
- (org-table-goto-column column-start)
- (skip-chars-backward "^|\n")
- (let ((p (point)))
- (org-table-goto-column column-end)
- (skip-chars-forward "^|\n")
- (org-table-add-rectangle-overlay p (point) face)))
- (forward-line)))
- (goto-char (car start-coordinates)))
- (add-hook 'before-change-functions #'org-table-remove-rectangle-highlight))
-
-(defun org-table-remove-rectangle-highlight (&rest _ignore)
- "Remove the rectangle overlays."
- (unless org-inhibit-highlight-removal
- (remove-hook 'before-change-functions 'org-table-remove-rectangle-highlight)
- (mapc 'delete-overlay org-table-rectangle-overlays)
- (setq org-table-rectangle-overlays nil)))
-
-(defvar-local org-table-coordinate-overlays nil
- "Collects the coordinate grid overlays, so that they can be removed.")
-
-(defun org-table-overlay-coordinates ()
- "Add overlays to the table at point, to show row/column coordinates."
- (interactive)
- (mapc 'delete-overlay org-table-coordinate-overlays)
- (setq org-table-coordinate-overlays nil)
- (save-excursion
- (let ((id 0) (ih 0) hline eol s1 s2 str ic ov beg)
- (goto-char (org-table-begin))
- (while (org-at-table-p)
- (setq eol (point-at-eol))
- (setq ov (make-overlay (point-at-bol) (1+ (point-at-bol))))
- (push ov org-table-coordinate-overlays)
- (setq hline (looking-at org-table-hline-regexp))
- (setq str (if hline (format "I*%-2d" (setq ih (1+ ih)))
- (format "%4d" (setq id (1+ id)))))
- (org-overlay-before-string ov str 'org-special-keyword 'evaporate)
- (when hline
- (setq ic 0)
- (while (re-search-forward "[+|]\\(-+\\)" eol t)
- (setq beg (1+ (match-beginning 0))
- ic (1+ ic)
- s1 (concat "$" (int-to-string ic))
- s2 (org-number-to-letters ic)
- str (if (eq org-table-use-standard-references t) s2 s1))
- (setq ov (make-overlay beg (+ beg (length str))))
- (push ov org-table-coordinate-overlays)
- (org-overlay-display ov str 'org-special-keyword 'evaporate)))
- (beginning-of-line 2)))))
-
-;;;###autoload
-(defun org-table-toggle-coordinate-overlays ()
- "Toggle the display of Row/Column numbers in tables."
- (interactive)
- (setq org-table-overlay-coordinates (not org-table-overlay-coordinates))
- (message "Tables Row/Column numbers display turned %s"
- (if org-table-overlay-coordinates "on" "off"))
- (if (and (org-at-table-p) org-table-overlay-coordinates)
- (org-table-align))
- (unless org-table-overlay-coordinates
- (mapc 'delete-overlay org-table-coordinate-overlays)
- (setq org-table-coordinate-overlays nil)))
-
-;;;###autoload
-(defun org-table-toggle-formula-debugger ()
- "Toggle the formula debugger in tables."
- (interactive)
- (setq org-table-formula-debug (not org-table-formula-debug))
- (message "Formula debugging has been turned %s"
- (if org-table-formula-debug "on" "off")))
-
-;;; The orgtbl minor mode
+
+;;; The Orgtbl minor mode
;; Define a minor mode which can be used in other modes in order to
;; integrate the Org table editor.
@@ -4308,7 +5013,6 @@ FACE, when non-nil, for the highlight."
;; active, this binding is ignored inside tables and replaced with a
;; modified self-insert.
-
(defvar orgtbl-mode-map (make-keymap)
"Keymap for `orgtbl-mode'.")
@@ -4324,10 +5028,78 @@ FACE, when non-nil, for the highlight."
0 (quote 'org-table) 'prepend))
"Extra `font-lock-keywords' to be added when `orgtbl-mode' is active.")
+;;;###autoload
+(defun turn-on-orgtbl ()
+ "Unconditionally turn on `orgtbl-mode'."
+ (require 'org-table)
+ (orgtbl-mode 1))
+
;; Install it as a minor mode.
(put 'orgtbl-mode :included t)
(put 'orgtbl-mode :menu-tag "Org Table Mode")
+(easy-menu-define orgtbl-mode-menu orgtbl-mode-map "OrgTbl menu"
+ '("OrgTbl"
+ ["Create or convert" org-table-create-or-convert-from-region
+ :active (not (org-at-table-p)) :keys "C-c |" ]
+ "--"
+ ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p) :keys "C-c C-c"]
+ ["Next Field" org-cycle :active (org-at-table-p) :keys "TAB"]
+ ["Previous Field" org-shifttab :active (org-at-table-p) :keys "S-TAB"]
+ ["Next Row" org-return :active (org-at-table-p) :keys "RET"]
+ "--"
+ ["Blank Field" org-table-blank-field :active (org-at-table-p) :keys "C-c SPC"]
+ ["Edit Field" org-table-edit-field :active (org-at-table-p) :keys "C-c ` "]
+ ["Copy Field from Above"
+ org-table-copy-down :active (org-at-table-p) :keys "S-RET"]
+ "--"
+ ("Column"
+ ["Move Column Left" org-metaleft :active (org-at-table-p) :keys "M-<left>"]
+ ["Move Column Right" org-metaright :active (org-at-table-p) :keys "M-<right>"]
+ ["Delete Column" org-shiftmetaleft :active (org-at-table-p) :keys "M-S-<left>"]
+ ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-<right>"])
+ ("Row"
+ ["Move Row Up" org-metaup :active (org-at-table-p) :keys "M-<up>"]
+ ["Move Row Down" org-metadown :active (org-at-table-p) :keys "M-<down>"]
+ ["Delete Row" org-shiftmetaup :active (org-at-table-p) :keys "M-S-<up>"]
+ ["Insert Row" org-shiftmetadown :active (org-at-table-p) :keys "M-S-<down>"]
+ ["Sort lines in region" org-table-sort-lines :active (org-at-table-p) :keys "C-c ^"]
+ "--"
+ ["Insert Hline" org-table-insert-hline :active (org-at-table-p) :keys "C-c -"])
+ ("Rectangle"
+ ["Copy Rectangle" org-copy-special :active (org-at-table-p)]
+ ["Cut Rectangle" org-cut-special :active (org-at-table-p)]
+ ["Paste Rectangle" org-paste-special :active (org-at-table-p)]
+ ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p)])
+ "--"
+ ("Radio tables"
+ ["Insert table template" orgtbl-insert-radio-table
+ (cl-assoc-if #'derived-mode-p orgtbl-radio-table-templates)]
+ ["Comment/uncomment table" orgtbl-toggle-comment t])
+ "--"
+ ["Set Column Formula" org-table-eval-formula :active (org-at-table-p) :keys "C-c ="]
+ ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="]
+ ["Edit Formulas" org-table-edit-formulas :active (org-at-table-p) :keys "C-c '"]
+ ["Recalculate line" org-table-recalculate :active (org-at-table-p) :keys "C-c *"]
+ ["Recalculate all" (org-table-recalculate '(4)) :active (org-at-table-p) :keys "C-u C-c *"]
+ ["Iterate all" (org-table-recalculate '(16)) :active (org-at-table-p) :keys "C-u C-u C-c *"]
+ ["Toggle Recalculate Mark" org-table-rotate-recalc-marks :active (org-at-table-p) :keys "C-c #"]
+ ["Sum Column/Rectangle" org-table-sum
+ :active (or (org-at-table-p) (org-region-active-p)) :keys "C-c +"]
+ ["Which Column?" org-table-current-column :active (org-at-table-p) :keys "C-c ?"]
+ ["Debug Formulas"
+ org-table-toggle-formula-debugger :active (org-at-table-p)
+ :keys "C-c {"
+ :style toggle :selected org-table-formula-debug]
+ ["Show Col/Row Numbers"
+ org-table-toggle-coordinate-overlays :active (org-at-table-p)
+ :keys "C-c }"
+ :style toggle :selected org-table-overlay-coordinates]
+ "--"
+ ("Plot"
+ ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"]
+ ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"])))
+
;;;###autoload
(define-minor-mode orgtbl-mode
"The Org mode table editor as a minor mode for use in other modes."
@@ -4355,15 +5127,12 @@ FACE, when non-nil, for the highlight."
(concat orgtbl-line-start-regexp "\\|"
auto-fill-inhibit-regexp)
orgtbl-line-start-regexp))
- (add-to-invisibility-spec '(org-cwidth))
(when (fboundp 'font-lock-add-keywords)
(font-lock-add-keywords nil orgtbl-extra-font-lock-keywords)
(org-restart-font-lock))
(easy-menu-add orgtbl-mode-menu))
(t
(setq auto-fill-inhibit-regexp org-old-auto-fill-inhibit-regexp)
- (org-table-cleanup-narrow-column-properties)
- (org-remove-from-invisibility-spec '(org-cwidth))
(remove-hook 'before-change-functions 'org-before-change-function t)
(when (fboundp 'font-lock-remove-keywords)
(font-lock-remove-keywords nil orgtbl-extra-font-lock-keywords)
@@ -4371,19 +5140,6 @@ FACE, when non-nil, for the highlight."
(easy-menu-remove orgtbl-mode-menu)
(force-mode-line-update 'all))))
-(defun org-table-cleanup-narrow-column-properties ()
- "Remove all properties related to narrow-column invisibility."
- (let ((s (point-min)))
- (while (setq s (text-property-any s (point-max)
- 'display org-narrow-column-arrow))
- (remove-text-properties s (1+ s) '(display t)))
- (setq s (point-min))
- (while (setq s (text-property-any s (point-max) 'org-cwidth 1))
- (remove-text-properties s (1+ s) '(org-cwidth t)))
- (setq s (point-min))
- (while (setq s (text-property-any s (point-max) 'invisible 'org-cwidth))
- (remove-text-properties s (1+ s) '(invisible t)))))
-
(defun orgtbl-make-binding (fun n &rest keys)
"Create a function for binding in the table minor mode.
FUN is the command to call inside a table. N is used to create a unique
@@ -4498,67 +5254,6 @@ to execute outside of tables."
'delete-char 'org-delete-char
'delete-backward-char 'org-delete-backward-char)
(org-defkey orgtbl-mode-map "|" 'org-force-self-insert))
- (easy-menu-define orgtbl-mode-menu orgtbl-mode-map "OrgTbl menu"
- '("OrgTbl"
- ["Create or convert" org-table-create-or-convert-from-region
- :active (not (org-at-table-p)) :keys "C-c |" ]
- "--"
- ["Align" org-ctrl-c-ctrl-c :active (org-at-table-p) :keys "C-c C-c"]
- ["Next Field" org-cycle :active (org-at-table-p) :keys "TAB"]
- ["Previous Field" org-shifttab :active (org-at-table-p) :keys "S-TAB"]
- ["Next Row" org-return :active (org-at-table-p) :keys "RET"]
- "--"
- ["Blank Field" org-table-blank-field :active (org-at-table-p) :keys "C-c SPC"]
- ["Edit Field" org-table-edit-field :active (org-at-table-p) :keys "C-c ` "]
- ["Copy Field from Above"
- org-table-copy-down :active (org-at-table-p) :keys "S-RET"]
- "--"
- ("Column"
- ["Move Column Left" org-metaleft :active (org-at-table-p) :keys "M-<left>"]
- ["Move Column Right" org-metaright :active (org-at-table-p) :keys "M-<right>"]
- ["Delete Column" org-shiftmetaleft :active (org-at-table-p) :keys "M-S-<left>"]
- ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-<right>"])
- ("Row"
- ["Move Row Up" org-metaup :active (org-at-table-p) :keys "M-<up>"]
- ["Move Row Down" org-metadown :active (org-at-table-p) :keys "M-<down>"]
- ["Delete Row" org-shiftmetaup :active (org-at-table-p) :keys "M-S-<up>"]
- ["Insert Row" org-shiftmetadown :active (org-at-table-p) :keys "M-S-<down>"]
- ["Sort lines in region" org-table-sort-lines :active (org-at-table-p) :keys "C-c ^"]
- "--"
- ["Insert Hline" org-table-insert-hline :active (org-at-table-p) :keys "C-c -"])
- ("Rectangle"
- ["Copy Rectangle" org-copy-special :active (org-at-table-p)]
- ["Cut Rectangle" org-cut-special :active (org-at-table-p)]
- ["Paste Rectangle" org-paste-special :active (org-at-table-p)]
- ["Fill Rectangle" org-table-wrap-region :active (org-at-table-p)])
- "--"
- ("Radio tables"
- ["Insert table template" orgtbl-insert-radio-table
- (cl-assoc-if #'derived-mode-p orgtbl-radio-table-templates)]
- ["Comment/uncomment table" orgtbl-toggle-comment t])
- "--"
- ["Set Column Formula" org-table-eval-formula :active (org-at-table-p) :keys "C-c ="]
- ["Set Field Formula" (org-table-eval-formula '(4)) :active (org-at-table-p) :keys "C-u C-c ="]
- ["Edit Formulas" org-table-edit-formulas :active (org-at-table-p) :keys "C-c '"]
- ["Recalculate line" org-table-recalculate :active (org-at-table-p) :keys "C-c *"]
- ["Recalculate all" (org-table-recalculate '(4)) :active (org-at-table-p) :keys "C-u C-c *"]
- ["Iterate all" (org-table-recalculate '(16)) :active (org-at-table-p) :keys "C-u C-u C-c *"]
- ["Toggle Recalculate Mark" org-table-rotate-recalc-marks :active (org-at-table-p) :keys "C-c #"]
- ["Sum Column/Rectangle" org-table-sum
- :active (or (org-at-table-p) (org-region-active-p)) :keys "C-c +"]
- ["Which Column?" org-table-current-column :active (org-at-table-p) :keys "C-c ?"]
- ["Debug Formulas"
- org-table-toggle-formula-debugger :active (org-at-table-p)
- :keys "C-c {"
- :style toggle :selected org-table-formula-debug]
- ["Show Col/Row Numbers"
- org-table-toggle-coordinate-overlays :active (org-at-table-p)
- :keys "C-c }"
- :style toggle :selected org-table-overlay-coordinates]
- "--"
- ("Plot"
- ["Ascii plot" orgtbl-ascii-plot :active (org-at-table-p) :keys "C-c \" a"]
- ["Gnuplot" org-plot/gnuplot :active (org-at-table-p) :keys "C-c \" g"])))
t))
(defun orgtbl-ctrl-c-ctrl-c (arg)
@@ -5476,9 +6171,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