├── .gitignore ├── README.markdown ├── autoload └── transpose.vim ├── doc └── transpose.txt ├── plugin └── transpose.vim └── screenshots └── demo.png /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | This is a vim plugin that helps you transpose (in the sense of matrix 2 | transposition) blocks of text, words, delimited text, or lines that you can 3 | tokenize with a custom pattern. 4 | 5 | vim-transpose is written in pure VimL (no need for Python nor Perl). 6 | 7 | ![Transposition examples](https://github.com/salsifis/vim-transpose/blob/master/screenshots/demo.png?raw=true) 8 | 9 | Commands 10 | -------- 11 | Five commands are provided: 12 | 13 | * `:Transpose` (for character array transposition), 14 | * `:TransposeWords` (for word array transposition), 15 | * `:TransposeTab` (for tab-separated table transposition), 16 | * `:TransposeCSV` (for general delimited text transposition), and 17 | * `:TransposeInteractive` (for custom transposition). 18 | 19 | An additional variable, `g:transpose_keepindent` controls whether the plugin 20 | should detect indentation of the range. The `:TransposeIndentToggle` command 21 | will toggle this variable. 22 | 23 | Installation 24 | ------------ 25 | 26 | If you have the Pathogen plugin installed or any other plugin manager, then 27 | just copy this filetree into a subfolder of your Bundle folder. 28 | 29 | Otherwise, copy the contents of the `doc/`, `plugin/` and `autoload/` folders 30 | to resp. `~/.vim/doc`, `~/.vim/plugin` and `~/.vim/autoload` respectively. 31 | 32 | Getting started 33 | --------------- 34 | 35 | After having built help tags (run `:Helptags` if you have pathogen installed) 36 | run `:help transpose-tutorial`, and follow the instructions. If you don't have 37 | pathogen installed and don't know how to build the help tags, just open 38 | `doc/transpose.txt` inside vim. 39 | 40 | Example use cases 41 | ----------------- 42 | 43 | This plugin can be useful: 44 | 45 | * When you have tabular data (for example, pasted from a spreadsheet 46 | application) and you need to remove or swap columns: using regular 47 | expressions should be more difficult than using `:TransposeTab`, perform the 48 | operations on lines instead of columns, and `:TransposeTab` again. 49 | * When you need to ensure that tabular data has a consistent number of 50 | columns: since transposition will create missing fields such that it 51 | operates on a rectangular matrix, running twice a `:Transpose`… command will 52 | create the missing fields. 53 | 54 | Development 55 | ----------- 56 | 57 | The main git repository for this plugin is at 58 | `http://github.com/salsifis/vim-transpose` 59 | 60 | License 61 | ------- 62 | 63 | zlib/libpng license. 64 | 65 | Copyright (c) 2012 Benoit Mortgat 66 | 67 | This software is provided 'as-is', without any express or implied warranty. In 68 | no event will the authors be held liable for any damages arising from the use 69 | of this software. 70 | 71 | Permission is granted to anyone to use this software for any purpose, including 72 | commercial applications, and to alter it and redistribute it freely, subject to 73 | the following restrictions: 74 | 75 | 1. The origin of this software must not be misrepresented; you must not claim 76 | that you wrote the original software. If you use this software in a product, 77 | an acknowledgment in the product documentation would be appreciated but is not 78 | required. 79 | 80 | 2. Altered source versions must be plainly marked as such, and must not be 81 | misrepresented as being the original software. 82 | 83 | 3. This notice may not be removed or altered from any source distribution. 84 | -------------------------------------------------------------------------------- /autoload/transpose.vim: -------------------------------------------------------------------------------- 1 | " Vim transposition plugin. 2 | " Plugin author: Benoit Mortgat 3 | " Main git repository: http://github.com/salsifis/vim-transpose 4 | 5 | " this script should be pure ASCII but just in case 6 | scriptencoding utf-8 7 | 8 | " == Main function == {{{ 9 | " This function does the work, all other functions call it. 10 | " 11 | " * first_line , last_line | range of lines on which to operate 12 | " * isp | Input separator pattern (tokenizes input lines) 13 | " | This is a Vim pattern. 14 | " * ofs | Output field separator 15 | " * dfv | Default field value when transposed data has not 16 | " | The same number of fields on each line 17 | function transpose#t(first_line, last_line, isp, ofs, dfv) 18 | let whole_buffer = (a:first_line <= 1 && a:last_line == line('$')) ? 1 : 0 19 | 20 | let cursor_was_at_first_line = (line('.') == a:first_line ? 1 : 0) 21 | let cursor_was_at_last_line = (line('.') == a:last_line ? 1 : 0) 22 | let old_mode = mode() 23 | 24 | let input_lines = getline(a:first_line, a:last_line) 25 | 26 | let indentation = 0 27 | if exists('g:transpose_keepindent') && g:transpose_keepindent > 0 28 | " find the first non-space on each line; the indentation is the 29 | " lowest value found. TODO: accept tabs 30 | let indentation = min(map(copy(input_lines), 'match(v:val,"[^ ]")')) 31 | if indentation > 0 32 | " de-indent each input line 33 | call map(input_lines, 'v:val['.indentation.':]') 34 | endif 35 | endif 36 | 37 | " Split each line into fields according to the input pattern separator 38 | let tokenized_lines = map(input_lines, 'len(v:val)?split(v:val, a:isp, 1):[]') 39 | 40 | " Delete input lines from buffer, to the black hole register 41 | silent! execute a:first_line . ',' . a:last_line . 'd _' 42 | 43 | " There are as many lines in output as number of fields in the line that has 44 | " the greatest amount of fields 45 | let nb_output_lines = max(map(copy(tokenized_lines), 'len(v:val)')) 46 | 47 | " For each output line (line i) take i^th field of each input line. If it does 48 | " not exist, take default value. 49 | for i in range(0, nb_output_lines - 1) 50 | call append( a:first_line - 1 + i 51 | \ , repeat(' ', indentation) 52 | \ . join( map(copy(tokenized_lines) 53 | \ ,'(i < len(v:val)) ? v:val[i] : a:dfv' 54 | \ ) 55 | \ , a:ofs 56 | \ ) 57 | \ ) 58 | endfor 59 | 60 | " If the whole buffer was transposed, there was a moment after deleting 61 | " all input lines, where there was an empty line. Now time to remove it. 62 | if(whole_buffer) 63 | execute '$d _' 64 | else 65 | execute '' . a:first_line . 'mark <' 66 | execute '' . (a:first_line + nb_output_lines - 1) . 'mark >' 67 | endif 68 | 69 | if cursor_was_at_first_line == 1 70 | execute a:first_line 71 | elseif cursor_was_at_last_line == 1 72 | execute '' . (a:first_line + nb_output_lines - 1) 73 | endif 74 | 75 | if old_mode ==? 'v' 76 | execute 'normal gv' 77 | endif 78 | 79 | endfunction " }}} 80 | 81 | " == Specialized function: Array of characters == {{{ 82 | function transpose#block(first_line, last_line) 83 | " Input separator pattern: any zero-width match between two chars => .\zs\ze. 84 | " Output separator: empty 85 | " Default field value: a space. 86 | call transpose#t(a:first_line, a:last_line, '.\zs\ze.', '', ' ') 87 | endfunction " }}} 88 | 89 | " == Specialized function: Words (separated by whitespace) {{{ 90 | function transpose#words(first_line, last_line, ...) 91 | " If an argument is provided, this is the default field value 92 | if a:0 > 1 93 | throw 'Only one additional argument allowed (default field value)' 94 | endif 95 | let dfv = (a:0 > 0) ? a:1 : '?' 96 | " Input separator pattern: whitespace 97 | " Output separator: one space 98 | " Default field value: nonempty. 99 | call transpose#t(a:first_line, a:last_line, '\s\+', ' ', dfv) 100 | endfunction " }}} 101 | 102 | " == Specialized function: Tabs (separated by tabulation character) {{{ 103 | function transpose#tab(first_line, last_line) 104 | " Input separator pattern: single tab 105 | " Output separator: tab 106 | " Default field value: empty 107 | call transpose#t(a:first_line, a:last_line, "\x09", "\x09", '') 108 | endfunction " }}} 109 | 110 | " == Specialized function: Separated fields == {{{ 111 | " There are two special sequences of characters in the input: 112 | " * The separator (typically semicolon) 113 | " * The delimiter; when fields contain the separator their contents can be 114 | " surrounded with two delimiters. If they must contain the delimiter itself, 115 | " it has to be doubled. 116 | " If the delimiter is empty, then no field can contain the separator. 117 | " 118 | " Example: Separator and delimiter being resp ; and ' 119 | " 'a;b';c;'d;e''f' 120 | " Contains three fields : a;b | c | d;e'f 121 | function transpose#delimited(first_line, last_line, separator, delimiter) 122 | let separator = escape(a:separator, '\*[].^$') 123 | let delimiter = escape(a:delimiter, '\*[].^$') 124 | 125 | " Vim pattern that matches a field: 126 | " Either: 127 | " * It starts with the delimiter 128 | " * It globs as many non-delimiters and double-delimiters as it can 129 | " * It ends with the delimiter 130 | " * There is no delimiter afterwards 131 | " Or: 132 | " * It consists of characters that are neither the delimiter nor the 133 | " separator 134 | " 135 | " And there is always a separator afterwards. 136 | " 137 | " We put \zs in the pattern to only match the separator. 138 | 139 | if delimiter !=# '' 140 | let isp = '\%(' 141 | \ . delimiter 142 | \ . '\%([^' . delimiter . ']\|' . delimiter . delimiter . '\)*' 143 | \ . delimiter . '\%(' . delimiter . '\)\@!' 144 | \ . '\|[^' . separator.delimiter . ']*\)\zs' . separator 145 | else 146 | let isp = separator 147 | endif 148 | let ofs = a:separator 149 | let placeholder = '' 150 | call transpose#t(a:first_line, a:last_line, isp, ofs, placeholder) 151 | endfunction " }}} 152 | 153 | " == Specialized function: CSV input == {{{ 154 | " Calls the previous function, with variable number of arguments. Number of 155 | " additional arguments shall not exceed 2. 156 | " 1st argument: separator, default is semicolon 157 | " 2nd argument: delimiter, default is none. 158 | function transpose#csv(first_line, last_line, ...) 159 | if a:0 > 2 160 | throw 'Optional arguments: separator, delimiter. Only 2 allowed.' 161 | endif 162 | let separator = (a:0 > 0) ? a:1 : (exists('g:transpose_csv_default_separator') ? g:transpose_csv_default_separator : ';') 163 | let delimiter = (a:0 > 1) ? a:2 : '' 164 | call transpose#delimited(a:first_line, a:last_line, separator, delimiter) 165 | endfunction " }}} 166 | 167 | " == Interactive way == {{{ 168 | " Asks for parameters 169 | function transpose#interactive(first_line, last_line) 170 | let isp = input('What Vim pattern separates input fields? ') 171 | let ofs = input('What string will join fields in output? ') 172 | let dfv = input('What is default field value? ') 173 | call transpose#t(a:first_line, a:last_line, isp, ofs, dfv) 174 | endfunction " }}} 175 | 176 | " vim: ts=2 sw=2 et tw=80 colorcolumn=+1 fdm=marker fmr={{{,}}} 177 | -------------------------------------------------------------------------------- /doc/transpose.txt: -------------------------------------------------------------------------------- 1 | *transpose.txt* For Vim version 7.3 Last change: 2015 Jul 1 2 | 3 | Plugin author: Benoit Mortgat 4 | *transpose-tutorial* 5 | *transpose* 6 | Welcome to the transpose plugin tutorial. 7 | 8 | Note~ 9 | For your convenience, tab characters appear as > throughout this tutorial, and 10 | trailing spaces as *, except if you don't have `:set modeline` in your vimrc. 11 | 12 | First, ensure that |g:transpose_keepindent| is equal to 1. This should be the 13 | case if you did not mess with the plugin settings. 14 | 15 | =============================================================================== 16 | Basic transposition~ 17 | *:Transpose* 18 | 19 | Visually select the following lines (V2j) and use the |:Transpose| command 20 | (you will see :'<,'>Transpose in the command bar) 21 | Then, Use :Transpose another time on the result. 22 | > 23 | abcd 24 | efghij 25 | klmno 26 | < 27 | As you see, the :Transpose command takes an array of characters, and swaps 28 | lines with columns. When you run :Transpose a second time, you will get the 29 | original text back, except that extra spaces will have been inserted, which 30 | is because :Transpose needs to work on rectangular data 31 | 32 | =============================================================================== 33 | Word transposition~ 34 | *:TransposeWords* 35 | 36 | Visually select the following lines (V3j) and use the |:TransposeWords| 37 | command. 38 | Then, Use :TransposeWords another time on the result. 39 | > 40 | Eeny meenie miny moe 41 | Catch a tiger by the toe 42 | If he hollers let him go 43 | Eeny meenie miny moe 44 | < 45 | You can see that this command will insert interrogation marks when an input 46 | line was not long enough and there is a missing field. If it did not, the 47 | command would not be invertible. 48 | 49 | However you can provide an additional argument to :TransposeWords to set that 50 | default value and override the interrogation mark. Since this command can be 51 | chained with another command, you must escape vertical bars with backslashes 52 | if you need them in the default value. 53 | 54 | =============================================================================== 55 | Tab-separated fields transposition~ 56 | *:TransposeTab* 57 | 58 | Visually select the following lines (V3j) and use the |:TransposeTab| command. 59 | Then, Use :TransposeTab another time on the result. 60 | > 61 | Eeny meenie miny moe 62 | Catch a tiger by the toe 63 | If he hollers let him go 64 | Eeny meenie miny moe 65 | < 66 | This is similar to :TransposeWords, but any tabulation is a separator. 67 | Multiple tabs are considered as many separators. Therefore, there is no need 68 | for a default value. 69 | 70 | =============================================================================== 71 | Separated fields transposition~ 72 | *:TransposeCSV* 73 | 74 | Visually select the following lines (V2j) and use the |:TransposeCSV| command. 75 | Then, Use :TransposeCSV another time on the result. 76 | > 77 | ab;cd 78 | e;f;gh;ij 79 | klm;no 80 | < 81 | This is like the tab-separated case, but the separator is by default the 82 | semicolon. However you can specify the separator of your choice in a first 83 | argument. You can Use :TransposeCSV \ instead of :TransposeTab but as you 84 | can see it is necessary to escape the tabulation character, which is messy. 85 | 86 | Visually select the following lines (V2j) and type > 87 | :TransposeCSV , 88 | 90 | ab,cd 91 | e,f,gh,ij 92 | klm,no 93 | < 94 | You can also provide a second argument, which is the delimiter. In this case: 95 | - A field will be delimited when the first character in the field is the 96 | delimiter. 97 | - When a field is delimited, it must end with the delimiter 98 | - When a field is delimited, it can include the separator 99 | - When a field is delimited, it can include the delimiter, escaped by itself 100 | (like SQL string literals). 101 | 102 | Try to transpose this CSV separated by tab and delimited by apostrophe: you 103 | will have to escape the tab with a backslash in the command line. 104 | :TransposeCSV \ ' 105 | > 106 | 'I''m a field with a tab' I'm a field without tab 107 | No tab here 'There is a tab here' A third field 108 | < 109 | Since this command can be chained with another command, you must escape 110 | vertical bars with backslashes if you need them in the delimiter or separator. 111 | 112 | *g:transpose_csv_default_separator* 113 | 114 | You can override the default separator. If you want it to be a comma by 115 | default, put the following line into your vimrc: 116 | > 117 | let g:transpose_csv_default_separator = ',' 118 | < 119 | =============================================================================== 120 | Interactive transposition~ 121 | *:TransposeInteractive* 122 | 123 | Assume this input 124 | > 125 | A b c 126 | D e f 127 | ghi 128 | < 129 | You want this output: 130 | > 131 | A,D, 132 | b,e,ghi 133 | c,f,xxx 134 | < 135 | It is possible! Basically, your input fields are separated by one or more 136 | tabs, you want to have a table with commas in the output, and nonexistent 137 | fields (the third input line had only two fields) will come as xxx 138 | 139 | Select the input lines and use |:TransposeInteractive|, you will be prompted 140 | for: 141 | - A vim pattern that will be used to separate input fields: type > 142 | 143 | \t\+ 144 | < 145 | - An output separator, type a comma 146 | - A default field value, type xxx 147 | 148 | That's all. 149 | 150 | =============================================================================== 151 | 152 | vim: readonly noexpandtab ft=help modifiable list listchars=tab\:>\ ,trail\:* 153 | -------------------------------------------------------------------------------- /plugin/transpose.vim: -------------------------------------------------------------------------------- 1 | " Vim transposition plugin. 2 | " Plugin author: Benoit Mortgat 3 | " Main git repository: http://github.com/salsifis/vim-transpose 4 | 5 | if exists('s:transpose_plugin_loaded') 6 | finish 7 | endif 8 | let s:transpose_plugin_loaded = 1 9 | 10 | if !exists('g:transpose_keepindent') 11 | let g:transpose_keepindent = 1 12 | endif 13 | 14 | command -range=% -nargs=0 -bar Transpose 15 | \ call transpose#block (, ) 16 | 17 | command -range=% -nargs=? -bar TransposeWords 18 | \ call transpose#words (, , ) 19 | 20 | command -range=% -nargs=0 -bar TransposeTab 21 | \ call transpose#tab (, ) 22 | 23 | command -range=% -nargs=* -bar TransposeCSV 24 | \ call transpose#csv (, , ) 25 | 26 | command -range=% -nargs=0 -bar TransposeInteractive 27 | \ call transpose#interactive(, ) 28 | 29 | command -nargs=0 -bar TransposeIndentToggle 30 | \ let g:transpose_keepindent = 1 - g:transpose_keepindent 31 | 32 | " vim: ts=2 sw=2 et tw=80 colorcolumn=+1 33 | -------------------------------------------------------------------------------- /screenshots/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salsifis/vim-transpose/bdb00d2aeb3bdf9366f01a3bee4fb142d78278f9/screenshots/demo.png --------------------------------------------------------------------------------