DEV Community

Mirko Vukušić
Mirko Vukušić

Posted on

VimWiki: how to automate wikis per project folder (Neovim)

Neovim is grabbing more and more pieces of my workflow from other systems, and last two pieces are notes and tasks.
Org-mode plugins are great, but focused more on tasks.

However, one small thing almost made me adopt org-mode instead of VimWiki. That is ease of creating .org files anywhere, and that usually means in many of my project folders. I like to have all my project related stuff inside individual project folders, leave it or hide it from source control, backup, sync, ... I don't like scattered files.

Of course, VimWiki can have multiple wikis... just add them to your config. Ah, not my preferred way of managing any list. Who wants to edit wiki config manually every time there is a new project.

So this is what I did to automate it. Please note it requires Neovim (script is written in Lua), Linux (or better to say find which I use for searching folders, and optionally Fzf (but this can be easily changed).

General idea

... is to use find to search for a wiki folder recursively in projects_folder. Then, result list is used to dynamically populate VimWiki global variable containing list of wikis (g:vimwiki_list) and tell VimWiki to refresh the list. Now, VimWiki's <Leader>ws is populated with project wikis! Wikis defined in config are there too, so you can still manually add wikis outside of projects folder if you want.

Using VimWiki's original wiki search feature

Minimal code for the above is:

-- configuration
local config = {
    projectsFolder = '/home/your_user/work/', --full path without ~
    maxDepth = 3,
    ignoreFolders = { 'node_modules', '.git' },
    rootWikiFolder = '_wiki',
    wikiConfig = { syntax='markdown', ext='.md' }
}

-- store original vimwiki_list config, we will need it later
-- !!!make sure vimwiki plugin is loaded before running this!!!
if vim.g.vimwiki_list == nil then
    error('VimWiki not loaded yet! make sure VimWiki is initialized before my-projects-wiki')
else
    _G.vimwiki_list_orig = vim.fn.copy(vim.g.vimwiki_list) or {}
end

-- function to update g:vimwiki_list config item from list of subfolder names (append project wikis)
--   this way, orginal <Leader>ws will get new project wikis in the list and also keep ones from config
local function updateVimwikiList(folders)
    local new_list = vim.fn.copy(vimwiki_list_orig)
    for _, f in ipairs(folders) do
        local item = {
            path = config.projectsFolder..f,
            syntax = config.wikiConfig.syntax,
            ext = config.wikiConfig.ext
        }
        table.insert(new_list, item)
    end
    vim.g.vimwiki_list = new_list
    vim.api.nvim_call_function('vimwiki#vars#init',{})
end

-- function to search project folders for root wiki folders (returns system list)
local function searchForWikis()
    local command = 'find ' .. config.projectsFolder ..
        ' -maxdepth ' .. config.maxDepth
    if #config.ignoreFolders > 0 then command = command .. " \\(" end
    for _, f in ipairs(config.ignoreFolders) do
        command = command .. " -path '*/"..f.."/*' -prune"
        if next(config.ignoreFolders,_) == nil then
            command = command .. " \\) -o"
        else
            command = command .. " -o"
        end
    end
    command = command .. ' -type d -name ' .. config.rootWikiFolder
    command = command .. ' -print | '
    command = command .. ' sed s#' .. config.projectsFolder .. '##'
    local list = vim.api.nvim_call_function('systemlist', {command})
    return list
end

Enter fullscreen mode Exit fullscreen mode

Now, you just have to call updateVimwikiList(searchForWikis()) somewhere to populate VimWiki with project wikis. There are so many options to do this. You can define a new command to do it manually, you can remap VimWiki's <Leader>ws to execute it before displaying a list of wikis, or you can simply call it at the bottom of the script to refresh the list on Neovim start (downside of this method is that new _wiki folders will not be detected till you restart).

Extend it with Fzf fuzzy-find menu

However, I took a different approach. Script is really fast, so I don't mind running it every time I'm searching for wikis. Also, I use Fzf fuzzy-finder. So I decided to feed the list to Fzf for a nice fuzzy-find menu. This will replace default VimWiki's wiki select feature which is not very nice, especially for larger number of projects.

Here is the additional code, just below the above code:

-- wrapper for Vimwiki's goto_index() to bypass :VimWikiUISelect and use FZF instead
-- if wiki is passed to the function, index page is opened directly, bypassing FZF
function _G.ProjectWikiOpen(name)
    -- show fzf wiki search if no wiki passed
    if not name then
        local wikis = searchForWikis()
        updateVimwikiList(searchForWikis())
        for _,f in ipairs(vimwiki_list_orig) do table.insert(wikis,f.path) end
        local options = {
            sink = function(selected) ProjectWikiOpen(selected) end,
            source = wikis,
            options = '--ansi --reverse --no-preview',
            window = {
                width = 0.3,
                height = 0.6,
                border = 'sharp'
            }
        }
        vim.fn.call('fzf#run', {options})
    else
        for i, v in ipairs(vim.g.vimwiki_list) do
            if v.path == name or v.path == config.projectsFolder..name then
                vim.fn.call('vimwiki#base#goto_index',{i})
                return
            end
        end
        print("Error. Selected project wiki not found")
    end
end


-- add commands
vim.api.nvim_command([[command! -nargs=? ProjectWikiOpen lua ProjectWikiOpen(<f-args>)]])

-- add keybindings
vim.api.nvim_set_keymap("n", "<Leader>wp", ":ProjectWikiOpen<CR>", { noremap=true })
Enter fullscreen mode Exit fullscreen mode

So, a new :ProjectWikiOpen command is registered and mapped to wp. I like this key combo better than original <Leader>ws. This leaves you with a very simple workflow... just add _wiki folder to any project and hit <Leader>wp. Fzf menu will open and it will already be there. Just select it and VimWiki takes over.

todo

1) Finish Project/Session switcher script - For me, everything is a project and everything project related is in a project folder. That's why I also have a few scripts to easily manage/switch my projects in Neovim. Those integrate project sessions (using mksession), automatic fuzzy-finder for projects (using find to search for .git folders to find roots), and Kitty terminal project launcher integrated in Dmenu which starts project environment using Kitty terminal: opens few Kitty windows and tabs with Neovim (session preloaded), LazyGit, few terminals, etc. It's almost done so hopefully I find the time to publish that too.

2) Finish synchronization with mobile phone - I want my wikis on the go too. This is almost done with Syncthing client on my laptop, server (Nextcloud) and mobile phone. Wikis are pushed and synchronized on all devices. Currently trying Obsidian Android app to read/manage wikis on the mobile side.

3) Integrate VimWiki tasks and taskwarrior (or similar)

Top comments (0)