--- Personal lua config files for NeoVim. -- Last Changed: 2019-01-27 -- Author: Federico Igne -- License: This file is placed in the public domain. local zettel = {} local api = vim.api local cmd = vim.cmd local exec = api.nvim_command local base = vim.env.NOTES --- Base directory of the Zettelkasten zettel.base = base --- Gets title of a zettel given the file -- -- @param file name of the zettel file -- @return title of the zettel -- -- @note this assumes the second line of the zettel has the following -- structure -- title: - local function get_title(file) local fp = io.open(base .. '/' .. file) fp:read() local line2 = fp:read() fp:close() if line2 then local _,t = line2:find(" - ", 1, true) if t then return line2:sub(t+1) end end return "[Not a Zettel]" end --- Generate the markdown link to a given zettel -- -- @param index index of the zettel -- @param linenr optional line number. Can be `nil` (no line number), a -- positive integer (line number), a negative integer (current -- line number). -- @return markdown link of the zettel local function markdown_link(index, linenr) local linenr = linenr if not linenr then linenr = '' elseif linenr >= 0 then linenr = ' ' .. linenr else linenr = ' ' .. vim.api.nvim_win_get_cursor(0).row end return '[' .. index .. '](' .. index .. '.md' .. linenr .. ')' end --- Inserts a link to the given zettel in the current buffer -- -- @param index index of the zettel -- @param linenr optional line number. can be `nil` (no line number), a -- positive integer (line number), a negative integer (current -- line number). -- @return inserted link local function breadcrumb(index, linenr) local link = markdown_link(index, linenr) exec('normal a' .. link .. ' ') return link end --- Telescope action that inserts a markdown link and yanks. -- -- @see Telescope documentation for more info on the signature. local function yank_and_insert(line) return function(prompt_bufnr) local actions = require 'telescope.actions' local actions_state = require 'telescope.actions.state' -- Close Telescope buffer actions.close(prompt_bufnr) -- Get current entry from Telescope local entry = actions_state.get_selected_entry() -- Get line number local linenr = nil if line and entry.lnum then linenr = entry.lnum end -- Yank to `z` and insert the markdown link to the entry vim.fn.setreg('z', breadcrumb(entry.value, linenr)) end end --- Create a new zettel -- -- @param bang leave a link to the new note where the cursor was -- originally (if set to > 0). -- @param mods mods applied to the new split (see `:h <mods>`) -- @param ... title of the new zettel function zettel.new(bang, mods, ...) local index = vim.fn.strftime('%Y%m%d%H%M') local fullpath = base .. '/' .. index .. '.md' -- Leave breadcrumb (link to new note) if bang > 0 then breadcrumb(index) end -- Edit the note in a new window (and change the local pwd) exec(mods .. ' new +lcd\\ ' .. base .. ' ' .. fullpath) -- Load template exec('0read ' .. base .. '/.template.md') -- Fill in title local title = table.concat({...}, ' ') if title:len() == 0 then title = '<+title+>' end exec('2s/^title:.*$/title: ' .. index .. ' - ' .. title .. '/') exec('nohlsearch') end --- Open the Zettelkasten index. -- -- @param mods mods applied to the new split (see `:h <mods>`) function zettel.kasten(mods) -- Edit the note in a new window (and change the local pwd) local fullpath = base .. '/index.md' exec(mods .. ' new +lcd\\ ' .. base .. ' ' .. fullpath) end --- Grab a markdown link of the current open zettel -- -- @param line if > 0 include the current line in the link -- @param reg yank to specific register (defaults to `z`) function zettel.link_yank(line, reg) local index = vim.fn.expand('%:t:r') local reg = reg if not reg or reg:len() == 0 then reg = 'z' end local linenr = nil if line > 0 then linenr = -1 end vim.fn.setreg(reg, markdown_link(index, linenr)) end --- (File) Telescope into the zettelkasten -- -- Modifies version of the `find_files` builtin to search into the -- zettelkasten. function zettel.find_files() require('telescope.builtin').find_files { -- Search in zettelkasten cwd = base, -- Do not search recursively -- Note that this assumes `rg` is available in the system find_command = { 'rg', '--files', '--glob', '/*.md' }, -- Additional mappings attach_mappings = function(prompt_bufnr, map) map('i', '<C-y>', yank_and_insert(false)) return true end, -- Custom entry maker entry_maker = function(entry) local title = get_title(entry) return { value = entry:gsub('%.md$', ''), -- entry value (see actions) path = base .. '/' .. entry, -- path used for preview text = title, display = title, -- displayed on the matching list ordinal = title, -- what the prompt is matched against } end, } end --- (Grep) Telescope into the zettelkasten -- -- Modifies version of the `live_grep` builtin to search into the -- zettelkasten. function zettel.live_grep(mappings) require('telescope.builtin').live_grep { -- Search in zettelkasten cwd = base, -- Do not search recursively glob_pattern = '/*.md', -- Additional mappings attach_mappings = function(prompt_bufnr, map) map('i', '<C-y>', yank_and_insert(false)) map('i', '<C-Y>', yank_and_insert(true)) return true end } end --- Returns the path of the resource folder for the current zettel. function zettel.resources() return 'resources/' .. vim.fn.expand('%:t:r') .. '/' end --- Opens the resource path in nnn. -- -- It creates the necessary directory structure if necessary. function zettel.open_resources() local dir = zettel.base .. '/' .. zettel.resources() -- Create directory if not existent vim.fn.system({'mkdir', '-p', dir}) -- Open resource directory in nnn cmd.NnnPicker(dir) end --- Setup zettel-specific mappings and options. function zettel.setup() local map = require 'dyamon.util.map' cmd([[ command! -buffer ZResources call v:lua.dyamon.zettel.open_resources() command! -buffer ZTitle call v:lua.dyamon.zettel.hover_title() command! -bang -buffer ZLink call v:lua.dyamon.zettel.link_yank(<bang>0) ]]) map.b.nore.n(0, '<leader>zr', '<cmd>ZResources<cr>', opts) map.b.nore.n(0, 'K', '<cmd>ZTitle<cr>', opts) map.b.nore.i(0, ';zr', zettel.resources(), opts) end --- Compute inbound links of a zettel -- -- @param zettel filename of a zettel (defaults to current one) function zettel.inbound(zettel) local zettel = zettel or vim.fn.expand('%:t') return vim.fn.systemlist('rg --files-with-matches --glob /*.md "' .. zettel) end --- Compute outbound links of a zettel -- -- @param zettel filename of a zettel (defaults to current one) function zettel.outbound() local zettel = zettel or vim.fn.expand('%:t') return vim.fn.systemlist('rg -oN "[0-9]+.md" ' .. zettel .. ' | sort | uniq') end --- Statusbar indicator function zettel.status() if vim.bo.filetype:find('zettel') then return ' <' .. table.getn(zettel.inbound()) .. ',' .. table.getn(zettel.outbound()) .. '> ' end return '' end return zettel