VimWiki: how to automate wikis per project folder (Neovim)
Tags: linux, vim
Published: 2022-02-11 00:06:21

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

```

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 })
```

So, a new `:ProjectWikiOpen` command is registered and mapped to <Leader>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)*