From 5df2b799385b313c9cd297dfe6562863044ea55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Wed, 4 Mar 2026 10:56:18 +0100 Subject: [PATCH] handle variable-length front matter --- autoload/denote/notes.vim | 124 ++++++++++++++++++++++++++++++++++---- autoload/libdenote.vim | 13 +--- doc/denote.txt | 15 ++--- 3 files changed, 121 insertions(+), 31 deletions(-) diff --git a/autoload/denote/notes.vim b/autoload/denote/notes.vim index 12e1489..dd223c9 100644 --- a/autoload/denote/notes.vim +++ b/autoload/denote/notes.vim @@ -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 " empty. For improving search, white spaces are replaced by the * |wildcard|. function denote#notes#list(search) @@ -35,7 +122,7 @@ function denote#notes#new(title, ext=g:denote_new_ext) 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)) + 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 @@ -56,11 +143,18 @@ function denote#notes#settitle(linenr, title) if index(g:denote_note_file_extensions, l:ext) >= 0 " Handle front matter 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('.') + 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 @@ -108,10 +202,18 @@ function denote#notes#tagmod(line1, line2, tag, add) if index(g:denote_note_file_extensions, l:ext) >= 0 " Handle front matter 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('.') + 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 @@ -145,7 +247,7 @@ function denote#notes#copy(origfile) if index(g:denote_note_file_extensions, l:ext) >= 0 call denote#loclist#jumptowindow() exe 'edit ' .. 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' endif endfunction diff --git a/autoload/libdenote.vim b/autoload/libdenote.vim index 339197f..92231f8 100644 --- a/autoload/libdenote.vim +++ b/autoload/libdenote.vim @@ -155,7 +155,7 @@ endfunction " @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') +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) @@ -180,7 +180,8 @@ function libdenote#fm_alter(fm, mod) 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 : []) + \ 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}) @@ -199,11 +200,3 @@ function libdenote#fm_alter(fm, mod) endif return l:new endfunction - -" Returns the length of a front matter (number of lines) -" @argument a:ext string: File extension -function libdenote#fm_len(ext) - return a:ext == 'org' ? 4 - \ : a:ext == 'txt' ? 5 - \ : 6 -endfunction diff --git a/doc/denote.txt b/doc/denote.txt index 498b4c8..74ffede 100644 --- a/doc/denote.txt +++ b/doc/denote.txt @@ -299,15 +299,15 @@ libdenote#scheme_metadata({filename}) *libdenote-frontmatter* Front-matter functions~ *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 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. Per default {tags} is set to the empty list - []. Finally, {md_type} 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. + 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}) @@ -321,11 +321,6 @@ libdenote#fm_alter({fm}, {mod}) 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*