[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/ftable 2b08dc9 01/14: Init
From: |
Stefan Monnier |
Subject: |
[elpa] externals/ftable 2b08dc9 01/14: Init |
Date: |
Mon, 2 Aug 2021 18:27:30 -0400 (EDT) |
branch: externals/ftable
commit 2b08dc96d287517d02bf49c1ca785e765a92f0fb
Author: Yuan Fu <casouri@gmail.com>
Commit: Yuan Fu <casouri@gmail.com>
Init
* README.org: New file.
* ftable.gif: New file.
* ftable.el: New file.
---
README.org | 23 +++
ftable.el | 626 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ftable.gif | Bin 0 -> 368933 bytes
3 files changed, 649 insertions(+)
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..5f68a96
--- /dev/null
+++ b/README.org
@@ -0,0 +1,23 @@
+#+TITLE: ftable.el
+
+This package provides some convenient commands for filling a table, i.e.,
adjusting the layout of the table so it can fit in n columns.
+
+[[./ftable.gif]]
+
+Commands provided:
+
+- ftable-fill :: Fill the table at point
+- ftable-reformat :: Change the style of the table. For example, from
+#+begin_example
+ ASCII +--+--+ to Unicode ┌──┬──┐
+ | | | │ │ │
+ +--+--+ └──┴──┘
+#+end_example
+
+- ftable-edit-cell :: Edit the cell at point
+
+There are some limitations. Currently ftable doesn’t support tables with
compound cells (cells that span multiple rows/columns) because they are more
complicated to handle. If the need arises in the future (unlikely), I might
improve ftable to handle more complex tables. Also, after filling, any manual
line breaks in a cell is discarded.
+
+* Customization
+
+- ftable-fill-column :: ~fill-column~ for ftable.
diff --git a/ftable.el b/ftable.el
new file mode 100644
index 0000000..df01b4d
--- /dev/null
+++ b/ftable.el
@@ -0,0 +1,626 @@
+;;; ftable.el --- Fill a table to fit in n columns -*- lexical-binding:
t; -*-
+
+;; Author: Yuan Fu <casouri@gmail.com>
+
+;;; This file is NOT part of GNU Emacs
+
+;;; Commentary:
+;;
+;; This package provides some convenient commands for filling a table,
+;; i.e., adjusting the layout of the table so it can fit in n columns.
+;;
+;; Commands provided:
+;;
+;; - ftable-fill Fill the table at point
+;; - ftable-reformat Change the style of the table. For example, from
+;; ASCII +--+--+ to Unicode ┌──┬──┐
+;; | | | │ │ │
+;; +--+--+ └──┴──┘
+;; - ftable-edit-cell Edit the cell at point
+;;
+;; There are some limitations. Currently ftable doesn’t support tables
+;; with compound cells (cells that span multiple rows/columns) because
+;; they are more complicated to handle. If the need arises in the
+;; future (unlikely), I might improve ftable to handle more complex
+;; tables. Also, after filling, any manual line breaks in a cell is
+;; discarded.
+;;
+;; Customization:
+;;
+;; - ftable-fill-column
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'cl-generic)
+(require 'pcase)
+;; (require 'fill) fill.el doesn’t have a provide form.
+
+;;; Customization
+
+(defvar-local ftable-fill-column fill-column
+ "Basically `fill-column' for fill-table.")
+
+;;; Table structure
+
+(cl-defstruct ftable
+ "A table.
+
+COLUMN-WIDTH-LIST A list that records the width (in characters)
+ of each column.
+MIN-WIDTH-LIST A list that records the minimum width (in
+ characters) of each column.
+CELL-MATRIX A list of list of strings. Each string is a cell.
+ Cells don’t contain newlines.
+
+Each cell is a string, the cell doesn’t contain newlines. Column
+width can be smaller than the string length of a cell, in which
+case means the line cell is filled to that width."
+ column-width-list
+ min-width-list
+ matrix)
+
+(cl-deftype ftable-cell () '(satisfies stringp))
+
+(cl-defmethod ftable--row-count ((table ftable))
+ "Return the number of rows in TABLE."
+ (length (ftable-matrix table)))
+
+(cl-defmethod ftable--column-count ((table ftable))
+ "Return the number of columns in TABLE."
+ (length (car (ftable-matrix table))))
+
+;;; Parse
+;;
+;; Transforming between text and table structure
+
+(defvar ftable-box-charset-alist
+ '((ascii . "
++-++
+| ||
++-++
++-++")
+ (unicode . "
+┌─┬┐
+│ ││
+├─┼┤
+└─┴┘"))
+ "An alist of (NAME . CHARSET).
+A charset tells ftable how to parse the table. I.e., what are the
+box drawing characters to use. Don’t forget the first newline.
+NAME is the mnemonic for that charset.")
+
+(defun ftable-box-char (code charset)
+ "Return a specific box drawing character in CHARSET.
+
+Return a string. CHARSET should be like `ftable-box-char-set'.
+Mapping between CODE and position:
+
+ ┌┬┐ 123
+ ├┼┤ <-> 456
+ └┴┘ 789
+
+ ┌─┐ 1 H 3 H: horizontal
+ │ │ <-> V V V: vertical
+ └─┘ 7 H 9
+
+Examples:
+
+ (ftable-box-char 'h charset) => \"─\"
+ (ftable-box-char 2 charset) => \"┬\""
+ (let ((index (pcase code
+ ('h 10)
+ ('v 11)
+ ('n 12)
+ ('s 13)
+ (_ code))))
+
+ (char-to-string
+ (aref charset ; 1 2 3 4 5 6 7 8 9 H V N S
+ (nth index '(nil 1 3 4 11 13 14 16 18 19 2 6 0 7))))))
+
+;; Check `ftable-box-char' with the following form, you should see:
+;; ┌─┬┐
+;; │ ││
+;; ├─┼┤
+;; └─┴┘
+;; (dolist (code '(
+;; 1 h 2 3 n
+;; v s v v n
+;; 4 h 5 6 n
+;; 7 h 8 9 n))
+;; (insert (ftable-box-char code (cdadr ftable-box-charset-alist))))
+
+(cl-defmethod ftable--parse-to ((table-type (eql ftable)) text
+ &optional box-charset)
+ "Parse TEXT into a table of TABLE-TYPE.
+For BOX-CHARSET, see documentation of `ftable-box-charset-alist'.
+It defaults to the first charset."
+ (ignore table-type)
+ ;; TODO Handle parse error.
+ (let ((charset (or box-charset (cdar ftable-box-charset-alist)))
+ line-list
+ matrix
+ buffer)
+ ;; TEXT:
+ ;; ┌──┬─┐
+ ;; │ab│c│
+ ;; ├──┼─┤
+ ;; │de│f│
+ ;; │gh│i│
+ ;; └──┴─┘
+ ;; 1. Split into lines.
+ (setq line-list (split-string text "\n"))
+ ;; LINE-LIST:
+ ;; ("┌──┬─┐" "│ab│c│" "├──┼─┤" "│de│f│" "│gh│i│" "└──┴─┘")
+ ;;
+ ;; 2. Group each line into columns.
+ (dolist (line line-list)
+ (setq line (string-trim line))
+ (if (or (string-prefix-p (ftable-box-char 1 charset) line)
+ (string-prefix-p (ftable-box-char 4 charset) line)
+ (string-prefix-p (ftable-box-char 7 charset) line))
+ ;; Delimiter line, i.e. ┌──┬─┐, ├──┼─┤, etc.
+ (progn (when buffer
+ (push (reverse buffer) matrix))
+ (setq buffer nil))
+ (push (ftable--tokenize-line line charset) buffer)))
+ (setq matrix (reverse matrix))
+ ;; MATRIX:
+ ;; ((("ab" "c")) (("de" "f") ("gh" "i")))
+ ;;
+ ;; 3. Merge lines that belongs to the same row.
+ (setq matrix (mapcar #'ftable--merge-lines matrix))
+ ;; MATRIX:
+ ;; (("ab" "c") ("de gh" "f i"))
+ (make-ftable
+ :column-width-list
+ (mapcar (lambda (column)
+ (apply #'max (mapcar #'length column)))
+ (cl-loop for n from 0 to (1- (length (car matrix)))
+ collect (ftable--nth-column n matrix)))
+ :min-width-list
+ (ftable--min-column-width matrix)
+ :matrix matrix)))
+
+(defun ftable--tokenize-line (text-line box-charset)
+ "Tokenize TEXT-LINE into a list of tokens.
+
+Each token belongs to a cell. I.e.,
+
+ (ftable--tokenize-line \"│a│b│c│\") => (\"a\" \"b\" \"c\")
+
+BOX-CHARSET is the same as in `ftable--parse-to'.
+
+Assumes each line begines with box drawing characters, i.e., no
+white space characters."
+ (cl-assert (string-prefix-p (ftable-box-char 'v box-charset) text-line)
+ t "TEXT-LINE doesn’t begin with box drawing char")
+ (mapcar #'string-trim
+ (split-string (string-trim
+ text-line
+ (ftable-box-char 'v box-charset)
+ (ftable-box-char 'v box-charset))
+ (ftable-box-char 'v box-charset))))
+
+(defun ftable--merge-lines (line-list)
+ "Merge lines in LINE-LIST together.
+
+ (ftable--merge-lines '((\"1\" \"2\" \"3\") (\"a\" \"b\" \"c\")))
+ => (\"1 a\" \"2 b\" \"3 c\")
+
+Assumes each line in LINE-LIST has the same length."
+ (let (row)
+ ;; Initialize ROW.
+ (dotimes (_ (length (nth 0 line-list)))
+ (push "" row))
+ ;; Append cell contents.
+ (dolist (line line-list)
+ (dotimes (col-idx (length line))
+ (setf (nth col-idx row)
+ (concat (nth col-idx row) " "
+ (nth col-idx line)))))
+ (mapcar #'string-trim row)))
+
+(defun ftable--nth-column (n matrix)
+ "Return the Nth column of MATRIX."
+ (mapcar (lambda (row) (nth n row)) matrix))
+
+(defun ftable--min-column-width (matrix)
+ "Return the minimum width of each column in MATRIX."
+ (with-temp-buffer
+ (mapcar (lambda (column)
+ (cl-loop for cell in column
+ maximize
+ (progn
+ (erase-buffer)
+ (insert cell)
+ (let ((fill-column 1))
+ (fill-region-as-paragraph
+ (point-min) (point-max)))
+ (ftable--max-line-width))))
+ (cl-loop for col from 0 to (1- (length (car matrix)))
+ collect (ftable--nth-column col matrix)))))
+
+
+;;; Fill
+
+(cl-defmethod ftable--fill ((table ftable) table-max-width)
+ "Return a new TABLE that fits in TABLE-MAX-WIDTH.
+Try to fit in TABLE-MAX-WIDTH, if not possible, return the
+mininum width table."
+ (let ((desired-width table-max-width)
+ table-height)
+ (when (< table-max-width (ftable--min-width table))
+ (setq desired-width (ftable--min-width table)))
+ ;; While we haven’t satisfied the requirement and there is still
+ ;; room for improvement:
+ (while (< desired-width (ftable--width table))
+ (setq table-height (ftable--height table))
+ (let ((candidate-list
+ ;; A list of (delta of height . new table).
+ (cl-loop
+ for col = 0 then (1+ col)
+ for col-width in (ftable-column-width-list table)
+ for min-width in (ftable-min-width-list table)
+ if (> col-width min-width)
+ collect
+ (let ((new-table
+ (ftable--shrink-column table col 1)))
+ (cons (- (ftable--height new-table)
+ table-height)
+ new-table)))))
+ (if (= 0 (length candidate-list))
+ (debug))
+ (setq table
+ (if (< (length candidate-list) 2)
+ (cdar candidate-list)
+ (cdr
+ (cl-reduce
+ ;; Find argmin(delta of height).
+ (lambda (a b)
+ (if (< (car a) (car b)) a b))
+ candidate-list))))))
+ table))
+
+(cl-defmethod ftable--width ((table ftable))
+ "Return the width of TABLE in characters.
+This width includes all the box drawing characters."
+ (let ((lst (ftable-column-width-list table)))
+ (+ (apply #'+ lst)
+ ;; Plus the horizontal bars.
+ (1+ (length lst))
+ ;; Plus one space padding for each column.
+ (1+ (length lst)))))
+
+(cl-defmethod ftable--min-width ((table ftable))
+ "Return the smallest possible width of TABLE."
+ (let ((lst (ftable-min-width-list table)))
+ (+ (apply #'+ lst)
+ ;; Plus the horizontal bars.
+ (1+ (length lst))
+ ;; Plus one space padding for each column.
+ (1+ (length lst)))))
+
+(cl-defmethod ftable--height ((table ftable))
+ "Return the height of TABLE in chracters.
+This height includes all the box drawing characters."
+ (let ((width-list (ftable-column-width-list table))
+ (matrix (ftable-matrix table)))
+ (+ (cl-loop for row in matrix
+ sum (ftable--row-height row width-list))
+ (1+ (length (ftable-matrix table))))))
+
+(defun ftable--row-height (row column-width-list)
+ "Return the height of ROW.
+Each cell in ROW is first filled according to COLUMN-WIDTH-LIST,
+then the height is calculated."
+ (with-temp-buffer
+ (cl-loop
+ for col from 0 to (1- (length row))
+ ;; For each cell, fill the cell and count lines.
+ maximize (let ((cell (nth col row))
+ (width (nth col column-width-list)))
+ (erase-buffer)
+ (insert cell)
+ (let ((fill-column width))
+ (fill-region-as-paragraph (point-min) (point-max))
+ (count-lines (point-min) (point-max)))))))
+
+(cl-defmethod ftable--shrink-column ((table ftable) n step)
+ "Shrink column N of TABLE by STEP character.
+Return a new table with shrinked column."
+ (let ((width-list (ftable-column-width-list table))
+ (min-list (ftable-min-width-list table))
+ (matrix (ftable-matrix table)))
+ (setf (nth n width-list)
+ (- (nth n width-list) step))
+ (make-ftable
+ :column-width-list width-list
+ :min-width-list min-list
+ :matrix matrix)))
+
+(defun ftable--max-line-width ()
+ "Return the maximum line width in buffer."
+ (apply #'max
+ (mapcar #'length
+ (split-string (buffer-string) "\n"))))
+
+;;; Unparse
+
+(cl-defmethod ftable--unparse ((table ftable) &optional box-charset)
+ "Export TABLE to text form.
+BOX-CHARSET is the same as in `ftable--parse-to'."
+ (let ((charset (or box-charset (cdar ftable-box-charset-alist)))
+ (matrix (ftable-matrix table))
+ (column-width-list (ftable-column-width-list table)))
+ ;; MATRIX:
+ ;; (("abc def" "123")
+ ;; ("ghi" "m"))
+ ;;
+ ;; WIDTH-LIST:
+ ;; (3 3)
+ ;;
+ ;; 1. Split each row into lines.
+ (setq matrix (mapcar (lambda (row)
+ (ftable--split-row row column-width-list))
+ matrix))
+ ;; MATRIX:
+ ;; ((("abc" "123")
+ ;; ("def" ""))
+ ;; (("ghi" "m")))
+ ;;
+ ;; We add a one-space padding to each column (only) when drawing
+ ;; the table.
+ (setq column-width-list (mapcar #'1+ column-width-list))
+ (with-temp-buffer
+ (cl-loop
+ ;; Draw the top border.
+ initially do
+ (ftable--insert-grid-line column-width-list '(1 2 3) charset)
+ for row in matrix
+ ;; Draw lines of each row.
+ do (cl-loop
+ for line in row do
+ (cl-loop initially do
+ (insert (ftable-box-char 'v charset))
+ for column in line
+ for width in column-width-list
+ do (insert (ftable--pad-to column width))
+ do (insert (ftable-box-char 'v charset))
+ finally do (insert "\n")))
+ ;; Draw the separator line.
+ (ftable--insert-grid-line column-width-list '(4 5 6) charset)
+ ;; Draw the bottom border.
+ finally do
+ (progn
+ (forward-line -1)
+ (delete-region (line-beginning-position) (line-end-position))
+ (ftable--insert-grid-line
+ column-width-list '(7 8 9) charset)))
+
+ (string-trim (buffer-string)))))
+
+;; (defun ftable--transpose (matrix)
+;; "Transpose MATRIX."
+;; (cl-loop for col-idx from 0 to (1- (length (car matrix)))
+;; collect
+;; (cl-loop for row in matrix
+;; collect (nth col-idx row))))
+
+(defun ftable--insert-grid-line (column-width-list codeset charset)
+ "Insert a grid line that separates cells vertically.
+For example, ├──┼─┤. COLUMN-WIDTH-LIST is the one in `ftable'
+struct. CODESET is a list of codes that corresponds to the left,
+middle and right box drawing character codes to pass to
+`ftable-box-char'. It can be (1 2 3), (4 5 6), or (7 8 9).
+CHARSET is the same as BOX-CHARSET in `ftable--parse'."
+ (let ((left (ftable-box-char (nth 0 codeset) charset))
+ (middle (ftable-box-char (nth 1 codeset) charset))
+ (right (ftable-box-char (nth 2 codeset) charset)))
+ (cl-loop
+ initially do (insert left)
+ for width in column-width-list
+ do (dotimes (_ width) (insert (ftable-box-char 'h charset)))
+ do (insert middle)
+ finally do (progn (backward-delete-char 1)
+ (insert right "\n")))))
+
+(defun ftable--split-row (row column-width-list)
+ "Split ROW into several lines according to COLUMN-WIDTH-LIST.
+This is the opposite of `ftable--merge-lines'.
+
+Return value has the form of:
+
+ ((abc 123 ...) (def 456 ...) ...)
+
+which corresponds to
+
+ |abc|123|...|
+ |def|456|...|
+ ..."
+ (let (line-count line-list line)
+ (with-temp-buffer
+ (setq row (cl-loop
+ for cell in row
+ for width in column-width-list
+ collect (progn
+ (erase-buffer)
+ (insert cell)
+ (let ((fill-column width))
+ (fill-region-as-paragraph
+ (point-min) (point-max)))
+ (split-string (string-trim (buffer-string))
+ "\n"))))
+ (setq line-count (apply #'max (mapcar #'length row)))
+ (dotimes (idx line-count)
+ (setq line nil)
+ (dolist (cell row)
+ (push (or (nth idx cell) "") line))
+ (push (reverse line) line-list))
+ (reverse line-list))))
+
+(defun ftable--pad-to (text width)
+ "Append padding to TEXT until it is WIDTH characters long.
+Return a new string."
+ (if (< (length text) width)
+ (concat text (make-vector (- width (length text)) ?\s))
+ text))
+
+;;; Convenience
+
+(defun ftable-fill ()
+ "Fill the table (or paragraph) at point."
+ (interactive)
+ (pcase-let ((`(,text ,beg ,end ,tablep , charset)
+ (ftable--table-info)))
+ (if tablep
+ (ftable--replace-text
+ beg end text
+ (string-trim
+ (ftable--unparse
+ (ftable--fill (ftable--parse-to
+ 'ftable text charset)
+ ftable-fill-column)
+ charset)))
+ (fill-paragraph))))
+
+(defun ftable-edit-cell ()
+ "Edit the cell at point."
+ (interactive)
+ (pcase-let* ((pt (point))
+ (p-column (- (point) (line-beginning-position)))
+ (`(,text ,beg ,end ,tablep ,charset) (ftable--table-info))
+ (x -1)
+ ;; If these two characters are the same, we will count
+ ;; one extra.
+ (y (if (equal (ftable-box-char 3 charset)
+ (ftable-box-char 6 charset))
+ -1 0)))
+ (if (not tablep)
+ (user-error "There is no table at point")
+ (save-excursion
+ (goto-char beg)
+ ;; Parse out the coordinate of the cell at point.
+ (while (search-forward
+ (format "%s\n" (ftable-box-char 6 charset))
+ pt t)
+ (cl-incf y))
+ (while (search-forward
+ (ftable-box-char 'v charset)
+ (+ (line-beginning-position) p-column) t)
+ (cl-incf x)))
+ ;; Prompt user to edit.
+ (let* ((table (ftable--parse-to 'ftable text charset))
+ (cell (nth x (nth y (ftable-matrix table))))
+ (new-cell (read-string "Edit: " cell)))
+ (setf (nth x (nth y (ftable-matrix table))) new-cell)
+ ;; Apply change.
+ (ftable--replace-text
+ beg end text
+ (ftable--unparse
+ (ftable--fill table ftable-fill-column)
+ charset))))))
+
+(defun ftable-reformat (style)
+ "Change box drawing STYLE for table at point.
+STYLE can be ’ascii or ’unicode."
+ (interactive (list (intern
+ (downcase
+ (completing-read "Style: "
+ '("ASCII" "Unicode"))))))
+ (pcase-let ((`(,text ,beg ,end ,tablep ,charset)
+ (ftable--table-info)))
+ (if (not tablep)
+ (user-error "There is no table at point")
+ (ftable--replace-text
+ beg end text
+ (ftable--unparse
+ ;; We still need to fill the table, otherwise it will be
+ ;; the widest table layout.
+ (ftable--fill
+ (ftable--parse-to 'ftable text charset)
+ ftable-fill-column)
+ (alist-get style ftable-box-charset-alist))))))
+
+(defun ftable--table-info ()
+ "Return (TEXT BEG END TABLEP CHARSET) for the table at point.
+TEXT is the table’s text. BEG and END are the beginning and end
+position of the table, not including any newlines. TABLEP is t if
+point is on a table, nil if not. CHARSET is the box drawing
+charset used by the table (if there is a table).
+\(See `ftable-box-charset-alist'.)"
+ (let* ((beg (save-excursion (forward-paragraph -1)
+ (skip-chars-forward "\n")
+ (point)))
+ (end (save-excursion (forward-paragraph 1)
+ (skip-chars-backward "\n")
+ (point)))
+ (text (buffer-substring-no-properties
+ beg end)))
+ (append (list text beg end)
+ (cl-loop for charset
+ in (mapcar #'cdr ftable-box-charset-alist)
+ if (equal (substring text 0 1)
+ (ftable-box-char 1 charset))
+ return (list t charset)
+ finally return (list nil nil)))))
+
+(defun ftable--replace-text (beg end text new-text)
+ "Replace TEXT between BEG and END with NEW-TEXT."
+ (unless (equal text new-text)
+ (let ((p (point)))
+ (delete-region beg end)
+ (insert new-text)
+ ;; Go back to roughly where we were.
+ (goto-char p))))
+
+;;; Test
+
+(with-eval-after-load 'ert
+ (ert-deftest ftable--misc-test ()
+ (let ((text (string-trim "
+┌──┬─┐
+│ab│c│
+├──┼─┤
+│de│f│
+│gh│i│
+└──┴─┘")))
+ (should (equal
+ (ftable--parse-to 'ftable text
+ (cdadr ftable-box-charset-alist))
+ (make-ftable
+ :column-width-list '(5 3)
+ :min-width-list '(2 1)
+ :matrix '(("ab" "c") ("de gh" "f i"))))))
+ ;; ftable--tokenize-line
+ (should (equal (mapcar (lambda (x)
+ (ftable--tokenize-line
+ x (cdadr ftable-box-charset-alist)))
+ '( "│ab│c│" "│de│f│" "│gh│i│"))
+ '(("ab" "c") ("de" "f") ("gh" "i") )))
+ (should (equal (ftable--tokenize-line
+ "|fgh| | z|" (cdadr ftable-box-charset-alist))
+ '("fgh" "" "z")))
+ ;; ftable--merge-lines
+ (should (equal (mapcar #'ftable--merge-lines
+ '((("ab" "c")) (("de" "f") ("gh" "i"))))
+ '(("ab" "c") ("de gh" "f i"))))
+ ;; ftable--nth-column
+ (should (equal (ftable--nth-column 1 '((1 2 3) (4 5 6) (7 8 9)))
+ '(2 5 8)))
+ ;; ftable--row-height
+ (should (equal (ftable--row-height '("ab c" "def" "ghi") '(2 3 3))
+ 2))
+ ;; ftable--split-row
+ (should (equal (ftable--split-row '("abc de" "12" "xy z")
+ '(3 2 2))
+ '(("abc" "12" "xy") ("de" "" "z"))))
+ ;; ftable--pad-to
+ (should (equal (ftable--pad-to "123" 5)
+ "123 "))))
+
+
+(provide 'ftable)
+
+;;; ftable.el ends here
diff --git a/ftable.gif b/ftable.gif
new file mode 100644
index 0000000..2cf8552
Binary files /dev/null and b/ftable.gif differ
- [elpa] branch externals/ftable created (now 96a0475), Stefan Monnier, 2021/08/02
- [elpa] externals/ftable ec019c2 06/14: Add customization, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 2b08dc9 01/14: Init,
Stefan Monnier <=
- [elpa] externals/ftable 63fb0ac 04/14: Add checks for parsing, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 9c13bfc 07/14: Add table.el intergration, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 71dcbd0 12/14: Fix beginning- and end-of-table function, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 939cc1b 13/14: Fix typo, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 1ea20be 11/14: Allow non-table lines before and after table, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 96a0475 14/14: Prepare for ELPA, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable a647832 03/14: xx, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable 8ff305b 02/14: Not use forward-paragraph, Stefan Monnier, 2021/08/02
- [elpa] externals/ftable e029b2f 10/14: * ftable.el: Add a workaround., Stefan Monnier, 2021/08/02
- [elpa] externals/ftable d2385e3 05/14: * ftable.el (ftable-misc-test): Fix test., Stefan Monnier, 2021/08/02