├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── autoload ├── tablemode.vim └── tablemode │ ├── align.vim │ ├── logger.vim │ ├── spreadsheet.vim │ ├── spreadsheet │ ├── cell.vim │ └── formula.vim │ ├── table.vim │ └── utils.vim ├── doc └── table-mode.txt ├── ftplugin ├── markdown_tablemode.vim └── rst_tablemode.vim ├── plugin └── table-mode.vim ├── t ├── autoload │ ├── tablemode │ │ ├── align_test.vim │ │ ├── spreadsheet │ │ │ ├── api │ │ │ │ ├── count_test.vim │ │ │ │ ├── manipulation_test.vim │ │ │ │ ├── manipulation_with_escaped_table_separator_test.vim │ │ │ │ ├── manipulation_with_headers_test.vim │ │ │ │ ├── manipulation_with_unicode_test.vim │ │ │ │ ├── math_test.vim │ │ │ │ ├── repeated_manipulations_test.vim │ │ │ │ └── test.vim │ │ │ ├── cell_test.vim │ │ │ └── formula │ │ │ │ ├── add_test.vim │ │ │ │ └── eval_test.vim │ │ └── table │ │ │ ├── add_border_test.vim │ │ │ ├── add_border_with_unicode_test.vim │ │ │ ├── is_border_test.vim │ │ │ ├── is_header_test.vim │ │ │ ├── is_row_test.vim │ │ │ ├── is_table_test.vim │ │ │ ├── realign_test.vim │ │ │ ├── realign_with_header_alignments_test.vim │ │ │ ├── realign_with_header_realignments_and_unicode_test.vim │ │ │ └── realign_with_unicode_test.vim │ ├── tablemode_tableize_test.vim │ └── tablemode_test.vim ├── config │ └── options.vim ├── fixtures │ ├── align │ │ ├── simple_after.txt │ │ ├── simple_before.txt │ │ ├── unicode_after.txt │ │ └── unicode_before.txt │ ├── big_sample.txt │ ├── cell │ │ ├── counts.txt │ │ └── sample.txt │ ├── complex_header.txt │ ├── escaped_seperator.txt │ ├── formula │ │ ├── formula.txt │ │ └── sample.txt │ ├── sample.txt │ ├── table │ │ ├── sample.txt │ │ ├── sample_for_header.txt │ │ ├── sample_for_header_unicode.txt │ │ ├── sample_header_realign_after.txt │ │ ├── sample_header_realign_before.txt │ │ ├── sample_header_realign_unicode_after.txt │ │ ├── sample_header_realign_unicode_before.txt │ │ ├── sample_realign_after.txt │ │ ├── sample_realign_before.txt │ │ ├── sample_realign_unicode_after.txt │ │ ├── sample_realign_unicode_before.txt │ │ └── sample_with_header.txt │ └── tableize.txt └── utils.vim └── youtube.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dhruvasagar 4 | patreon: # Replace with a single Patreon username 5 | open_collective: vim-table-mode 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | tests: 17 | name: Vim Table Mode Tests 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, macos-latest] 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | with: 25 | path: pack/plugins/start/vim-table-mode 26 | 27 | - name: Checkout vim-testify 28 | uses: actions/checkout@v3 29 | with: 30 | repository: dhruvasagar/vim-testify 31 | path: pack/plugins/start/vim-testify 32 | 33 | - name: Install Vim or neovim 34 | uses: rhysd/action-setup-vim@v1 35 | id: vim 36 | with: 37 | neovim: true 38 | version: nightly 39 | 40 | - name: Run unit tests 41 | env: 42 | VIM: ${{ steps.vim.outputs.executable }} 43 | run: | 44 | cd ${{ github.workspace }}/pack/plugins/start/vim-table-mode 45 | echo "set packpath+=${{ github.workspace }}" > vimrc 46 | ${VIM} --headless -u vimrc +TestifySuite +qall 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | .vim-flavor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 3.0.0 4 | script: rake ci 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Version 4.8.0 4 | * Improved formula engine 5 | - Does not cast column values to float 6 | - Silences errors during evaluation, see `v:errmsg` for error information 7 | from last evaluation for debugging issues with formulas 8 | 9 | ## Version 4.7.6.1 10 | * Improved handling of `g:table_mode_ignore_align` configuration, now allows 11 | per buffer overrides 12 | 13 | ## Version 4.7.6 14 | * Add configuration `g:table_mode_ignore_align` 15 | 16 | ## Version 4.7.5 17 | * Improved undo 18 | 19 | ## Version 4.7.3 20 | * Adding option `g:table_mode_tableize_auto_border` to enable automatic border 21 | creation when using Tableize to create tables 22 | 23 | ## Version 4.7.2 24 | * Fix formula evaluation to respect border rows and apply formula expressions 25 | correctly 26 | 27 | ## Version 4.6.8 28 | * Upgrade rake 29 | 30 | ## Version 4.6.7 31 | * Remove auto align feature for insert mode 32 | 33 | ## Version 4.6.6 34 | * Add configuration `g:table_mode_update_time` 35 | 36 | ## Version 4.6.5 37 | * Add support for auto aligning 38 | 39 | ## Version 4.6.4.1 40 | * Added a fix for markdown commentstring 41 | 42 | ## Version 4.6.4 43 | * Added support for center aligning columns 44 | 45 | ## Version 4.6.3 46 | * Fixed tablemode#spreadsheet#LineNr() 47 | * Fixed tablemode#spreadsheet#cell#SetCell() 48 | 49 | ## Version 4.6.2 50 | * Added custom User autocmd event for tablemode activation (enabled / 51 | disabled) 52 | * Adding better header support for pandoc, headers can now have a different 53 | fillchar configured with `g:table_mode_header_fillchar` 54 | 55 | ## Version 4.6.1 56 | * Minor bug fixes 57 | 58 | ## Version 4.6.0 59 | * Added better table header support. The first line of the table if separated 60 | by a table border will be considered as the header. This also means that it 61 | will not be considered / included when evaluating table formulas and that 62 | the first line after the header would be considered the first line of the 63 | table. 64 | 65 | ## Version 4.5.0 66 | * Refactored toggled mappings 67 | * Table Syntax now gets toggled with Table Mode 68 | 69 | ## Version 4.4.2 70 | * Updated mappings to be buffer local. 71 | * Updated mappings to toggle and function only when table mode is active. 72 | 73 | ## Version 4.4.1 74 | * Added syntax for matching tables 75 | 76 | ## Version 4.4.0 77 | * Minor bug fixes 78 | * Added feature to allow using negative indices within formulas to access rows, 79 | columns relative to the last, -1 being the last. 80 | 81 | ## Version 4.3.0 82 | * Refactored some more 83 | * Fixed issue #19, hiphens in the table broke alignment 84 | * Added feature #26, you can now define column alignments in the table header 85 | 86 | ## Version 4.2.0 87 | * Refactored cells logic out to autoload/tablemode/spreadsheet/cell.vim 88 | * Refactored formula logic out to autoload/tablemode/spreadsheet/formula.vim 89 | 90 | ## Version 4.1.0 91 | * Fixed bad references within plugin 92 | * Added fixtures 93 | 94 | ## Version 4.0.0 95 | * Major refactoring of the codebase. 96 | * Improved modular tests. 97 | * Fixed long standing unicode character alignment issue. 98 | * Moved to providing \ mappings rather than configuration based mappings 99 | which can be more easily overriden by end user. 100 | 101 | ## Version 3.3.2 102 | * Added new mapping \t? to echo a cells representation for use while defining 103 | formulas. 104 | 105 | ## Version 3.3.1 106 | * Improved logic to ignore table borders (add as many as you'd like), the 107 | first row is not treated special, it is row # 1. Keep that in mind while 108 | defining Formulas 109 | * Improved test coverage 110 | 111 | ## Version 3.3 112 | * Dropped +- mapping to create table border instead now using || 113 | * You can now have a top table border (before header) as well as a bottom 114 | table border. 115 | 116 | ## Version 3.2 117 | * Added tests to test various use cases using Vspec.. 119 | * Added travis integration for automated tests. 120 | 121 | ## Version 3.1 122 | * Removed borders. You can now optionally create a table header by simply 123 | adding a header border immidiately after the header line by using the 124 | iabbrev trigger '+-'. Just type that on the line immidiately after the 125 | header and press space / \ to complete the header border. 126 | * Some Bug Fixes 127 | 128 | ## Version 3.0 129 | * Removed dependence on Tabular and added code borrowed from Tabular for 130 | aligning the table rows. 131 | * Added feature to be able to define & evaluate formulas. 132 | 133 | ## Version 2.4.0 134 | * Added Table Cell text object. 135 | * Added api to delete entire table row. 136 | * Added api to delete entire table column. 137 | 138 | ## Version 2.3.0 139 | * Refactored realignment logic. Generating borders by hand. 140 | 141 | ## Version 2.2.2 142 | * Added mapping for realigning table columns. 143 | * Added table motions to move around in the table. 144 | 145 | ## Version 2.2.1 146 | * Added feature to allow Table-Mode to work within comments. Uses 147 | 'commentstring' option of vim to identify comments, so it should work for 148 | most filetypes as long as 'commentstring' option has been set. This is 149 | usually done appropriately in filetype plugins. 150 | 151 | ## Version 2.2 152 | * Improved :Tableize to now accept a {pattern} just like :Tabular to match the 153 | delimiter. 154 | 155 | ## Version 2.1.3 : 156 | * Bug Fix #1, added new option `g:table_mode_no_border_padding` which removes 157 | padding from the border. 158 | 159 | ## Version 2.1.2 : 160 | * Bug Fixes #2, #3 & #4 161 | 162 | ## Version 2.1.1 : 163 | * Added option `g:table_mode_align` to allow setting Tabular format option for 164 | more control on how Tabular aligns text. 165 | 166 | ## Version 2.1 : 167 | * VIM loads plugins in alphabetical order and so table-mode would be loaded 168 | before Tabularize which it depends on. Hence Moved plugin into an after 169 | plugin. Checking if Tabularize is available and finish immidiately if it's 170 | not. 171 | 172 | ## Version 2.0 : 173 | * Moved bulk of code to autoload for vimscript optimisation. 174 | 175 | ## Version 1.1 : 176 | * Added Tableize command and mapping to convert existing content into a table. 177 | 178 | ## Version 1.0 : 179 | * First stable release, create tables as you type. 180 | 181 | 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VIM Table Mode v4.8.1 [![Build](https://github.com/dhruvasagar/vim-table-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/dhruvasagar/vim-table-mode/actions/workflows/ci.yml) 2 | 3 | An awesome automatic table creator & formatter allowing one to create neat 4 | tables as you type. 5 | 6 | ## Getting Started 7 | 8 | ### Installation 9 | 10 | #### Vim 8+ native package manager 11 | 12 | clone into `.vim/pack/plugins/start` (the `plugins` folder can have any name) 13 | 14 | Add `packloadall` in your `~/.vimrc`. 15 | 16 | #### NeoBundle 17 | 18 | Add `NeoBundle 'dhruvasagar/vim-table-mode'` to your `~/.vimrc`. 19 | 20 | #### pathogen.vim 21 | 22 | Add a git submodule for your plugin: 23 | 24 | ```sh 25 | $ cd ~/.vim 26 | $ git submodule add git@github.com:dhruvasagar/vim-table-mode.git bundle/table-mode 27 | ``` 28 | 29 | Copy all files under `autoload/`, `plugin/`, and `doc/` to respective 30 | `~/.vim/autoload/`, `~/.vim/plugin` and `~/.vim/doc` under UNIX, or 31 | `vimfiles/autoload/`, `vimfiles/plugin/` and `vimfiles/doc` under WINDOWS and 32 | restart Vim. 33 | 34 | #### vim-plug 35 | 36 | Add `Plug 'dhruvasagar/vim-table-mode'` to your `~/.vimrc`. 37 | 38 | ### Creating table on-the-fly 39 | 40 | To start using the plugin in the on-the-fly mode use `:TableModeToggle` mapped to \tm by default (which means \\ t m if you didn't override the by `:let mapleader = ','` to have , t m). 41 | 42 | Tip: 43 | You can use the following to quickly enable / disable table mode in insert mode by using `||` or `__`: 44 | 45 | > ```vim 46 | > function! s:isAtStartOfLine(mapping) 47 | > let text_before_cursor = getline('.')[0 : col('.')-1] 48 | > let mapping_pattern = '\V' . escape(a:mapping, '\') 49 | > let comment_pattern = '\V' . escape(substitute(&l:commentstring, '%s.*$', '', ''), '\') 50 | > return (text_before_cursor =~? '^' . ('\v(' . comment_pattern . '\v)?') . '\s*\v' . mapping_pattern . '\v$') 51 | > endfunction 52 | > 53 | > inoreabbrev 54 | > \ isAtStartOfLine('\|\|') ? 55 | > \ ':TableModeEnable' : '' 56 | > inoreabbrev __ 57 | > \ isAtStartOfLine('__') ? 58 | > \ ':silent! TableModeDisable' : '__' 59 | > ``` 60 | 61 | Enter the first line, delimiting columns by the `|` symbol. The plugin reacts by inserting spaces between the text and the separator if you omit them: 62 | 63 | | name | address | phone | 64 | 65 | In the second line (without leaving Insert mode), enter `|` twice. The plugin will write a properly formatted horizontal line: 66 | 67 | | name | address | phone | 68 | |------+---------+-------| 69 | 70 | When you enter the subsequent lines, the plugin will automatically adjust the formatting to match the text you’re entering every time you press `|`: 71 | 72 | | name | address | phone | 73 | |------------+---------+-------| 74 | | John Adams | 75 | 76 | Go on until the table is ready: 77 | 78 | | name | address | phone | 79 | |-----------------+--------------------------+------------| 80 | | John Adams | 1600 Pennsylvania Avenue | 0123456789 | 81 | |-----------------+--------------------------+------------| 82 | | Sherlock Holmes | 221B Baker Street | 0987654321 | 83 | |-----------------+--------------------------+------------| 84 | 85 | Then you can return to the first line and above it enter `||`: 86 | 87 | |-----------------+--------------------------+------------| 88 | | name | address | phone | 89 | |-----------------+--------------------------+------------| 90 | | John Adams | 1600 Pennsylvania Avenue | 0123456789 | 91 | |-----------------+--------------------------+------------| 92 | | Sherlock Holmes | 221B Baker Street | 0987654321 | 93 | |-----------------+--------------------------+------------| 94 | 95 | Corner separators are adjustable: 96 | 97 | For Markdown-compatible tables use 98 | 99 | let g:table_mode_corner='|' 100 | 101 | 102 | |-----------------|--------------------------|------------| 103 | | name | address | phone | 104 | |-----------------|--------------------------|------------| 105 | | John Adams | 1600 Pennsylvania Avenue | 0123456789 | 106 | |-----------------|--------------------------|------------| 107 | | Sherlock Holmes | 221B Baker Street | 0987654321 | 108 | |-----------------|--------------------------|------------| 109 | 110 | To get ReST-compatible tables use 111 | 112 | let g:table_mode_corner_corner='+' 113 | let g:table_mode_header_fillchar='=' 114 | 115 | 116 | +-----------------+--------------------------+------------+ 117 | | name | address | phone | 118 | +=================+==========================+============+ 119 | | John Adams | 1600 Pennsylvania Avenue | 0123456789 | 120 | +-----------------+--------------------------+------------+ 121 | | Sherlock Holmes | 221B Baker Street | 0987654321 | 122 | +-----------------+--------------------------+------------+ 123 | 124 | Markdown and ReST filetypes have automatically configured corners. 125 | 126 | > If you wish to override their configurations, it should be done in an after 127 | > plugin, for example : 128 | > 129 | > In a `$VIM/after/ftplugin/markdown/custom.vim` you can add the following : 130 | > 131 | > ```viml 132 | > let b:table_mode_corner='+' 133 | > ``` 134 | 135 | You can also define in a table header border how its content should be 136 | aligned, whether center, right or left by using a `:` character defined by 137 | `g:table_mode_align_char` option. 138 | 139 | If you manipulate the table when table mode is disabled or copy paste a table 140 | from clipboard from outside and it ends up being misaligned, you can realign 141 | it using `:TableModeRealign` or using the default mapping 142 | \tr (defined by the option `g:table_mode_relign_map`). 143 | 144 | ### Formatting existing content into a table 145 | 146 | Table Mode wouldn't justify its name if it didn't allow formatting 147 | existing content into a table. And it does as promised. Like table creation typing on the fly, 148 | formatting existing content into a table is equally 149 | simple. You can visually select multiple lines and call `:Tableize` on it. 150 | Alternatively, the mapping \tt can be used (defined by the 151 | option `g:table_mode_tableize_map`). This converts CSV (Comma-separated 152 | Values) data into a table. 153 | 154 | If however you wish to use a different delimiter, you can use the command 155 | `:Tableize/{pattern}` in a similar fashion as you tabulate (e.g. 156 | `:Tableize/;` uses ';' as the delimiter) or use the mapping \T 157 | (defined by the option `g:table_mode_tableize_op_map`) which takes input in the 158 | cmd-line and uses the `{pattern}` input as the delimiter. 159 | 160 | `:Tableize` also accepts a range. Call it by giving 161 | lines manually like `:line1,line2Tableize`. However this may not be intuitive. 162 | You can use the mapping \T with a `[count]` to apply it to the 163 | next `[count]` lines in standard vim style. 164 | 165 | ### Moving around 166 | 167 | Now you can move between cells using table mode motions [|, 168 | ]|, {| & }| to move left | right | up | 169 | down cells respectively. The left | right motions wrap around the table 170 | and move to the next | previous row after the last | first cell in the 171 | current row if one exists. 172 | 173 | ### Manipulating Table 174 | 175 | - **Cell Text Object** : 176 | 177 | Tableize provides a text object for manipulating table cells. Following 178 | the vim philosophy the you have i| & a| for the 179 | inner and around (including the immediate right table separator) the 180 | table cell. 181 | 182 | - **Delete Row** : 183 | 184 | You can use the \tdd mapping (defined by the option 185 | `g:table_mode_delete_row_map`) to delete the current table row (provided 186 | you are within a table row). This can be preceeded with a `[count]` to 187 | delete multiple rows as per Vim command grammar. 188 | 189 | - **Delete Column** : 190 | 191 | You can use the \tdc mapping (defined by the option 192 | `g:table_mode_delete_column_map`) to delete the entire current column 193 | (provided you are within a table row), this can also be preceeded with a 194 | `[count]` to delete multiple columns. 195 | 196 | - **Insert Column** : 197 | 198 | You can use the \tic mapping (defined by the option 199 | `g:table_mode_insert_column_after_map`) to insert a column after the 200 | cursor (provided you are within a table row). Of course you can use the 201 | \tiC mapping defined by 202 | `g:table_mode_insert_column_before_map` to insert a column before the 203 | cursor. Both can also be preceeded with a [count] to insert multiple 204 | columns. 205 | 206 | ### Highlight cells based on content 207 | 208 | You can highlight cells based on content by setting `let g:table_mode_color_cells` : - cells starting with `yes` will use the `yesCell` highlight group. - cells starting with `no` will use the `noCell` highlight group. - cells starting with `?` will use the `maybeCell` hightlight group. 209 | 210 | You can overwrite any highlight group. For exemple use `hi yesCell ctermfg=2` to remove the background color. 211 | 212 | ## Advanced Usage: Spreadsheet Capabilities 213 | 214 | ### Table Formulas 215 | 216 | Table Mode now has support for formulas like a spreadsheet. There are 2 ways 217 | of defining formulas : 218 | 219 | - You can add formulas using `:TableAddFormula` or the mapping \tfa 220 | (defined by the option `g:table_mode_add_formula_map`) from within a table 221 | cell, which will ask for input on the cmd-line with a `f=` prompt. The 222 | input formula will be appended to the formula line if one exists or a new 223 | one will be created with the input formula taking the current cell as the 224 | target cell. The formula line is evaluated immidiately to reflect the 225 | results. 226 | 227 | - You can directly add / manipulate formula expressions in the formula line. 228 | The formula line is a commented line right after the table, or optionally 229 | separated from the table by a single empty line. It begins with 'tmf:' 230 | (table mode formula). eg) `# tmf: $3=$2*$1`. You can add multiple formulas on 231 | the line separated with a ';' eg) `# tmf: $3=$2*$1;$4=$3/3.14` 232 | 233 | You can evaluate the formula line using `:TableEvalFormulaLine` or the 234 | mapping \tfe (defined by the option `g:table_mode_eval_expr_map`) 235 | from anywhere inside the table or while on the formula line. 236 | 237 | NOTE: You can now use the mapping \t? 238 | 239 | ### Formula Expressions 240 | 241 | Expressions are of the format `$target = formula`. 242 | 243 | - The `target` can be of 2 forms : 244 | 245 | - `$n`: This matches the table column number `n`. So the `formula` would 246 | be evaluated for each cell in that column and the result would be placed 247 | in it. You can use negative indice to represent column relative to the 248 | last, -1 being the last. 249 | 250 | - `$n,m`: This matches the table cell n,m (row, column). So in this case 251 | the formula would be evaluated and the result will be placed in this 252 | cell. You can also use negative values to refer to cells relative to 253 | the size, -1 being the last (row or column). 254 | 255 | - The `formula` can be a simple mathematical expression involving cells 256 | which are also defined by the same format as that of the target cell. You 257 | can use all native vim functions within the formula. Apart from that table 258 | mode also provides 2 special functions `Sum` and `Average`. Both these 259 | functions take a range as input. A range can be of two forms: 260 | 261 | - `r1:r2`: This represents cells in the current column from row `r1` 262 | through `r2`. If `r2` is negative it represents `r2` rows above the 263 | current row (of the target cell). 264 | 265 | - `r1,c1:r2,c2`: This represents cells in the table from cell r1,c1 266 | through cell r2,c2 (row, column). 267 | 268 | - Examples : 269 | - `$2 = $1 * $1` 270 | - `$2 = pow($1, 5)` NOTE: Remember to put space between the $1, and 5 271 | here otherwise it will be treated like a table cell. 272 | - `$2 = $1 / $1,3` 273 | - `$1,2 = $1,1 * $1,1` 274 | - `$5,1 = Sum(1:-1)` 275 | - `$5,1 = float2nr(Sum(1:-1))` 276 | - `$5,3 = Sum(1,2:5,2)` 277 | - `$5,3 = Sum(1,2:5,2)/$5,1` 278 | - `$5,3 = Average(1,2:5,2)/$5,1` 279 | 280 | ## Demo 281 | 282 | 284 | 285 | ## Change Log 286 | 287 | See 289 | CHANGELOG.md 290 | 291 | ## Contributing 292 | 293 | ### Reporting an Issue : 294 | 295 | - Use Github 296 | Issue Tracker 297 | 298 | ### Contributing to code : 299 | 300 | - Fork it. 301 | - Commit your changes and give your commit message some love. 302 | - Push to your fork on github. 303 | - Open a Pull Request. 304 | 305 | ## Credit 306 | 307 | I must thank Tim Pope for inspiration. The initial concept was created by him 308 | named cucumbertables.vim. 309 | 310 | Also a shout out to godlygeek who developed the incredible Tabular plugin. 312 | 313 | ## Contributors 314 | 315 | ### Code Contributors 316 | 317 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 318 | 319 | 320 | ### Financial Contributors 321 | 322 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/vim-table-mode/contribute)] 323 | 324 | #### Individuals 325 | 326 | 327 | 328 | #### Organizations 329 | 330 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/vim-table-mode/contribute)] 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /autoload/tablemode.vim: -------------------------------------------------------------------------------- 1 | " Private Functions {{{1 2 | function! s:SetBufferOptDefault(opt, val) "{{{2 3 | if !exists('b:' . a:opt) 4 | let b:{a:opt} = a:val 5 | endif 6 | endfunction 7 | 8 | function! s:Map(map, to, mode) "{{{2 9 | if !empty(a:to) && !hasmapto(a:map, a:mode) 10 | for l:mode in split(a:mode, '.\zs') 11 | execute l:mode . 'map ' a:to a:map 12 | endfor 13 | endif 14 | endfunction 15 | 16 | function! s:UnMap(map, mode) "{{{2 17 | if !empty(maparg(a:map, a:mode)) 18 | for mode in split(a:mode, '.\zs') 19 | execute l:mode . 'unmap ' a:map 20 | endfor 21 | endif 22 | endfunction 23 | 24 | function! s:ToggleMapping() "{{{2 25 | if !g:table_mode_disable_mappings 26 | if tablemode#IsActive() 27 | call s:Map('(table-mode-tableize)', g:table_mode_separator_map, 'i') 28 | call s:Map('(table-mode-motion-up)', g:table_mode_motion_up_map, 'n') 29 | call s:Map('(table-mode-motion-down)', g:table_mode_motion_down_map, 'n') 30 | call s:Map('(table-mode-motion-left)', g:table_mode_motion_left_map, 'n') 31 | call s:Map('(table-mode-motion-right)', g:table_mode_motion_right_map, 'n') 32 | 33 | call s:Map('(table-mode-cell-text-object-a)', g:table_mode_cell_text_object_a_map, 'ox') 34 | call s:Map('(table-mode-cell-text-object-i)', g:table_mode_cell_text_object_i_map, 'ox') 35 | 36 | call s:Map('(table-mode-realign)', g:table_mode_realign_map, 'n') 37 | call s:Map('(table-mode-delete-row)', g:table_mode_delete_row_map, 'n') 38 | call s:Map('(table-mode-delete-column)', g:table_mode_delete_column_map, 'n') 39 | call s:Map('(table-mode-insert-column-before)', g:table_mode_insert_column_before_map, 'n') 40 | call s:Map('(table-mode-insert-column-after)', g:table_mode_insert_column_after_map, 'n') 41 | call s:Map('(table-mode-add-formula)', g:table_mode_add_formula_map, 'n') 42 | call s:Map('(table-mode-eval-formula)', g:table_mode_eval_formula_map, 'n') 43 | call s:Map('(table-mode-echo-cell)', g:table_mode_echo_cell_map, 'n') 44 | call s:Map('(table-mode-sort)', g:table_mode_sort_map, 'n') 45 | else 46 | call s:UnMap(g:table_mode_separator_map, 'i') 47 | call s:UnMap(g:table_mode_motion_up_map, 'n') 48 | call s:UnMap(g:table_mode_motion_down_map, 'n') 49 | call s:UnMap(g:table_mode_motion_left_map, 'n') 50 | call s:UnMap(g:table_mode_motion_right_map, 'n') 51 | 52 | call s:UnMap(g:table_mode_cell_text_object_a_map, 'ox') 53 | call s:UnMap(g:table_mode_cell_text_object_i_map, 'ox') 54 | 55 | call s:UnMap(g:table_mode_realign_map, 'n') 56 | call s:UnMap(g:table_mode_delete_row_map, 'n') 57 | call s:UnMap(g:table_mode_delete_column_map, 'n') 58 | call s:UnMap(g:table_mode_insert_column_before_map, 'n') 59 | call s:UnMap(g:table_mode_insert_column_after_map, 'n') 60 | call s:UnMap(g:table_mode_add_formula_map, 'n') 61 | call s:UnMap(g:table_mode_eval_formula_map, 'n') 62 | call s:UnMap(g:table_mode_echo_cell_map, 'n') 63 | call s:UnMap(g:table_mode_sort_map, 'n') 64 | endif 65 | endif 66 | endfunction 67 | 68 | function! s:ToggleSyntax() "{{{2 69 | if !g:table_mode_syntax | return | endif 70 | 71 | if tablemode#IsActive() 72 | exec 'syntax match Table' 73 | \ '/' . tablemode#table#StartExpr() . '\zs|.\+|\ze' . tablemode#table#EndExpr() . '/' 74 | \ 'contains=TableBorder,TableSeparator,TableColumnAlign,yesCell,noCell,maybeCell,redCell,greenCell,yellowCell,blueCell,whiteCell,darkCell' 75 | \ 'containedin=ALL' 76 | syntax match TableSeparator /|/ contained 77 | syntax match TableColumnAlign /:/ contained 78 | syntax match TableBorder /[\-+]\+/ contained 79 | 80 | hi! link TableBorder Delimiter 81 | hi! link TableSeparator Delimiter 82 | hi! link TableColumnAlign Type 83 | 84 | syntax match redCell '|\@<= *r:[^|]*' contained 85 | hi redCell ctermfg=9 ctermbg=1 86 | 87 | syntax match greenCell '|\@<= *g:[^|]*' contained 88 | hi greenCell ctermfg=10 ctermbg=2 89 | 90 | syntax match yellowCell '|\@<= *y:[^|]*' contained 91 | hi yellowCell ctermfg=11 ctermbg=3 92 | 93 | syntax match blueCell '|\@<= *b:[^|]*' contained 94 | hi blueCell ctermfg=12 ctermbg=4 95 | 96 | syntax match whiteCell '|\@<= *w:[^|]*' contained 97 | hi whiteCell ctermfg=0 ctermbg=15 98 | 99 | syntax match darkCell '|\@<= *d:[^|]*' contained 100 | hi darkCell ctermfg=15 ctermbg=0 101 | 102 | if exists("g:table_mode_color_cells") && g:table_mode_color_cells 103 | syntax match yesCell '|\@<= *yes[^|]*' contained 104 | syntax match noCell '|\@<= *no\A[^|]*' contained " \A to exclude words like notes 105 | syntax match maybeCell '|\@<= *?[^|]*' contained 106 | " '|\@<=' : Match previous characters, excluding them from the group 107 | endif 108 | 109 | else 110 | syntax clear Table 111 | syntax clear TableBorder 112 | syntax clear TableSeparator 113 | syntax clear TableColumnAlign 114 | 115 | hi! link TableBorder NONE 116 | hi! link TableSeparator NONE 117 | hi! link TableColumnAlign NONE 118 | endif 119 | endfunction 120 | 121 | function! s:ToggleAutoAlign() "{{{2 122 | if !g:table_mode_auto_align | return | endif 123 | 124 | if tablemode#IsActive() 125 | augroup TableModeAutoAlign 126 | au! 127 | 128 | autocmd CursorHold nested silent! if &modified | call tablemode#table#Realign('.') | endif 129 | " autocmd InsertLeave nested silent! if &modified | call tablemode#table#Realign('.') | endif 130 | augroup END 131 | else 132 | autocmd! TableModeAutoAlign 133 | endif 134 | endfunction 135 | 136 | function! s:ToggleOptions() "{{{2 137 | if tablemode#IsActive() 138 | let b:old_update_time = &updatetime 139 | exec 'set updatetime='.g:table_mode_update_time 140 | else 141 | exec 'set updatetime='.get(b:, 'old_update_time', 4000) 142 | endif 143 | endfunction 144 | 145 | function! s:SetActive(bool) "{{{2 146 | let b:table_mode_active = a:bool 147 | call s:ToggleSyntax() 148 | call s:ToggleMapping() 149 | call s:ToggleAutoAlign() 150 | call s:ToggleOptions() 151 | if tablemode#IsActive() 152 | doautocmd User TableModeEnabled 153 | else 154 | doautocmd User TableModeDisabled 155 | endif 156 | endfunction 157 | 158 | function! s:ConvertDelimiterToSeparator(line, ...) "{{{2 159 | let old_gdefault = &gdefault 160 | set nogdefault 161 | 162 | let delim = g:table_mode_delimiter 163 | if a:0 | let delim = a:1 | endif 164 | if delim ==# ',' 165 | silent! execute a:line . 's/' . "[\'\"][^\'\"]*\\zs,\\ze[^\'\"]*[\'\"]/__COMMA__/g" 166 | endif 167 | 168 | let [cstart, cend] = [tablemode#table#GetCommentStart(), tablemode#table#GetCommentEnd()] 169 | let [match_char_start, match_char_end] = ['.', '.'] 170 | if tablemode#utils#strlen(cend) > 0 | let match_char_end = '[^' . cend . ']' | endif 171 | if tablemode#utils#strlen(cstart) > 0 | let match_char_start = '[^' . cstart . ']' | endif 172 | 173 | silent! execute a:line . 's/' . tablemode#table#StartExpr() . '\zs\ze' . match_char_start . 174 | \ '\|' . delim . '\|' . match_char_end . '\zs\ze' . tablemode#table#EndExpr() . '/' . 175 | \ g:table_mode_separator . '/g' 176 | 177 | if delim ==# ',' 178 | silent! execute a:line . 's/' . "[\'\"][^\'\"]*\\zs__COMMA__\\ze[^\'\"]*[\'\"]/,/g" 179 | endif 180 | 181 | let &gdefault=old_gdefault 182 | endfunction 183 | 184 | function! s:Tableizeline(line, ...) "{{{2 185 | let delim = g:table_mode_delimiter 186 | if a:0 && type(a:1) == type('') && !empty(a:1) | let delim = a:1[1:-1] | endif 187 | call s:ConvertDelimiterToSeparator(a:line, delim) 188 | endfunction 189 | 190 | " Public API {{{1 191 | function! tablemode#IsActive() "{{{2 192 | if g:table_mode_always_active | return 1 | endif 193 | 194 | call s:SetBufferOptDefault('table_mode_active', 0) 195 | return b:table_mode_active 196 | endfunction 197 | 198 | function! tablemode#TableizeInsertMode() "{{{2 199 | if tablemode#IsActive() 200 | if getline('.') =~# (tablemode#table#StartExpr() . g:table_mode_separator . g:table_mode_separator . tablemode#table#EndExpr()) 201 | call tablemode#table#AddBorder('.') 202 | normal! A 203 | elseif getline('.') =~# (tablemode#table#StartExpr() . g:table_mode_separator) 204 | let column = tablemode#utils#strlen(substitute(getline('.')[0:col('.')], '[^' . g:table_mode_separator . ']', '', 'g')) 205 | let position = tablemode#utils#strlen(matchstr(getline('.')[0:col('.')], '.*' . g:table_mode_separator . '\s*\zs.*')) 206 | call tablemode#table#Realign('.') 207 | normal! 0 208 | call search(repeat('[^' . g:table_mode_separator . ']*' . g:table_mode_separator, column) . '\s\{-\}' . repeat('.', position), 'ce', line('.')) 209 | endif 210 | endif 211 | endfunction 212 | 213 | function! tablemode#Enable() "{{{2 214 | call s:SetActive(1) 215 | endfunction 216 | 217 | function! tablemode#Disable() "{{{2 218 | call s:SetActive(0) 219 | endfunction 220 | 221 | function! tablemode#Toggle() "{{{2 222 | if g:table_mode_always_active 223 | return 1 224 | endif 225 | 226 | call s:SetBufferOptDefault('table_mode_active', 0) 227 | call s:SetActive(!b:table_mode_active) 228 | endfunction 229 | 230 | function! tablemode#TableizeRange(...) range "{{{2 231 | let lnum = a:firstline 232 | let total = (a:lastline - a:firstline + 1) 233 | " echom total 234 | let cntr = 1 235 | while cntr <= total 236 | call s:Tableizeline(lnum, a:1) 237 | undojoin 238 | if g:table_mode_tableize_auto_border 239 | if cntr == 1 240 | normal! O 241 | call tablemode#table#AddBorder('.') 242 | normal! j 243 | let lnum += 1 244 | endif 245 | normal! o 246 | call tablemode#table#AddBorder('.') 247 | let lnum += 1 248 | endif 249 | let cntr += 1 250 | let lnum += 1 251 | endwhile 252 | 253 | call tablemode#table#Realign(lnum - 1) 254 | endfunction 255 | 256 | function! tablemode#TableizeByDelimiter() "{{{2 257 | let delim = input('/') 258 | if delim =~# "\" || delim =~# "\" | return | endif 259 | let vm = visualmode() 260 | if vm ==? 'line' || vm ==? 'V' 261 | exec line("'<") . ',' . line("'>") . "call tablemode#TableizeRange('/' . delim)" 262 | endif 263 | endfunction 264 | 265 | if !hlexists('yesCell') | hi yesCell cterm=bold ctermfg=10 ctermbg=2 | endif | 266 | if !hlexists('noCell') | hi noCell cterm=bold ctermfg=9 ctermbg=1 | endif | 267 | if !hlexists('maybeCell') | hi maybeCell cterm=bold ctermfg=11 ctermbg=3 | endif | 268 | -------------------------------------------------------------------------------- /autoload/tablemode/align.vim: -------------------------------------------------------------------------------- 1 | " Borrowed from Tabular 2 | " Private Functions {{{1 3 | " function! s:StripTrailingSpaces(string) - Remove all trailing spaces {{{2 4 | " from a string. 5 | function! s:StripTrailingSpaces(string) 6 | return matchstr(a:string, '^.\{-}\ze\s*$') 7 | endfunction 8 | 9 | function! s:Padding(string, length, where) "{{{3 10 | let gap_length = a:length - tablemode#utils#StrDisplayWidth(a:string) 11 | if a:where =~# 'l' 12 | return a:string . repeat(" ", gap_length) 13 | elseif a:where =~# 'r' 14 | return repeat(" ", gap_length) . a:string 15 | elseif a:where =~# 'c' 16 | let right = gap_length / 2 17 | let left = right + (right * 2 != gap_length) 18 | return repeat(" ", left) . a:string . repeat(" ", right) 19 | endif 20 | endfunction 21 | 22 | " Public Functions {{{1 23 | " function! tablemode#align#Split() - Split a string into fields and delimiters {{{2 24 | " Like split(), but include the delimiters as elements 25 | " All odd numbered elements are delimiters 26 | " All even numbered elements are non-delimiters (including zero) 27 | function! tablemode#align#Split(string, delim) 28 | let rv = [] 29 | let beg = 0 30 | 31 | let len = len(a:string) 32 | let searchoff = 0 33 | 34 | while 1 35 | let mid = match(a:string, a:delim, beg + searchoff, 1) 36 | if mid == -1 || mid == len 37 | break 38 | endif 39 | 40 | let matchstr = matchstr(a:string, a:delim, beg + searchoff, 1) 41 | let length = strlen(matchstr) 42 | 43 | if length == 0 && beg == mid 44 | " Zero-length match for a zero-length delimiter - advance past it 45 | let searchoff += 1 46 | continue 47 | endif 48 | 49 | if beg == mid 50 | let rv += [ "" ] 51 | else 52 | let rv += [ a:string[beg : mid-1] ] 53 | endif 54 | 55 | let rv += [ matchstr ] 56 | 57 | let beg = mid + length 58 | let searchoff = 0 59 | endwhile 60 | 61 | let rv += [ strpart(a:string, beg) ] 62 | 63 | return rv 64 | endfunction 65 | 66 | function! tablemode#align#alignments(lnum, ncols) "{{{2 67 | let achr = g:table_mode_align_char 68 | let alignments = [] 69 | if tablemode#table#IsBorder(a:lnum+1) 70 | let corner = tablemode#utils#get_buffer_or_global_option('table_mode_corner') 71 | let corner_corner = tablemode#utils#get_buffer_or_global_option('table_mode_corner_corner') 72 | let hcols = tablemode#align#Split(getline(a:lnum+1), '[' . corner . corner_corner . ']') 73 | for idx in range(len(hcols)) 74 | " Right align if header 75 | call add(alignments, 'l') 76 | if hcols[idx] =~# achr . '[^'.achr.']\+' . achr 77 | let alignments[idx] = 'c' 78 | elseif hcols[idx] =~# achr . '$' 79 | let alignments[idx] = 'r' 80 | endif 81 | " if hcols[idx] !~# '[^0-9\.]' | let alignments[idx] = 'r' | endif 82 | endfor 83 | end 84 | return alignments 85 | endfunction 86 | 87 | function! tablemode#align#Align(lines) "{{{2 88 | if empty(a:lines) | return [] | endif 89 | let lines = map(a:lines, 'map(v:val, "v:key =~# \"text\" ? tablemode#align#Split(v:val, g:table_mode_escaped_separator_regex) : v:val")') 90 | 91 | for line in lines 92 | let stext = line.text 93 | if len(stext) <= 1 | continue | endif 94 | 95 | if stext[0] !~ tablemode#table#StartExpr() 96 | let stext[0] = s:StripTrailingSpaces(stext[0]) 97 | endif 98 | if len(stext) >= 2 99 | for i in range(1, len(stext)-1) 100 | let stext[i] = tablemode#utils#strip(stext[i]) 101 | endfor 102 | endif 103 | endfor 104 | 105 | let maxes = [] 106 | for line in lines 107 | let stext = line.text 108 | if len(stext) <= 1 | continue | endif 109 | for i in range(len(stext)) 110 | if i == len(maxes) 111 | let maxes += [ tablemode#utils#StrDisplayWidth(stext[i]) ] 112 | else 113 | let maxes[i] = max([ maxes[i], tablemode#utils#StrDisplayWidth(stext[i]) ]) 114 | endif 115 | endfor 116 | endfor 117 | 118 | if tablemode#utils#get_buffer_or_global_option('table_mode_ignore_align') ==# 1 119 | let alignments = [] 120 | else 121 | let alignments = tablemode#align#alignments(lines[0].lnum, len(lines[0].text)) 122 | endif 123 | 124 | for idx in range(len(lines)) 125 | let tlnum = lines[idx].lnum 126 | let tline = lines[idx].text 127 | 128 | if len(tline) <= 1 | continue | endif 129 | for jdx in range(len(tline)) 130 | " Dealing with the header being the first line 131 | if jdx >= len(alignments) | call add(alignments, 'l') | endif 132 | let field = s:Padding(tline[jdx], maxes[jdx], alignments[jdx]) 133 | let tline[jdx] = field . (jdx == 0 || jdx == len(tline) ? '' : ' ') 134 | endfor 135 | 136 | let lines[idx].text = s:StripTrailingSpaces(join(tline, '')) 137 | endfor 138 | 139 | return lines 140 | endfunction 141 | -------------------------------------------------------------------------------- /autoload/tablemode/logger.vim: -------------------------------------------------------------------------------- 1 | function! tablemode#logger#log(message) 2 | if g:table_mode_verbose 3 | echom a:message 4 | endif 5 | endfunction 6 | -------------------------------------------------------------------------------- /autoload/tablemode/spreadsheet.vim: -------------------------------------------------------------------------------- 1 | " Private Functions {{{1 2 | function! s:TotalCells(list) "{{{2 3 | let result = 0 4 | for item in a:list 5 | if type(item) == type([]) 6 | let result += s:TotalCells(item) 7 | else 8 | let result += 1 9 | endif 10 | endfor 11 | return result 12 | endfunction 13 | 14 | function! s:Min(list) "{{{2 15 | let found = v:false 16 | let result = 0 17 | for item in a:list 18 | if empty(item) 19 | continue 20 | endif 21 | if type(item) == type(1) || type(item) == type(1.0) 22 | if found == v:false || item < result 23 | let found = v:true 24 | let result = item 25 | endif 26 | elseif type(item) == type('') 27 | let val = str2float(item) 28 | if found == v:false || val < result 29 | let found = v:true 30 | let result = val 31 | endif 32 | elseif type(item) == type([]) 33 | let val = s:Min(item) 34 | if found == v:false || val < result 35 | let found = v:true 36 | let result = val 37 | endif 38 | endif 39 | endfor 40 | return result 41 | endfunction 42 | 43 | function! s:Max(list) "{{{2 44 | let found = v:false 45 | let result = 0 46 | for item in a:list 47 | if empty(item) 48 | continue 49 | endif 50 | if type(item) == type(1) || type(item) == type(1.0) 51 | if found == v:false || item > result 52 | let found = v:true 53 | let result = item 54 | endif 55 | elseif type(item) == type('') 56 | let val = str2float(item) 57 | if found == v:false || val > result 58 | let found = v:true 59 | let result = val 60 | endif 61 | elseif type(item) == type([]) 62 | let val = s:Max(item) 63 | if found == v:false || val > result 64 | let found = v:true 65 | let result = val 66 | endif 67 | endif 68 | endfor 69 | return result 70 | endfunction 71 | 72 | function! s:CountE(list) "{{{2 73 | let result = 0 74 | for item in a:list 75 | if empty(item) 76 | let result += 1 77 | elseif type(item) == type([]) 78 | let result += s:CountE(item) 79 | endif 80 | endfor 81 | return result 82 | endfunction 83 | 84 | function! s:CountNE(list) "{{{2 85 | let result = 0 86 | for item in a:list 87 | if type(item) == type([]) 88 | let result += s:CountNE(item) 89 | elseif !empty(item) 90 | let result += 1 91 | endif 92 | endfor 93 | return result 94 | endfunction 95 | 96 | function! s:PercentE(list) "{{{2 97 | return (s:CountE(a:list)*100)/s:TotalCells(a:list) 98 | endfunction 99 | 100 | function! s:PercentNE(list) "{{{2 101 | return (s:CountNE(a:list)*100)/s:TotalCells(a:list) 102 | endfunction 103 | 104 | function! s:Sum(list) "{{{2 105 | let result = 0.0 106 | for item in a:list 107 | if type(item) == type(1) || type(item) == type(1.0) 108 | let result += item 109 | elseif type(item) == type('') 110 | let result += str2float(item) 111 | elseif type(item) == type([]) 112 | let result += s:Sum(item) 113 | endif 114 | endfor 115 | return result 116 | endfunction 117 | 118 | function! s:Average(list) "{{{2 119 | return s:Sum(a:list)/s:TotalCells(a:list) 120 | endfunction 121 | 122 | function! s:AverageNE(list) "{{{2 123 | return s:Sum(a:list)/s:CountNE(a:list) 124 | endfunction 125 | 126 | " Public Functions {{{1 127 | function! tablemode#spreadsheet#GetFirstRow(line) "{{{2 128 | if tablemode#table#IsRow(a:line) 129 | let line = tablemode#utils#line(a:line) 130 | 131 | while !tablemode#table#IsHeader(line - 1) && (tablemode#table#IsRow(line - 1) || tablemode#table#IsBorder(line - 1)) 132 | let line -= 1 133 | endwhile 134 | if tablemode#table#IsBorder(line) | let line += 1 | endif 135 | 136 | return line 137 | endif 138 | endfunction 139 | 140 | function! tablemode#spreadsheet#GetFirstRowOrHeader(line) "{{{2 141 | if tablemode#table#IsRow(a:line) 142 | let line = tablemode#utils#line(a:line) 143 | 144 | while tablemode#table#IsTable(line - 1) 145 | let line -= 1 146 | endwhile 147 | if tablemode#table#IsBorder(line) | let line += 1 | endif 148 | 149 | return line 150 | endif 151 | endfunction 152 | 153 | function! tablemode#spreadsheet#MoveToFirstRow() "{{{2 154 | if tablemode#table#IsRow('.') 155 | call tablemode#utils#MoveToLine(tablemode#spreadsheet#GetFirstRow('.')) 156 | endif 157 | endfunction 158 | 159 | function! tablemode#spreadsheet#MoveToFirstRowOrHeader() "{{{2 160 | if tablemode#table#IsRow('.') 161 | call tablemode#utils#MoveToLine(tablemode#spreadsheet#GetFirstRowOrHeader('.')) 162 | endif 163 | endfunction 164 | 165 | function! tablemode#spreadsheet#GetLastRow(line) "{{{2 166 | if tablemode#table#IsRow(a:line) 167 | let line = tablemode#utils#line(a:line) 168 | 169 | while tablemode#table#IsTable(line + 1) 170 | let line += 1 171 | endwhile 172 | if tablemode#table#IsBorder(line) | let line -= 1 | endif 173 | 174 | return line 175 | endif 176 | endfunction 177 | 178 | function! tablemode#spreadsheet#MoveToLastRow() "{{{2 179 | if tablemode#table#IsRow('.') 180 | call tablemode#utils#MoveToLine(tablemode#spreadsheet#GetLastRow('.')) 181 | endif 182 | endfunction 183 | 184 | function! tablemode#spreadsheet#LineNr(line, row) "{{{2 185 | if tablemode#table#IsRow(a:line) 186 | let line = tablemode#spreadsheet#GetFirstRow(a:line) 187 | let row_nr = 0 188 | 189 | while tablemode#table#IsTable(line + 1) 190 | if tablemode#table#IsRow(line) 191 | let row_nr += 1 192 | if a:row ==# row_nr | break | endif 193 | endif 194 | let line += 1 195 | endwhile 196 | 197 | return line 198 | endif 199 | endfunction 200 | 201 | function! tablemode#spreadsheet#RowNr(line) "{{{2 202 | let line = tablemode#utils#line(a:line) 203 | 204 | let rowNr = 0 205 | while !tablemode#table#IsHeader(line) && tablemode#table#IsTable(line) 206 | if tablemode#table#IsRow(line) | let rowNr += 1 | endif 207 | let line -= 1 208 | endwhile 209 | 210 | return rowNr 211 | endfunction 212 | 213 | function! tablemode#spreadsheet#RowCount(line) "{{{2 214 | let line = tablemode#utils#line(a:line) 215 | 216 | let [tline, totalRowCount] = [line, 0] 217 | while !tablemode#table#IsHeader(tline) && tablemode#table#IsTable(tline) 218 | if tablemode#table#IsRow(tline) | let totalRowCount += 1 | endif 219 | let tline -= 1 220 | endwhile 221 | 222 | let tline = line + 1 223 | while !tablemode#table#IsHeader(tline) && tablemode#table#IsTable(tline) 224 | if tablemode#table#IsRow(tline) | let totalRowCount += 1 | endif 225 | let tline += 1 226 | endwhile 227 | 228 | return totalRowCount 229 | endfunction 230 | 231 | function! tablemode#spreadsheet#ColumnNr(pos) "{{{2 232 | let pos = [] 233 | if type(a:pos) == type('') 234 | let pos = [line(a:pos), col(a:pos)] 235 | elseif type(a:pos) == type([]) 236 | let pos = a:pos 237 | else 238 | return 0 239 | endif 240 | let row_start = stridx(getline(pos[0]), g:table_mode_separator) 241 | return tablemode#utils#SeparatorCount(getline(pos[0])[row_start:pos[1]-2]) 242 | endfunction 243 | 244 | function! tablemode#spreadsheet#ColumnCount(line) "{{{2 245 | return tablemode#utils#SeparatorCount(getline(tablemode#utils#line(a:line))) - 1 246 | endfunction 247 | 248 | function! tablemode#spreadsheet#IsFirstCell() "{{{2 249 | return tablemode#spreadsheet#ColumnNr('.') ==# 1 250 | endfunction 251 | 252 | function! tablemode#spreadsheet#IsLastCell() "{{{2 253 | return tablemode#spreadsheet#ColumnNr('.') ==# tablemode#spreadsheet#ColumnCount('.') 254 | endfunction 255 | 256 | function! tablemode#spreadsheet#MoveToStartOfCell() "{{{2 257 | if getline('.')[col('.')-1] !=# g:table_mode_separator || tablemode#spreadsheet#IsLastCell() 258 | call search(g:table_mode_escaped_separator_regex, 'b', line('.')) 259 | endif 260 | normal! 2l 261 | endfunction 262 | 263 | function! tablemode#spreadsheet#MoveToEndOfCell() "{{{2 264 | call search(g:table_mode_escaped_separator_regex, 'z', line('.')) 265 | normal! 2h 266 | endfunction 267 | 268 | function! tablemode#spreadsheet#DeleteColumn() "{{{2 269 | if tablemode#table#IsRow('.') 270 | for i in range(v:count1) 271 | call tablemode#spreadsheet#MoveToStartOfCell() 272 | call tablemode#spreadsheet#MoveToFirstRowOrHeader() 273 | silent! execute "normal! h\" 274 | call tablemode#spreadsheet#MoveToEndOfCell() 275 | normal! 2l 276 | call tablemode#spreadsheet#MoveToLastRow() 277 | normal! d 278 | endfor 279 | 280 | call tablemode#table#Realign('.') 281 | endif 282 | endfunction 283 | 284 | function! tablemode#spreadsheet#DeleteRow() "{{{2 285 | if tablemode#table#IsRow('.') 286 | for i in range(v:count1) 287 | if tablemode#table#IsRow('.') 288 | normal! dd 289 | endif 290 | 291 | if !tablemode#table#IsRow('.') 292 | normal! k 293 | endif 294 | endfor 295 | 296 | call tablemode#table#Realign('.') 297 | endif 298 | endfunction 299 | 300 | function! tablemode#spreadsheet#InsertColumn(after) "{{{2 301 | if tablemode#table#IsRow('.') 302 | let quantity = v:count1 303 | 304 | call tablemode#spreadsheet#MoveToFirstRowOrHeader() 305 | call tablemode#spreadsheet#MoveToStartOfCell() 306 | if a:after 307 | call tablemode#spreadsheet#MoveToEndOfCell() 308 | normal! 3l 309 | endif 310 | execute "normal! h\" 311 | call tablemode#spreadsheet#MoveToLastRow() 312 | normal! y 313 | 314 | let corner = tablemode#utils#get_buffer_or_global_option('table_mode_corner') 315 | if a:after 316 | let cell_line = g:table_mode_separator.' ' 317 | let header_line = corner.g:table_mode_fillchar.g:table_mode_fillchar 318 | else 319 | let cell_line = ' '.g:table_mode_separator 320 | let header_line = g:table_mode_fillchar.g:table_mode_fillchar.corner 321 | endif 322 | let cell_line = escape(cell_line, '\&') 323 | let header_line = escape(header_line, '\&') 324 | 325 | " This transforms the character column before or after the column separator 326 | " into a new column with separator. 327 | " This requires, that 328 | " g:table_mode_separator != g:table_mode_fillchar 329 | " && g:table_mode_separator != g:table_mode_header_fillchar 330 | " && g:table_mode_separator != g:table_mode_align_char 331 | call setreg( 332 | \ '"', 333 | \ substitute( 334 | \ substitute(@", ' ', cell_line, 'g'), 335 | \ '\V\C'.escape(g:table_mode_fillchar, '\') 336 | \ .'\|'.escape(g:table_mode_header_fillchar, '\') 337 | \ .'\|'.escape(g:table_mode_align_char, '\'), 338 | \ header_line, 339 | \ 'g'), 340 | \ 'b') 341 | 342 | if a:after 343 | execute "normal! ".quantity."pl" 344 | else 345 | execute "normal! ".quantity."P" 346 | endif 347 | 348 | call tablemode#table#Realign('.') 349 | startinsert 350 | endif 351 | endfunction 352 | 353 | function! tablemode#spreadsheet#Min(range, ...) abort "{{{2 354 | let args = copy(a:000) 355 | call insert(args, a:range) 356 | return s:Min(call('tablemode#spreadsheet#cell#GetCellRange', args)) 357 | endfunction 358 | 359 | function! tablemode#spreadsheet#Max(range, ...) abort "{{{2 360 | let args = copy(a:000) 361 | call insert(args, a:range) 362 | return s:Max(call('tablemode#spreadsheet#cell#GetCellRange', args)) 363 | endfunction 364 | 365 | function! tablemode#spreadsheet#CountE(range, ...) abort "{{{2 366 | let args = copy(a:000) 367 | call insert(args, a:range) 368 | return s:CountE(call('tablemode#spreadsheet#cell#GetCellRange', args)) 369 | endfunction 370 | 371 | function! tablemode#spreadsheet#CountNE(range, ...) abort "{{{2 372 | let args = copy(a:000) 373 | call insert(args, a:range) 374 | return s:CountNE(call('tablemode#spreadsheet#cell#GetCellRange', args)) 375 | endfunction 376 | 377 | function! tablemode#spreadsheet#PercentE(range, ...) abort "{{{2 378 | let args = copy(a:000) 379 | call insert(args, a:range) 380 | return s:PercentE(call('tablemode#spreadsheet#cell#GetCellRange', args)) 381 | endfunction 382 | 383 | function! tablemode#spreadsheet#PercentNE(range, ...) abort "{{{2 384 | let args = copy(a:000) 385 | call insert(args, a:range) 386 | return s:PercentNE(call('tablemode#spreadsheet#cell#GetCellRange', args)) 387 | endfunction 388 | 389 | function! tablemode#spreadsheet#Sum(range, ...) abort "{{{2 390 | let args = copy(a:000) 391 | call insert(args, a:range) 392 | return s:Sum(call('tablemode#spreadsheet#cell#GetCellRange', args)) 393 | endfunction 394 | 395 | function! tablemode#spreadsheet#Average(range, ...) abort "{{{2 396 | let args = copy(a:000) 397 | call insert(args, a:range) 398 | return s:Average(call('tablemode#spreadsheet#cell#GetCellRange', args)) 399 | endfunction 400 | 401 | function! tablemode#spreadsheet#AverageNE(range, ...) abort "{{{2 402 | let args = copy(a:000) 403 | call insert(args, a:range) 404 | return s:AverageNE(call('tablemode#spreadsheet#cell#GetCellRange', args)) 405 | endfunction 406 | 407 | function! tablemode#spreadsheet#Sort(bang, ...) range "{{{2 408 | if exists('*getcurpos') 409 | let col = getcurpos()[4] " curswant 410 | else 411 | let col = col('.') 412 | endif 413 | let opts = a:0 ? a:1 : '' 414 | let bang = a:bang ? '!' : '' 415 | if a:firstline == a:lastline 416 | let [firstRow, lastRow] = [tablemode#spreadsheet#GetFirstRow('.'), tablemode#spreadsheet#GetLastRow('.')] 417 | else 418 | let [firstRow, lastRow] = [a:firstline, a:lastline] 419 | endif 420 | call tablemode#spreadsheet#MoveToStartOfCell() 421 | exec ':undojoin | '.firstRow.','.lastRow . 'sort'.bang opts '/.*\%'.col.'v/' 422 | endfunction 423 | 424 | function! tablemode#spreadsheet#EchoCell() 425 | if tablemode#table#IsRow('.') 426 | echomsg '$' . tablemode#spreadsheet#RowNr('.') . ',' . tablemode#spreadsheet#ColumnNr('.') 427 | endif 428 | endfunction 429 | -------------------------------------------------------------------------------- /autoload/tablemode/spreadsheet/cell.vim: -------------------------------------------------------------------------------- 1 | " Private Functions {{{1 2 | " function! s:ParseRange(range, ...) {{{2 3 | " range: A string representing range of cells. 4 | " - Can be row1:row2 for values in the current columns in those rows. 5 | " - Can be row1,col1:row2,col2 for range between row1,col1 till 6 | " row2,col2. 7 | function! s:ParseRange(range, ...) 8 | if a:0 < 1 9 | let default_col = tablemode#spreadsheet#ColumnNr('.') 10 | elseif a:0 < 2 11 | let default_col = a:1 12 | endif 13 | 14 | if type(a:range) != type('') 15 | let range = string(a:range) 16 | else 17 | let range = a:range 18 | endif 19 | 20 | let [rowcol1, rowcol2] = split(range, ':') 21 | let [rcs1, rcs2] = [map(split(rowcol1, ','), 'str2nr(v:val)'), map(split(rowcol2, ','), 'str2nr(v:val)')] 22 | 23 | if len(rcs1) == 2 24 | let [row1, col1] = rcs1 25 | else 26 | let [row1, col1] = [rcs1[0], default_col] 27 | endif 28 | 29 | if len(rcs2) == 2 30 | let [row2, col2] = rcs2 31 | else 32 | let [row2, col2] = [rcs2[0], default_col] 33 | endif 34 | 35 | return [row1, col1, row2, col2] 36 | endfunction 37 | 38 | 39 | " Public Functions {{{1 40 | " function! tablemode#spreadsheet#cell#GetCells() - Function to get values of cells in a table {{{2 41 | " tablemode#spreadsheet#GetCells(row) - Get values of all cells in a row as a List. 42 | " tablemode#spreadsheet#GetCells(0, col) - Get values of all cells in a column as a List. 43 | " tablemode#spreadsheet#GetCells(row, col) - Get the value of table cell by given row, col. 44 | function! tablemode#spreadsheet#cell#GetCells(line, ...) abort 45 | let line = tablemode#utils#line(a:line) 46 | 47 | if tablemode#table#IsRow(line) 48 | if a:0 < 1 49 | let [row, colm] = [line, 0] 50 | elseif a:0 < 2 51 | let [row, colm] = [a:1, 0] 52 | elseif a:0 < 3 53 | let [row, colm] = a:000 54 | endif 55 | 56 | let first_row = tablemode#spreadsheet#GetFirstRow(line) 57 | let last_row = tablemode#spreadsheet#GetLastRow(line) 58 | if row == 0 59 | let values = [] 60 | let line = first_row 61 | while tablemode#table#IsTable(line) 62 | if tablemode#table#IsRow(line) 63 | let row_line = getline(line)[stridx(getline(line), g:table_mode_separator):strridx(getline(line), g:table_mode_separator)] 64 | call add(values, tablemode#utils#strip(get(split(row_line, g:table_mode_separator), colm>0?colm-1:colm, ''))) 65 | endif 66 | let line += 1 67 | endwhile 68 | return values 69 | else 70 | let row_nr = 0 71 | let row_diff = row > 0 ? 1 : -1 72 | let line = row > 0 ? first_row : last_row 73 | while tablemode#table#IsTable(line) 74 | if tablemode#table#IsRow(line) 75 | let row_nr += row_diff 76 | if row ==# row_nr | break | endif 77 | endif 78 | let line += row_diff 79 | endwhile 80 | 81 | let row_line = getline(line)[stridx(getline(line), g:table_mode_separator):strridx(getline(line), g:table_mode_separator)] 82 | if colm == 0 83 | return map(split(row_line, g:table_mode_separator), 'tablemode#utils#strip(v:val)') 84 | else 85 | let split_line = split(row_line, g:table_mode_separator) 86 | return tablemode#utils#strip(get(split(row_line, g:table_mode_separator), colm>0?colm-1:colm, '')) 87 | endif 88 | endif 89 | endif 90 | endfunction 91 | 92 | function! tablemode#spreadsheet#cell#GetCell(...) "{{{2 93 | if a:0 == 0 94 | let [row, colm] = [tablemode#spreadsheet#RowNr('.'), tablemode#spreadsheet#ColumnNr('.')] 95 | elseif a:0 == 2 96 | let [row, colm] = [a:1, a:2] 97 | endif 98 | 99 | return tablemode#spreadsheet#cell#GetCells('.', row, colm) 100 | endfunction 101 | 102 | function! tablemode#spreadsheet#cell#GetRow(row, ...) abort "{{{2 103 | let line = a:0 ? a:1 : '.' 104 | return tablemode#spreadsheet#cell#GetCells(line, a:row) 105 | endfunction 106 | 107 | function! tablemode#spreadsheet#cell#GetRowColumn(col, ...) abort "{{{2 108 | let line = a:0 ? a:1 : '.' 109 | let row = tablemode#spreadsheet#RowNr('.') 110 | return tablemode#spreadsheet#cell#GetCells(line, row, a:col) 111 | endfunction 112 | 113 | function! tablemode#spreadsheet#cell#GetColumn(col, ...) abort "{{{2 114 | let line = a:0 ? a:1 : '.' 115 | return tablemode#spreadsheet#cell#GetCells(line, 0, a:col) 116 | endfunction 117 | 118 | function! tablemode#spreadsheet#cell#GetColumnRow(row, ...) abort "{{{2 119 | let line = a:0 ? a:1 : '.' 120 | let col = tablemode#spreadsheet#ColumnNr('.') 121 | return tablemode#spreadsheet#cell#GetCells(line, a:row, col) 122 | endfunction 123 | 124 | function! tablemode#spreadsheet#cell#GetCellRange(range, ...) abort "{{{2 125 | if a:0 < 1 126 | let [line, colm] = ['.', tablemode#spreadsheet#ColumnNr('.')] 127 | elseif a:0 < 2 128 | let [line, colm] = [a:1, tablemode#spreadsheet#ColumnNr('.')] 129 | elseif a:0 < 3 130 | let [line, colm] = [a:1, a:2] 131 | else 132 | call tablemode#utils#throw('Invalid Range') 133 | endif 134 | 135 | let values = [] 136 | 137 | if tablemode#table#IsRow(line) 138 | let [row1, col1, row2, col2] = s:ParseRange(a:range, colm) 139 | 140 | if row1 == row2 141 | if col1 == col2 142 | call add(values, tablemode#spreadsheet#cell#GetCells(line, row1, col1)) 143 | else 144 | let values = tablemode#spreadsheet#cell#GetRow(row1, line)[(col1-1):(col2-1)] 145 | endif 146 | else 147 | if col1 == col2 148 | let values = tablemode#spreadsheet#cell#GetColumn(col1, line)[(row1-1):(row2-1)] 149 | else 150 | let tcol = col1 151 | while tcol <= col2 152 | call add(values, tablemode#spreadsheet#cell#GetColumn(tcol, line)[(row1-1):(row2-1)]) 153 | let tcol += 1 154 | endwhile 155 | endif 156 | endif 157 | endif 158 | 159 | return values 160 | endfunction 161 | 162 | function! tablemode#spreadsheet#cell#SetCell(val, ...) "{{{2 163 | if a:0 == 0 164 | let [line, row, colm] = ['.', tablemode#spreadsheet#RowNr('.'), tablemode#spreadsheet#ColumnNr('.')] 165 | elseif a:0 == 2 166 | let [line, row, colm] = ['.', a:1, a:2] 167 | elseif a:0 == 3 168 | let [line, row, colm] = a:000 169 | endif 170 | 171 | " Account for negative values to reference from relatively from the last 172 | if row < 0 | let row = tablemode#spreadsheet#RowCount(line) + row + 1 | endif 173 | if colm < 0 | let colm = tablemode#spreadsheet#ColumnCount(line) + colm + 1 | endif 174 | 175 | if tablemode#table#IsRow(line) 176 | let line = tablemode#spreadsheet#LineNr(line, row) 177 | let line_val = getline(line) 178 | let cstartexpr = tablemode#table#StartCommentExpr() 179 | let values = split(getline(line)[stridx(line_val, g:table_mode_separator):strridx(line_val, g:table_mode_separator)], g:table_mode_separator) 180 | if len(values) < colm | return | endif 181 | let values[colm-1] = a:val 182 | let line_value = g:table_mode_separator . join(values, g:table_mode_separator) . g:table_mode_separator 183 | if tablemode#utils#strlen(cstartexpr) > 0 && line_val =~# cstartexpr 184 | let sce = matchstr(line_val, tablemode#table#StartCommentExpr()) 185 | let ece = matchstr(line_val, tablemode#table#EndCommentExpr()) 186 | let line_value = sce . line_value . ece 187 | endif 188 | call setline(line, line_value) 189 | call tablemode#table#Realign(line) 190 | endif 191 | endfunction 192 | function! tablemode#spreadsheet#cell#TextObject(inner) "{{{2 193 | if tablemode#table#IsRow('.') 194 | call tablemode#spreadsheet#MoveToStartOfCell() 195 | if a:inner 196 | normal! v 197 | call search('[^' . g:table_mode_separator . ']\ze\s*' . g:table_mode_separator) 198 | else 199 | execute 'normal! vf' . g:table_mode_separator . 'l' 200 | endif 201 | endif 202 | endfunction 203 | function! tablemode#spreadsheet#cell#Motion(direction, ...) "{{{2 204 | let l:count = a:0 ? a:1 : (v:count + 1) 205 | if tablemode#table#IsRow('.') 206 | for ii in range(l:count) 207 | if a:direction ==# 'l' 208 | if tablemode#spreadsheet#IsLastCell() 209 | if !tablemode#table#IsRow(line('.') + 1) && (tablemode#table#IsBorder(line('.') + 1) && !tablemode#table#IsRow(line('.') + 2)) 210 | return 211 | endif 212 | call tablemode#spreadsheet#cell#Motion('j', 1) 213 | normal! 0 214 | endif 215 | 216 | " If line starts with g:table_mode_separator 217 | if getline('.')[col('.')-1] ==# g:table_mode_separator 218 | normal! 2l 219 | else 220 | execute 'normal! f' . g:table_mode_separator . '2l' 221 | endif 222 | elseif a:direction ==# 'h' 223 | if tablemode#spreadsheet#IsFirstCell() 224 | if !tablemode#table#IsRow(line('.') - 1) && (tablemode#table#IsBorder(line('.') - 1) && !tablemode#table#IsRow(line('.') - 2)) 225 | return 226 | endif 227 | call tablemode#spreadsheet#cell#Motion('k', 1) 228 | normal! $ 229 | endif 230 | 231 | " If line ends with g:table_mode_separator 232 | if getline('.')[col('.')-1] ==# g:table_mode_separator 233 | execute 'normal! F' . g:table_mode_separator . '2l' 234 | else 235 | execute 'normal! 2F' . g:table_mode_separator . '2l' 236 | endif 237 | elseif a:direction ==# 'j' 238 | if tablemode#table#IsRow(line('.') + 1) 239 | " execute 'normal! ' . 1 . 'j' 240 | normal! j 241 | elseif tablemode#table#IsBorder(line('.') + 1) && tablemode#table#IsRow(line('.') + 2) 242 | " execute 'normal! ' . 2 . 'j' 243 | normal! 2j 244 | endif 245 | elseif a:direction ==# 'k' 246 | if tablemode#table#IsRow(line('.') - 1) 247 | " execute 'normal! ' . 1 . 'k' 248 | normal! k 249 | elseif tablemode#table#IsBorder(line('.') - 1) && tablemode#table#IsRow(line('.') - 2) 250 | " execute 'normal! ' . (1 + 1) . 'k' 251 | normal! 2k 252 | endif 253 | endif 254 | endfor 255 | endif 256 | endfunction 257 | 258 | " vim: sw=2 sts=2 fdl=0 fdm=marker 259 | -------------------------------------------------------------------------------- /autoload/tablemode/spreadsheet/formula.vim: -------------------------------------------------------------------------------- 1 | " Private Functions {{{1 2 | function! s:IsFormulaLine(line) "{{{2 3 | return getline(a:line) =~# 'tmf: ' 4 | endfunction 5 | 6 | function! s:IsHTMLComment(line) "{{{2 7 | return !s:IsFormulaLine(a:line) && getline(a:line) =~# '^\s*