├── LICENSE ├── README.md ├── autoload └── list2tree.vim └── plugin └── list2tree.vim /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 shinespark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-list2tree 2 | 3 | Convert markdown list format to tree format. 4 | 5 | ![images](https://user-images.githubusercontent.com/12206768/73352415-68fafa80-42d4-11ea-9339-db05f5b40dc3.gif) 6 | 7 | ## Usage 8 | 9 | Write directories / files by markdown list. 10 | 11 | ``` 12 | * . 13 | * dir 14 | * file 15 | * file 16 | * file 17 | * dir 18 | * dir 19 | * file 20 | * file 21 | * file 22 | ``` 23 | 24 | Select target lines by linewise-visual, type `:'<,'>List2Tree` . 25 | 26 | ``` 27 | . 28 | ├── dir 29 | │ ├── file 30 | │ ├── file 31 | │ └── file 32 | ├── dir 33 | │ └── dir 34 | │ ├── file 35 | │ └── file 36 | └── file 37 | ``` 38 | 39 | 40 | If you need to use keymapping, add this vim setting. 41 | 42 | ``` 43 | vnoremap :'<,'>List2Tree 44 | ``` 45 | 46 | ## Install 47 | 48 | ### dein.vim 49 | 50 | ``` 51 | call dein#add('shinespark/vim-list2tree', {'lazy': 1, 'on_cmd': 'List2Tree'}) 52 | ``` 53 | -------------------------------------------------------------------------------- /autoload/list2tree.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpo 2 | set cpo&vim 3 | 4 | let s:INDENT_UNIT = 2 5 | let s:NON_RULE = 0 6 | let s:VERTICAL_RULE = 1 "│ 7 | let s:CONTINUOUS_RULE = 2 "├── 8 | let s:LAST_RULE = 3 "└── 9 | 10 | function! list2tree#make() range 11 | let s:firstline = a:firstline 12 | let s:lastline = a:lastline 13 | 14 | try 15 | let [l:parse_lines_list, l:depths] = list2tree#parse_lines() 16 | catch 17 | return 18 | endtry 19 | 20 | let l:tree = list2tree#make_tree(l:parse_lines_list, l:depths) 21 | 22 | for l:line_number in range(s:firstline, s:lastline) 23 | call setline(l:line_number, l:tree[l:line_number - s:firstline]) 24 | endfor 25 | endfunction 26 | 27 | 28 | function! list2tree#parse_lines() abort 29 | let l:parse_lines_list = [] 30 | let l:depths = [] 31 | 32 | for l:line_number in range(s:firstline, s:lastline) 33 | let l:raw_line_text = getline(l:line_number) 34 | 35 | " get '* ' 36 | let l:match_end = matchend(l:raw_line_text, '^\ *\*\ ', 0) 37 | if l:match_end == -1 38 | echo 'List2Tree: Syntax error. Couldn''t find *.' 39 | return 40 | endif 41 | 42 | let l:line_text = l:raw_line_text[l:match_end:] 43 | if l:match_end % s:INDENT_UNIT != 0 44 | echo 'List2Tree: Indent error. Use ' . s:INDENT_UNIT . ' spaces indent.' 45 | return 46 | endif 47 | 48 | " calculate depth 49 | let l:depth = l:match_end / s:INDENT_UNIT - 1 50 | 51 | call add(l:parse_lines_list, [l:line_number, l:depth, l:line_text]) 52 | call add(l:depths, l:depth) 53 | endfor 54 | 55 | return [l:parse_lines_list, l:depths] 56 | endfunction 57 | 58 | 59 | function! list2tree#make_tree(parse_lines_list, depths) 60 | let l:tree = [] 61 | let l:rules_flag_list = list2tree#make_empty_list(max(a:depths)) 62 | let l:previous_depth = 0 63 | 64 | for [l:absolute_line_number, l:depth, l:original_text] in a:parse_lines_list 65 | let l:text = '' 66 | let l:relative_line_number = l:absolute_line_number - s:firstline + 1 67 | 68 | " set previous depth rules 69 | if l:depth != l:previous_depth && l:previous_depth != 0 70 | if l:depth > l:previous_depth 71 | " fix previous depth rule 72 | if l:rules_flag_list[l:previous_depth - 1] == s:LAST_RULE 73 | let l:rules_flag_list[l:previous_depth - 1] = s:NON_RULE 74 | elseif l:rules_flag_list[l:previous_depth - 1] == s:CONTINUOUS_RULE 75 | let l:rules_flag_list[l:previous_depth - 1] = s:VERTICAL_RULE 76 | endif 77 | else 78 | for l:i in range(l:depth + 1, l:previous_depth) 79 | let l:rules_flag_list[l:i - 1] = s:NON_RULE 80 | endfor 81 | endif 82 | endif 83 | 84 | " set current rule 85 | if l:depth != 0 86 | if l:relative_line_number >= len(a:depths) 87 | let l:is_last_of_same_depth = v:true 88 | else 89 | let l:is_last_of_same_depth = list2tree#is_last_of_same_depth(l:depth, a:depths[l:relative_line_number:]) 90 | endif 91 | 92 | if l:is_last_of_same_depth 93 | let l:rules_flag_list[l:depth - 1] = s:LAST_RULE 94 | else 95 | let l:rules_flag_list[l:depth - 1] = s:CONTINUOUS_RULE 96 | endif 97 | endif 98 | 99 | " join rule strings 100 | let l:text .= list2tree#make_rule_strings(l:rules_flag_list) 101 | 102 | if l:depth != 0 103 | " rstrip 104 | let l:text = substitute(l:text, '^\(.\{-}\)\s*$', '\1', '') . ' ' 105 | else 106 | " strip 107 | let l:text = substitute(l:text, '^\s*\(.\{-}\)\s*$', '\1', '') 108 | endif 109 | 110 | " join original_text 111 | let l:text .= l:original_text 112 | 113 | call add(l:tree, l:text) 114 | let l:previous_depth = l:depth 115 | endfor 116 | 117 | return l:tree 118 | endfunction 119 | 120 | 121 | function! list2tree#make_empty_list(length) 122 | let l:list = [] 123 | 124 | for l:i in range(a:length) 125 | call add(l:list, 0) 126 | endfor 127 | 128 | return l:list 129 | endfunction 130 | 131 | 132 | function! list2tree#make_rule_strings(rules_flag_list) 133 | let l:text = '' 134 | 135 | for l:i in a:rules_flag_list 136 | if l:i == s:VERTICAL_RULE 137 | let l:text .= '│ ' 138 | elseif l:i == s:CONTINUOUS_RULE 139 | let l:text .= '├── ' 140 | elseif l:i == s:LAST_RULE 141 | let l:text .= '└── ' 142 | else 143 | let l:text .= ' ' 144 | endif 145 | endfor 146 | 147 | return l:text 148 | endfunction 149 | 150 | 151 | function! list2tree#is_last_of_same_depth(current_depth, after_depths) 152 | for l:i in a:after_depths 153 | if a:current_depth < l:i 154 | continue 155 | elseif a:current_depth == l:i 156 | return v:false 157 | else 158 | return v:true 159 | endif 160 | endfor 161 | 162 | return v:true 163 | endfunction 164 | 165 | 166 | let &cpo = s:save_cpo 167 | unlet s:save_cpo 168 | -------------------------------------------------------------------------------- /plugin/list2tree.vim: -------------------------------------------------------------------------------- 1 | let s:save_cpo = &cpo 2 | set cpo&vim 3 | 4 | command! -range List2Tree ,call list2tree#make() 5 | 6 | let &cpo = s:save_cpo 7 | unlet s:save_cpo 8 | --------------------------------------------------------------------------------