Compare commits

...

5 Commits

6 changed files with 148 additions and 47 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 above-mentioned plugins require the denote note identifiers to follow the rigid
format `YYYYMMDDTHHMMSS`. Per denote manual, however, the identify may be [any format `YYYYMMDDTHHMMSS`. Per denote manual, however, the identify may be [any
string](https://protesilaos.com/emacs/denote#h:3048f558-7d84-45d6-9ef2-53055483e801) 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 ### Installation
You may use any of your favorite plugin managers, or, manually place a copy of 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 this package in, e.g., `~/.vim/pack/tools/start`. For such a manual
installation, you may want to run `:helptags installation, you may want to run `:helptags
~/.vim/pack/tools/start/vim-denote` to regenerate the help files. This allows ~/.vim/pack/tools/start/vim-denote/doc` to regenerate the help files. This
you to display the package documentation using `:help vim-denote` and similar allows you to display the package documentation using `:help vim-denote` and
commands. similar commands.
### Usage ### Usage
vim needs to know where your denote entries are stored. To do so, run the 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>` `denote:<str>`, then the link is completed for denote entries that have `<str>`
as part of the title. 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 [location list](https://vimhelp.org/quickfix.txt.html#location-list). The
benefit of doing so is that commands like `:lnext`, `:lprev` etc. become benefit of doing so is that commands like `:lnext`, `:lprev` etc. become
available. available.

View File

@@ -25,7 +25,7 @@ let b:loaded_denote_ftplugin_notes = 1
" Find the column where the completion starts. This must be between 1 and " Find the column where the completion starts. This must be between 1 and
" col('.'). Denote links are of this form: `denote:<identifier>`. " col('.'). Denote links are of this form: `denote:<identifier>`.
function s:column() function DenoteNoteCompleteLinkColumn()
" Get the substring from the start of the line until col('.') " Get the substring from the start of the line until col('.')
let l:x = max([1, col('.')-2]) let l:x = max([1, col('.')-2])
let l:l = getline('.')[:l:x] let l:l = getline('.')[:l:x]
@@ -48,7 +48,7 @@ function s:column()
endfunction endfunction
" Return completion items given by the base " Return completion items given by the base
function s:suggestions(base) function DenoteNoteCompleteLinkSuggestions(base)
let l:prefix = a:base->matchstr('^denote:\zs.*$') let l:prefix = a:base->matchstr('^denote:\zs.*$')
let l:flist = glob(g:denote_directory .. '/' .. (l:prefix ? '*' .. l:prefix .. '*' : '*'), 0, v:true) let l:flist = glob(g:denote_directory .. '/' .. (l:prefix ? '*' .. l:prefix .. '*' : '*'), 0, v:true)
let l:res = [] let l:res = []
@@ -68,19 +68,19 @@ function s:suggestions(base)
endfunction endfunction
" Completion function for denote links " Completion function for denote links
function s:complete_link(findstart, base) function DenoteNoteCompleteLink(findstart, base)
return a:findstart == 1 ? s:column() : s:suggestions(a:base) return a:findstart == 1 ? DenoteNoteCompleteLinkColumn() : DenoteNoteCompleteLinkSuggestions(a:base)
endfunction endfunction
setlocal omnifunc=s:complete_link() setlocal omnifunc=DenoteNoteCompleteLink
" Denote links are of the form 'denote:<note id>'; we require the column. " Denote links are of the form 'denote:<note id>'; we require the column.
setlocal isfname+=: setlocal isfname+=:
" Set the function to resolve the filename under the cursor (see |gf|). " Set the function to resolve the filename under the cursor (see |gf|).
function s:gotofile() function DenoteNoteGotoFile()
return v:fname !~ '^denote:' return v:fname !~ '^denote:'
\ ? v:fname \ ? v:fname
\ : libdenote#scheme_find(g:denote_directory, v:fname[7:]) ?? v:fname \ : libdenote#scheme_find(g:denote_directory, v:fname[7:]) ?? v:fname
endfunction endfunction
setlocal includeexpr=s:gotofile() setlocal includeexpr=DenoteNoteGotoFile()
" Back references command " Back references command
let b:meta = libdenote#scheme_metadata(expand('%:t')) let b:meta = libdenote#scheme_metadata(expand('%:t'))

View File

@@ -39,6 +39,12 @@ endif
" Denote-list specific configuration " Denote-list specific configuration
if b:context['denote'] == 'list' 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 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 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! -nargs=1 -range -buffer -complete=custom,denote#completion#tags DenoteTagRm :call denote#notes#tagmod(<line1>, <line2>, <q-args>, v:false) | :normal r
@@ -47,6 +53,7 @@ if b:context['denote'] == 'list'
nnoremap <buffer> + :DenoteTagAdd nnoremap <buffer> + :DenoteTagAdd
nnoremap <buffer> - :DenoteTagRm nnoremap <buffer> - :DenoteTagRm
nnoremap <buffer> dd :DenoteDelete<CR>r nnoremap <buffer> dd :DenoteDelete<CR>r
nnoremap <buffer> o :call OpenDenoteEntry()<CR>
xnoremap <buffer> + :DenoteTagAdd xnoremap <buffer> + :DenoteTagAdd
xnoremap <buffer> - :DenoteTagRm xnoremap <buffer> - :DenoteTagRm
xnoremap <buffer> d :DenoteDelete<CR> xnoremap <buffer> d :DenoteDelete<CR>

View File

@@ -1,3 +1,90 @@
" 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 " 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|. " empty. For improving search, white spaces are replaced by the * |wildcard|.
function denote#notes#list(search) function denote#notes#list(search)
@@ -35,7 +122,7 @@ function denote#notes#new(title, ext=g:denote_new_ext)
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, libdenote#fm_gen(a:ext, l:identifier, a:title)) call setline(1, libdenote#fm_gen(a:ext, l:identifier, a:title, [], g:denote_fm_md_type))
endfunction endfunction
" Function to set the title of the selected entry " Function to set the title of the selected entry
@@ -56,11 +143,18 @@ function denote#notes#settitle(linenr, title)
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 bufload(l:filename) call bufload(l:filename)
let l:frontmatter = getbufline(l:filename, let l:frontmatter = s:getfrontmatter(l:filename)
\ 1, 1 + libdenote#fm_len(fnamemodify(l:filename, ':e'))) 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}) let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'title': a:title})
call setbufline(l:filename, 1, l:frontmatter) call setbufline(l:filename, 1, l:frontmatter)
let curl=line('.') endif
let curl = line('.')
call denote#loclist#jumptowindow() call denote#loclist#jumptowindow()
exe 'silent buf ' .. l:bufnr exe 'silent buf ' .. l:bufnr
exe 'silent file ' .. l:newfilename exe 'silent file ' .. l:newfilename
@@ -108,10 +202,18 @@ function denote#notes#tagmod(line1, line2, tag, add)
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 bufload(l:filename) call bufload(l:filename)
let l:frontmatter = getbufline(l:filename, 1, 1 + libdenote#fm_len(fnamemodify(l:filename, ':e'))) 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}) let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'tags': l:meta.tags})
call setbufline(l:filename, 1, l:frontmatter) call setbufline(l:filename, 1, l:frontmatter)
let curl=line('.') endif
let curl = line('.')
call denote#loclist#jumptowindow() call denote#loclist#jumptowindow()
exe 'silent buf ' .. l:bufnr exe 'silent buf ' .. l:bufnr
exe 'silent file ' .. l:newfilename exe 'silent file ' .. l:newfilename
@@ -144,8 +246,8 @@ function denote#notes#copy(origfile)
" 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 ' .. fnameescape(l:filename)
call appendbufline(l:filename, 0, libdenote#fm_gen(l:ext, l:identifier, l:title)) call appendbufline(l:filename, 0, libdenote#fm_gen(l:ext, l:identifier, l:title), [], g:denote_fm_md_type)
exe 'w' exe 'w'
endif endif
endfunction endfunction

View File

@@ -155,7 +155,7 @@ endfunction
" @argument a:md_type string: Any of 'yaml' (default) or 'toml', for " @argument a:md_type string: Any of 'yaml' (default) or 'toml', for
" markdown front matter " markdown front matter
" @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_gen(ext, id, title, tags=[], md_type='yaml') function libdenote#fm_gen(ext, id, title, tags, md_type='yaml')
return a:ext == 'org' ? s:org_new(a:id, a:title, a:tags) 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: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) \ : a:md_type == 'toml' ? s:md_new_toml(a:id, a:title, a:tags)
@@ -180,7 +180,8 @@ function libdenote#fm_alter(fm, mod)
let l:repl = libdenote#fm_gen(l:ext, let l:repl = libdenote#fm_gen(l:ext,
\ has_key(a:mod, 'id') ? a:mod.id : '', \ has_key(a:mod, 'id') ? a:mod.id : '',
\ has_key(a:mod, 'title') ? a:mod.title : '', \ has_key(a:mod, 'title') ? a:mod.title : '',
\ has_key(a:mod, 'tags') ? a:mod.tags : []) \ has_key(a:mod, 'tags') ? a:mod.tags : [],
\ g:denote_fm_md_type)
if has_key(a:mod, 'date') if has_key(a:mod, 'date')
let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?date"') let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?date"')
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?date" ? l:repl[l:ididx] : v}) call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?date" ? l:repl[l:ididx] : v})
@@ -199,11 +200,3 @@ function libdenote#fm_alter(fm, mod)
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,6 +1,6 @@
*denote.txt* Handling denote entries the vim way *denote.txt* Handling denote entries the vim way
Last change: 2026 Mar 3 Last change: 2026 Mar 4
This is the documentation for the denote package. This package also introduces 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 the |libdenote| plugin in the file autoload/libdenote.vim, which may be of
@@ -257,6 +257,9 @@ C Change the title of the selected entry.
dd Delete the selected entry. dd Delete the selected entry.
{Visual}d Delete the visually selected entries. {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* APPENDIX A - THE LIBDENOTE PLUGIN *libdenote*
@@ -299,15 +302,15 @@ libdenote#scheme_metadata({filename})
*libdenote-frontmatter* *libdenote-frontmatter*
Front-matter functions~ Front-matter functions~
*libdenote#fm_gen()* *libdenote#fm_gen()*
libdenote#fm_gen({ext}, {id}, {title}, {tags}, {md_type}) libdenote#fm_gen({ext}, {id}, {title}, {tags}, {mdtype})
This function returns the list of lines for the front matter that This function returns the list of lines for the front matter that
stores the given metadata. The paramter {ext} is the extension of the stores the given metadata. The paramter {ext} is the extension of the
file, the parameter {id} the identifier, the parameter {title}, 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 title of the note, and {tags} the optional parameter as a list of tags
associated to the note. Per default {tags} is set to the empty list associated to the note. The argument {mdtype} descries the format to
[]. Finally, {md_type} descries the format to be used in markdown. For be used in markdown. For markdown files (extension 'md'), two formats
markdown files (extension 'md'), two formats are possible: 'yaml' and are possible: 'yaml' and 'toml'. The former format is used per
'toml'. The former format is used per default. default.
*libdenote#fm_alter()* *libdenote#fm_alter()*
libdenote#fm_alter({fm}, {mod}) libdenote#fm_alter({fm}, {mod})
@@ -321,11 +324,6 @@ libdenote#fm_alter({fm}, {mod})
and "tags" are used to update the title and tag list of the front and "tags" are used to update the title and tag list of the front
matter. 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* APPENDIX B - PACKAGE API *denote-api*
@@ -386,8 +384,8 @@ denote#notes#bytag({tag})
*denote#notes#grep()* *denote#notes#grep()*
denote#notes#grep({re}) denote#notes#grep({re})
This is the function used by |:DenoteGrep| to search for patterns This is the function used by |:DenoteGrep| and |:DenoteBackReferences|
within the denote files. to search for patterns within the denote files.
*denote#notes#new()* *denote#notes#new()*
denote#notes#new({title}, {ext}) denote#notes#new({title}, {ext})