Homepage: https://www.gnu.org/software/emacs
Author: Takaaki Ota
Create and edit WYSIWYG text based embedded tables
------------- Introduction: ------------- This package provides text based table creation and editing feature. With this package Emacs is capable of editing tables that are embedded inside a text document, the feature similar to the ones seen in modern WYSIWYG word processors. A table is a rectangular text area consisting from a surrounding frame and content inside the frame. The content is usually subdivided into multiple rectangular cells, see the actual tables used below in this document. Once a table is recognized, editing operation inside a table cell is confined into that specific cell's rectangular area. This means that typing and deleting characters inside a cell do not affect any outside text but introduces appropriate formatting only to the cell contents. If necessary for accommodating added text in the cell, the cell automatically grows vertically and/or horizontally. The package uses no major mode nor minor mode for its implementation because the subject text is localized within a buffer. Therefore the special behaviors inside a table cells are implemented by using keymap text property instead of buffer wide mode-map. ----------- Background: ----------- Paul Georgief is one of my best friends. He became an Emacs convert after I recommended him trying it several years ago. Now we both are devoted disciples of Emacsism and elisp cult. One day in his Emacs exploration he asked me "Tak, what is a command to edit tables in Emacs?". This question started my journey of this table package development. May the code be with me! In the software world Emacs is probably one of the longest lifetime record holders. Amazingly there have been no direct support for WYSIWYG table editing tasks in Emacs. Many people must have experienced manipulating existing overwrite-mode and picture-mode for this task and only dreamed of having such a Lisp package which supports this specific task directly. Certainly, I have been one of them. The most difficult part of dealing with table editing in Emacs probably is how to realize localized rectangular editing effect. Emacs has no rectangular narrowing mechanism. Existing rect package provides basically kill, delete and yank operations of a rectangle, which internally is a mere list of strings. A simple approach for realizing the localized virtual rectangular operation is combining rect package capability with a temporary buffer. Insertion and deletion of a character to a table cell can be trapped by a function that copies the cell rectangle to a temporary buffer then apply the insertion/deletion to the temporary contents. Then it formats the contents by filling the paragraphs in order to fit it into the original rectangular area and finally copy it back to the original buffer. This simplistic approach has to bear with significant performance hit. As cell grows larger the copying rectangle back and forth between the original buffer and the temporary buffer becomes expensive and unbearably slow. It was completely impractical and an obvious failure. An idea has been borrowed from the original Emacs design to overcome this shortcoming. When the terminal screen update was slow and expensive Emacs employed a clever algorithm to reduce actual screen update by removing redundant redrawing operations. Also the actual redrawing was done only when there was enough idling time. This technique significantly improved the previously mentioned undesirable situation. Now the original buffer's rectangle is copied into a cache buffer only once. Any cell editing operation is done only to the cache contents. When there is enough idling time the original buffer's rectangle is updated with the current cache contents. This delayed operation is implemented by using Emacs's timer function. To reduce the visual awkwardness introduced by the delayed effect the cursor location is updated in real-time as a user types while the cell contents remains the same until the next idling time. A key to the success of this approach is how to maintain cache coherency. As a user moves point in and out of a cell the table buffer contents and the cache buffer contents must be synchronized without a mistake. By observing user action carefully this is possible however not easy. Once this mechanism is firmly implemented the rest of table features grew in relatively painless progression. Those users who are familiar with Emacs internals appreciate this table package more. Because it demonstrates how extensible Emacs is by showing something that appears like a magic. It lets you re-discover the potential of Emacs. ------------- Entry Points: ------------- If this is the first time for you to try this package, go ahead and load the package by M-x `load-file' RET. Specify the package file name "table.el". Then switch to a new test buffer and issue the command M-x `table-insert' RET. It'll ask you number of columns, number of rows, cell width and cell height. Give some small numbers for each of them. Play with the resulted table for a while. If you have menu system find the item "Table" under "Tools" and "Table" in the menu bar when the point is in a table cell. Some of them are pretty intuitive and you can easily guess what they do. M-x `describe-function' and get the documentation of `table-insert'. The document includes a short tutorial. When you are tired of guessing how it works come back to this document again. To use the package regularly, add this to your init file: (require 'table) Have the next expression also, if you want always be ready to edit tables inside text files. This mechanism is analogous to fontification in a sense that tables are recognized at editing time without having table information saved along with the text itself. (add-hook 'text-mode-hook 'table-recognize) Following is a table of entry points and brief description of each of them. The tables below are of course generated and edited by using this package. Not all the commands are bound to keys. Many of them must be invoked by "M-x" (`execute-extended-command') command. Refer to the section "Keymap" below for the commands available from keys. +------------------------------------------------------------------+ | User Visible Entry Points | +-------------------------------+----------------------------------+ | Function | Description | +-------------------------------+----------------------------------+ |`table-insert' |Insert a table consisting of grid | | |of cells by specifying the number | | |of COLUMNS, number of ROWS, cell | | |WIDTH and cell HEIGHT. | +-------------------------------+----------------------------------+ |`table-insert-row' |Insert row(s) of cells before the | | |current row that matches the | | |current row structure. | +-------------------------------+----------------------------------+ |`table-insert-column' |Insert column(s) of cells before | | |the current column that matches | | |the current column structure. | +-------------------------------+----------------------------------+ |`table-delete-row' |Delete row(s) of cells. The row | | |must consist from cells of the | | |same height. | +-------------------------------+----------------------------------+ |`table-delete-column' |Delete column(s) of cells. The | | |column must consist from cells of | | |the same width. | +-------------------------------+----------------------------------+ |`table-recognize' |Recognize all tables in the | |`table-unrecognize' |current buffer and | | |activate/deactivate them. | +-------------------------------+----------------------------------+ |`table-recognize-region' |Recognize all the cells in a | |`table-unrecognize-region' |region and activate/deactivate | | |them. | +-------------------------------+----------------------------------+ |`table-recognize-table' |Recognize all the cells in a | |`table-unrecognize-table' |single table and | | |activate/deactivate them. | +-------------------------------+----------------------------------+ |`table-recognize-cell' |Recognize a cell. Find a cell | |`table-unrecognize-cell' |which contains the current point | | |and activate/deactivate that cell.| +-------------------------------+----------------------------------+ |`table-forward-cell' |Move point to the next Nth cell in| | |a table. | +-------------------------------+----------------------------------+ |`table-backward-cell' |Move point to the previous Nth | | |cell in a table. | +-------------------------------+----------------------------------+ |`table-span-cell' |Span the current cell toward the | | |specified direction and merge it | | |with the adjacent cell. The | | |direction is right, left, above or| | |below. | +-------------------------------+----------------------------------+ |`table-split-cell-vertically' |Split the current cell vertically | | |and create a cell above and a cell| | |below the point location. | +-------------------------------+----------------------------------+ |`table-split-cell-horizontally'|Split the current cell | | |horizontally and create a cell on | | |the left and a cell on the right | | |of the point location. | +-------------------------------+----------------------------------+ |`table-split-cell' |Split the current cell vertically | | |or horizontally. This is a | | |wrapper command to the other two | | |orientation specific commands. | +-------------------------------+----------------------------------+ |`table-heighten-cell' |Heighten the current cell. | +-------------------------------+----------------------------------+ |`table-shorten-cell' |Shorten the current cell. | +-------------------------------+----------------------------------+ |`table-widen-cell' |Widen the current cell. | +-------------------------------+----------------------------------+ |`table-narrow-cell' |Narrow the current cell. | +-------------------------------+----------------------------------+ |`table-fixed-width-mode' |Toggle fixed width mode. In the | | |fixed width mode, typing inside a | | |cell never changes the cell width,| | |while in the normal mode the cell | | |width expands automatically in | | |order to prevent a word being | | |folded into multiple lines. Fixed| | |width mode reverses video or | | |underline the cell contents for | | |its indication. | +-------------------------------+----------------------------------+ |`table-query-dimension' |Compute and report the current | | |cell dimension, current table | | |dimension and the number of | | |columns and rows in the table. | +-------------------------------+----------------------------------+ |`table-generate-source' |Generate the source of the current| | |table in the specified language | | |and insert it into a specified | | |buffer. | +-------------------------------+----------------------------------+ |`table-insert-sequence' |Travel cells forward while | | |inserting a specified sequence | | |string into each cell. | +-------------------------------+----------------------------------+ |`table-capture' |Convert plain text into a table by| | |capturing the text in the region. | +-------------------------------+----------------------------------+ |`table-release' |Convert a table into plain text by| | |removing the frame from a table. | +-------------------------------+----------------------------------+ |`table-justify' |Justify the contents of cell(s). | +-------------------------------+----------------------------------+ *Note* You may find that some of commonly expected table commands are missing such as copying a row/column and yanking it. Those functions can be obtained through existing Emacs text editing commands. Rows are easily manipulated with region commands and columns can be copied and pasted through rectangle commands. After all a table is still a part of text in the buffer. Only the special behaviors exist inside each cell through text properties. ------- Keymap: ------- Although this package does not use a mode it does use its own keymap inside a table cell by way of keymap text property. Some of the standard basic editing commands bound to certain keys are replaced with the table specific version of corresponding commands. This replacement combination is listed in the constant alist `table-command-remap-alist' declared below. This alist is not meant to be user configurable but mentioned here for your better understanding of using this package. In addition, table cells have some table specific bindings for cell navigation and cell reformation. You can find these additional bindings in the constant `table-cell-bindings'. Those key bound functions are considered as internal functions instead of normal commands, therefore they have special prefix, *table-- instead of table-, for symbols. The purpose of this is to make it easier for a user to use command name completion. There is a "normal hooks" variable `table-cell-map-hook' prepared for users to override the default table cell bindings. Following is the table of predefined default key bound commands inside a table cell. Remember these bindings exist only inside a table cell. When your terminal is a tty, the control modifier may not be available or applicable for those special characters. In this case use "C-cC-c", which is customizable via `table-command-prefix', as the prefix key sequence. This should preceding the following special character without the control modifier. For example, use "C-cC-c|" instead of "C-|". +------------------------------------------------------------------+ | Default Bindings in a Table Cell | +-------+----------------------------------------------------------+ | Key | Function | +-------+----------------------------------------------------------+ | TAB |Move point forward to the beginning of the next cell. | +-------+----------------------------------------------------------+ | "C->" |Widen the current cell. | +-------+----------------------------------------------------------+ | "C-<" |Narrow the current cell. | +-------+----------------------------------------------------------+ | "C-}" |Heighten the current cell. | +-------+----------------------------------------------------------+ | "C-{" |Shorten the current cell. | +-------+----------------------------------------------------------+ | "C--" |Split current cell vertically. (one above and one below) | +-------+----------------------------------------------------------+ | "C-|" |Split current cell horizontally. (one left and one right) | +-------+----------------------------------------------------------+ | "C-*" |Span current cell into adjacent one. | +-------+----------------------------------------------------------+ | "C-+" |Insert row(s)/column(s). | +-------+----------------------------------------------------------+ | "C-!" |Toggle between normal mode and fixed width mode. | +-------+----------------------------------------------------------+ | "C-#" |Report cell and table dimension. | +-------+----------------------------------------------------------+ | "C-^" |Generate the source in a language from the current table. | +-------+----------------------------------------------------------+ | "C-:" |Justify the contents of cell(s). | +-------+----------------------------------------------------------+ *Note* When using `table-cell-map-hook' do not use `local-set-key'. (add-hook 'table-cell-map-hook (lambda () (local-set-key [] ' ))) Adding the above to your init file is a common way to customize a mode specific keymap. However it does not work for this package. This is because there is no table mode in effect. This package does not use a local map therefore you must modify `table-cell-map' explicitly. The correct way of achieving above task is: (add-hook 'table-cell-map-hook (lambda () (define-key table-cell-map [ ] ' ))) ----- Menu: ----- If a menu system is available a group of table specific menu items, "Table" under "Tools" section of the menu bar, is globally added after this package is loaded. The commands in this group are limited to the ones that are related to creation and initialization of tables, such as to insert a table, to insert rows and columns, or recognize and unrecognize tables. Once tables are created and point is placed inside of a table cell a table specific menu item "Table" appears directly on the menu bar. The commands in this menu give full control on table manipulation that include cell navigation, insertion, splitting, spanning, shrinking, expansion and unrecognizing. In addition to above two types of menu there is a pop-up menu available within a table cell. The content of pop-up menu is identical to the full table menu. [mouse-3] is the default button, defined in `table-cell-bindings', to bring up the pop-up menu. It can be reconfigured via `table-cell-map-hook'. The benefit of a pop-up menu is that it combines selection of the location (which cell, where in the cell) and selection of the desired operation into a single clicking action. ------------------------------- Definition of tables and cells: ------------------------------- There is no artificial-intelligence magic in this package. The definition of a table and the cells inside the table is reasonably limited in order to achieve acceptable performance in the interactive operation under Emacs Lisp implementation. A valid table is a rectangular text area completely filled with valid cells. A valid cell is a rectangle text area, which four borders consist of valid border characters. Cells can not be nested one to another or overlapped to each other except sharing the border lines. A valid character of a cell's vertical border is either table-cell-vertical-char `|' or table-cell-intersection-char `+'. A valid character of a cell's horizontal border is either one of table-cell-horizontal-chars (`-' or `=') or table-cell-intersection-char `+'. A valid character of the four corners of a cell must be table-cell-intersection-char `+'. A cell must contain at least one character space inside. There is no restriction about the contents of a table cell, however it is advised if possible to avoid using any of the border characters inside a table cell. Normally a few boarder characters inside a table cell are harmless. But it is possible that they accidentally align up to emulate a bogus cell corner on which software relies on for cell recognition. When this happens the software may be fooled by it and fail to determine correct cell dimension. Following are the examples of valid tables. +--+----+---+ +-+ +--+-----+ | | | | | | | | | +--+----+---+ +-+ | +--+--+ | | | | | | | | +--+----+---+ +--+--+ | | | | +-----+--+ The next five tables are the examples of invalid tables. (From left to right, 1. nested cells 2. overlapped cells and a non-rectangle cell 3. non-rectangle table 4. zero width/height cells 5. zero sized cell) +-----+ +-----+ +--+ +-++--+ ++ | | | | | | | || | ++ | +-+ | | | | | | || | | | | | +--+ | +--+--+ +-++--+ | +-+ | | | | | | | +-++--+ | | | | | | | | | || | +-----+ +--+--+ +--+--+ +-++--+ Although the program may recognizes some of these invalid tables, results from the subsequent editing operations inside those cells are not predictable and will most likely start destroying the table structures. It is strongly recommended to have at least one blank line above and below a table. For a table to coexist peacefully with surrounding environment table needs to be separated from unrelated text. This is necessary for the left table to grow or shrink horizontally without breaking the right table in the following example. +-----+-----+-----+ +-----+-----+ | | | | | | | +-----+-----+-----+ +-----+-----+ | | | | +-----+-----+-----+ ------------------------- Cell contents formatting: ------------------------- The cell contents are formatted by filling a paragraph immediately after characters are inserted into or deleted from a cell. Because of this, cell contents always remain fit inside a cell neatly. One drawback of this is that users do not have full control over spacing between words and line breaking. Only one space can be entered between words and up to two spaces between sentences. For a newline to be effective the new line must form a beginning of paragraph, otherwise it'll automatically be merged with the previous line in a same paragraph. To form a new paragraph the line must start with some space characters or immediately follow a blank line. Here is a typical example of how to list items within a cell. Without a space at the beginning of each line the items can not stand on their own. +---------------------------------+ |Each one of the following three | |items starts with a space | |character thus forms a paragraph | |of its own. Limitations in cell | |contents formatting are: | | | | 1. Only one space between words.| | 2. Up to two spaces between | |sentences. | | 3. A paragraph must start with | |spaces or follow a blank line. | | | |This paragraph stays away from | |the item 3 because there is a | |blank line between them. | +---------------------------------+ In the normal operation table cell width grows automatically when certain word has to be folded into the next line if the width had not been increased. This normal operation is useful and appropriate for most of the time, however, it is sometimes useful or necessary to fix the width of table and width of table cells. For this purpose the package provides fixed width mode. You can toggle between fixed width mode and normal mode by "C-!". Here is a simple example of the fixed width mode. Suppose we have a table like this one. +-----+ | | +-----+ In normal mode if you type a word "antidisestablishmentarianism" it grows the cell horizontally like this. +----------------------------+ |antidisestablishmentarianism| +----------------------------+ In the fixed width mode the same action produces the following result. The folded locations are indicated by a continuation character (`\' is the default). The continuation character is treated specially so it is recommended to choose a character that does not appear elsewhere in table cells. This character is configurable via customization and is kept in the variable `table-word-continuation-char'. The continuation character is treated specially only in the fixed width mode and has no special meaning in the normal mode however. +-----+ |anti\| |dise\| |stab\| |lish\| |ment\| |aria\| |nism | +-----+ ------------------- Cell Justification: ------------------- By default the cell contents are filled with left justification and no vertical justification. A paragraph can be justified individually but only horizontally. Paragraph justification is for appearance only and does not change any structural information while cell justification affects table's structural information. For cell justification a user can select horizontal justification and vertical justification independently. Horizontal justification must be one of the three 'left, 'center or 'right. Vertical justification can be 'top, 'middle, 'bottom or 'none. When a cell is justified, that information is recorded as a part of text property therefore the information is persistent as long as the cell remains within the Emacs world. Even copying tables by region and rectangle manipulation commands preserve this information. However, once the table text is saved as a file and the buffer is killed the justification information vanishes permanently. To alleviate this shortcoming without forcing users to save and maintain a separate attribute file, the table code detects justification of each cell when recognizing a table. This detection is done by guessing the justification by looking at the appearance of the cell contents. Since it is a guessing work it does not guarantee the perfectness but it is designed to be practically good enough. The guessing algorithm is implemented in the function `table--detect-cell-alignment'. If you have better algorithm or idea any suggestion is welcome. ----- Todo: (in the order of priority, some are just possibility) ----- Fix incompatibilities with input methods other than quail Resolve conflict with flyspell Use mouse for resizing cells A mechanism to link cells internally Consider the use of variable width font under Emacs 21 Consider the use of `:box' face attribute under Emacs 21 Consider the use of `modification-hooks' text property instead of rebinding the keymap --------------- Acknowledgment: --------------- Table would not have been possible without the help and encouragement of the following spirited contributors. Paul Georgief has been the best tester of the code as well as the constructive criticizer. Gerd Moellmann gave me useful suggestions from Emacs 21 point of view. Richard Stallman showed the initial interest in this attempt of implementing the table feature to Emacs. This greatly motivated me to follow through to its completion. Kenichi Handa kindly guided me through to overcome many technical issues while I was struggling with quail related internationalization problems. Christoph Conrad suggested making symbol names consistent as well as fixing several bugs. Paul Lew suggested implementing fixed width mode as well as multi column width (row height) input interface. Michael Smith a well-informed DocBook user asked for CALS table source generation and helped me following through the work by offering valuable suggestions and testing out the code. Jorge Godoy has also suggested supporting for DocBook tables. And many other individuals who reported bugs and suggestions.