" Vim auto-load script " Author: Peter Odding " Last Change: May 5, 2013 " URL: http://peterodding.com/code/vim/notes/ if !exists('s:currently_tagged_notes') let s:currently_tagged_notes = {} " The in-memory representation of tags and the notes in which they're used. let s:previously_tagged_notes = {} " Copy of index as it is / should be now on disk (to detect changes). let s:last_disk_sync = 0 " Whether the on-disk representation of the tags has been read. let s:buffer_name = 'Tagged Notes' " The buffer name for the list of tagged notes. let s:loading_index = 0 endif function! xolox#notes#tags#load_index() " {{{1 if s:loading_index " Guard against recursive calls. return s:currently_tagged_notes endif let starttime = xolox#misc#timer#start() let indexfile = expand(g:notes_tagsindex) let lastmodified = getftime(indexfile) if lastmodified == -1 let s:loading_index = 1 call xolox#notes#tags#create_index() let s:loading_index = 0 elseif lastmodified > s:last_disk_sync let s:currently_tagged_notes = {} for line in readfile(indexfile) let filenames = split(line, "\t") if len(filenames) > 1 let tagname = remove(filenames, 0) let s:currently_tagged_notes[tagname] = filenames endif endfor let s:previously_tagged_notes = deepcopy(s:currently_tagged_notes) let s:last_disk_sync = lastmodified call xolox#misc#timer#stop("notes.vim %s: Loaded tags index in %s.", g:xolox#notes#version, starttime) endif return s:currently_tagged_notes endfunction function! xolox#notes#tags#create_index() " {{{1 let exists = filereadable(expand(g:notes_tagsindex)) let starttime = xolox#misc#timer#start() let filenames = xolox#notes#get_fnames(0) let s:currently_tagged_notes = {} for idx in range(len(filenames)) if filereadable(filenames[idx]) let title = xolox#notes#fname_to_title(filenames[idx]) call xolox#misc#msg#info("notes.vim %s: Scanning note %i/%i: %s", g:xolox#notes#version, idx + 1, len(filenames), title) call xolox#notes#tags#scan_note(title, join(readfile(filenames[idx]), "\n")) endif endfor if xolox#notes#tags#save_index() let s:previously_tagged_notes = deepcopy(s:currently_tagged_notes) call xolox#misc#timer#stop('notes.vim %s: %s tags index in %s.', g:xolox#notes#version, exists ? "Updated" : "Created", starttime) else call xolox#misc#msg#warn("notes.vim %s: Failed to save tags index as %s!", g:xolox#notes#version, g:notes_tagsindex) endif endfunction function! xolox#notes#tags#save_index() " {{{1 let indexfile = expand(g:notes_tagsindex) let existingfile = filereadable(indexfile) let nothingchanged = (s:currently_tagged_notes == s:previously_tagged_notes) if existingfile && nothingchanged call xolox#misc#msg#debug("notes.vim %s: Index not dirty so not saved.", g:xolox#notes#version) return 1 " Nothing to be done else let lines = [] for [tagname, filenames] in items(s:currently_tagged_notes) call add(lines, join([tagname] + filenames, "\t")) endfor let status = writefile(lines, indexfile) == 0 if status call xolox#misc#msg#debug("notes.vim %s: Index saved to %s.", g:xolox#notes#version, g:notes_tagsindex) let s:last_disk_sync = getftime(indexfile) else call xolox#misc#msg#debug("notes.vim %s: Failed to save index to %s.", g:xolox#notes#version, g:notes_tagsindex) endif return status endif endfunction function! xolox#notes#tags#scan_note(title, text) " {{{1 " Add a note to the tags index. call xolox#notes#tags#load_index() " Don't scan tags inside code blocks. let text = substitute(a:text, '{{{\w\+\_.\{-}}}}', '', 'g') " Split everything on whitespace. for token in split(text) " Match words that start with @ and don't contain { (BibTeX entries). if token =~ '^@\w' && token !~ '{' " Strip any trailing punctuation. let token = substitute(token[1:], '[[:punct:]]*$', '', '') if token != '' if !has_key(s:currently_tagged_notes, token) let s:currently_tagged_notes[token] = [a:title] elseif index(s:currently_tagged_notes[token], a:title) == -1 " Keep the tags sorted. call xolox#misc#list#binsert(s:currently_tagged_notes[token], a:title, 1) endif endif endif endfor endfunction function! xolox#notes#tags#forget_note(title) " {{{1 " Remove a note from the tags index. call xolox#notes#tags#load_index() for tagname in keys(s:currently_tagged_notes) call filter(s:currently_tagged_notes[tagname], "v:val != a:title") if empty(s:currently_tagged_notes[tagname]) unlet s:currently_tagged_notes[tagname] endif endfor endfunction function! xolox#notes#tags#show_tags(minsize) " {{{1 " TODO Mappings to "zoom" in/out (show only big tags). let starttime = xolox#misc#timer#start() call xolox#notes#tags#load_index() let lines = [s:buffer_name, ''] if empty(s:currently_tagged_notes) call add(lines, "You haven't used any tags yet!") else " Create a dictionary with note titles as keys. let unmatched = {} for title in xolox#notes#get_titles(0) let unmatched[title] = 1 endfor let totalnotes = len(unmatched) " Group matching notes and remove them from the dictionary. let grouped_notes = [] let numtags = 0 for tagname in sort(keys(s:currently_tagged_notes), 1) let numnotes = len(s:currently_tagged_notes[tagname]) if numnotes >= a:minsize let matched_notes = s:currently_tagged_notes[tagname] for title in matched_notes if has_key(unmatched, title) unlet unmatched[title] endif endfor call add(grouped_notes, {'name': tagname, 'notes': matched_notes}) let numtags += 1 endif endfor " Add a "fake tag" with all unmatched notes. if !empty(unmatched) call add(grouped_notes, {'name': "Unmatched notes", 'notes': keys(unmatched)}) endif " Format the results as a note. let bullet = xolox#notes#get_bullet('*') for group in grouped_notes let tagname = group['name'] let friendly_name = xolox#notes#tags#friendly_name(tagname) let numnotes = len(group['notes']) if numnotes >= a:minsize call extend(lines, ['', printf('# %s (%i note%s)', friendly_name, numnotes, numnotes == 1 ? '' : 's'), '']) for title in group['notes'] let lastmodified = xolox#notes#friendly_date(getftime(xolox#notes#title_to_fname(title))) call add(lines, ' ' . bullet . ' ' . title . ' (last edited ' . lastmodified . ')') endfor endif endfor if a:minsize <= 1 let message = printf("You've used %i %s in %i %s", \ numtags, numtags == 1 ? "tag" : "tags", \ totalnotes, totalnotes == 1 ? "note" : "notes") else let message = printf("There %s %i %s that %s been used at least %s times", \ numtags == 1 ? "is" : "are", numtags, \ numtags == 1 ? "tag" : "tags", \ numtags == 1 ? "has" : "have", a:minsize) endif let message .= ", " . (numtags == 1 ? "it's" : "they're") let message .= " listed below. Tags and notes are sorted alphabetically and after each note is the date when it was last modified." if !empty(unmatched) if a:minsize <= 1 let message .= " At the bottom is a list of untagged notes." else let message .= " At the bottom is a list of unmatched notes." endif endif if numtags > 1 && !(&foldmethod == 'expr' && &foldenable) let message .= " You can enable text folding to get an overview of just the tag names and how many times they've been used." endif call insert(lines, message, 2) endif call xolox#misc#buffer#prepare(s:buffer_name) call setline(1, lines) call xolox#misc#buffer#lock() call xolox#notes#set_filetype() setlocal nospell wrap call xolox#misc#timer#stop('notes.vim %s: Generated [%s] in %s.', g:xolox#notes#version, s:buffer_name, starttime) endfunction function! xolox#notes#tags#friendly_name(tagname) " {{{1 return substitute(a:tagname, '\(\U\)\(\u\)', '\1 \2', 'g') endfunction