From caf21ab06000b6b992c1ff2357485a9280a07a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=84min=20Baumeler?= Date: Wed, 25 Feb 2026 15:32:05 +0100 Subject: [PATCH] refacotred and split --- {after => after.bak}/ftplugin/markdown.vim | 0 {after => after.bak}/ftplugin/org.vim | 0 .../qf.vim => after.bak/ftplugin/qf.vim.bak | 0 autoload/denote/commands.vim | 32 +++ autoload/denote/completion.vim | 2 +- autoload/denote/ft.vim | 40 ++++ autoload/denote/loclist.vim | 35 ++- autoload/denote/meta.vim | 2 +- autoload/denote/notes.vim | 40 ++++ plugin/denote.vim | 213 ++++++------------ 10 files changed, 207 insertions(+), 157 deletions(-) rename {after => after.bak}/ftplugin/markdown.vim (100%) rename {after => after.bak}/ftplugin/org.vim (100%) rename after/ftplugin/qf.vim => after.bak/ftplugin/qf.vim.bak (100%) create mode 100644 autoload/denote/commands.vim create mode 100644 autoload/denote/ft.vim create mode 100644 autoload/denote/notes.vim diff --git a/after/ftplugin/markdown.vim b/after.bak/ftplugin/markdown.vim similarity index 100% rename from after/ftplugin/markdown.vim rename to after.bak/ftplugin/markdown.vim diff --git a/after/ftplugin/org.vim b/after.bak/ftplugin/org.vim similarity index 100% rename from after/ftplugin/org.vim rename to after.bak/ftplugin/org.vim diff --git a/after/ftplugin/qf.vim b/after.bak/ftplugin/qf.vim.bak similarity index 100% rename from after/ftplugin/qf.vim rename to after.bak/ftplugin/qf.vim.bak diff --git a/autoload/denote/commands.vim b/autoload/denote/commands.vim new file mode 100644 index 0000000..e0a38a9 --- /dev/null +++ b/autoload/denote/commands.vim @@ -0,0 +1,32 @@ +" This function is used to autocomplete the tags in user commands. +function s:tagList(ArgLead, cmdLine, CursorPos) + let files=glob(g:denote_directory .. "/*", 0, v:true) + let tags=[] + for f in files + let tags=extend(tags, denote#meta#noteTagsFromFile(f)) + endfor + return uniq(sort(tags))->join("\n") +endfunction + + +" Public commands +function denote#commands#load() + if exists('g:denote_commands_loaded') && g:denote_commands_loaded + return + endif + " Register user commands + command -nargs=* Denote call denote#notes#list() | lcl | lw + command -nargs=1 -complete=custom,s:tagList DenoteByTag call denote#notes#bytag() | lcl | lw + command -nargs=+ DenoteGrep call denote#notes#grep() | lcl | lw + command -nargs=1 DenoteNew call denote#notes#new() + + " 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 ' .. l:aupat .. ' call denote#ft#denote()' + + " Useful key mappings + " nnoremap DenoteList :DenoteSearch:lclose:lopen:resize 20 + " nnoremap DenoteBackReferences :DenoteBackReferences:lclose:lopen:resize 20 + let g:denote_commands_loaded = v:true +endfunction diff --git a/autoload/denote/completion.vim b/autoload/denote/completion.vim index 54e189c..4f89cfb 100644 --- a/autoload/denote/completion.vim +++ b/autoload/denote/completion.vim @@ -24,7 +24,7 @@ endfunction " Return completion items given by the base function s:suggestions(base) let prefix = a:base->matchstr('^denote:\zs.*$') - let flist = glob(t:denote_directory .. (prefix ? "*" .. prefix .. "*" : "*"), 0, v:true) + let flist = glob(g:denote_directory .. "/" .. (prefix ? "*" .. prefix .. "*" : "*"), 0, v:true) let res = [] for filename in flist let noteId = denote#meta#noteIdFromFile(filename) diff --git a/autoload/denote/ft.vim b/autoload/denote/ft.vim new file mode 100644 index 0000000..03dedbc --- /dev/null +++ b/autoload/denote/ft.vim @@ -0,0 +1,40 @@ +" 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:'; 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 /\/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 + " nunmap q + return + endif + setlocal nospell + nnoremap q :lclose +endfunction diff --git a/autoload/denote/loclist.vim b/autoload/denote/loclist.vim index 51ed4cd..f755e4c 100644 --- a/autoload/denote/loclist.vim +++ b/autoload/denote/loclist.vim @@ -51,13 +51,8 @@ endfunction " Load all references to the given note into the location list. function denote#loclist#references(noteId) - if !exists('t:denote_directory') - echohl WarningMsg - echom "Denote directory not specified, see |vim-denote|." - return - endif " Populate location list - silent! execute "lvimgrep /\\/gj " .. t:denote_directory .. "*" + silent! execute "lvimgrep /\\/gj " .. g:denote_directory .. "/*" " Adjust location list: set title and specify display function let file=denote#meta#fileFromNoteId(a:noteId) let noteTitle=denote#meta#noteTitleFromFile(file) @@ -65,3 +60,31 @@ function denote#loclist#references(noteId) \ {'title': 'References to ' .. noteTitle .. ' (' .. a:noteId .. ')', \ 'quickfixtextfunc' : 'denote#loclist#textReferences'}) endfunction + +" Re-populate location list with denote entries +function denote#loclist#fill(title, files) + " Clear first + call setloclist(0, [], ' ') + " Set properties + call setloclist(0, [], 'r', + \ {'title': a:title, + \ 'quickfixtextfunc' : 'denote#loclist#textNoteList', + \ 'context' : 'denote'}) + " Populate + let l:notes=[] + for f in a:files + call add(l:notes, { + \ 'filename' : f, + \ 'lnum' : 1 + \ }) + endfor + call setloclist(0, l:notes, 'a') +endfunction + +" Specify location list as denote-grep list +function denote#loclist#setgrep(title) + call setloclist(0, [], 'r', + \ {'title': a:title, + \ 'quickfixtextfunc' : 'denote#loclist#textReferences', + \ 'context' : 'denote'}) +endfunction diff --git a/autoload/denote/meta.vim b/autoload/denote/meta.vim index 6d22ced..4a738fe 100644 --- a/autoload/denote/meta.vim +++ b/autoload/denote/meta.vim @@ -10,7 +10,7 @@ function denote#meta#fileFromNoteId(noteId) " (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 files = glob(t:denote_directory .. "*" .. a:noteId .. "*", 0, v:true) + let 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(files) ? v:false : files[0] diff --git a/autoload/denote/notes.vim b/autoload/denote/notes.vim new file mode 100644 index 0000000..6fc20e1 --- /dev/null +++ b/autoload/denote/notes.vim @@ -0,0 +1,40 @@ +" 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) +endfunction + +" Put all notes of the given tag to the location list. The tag argument is +" mandatory. +function denote#notes#bytag(tag) + let 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) +endfunction + +" Search in denote notes +function denote#notes#grep(re) + let l:title = "Grep results for: " .. a:re + let fpat=map(copy(g:denote_note_file_extensions), {_, e -> g:denote_directory .. "/*." .. e})->join() + execute "silent! lvimgrep " .. a:re .. " " .. fpat + call denote#loclist#setgrep(l:title) +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) + let identifier=denote#meta#identifier_generate() + let fn=identifier .. '--' .. a:title + \ ->tolower() + \ ->substitute('[^[:fname:]]\|/', '-', 'g') + \ ->substitute('-\+', '-', 'g') + \ ->substitute('_\+', '_', 'g') + \ ->substitute('=\+', '=', 'g') + \ ->substitute('@\+', '@', 'g') + \ ->trim('-_@=') .. '.' .. a:ft + execute "edit " .. g:denote_directory .. "/" .. fn + call setline(1, denote#frontmatter#new(a:ft, identifier, a:title)) +endfunction diff --git a/plugin/denote.vim b/plugin/denote.vim index 536c0e1..62e392e 100644 --- a/plugin/denote.vim +++ b/plugin/denote.vim @@ -1,21 +1,21 @@ -" Global configurations {{{1 +" Global configurations -" List of denote directories: +" 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 ! empty(g:denote_directories) - let t:denote_directory = g:denote_directories[0] -endif +" 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') let g:denote_note_file_extensions=['md', 'org', 'txt'] endif -" Number of columns used for the title in the location window. +" Number of columns used for the title in the location window if !exists('g:denote_loc_title_columns') let g:denote_loc_title_columns=40 endif @@ -36,169 +36,84 @@ if !exists('g:denote_identifier_fun') let g:denote_identifier_fun='' endif -" Local functions {{{1 -" Put all notes of the given tag to the location list. The tag argument is -" mandatory. -function s:DenoteNotesByTag(tag) - if !exists('t:denote_directory') - echohl WarningMsg - echom "Denote directory not specified, see |vim-denote|." - return - endif - " Clear location list - call denote#loclist#clear() - " Find files - let files = glob(t:denote_directory .. "*_" .. a:tag .. "*", 0, v:true)->filter('v:val->split("/")[-1] =~ "_' .. a:tag .. '\\(==\\|@@\\|__\\|_\\|\\.\\)"') - " Populate location list - let locTitle="Denote notes: " .. a:tag - call setloclist(0, [], 'r', - \ {'title': locTitle, - \ 'quickfixtextfunc' : 'denote#loclist#textNoteList'}) - let notes=[] - for f in files - call add(notes, { - \ 'filename' : f, - \ 'lnum' : 1 - \ }) - endfor - call setloclist(0, notes, 'a') -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 s:DenoteNotes(search) - if !exists('t:denote_directory') - echohl WarningMsg - echom "Denote directory not specified, see |vim-denote|." - return - endif - " Clear location list - call denote#loclist#clear() - " Find files - let s = substitute(" " .. a:search .. " ", " ", "*", "g") - let files = glob(t:denote_directory .. s, 0, v:true) - " Populate location list - let locTitle="Denote notes search:" .. a:search - call setloclist(0, [], 'r', - \ {'title': locTitle, - \ 'quickfixtextfunc' : 'denote#loclist#textNoteList'}) - let notes=[] - for f in files - call add(notes, { - \ 'filename' : f, - \ 'lnum' : 1 - \ }) - endfor - call setloclist(0, notes, 'a') -endfunction - -" This function implements the similar functionality of :vimgrep, but for -" denote notes. -function s:DenoteGrep(search) - if !exists('t:denote_directory') - echohl WarningMsg - echom "Denote directory not specified, see |vim-denote|." - return - endif - " Clear location list - call denote#loclist#clear() - " Grep all greppable files - let fpat=map(copy(g:denote_note_file_extensions), {_, e -> t:denote_directory .. "*." .. e})->join() - execute "silent! lvimgrep " .. a:search .. " " .. fpat - " Adjust location list: set title and specify display function - call setloclist(0, [], 'r', - \ {'title': 'Denote grep: ' .. a:search, - \ 'quickfixtextfunc' : 'denote#loclist#textReferences'}) - lclose - lopen -endfunction - -" This function is used to autocomplete the tags in user commands. -function s:tagList(ArgLead, cmdLine, CursorPos) - let files=glob(t:denote_directory .. "*", 0, v:true) - let tags=[] - for f in files - let tags=extend(tags, denote#meta#noteTagsFromFile(f)) - endfor - return uniq(sort(tags))->join("\n") -endfunction - -" This creates a new denote entry with the given title and of the given -" filetype. The title may be empty. -function s:DenoteNew(title, ft=g:denote_new_ft) - if !exists('t:denote_directory') - echohl WarningMsg - echom "Denote directory not specified, see |vim-denote|." - return - endif - let identifier=denote#meta#identifier_generate() - let fn=identifier .. '--' .. a:title - \ ->tolower() - \ ->substitute('[^[:fname:]]\|/', '-', 'g') - \ ->substitute('-\+', '-', 'g') - \ ->trim('-') .. '.' .. a:ft - execute "edit " .. t:denote_directory .. fn - call setline(1, denote#frontmatter#new(a:ft, identifier, a:title)) -endfunction - " Transform full path into canonical form WITH trailing '/' function s:canonicalFullPath(path) - let parts = split(a:path, '/') - let result = [] - for part in parts + let l:parts = split(a:path, '/') + let l:result = [] + for part in l:parts if part == '.' || part == '' continue elseif part == '..' - if len(result) > 0 - call remove(result, len(result)-1) + if len(l:result) > 0 + call remove(l:result, len(l:result)-1) endif else - call add(result, part) + call add(l:result, part) endif endfor - return empty(result) ? '/' : '/' .. join(result, '/') .. '/' + return empty(l:result) ? '/' : '/' .. join(l:result, '/') .. '/' endfunction " Compute the relative path from start to target. Both arguments are given as " full paths. function s:relativePath(start, target) - let a = s:canonicalFullPath(a:start) - let b = s:canonicalFullPath(a:target) - " Simple cases first: both paths are the same, or the target is a subpath + let l:a = s:canonicalFullPath(a:start) + let l:b = s:canonicalFullPath(a:target) + " Simple cases first: both paths are the same, or the target is l:a subpath " from the start. - if a == b[:strlen(a)-1] - return b[strlen(a):] + if l:a == l:b[:strlen(l:a)-1] + return l:b[strlen(l:a):] endif " Now, we need to go back. If the following match fails, then we need to go " back all the way - let l = matchstrpos(a .. b, '^\(/.*\)/\zs.*/\ze\1') - return l[1] == -1 - \ ? substitute(a[1:-2], '[^/]\+', '..', 'g') .. b - \ : substitute(l[0], '[^/]\+', '..', 'g') .. b[l[1]:] + let l:l = matchstrpos(l:a .. l:b, '^\(/.*\)/\zs.*/\ze\1') + return l:l[1] == -1 + \ ? substitute(l:a[1:-2], '[^/]\+', '..', 'g') .. l:b + \ : substitute(l:l[0], '[^/]\+', '..', 'g') .. l:b[l:l[1]:] endfunction " Complete all paths to the denote directories. This functions completes the -" paths to the configured directories, TODO: but also shows the directories -" accessible from the current position. -function s:denoteDirectoryList(ArgLead, CmdLine, CursorPos) - let prefix = fnamemodify(a:ArgLead ?? '/', ':p') - let res = [] - for dendir in g:denote_directories - if prefix == dendir[:strlen(prefix)-1] - call add(res, substitute(a:ArgLead .. '/' .. s:relativePath(prefix, dendir), '/\+', '/', 'g')) - endif - endfor - " TODO: Append all subdirectories (default directory completion) - return res->join("\n") +" paths to the configured directories. +function s:denoteDirectoryCompletion(ArgLead, CmdLine, CursorPos) + let l:res = [] + let l:bang = match(a:CmdLine, '^\S\+!') >= 0 + let l:argleaddir = isdirectory(expand(a:ArgLead)) ? a:ArgLead : matchstr(a:ArgLead, '^.*/') + if strlen(l:argleaddir) == 0 + let l:argleaddir = '~' + endif + if l:argleaddir !~ '/$' + let l:argleaddir = l:argleaddir .. '/' + endif + if l:bang + for ldir in (glob(expand(l:argleaddir) .. '/*', v:true, v:true)->filter('isdirectory(v:val)')) + call add(l:res, l:argleaddir .. fnamemodify(ldir, ':t')) + endfor + else + let l:leaddir = s:canonicalFullPath(fnamemodify(l:argleaddir, ':p')) + for ldir in g:denote_directories + if l:leaddir == ldir[:strlen(l:leaddir)-1] + call add(l:res, l:argleaddir .. s:relativePath(l:leaddir, ldir)) + endif + endfor + endif + return l:res->join("\n") endfunction -" Public commands and key mappings {{{1 -command -nargs=1 -complete=custom,denoteDirectoryList Denote let t:denote_directory = fnamemodify(, ':p') -command -nargs=* DenoteSearch call DenoteNotes() -command -nargs=1 -complete=custom,tagList DenoteTag call DenoteNotesByTag() -command -nargs=+ DenoteGrep call DenoteGrep() -command -nargs=1 DenoteNew call DenoteNew() +" Setup denote plugin, i.e., +" - set the denote directory +" - register user commands +" - register auto commands +function s:setup(dir) + " Set the denote directory + let l:tmp = s:canonicalFullPath(fnamemodify(a:dir, ':p'))[:-2] + if !isdirectory(l:tmp) + echohl WarningMsg + echom "The specified argument is not a directory (" .. a:dir .. ")" + return + endif + let g:denote_directory = l:tmp + " Register user commands and auto commands + call denote#commands#load() +endfunction -" Useful key mappings -nnoremap DenoteList :DenoteSearch:lclose:lopen:resize 20 -nnoremap DenoteBackReferences :DenoteBackReferences:lclose:lopen:resize 20 +command -nargs=1 -bang -complete=custom,s:denoteDirectoryCompletion DenoteDirectory call s:setup()