handle variable-length front matter

This commit is contained in:
2026-03-04 10:56:18 +01:00
parent 320bc0b6c0
commit 5df2b79938
3 changed files with 121 additions and 31 deletions

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)
let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'title': a:title}) " Write fresh front matter
call setbufline(l:filename, 1, l:frontmatter) let l:frontmatter = libdenote#fm_gen(l:ext, l:meta.id, l:meta.title, l:meta.tags, g:denote_fm_md_type)
let curl=line('.') 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() 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)
let l:frontmatter = libdenote#fm_alter(l:frontmatter, {'tags': l:meta.tags}) if empty(l:frontmatter)
call setbufline(l:filename, 1, l:frontmatter) " Write fresh front matter
let curl=line('.') 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() call denote#loclist#jumptowindow()
exe 'silent buf ' .. l:bufnr exe 'silent buf ' .. l:bufnr
exe 'silent file ' .. l:newfilename exe 'silent file ' .. l:newfilename
@@ -145,7 +247,7 @@ function denote#notes#copy(origfile)
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 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

@@ -299,15 +299,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 +321,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*