Compare commits

...

17 Commits

15 changed files with 1044 additions and 335 deletions

View File

@@ -23,15 +23,16 @@ Also, the present package aims at remaining flexible. Again, both
above-mentioned plugins require the denote note identifiers to follow the rigid
format `YYYYMMDDTHHMMSS`. Per denote manual, however, the identify may be [any
string](https://protesilaos.com/emacs/denote#h:3048f558-7d84-45d6-9ef2-53055483e801)
(free of field delimiters, of course).
(free of field delimiters, of course). This package offers great flexibility in
configuration (see `:help denote-settings`).
### Installation
You may use any of your favorite plugin managers, or, manually place a copy of
this package in, e.g., `~/.vim/pack/tools/start`. For such a manual
installation, you may want to run `:helptags
~/.vim/pack/tools/start/vim-denote` to regenerate the help files. This allows
you to display the package documentation using `:help vim-denote` and similar
commands.
~/.vim/pack/tools/start/vim-denote/doc` to regenerate the help files. This
allows you to display the package documentation using `:help vim-denote` and
similar commands.
### Usage
vim needs to know where your denote entries are stored. To do so, run the
@@ -54,7 +55,7 @@ completion](https://vimhelp.org/insert.txt.html#compl-omni) by pressing
`denote:<str>`, then the link is completed for denote entries that have `<str>`
as part of the title.
_Browsing_ and searching notes:_ Browsing and searching is done using vim's
_Browsing and searching notes:_ Browsing and searching is done using vim's
[location list](https://vimhelp.org/quickfix.txt.html#location-list). The
benefit of doing so is that commands like `:lnext`, `:lprev` etc. become
available.
@@ -70,10 +71,27 @@ references to that note.
[pattern](https://vimhelp.org/quickfix.txt.html#%3Avimgrep).
_Adding notes:_ Run `:DenoteNew {title}` to add a new note with the given
title.
title. You may also copy a file (any file) to your denote directory. To do so,
run `:DenoteCopy {file}`.
_Managing entries:_ You can change the title of denote entries using
`:DenoteSetTitle {newtitle}`. Also, you can add and remove tags using
`:DenoteTagAdd {tag}` and `:DenoteTagRm {tag}`, respectively. These latter two
commands offer auto completion for the tags. Also, they accept line ranges that
may be given via, e.g., visual selection. Finally, denote entries are deleted
using `:DenoteDelete`. This command again accepts line ranges.
[^1]: Run `:help g:denote_directories` for more information.
### Basic keys
The denote location-list window comes with a set of keys:
- `q`: close the list
- `r`: reload the list
- `C`: rename the selected entry
- `+`: add a tag to the entry (also in visual mode)
- `-`: remove a tag from the entry (also in visual mode)
- `dd`: delete the selected entry (also in visual mode, using a single `d`)
### Example setup
You get a possibly useful setup with a single default denote directory at
`~/Documents/notes/` by placing these lines in your vimrc file:
@@ -91,14 +109,12 @@ using the `<Leader>d` key, move forwards and backwards in the list using the
`<Leader>D` key (again, you can jump around the references using `[l` and
`]l`).
### Customization
You can customize the behavior of this package using several
global variables. Customization is explained in the [help
file](doc/denote.txt), also accessible through `:help denote-settings`.
### Future features These features are planned:
- Tag manipulation
- Title manipulation
### Future features
- Signature handling?
- Note deletion?
- Subdirectories
- Denote query links

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 DenoteNoteCompleteLinkColumn()
" 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 DenoteNoteCompleteLinkSuggestions(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 DenoteNoteCompleteLink(findstart, base)
return a:findstart == 1 ? DenoteNoteCompleteLinkColumn() : DenoteNoteCompleteLinkSuggestions(a:base)
endfunction
setlocal omnifunc=DenoteNoteCompleteLink
" 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 DenoteNoteGotoFile()
return v:fname !~ '^denote:'
\ ? v:fname
\ : libdenote#scheme_find(g:denote_directory, v:fname[7:]) ?? v:fname
endfunction
setlocal includeexpr=DenoteNoteGotoFile()
" 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

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

@@ -0,0 +1,60 @@
" 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'
function OpenDenoteEntry()
let l:item = getloclist(0, {'items': 1})['items'][line('.')-1]
let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr)
call system('open ' .. shellescape(l:filename))
endfunction
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
nnoremap <buffer> o :call OpenDenoteEntry()<CR>
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

@@ -1,32 +1,13 @@
" This function is used to autocomplete the tags in user commands.
function s:tagList(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))
endfor
return uniq(sort(l:tags))->join("\n")
endfunction
" Public commands
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(<q-args>) | lcl | lw
command -nargs=1 -complete=custom,s:tagList DenoteByTag call denote#notes#bytag(<q-args>) | lcl | lw
command -nargs=+ DenoteGrep call denote#notes#grep(<q-args>) | lcl | lw
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=+ DenoteGrep call denote#notes#grep(<q-args>) | lcl | lopen
command -nargs=1 DenoteNew call denote#notes#new(<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
command -nargs=1 -complete=file DenoteCopy call denote#notes#copy(<q-args>)
endfunction

View File

@@ -1,49 +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(', ')
\ })
" Completion function for denote tags
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, libdenote#scheme_metadata(f).tags)
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)
return uniq(sort(l:tags))->join("\n")
endfunction

View File

@@ -1,59 +0,0 @@
" Functions to create 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

View File

@@ -1,39 +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! 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()
if getloclist(0, {'context': 1})['context'] != 'denote'
" Clear settings
set spell<
nmapclear <buffer>
return
endif
setlocal nospell
nnoremap <buffer> q :lclose<CR>
endfunction

View File

@@ -5,7 +5,7 @@ endfunction
" Local helper function to retrieve and format the title.
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
\ ? printf('%' .. g:denote_loc_title_columns .. 's', l:name)
\ : printf('%.' .. (g:denote_loc_title_columns - 1) .. 's', l:name) .. '…'
@@ -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,27 +37,27 @@ 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
return l:l
endfunction
" Re-populate location list with denote entries
function denote#loclist#fill(title, files)
function denote#loclist#fill(title, files, Gfun)
" Clear first
call setloclist(0, [], ' ')
" Set properties
call setloclist(0, [], 'r',
\ {'title': a:title,
\ 'quickfixtextfunc' : 'denote#loclist#textNoteList',
\ 'context' : 'denote'})
\ 'quickfixtextfunc' : 's:textNoteList',
\ 'context' : {'denote': 'list', 'gfun': a:Gfun}})
" Populate
let l:notes=[]
for f in a:files
@@ -70,9 +70,35 @@ function denote#loclist#fill(title, files)
endfunction
" Specify location list as denote-grep list
function denote#loclist#setgrep(title)
function denote#loclist#setgrep(title, Gfun)
call setloclist(0, [], 'r',
\ {'title': a:title,
\ 'quickfixtextfunc' : 'denote#loclist#textReferences',
\ 'context' : 'denote'})
\ 'quickfixtextfunc' : 's:textReferences',
\ 'context' : {'denote': 'grep', 'gfun': a:Gfun}})
endfunction
" Reload location list
function denote#loclist#reload()
let l:context = getloclist(0, {'context': 1})['context']
if has_key(l:context, 'denote') && has_key(l:context, 'gfun')
call denote#loclist#jumptowindow()
exe 'lclose'
call l:context['gfun']()
exe 'lwindow'
endif
endfunction
" Jump to window this location list belongs to
function denote#loclist#jumptowindow()
let l:locprop = getloclist(0, {'filewinid': 0})
if type(l:locprop) != v:t_dict || !has_key(l:locprop, 'filewinid')
return
endif
let l:winid = l:locprop['filewinid']
if l:winid == 0
exe 'new'
exe 'wincmd K'
else
call win_gotoid(l:winid)
endif
endfunction

View File

@@ -1,41 +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
" Identifier creation
function denote#meta#identifier_generate()
return exists('*strftime')
\ ? strftime('%Y%m%dT%H%M%S')
\ : rand()
endfunction

View File

@@ -1,10 +1,98 @@
" Script-local function that retrieves the front-matter of a file.
function s:getfrontmatter(filename)
let l:ext = fnamemodify(a:filename, ':e')
if index(g:denote_note_file_extensions, l:ext) == -1
return []
endif
" The following code aims to be as robust as possible. This is achieved by
" parsing each file-type (extension) separately. A common signature of the
" front matter is the empty line that separates the front matter from the
" rest of the text. As fail safe, we assume that the front matter is shorter
" than l:max lines.
" Markdown:
" - The first line is either '---' or '+++'.
" - The last line is the same as the first line.
" - All entries (apart first and last lines) are of the form '^\w\+:\?'.
" - Contains the entry '^identifier'.
" Org:
" - All lines start with '^#+\w\+:\s*'.
" - Contains the entry '^#identifier:
" Text:
" - The last line is '^-\+$.
" - All lines start with '^\w\+:\s*.
" - Contains the entry '^identifier:'
"
" Note: getbufline() returns an empty list if no more lines are available.
let l:max = 50
call bufload(a:filename)
let l:fmt = []
let l:lnr = 1
let l:identifier_seen = v:false
if l:ext == 'md'
let l:separator = getbufline(a:filename, l:lnr)[0]
if index(['---', '+++'], l:separator) == -1
return []
endif
call add(l:fmt, l:separator)
let l:lnr += 1
while l:lnr < l:max
let l:line = getbufline(a:filename, l:lnr)[0]
if l:line == l:separator
call add(l:fmt, l:line)
return l:identifier_seen && getbufline(a:filename, l:lnr + 1)[0] == '' ? l:fmt : []
endif
if l:line !~ '^\w\+:\?'
return []
endif
call add(l:fmt, l:line)
if l:line =~ '^identifier'
let l:identifier_seen = v:true
endif
let l:lnr += 1
endwhile
elseif l:ext == 'org'
while l:lnr < l:max
let l:line = getbufline(a:filename, l:lnr)[0]
if l:line == ''
return l:identifier_seen ? l:fmt : []
endif
if l:line !~ '^#+\w\+:'
return []
endif
call add(l:fmt, l:line)
if l:line =~ '^#+identifier:'
let l:identifier_seen = v:true
endif
let l:lnr += 1
endwhile
elseif l:ext == 'txt'
while l:lnr < l:max
let l:line = getbufline(a:filename, l:lnr)[0]
if l:line =~ '^-\+$'
call add(l:fmt, l:line)
return l:identifier_seen && getbufline(a:filename, l:lnr + 1)[0] == '' ? l:fmt : []
endif
if l:line !~ '^\w\+:'
return []
endif
call add(l:fmt, l:line)
if l:line =~ '^identifier:'
let l:identifier_seen = v:true
endif
let l:lnr += 1
endwhile
endif
return []
endfunction
" Put all notes of the given tag to the location list. The search argument may be
" empty. For improving search, white spaces are replaced by the * |wildcard|.
function denote#notes#list(search)
let l:s = substitute(' ' .. a:search .. ' ', ' ', '*', 'g')
let l:files = glob(g:denote_directory .. '/' .. l:s, 0, v:true)
let l:title = 'Denote notes search:' .. a:search
call denote#loclist#fill(l:title, l:files)
let l:Gfun = function('denote#notes#list', [a:search])
call denote#loclist#fill(l:title, l:files, l:Gfun)
endfunction
" Put all notes of the given tag to the location list. The tag argument is
@@ -12,29 +100,180 @@ endfunction
function denote#notes#bytag(tag)
let l:files = glob(g:denote_directory .. '/*_' .. a:tag .. '*', 0, v:true)->filter('v:val->split("/")[-1] =~ "_' .. a:tag .. '\\(==\\|@@\\|__\\|_\\|\\.\\)"')
let l:title = 'Denote notes: ' .. a:tag
call denote#loclist#fill(l:title, l:files)
let l:Gfun = function('denote#notes#bytag', [a:tag])
call denote#loclist#fill(l:title, l:files, l:Gfun)
endfunction
" Search in denote notes
function denote#notes#grep(re)
let l:title = 'Grep results for: ' .. a:re
let l:fpat=map(copy(g:denote_note_file_extensions), {_, e -> g:denote_directory .. '/*.' .. e})->join()
execute 'silent! lvimgrep ' .. a:re .. ' ' .. l:fpat
call denote#loclist#setgrep(l:title)
exe 'silent! lvimgrep ' .. a:re .. ' ' .. l:fpat
let l:Gfun = function('denote#notes#grep', [a:re])
call denote#loclist#setgrep(l:title, l:Gfun)
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=l:identifier .. '--' .. a:title
\ ->tolower()
\ ->substitute('[^[:fname:]]\|/', '-', 'g')
\ ->substitute('-\+', '-', 'g')
\ ->substitute('_\+', '_', 'g')
\ ->substitute('=\+', '=', 'g')
\ ->substitute('@\+', '@', 'g')
\ ->trim('-_@=') .. '.' .. a:ft
execute 'edit ' .. g:denote_directory .. '/' .. l:fn
call setline(1, denote#frontmatter#new(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, libdenote#fm_gen(a:ext, l:identifier, a:title, [], g:denote_fm_md_type))
endfunction
" Function to set the title of the selected entry
function denote#notes#settitle(linenr, title)
" Get file first!
let l:items = getloclist(0, {'items': 1})['items']
if empty(l:items)
return
endif
let l:item = l:items[a:linenr-1]
let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr)
let l:meta = libdenote#scheme_metadata(l:filename)
let l:ext = fnamemodify(l:filename, ':e')
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 bufload(l:filename)
let l:frontmatter = s:getfrontmatter(l:filename)
if empty(l:frontmatter)
" Write fresh front matter
let l:frontmatter = libdenote#fm_gen(l:ext, l:meta.id, l:meta.title, l:meta.tags, g:denote_fm_md_type)
call add(l:frontmatter, '')
call appendbufline(l:filename, 0, l:frontmatter)
else
" Modify front matter
let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'title': a:title})
call setbufline(l:filename, 1, l:frontmatter)
endif
let curl = line('.')
call denote#loclist#jumptowindow()
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
else
if fnamemodify(l:filename, ':t') == fnamemodify(l:newfilename, ':t')
return
endif
call rename(l:filename, l:newfilename)
endif
endfunction
" Function to add or remove a tag to the selected entries. The last argument
" a:add, is set to v:true to add, and v:false to remove.
function denote#notes#tagmod(line1, line2, tag, add)
" Get file first!
let l:items = getloclist(0, {'items': 1})['items']
if empty(l:items)
return
endif
for i in range(a:line1, a:line2)
let l:item = l:items[i-1]
let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr)
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:meta.tags, a:tag)
else
if l:idx == -1
continue
endif
call remove(l:meta.tags, l:idx)
endif
let l:ext = fnamemodify(l:filename, ':e')
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 bufload(l:filename)
let l:frontmatter = s:getfrontmatter(l:filename)
if empty(l:frontmatter)
" Write fresh front matter
let l:frontmatter = libdenote#fm_gen(l:ext, l:meta.id, l:meta.title, l:meta.tags, g:denote_fm_md_type)
call add(l:frontmatter, '')
call appendbufline(l:filename, 0, l:frontmatter)
else
" Modify front matter
let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'tags': l:meta.tags})
call setbufline(l:filename, 1, l:frontmatter)
endif
let curl = line('.')
call denote#loclist#jumptowindow()
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')
return
endif
call rename(l:filename, l:newfilename)
endif
endfor
endfunction
" Add file to denote directory
function denote#notes#copy(origfile)
if !filereadable(a:origfile)
echohl WarningMsg
echom 'Cannot copy specified file to denote directory.'
return
endif
" Derive title from 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 = 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 ' .. fnameescape(l:filename)
call appendbufline(l:filename, 0, libdenote#fm_gen(l:ext, l:identifier, l:title), [], g:denote_fm_md_type)
exe 'w'
endif
endfunction
" Delete notes from denote directory
function denote#notes#rm(line1, line2, bang)
let l:items = getloclist(0, {'items': 1})['items']
if empty(l:items)
return
endif
for i in range(a:line1, a:line2)
let l:item = l:items[i-1]
let l:bufnr = l:item['bufnr']
let l:filename = bufname(l:bufnr)
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
continue
endif
endif
" Wipe buffer, if it exists
if bufexists(l:filename)
exe 'silent bwipe ' .. fnameescape(l:filename)
endif
" Delete file
call delete(l:filename)
endfor
endfunction

202
autoload/libdenote.vim Normal file
View File

@@ -0,0 +1,202 @@
" libdenote plugin for vim
" This plugin describes basic denote functions. Each one of these functions
" deals wither with the file-naming scheme (prefixed with scheme_), or with the
" front matter (prefixed with fm_). All functions are pure, i.e., have no side
" affects.
"
" FILE-NAMING SCHEME {{{1
"
" Script-local functions {{{2
"
" 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
" API {{{2
" Identifier creation
" This unction generates a fresh denote identifier
function libdenote#scheme_idgen()
return exists('*strftime')
\ ? strftime('%Y%m%dT%H%M%S')
\ : rand()
endfunction
" Generate file name
" This function returns the Filename given all components in the file-naming
" scheme
"
" @argument a:ext string: File extension
" @argument a:id string: Identifier of denote entry
" @argument a:title string: Title of denote entry
" @argument a:tags list[string]: List of strings that specify the tags
" @argument a:sig string: Signature string of denote entry
function libdenote#scheme_filename(ext, identifier, title='', tags=[], sig='')
let l:f = s:santizefnpart(a:identifier)
let l:f ..= len(a:sig) > 0 ? ('==' .. s:santizefnpart(a:sig)) : ''
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
" Retrieve metadata from file name
" This function returns a dict with the entries 'id', 'title', 'tags', and
" 'sig.' The value type of 'tags' is a list, the other entries hold strings.
"
" @argument a:filename string: Filename or path to file
function libdenote#scheme_metadata(filename)
return {
\ 'id': a:filename->fnamemodify(':t')->matchstr('@@\zs.\{-\}\ze\(==\|--\|__\|\..\)')
\ ?? a:filename->fnamemodify(':t')->matchstr('^.\{-\}\ze\(==\|--\|__\|\..\)'),
\ 'title': a:filename->fnamemodify(':t')->matchstr('--\zs.\{-\}\ze\(==\|@@\|__\|\..\)')
\ ->substitute('-', ' ', 'g'),
\ 'tags': a:filename->fnamemodify(':t')->matchstr('__\zs.\{-\}\ze\(==\|@@\|--\|\..\)')
\ ->split('_'),
\ 'sig': a:filename->fnamemodify(':t')->matchstr('==\zs.\{-\}\ze\(==\|@@\|__\|\..\)')
\}
endfunction
" Get path to file from denote identifier
" This function returns the path to the file the note corresponds to, if it
" exists, and v:false otherwise.
"
" @argument a:dir string: Path to denote directory
" @argument a:id string: Identifier of denote entry
function libdenote#scheme_find(dir, id)
" According to the file-naming scheme, the note id is prefixed with '@@'. If
" the note id appears at the beginning of the file name, 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(a:dir .. '/*' .. a:id .. '*', 0, v:true)
\ ->filter('v:val->split("/")[-1] =~ "' .. a:id .. '\\(==\\|--\\|__\\|\\.\\)"')
\ ->filter('v:val->split("/")[-1] =~ "^' .. a:id .. '\\|@@' .. a:id .. '"')
return empty(l:files) ? v:false : l:files[0]
endfunction
" FRONT-MATTER HANDLING {{{1
"
" Script-local functions {{{2
"
" 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
" API {{{2
" Create front matter
" This function generates a front matter. It may use the global variable
" g:denote_fm_md_type.
"
" @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]: List of strings that specify the tags
" @argument a:md_type string: Any of 'yaml' (default) or 'toml', for
" markdown front matter
" @return: List of strings with a line-per item that describes the front matter
function libdenote#fm_gen(ext, id, title, tags, md_type='yaml')
return a:ext == 'org' ? s:org_new(a:id, a:title, a:tags)
\ : a:ext == 'txt' ? s:plain_new(a:id, a:title, a:tags)
\ : a:md_type == 'toml' ? s:md_new_toml(a:id, a:title, a:tags)
\ : s:md_new_yaml(a:id, a:title, a:tags)
endfunction
" Alter front matter
" 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: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, 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_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 : [],
\ g:denote_fm_md_type)
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 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 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

View File

@@ -1,17 +1,34 @@
*denote.txt* For Vim version 9.0. Last change: 2026 Feb 26
*denote.txt* Handling denote entries the vim way
This is the documentation for the denote plugin.
Last change: 2026 Mar 4
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*
1. Introduction |denote-intro|
2. Commands |denote-commands|
2.1 Universal commands |denote-universal-commands|
2.2 Note command |denote-note-command|
2.3 Denote-list commands |denote-list-commands|
3. Settings |denote-settings|
4. Keys |denote-keys|
Appendices
A. The libdenote plugin |libdenote|
A.1 File-naming functions |libdentoe-filenaming|
A.2 Front-matter functions |libdenote-frontmatter|
B. Package API |denote-api|
==============================================================================
*denote-intro*
Introduction ~
INTRODUCTION *denote-intro*
Denote is a file-naming scheme developed by Protesilaos Stavrou and an Emacs
tool for handling such files. Notes and other files that follow this scheme
@@ -20,24 +37,36 @@ do adjusting the tags or changing the title of a note. The official manual is
available online:
https://protesilaos.com/emacs/denote
The denote plugin adds denote functionality to vim --- the vim way. We are
aware of two other plugins, but we fear that these plugins are not flexible
enough, and more importantly, do not follow the vim philosophy for handling
denote notes. These mentioned plugins are available here:
The denote plugin adds denote functionality to vim the vim way. We are aware
of two other plugins, but we fear that these plugins are not flexible enough,
and more importantly, do not follow the vim philosophy for handling denote
notes. These mentioned plugins are available here:
https://github.com/shuckster/denote-md
https://git.sr.ht/~ashton314/vim-denote
In contrast to these plugins, the present package relies on the
|location-list| features for displaying denote entries, and on the options
|'quickfixtextfunc'|, |'includeexpr'|, and |'omnifunc'|. With these, denote
links can be followed using |gf|, and completed using omni comletion (see,
'quickfixtextfunc', 'includeexpr', and 'omnifunc'. With these, denote links
can be followed using |gf|, and completed using omni completion (see,
|compl-omni|). For link completion, press |i_CTRL-X_CTRL-O| after typing any
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.
*denote-commands*
Commands ~
==============================================================================
COMMANDS *denote-commands*
After loading this package, only the first of the following commands is
available. This first command, as descried below, sets the directory to be
used for the denote entries. After that command has been issued, several other
commands become possible, such as |:DenoteTag|. Some command are available
within any window, others only in denote notes, and yet other again only in
the location-list that lists denote entries.
*denote-universal-commands*
Universal commands~
*:DenoteDirectory*
*:DenoteDirectory!*
:DenoteDirectory[!] {path}
@@ -47,19 +76,22 @@ Commands ~
system. Without the bang, only directories listed in the
|g:denote_directories| variable are autocompleted.
After the denote directory has been specified, the following commands become
available. They all operate on that specified directory.
*:Denote*
:Denote [{keyword ..}]
Populate the location list of the current window with the denote
entries present in the current directory. This command may be
entries present in the specified directory. This command may be
supplemented with any number of arguments that filter the entries
according to the appearance of the keywords in the file name.
*:DenoteTag*
:DenoteTag {tag}
*:DenoteByTag*
:DenoteByTag {tag}
This command takes as argument a tag, and populates the location list
with all denote entries of that are tagged accordingly. The tags are
autocompleted (see, |c_<Tab>|). Also fuzzy matching is possible when
"fuzzy" is contained in |'wildoptions'|.
"fuzzy" is contained in 'wildoptions'.
*:DenoteGrep*
:DenoteGrep /{pattern}/[g][j][f]
@@ -73,14 +105,67 @@ Commands ~
denote entry with the specified title. The entry file type is
controlled by the setting |g:denote_new_ft|.
*:DenoteCopy*
:DenoteCopy {file}
With this, the specified file is copied into the denote directory. The
file name is taken as the title, and the identifier is freshly
generated. Any file, i.e., not only those specified using
|g:denote_note_file_extension| may be chosen.
*denote-note-command*
Note command~
This is the only command exclusive to the windows that hold denote note
entries, i.e., files with an extension in |g:denote_note_file_extension|.
*:DenoteBackReferences*
:DenoteBackReferences
This command populates the location list with all references to the
current note. This command can only be called from an opened denote
note.
*denote-settings*
Settings ~
*denote-list-commands*
Denote-list commands~
The denote list may be opened using one of the commands |:Denote| or
|:DenoteByTag|. Also the commands |:DenoteGrep| and |:DenoteBackReferences|
open the location list, but not as a list of entries, but as a list of
matching patterns. The following commands are available in the denote list
(first case).
*:DenoteSetTitle*
:DenoteSetTitle {string}
The title of the selected entry is changed as specified.
*:DenoteTagAdd*
:DenoteTagAdd {tag}
With this, the provided tag is added to the list of tags of the
selected entry. The tag may be autocompleted. This command also
accepts a range, resulting in adding the tag to every entry within
that range.
*:DenoteTagRm*
:DenoteTagRm {tag}
Just as above, but for removing the specified tag.
*:DenoteDelete*
:DenoteDelete[!]
With this, the selected entry is deleted from the denote directory
(and from the disk). This command also accepts a range, to delete a
bunch of entries. After the deletion command has been invoked, the
user is asked to confirm the deletion. With the optional bang, no
confirmation will be asked.
==============================================================================
SETTINGS *denote-settings*
All settings described below are set to default values. For this package to
function, it is not necessary to specify a setting manually.
*g:denote_directories*
g:denote_directories list
With this option, may may specify a list of your denote directories.
@@ -134,13 +219,203 @@ g:Denote_identifier_fun Funcref
>
let g:Denote_identifier_fun = function('denote#meta#identifier_generate')
<
which calls |strftime('%Y%m%dT%H%M%S')| if |strftime()| is available, and
|rand()| otherwise. Another reasonable setting would be to use some uuid
generator and to specify, e.g.,
which calls |strftime('%Y%m%dT%H%M%S')| if |strftime()| is available,
and |rand()| otherwise. Another reasonable setting would be to use
some uuid generator and to specify, e.g.,
>
let g:Denote_identifier_fun = { -> system('uuidgen')
\ ->substitute('[^[:xdigit:]]', '', 'g') }
<
==============================================================================
KEYS *denote-keys*
The location lists generated by the present package, e.g., through the command
|:DenoteGrep| or |:DenoteByTag|, have the following key key-bindings.
*denote-r*
r Reload the location list.
*denote-q*
q Close the location list.
In addition, denote lists (see |denote-list-commands|) come with the following
keys:
*denote-list-C*
C Change the title of the selected entry.
*denote-list-+*
+ Add a tag to the selected entries (also in visual mode).
*denote-list--*
- As |denote-list-+| but for removing tags.
*denote-list-dd*
dd Delete the selected entry.
{Visual}d Delete the visually selected entries.
*denote-list-o*
o Open the file using the system "open" command.
==============================================================================
APPENDIX A - THE LIBDENOTE PLUGIN *libdenote*
The present package has as constituent the libdenote plugin for basic
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 fm_, see
|libdenote-frontmatter|).
*libdenote-filenaming*
File-naming functions~
*libdenote#scheme_idgen()*
libdenote#scheme_idgen()
This function generates a new identifier. If the function |strftime()|
is available, than strftime(%Y%m%dT%H%M%S) is used, otherwise a random
identifier is generated using |rand()|.
*libdenote#scheme_filename()*
libdenote#scheme_filename({ext}, {id}, {title}, {tags}, {sig})
With this, the file name of the denote entry with the given metadata
is returned. The parameter {ext} describes the extension of the file
is is one of 'md', 'txt', or 'org'. The parameter {id} is the denote
identifier, possibly generated via |libdenote#scheme_idgen()|. The
{title} parameter is optional and describes the title of the note. By
default, it is set to the empty string ''. The {tags} parameter is
optional as well, is a list of tags associated to the note. The
default value is the empty list []. Also, the {sig} parameter is
optional, and used to describe the signature of the entry.
*libdenote#scheme_metadata()*
libdenote#scheme_metadata({filename})
This function returns the metadata of the file given by {filename}.
This parameter may describe the path to the file, or the tail only.
The returned |dict| has the keys 'id' for the identifier, 'title' for
the title, 'tags' for the list of tags, and 'sig' for the signature.
*libdenote-frontmatter*
Front-matter functions~
*libdenote#fm_gen()*
libdenote#fm_gen({ext}, {id}, {title}, {tags}, {mdtype})
This function returns the list of lines for the front matter that
stores the given metadata. The paramter {ext} is the extension of the
file, the parameter {id} the identifier, the parameter {title}, the
title of the note, and {tags} the optional parameter as a list of tags
associated to the note. The argument {mdtype} descries the format to
be used in markdown. For markdown files (extension 'md'), two formats
are possible: 'yaml' and 'toml'. The former format is used per
default.
*libdenote#fm_alter()*
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 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.
==============================================================================
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| and |:DenoteBackReferences|
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:

View File

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