You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
14 KiB
VimL
562 lines
14 KiB
VimL
11 years ago
|
" plugin to handle the TaskPaper to-do list format
|
||
|
" Language: Taskpaper (http://hogbaysoftware.com/projects/taskpaper)
|
||
|
" Maintainer: David O'Callaghan <david.ocallaghan@cs.tcd.ie>
|
||
|
" URL: https://github.com/davidoc/taskpaper.vim
|
||
|
" Last Change: 2012-03-07
|
||
|
|
||
|
let s:save_cpo = &cpo
|
||
|
set cpo&vim
|
||
|
|
||
|
function! s:add_delete_tag(tag, value, add)
|
||
|
let cur_line = getline(".")
|
||
|
|
||
|
let tag = " @" . a:tag
|
||
|
if a:value != ''
|
||
|
let tag .= "(" . a:value . ")"
|
||
|
endif
|
||
|
|
||
|
" Add tag
|
||
|
if a:add
|
||
|
let new_line = cur_line . tag
|
||
|
call setline(".", new_line)
|
||
|
return 1
|
||
|
endif
|
||
|
|
||
|
" Delete tag
|
||
|
if cur_line =~# '\V' . tag
|
||
|
if a:value != ''
|
||
|
let new_line = substitute(cur_line, '\V' . tag, "", "g")
|
||
|
else
|
||
|
let new_line = substitute(cur_line, '\V' . tag . '\v(\([^)]*\))?',
|
||
|
\ "", "g")
|
||
|
endif
|
||
|
|
||
|
call setline(".", new_line)
|
||
|
return 1
|
||
|
endif
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#add_tag(tag, ...)
|
||
|
let value = a:0 > 0 ? a:1 : input('Value: ')
|
||
|
return s:add_delete_tag(a:tag, value, 1)
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#delete_tag(tag, ...)
|
||
|
let value = a:0 > 0 ? a:1 : ''
|
||
|
return s:add_delete_tag(a:tag, value, 0)
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#swap_tag(oldtag, newtag)
|
||
|
call taskpaper#delete_tag(a:oldtag)
|
||
|
call taskpaper#add_tag(a:newtag, '')
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#swap_tags(oldtags, newtags)
|
||
|
for oldtag in a:oldtags
|
||
|
call taskpaper#delete_tag(oldtag)
|
||
|
endfor
|
||
|
for newtag in a:newtags
|
||
|
call taskpaper#add_tag(newtag, '')
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#toggle_tag(tag, ...)
|
||
|
if !taskpaper#delete_tag(a:tag, '')
|
||
|
let args = a:0 > 0 ? [a:tag, a:1] : [a:tag]
|
||
|
call call("taskpaper#add_tag", args)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#has_tag(tag)
|
||
|
let cur_line = getline(".")
|
||
|
let m = matchstr(cur_line, '@'.a:tag)
|
||
|
if m != ''
|
||
|
return 1
|
||
|
else
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#cycle_tags(...)
|
||
|
let tags_index = 0
|
||
|
let tag_list = a:000
|
||
|
let tag_added = 0
|
||
|
for tag_name in tag_list
|
||
|
let tags_index = tags_index + 1
|
||
|
if tags_index == len(tag_list)
|
||
|
let tags_index = 0
|
||
|
endif
|
||
|
let has_tag = taskpaper#has_tag(tag_name)
|
||
|
if has_tag == 1
|
||
|
let tag_added = 1
|
||
|
call taskpaper#delete_tag(tag_name)
|
||
|
let new_tag = tag_list[tags_index]
|
||
|
if new_tag != ''
|
||
|
call taskpaper#add_tag(new_tag, '')
|
||
|
endif
|
||
|
break
|
||
|
endif
|
||
|
endfor
|
||
|
if tag_added == 0
|
||
|
call taskpaper#add_tag(tag_list[0], '')
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#update_tag(tag, ...)
|
||
|
call taskpaper#delete_tag(a:tag, '')
|
||
|
let args = a:0 > 0 ? [a:tag, a:1] : [a:tag]
|
||
|
call call("taskpaper#add_tag", args)
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#date()
|
||
|
return strftime(g:task_paper_date_format, localtime())
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#complete_project(lead, cmdline, pos)
|
||
|
let lnum = 1
|
||
|
let list = []
|
||
|
let stack = ['']
|
||
|
let depth = 0
|
||
|
|
||
|
while lnum <= line('$')
|
||
|
let line = getline(lnum)
|
||
|
let ml = matchlist(line, '\v\C^\t*(.+):(\s+\@[^ \t(]+(\([^)]*\))?)*$')
|
||
|
|
||
|
if !empty(ml)
|
||
|
let d = len(matchstr(line, '^\t*'))
|
||
|
|
||
|
while d < depth
|
||
|
call remove(stack, -1)
|
||
|
let depth -= 1
|
||
|
endwhile
|
||
|
|
||
|
while d > depth
|
||
|
call add(stack, '')
|
||
|
let depth += 1
|
||
|
endwhile
|
||
|
|
||
|
let stack[d] = ml[1]
|
||
|
|
||
|
let candidate = join(stack, ':')
|
||
|
if candidate =~ '^' . a:lead
|
||
|
call add(list, join(stack, ':'))
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
let lnum += 1
|
||
|
endwhile
|
||
|
|
||
|
return list
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#go_to_project()
|
||
|
let res = input('Project: ', '', 'customlist,taskpaper#complete_project')
|
||
|
|
||
|
if res != ''
|
||
|
call taskpaper#search_project(split(res, ':'))
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#next_project()
|
||
|
return search('^\t*\zs.\+:\(\s\+@[^\s(]\+\(([^)]*)\)\?\)*$', 'w')
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#previous_project()
|
||
|
return search('^\t*\zs.\+:\(\s\+@[^\s(]\+\(([^)]*)\)\?\)*$', 'bw')
|
||
|
endfunction
|
||
|
|
||
|
function! s:search_project(project, depth, begin, end)
|
||
|
call cursor(a:begin, 1)
|
||
|
return search('\v^\t{' . a:depth . '}\V' . a:project . ':', 'c', a:end)
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#search_project(projects)
|
||
|
if empty(a:projects)
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
let save_pos = getpos('.')
|
||
|
|
||
|
let begin = 1
|
||
|
let end = line('$')
|
||
|
let depth = 0
|
||
|
|
||
|
for project in a:projects
|
||
|
if !s:search_project(project, depth, begin, end)
|
||
|
call setpos('.', save_pos)
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
let begin = line('.')
|
||
|
let end = taskpaper#search_end_of_item(begin)
|
||
|
let depth += 1
|
||
|
endfor
|
||
|
|
||
|
call cursor(begin, 1)
|
||
|
normal! ^
|
||
|
|
||
|
return begin
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#search_end_of_item(...)
|
||
|
let lnum = a:0 > 0 ? a:1 : line('.')
|
||
|
let flags = a:0 > 1 ? a:2 : ''
|
||
|
|
||
|
let depth = len(matchstr(getline(lnum), '^\t*'))
|
||
|
|
||
|
let end = lnum
|
||
|
let lnum += 1
|
||
|
while lnum <= line('$')
|
||
|
let line = getline(lnum)
|
||
|
|
||
|
if line =~ '^\s*$'
|
||
|
" Do nothing
|
||
|
elseif depth < len(matchstr(line, '^\t*'))
|
||
|
let end = lnum
|
||
|
else
|
||
|
break
|
||
|
endif
|
||
|
|
||
|
let lnum += 1
|
||
|
endwhile
|
||
|
|
||
|
if flags !~# 'n'
|
||
|
call cursor(end, 0)
|
||
|
normal! ^
|
||
|
endif
|
||
|
|
||
|
return end
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#delete(...)
|
||
|
let start = a:0 > 0 ? a:1 : line('.')
|
||
|
let reg = a:0 > 1 ? a:2 : '"'
|
||
|
let kill_indent = a:0 > 2 ? a:3 : 0
|
||
|
|
||
|
let reg_save = ''
|
||
|
if kill_indent && reg =~# '\u'
|
||
|
let reg = tolower(reg)
|
||
|
let reg_save = getreg(reg)
|
||
|
endif
|
||
|
|
||
|
let save_fen = &l:foldenable
|
||
|
setlocal nofoldenable
|
||
|
|
||
|
let depth = len(matchstr(getline(start), '^\t*'))
|
||
|
|
||
|
let end = taskpaper#search_end_of_item(start)
|
||
|
silent execute start . ',' . end . 'delete ' . reg
|
||
|
|
||
|
let &l:foldenable = save_fen
|
||
|
|
||
|
if kill_indent
|
||
|
let pat = '\(^\|\n\)\t\{' . depth . '\}'
|
||
|
let content = substitute(getreg(reg), pat, '\1', 'g')
|
||
|
if reg_save != ''
|
||
|
let content = reg_save . content
|
||
|
endif
|
||
|
call setreg(reg, content)
|
||
|
endif
|
||
|
|
||
|
return end - start + 1
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#put(...)
|
||
|
let projects = a:0 > 0 ? a:1 : []
|
||
|
let reg = a:0 > 1 ? a:2 : '"'
|
||
|
let indent = a:0 > 2 ? a:3 : 0
|
||
|
|
||
|
let save_fen = &l:foldenable
|
||
|
setlocal nofoldenable
|
||
|
|
||
|
if !empty(projects) && !taskpaper#search_project(projects)
|
||
|
let &l:foldenable = save_fen
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
if indent > 0
|
||
|
let project_depth = len(matchstr(getline('.'), '^\t*'))
|
||
|
let tabs = repeat("\t", project_depth + indent)
|
||
|
else
|
||
|
let tabs = ''
|
||
|
endif
|
||
|
|
||
|
execute 'put' reg
|
||
|
silent execute "'[,']" . 's/^\ze./' . tabs
|
||
|
|
||
|
let &l:foldenable = save_fen
|
||
|
|
||
|
return line("']") - line("'[") + 1
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#move(projects, ...)
|
||
|
let lnum = a:0 > 0 ? a:1 : line('.')
|
||
|
|
||
|
let save_fen = &l:foldenable
|
||
|
setlocal nofoldenable
|
||
|
|
||
|
if !taskpaper#search_project(a:projects)
|
||
|
let &l:foldenable = save_fen
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
let reg = 'a'
|
||
|
let save_reg = [getreg(reg), getregtype(reg)]
|
||
|
|
||
|
let nlines = taskpaper#delete(lnum, reg, 1)
|
||
|
call taskpaper#put(a:projects, reg, 1)
|
||
|
|
||
|
let &l:foldenable = save_fen
|
||
|
call setreg(reg, save_reg[0], save_reg[1])
|
||
|
if g:task_paper_follow_move == 0
|
||
|
execute lnum
|
||
|
endif
|
||
|
return nlines
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#move_to_project()
|
||
|
let res = input('Project: ', '', 'customlist,taskpaper#complete_project')
|
||
|
call taskpaper#move(split(res, ':'))
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#update_project()
|
||
|
let indent = matchstr(getline("."), '^\t*')
|
||
|
let depth = len(indent)
|
||
|
|
||
|
let projects = []
|
||
|
|
||
|
for linenr in range(line('.'), 1, -1)
|
||
|
let line = getline(linenr)
|
||
|
let ml = matchlist(line, '\v^\t{0,' . depth . '}([^\t:]+):')
|
||
|
if empty(ml)
|
||
|
continue
|
||
|
endif
|
||
|
|
||
|
let project = ml[1]
|
||
|
if project != ""
|
||
|
call add(projects, project)
|
||
|
|
||
|
let indent = matchstr(line, '^\t*')
|
||
|
let depth = len(indent) - 1
|
||
|
|
||
|
if depth < 0
|
||
|
break
|
||
|
endif
|
||
|
endif
|
||
|
endfor
|
||
|
|
||
|
call taskpaper#update_tag('project', join(reverse(projects), ' / '))
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#archive_done()
|
||
|
let archive_start = search('^' . g:task_paper_archive_project . ':', 'cw')
|
||
|
if archive_start == 0
|
||
|
call append('$', g:task_paper_archive_project . ':')
|
||
|
let archive_start = line('$')
|
||
|
let archive_end = 0
|
||
|
else
|
||
|
let archive_end = search('^\S\+:', 'W')
|
||
|
endif
|
||
|
|
||
|
let save_fen = &l:foldenable
|
||
|
let save_reg = [getreg('a'), getregtype('a')]
|
||
|
setlocal nofoldenable
|
||
|
call setreg('a', '')
|
||
|
|
||
|
call cursor(1, 1)
|
||
|
let deleted = 0
|
||
|
|
||
|
while 1
|
||
|
let lnum = search('@done', 'W', archive_start - deleted)
|
||
|
if lnum == 0
|
||
|
break
|
||
|
endif
|
||
|
|
||
|
call taskpaper#update_project()
|
||
|
let deleted += taskpaper#delete(lnum, 'A', 1)
|
||
|
endwhile
|
||
|
|
||
|
if archive_end != 0
|
||
|
call cursor(archive_end, 1)
|
||
|
|
||
|
while 1
|
||
|
let lnum = search('@done', 'W')
|
||
|
if lnum == 0
|
||
|
break
|
||
|
endif
|
||
|
|
||
|
call taskpaper#update_project()
|
||
|
let deleted += taskpaper#delete(lnum, 'A', 1)
|
||
|
endwhile
|
||
|
endif
|
||
|
|
||
|
if deleted != 0
|
||
|
call taskpaper#put([g:task_paper_archive_project], 'a', 1)
|
||
|
else
|
||
|
echo 'No done items.'
|
||
|
endif
|
||
|
|
||
|
let &l:foldenable = save_fen
|
||
|
call setreg('a', save_reg[0], save_reg[1])
|
||
|
|
||
|
return deleted
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#fold(lnum, pat, ipat)
|
||
|
let line = getline(a:lnum)
|
||
|
let level = foldlevel(a:lnum)
|
||
|
|
||
|
if line =~? a:pat && (a:ipat == '' || line !~? a:ipat)
|
||
|
return 0
|
||
|
elseif synIDattr(synID(a:lnum, 1, 1), "name") != 'taskpaperProject'
|
||
|
return 1
|
||
|
elseif level != -1
|
||
|
return level
|
||
|
endif
|
||
|
|
||
|
let depth = len(matchstr(getline(a:lnum), '^\t*'))
|
||
|
|
||
|
for lnum in range(a:lnum + 1, line('$'))
|
||
|
let line = getline(lnum)
|
||
|
|
||
|
if depth >= len(matchstr(line, '^\t*'))
|
||
|
break
|
||
|
endif
|
||
|
|
||
|
if line =~? a:pat && (a:ipat == '' || line !~? a:ipat)
|
||
|
return 0
|
||
|
endif
|
||
|
endfor
|
||
|
return 1
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#search(...)
|
||
|
let pat = a:0 > 0 ? a:1 : input('Search: ')
|
||
|
let ipat = a:0 > 1 ? a:2 : ''
|
||
|
if pat == ''
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
setlocal foldexpr=taskpaper#fold(v:lnum,pat,ipat)
|
||
|
setlocal foldminlines=0 foldtext=''
|
||
|
setlocal foldmethod=expr foldlevel=0 foldenable
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#fold_except_range(lnum, begin, end)
|
||
|
if a:lnum > a:end
|
||
|
return 1
|
||
|
elseif a:lnum >= a:begin
|
||
|
return 0
|
||
|
elseif synIDattr(synID(a:lnum, 1, 1), "name") != 'taskpaperProject'
|
||
|
return 1
|
||
|
elseif level != -1
|
||
|
return level
|
||
|
endif
|
||
|
|
||
|
if a:end <= taskpaper#search_end_of_item(a:lnum, 'n')
|
||
|
return 0
|
||
|
endif
|
||
|
|
||
|
return 1
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#focus_project()
|
||
|
let pos = getpos('.')
|
||
|
|
||
|
normal! $
|
||
|
let begin = taskpaper#previous_project()
|
||
|
if begin == 0
|
||
|
call setpos('.', pos)
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
let end = taskpaper#search_end_of_item(begin, 'n')
|
||
|
|
||
|
" Go to the top level project
|
||
|
while taskpaper#previous_project()
|
||
|
if getline('.') =~ '^[^\t]'
|
||
|
break
|
||
|
endif
|
||
|
endwhile
|
||
|
|
||
|
setlocal foldexpr=taskpaper#fold_except_range(v:lnum,begin,end)
|
||
|
setlocal foldminlines=0 foldtext=''
|
||
|
setlocal foldmethod=expr foldlevel=0 foldenable
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#search_tag(...)
|
||
|
if a:0 > 0
|
||
|
let tag = a:1
|
||
|
else
|
||
|
let cword = expand('<cword>')
|
||
|
let tag = input('Tag: ', cword =~ '@\k\+' ? cword[1:] : '')
|
||
|
endif
|
||
|
|
||
|
if tag != ''
|
||
|
let ipat = (g:task_paper_search_hide_done == 1)?'\<@done\>':''
|
||
|
call taskpaper#search('\<@' . tag . '\>', ipat)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#_fold_projects(lnum)
|
||
|
if synIDattr(synID(a:lnum, 1, 1), "name") != 'taskpaperProject'
|
||
|
return '='
|
||
|
endif
|
||
|
|
||
|
let line = getline(a:lnum)
|
||
|
let depth = len(matchstr(line, '^\t*'))
|
||
|
return '>' . (depth + 1)
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#fold_projects()
|
||
|
setlocal foldexpr=taskpaper#_fold_projects(v:lnum)
|
||
|
setlocal foldminlines=0 foldtext=foldtext()
|
||
|
setlocal foldmethod=expr foldlevel=0 foldenable
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#newline()
|
||
|
let lnum = line('.')
|
||
|
let line = getline('.')
|
||
|
|
||
|
if lnum == 1 || line !~ '^\s*$' ||
|
||
|
\ synIDattr(synID(lnum - 1, 1, 1), "name") != 'taskpaperProject'
|
||
|
return ''
|
||
|
endif
|
||
|
|
||
|
let pline = getline(lnum - 1)
|
||
|
let depth = len(matchstr(pline, '^\t*'))
|
||
|
call setline(lnum, repeat("\t", depth + 1) . '- ')
|
||
|
|
||
|
return "\<End>"
|
||
|
endfunction
|
||
|
|
||
|
|
||
|
function! taskpaper#tag_style(...)
|
||
|
if a:0 > 0
|
||
|
let tag_name = a:1
|
||
|
endif
|
||
|
|
||
|
if a:0 > 1
|
||
|
let tag_style = a:2
|
||
|
let tag_style_name = 'taskpaperAutoStyle_' . tag_name
|
||
|
execute 'syn match' tag_style_name '/\s\zs@'.tag_name.'\(([^)]*)\)\?/'
|
||
|
execute 'hi' tag_style_name tag_style
|
||
|
if version < 508
|
||
|
execute 'hi link' tag_style_name tag_style_name
|
||
|
else
|
||
|
execute 'hi def link' tag_style_name tag_style_name
|
||
|
endif
|
||
|
else
|
||
|
echo "No style specified."
|
||
|
return ''
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! taskpaper#tag_style_dict(tsd)
|
||
|
for tag_name in keys(a:tsd)
|
||
|
call taskpaper#tag_style(tag_name,a:tsd[tag_name])
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
let &cpo = s:save_cpo
|