mirror of
https://github.com/gryf/.vim.git
synced 2025-12-17 19:40:29 +01:00
556 lines
14 KiB
VimL
556 lines
14 KiB
VimL
" vim:tabstop=2:shiftwidth=2:expandtab:foldmethod=marker:textwidth=79
|
|
" Vimwiki autoload plugin file
|
|
" Todo lists related stuff here.
|
|
" Author: Maxim Kim <habamax@gmail.com>
|
|
" 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 = "\<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 "}}}
|