diff --git a/after/ftplugin/markdown.vim b/after/ftplugin/markdown.vim new file mode 100644 index 0000000..d5367a8 --- /dev/null +++ b/after/ftplugin/markdown.vim @@ -0,0 +1,87 @@ +" Run this plugin only if the denote package has been setup +if !exists('g:denote_directory') + finish +endif + +" ... and if this filetype is specified as denote-note file +if index(g:denote_note_file_extensions, expand('%:e')) == -1 + finish +endif + +" ... and if the file is in the denote directory +if g:denote_directory != expand('%:p:h') + finish +endif + +" Load only once per buffer +if exists('b:loaded_denote_ftplugin_notes') + finish +endif +let b:loaded_denote_ftplugin_notes = 1 + +" Link completion +" This works by using the functions s:column(), s:suggestions(), +" s:complete_link(), and by setting 'omnifunc'. + +" Find the column where the completion starts. This must be between 1 and +" col('.'). Denote links are of this form: `denote:`. +function s:column() + " Get the substring from the start of the line until col('.') + let l:x = max([1, col('.')-2]) + let l:l = getline('.')[:l:x] + " Take the shortest prefix of a denote link. This may be any of + " \matchstrpos('\= 0 + return l:res[1] + endif + let l:res = l:l->matchstrpos('\matchstr('^denote:\zs.*$') + let l:flist = glob(g:denote_directory .. '/' .. (l:prefix ? '*' .. l:prefix .. '*' : '*'), 0, v:true) + let l:res = [] + for filename in l:flist + let l:meta = libdenote#scheme_metadata(filename) + if l:meta.id == v:false || (l:meta.id !~ '^' .. l:prefix && l:meta.title !~ l:prefix) + continue + endif + let l:meta.title = l:meta.title ?? '(no title)' + call add(l:res, { + \ 'word' : 'denote:' .. l:meta.id, + \ 'abbr' : l:meta.title, + \ 'menu' : l:meta.tags->join(', ') + \ }) + endfor + return l:res +endfunction + +" Completion function for denote links +function s:complete_link(findstart, base) + return a:findstart == 1 ? s:column() : s:suggestions(a:base) +endfunction +setlocal omnifunc=s:complete_link() +" Denote links are of the form 'denote:'; we require the column. +setlocal isfname+=: +" Set the function to resolve the filename under the cursor (see |gf|). +function s:gotofile() + return v:fname !~ '^denote:' + \ ? v:fname + \ : libdenote#scheme_find(g:denote_directory, v:fname[7:]) ?? v:fname +endfunction +setlocal includeexpr=s:gotofile() + +" Back references command +let b:meta = libdenote#scheme_metadata(expand('%:t')) +exe 'command! -buffer DenoteBackReferences DenoteGrep /\/gj' diff --git a/after/ftplugin/org.vim b/after/ftplugin/org.vim new file mode 120000 index 0000000..4d7d231 --- /dev/null +++ b/after/ftplugin/org.vim @@ -0,0 +1 @@ +markdown.vim \ No newline at end of file diff --git a/after/ftplugin/qf.vim b/after/ftplugin/qf.vim new file mode 100644 index 0000000..d30232c --- /dev/null +++ b/after/ftplugin/qf.vim @@ -0,0 +1,53 @@ +" Run this plugin only if the denote package has been setup +if !exists('g:denote_directory') + finish +endif + +" Load only once per buffer +if exists('b:loaded_denote_ftplugin_qf') + finish +endif +let b:loaded_denote_ftplugin_qf = 1 + +" This will be called for every location and quickfix, when data is loaded into +" the list. This does nothing for such lists that are note 'denote' lists. + +let b:context = getloclist(0, {'context': 1})['context'] +if type(b:context) != v:t_dict || !has_key(b:context, 'denote') + " Clear settings + set spell< + nmapclear + finish +endif + +setlocal nospell +nnoremap q :lclose + +" Reload capability +if has_key(b:context, 'gfun') + function DenoteLocListReload() + let curl = line('.') + let Gfun = b:context['gfun'] + call denote#loclist#jumptowindow() + exe 'lclose' + call Gfun() + exe 'lwindow' + exe curl + endfunction + nnoremap r :call DenoteLocListReload() +endif + +" Denote-list specific configuration +if b:context['denote'] == 'list' + command! -nargs=1 -range -buffer DenoteSetTitle :call denote#notes#settitle(, ) | :normal r + command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagAdd :call denote#notes#tagmod(, , , v:true) | :normal r + command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagRm :call denote#notes#tagmod(, , , v:false) | :normal r + command! -range -buffer -bang DenoteDelete :call denote#notes#rm(, , 0) | :normal r + nnoremap C :DenoteSetTitle + nnoremap + :DenoteTagAdd + nnoremap - :DenoteTagRm + nnoremap dd :DenoteDeleter + xnoremap + :DenoteTagAdd + xnoremap - :DenoteTagRm + xnoremap d :DenoteDelete +endif diff --git a/after/ftplugin/text.vim b/after/ftplugin/text.vim new file mode 120000 index 0000000..4d7d231 --- /dev/null +++ b/after/ftplugin/text.vim @@ -0,0 +1 @@ +markdown.vim \ No newline at end of file diff --git a/autoload/denote/commands.vim b/autoload/denote/commands.vim index 17d1a6f..719edd6 100644 --- a/autoload/denote/commands.vim +++ b/autoload/denote/commands.vim @@ -3,26 +3,11 @@ function denote#commands#load() if exists('g:denote_commands_loaded') && g:denote_commands_loaded return endif + let g:denote_commands_loaded = 1 " Register user commands command -nargs=* Denote call denote#notes#list() | lcl | lopen command -nargs=1 -complete=custom,denote#completion#tags DenoteByTag call denote#notes#bytag() | lcl | lopen command -nargs=+ DenoteGrep call denote#notes#grep() | lcl | lopen command -nargs=1 DenoteNew call denote#notes#new() command -nargs=1 -complete=file DenoteCopy call denote#notes#copy() - - " Register auto commands - autocmd BufReadPost quickfix call denote#ft#qf() - let l:aupat = map(copy(g:denote_note_file_extensions), {_, v -> '*.' .. v})->join(',') - exe 'autocmd BufReadPost,BufNewFile ' .. l:aupat .. ' call denote#ft#denote()' - - " Useful key mappings - " nnoremap DenoteList :DenoteSearch:lclose:lopen:resize 20 - " nnoremap DenoteBackReferences :DenoteBackReferences:lclose:lopen:resize 20 - let g:denote_commands_loaded = v:true -endfunction - -" Public commands for the denote-list location list -function denote#commands#loadll() - command DenoteReload call denote# - " command -nargs=1 DenoteSetTitle call denote#notes#settitle() endfunction diff --git a/autoload/denote/completion.vim b/autoload/denote/completion.vim index 6f05c45..2e2ceec 100644 --- a/autoload/denote/completion.vim +++ b/autoload/denote/completion.vim @@ -1,59 +1,9 @@ -" Find the column where the completion starts. This must be between 1 and -" col('.'). Denote links are of this form: `denote:`. -function s:column() - " Get the substring from the start of the line until col('.') - let l:l = getline('.')[:col('.')] - " Take the shortest prefix of a denote link. This may be any of - " \matchstrpos('\= 0 - return l:res[1] - endif - let l:res = l:l->matchstrpos('\matchstr('^denote:\zs.*$') - let l:flist = glob(g:denote_directory .. '/' .. (l:prefix ? '*' .. l:prefix .. '*' : '*'), 0, v:true) - let l:res = [] - for filename in l:flist - let l:noteId = denote#meta#noteIdFromFile(filename) - let l:noteTitle = denote#meta#noteTitleFromFile(filename) - if l:noteId == v:false || (l:noteId !~ '^' .. l:prefix && l:noteTitle !~ l:prefix) - continue - endif - let l:noteTitle = l:noteTitle ?? '(no title)' - let l:noteTags = denote#meta#noteTagsFromFile(filename) - call add(l:res, { - \ 'word' : 'denote:' .. l:noteId, - \ 'abbr' : l:noteTitle, - \ 'menu' : l:noteTags->join(', ') - \ }) - endfor - return l:res -endfunction - -" Completion function for denote links -function denote#completion#get(findstart, base) - return a:findstart == 1 ? s:column() : s:suggestions(a:base) -endfunction - " Completion function for denote tags -function denote#completion#tags(ArgLead, cmdLine, CursorPos) +function denote#completion#tags(ArgLead, CmdLine, CursorPos) let l:files=glob(g:denote_directory .. '/*', 0, v:true) let l:tags=[] for f in l:files - let l:tags=extend(l:tags, denote#meta#noteTagsFromFile(f)) + let l:tags=extend(l:tags, libdenote#scheme_metadata(f).tags) endfor return uniq(sort(l:tags))->join("\n") endfunction diff --git a/autoload/denote/frontmatter.vim b/autoload/denote/frontmatter.vim deleted file mode 100644 index 320cd2d..0000000 --- a/autoload/denote/frontmatter.vim +++ /dev/null @@ -1,93 +0,0 @@ -" Functions to manipulate and query the front matter - -" Helper function to put string in double quotes, and remove inside double -" quotes. -function s:escapeDQ(s) - return '"' .. substitute(a:s, '"', '', 'g') .. '"' -endfunction - -" Create front matter (yaml) -function s:md_new_yaml(id, title, tags) - return [ - \ '---', - \ 'title: ' .. s:escapeDQ(a:title), - \ 'date: ' .. strftime('%FT%T%z'), - \ 'tags: [' .. map(copy(a:tags), {_, t -> s:escapeDQ(t) })->join(', ') .. ']', - \ 'identifier: ' .. s:escapeDQ(a:id), - \ '---' - \ ] -endfunction - -" Create front matter (toml) -function s:md_new_toml(id, title, tags) - return [ - \ '+++', - \ 'title ' .. s:escapeDQ(a:title), - \ 'date ' .. strftime('%FT%T%z'), - \ 'tags [' .. map(copy(a:tags), {_, t -> s:escapeDQ(t) })->join(', ') .. ']', - \ 'identifier ' .. s:escapeDQ(a:id), - \ '+++' - \ ] -endfunction - -" Create front matter (plain) -function s:plain_new(id, title, tags) - return [ - \ 'title: ' .. a:title, - \ 'date: ' .. strftime('%F'), - \ 'tags: ' .. a:tags->join(' '), - \ 'identifier: ' .. a:id, - \ '---------------------------', - \ ] -endfunction - -" Create front matter (org) -function s:org_new(id, title, tags) - return [ - \ '#+title: ' .. a:title, - \ '#+date: [' .. strftime('%F %a %R') .. ']', - \ '#+filetags: :' .. map(copy(a:tags), {_, t -> substitute(t, ':', '', 'g') })->join(':') .. ':', - \ '#+identifier: ' .. a:id, - \ ] -endfunction - -" Create front matter -function denote#frontmatter#new(ft, id, title, tags=[]) - return a:ft == 'org' ? s:org_new(a:id, a:title, a:tags) - \ : a:ft == 'plain' ? s:plain_new(a:id, a:title, a:tags) - \ : g:denote_fm_md_type == 'toml' ? s:md_new_toml(a:id, a:title, a:tags) - \ : s:md_new_yaml(a:id, a:title, a:tags) -endfunction - -" Return the frontmatter of the specified file (as a list) -function s:getFrontmatter(filename) - let l:ft = fnamemodify(a:filename, ':e') - let l:cnt = l:ft == 'org' ? 4 - \ : l:ft == 'txt' ? 5 - \ : 6 - call bufload(a:filename) - return getbufline(a:filename, 1, 1 + l:cnt) -endfunction - -" Get title from front matter -function denote#frontmatter#setTitle(filename, title) - " Retrieve front matter (number of lines depends on ft), and replace title - let l:frontmatter = s:getFrontmatter(a:filename) - call map(l:frontmatter, { _, v -> substitute(v, '^#\?+\?title:\?\s*"\?\zs.\{-\}\ze\"\?$', a:title, '')}) - call setbufline(a:filename, 1, l:frontmatter) -endfunction - -" Set tags in front matter -function denote#frontmatter#setTags(filename, tags) - let l:frontmatter = s:getFrontmatter(a:filename) - let l:tagline = denote#frontmatter#new(fnamemodify(a:filename, ':t'), '', '', a:tags) - \ ->filter('v:val =~ "^\\(#+file\\)\\?tags"')[0] - call map(l:frontmatter, { _, v -> v =~ '^\(#+file\)\?tags' ? l:tagline : v}) - call setbufline(a:filename, 1, l:frontmatter) -endfunction - -" Prepend frontmatter to file -function denote#frontmatter#prepend(filename, id, title, tags=[]) - let l:frontmatter = denote#frontmatter#new(fnamemodify(a:filename, ':t'), a:id, a:title, a:tags) - call appendbufline(a:filename, 0, l:frontmatter) -endfunction diff --git a/autoload/denote/ft.vim b/autoload/denote/ft.vim deleted file mode 100644 index 4f11ee3..0000000 --- a/autoload/denote/ft.vim +++ /dev/null @@ -1,57 +0,0 @@ -" Go to file command |gf| adjustments -" This resolves denote links. The function has access to the variable v:fname, -" which corresponds to the filename under the cursor. -function s:gotofile() - return v:fname !~ '^denote:' - \ ? v:fname - \ : denote#meta#fileFromNoteId(v:fname[7:]) ?? v:fname -endfunction - -" Denote note specifics -function denote#ft#denote() - if expand('%:p:h') != g:denote_directory - return - endif - " Link completion - setlocal omnifunc=denote#completion#get - " Denote links are of the form 'denote:'; we require the column. - setlocal isfname+=: - " Set the function to resolve the filename under the cursor (see |gf|). - setlocal includeexpr=s:gotofile() - " Back references command - let l:noteid = denote#meta#noteIdFromFile(expand('%:t')) - exe 'command! -buffer DenoteBackReferences DenoteGrep /\/gj' -endfunction - -" Location-list specifics -" -" This will be called for every location and quickfix, when data is loaded into -" the list. This does nothing for such lists that are note 'denote' lists. -function denote#ft#qf() - let l:context = getloclist(0, {'context': 1})['context'] - if type(l:context) != v:t_dict || !has_key(l:context, 'denote') - " Clear settings - set spell< - nmapclear - return - endif - setlocal nospell - nnoremap q :lclose - if has_key(l:context, 'gfun') - nnoremap r :call denote#loclist#reload() - endif - " Denote-list specific configuration - if l:context['denote'] == 'list' - command! -nargs=1 -range -buffer DenoteSetTitle :call denote#notes#settitle(, ) - command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagAdd :call denote#notes#tagmod(, , , v:true) - command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagRm :call denote#notes#tagmod(, , , v:false) - command! -range -buffer -bang DenoteDelete :call denote#notes#rm(, , 0) - nnoremap C :DenoteSetTitle - nnoremap + :DenoteTagAdd - nnoremap - :DenoteTagRm - nnoremap dd :DenoteDeleter - xnoremap + :DenoteTagAdd - xnoremap - :DenoteTagRm - xnoremap d :DenoteDelete - endif -endfunction diff --git a/autoload/denote/loclist.vim b/autoload/denote/loclist.vim index bd8f34a..af1dd01 100644 --- a/autoload/denote/loclist.vim +++ b/autoload/denote/loclist.vim @@ -5,10 +5,10 @@ endfunction " Local helper function to retrieve and format the title. function s:titleFromBuf(buf) - let l:name=denote#meta#noteTitleFromFile(bufname(a:buf)) - return strchars(l:name, 1) <= g:denote_loc_title_columns - \ ? printf('%' .. g:denote_loc_title_columns .. 's', l:name) - \ : printf('%.' .. (g:denote_loc_title_columns - 1) .. 's', l:name) .. '…' + let l:name = libdenote#scheme_metadata(bufname(a:buf)).title + return strchars(l:name, 1) <= g:denote_loc_title_columns + \ ? printf('%' .. g:denote_loc_title_columns .. 's', l:name) + \ : printf('%.' .. (g:denote_loc_title_columns - 1) .. 's', l:name) .. '…' endfunction " Local helper function to truncate text with match at the given column as a @@ -20,7 +20,7 @@ function s:formatText(text, col, width) endfunction " This modifies the location list for pretty display. -function denote#loclist#textReferences(info) +function s:textReferences(info) let l:items=getloclist(a:info.winid) let l:l=[] let l:width=winwidth(0) - g:denote_loc_title_columns - 19 @@ -37,15 +37,15 @@ function denote#loclist#textReferences(info) endfunction " This modifies the location list for pretty display. -function denote#loclist#textNoteList(info) +function s:textNoteList(info) let l:items=getloclist(a:info.winid) let l:l=[] for idx in range(a:info.start_idx - 1, a:info.end_idx - 1) let l:e=l:items[idx] let l:name=s:titleFromBuf(l:e.bufnr) - let l:ntags=denote#meta#noteTagsFromFile(bufname(l:e.bufnr))->join() + let l:ntags = libdenote#scheme_metadata(bufname(l:e.bufnr)).tags->join() call add(l:l, l:name .. ' | ' .. l:ntags) -endfor + endfor return l:l endfunction @@ -56,7 +56,7 @@ function denote#loclist#fill(title, files, Gfun) " Set properties call setloclist(0, [], 'r', \ {'title': a:title, - \ 'quickfixtextfunc' : 'denote#loclist#textNoteList', + \ 'quickfixtextfunc' : 's:textNoteList', \ 'context' : {'denote': 'list', 'gfun': a:Gfun}}) " Populate let l:notes=[] @@ -73,7 +73,7 @@ endfunction function denote#loclist#setgrep(title, Gfun) call setloclist(0, [], 'r', \ {'title': a:title, - \ 'quickfixtextfunc' : 'denote#loclist#textReferences', + \ 'quickfixtextfunc' : 's:textReferences', \ 'context' : {'denote': 'grep', 'gfun': a:Gfun}}) endfunction diff --git a/autoload/denote/meta.vim b/autoload/denote/meta.vim deleted file mode 100644 index b5f89dd..0000000 --- a/autoload/denote/meta.vim +++ /dev/null @@ -1,72 +0,0 @@ -" Return the filename of the note with id `noteId`. If the file is note found, -" then v:false is returned. -function denote#meta#fileFromNoteId(noteId) - " According to the file-naming scheme, the note id is prefixed with '@@'. If - " the note id appears at the beginning of the filename, then this prefix is - " optional. There may exist another field (such as the signature, title, or - " keywords field) after the note id. These are prefixed with '==', '--', or - " '__'. If the note id is the last field, then the id is followed by the file - " extension, e.g., '.md'. - " (A) First, we get all files that contain the note id as substring. - " (B) Then we ensure that the note id is followed by another field or by the - " file extension. - let l:files = glob(g:denote_directory .. '/*' .. a:noteId .. '*', 0, v:true) - \ ->filter('v:val->split("/")[-1] =~ "' .. a:noteId .. '\\(==\\|--\\|__\\|\\.\\)"') - \ ->filter('v:val->split("/")[-1] =~ "^' .. a:noteId .. '\\|@@' .. a:noteId .. '"') - return empty(l:files) ? v:false : l:files[0] -endfunction - -" Return the note id from the filename. On failure, v:false is returned. -function denote#meta#noteIdFromFile(file) - return a:file->fnamemodify(':t')->matchstr('@@\zs.\{-\}\ze\(==\|--\|__\|\..\)') - \ ?? a:file->fnamemodify(':t')->matchstr('^.\{-\}\ze\(==\|--\|__\|\..\)') - \ ?? v:false -endfunction - -" Return the note title from the filename. -function denote#meta#noteTitleFromFile(file) - return a:file->fnamemodify(':t') - \ ->matchstr('--\zs.\{-\}\ze\(==\|@@\|__\|\..\)') - \ ->substitute('-', ' ', 'g') -endfunction - -" Return the note tags from the filename as a list. -function denote#meta#noteTagsFromFile(file) - return a:file->fnamemodify(':t') - \ ->matchstr('__\zs.\{-\}\ze\(==\|@@\|--\|\..\)') - \ ->split('_') -endfunction - -" Return the note signature from the filename. -function denote#meta#noteSignatureFromFile(file) - return a:file->fnamemodify(':t') - \ ->matchstr('==\zs.\{-\}\ze\(==\|@@\|__\|\..\)') -endfunction - -" Identifier creation -function denote#meta#identifier_generate() - return exists('*strftime') - \ ? strftime('%Y%m%dT%H%M%S') - \ : rand() -endfunction - -" This function removes illegal characters in parts of the filename. -function s:santizefnpart(part) - return a:part->tolower() - \ ->substitute('[^[:alnum:]]', '-', 'g') - \ ->substitute('-\+', '-', 'g') - \ ->substitute('_\+', '_', 'g') - \ ->substitute('=\+', '=', 'g') - \ ->substitute('@\+', '@', 'g') - \ ->trim('-_@=') -endfunction - -" Function that returns the filename give all metadata. The filename is -" returned with the path to the denote directory. -function denote#meta#filename(ext, identifier, title='', tags=[], signature='') - let l:f = g:denote_directory .. '/' .. s:santizefnpart(a:identifier) - let l:f ..= len(a:signature) > 0 ? ('==' .. s:santizefnpart(a:signature)) : '' - let l:f ..= len(a:title) > 0 ? ('--' .. s:santizefnpart(a:title)) : '' - let l:f ..= len(a:tags) > 0 ? ('__' .. map(copy(a:tags), {_, v -> s:santizefnpart(v)})->join('_')) : '' - return l:f .. '.' .. a:ext -endfunction diff --git a/autoload/denote/notes.vim b/autoload/denote/notes.vim index 3965bef..12e1489 100644 --- a/autoload/denote/notes.vim +++ b/autoload/denote/notes.vim @@ -27,15 +27,15 @@ function denote#notes#grep(re) endfunction " This creates a new denote entry with the given title and of the given -" filetype. The title may be empty. -function denote#notes#new(title, ft=g:denote_new_ft) +" extension. The title may be empty. +function denote#notes#new(title, ext=g:denote_new_ext) let l:identifier = g:Denote_identifier_fun() - let l:fn = denote#meta#filename(a:ft, l:identifier, a:title) + let l:fn = g:denote_directory .. '/' .. libdenote#scheme_filename(a:ext, l:identifier, a:title) " Jump to window this location list belongs to call denote#loclist#jumptowindow() " Open file and write front matter exe 'edit ' l:fn - call setline(1, denote#frontmatter#new(a:ft, l:identifier, a:title)) + call setline(1, libdenote#fm_gen(a:ext, l:identifier, a:title)) endfunction " Function to set the title of the selected entry @@ -48,22 +48,25 @@ function denote#notes#settitle(linenr, title) let l:item = l:items[a:linenr-1] let l:bufnr = l:item['bufnr'] let l:filename = bufname(l:bufnr) - let l:noteid = denote#meta#noteIdFromFile(l:filename) - let l:notetags = denote#meta#noteTagsFromFile(l:filename) - let l:notesignature = denote#meta#noteSignatureFromFile(l:filename) - let l:oldtitle = denote#meta#noteTitleFromFile(l:filename) + let l:meta = libdenote#scheme_metadata(l:filename) let l:ext = fnamemodify(l:filename, ':e') - let l:newfilename = denote#meta#filename(l:ext, l:noteid, a:title, l:notetags, l:notesignature) + let l:newfilename = g:denote_directory .. '/' .. libdenote#scheme_filename(l:ext, l:meta.id, a:title, l:meta.tags, l:meta.sig) " If this note has a front matter, we rewrite the front matter and rename the " file. Otherwise, we rename the file only. if index(g:denote_note_file_extensions, l:ext) >= 0 " Handle front matter - call denote#frontmatter#setTitle(l:filename, a:title) + call bufload(l:filename) + let l:frontmatter = getbufline(l:filename, + \ 1, 1 + libdenote#fm_len(fnamemodify(l:filename, ':e'))) + let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'title': a:title}) + call setbufline(l:filename, 1, l:frontmatter) + let curl=line('.') call denote#loclist#jumptowindow() - exe l:bufnr .. 'buf' + exe 'silent buf ' .. l:bufnr exe 'silent file ' .. l:newfilename exe 'silent w' exe 'lopen' + exe curl if fnamemodify(l:filename, ':t') != fnamemodify(l:newfilename, ':t') call delete(l:filename) endif @@ -87,32 +90,34 @@ function denote#notes#tagmod(line1, line2, tag, add) let l:item = l:items[i-1] let l:bufnr = l:item['bufnr'] let l:filename = bufname(l:bufnr) - let l:noteid = denote#meta#noteIdFromFile(l:filename) - let l:notetags = denote#meta#noteTagsFromFile(l:filename) - let l:idx = index(l:notetags, a:tag) + let l:meta = libdenote#scheme_metadata(l:filename) + let l:idx = index(l:meta.tags, a:tag) if a:add if l:idx >= 0 continue endif - call add(l:notetags, a:tag) + call add(l:meta.tags, a:tag) else if l:idx == -1 continue endif - call remove(l:notetags, l:idx) + call remove(l:meta.tags, l:idx) endif - let l:notesignature = denote#meta#noteSignatureFromFile(l:filename) - let l:title = denote#meta#noteTitleFromFile(l:filename) let l:ext = fnamemodify(l:filename, ':e') - let l:newfilename = denote#meta#filename(l:ext, l:noteid, l:title, l:notetags, l:notesignature) + let l:newfilename = g:denote_directory .. '/' .. libdenote#scheme_filename(l:ext, l:meta.id, l:meta.title, l:meta.tags, l:meta.sig) if index(g:denote_note_file_extensions, l:ext) >= 0 " Handle front matter - call denote#frontmatter#setTags(l:filename, l:notetags) + call bufload(l:filename) + let l:frontmatter = getbufline(l:filename, 1, 1 + libdenote#fm_len(fnamemodify(l:filename, ':e'))) + let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'tags': l:meta.tags}) + call setbufline(l:filename, 1, l:frontmatter) + let curl=line('.') call denote#loclist#jumptowindow() - exe l:bufnr .. 'buf' + exe 'silent buf ' .. l:bufnr exe 'silent file ' .. l:newfilename exe 'silent w' exe 'lopen' + exe curl call delete(l:filename) else if fnamemodify(l:filename, ':t') == fnamemodify(l:newfilename, ':t') @@ -134,20 +139,19 @@ function denote#notes#copy(origfile) let l:title = fnamemodify(a:origfile, ':t:r') let l:ext = fnamemodify(a:origfile, ':e') let l:identifier = g:Denote_identifier_fun() - let l:filename = denote#meta#filename(l:ext, l:identifier, l:title) + let l:filename = g:denote_directory .. '/' .. libdenote#scheme_filename(l:ext, l:identifier, l:title) call system('cp ' .. shellescape(a:origfile) .. ' ' .. shellescape(l:filename)) " Write front matter, if this is supported if index(g:denote_note_file_extensions, l:ext) >= 0 call denote#loclist#jumptowindow() exe 'edit ' .. l:filename - call denote#frontmatter#prepend(l:filename, l:identifier, l:title) + call appendbufline(l:filename, 0, libdenote#fm_gen(l:ext, l:identifier, l:title)) exe 'w' endif endfunction " Delete notes from denote directory function denote#notes#rm(line1, line2, bang) - echom "bang set to:"..a:bang let l:items = getloclist(0, {'items': 1})['items'] if empty(l:items) return @@ -156,7 +160,7 @@ function denote#notes#rm(line1, line2, bang) let l:item = l:items[i-1] let l:bufnr = l:item['bufnr'] let l:filename = bufname(l:bufnr) - let l:title = denote#meta#noteTitleFromFile(l:filename) + let l:title = libdenote#scheme_metadata(l:filename).title if a:bang == v:false let l:answer = confirm('Are you sure you want to delete the note "' .. l:title .. '"?', "&Yes\n&No\n", 2, 'Question') if l:answer != 1 diff --git a/autoload/libdenote.vim b/autoload/libdenote.vim index f91ec28..339197f 100644 --- a/autoload/libdenote.vim +++ b/autoload/libdenote.vim @@ -163,33 +163,47 @@ function libdenote#fm_gen(ext, id, title, tags=[], md_type='yaml') endfunction " Alter front matter -" This function returns the (modified) a:fm front matter. For any of the -" arguments a:id, a:title, and a:tags, if the argument is specified (for the -" tag, it may also be an empty list), then the value of the argument is used as -" a replacement in the input front matter a:fm. +" This function returns the (modified) a:fm front matter. The argument a:mod +" of type dict specifies the fields to be updated. If a:mod contains the key +" 'date', then the date field will be updated. If it contains the key 'id', +" then the identifier will be updated with the value a:mod.id. Similar for the +" title (key 'title'), and the tags (key 'tags'). For the tags, the value type +" is a list of strings. " " @argument a:fm list[string] List of strings describes a frontmatter -" @argument a:ext string: Generate front matter for a file of this -" extension -" @argument a:id string: Identifier of the denote note -" @argument a:title string: Title of the denote note -" @argument a:tags list[string] or v:false: -" List of strings that specify the tags +" @argument a:mod dict Dictionary to control updates " @return: List of strings with a line-per item that describes the front matter -function libdenote#fm_alter(fm, ext, id='', title='', tags=v:false) +function libdenote#fm_alter(fm, mod) + let l:md_type = a:fm[0] == '+++' ? 'toml' : 'yaml' + let l:ext = { n -> n == 4 ? 'org' : n == 5 ? 'txt' : 'md'}(len(a:fm)) let l:new = copy(a:fm) - let l:repl = libdenote#fm(a:ext, a:id ?? '', a:title ?? '', a:tags ?? []) - if a:id + let l:repl = libdenote#fm_gen(l:ext, + \ has_key(a:mod, 'id') ? a:mod.id : '', + \ has_key(a:mod, 'title') ? a:mod.title : '', + \ has_key(a:mod, 'tags') ? a:mod.tags : []) + if has_key(a:mod, 'date') + let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?date"') + call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?date" ? l:repl[l:ididx] : v}) + endif + if has_key(a:mod, 'id') let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?identifier"') call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?identifier" ? l:repl[l:ididx] : v}) endif - if a:title + if has_key(a:mod, 'title') let l:titleidx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?title"') call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?title" ? l:repl[l:titleidx] : v}) endif - if type(a:tags) == v:t_list + if has_key(a:mod, 'tags') let l:tagsidx = indexof(l:repl, 'v:val =~ "^\\(#+file\\)\\?tags"') call map(l:new, {_, v -> v =~ "^\\(#+file\\)\\?tags" ? l:repl[l:tagsidx] : v}) endif return l:new endfunction + +" Returns the length of a front matter (number of lines) +" @argument a:ext string: File extension +function libdenote#fm_len(ext) + return a:ext == 'org' ? 4 + \ : a:ext == 'txt' ? 5 + \ : 6 +endfunction diff --git a/doc/denote.txt b/doc/denote.txt index a9ea605..60d4b55 100644 --- a/doc/denote.txt +++ b/doc/denote.txt @@ -1,8 +1,14 @@ *denote.txt* Handling denote entries – the vim way - Last change: 2026 Feb 28 + Last change: 2026 Mar 3 -This is the documentation for the denote plugin. +This is the documentation for the denote package. This package also introduces +the |libdenote| plugin in the file autoload/libdenote.vim, which may be of +independent interest. If this package is loaded automatically, but you prefer +to opt-out, then add this line to your vimrc: +> + let g:loaded_denote = 1 +< ============================================================================== CONTENTS *vim-denote* *denote* @@ -17,8 +23,8 @@ CONTENTS *vim-denote* *denote* Appendices A. The libdenote plugin |libdenote| - A.1 File-naming functions |libdentoe-filenaming| - A.2 Front-matter functions |libdenote-frontmatter| + A.1 File-naming functions |libdentoe-filenaming| + A.2 Front-matter functions |libdenote-frontmatter| B. Package API |denote-api| ============================================================================== @@ -47,6 +53,7 @@ prefix of a denote link. This completion also works with titles: by invoking omni completion after typing "denote:foo", denote links are completed for entries containing "foo" in the title. + ============================================================================== COMMANDS *denote-commands* @@ -259,7 +266,7 @@ denote-centered operations. That plugin is given by the file autoload/libdenote.vim. Each function in this plugin is pure, i.e., produces no side effects. There are two classes of functions: functions concerning the file-naming scheme (prefixed with scheme_, see |libdenote-filenaming|) and -functions concerning the front matter (prefixed with fmt_, see +functions concerning the front matter (prefixed with fm_, see |libdenote-frontmatter|). *libdenote-filenaming* @@ -303,12 +310,114 @@ libdenote#fm_gen({ext}, {id}, {title}, {tags}, {md_type}) 'toml'. The former format is used per default. *libdenote#fm_alter()* -libdenote#fm_alter({fm}, {ext}, {id}, {title}, {tags}, {md_type}) +libdenote#fm_alter({fm}, {mod}) Similar to |libdenote#fm_gen()|, this function returns a list containing the lines of a front matter. In contrast to the above function, this functions takes a front matter {fm} as base (again, - given as list of lines), and alters the title or tags associated with - the note. The arguments {title} and {tags} are optional. When set, - then {fm} is returned with the corresponding replaced lines. + given as list of lines), and updates the fields specified by the + |dict| {mod}. If {mod} contains the key "date", then the date string + will be updated. If {mod} contains the key "id", then the identifier + will be updated with the value a:mod.id. Similarly, the keys "title" + and "tags" are used to update the title and tag list of the front + matter. + + *libdenote#fm_len()* +libdenote#fm_len({ext}) + This returns the number of lines of the front matter for files with + the extension {ext}. + + +============================================================================== +APPENDIX B - PACKAGE API *denote-api* + +Here, we list and briefly describe all functions that come with this package. +The aim of providing this information is to keep vim-denote hackable. + + *denote#commands#load()* +denote#commands#load() + This function initializes the user commands (see, |denote-commands|) + that are globally available. + + *denote#completion#tags()* +denote#completion#tags({ArgLead}, {CmdLine}, {CursorPos}) + This is the autocompletion function for tag arguments in the user + commands |:DenoteByTag|, |:DenoteTagAdd|, and |:DenoteTagRm|. + + *denote#loclist#clear()* +denote#loclist#clear() + With this, the location list is cleared. + + *denote#loclist*fill()* +denote#loclist*fill({title}, {files}, {Gfun}) + This function populates the location list with denote entries. The + {title} argument specifies the title of the location list. The {files} + argument is a list of filenames of denote entries. Finally, the {Gfun} + argument is a Funcref to the function that reloads the location list — + this is the function (with all arguments set) that invoked this call + to |denote#loclist#fill()|. + + *denote#loclist#setgrep()* +denote#loclist#setgrep({title}, {Gfun}) + This function makes the location list fit for showing the result of + |:DenoteGrep|. The {title} and {Gfun} arguments are as in + |denote#loclist#fill()|. + + *denote#loclist#reload()* +denote#loclist#reload() + This function reruns the location-list generating function (see {Gfun} + in |denote#loclist#fill()|. + + *denote#loclist#jumptowindow()* +denote#loclist#jumptowindow() + With this, the cursor is moved from the location list to the window + the location list belongs to. + + *denote#notes#list()* +denote#notes#list({search}) + This function searches for denote entries that contain {search} in the + path, and displays the results in the location list. The command + |:Denote| is bound to this function. + + *denote#notes#bytag()* +denote#notes#bytag({tag}) + This is similar to |denote#notes#list()|, but only entries with the + tag {tag} are put to the location list. This is used by the command + |:DenoteByTag|. + + *denote#notes#grep()* +denote#notes#grep({re}) + This is the function used by |:DenoteGrep| to search for patterns + within the denote files. + + *denote#notes#new()* +denote#notes#new({title}, {ext}) + This function is used by |:DenoteNew|. Here, {title} is the title of + the new note, and {ext} the file extension. This second argument is + optional and has the default value |g:denote_new_ext|. + + *denote#notes#settitle()* +denote#notes#settitle({linenr}, {title}) + With this, the title of the entry on line {linenr} of the location + list is set to {title}. This is used by |:DenoteSetTitle|. + + *denote#notes#tagmod()* +denote#notes#tagmod({line1}, {line2}, {tag}, {add}) + This function modifies the tags of the denote entries from line + {line1} to line {line2} of the location list. In each of these + entries, the tag {tag} is added if {add} is 1, and removed if {add} is + 0. + + *denote#notes#copy()* +denote#notes#copy({origfile}) + This copies the file {origfile} to the denote directory, and renames + the copy to make it denote compatible. This function is used by + |:DenoteCopy|. + + *denote#notes#rm()* +denote#notes#rm({line1}, {line2}, {bang}) + With this, the files that correspond to the entries from line {line1} + to line {line2} of the location list are deleted. If the {bang} is set + to 1, then no confirmation will be asked. Otherwise, the user is asked + to confirm every deletion. This function is used by |:DenoteDelete|. vim:tw=78:sw=4:ts=8:noet:ft=help:norl: diff --git a/plugin/denote.vim b/plugin/denote.vim index c36b0a0..3bfa32a 100644 --- a/plugin/denote.vim +++ b/plugin/denote.vim @@ -1,14 +1,14 @@ " Global configurations +if exists('g:loaded_denote') + finish +endif +let g:loaded_denote = 1 " List of denote directories if !exists('g:denote_directories') let g:denote_directories = [] endif call map(g:denote_directories, {_, d -> fnamemodify(d, ':p')}) -" If only one directory has been specified, use that as denote directory -" if len(g:denote_directories) == 1 -" let g:denote_directory = g:denote_directories[0] -" endif " Restrict basic operations to these files if !exists('g:denote_note_file_extensions') @@ -21,8 +21,8 @@ if !exists('g:denote_loc_title_columns') endif " Default filetype for newly created denote entries -if !exists('g:denote_new_ft') - let g:denote_new_ft = 'md' +if !exists('g:denote_new_ext') + let g:denote_new_ext = 'md' endif " Default front-matter type for markdown notes, may be one of 'yaml' or 'toml' @@ -33,7 +33,7 @@ endif " By using the following global variable, the user may specify a custom " function for creating identifiers. if !exists('g:Denote_identifier_fun') - let g:Denote_identifier_fun = function('denote#meta#identifier_generate') + let g:Denote_identifier_fun = function('libdenote#scheme_idgen') endif " Transform full path into canonical form WITH trailing '/'