1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
--- Personal lua config files for NeoVim.
-- Last Changed: 2019-01-27
-- Author: Federico Igne <git@federicoigne.com>
-- 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: <index> - <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
|