" vim:tabstop=2:shiftwidth=2:expandtab:foldmethod=marker:textwidth=79 " Vimwiki autoload plugin file " Todo lists related stuff here. " Author: Maxim Kim " Home: http://code.google.com/p/vimwiki/ if exists("g:loaded_vimwiki_list_auto") || &cp finish endif let g:loaded_vimwiki_lst_auto = 1 " Script variables {{{ let s:rx_li_box = '\[.\?\]' " }}} " Script functions {{{ " Get unicode string symbol at index function! s:str_idx(str, idx) "{{{ " Unfortunatly vimscript cannot get symbol at index in unicode string such as " '✗○◐●✓' return matchstr(a:str, '\%'.a:idx.'v.') endfunction "}}} " Get checkbox regexp function! s:rx_li_symbol(rate) "{{{ let result = '' if a:rate == 100 let result = s:str_idx(g:vimwiki_listsyms, 5) elseif a:rate == 0 let result = s:str_idx(g:vimwiki_listsyms, 1) elseif a:rate >= 67 let result = s:str_idx(g:vimwiki_listsyms, 4) elseif a:rate >= 34 let result = s:str_idx(g:vimwiki_listsyms, 3) else let result = s:str_idx(g:vimwiki_listsyms, 2) endif return '\['.result.'\]' endfunction "}}} " Get blank checkbox function! s:blank_checkbox() "{{{ return '['.s:str_idx(g:vimwiki_listsyms, 1).'] ' endfunction "}}} " Get regexp of the list item. function! s:rx_list_item() "{{{ return '\('.g:vimwiki_rxListBullet.'\|'.g:vimwiki_rxListNumber.'\)' endfunction "}}} " Get regexp of the list item with checkbox. function! s:rx_cb_list_item() "{{{ return s:rx_list_item().'\s*\zs\[.\?\]' endfunction "}}} " Get level of the list item. function! s:get_level(lnum) "{{{ if VimwikiGet('syntax') == 'media' let level = vimwiki#u#count_first_sym(getline(a:lnum)) else let level = indent(a:lnum) endif return level endfunction "}}} " Get previous list item. " Returns: line number or 0. function! s:prev_list_item(lnum) "{{{ let c_lnum = a:lnum - 1 while c_lnum >= 1 let line = getline(c_lnum) if line =~ s:rx_list_item() return c_lnum endif if line =~ '^\s*$' return 0 endif let c_lnum -= 1 endwhile return 0 endfunction "}}} " Get next list item in the list. " Returns: line number or 0. function! s:next_list_item(lnum) "{{{ let c_lnum = a:lnum + 1 while c_lnum <= line('$') let line = getline(c_lnum) if line =~ s:rx_list_item() return c_lnum endif if line =~ '^\s*$' return 0 endif let c_lnum += 1 endwhile return 0 endfunction "}}} " Find next list item in the buffer. " Returns: line number or 0. function! s:find_next_list_item(lnum) "{{{ let c_lnum = a:lnum + 1 while c_lnum <= line('$') let line = getline(c_lnum) if line =~ s:rx_list_item() return c_lnum endif let c_lnum += 1 endwhile return 0 endfunction "}}} " Set state of the list item on line number "lnum" to [ ] or [x] function! s:set_state(lnum, rate) "{{{ let line = getline(a:lnum) let state = s:rx_li_symbol(a:rate) let line = substitute(line, s:rx_li_box, state, '') call setline(a:lnum, line) endfunction "}}} " Get state of the list item on line number "lnum" function! s:get_state(lnum) "{{{ let state = 0 let line = getline(a:lnum) let opt = matchstr(line, s:rx_cb_list_item()) if opt =~ s:rx_li_symbol(100) let state = 100 elseif opt =~ s:rx_li_symbol(0) let state = 0 elseif opt =~ s:rx_li_symbol(25) let state = 25 elseif opt =~ s:rx_li_symbol(50) let state = 50 elseif opt =~ s:rx_li_symbol(75) let state = 75 endif return state endfunction "}}} " Returns 1 if there is checkbox on a list item, 0 otherwise. function! s:is_cb_list_item(lnum) "{{{ return getline(a:lnum) =~ s:rx_cb_list_item() endfunction "}}} " Returns start line number of list item, 0 if it is not a list. function! s:is_list_item(lnum) "{{{ let c_lnum = a:lnum while c_lnum >= 1 let line = getline(c_lnum) if line =~ s:rx_list_item() return c_lnum endif if line =~ '^\s*$' return 0 endif if indent(c_lnum) > indent(a:lnum) return 0 endif let c_lnum -= 1 endwhile return 0 endfunction "}}} " Returns char column of checkbox. Used in parent/child checks. function! s:get_li_pos(lnum) "{{{ return stridx(getline(a:lnum), '[') endfunction "}}} " Returns list of line numbers of parent and all its child items. function! s:get_child_items(lnum) "{{{ let result = [] let lnum = a:lnum let p_pos = s:get_level(lnum) " add parent call add(result, lnum) let lnum = s:next_list_item(lnum) while lnum != 0 && s:is_list_item(lnum) && s:get_level(lnum) > p_pos call add(result, lnum) let lnum = s:next_list_item(lnum) endwhile return result endfunction "}}} " Returns list of line numbers of all items of the same level. function! s:get_sibling_items(lnum) "{{{ let result = [] let lnum = a:lnum let ind = s:get_level(lnum) while lnum != 0 && s:get_level(lnum) >= ind if s:get_level(lnum) == ind && s:is_cb_list_item(lnum) call add(result, lnum) endif let lnum = s:next_list_item(lnum) endwhile let lnum = s:prev_list_item(a:lnum) while lnum != 0 && s:get_level(lnum) >= ind if s:get_level(lnum) == ind && s:is_cb_list_item(lnum) call add(result, lnum) endif let lnum = s:prev_list_item(lnum) endwhile return result endfunction "}}} " Returns line number of the parent of lnum item function! s:get_parent_item(lnum) "{{{ let lnum = a:lnum let ind = s:get_level(lnum) let lnum = s:prev_list_item(lnum) while lnum != 0 && s:is_list_item(lnum) && s:get_level(lnum) >= ind let lnum = s:prev_list_item(lnum) endwhile if s:is_cb_list_item(lnum) return lnum else return a:lnum endif endfunction "}}} " Creates checkbox in a list item. function! s:create_cb_list_item(lnum) "{{{ let line = getline(a:lnum) let m = matchstr(line, s:rx_list_item()) if m != '' let li_content = substitute(strpart(line, len(m)), '^\s*', '', '') let line = substitute(m, '\s*$', ' ', '').s:blank_checkbox().li_content call setline(a:lnum, line) endif endfunction "}}} " Tells if all of the sibling list items are checked or not. function! s:all_siblings_checked(lnum) "{{{ let result = 0 let cnt = 0 let siblings = s:get_sibling_items(a:lnum) for lnum in siblings let cnt += s:get_state(lnum) endfor let result = cnt/len(siblings) return result endfunction "}}} " Creates checkbox on a list item if there is no one. function! s:TLI_create_checkbox(lnum) "{{{ if a:lnum && !s:is_cb_list_item(a:lnum) if g:vimwiki_auto_checkbox call s:create_cb_list_item(a:lnum) endif return 1 endif return 0 endfunction "}}} " Switch state of the child list items. function! s:TLI_switch_child_state(lnum) "{{{ let current_state = s:get_state(a:lnum) if current_state == 100 let new_state = 0 else let new_state = 100 endif for lnum in s:get_child_items(a:lnum) call s:set_state(lnum, new_state) endfor endfunction "}}} " Switch state of the parent list items. function! s:TLI_switch_parent_state(lnum) "{{{ let c_lnum = a:lnum while s:is_cb_list_item(c_lnum) let parent_lnum = s:get_parent_item(c_lnum) if parent_lnum == c_lnum break endif call s:set_state(parent_lnum, s:all_siblings_checked(c_lnum)) let c_lnum = parent_lnum endwhile endfunction "}}} function! s:TLI_toggle(lnum) "{{{ if !s:TLI_create_checkbox(a:lnum) call s:TLI_switch_child_state(a:lnum) endif call s:TLI_switch_parent_state(a:lnum) endfunction "}}} " Script functions }}} " Toggle list item between [ ] and [X] function! vimwiki#lst#ToggleListItem(line1, line2) "{{{ let line1 = a:line1 let line2 = a:line2 if line1 != line2 && !s:is_list_item(line1) let line1 = s:find_next_list_item(line1) endif let c_lnum = line1 while c_lnum != 0 && c_lnum <= line2 let li_lnum = s:is_list_item(c_lnum) if li_lnum let li_level = s:get_level(li_lnum) if c_lnum == line1 let start_li_level = li_level endif if li_level <= start_li_level call s:TLI_toggle(li_lnum) let start_li_level = li_level endif endif let c_lnum = s:find_next_list_item(c_lnum) endwhile endfunction "}}} function! vimwiki#lst#kbd_cr() "{{{ " This function is heavily relies on proper 'set comments' option. let cr = "\" if getline('.') =~ s:rx_cb_list_item() let cr .= s:blank_checkbox() endif return cr endfunction "}}} function! vimwiki#lst#kbd_oO(cmd) "{{{ " cmd should be 'o' or 'O' let l:count = v:count1 while l:count > 0 let beg_lnum = foldclosed('.') let end_lnum = foldclosedend('.') if end_lnum != -1 && a:cmd ==# 'o' let lnum = end_lnum let line = getline(beg_lnum) else let line = getline('.') let lnum = line('.') endif let m = matchstr(line, s:rx_list_item()) let res = '' if line =~ s:rx_cb_list_item() let res = substitute(m, '\s*$', ' ', '').s:blank_checkbox() elseif line =~ s:rx_list_item() let res = substitute(m, '\s*$', ' ', '') elseif &autoindent || &smartindent let res = matchstr(line, '^\s*') endif if a:cmd ==# 'o' call append(lnum, res) call cursor(lnum + 1, col('$')) else call append(lnum - 1, res) call cursor(lnum, col('$')) endif let l:count -= 1 endwhile startinsert! endfunction "}}} function! vimwiki#lst#default_symbol() "{{{ " TODO: initialize default symbol from syntax/vimwiki_xxx.vim if VimwikiGet('syntax') == 'default' return '-' else return '*' endif endfunction "}}} function vimwiki#lst#get_list_margin() "{{{ if VimwikiGet('list_margin') < 0 return &sw else return VimwikiGet('list_margin') endif endfunction "}}} function s:get_list_sw() "{{{ if VimwikiGet('syntax') == 'media' return 1 else return &sw endif endfunction "}}} function s:get_list_nesting_level(lnum) "{{{ if VimwikiGet('syntax') == 'media' if getline(a:lnum) !~ s:rx_list_item() let level = 0 else let level = vimwiki#u#count_first_sym(getline(a:lnum)) - 1 let level = level < 0 ? 0 : level endif else let level = indent(a:lnum) endif return level endfunction "}}} function s:get_list_indent(lnum) "{{{ if VimwikiGet('syntax') == 'media' return indent(a:lnum) else return 0 endif endfunction "}}} function! s:compose_list_item(n_indent, n_nesting, sym_nest, sym_bullet, li_content, ...) "{{{ if a:0 let sep = a:1 else let sep = '' endif let li_indent = repeat(' ', max([0,a:n_indent])).sep let li_nesting = repeat(a:sym_nest, max([0,a:n_nesting])).sep if len(a:sym_bullet) > 0 let li_bullet = a:sym_bullet.' '.sep else let li_bullet = ''.sep endif return li_indent.li_nesting.li_bullet.a:li_content endfunction "}}} function s:compose_cb_bullet(prev_cb_bullet, sym) "{{{ return a:sym.matchstr(a:prev_cb_bullet, '\S*\zs\s\+.*') endfunction "}}} function! vimwiki#lst#change_level(...) "{{{ let default_sym = vimwiki#lst#default_symbol() let cmd = '>>' let sym = default_sym " parse argument if a:0 if a:1 != '<<' && a:1 != '>>' let cmd = '--' let sym = a:1 else let cmd = a:1 endif endif " is symbol valid if sym.' ' !~ s:rx_cb_list_item() && sym.' ' !~ s:rx_list_item() return endif " parsing setup let lnum = line('.') let line = getline('.') let list_margin = vimwiki#lst#get_list_margin() let list_sw = s:get_list_sw() let n_nesting = s:get_list_nesting_level(lnum) let n_indent = s:get_list_indent(lnum) " remove indent and nesting let li_bullet_and_content = strpart(line, n_nesting + n_indent) " list bullet and checkbox let cb_bullet = matchstr(li_bullet_and_content, s:rx_list_item()). \ matchstr(li_bullet_and_content, s:rx_cb_list_item()) " XXX: it could be not unicode proof --> if checkboxes are set up with unicode syms " content let li_content = strpart(li_bullet_and_content, len(cb_bullet)) " trim let cb_bullet = vimwiki#u#trim(cb_bullet) let li_content = vimwiki#u#trim(li_content) " nesting symbol if VimwikiGet('syntax') == 'media' if len(cb_bullet) > 0 let sym_nest = cb_bullet[0] else let sym_nest = sym endif else let sym_nest = ' ' endif if g:vimwiki_debug echomsg "PARSE: Sw [".list_sw."]" echomsg s:compose_list_item(n_indent, n_nesting, sym_nest, cb_bullet, li_content, '|') endif " change level if cmd == '--' let cb_bullet = s:compose_cb_bullet(cb_bullet, sym) if VimwikiGet('syntax') == 'media' let sym_nest = sym endif elseif cmd == '>>' if cb_bullet == '' let cb_bullet = sym else let n_nesting = n_nesting + list_sw endif elseif cmd == '<<' let n_nesting = n_nesting - list_sw if VimwikiGet('syntax') == 'media' if n_nesting < 0 let cb_bullet = '' endif else if n_nesting < list_margin let cb_bullet = '' endif endif endif let n_nesting = max([0, n_nesting]) if g:vimwiki_debug echomsg "SHIFT:" echomsg s:compose_list_item(n_indent, n_nesting, sym_nest, cb_bullet, li_content, '|') endif " XXX: this is the code that adds the initial indent let add_nesting = VimwikiGet('syntax') != 'media' if n_indent + n_nesting*(add_nesting) < list_margin let n_indent = list_margin - n_nesting*(add_nesting) endif if g:vimwiki_debug echomsg "INDENT:" echomsg s:compose_list_item(n_indent, n_nesting, sym_nest, cb_bullet, li_content, '|') endif let line = s:compose_list_item(n_indent, n_nesting, sym_nest, cb_bullet, li_content) " replace call setline(lnum, line) call cursor(lnum, match(line, '\S') + 1) endfunction "}}}