This commit is contained in:
2026-03-03 23:03:34 +01:00
parent 2270ab9069
commit 66e032ee38
14 changed files with 338 additions and 356 deletions

View File

@@ -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:<identifier>`.
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
" \<d$
" ..
" \<denote:$
" \<denote:\f$
" \<denote:\f\f$
" etc.
let l:res = l:l->matchstrpos('\<denote:\f*$')
if l:res[1] >= 0
return l:res[1]
endif
let l:res = l:l->matchstrpos('\<d\f*$')
if l:res[1] == -1
return -3
endif
return 'denote:' =~ '^' .. l:res[0] ? l:res[1] : -3
endfunction
" Return completion items given by the base
function s:suggestions(base)
let l:prefix = a:base->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:<note id>'; 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 /\<denote:' .. b:meta.id .. '\>/gj'

1
after/ftplugin/org.vim Symbolic link
View File

@@ -0,0 +1 @@
markdown.vim

53
after/ftplugin/qf.vim Normal file
View File

@@ -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 <buffer>
finish
endif
setlocal nospell
nnoremap <buffer> q :lclose<CR>
" 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 <buffer> <silent> r :call DenoteLocListReload()<CR>
endif
" Denote-list specific configuration
if b:context['denote'] == 'list'
command! -nargs=1 -range -buffer DenoteSetTitle :call denote#notes#settitle(<line1>, <q-args>) | :normal r
command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagAdd :call denote#notes#tagmod(<line1>, <line2>, <q-args>, v:true) | :normal r
command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagRm :call denote#notes#tagmod(<line1>, <line2>, <q-args>, v:false) | :normal r
command! -range -buffer -bang DenoteDelete :call denote#notes#rm(<line1>, <line2>, <bang>0) | :normal r
nnoremap <buffer> C :DenoteSetTitle
nnoremap <buffer> + :DenoteTagAdd
nnoremap <buffer> - :DenoteTagRm
nnoremap <buffer> dd :DenoteDelete<CR>r
xnoremap <buffer> + :DenoteTagAdd
xnoremap <buffer> - :DenoteTagRm
xnoremap <buffer> d :DenoteDelete<CR>
endif

1
after/ftplugin/text.vim Symbolic link
View File

@@ -0,0 +1 @@
markdown.vim

View File

@@ -3,26 +3,11 @@ function denote#commands#load()
if exists('g:denote_commands_loaded') && g:denote_commands_loaded if exists('g:denote_commands_loaded') && g:denote_commands_loaded
return return
endif endif
let g:denote_commands_loaded = 1
" Register user commands " Register user commands
command -nargs=* Denote call denote#notes#list(<q-args>) | lcl | lopen command -nargs=* Denote call denote#notes#list(<q-args>) | lcl | lopen
command -nargs=1 -complete=custom,denote#completion#tags DenoteByTag call denote#notes#bytag(<q-args>) | lcl | lopen command -nargs=1 -complete=custom,denote#completion#tags DenoteByTag call denote#notes#bytag(<q-args>) | lcl | lopen
command -nargs=+ DenoteGrep call denote#notes#grep(<q-args>) | lcl | lopen command -nargs=+ DenoteGrep call denote#notes#grep(<q-args>) | lcl | lopen
command -nargs=1 DenoteNew call denote#notes#new(<q-args>) command -nargs=1 DenoteNew call denote#notes#new(<q-args>)
command -nargs=1 -complete=file DenoteCopy call denote#notes#copy(<q-args>) command -nargs=1 -complete=file DenoteCopy call denote#notes#copy(<q-args>)
" 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 <silent> <Plug>DenoteList :DenoteSearch<CR>:lclose<CR>:lopen<CR>:resize 20<CR>
" nnoremap <silent> <Plug>DenoteBackReferences :DenoteBackReferences<CR>:lclose<CR>:lopen<CR>:resize 20<CR>
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(<q-args>)
endfunction endfunction

View File

@@ -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:<identifier>`.
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
" \<d$
" ..
" \<denote:$
" \<denote:\f$
" \<denote:\f\f$
" etc.
let l:res = l:l->matchstrpos('\<denote:\f*$')
if l:res[1] >= 0
return l:res[1]
endif
let l:res = l:l->matchstrpos('\<d\f*$')
if l:res[1] == -1
return -3
endif
return 'denote:' =~ '^' .. l:res[0] ? l:res[1] : -3
endfunction
" Return completion items given by the base
function s:suggestions(base)
let l:prefix = a:base->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 " 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:files=glob(g:denote_directory .. '/*', 0, v:true)
let l:tags=[] let l:tags=[]
for f in l:files 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 endfor
return uniq(sort(l:tags))->join("\n") return uniq(sort(l:tags))->join("\n")
endfunction endfunction

View File

@@ -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

View File

@@ -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:<note id>'; 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 /\<denote:' .. l:noteid .. '\>/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 <buffer>
return
endif
setlocal nospell
nnoremap <buffer> q :lclose<CR>
if has_key(l:context, 'gfun')
nnoremap <buffer> <silent> r :call denote#loclist#reload()<CR>
endif
" Denote-list specific configuration
if l:context['denote'] == 'list'
command! -nargs=1 -range -buffer DenoteSetTitle :call denote#notes#settitle(<line1>, <q-args>)
command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagAdd :call denote#notes#tagmod(<line1>, <line2>, <q-args>, v:true)
command! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagRm :call denote#notes#tagmod(<line1>, <line2>, <q-args>, v:false)
command! -range -buffer -bang DenoteDelete :call denote#notes#rm(<line1>, <line2>, <bang>0)
nnoremap <buffer> C :DenoteSetTitle
nnoremap <buffer> + :DenoteTagAdd
nnoremap <buffer> - :DenoteTagRm
nnoremap <buffer> dd :DenoteDelete<CR>r
xnoremap <buffer> + :DenoteTagAdd
xnoremap <buffer> - :DenoteTagRm
xnoremap <buffer> d :DenoteDelete<CR>
endif
endfunction

View File

@@ -5,7 +5,7 @@ endfunction
" Local helper function to retrieve and format the title. " Local helper function to retrieve and format the title.
function s:titleFromBuf(buf) function s:titleFromBuf(buf)
let l:name=denote#meta#noteTitleFromFile(bufname(a:buf)) let l:name = libdenote#scheme_metadata(bufname(a:buf)).title
return strchars(l:name, 1) <= g:denote_loc_title_columns 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 .. 's', l:name)
\ : printf('%.' .. (g:denote_loc_title_columns - 1) .. 's', l:name) .. '…' \ : printf('%.' .. (g:denote_loc_title_columns - 1) .. 's', l:name) .. '…'
@@ -20,7 +20,7 @@ function s:formatText(text, col, width)
endfunction endfunction
" This modifies the location list for pretty display. " 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:items=getloclist(a:info.winid)
let l:l=[] let l:l=[]
let l:width=winwidth(0) - g:denote_loc_title_columns - 19 let l:width=winwidth(0) - g:denote_loc_title_columns - 19
@@ -37,15 +37,15 @@ function denote#loclist#textReferences(info)
endfunction endfunction
" This modifies the location list for pretty display. " 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:items=getloclist(a:info.winid)
let l:l=[] let l:l=[]
for idx in range(a:info.start_idx - 1, a:info.end_idx - 1) for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
let l:e=l:items[idx] let l:e=l:items[idx]
let l:name=s:titleFromBuf(l:e.bufnr) 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) call add(l:l, l:name .. ' | ' .. l:ntags)
endfor endfor
return l:l return l:l
endfunction endfunction
@@ -56,7 +56,7 @@ function denote#loclist#fill(title, files, Gfun)
" Set properties " Set properties
call setloclist(0, [], 'r', call setloclist(0, [], 'r',
\ {'title': a:title, \ {'title': a:title,
\ 'quickfixtextfunc' : 'denote#loclist#textNoteList', \ 'quickfixtextfunc' : 's:textNoteList',
\ 'context' : {'denote': 'list', 'gfun': a:Gfun}}) \ 'context' : {'denote': 'list', 'gfun': a:Gfun}})
" Populate " Populate
let l:notes=[] let l:notes=[]
@@ -73,7 +73,7 @@ endfunction
function denote#loclist#setgrep(title, Gfun) function denote#loclist#setgrep(title, Gfun)
call setloclist(0, [], 'r', call setloclist(0, [], 'r',
\ {'title': a:title, \ {'title': a:title,
\ 'quickfixtextfunc' : 'denote#loclist#textReferences', \ 'quickfixtextfunc' : 's:textReferences',
\ 'context' : {'denote': 'grep', 'gfun': a:Gfun}}) \ 'context' : {'denote': 'grep', 'gfun': a:Gfun}})
endfunction endfunction

View File

@@ -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

View File

@@ -27,15 +27,15 @@ function denote#notes#grep(re)
endfunction endfunction
" This creates a new denote entry with the given title and of the given " This creates a new denote entry with the given title and of the given
" filetype. The title may be empty. " extension. The title may be empty.
function denote#notes#new(title, ft=g:denote_new_ft) function denote#notes#new(title, ext=g:denote_new_ext)
let l:identifier = g:Denote_identifier_fun() 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 " Jump to window this location list belongs to
call denote#loclist#jumptowindow() call denote#loclist#jumptowindow()
" Open file and write front matter " Open file and write front matter
exe 'edit ' l:fn 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 endfunction
" Function to set the title of the selected entry " 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:item = l:items[a:linenr-1]
let l:bufnr = l:item['bufnr'] let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr) let l:filename = bufname(l:bufnr)
let l:noteid = denote#meta#noteIdFromFile(l:filename) let l:meta = libdenote#scheme_metadata(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:ext = fnamemodify(l:filename, ':e') 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 " If this note has a front matter, we rewrite the front matter and rename the
" file. Otherwise, we rename the file only. " file. Otherwise, we rename the file only.
if index(g:denote_note_file_extensions, l:ext) >= 0 if index(g:denote_note_file_extensions, l:ext) >= 0
" Handle front matter " 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() call denote#loclist#jumptowindow()
exe l:bufnr .. 'buf' exe 'silent buf ' .. l:bufnr
exe 'silent file ' .. l:newfilename exe 'silent file ' .. l:newfilename
exe 'silent w' exe 'silent w'
exe 'lopen' exe 'lopen'
exe curl
if fnamemodify(l:filename, ':t') != fnamemodify(l:newfilename, ':t') if fnamemodify(l:filename, ':t') != fnamemodify(l:newfilename, ':t')
call delete(l:filename) call delete(l:filename)
endif endif
@@ -87,32 +90,34 @@ function denote#notes#tagmod(line1, line2, tag, add)
let l:item = l:items[i-1] let l:item = l:items[i-1]
let l:bufnr = l:item['bufnr'] let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr) let l:filename = bufname(l:bufnr)
let l:noteid = denote#meta#noteIdFromFile(l:filename) let l:meta = libdenote#scheme_metadata(l:filename)
let l:notetags = denote#meta#noteTagsFromFile(l:filename) let l:idx = index(l:meta.tags, a:tag)
let l:idx = index(l:notetags, a:tag)
if a:add if a:add
if l:idx >= 0 if l:idx >= 0
continue continue
endif endif
call add(l:notetags, a:tag) call add(l:meta.tags, a:tag)
else else
if l:idx == -1 if l:idx == -1
continue continue
endif endif
call remove(l:notetags, l:idx) call remove(l:meta.tags, l:idx)
endif 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: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 if index(g:denote_note_file_extensions, l:ext) >= 0
" Handle front matter " 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() call denote#loclist#jumptowindow()
exe l:bufnr .. 'buf' exe 'silent buf ' .. l:bufnr
exe 'silent file ' .. l:newfilename exe 'silent file ' .. l:newfilename
exe 'silent w' exe 'silent w'
exe 'lopen' exe 'lopen'
exe curl
call delete(l:filename) call delete(l:filename)
else else
if fnamemodify(l:filename, ':t') == fnamemodify(l:newfilename, ':t') 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:title = fnamemodify(a:origfile, ':t:r')
let l:ext = fnamemodify(a:origfile, ':e') let l:ext = fnamemodify(a:origfile, ':e')
let l:identifier = g:Denote_identifier_fun() 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)) call system('cp ' .. shellescape(a:origfile) .. ' ' .. shellescape(l:filename))
" Write front matter, if this is supported " Write front matter, if this is supported
if index(g:denote_note_file_extensions, l:ext) >= 0 if index(g:denote_note_file_extensions, l:ext) >= 0
call denote#loclist#jumptowindow() call denote#loclist#jumptowindow()
exe 'edit ' .. l:filename 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' exe 'w'
endif endif
endfunction endfunction
" Delete notes from denote directory " Delete notes from denote directory
function denote#notes#rm(line1, line2, bang) function denote#notes#rm(line1, line2, bang)
echom "bang set to:"..a:bang
let l:items = getloclist(0, {'items': 1})['items'] let l:items = getloclist(0, {'items': 1})['items']
if empty(l:items) if empty(l:items)
return return
@@ -156,7 +160,7 @@ function denote#notes#rm(line1, line2, bang)
let l:item = l:items[i-1] let l:item = l:items[i-1]
let l:bufnr = l:item['bufnr'] let l:bufnr = l:item['bufnr']
let l:filename = bufname(l: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 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') 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 if l:answer != 1

View File

@@ -163,33 +163,47 @@ function libdenote#fm_gen(ext, id, title, tags=[], md_type='yaml')
endfunction endfunction
" Alter front matter " Alter front matter
" This function returns the (modified) a:fm front matter. For any of the " This function returns the (modified) a:fm front matter. The argument a:mod
" arguments a:id, a:title, and a:tags, if the argument is specified (for the " of type dict specifies the fields to be updated. If a:mod contains the key
" tag, it may also be an empty list), then the value of the argument is used as " 'date', then the date field will be updated. If it contains the key 'id',
" a replacement in the input front matter a:fm. " 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:fm list[string] List of strings describes a frontmatter
" @argument a:ext string: Generate front matter for a file of this " @argument a:mod dict Dictionary to control updates
" 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
" @return: List of strings with a line-per item that describes the front matter " @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:new = copy(a:fm)
let l:repl = libdenote#fm(a:ext, a:id ?? '', a:title ?? '', a:tags ?? []) let l:repl = libdenote#fm_gen(l:ext,
if a:id \ 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"') let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?identifier"')
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?identifier" ? l:repl[l:ididx] : v}) call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?identifier" ? l:repl[l:ididx] : v})
endif endif
if a:title if has_key(a:mod, 'title')
let l:titleidx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?title"') let l:titleidx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?title"')
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?title" ? l:repl[l:titleidx] : v}) call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?title" ? l:repl[l:titleidx] : v})
endif endif
if type(a:tags) == v:t_list if has_key(a:mod, 'tags')
let l:tagsidx = indexof(l:repl, 'v:val =~ "^\\(#+file\\)\\?tags"') let l:tagsidx = indexof(l:repl, 'v:val =~ "^\\(#+file\\)\\?tags"')
call map(l:new, {_, v -> v =~ "^\\(#+file\\)\\?tags" ? l:repl[l:tagsidx] : v}) call map(l:new, {_, v -> v =~ "^\\(#+file\\)\\?tags" ? l:repl[l:tagsidx] : v})
endif endif
return l:new return l:new
endfunction 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

View File

@@ -1,8 +1,14 @@
*denote.txt* Handling denote entries the vim way *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* CONTENTS *vim-denote* *denote*
@@ -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 omni completion after typing "denote:foo", denote links are completed for
entries containing "foo" in the title. entries containing "foo" in the title.
============================================================================== ==============================================================================
COMMANDS *denote-commands* 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 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 no side effects. There are two classes of functions: functions concerning the
file-naming scheme (prefixed with scheme_, see |libdenote-filenaming|) and 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-frontmatter|).
*libdenote-filenaming* *libdenote-filenaming*
@@ -303,12 +310,114 @@ libdenote#fm_gen({ext}, {id}, {title}, {tags}, {md_type})
'toml'. The former format is used per default. 'toml'. The former format is used per default.
*libdenote#fm_alter()* *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 Similar to |libdenote#fm_gen()|, this function returns a list
containing the lines of a front matter. In contrast to the above containing the lines of a front matter. In contrast to the above
function, this functions takes a front matter {fm} as base (again, function, this functions takes a front matter {fm} as base (again,
given as list of lines), and alters the title or tags associated with given as list of lines), and updates the fields specified by the
the note. The arguments {title} and {tags} are optional. When set, |dict| {mod}. If {mod} contains the key "date", then the date string
then {fm} is returned with the corresponding replaced lines. 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: vim:tw=78:sw=4:ts=8:noet:ft=help:norl:

View File

@@ -1,14 +1,14 @@
" Global configurations " Global configurations
if exists('g:loaded_denote')
finish
endif
let g:loaded_denote = 1
" List of denote directories " List of denote directories
if !exists('g:denote_directories') if !exists('g:denote_directories')
let g:denote_directories = [] let g:denote_directories = []
endif endif
call map(g:denote_directories, {_, d -> fnamemodify(d, ':p')}) 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 " Restrict basic operations to these files
if !exists('g:denote_note_file_extensions') if !exists('g:denote_note_file_extensions')
@@ -21,8 +21,8 @@ if !exists('g:denote_loc_title_columns')
endif endif
" Default filetype for newly created denote entries " Default filetype for newly created denote entries
if !exists('g:denote_new_ft') if !exists('g:denote_new_ext')
let g:denote_new_ft = 'md' let g:denote_new_ext = 'md'
endif endif
" Default front-matter type for markdown notes, may be one of 'yaml' or 'toml' " 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 " By using the following global variable, the user may specify a custom
" function for creating identifiers. " function for creating identifiers.
if !exists('g:Denote_identifier_fun') 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 endif
" Transform full path into canonical form WITH trailing '/' " Transform full path into canonical form WITH trailing '/'