vimwiki

Personal wiki for vim
git clone https://github.com/vimwiki/vimwiki.git
Log | Files | Refs | README | LICENSE

commit c4b21b498e2896284eb2118e5510d8f07f20a64f
parent bdcfca1e5ce10b571d4ecacc9e2c3527f54fe226
Author: Henry Qin <root@hq6.me>
Date:   Mon, 18 Mar 2019 11:42:18 -0700

Convert DesignNotes to Markdown to fix display in GitHub.

Diffstat:
ADesignNotes.md | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DDesignNotes.wiki | 187-------------------------------------------------------------------------------
2 files changed, 186 insertions(+), 187 deletions(-)

diff --git a/DesignNotes.md b/DesignNotes.md @@ -0,0 +1,186 @@ +# Design Notes + +This file is meant to document design decisions and algorithms inside vimwiki +which are too large for code comments, and not necessarily interesting to +users. Please create a new section to document each behavior. + +## Formatting tables + +In vimwiki, formatting tables occurs dynamically, when navigating between cells +and adding new rows in a table in the Insert mode, or statically, when pressing +`gqq` or `gqw` (which are mappings for commands `VimwikiTableAlignQ` and +`VimwikiTableAlignW` respectively) in the Normal mode. It also triggers when +leaving Insert mode, provided variable `g:vimwiki_table_auto_fmt` is set. In +this section, the original and the newer optimized algorithms of table +formatting will be described and compared. + +### The older table formatting algorithm and why this is not optimal + +Let's consider a simple example. Open a new file, say _tmp.wiki_, and create a +new table with command `VimwikiTable`. This should create a blank table. + +``` +| | | | | | +|---|---|---|---|---| +| | | | | | +``` + +Let's put the cursor in the first header column of the table, enter the Insert +mode and type a name, say _Col1_. Then press _Tab_: the cursor will move to the +second column of the header and the table will get aligned (in the context of +the table formatting story, words _aligned_ and _formatted_ are considered as +synonyms). Now the table looks as in the following snippet. + +``` +| Col1 | | | | | +|------|---|---|---|---| +| | | | | | +``` + +Then, when moving cursor to the first data row (i.e. to the third line of the +table below the separator line) and typing anything here and there while +navigating using _Tab_ or _Enter_ (pressing this creates a new row below the +current row), the table shall keep formatting. Below is a result of such a +random edit. + +``` +| Col1 | | | | | +|------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| | | | | New data | +``` + +The lowest row gets aligned when leaving the Insert mode. Let's copy _Data1_ +(using `viwy` or another keystroke) and paste it (using `p`) in the second data +row of the first column. Now the table looks mis-aligned (as we did not enter +the Insert mode). + +``` +| Col1 | | | | | +|------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| Data1 | | | | New data | +``` + +This is not a big problem though, because we can put the cursor at _any_ place +in the table and press `gqq`: the table will get aligned. + +``` +| Col1 | | | | | +|-------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| Data1 | | | | New data | +``` + +Now let's make real problems! Move the cursor to the lowest row and copy it +with `yy`. Then 500-fold paste it with `500p`. Now the table very long. Move +the cursor to the lowest row (by pressing `G`), enter the Insert mode, and try +a new random editing session by typing anything in cells with _Tab_ and _Enter_ +navigation interleaves. The editing got painfully slow, did not? + +The reason of the slowing down is the older table formatting algorithm. Every +time _Tab_ or _Enter_ get pressed down, all rows in the table get visited to +calculate a new alignment. Moreover, by design it may happen even more than +once per one press! + +```vim +function! s:kbd_create_new_row(cols, goto_first) + let cmd = "\<ESC>o".s:create_empty_row(a:cols) + let cmd .= "\<ESC>:call vimwiki#tbl#format(line('.'))\<CR>" + let cmd .= "\<ESC>0" + if a:goto_first + let cmd .= ":call search('\\(".s:rxSep()."\\)\\zs', 'c', line('.'))\<CR>" + else + let cmd .= (col('.')-1)."l" + let cmd .= ":call search('\\(".s:rxSep()."\\)\\zs', 'bc', line('.'))\<CR>" + endif + let cmd .= "a" + + return cmd +endfunction +``` + +Function `s:kbd_create_new_row()` is called when _Tab_ or _Enter_ get pressed. +Formatting of the whole table happens in function `vimwiki#tbl#format()`. But +remember that leaving the Insert mode triggers re-formatting of a table when +variable `g:vimwiki_table_auto_fmt` is set. This means that formatting of the +whole table is called on all those multiple interleaves between the Insert and +the Normal mode in `s:kbd_create_new_row` (notice `\<ESC>`, `o`, etc.). + +### The newer table formating algorithm + +The newer algorithm was introduced to struggle against performance issues when +formatting large tables. + +Let's take the table from the previous example in an intermediate state. + +``` +| Col1 | | | | | +|------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| Data1 | | | | New data | +``` + +Then move the cursor to the first data row, copy it with `yy`, go down to the +mis-aligned line, and press `5p`. Now we have a slightly bigger mis-aligned +table. + +``` +| Col1 | | | | | +|------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| Data1 | | | | New data | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +``` + +Go down to the lowest, the 7th, data row and press `gq1`. Nothing happened. +Let's go to the second or the third data row and press `gq1` once again. Now +the table gets aligned. Let's undo formatting with `u`, go to the fourth row, +and press `gq1`. Now the table should look like in the following snippet. + +``` +| Col1 | | | | | +|------|-------|---|-------|----------| +| | Data1 | | Data2 | | +| Data1 | | | | New data | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +| | Data1 | | Data2 | | +``` + +What a peculiar command! Does using it make any sense? Not much, honestly. +Except it shows how the newer optimized table formatting algorithm works in the +Insert mode. + +Indeed, the newer table formatting algorithm introduces a _viewport_ on a table. +Now, when pressing _Tab_ or _Enter_ in the Insert mode, only a small part of +rows are checked for possible formatting: two rows above the current line and +the current line itself (the latter gets preliminary shrunk with function +`s:fmt_row()`). If all three lines in the viewport are of the same length, then +nothing happens (case 1 in the example). If the second or the shrunk current +line is longer then the topmost line in the viewport, then the algorithm falls +back to the older formatting algorithm and the whole table gets aligned +(case 2). If the topmost line in the viewport is longer than the second +and the shrunk current line, then the two lowest lines get aligned according to +the topmost line (case 3). + +Performance of the newer formatting algorithm should not depend on the height +of the table. The newer algorithm should also be consistent with respect to +user editing experience. Indeed, as soon as a table should normally be edited +row by row from the top to the bottom, dynamic formatting should be both fast +(watching only three rows in a table, re-formatting only when the shrunk +current row gets longer than any of the two rows above) and eager (a table +should look formatted on every press on _Tab_ and _Enter_). However, the newer +algorithm differs from the older algorithm when starting editing a mis-aligned +table in an area where mis-aligned rows do not get into the viewport: in this +case the newer algorithm will format the table partly (in the rows of the +viewport) until one of the being edited cells grows in length to a value big +enough to trigger the older algorithm and the whole table gets aligned. When +partial formatting is not desirable, the whole table can be formatted by +pressing `gqq` in the Normal mode. diff --git a/DesignNotes.wiki b/DesignNotes.wiki @@ -1,187 +0,0 @@ -= Design Notes = - -This file is meant to document design decisions and algorithms inside vimwiki -which are too large for code comments, and not necessarily interesting to -users. Please create a new section to document each behavior. - -== Formatting tables == - -In vimwiki, formatting tables occurs dynamically, when navigating between cells -and adding new rows in a table in the Insert mode, or statically, when pressing -`gqq` or `gqw` (which are mappings for commands `VimwikiTableAlignQ` and -`VimwikiTableAlignW` respectively) in the Normal mode. It also triggers when -leaving Insert mode, provided variable `g:vimwiki_table_auto_fmt` is set. In -this section, the original and the newer optimized algorithms of table -formatting will be described and compared. - -=== The older table formatting algorithm and why this is not optimal === - -Let's consider a simple example. Open a new file, say _tmp.wiki_, and create a -new table with command `VimwikiTable`. This should create a blank table. - -{{{ -| | | | | | -|---|---|---|---|---| -| | | | | | -}}} - -Let's put the cursor in the first header column of the table, enter the Insert -mode and type a name, say _Col1_. Then press _Tab_: the cursor will move to the -second column of the header and the table will get aligned (in the context of -the table formatting story, words _aligned_ and _formatted_ are considered as -synonyms). Now the table looks as in the following snippet. - -{{{ -| Col1 | | | | | -|------|---|---|---|---| -| | | | | | -}}} - -Then, when moving cursor to the first data row (i.e. to the third line of the -table below the separator line) and typing anything here and there while -navigating using _Tab_ or _Enter_ (pressing this creates a new row below the -current row), the table shall keep formatting. Below is a result of such a -random edit. - -{{{ -| Col1 | | | | | -|------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| | | | | New data | -}}} - -The lowest row gets aligned when leaving the Insert mode. Let's copy _Data1_ -(using `viwy` or another keystroke) and paste it (using `p`) in the second data -row of the first column. Now the table looks mis-aligned (as we did not enter -the Insert mode). - -{{{ -| Col1 | | | | | -|------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| Data1 | | | | New data | -}}} - -This is not a big problem though, because we can put the cursor at _any_ place -in the table and press `gqq`: the table will get aligned. - -{{{ -| Col1 | | | | | -|-------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| Data1 | | | | New data | -}}} - -Now let's make real problems! Move the cursor to the lowest row and copy it -with `yy`. Then 500-fold paste it with `500p`. Now the table very long. Move -the cursor to the lowest row (by pressing `G`), enter the Insert mode, and try -a new random editing session by typing anything in cells with _Tab_ and _Enter_ -navigation interleaves. The editing got painfully slow, did not? - -The reason of the slowing down is the older table formatting algorithm. Every -time _Tab_ or _Enter_ get pressed down, all rows in the table get visited to -calculate a new alignment. Moreover, by design it may happen even more than -once per one press! - -{{{vim -function! s:kbd_create_new_row(cols, goto_first) - let cmd = "\<ESC>o".s:create_empty_row(a:cols) - let cmd .= "\<ESC>:call vimwiki#tbl#format(line('.'))\<CR>" - let cmd .= "\<ESC>0" - if a:goto_first - let cmd .= ":call search('\\(".s:rxSep()."\\)\\zs', 'c', line('.'))\<CR>" - else - let cmd .= (col('.')-1)."l" - let cmd .= ":call search('\\(".s:rxSep()."\\)\\zs', 'bc', line('.'))\<CR>" - endif - let cmd .= "a" - - return cmd -endfunction -}}} - -Function `s:kbd_create_new_row()` is called when _Tab_ or _Enter_ get pressed. -Formatting of the whole table happens in function `vimwiki#tbl#format()`. But -remember that leaving the Insert mode triggers re-formatting of a table when -variable `g:vimwiki_table_auto_fmt` is set. This means that formatting of the -whole table is called on all those multiple interleaves between the Insert and -the Normal mode in `s:kbd_create_new_row` (notice `\<ESC>`, `o`, etc.). - -=== The newer table formating algorithm === - -The newer algorithm was introduced to struggle against performance issues when -formatting large tables. - -Let's take the table from the previous example in an intermediate state. - -{{{ -| Col1 | | | | | -|------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| Data1 | | | | New data | -}}} - -Then move the cursor to the first data row, copy it with `yy`, go down to the -mis-aligned line, and press `5p`. Now we have a slightly bigger mis-aligned -table. - -{{{ -| Col1 | | | | | -|------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| Data1 | | | | New data | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -}}} - -Go down to the lowest, the 7th, data row and press `gq1`. Nothing happened. -Let's go to the second or the third data row and press `gq1` once again. Now -the table gets aligned. Let's undo formatting with `u`, go to the fourth row, -and press `gq1`. Now the table should look like in the following snippet. - -{{{ -| Col1 | | | | | -|------|-------|---|-------|----------| -| | Data1 | | Data2 | | -| Data1 | | | | New data | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -| | Data1 | | Data2 | | -}}} - -What a peculiar command! Does using it make any sense? Not much, honestly. -Except it shows how the newer optimized table formatting algorithm works in the -Insert mode. - -Indeed, the newer table formatting algorithm introduces a _viewport_ on a table. -Now, when pressing _Tab_ or _Enter_ in the Insert mode, only a small part of -rows are checked for possible formatting: two rows above the current line and -the current line itself (the latter gets preliminary shrunk with function -`s:fmt_row()`). If all three lines in the viewport are of the same length, then -nothing happens (case 1 in the example). If the second or the shrunk current -line is longer then the topmost line in the viewport, then the algorithm falls -back to the older formatting algorithm and the whole table gets aligned -(case 2). If the topmost line in the viewport is longer than the second -and the shrunk current line, then the two lowest lines get aligned according to -the topmost line (case 3). - -Performance of the newer formatting algorithm should not depend on the height -of the table. The newer algorithm should also be consistent with respect to -user editing experience. Indeed, as soon as a table should normally be edited -row by row from the top to the bottom, dynamic formatting should be both fast -(watching only three rows in a table, re-formatting only when the shrunk -current row gets longer than any of the two rows above) and eager (a table -should look formatted on every press on _Tab_ and _Enter_). However, the newer -algorithm differs from the older algorithm when starting editing a mis-aligned -table in an area where mis-aligned rows do not get into the viewport: in this -case the newer algorithm will format the table partly (in the rows of the -viewport) until one of the being edited cells grows in length to a value big -enough to trigger the older algorithm and the whole table gets aligned. When -partial formatting is not desirable, the whole table can be formatted by -pressing `gqq` in the Normal mode. -