mirror of
https://github.com/Ascyii/telekasten.nvim.git
synced 2026-01-01 06:14:23 -05:00
We were previously only returning a uuid if the user wanted to include them in their filenames, which meant that they couldn't be included in the template itself.
3034 lines
95 KiB
Lua
3034 lines
95 KiB
Lua
local builtin = require("telescope.builtin")
|
|
local actions = require("telescope.actions")
|
|
local action_state = require("telescope.actions.state")
|
|
local action_set = require("telescope.actions.set")
|
|
local pickers = require("telescope.pickers")
|
|
local finders = require("telescope.finders")
|
|
local conf = require("telescope.config").values
|
|
local scan = require("plenary.scandir")
|
|
local utils = require("telescope.utils")
|
|
local previewers = require("telescope.previewers")
|
|
local make_entry = require("telescope.make_entry")
|
|
local entry_display = require("telescope.pickers.entry_display")
|
|
local sorters = require("telescope.sorters")
|
|
local themes = require("telescope.themes")
|
|
local debug_utils = require("plenary.debug_utils")
|
|
local filetype = require("plenary.filetype")
|
|
local taglinks = require("taglinks.taglinks")
|
|
local tagutils = require("taglinks.tagutils")
|
|
local linkutils = require("taglinks.linkutils")
|
|
local dateutils = require("taglinks.dateutils")
|
|
local Path = require("plenary.path")
|
|
|
|
-- declare locals for the nvim api stuff to avoid more lsp warnings
|
|
local vim = vim
|
|
|
|
-- Cleans home path for Windows users
|
|
-- Needs to be before default config, else with no user config
|
|
-- home will not be cleaned and issues will still occur
|
|
local function CleanPath(path)
|
|
-- File path delimeter for Windows machines
|
|
local windows_delim = "\\"
|
|
-- Returns the path delimeter for the machine
|
|
-- '\\' for Windows, '/' for Unix
|
|
local system_delim = package.config:sub(1, 1)
|
|
local new_path_start
|
|
|
|
-- Removes portion of path before '\\' for Windows machines
|
|
-- since Telescope does not like that
|
|
if system_delim == windows_delim then
|
|
new_path_start = path:find(windows_delim) -- Find the first '\\'
|
|
if new_path_start ~= nil then
|
|
path = path:sub(new_path_start) -- Start path at the first '\\'
|
|
end
|
|
end
|
|
|
|
-- Returns cleaned path
|
|
return path
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- DEFAULT CONFIG
|
|
-- ----------------------------------------------------------------------------
|
|
local home = vim.fn.expand("~/zettelkasten")
|
|
home = CleanPath(home)
|
|
local M = {}
|
|
|
|
M.Cfg = {
|
|
home = home,
|
|
|
|
-- if true, telekasten will be enabled when opening a note within the configured home
|
|
take_over_my_home = true,
|
|
|
|
-- auto-set telekasten filetype: if false, the telekasten filetype will not be used
|
|
-- and thus the telekasten syntax will not be loaded either
|
|
auto_set_filetype = true,
|
|
|
|
-- dir names for special notes (absolute path or subdir name)
|
|
dailies = home .. "/" .. "daily",
|
|
weeklies = home .. "/" .. "weekly",
|
|
templates = home .. "/" .. "templates",
|
|
|
|
-- image (sub)dir for pasting
|
|
-- dir name (absolute path or subdir name)
|
|
-- or nil if pasted images shouldn't go into a special subdir
|
|
image_subdir = nil,
|
|
|
|
-- markdown file extension
|
|
extension = ".md",
|
|
|
|
-- prefix file with uuid
|
|
prefix_title_by_uuid = false,
|
|
-- file uuid type ("rand" or input for os.date()")
|
|
uuid_type = "%Y%m%d%H%M",
|
|
-- UUID separator
|
|
uuid_sep = "-",
|
|
|
|
-- following a link to a non-existing note will create it
|
|
follow_creates_nonexisting = true,
|
|
dailies_create_nonexisting = true,
|
|
weeklies_create_nonexisting = true,
|
|
|
|
-- skip telescope prompt for goto_today and goto_thisweek
|
|
journal_auto_open = false,
|
|
|
|
-- templates for new notes
|
|
-- template_new_note = home .. "/" .. "templates/new_note.md",
|
|
-- template_new_daily = home .. "/" .. "templates/daily_tk.md",
|
|
-- template_new_weekly = home .. "/" .. "templates/weekly_tk.md",
|
|
|
|
-- image link style
|
|
-- wiki: ![[image name]]
|
|
-- markdown: 
|
|
image_link_style = "markdown",
|
|
|
|
-- default sort option: 'filename', 'modified'
|
|
sort = "filename",
|
|
|
|
-- when linking to a note in subdir/, create a [[subdir/title]] link
|
|
-- instead of a [[title only]] link
|
|
subdirs_in_links = true,
|
|
|
|
-- integrate with calendar-vim
|
|
plug_into_calendar = true,
|
|
calendar_opts = {
|
|
-- calendar week display mode: 1 .. 'WK01', 2 .. 'WK 1', 3 .. 'KW01', 4 .. 'KW 1', 5 .. '1'
|
|
weeknm = 4,
|
|
-- use monday as first day of week: 1 .. true, 0 .. false
|
|
calendar_monday = 1,
|
|
-- calendar mark: where to put mark for marked days: 'left', 'right', 'left-fit'
|
|
calendar_mark = "left-fit",
|
|
},
|
|
close_after_yanking = false,
|
|
insert_after_inserting = true,
|
|
|
|
-- tag notation: '#tag', ':tag:', 'yaml-bare'
|
|
tag_notation = "#tag",
|
|
|
|
-- command palette theme: dropdown (window) or ivy (bottom panel)
|
|
command_palette_theme = "ivy",
|
|
|
|
-- tag list theme:
|
|
-- get_cursor: small tag list at cursor; ivy and dropdown like above
|
|
show_tags_theme = "ivy",
|
|
|
|
-- template_handling
|
|
-- What to do when creating a new note via `new_note()` or `follow_link()`
|
|
-- to a non-existing note
|
|
-- - prefer_new_note: use `new_note` template
|
|
-- - smart: if day or week is detected in title, use daily / weekly templates (default)
|
|
-- - always_ask: always ask before creating a note
|
|
template_handling = "smart",
|
|
|
|
-- path handling:
|
|
-- this applies to:
|
|
-- - new_note()
|
|
-- - new_templated_note()
|
|
-- - follow_link() to non-existing note
|
|
--
|
|
-- it does NOT apply to:
|
|
-- - goto_today()
|
|
-- - goto_thisweek()
|
|
--
|
|
-- Valid options:
|
|
-- - smart: put daily-looking notes in daily, weekly-looking ones in weekly,
|
|
-- all other ones in home, except for notes/with/subdirs/in/title.
|
|
-- (default)
|
|
--
|
|
-- - prefer_home: put all notes in home except for goto_today(), goto_thisweek()
|
|
-- except for notes/with/subdirs/in/title.
|
|
--
|
|
-- - same_as_current: put all new notes in the dir of the current note if
|
|
-- present or else in home
|
|
-- except for notes/with/subdirs/in/title.
|
|
new_note_location = "smart",
|
|
|
|
-- should all links be updated when a file is renamed
|
|
rename_update_links = true,
|
|
}
|
|
|
|
local function file_exists(fname)
|
|
if fname == nil then
|
|
return false
|
|
end
|
|
|
|
local f = io.open(fname, "r")
|
|
if f ~= nil then
|
|
io.close(f)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
local function random_variable(length)
|
|
math.randomseed(os.clock() ^ 5)
|
|
local res = ""
|
|
for _ = 1, length do
|
|
res = res .. string.char(math.random(97, 122))
|
|
end
|
|
return res
|
|
end
|
|
|
|
local function get_uuid(opts)
|
|
opts.uuid_type = opts.uuid_type or M.Cfg.uuid_type
|
|
|
|
local uuid
|
|
if opts.uuid_type ~= "rand" then
|
|
uuid = os.date(opts.uuid_type)
|
|
else
|
|
uuid = random_variable(6)
|
|
end
|
|
return uuid
|
|
end
|
|
|
|
local function concat_uuid_title(uuid, title)
|
|
local sep = M.Cfg.uuid_sep or "-"
|
|
if uuid == nil or not M.Cfg.prefix_title_by_uuid then
|
|
return title
|
|
else
|
|
return uuid .. sep .. title
|
|
end
|
|
end
|
|
|
|
local function print_error(s)
|
|
vim.cmd("echohl ErrorMsg")
|
|
vim.cmd("echomsg " .. '"' .. s .. '"')
|
|
vim.cmd("echohl None")
|
|
end
|
|
|
|
local function check_dir_and_ask(dir, purpose)
|
|
local ret = false
|
|
if dir ~= nil and Path:new(dir):exists() == false then
|
|
vim.cmd("echohl ErrorMsg")
|
|
local answer = vim.fn.input(
|
|
"Telekasten.nvim: "
|
|
.. purpose
|
|
.. " folder "
|
|
.. dir
|
|
.. " does not exist!"
|
|
.. " Shall I create it? [y/N] "
|
|
)
|
|
vim.cmd("echohl None")
|
|
answer = vim.fn.trim(answer)
|
|
if answer == "y" or answer == "Y" then
|
|
if Path:new(dir):mkdir({ exists_ok = false }) then
|
|
vim.cmd('echomsg " "')
|
|
vim.cmd('echomsg "' .. dir .. ' created"')
|
|
ret = true
|
|
else
|
|
-- unreachable: plenary.Path:mkdir() will error out
|
|
print_error("Could not create directory " .. dir)
|
|
ret = false
|
|
end
|
|
end
|
|
else
|
|
ret = true
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local function global_dir_check()
|
|
local ret
|
|
if M.Cfg.home == nil then
|
|
print_error("Telekasten.nvim: home is not configured!")
|
|
ret = false
|
|
else
|
|
ret = check_dir_and_ask(M.Cfg.home, "home")
|
|
end
|
|
|
|
ret = ret and check_dir_and_ask(M.Cfg.dailies, "dailies")
|
|
ret = ret and check_dir_and_ask(M.Cfg.weeklies, "weeklies")
|
|
ret = ret and check_dir_and_ask(M.Cfg.templates, "templates")
|
|
ret = ret and check_dir_and_ask(M.Cfg.image_subdir, "images")
|
|
|
|
return ret
|
|
end
|
|
|
|
--- escapes a string for use as exact pattern within gsub
|
|
local function escape(s)
|
|
return string.gsub(s, "[%%%]%^%-$().[*+?]", "%%%1")
|
|
end
|
|
|
|
local function make_config_path_absolute(path)
|
|
local ret = path
|
|
if not (Path:new(path):is_absolute()) and path ~= nil then
|
|
ret = M.Cfg.home .. "/" .. path
|
|
end
|
|
return ret
|
|
end
|
|
|
|
-- sanitize strings
|
|
local escape_chars = function(string)
|
|
return string.gsub(string, "[%(|%)|\\|%[|%]|%-|%{%}|%?|%+|%*|%^|%$|%/]", {
|
|
["\\"] = "\\\\",
|
|
["-"] = "\\-",
|
|
["("] = "\\(",
|
|
[")"] = "\\)",
|
|
["["] = "\\[",
|
|
["]"] = "\\]",
|
|
["{"] = "\\{",
|
|
["}"] = "\\}",
|
|
["?"] = "\\?",
|
|
["+"] = "\\+",
|
|
["*"] = "\\*",
|
|
["^"] = "\\^",
|
|
["$"] = "\\$",
|
|
})
|
|
end
|
|
|
|
local function recursive_substitution(dir, old, new)
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
if vim.fn.executable("sed") == 0 then
|
|
vim.api.nvim_err_write("Sed not installed!\n")
|
|
return
|
|
end
|
|
|
|
old = escape_chars(old)
|
|
new = escape_chars(new)
|
|
|
|
local sedcommand = "sed -i"
|
|
if vim.fn.has("mac") == 1 then
|
|
sedcommand = "sed -i ''"
|
|
end
|
|
|
|
local replace_cmd = "rg -0 -l -t markdown '"
|
|
.. old
|
|
.. "' "
|
|
.. dir
|
|
.. " | xargs -0 "
|
|
.. sedcommand
|
|
.. " 's|"
|
|
.. old
|
|
.. "|"
|
|
.. new
|
|
.. "|g' >/dev/null 2>&1"
|
|
os.execute(replace_cmd)
|
|
end
|
|
|
|
local function save_all_tk_buffers()
|
|
for i = 1, vim.fn.bufnr("$") do
|
|
if
|
|
vim.fn.getbufvar(i, "&filetype") == "telekasten"
|
|
and vim.fn.getbufvar(i, "&mod") == 1
|
|
then
|
|
vim.cmd(i .. "bufdo w")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- strip an extension from a file name, escaping "." properly, eg:
|
|
-- strip_extension("path/Filename.md", ".md") -> "path/Filename"
|
|
local function strip_extension(str, ext)
|
|
return str:gsub("(" .. ext:gsub("%.", "%%.") .. ")$", "")
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- image stuff
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
local function make_relative_path(bufferpath, imagepath, sep)
|
|
sep = sep or "/"
|
|
|
|
-- Split the buffer and image path into their dirs/files
|
|
local buffer_dirs = {}
|
|
for w in string.gmatch(bufferpath, "([^" .. sep .. "]+)") do
|
|
buffer_dirs[#buffer_dirs + 1] = w
|
|
end
|
|
local image_dirs = {}
|
|
for w in string.gmatch(imagepath, "([^" .. sep .. "]+)") do
|
|
image_dirs[#image_dirs + 1] = w
|
|
end
|
|
|
|
-- The parts of the dir list that match won't matter, so skip them
|
|
local i = 1
|
|
while i < #image_dirs and i < #buffer_dirs do
|
|
if image_dirs[i] ~= buffer_dirs[i] then
|
|
break
|
|
else
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
-- Append ../ to walk up from the buffer location and the path downward
|
|
-- to the location of the image file in order to create a relative path
|
|
local relative_path = ""
|
|
while i <= #image_dirs or i <= #buffer_dirs do
|
|
if i <= #image_dirs then
|
|
if relative_path == "" then
|
|
relative_path = image_dirs[i]
|
|
else
|
|
relative_path = relative_path .. sep .. image_dirs[i]
|
|
end
|
|
end
|
|
if i <= #buffer_dirs - 1 then
|
|
relative_path = ".." .. sep .. relative_path
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
return relative_path
|
|
end
|
|
|
|
local function imgFromClipboard()
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local get_paste_command
|
|
if vim.fn.executable("xclip") == 1 then
|
|
get_paste_command = function(dir, filename)
|
|
return "xclip -selection clipboard -t image/png -o > "
|
|
.. dir
|
|
.. "/"
|
|
.. filename
|
|
end
|
|
elseif vim.fn.executable("osascript") == 1 then
|
|
get_paste_command = function(dir, filename)
|
|
return string.format(
|
|
'osascript -e "tell application \\"System Events\\" to write (the clipboard as «class PNGf») to '
|
|
.. '(make new file at folder \\"%s\\" with properties {name:\\"%s\\"})"',
|
|
dir,
|
|
filename
|
|
)
|
|
end
|
|
else
|
|
vim.api.nvim_err_write("No xclip installed!\n")
|
|
return
|
|
end
|
|
|
|
-- TODO: check `xclip -selection clipboard -t TARGETS -o` for the occurence of `image/png`
|
|
|
|
-- using plenary.job::new():sync() with on_stdout(_, data) unfortunately did some weird ASCII translation on the
|
|
-- data, so the PNGs were invalid. It seems like 0d 0a and single 0a bytes were stripped by the plenary job:
|
|
--
|
|
-- plenary job version:
|
|
-- $ hexdump -C /tmp/x.png|head
|
|
-- 00000000 89 50 4e 47 1a 00 00 00 49 48 44 52 00 00 03 19 |.PNG....IHDR....|
|
|
-- 00000010 00 00 01 c1 08 02 00 00 00 8a 73 e1 c3 00 00 00 |..........s.....|
|
|
-- 00000020 09 70 48 59 73 00 00 0e c4 00 00 0e c4 01 95 2b |.pHYs..........+|
|
|
-- 00000030 0e 1b 00 00 20 00 49 44 41 54 78 9c ec dd 77 58 |.... .IDATx...wX|
|
|
-- 00000040 14 d7 fa 07 f0 33 bb b3 4b af 0b 2c 08 22 1d 04 |.....3..K..,."..|
|
|
-- 00000050 05 11 10 1b a2 54 c5 1e bb b1 c6 98 c4 68 72 4d |.....T.......hrM|
|
|
-- 00000060 e2 cd 35 37 26 b9 49 6e 6e 7e f7 a6 98 98 a8 29 |..57&.Inn~.....)|
|
|
-- 00000070 26 6a 8c 51 63 8b bd 00 8a 58 40 b0 81 08 2a 45 |&j.Qc....X@...*E|
|
|
-- 00000080 69 52 17 58 ca ee b2 f5 f7 c7 ea 4a 10 66 d7 01 |iR.X.......J.f..|
|
|
-- 00000090 b1 e4 fb 79 7c f2 2c e7 cc 39 e7 3d 67 66 b3 2f |...y|.,..9.=gf./|
|
|
--
|
|
-- OK version
|
|
-- $ hexdump -C /tmp/x2.png|head
|
|
-- 00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
|
|
-- 00000010 00 00 03 19 00 00 01 c1 08 02 00 00 00 8a 73 e1 |..............s.|
|
|
-- 00000020 c3 00 00 00 09 70 48 59 73 00 00 0e c4 00 00 0e |.....pHYs.......|
|
|
-- 00000030 c4 01 95 2b 0e 1b 00 00 20 00 49 44 41 54 78 9c |...+.... .IDATx.|
|
|
-- 00000040 ec dd 77 58 14 d7 fa 07 f0 33 bb b3 4b af 0b 2c |..wX.....3..K..,|
|
|
-- 00000050 08 22 1d 04 05 11 10 1b a2 54 c5 1e bb b1 c6 98 |.".......T......|
|
|
-- 00000060 c4 68 72 4d e2 cd 35 37 26 b9 49 6e 6e 7e f7 a6 |.hrM..57&.Inn~..|
|
|
-- 00000070 98 98 a8 29 26 6a 8c 51 63 8b bd 00 8a 58 40 b0 |...)&j.Qc....X@.|
|
|
-- 00000080 81 08 2a 45 69 52 17 58 ca ee b2 f5 f7 c7 ea 4a |..*EiR.X.......J|
|
|
-- 00000090 10 66 d7 01 b1 e4 fb 79 7c f2 2c e7 cc 39 e7 3d |.f.....y|.,..9.=|
|
|
|
|
local pngname = "pasted_img_" .. os.date("%Y%m%d%H%M%S") .. ".png"
|
|
local pngdir = M.Cfg.image_subdir and M.Cfg.image_subdir or M.Cfg.home
|
|
local png = pngdir .. "/" .. pngname
|
|
local relpath = make_relative_path(vim.fn.expand("%:p"), png, "/")
|
|
|
|
local result = os.execute(get_paste_command(pngdir, pngname))
|
|
if result > 0 then
|
|
vim.api.nvim_err_writeln(
|
|
string.format(
|
|
"Unable to write image %s (exit code: %d). Is there an image on the clipboard? ",
|
|
png,
|
|
result
|
|
)
|
|
)
|
|
return
|
|
end
|
|
|
|
if file_exists(png) then
|
|
if M.Cfg.image_link_style == "markdown" then
|
|
vim.api.nvim_put({ "" }, "", true, true)
|
|
else
|
|
vim.api.nvim_put({ "![[" .. pngname .. "]]" }, "", true, true)
|
|
end
|
|
else
|
|
vim.api.nvim_err_writeln("Unable to write image " .. png)
|
|
end
|
|
end
|
|
-- end of image stuff
|
|
|
|
M.note_type_templates = {
|
|
normal = M.Cfg.template_new_note,
|
|
daily = M.Cfg.template_new_daily,
|
|
weekly = M.Cfg.template_new_weekly,
|
|
}
|
|
|
|
local function daysuffix(day)
|
|
day = tostring(day)
|
|
if (day == "1") or (day == "21") or (day == "31") then
|
|
return "st"
|
|
end
|
|
if (day == "2") or (day == "22") then
|
|
return "nd"
|
|
end
|
|
if (day == "3") or (day == "23") then
|
|
return "rd"
|
|
end
|
|
return "th"
|
|
end
|
|
|
|
local daymap = {
|
|
"Monday",
|
|
"Tuesday",
|
|
"Wednesday",
|
|
"Thursday",
|
|
"Friday",
|
|
"Saturday",
|
|
"Sunday",
|
|
}
|
|
local monthmap = {
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December",
|
|
}
|
|
|
|
local dateformats = {
|
|
date = "%Y-%m-%d",
|
|
week = "%V",
|
|
isoweek = "%Y-W%V",
|
|
}
|
|
|
|
local function calculate_dates(date)
|
|
local time = os.time(date)
|
|
local dinfo = os.date("*t", time) -- this normalizes the input to a full date table
|
|
local oneday = 24 * 60 * 60 -- hours * days * seconds
|
|
local oneweek = 7 * oneday
|
|
local df = dateformats
|
|
|
|
local dates = {}
|
|
|
|
-- this is to compensate for the calendar showing M-Su, but os.date Su is
|
|
-- always wday = 1
|
|
local wday = dinfo.wday - 1
|
|
if wday == 0 then
|
|
wday = 7
|
|
end
|
|
|
|
dates.year = dinfo.year
|
|
dates.month = dinfo.month
|
|
dates.day = dinfo.day
|
|
dates.hdate = daymap[wday]
|
|
.. ", "
|
|
.. monthmap[dinfo.month]
|
|
.. " "
|
|
.. dinfo.day
|
|
.. daysuffix(dinfo.day)
|
|
.. ", "
|
|
.. dinfo.year
|
|
|
|
local zonehour = string.sub(os.date("%z"), 1, 3)
|
|
local zonemin = string.sub(os.date("%z"), 4, 5)
|
|
dates.rfc3339 = os.date(df.date, time)
|
|
.. os.date("T%H:%M:%S")
|
|
.. "Z"
|
|
.. zonehour
|
|
.. ":"
|
|
.. zonemin
|
|
|
|
dates.date = os.date(df.date, time)
|
|
dates.prevday = os.date(df.date, time - oneday)
|
|
dates.nextday = os.date(df.date, time + oneday)
|
|
dates.week = os.date(df.week, time)
|
|
dates.prevweek = os.date(df.week, time - oneweek)
|
|
dates.nextweek = os.date(df.week, time + oneweek)
|
|
dates.isoweek = os.date(df.isoweek, time)
|
|
dates.isoprevweek = os.date(df.isoweek, time - oneweek)
|
|
dates.isonextweek = os.date(df.isoweek, time + oneweek)
|
|
|
|
-- things get a bit hairy at the year rollover. W01 only starts the first week ofs
|
|
-- January if it has more than 3 days. Partial weeks with less than 4 days are
|
|
-- considered W52, but os.date still sets the year as the new year, so Jan 1 2022
|
|
-- would appear as being in 2022-W52. That breaks linear linking respective
|
|
-- of next/prev week, so we want to put the days of that partial week in
|
|
-- January in 2021-W52. This tweak will only change the ISO formatted week string.
|
|
if tonumber(dates.week) == 52 and tonumber(dates.month) == 1 then
|
|
dates.isoweek = tostring(dates.year - 1) .. "-W52"
|
|
end
|
|
|
|
-- Find the Sunday that started this week regardless of the calendar
|
|
-- display preference. Then use that as the base to calculate the dates
|
|
-- for the days of the current week.
|
|
-- Finally, adjust Sunday to suit user calendar preference.
|
|
local starting_sunday = time - (wday * oneday)
|
|
local sunday_offset = 0
|
|
if M.Cfg.calendar_opts.calendar_monday == 1 then
|
|
sunday_offset = 7
|
|
end
|
|
dates.monday = os.date(df.date, starting_sunday + (1 * oneday))
|
|
dates.tuesday = os.date(df.date, starting_sunday + (2 * oneday))
|
|
dates.wednesday = os.date(df.date, starting_sunday + (3 * oneday))
|
|
dates.thursday = os.date(df.date, starting_sunday + (4 * oneday))
|
|
dates.friday = os.date(df.date, starting_sunday + (5 * oneday))
|
|
dates.saturday = os.date(df.date, starting_sunday + (6 * oneday))
|
|
dates.sunday = os.date(df.date, starting_sunday + (sunday_offset * oneday))
|
|
|
|
return dates
|
|
end
|
|
|
|
local function linesubst(line, title, dates, uuid)
|
|
if dates == nil then
|
|
dates = calculate_dates()
|
|
end
|
|
if uuid == nil then
|
|
uuid = ""
|
|
end
|
|
|
|
local substs = {
|
|
hdate = dates.hdate,
|
|
week = dates.week,
|
|
date = dates.date,
|
|
isoweek = dates.isoweek,
|
|
year = dates.year,
|
|
rfc3339 = dates.rfc3339,
|
|
|
|
prevday = dates.prevday,
|
|
nextday = dates.nextday,
|
|
prevweek = dates.prevweek,
|
|
nextweek = dates.nextweek,
|
|
isoprevweek = dates.isoprevweek,
|
|
isonextweek = dates.isonextweek,
|
|
|
|
sunday = dates.sunday,
|
|
monday = dates.monday,
|
|
tuesday = dates.tuesday,
|
|
wednesday = dates.wednesday,
|
|
thursday = dates.thursday,
|
|
friday = dates.friday,
|
|
saturday = dates.saturday,
|
|
|
|
title = title,
|
|
uuid = uuid,
|
|
}
|
|
for k, v in pairs(substs) do
|
|
line = line:gsub("{{" .. k .. "}}", v)
|
|
end
|
|
|
|
return line
|
|
end
|
|
|
|
local function create_note_from_template(
|
|
title,
|
|
uuid,
|
|
filepath,
|
|
templatefn,
|
|
calendar_info
|
|
)
|
|
-- first, read the template file
|
|
local lines = {}
|
|
if file_exists(templatefn) then
|
|
for line in io.lines(templatefn) do
|
|
lines[#lines + 1] = line
|
|
end
|
|
end
|
|
|
|
-- now write the output file, substituting vars line by line
|
|
local ofile = io.open(filepath, "a")
|
|
for _, line in pairs(lines) do
|
|
ofile:write(linesubst(line, title, calendar_info, uuid) .. "\n")
|
|
end
|
|
|
|
ofile:flush()
|
|
ofile:close()
|
|
end
|
|
|
|
--- Pinfo
|
|
--- - fexists : true if file exists
|
|
--- - title : the title (filename including subdirs without extension)
|
|
--- - if opts.subdirs_in_links is false, no subdirs will be included
|
|
--- - filename : filename component only
|
|
--- - filepath : full path, identical to p
|
|
--- - root_dir : the root dir (home, dailies, ...)
|
|
--- - sub_dir : subdir if present, relative to root_dir
|
|
--- - is_daily_or_weekly : bool
|
|
--- - is_daily : bool
|
|
--- - is_weekly : bool
|
|
--- - template : suggested template based on opts
|
|
local Pinfo = {
|
|
fexists = false,
|
|
title = "",
|
|
filename = "",
|
|
filepath = "",
|
|
root_dir = "",
|
|
sub_dir = "",
|
|
is_daily_or_weekly = false,
|
|
is_daily = false,
|
|
is_weekly = false,
|
|
template = "",
|
|
calendar_info = nil,
|
|
}
|
|
|
|
function Pinfo:new(opts)
|
|
opts = opts or {}
|
|
|
|
local object = {}
|
|
setmetatable(object, self)
|
|
self.__index = self
|
|
if opts.filepath then
|
|
return object:resolve_path(opts.filepath, opts)
|
|
end
|
|
if opts.title ~= nil then
|
|
return object:resolve_link(opts.title, opts)
|
|
end
|
|
return object
|
|
end
|
|
|
|
--- resolve_path(p, opts)
|
|
--- inspects the path and returns a Pinfo table
|
|
function Pinfo:resolve_path(p, opts)
|
|
opts = opts or {}
|
|
opts.subdirs_in_links = opts.subdirs_in_links or M.Cfg.subdirs_in_links
|
|
|
|
self.fexists = file_exists(p)
|
|
self.filepath = p
|
|
self.root_dir = M.Cfg.home
|
|
self.is_daily_or_weekly = false
|
|
self.is_daily = false
|
|
self.is_weekly = false
|
|
|
|
-- strip all dirs to get filename
|
|
local pp = Path:new(p)
|
|
local p_splits = pp:_split()
|
|
self.filename = p_splits[#p_splits]
|
|
self.title = self.filename:gsub(M.Cfg.extension, "")
|
|
|
|
if vim.startswith(p, M.Cfg.home) then
|
|
self.root_dir = M.Cfg.home
|
|
end
|
|
if vim.startswith(p, M.Cfg.dailies) then
|
|
self.root_dir = M.Cfg.dailies
|
|
-- TODO: parse "title" into calendarinfo like in resolve_link
|
|
-- not really necessary as the file exists anyway and therefore we don't need to instantiate a template
|
|
self.is_daily_or_weekly = true
|
|
self.is_daily = true
|
|
end
|
|
if vim.startswith(p, M.Cfg.weeklies) then
|
|
-- TODO: parse "title" into calendarinfo like in resolve_link
|
|
-- not really necessary as the file exists anyway and therefore we don't need to instantiate a template
|
|
self.root_dir = M.Cfg.weeklies
|
|
self.is_daily_or_weekly = true
|
|
self.is_weekly = true
|
|
end
|
|
|
|
-- now work out subdir relative to root
|
|
self.sub_dir = p
|
|
:gsub(escape(self.root_dir .. "/"), "")
|
|
:gsub(escape(self.filename), "")
|
|
:gsub("/$", "")
|
|
:gsub("^/", "")
|
|
|
|
if opts.subdirs_in_links and #self.sub_dir > 0 then
|
|
self.title = self.sub_dir .. "/" .. self.title
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function check_if_daily_or_weekly(title)
|
|
local daily_match = "^(%d%d%d%d)-(%d%d)-(%d%d)$"
|
|
local weekly_match = "^(%d%d%d%d)-W(%d%d)$"
|
|
|
|
local is_daily = false
|
|
local is_weekly = false
|
|
local dateinfo = calculate_dates() -- sane default
|
|
|
|
local start, _, year, month, day = title:find(daily_match)
|
|
if start ~= nil then
|
|
if tonumber(month) < 13 then
|
|
if tonumber(day) < 32 then
|
|
is_daily = true
|
|
dateinfo.year = tonumber(year)
|
|
dateinfo.month = tonumber(month)
|
|
dateinfo.day = tonumber(day)
|
|
dateinfo = calculate_dates(dateinfo)
|
|
end
|
|
end
|
|
end
|
|
|
|
local week
|
|
start, _, year, week = title:find(weekly_match)
|
|
if start ~= nil then
|
|
if tonumber(week) < 53 then
|
|
is_weekly = true
|
|
-- ISO8601 week -> date calculation
|
|
dateinfo = dateutils.isoweek_to_date(tonumber(year), tonumber(week))
|
|
dateinfo = calculate_dates(dateinfo)
|
|
end
|
|
end
|
|
return is_daily, is_weekly, dateinfo
|
|
end
|
|
|
|
function Pinfo:resolve_link(title, opts)
|
|
opts = opts or {}
|
|
opts.weeklies = opts.weeklies or M.Cfg.weeklies
|
|
opts.dailies = opts.dailies or M.Cfg.dailies
|
|
opts.home = opts.home or M.Cfg.home
|
|
opts.extension = opts.extension or M.Cfg.extension
|
|
opts.template_handling = opts.template_handling or M.Cfg.template_handling
|
|
opts.new_note_location = opts.new_note_location or M.Cfg.new_note_location
|
|
|
|
self.fexists = false
|
|
self.title = title
|
|
self.filename = title .. opts.extension
|
|
self.filename = self.filename:gsub("^%./", "") -- strip potential leading ./
|
|
self.root_dir = opts.home
|
|
self.is_daily_or_weekly = false
|
|
self.is_daily = false
|
|
self.is_weekly = false
|
|
self.template = nil
|
|
self.calendar_info = nil
|
|
|
|
if opts.weeklies and file_exists(opts.weeklies .. "/" .. self.filename) then
|
|
-- TODO: parse "title" into calendarinfo like below
|
|
-- not really necessary as the file exists anyway and therefore we don't need to instantiate a template
|
|
-- if we still want calendar_info, just move the code for it out of `if self.fexists == false`.
|
|
self.filepath = opts.weeklies .. "/" .. self.filename
|
|
self.fexists = true
|
|
self.root_dir = opts.weeklies
|
|
self.is_daily_or_weekly = true
|
|
self.is_weekly = true
|
|
end
|
|
if opts.dailies and file_exists(opts.dailies .. "/" .. self.filename) then
|
|
-- TODO: parse "title" into calendarinfo like below
|
|
-- not really necessary as the file exists anyway and therefore we don't need to instantiate a template
|
|
-- if we still want calendar_info, just move the code for it out of `if self.fexists == false`.
|
|
self.filepath = opts.dailies .. "/" .. self.filename
|
|
self.fexists = true
|
|
self.root_dir = opts.dailies
|
|
self.is_daily_or_weekly = true
|
|
self.is_daily = true
|
|
end
|
|
if file_exists(opts.home .. "/" .. self.filename) then
|
|
self.filepath = opts.home .. "/" .. self.filename
|
|
self.fexists = true
|
|
end
|
|
|
|
if self.fexists == false then
|
|
-- now search for it in all subdirs
|
|
local subdirs = scan.scan_dir(opts.home, { only_dirs = true })
|
|
local tempfn
|
|
for _, folder in pairs(subdirs) do
|
|
tempfn = folder .. "/" .. self.filename
|
|
-- [[testnote]]
|
|
if file_exists(tempfn) then
|
|
self.filepath = tempfn
|
|
self.fexists = true
|
|
-- print("Found: " ..self.filename)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- if we just cannot find the note, check if it's a daily or weekly one
|
|
if self.fexists == false then
|
|
-- TODO: if we're not smart, we also shouldn't need to try to set the calendar info..?
|
|
-- I bet someone will want the info in there, so let's put it in if possible
|
|
_, _, self.calendar_info = check_if_daily_or_weekly(self.title) -- will set today as default, so leave in!
|
|
|
|
if opts.new_note_location == "smart" then
|
|
self.filepath = opts.home .. "/" .. self.filename -- default
|
|
self.is_daily, self.is_weekly, self.calendar_info =
|
|
check_if_daily_or_weekly(
|
|
self.title
|
|
)
|
|
if self.is_daily == true then
|
|
self.root_dir = opts.dailies
|
|
self.filepath = opts.dailies .. "/" .. self.filename
|
|
self.is_daily_or_weekly = true
|
|
end
|
|
if self.is_weekly == true then
|
|
self.root_dir = opts.weeklies
|
|
self.filepath = opts.weeklies .. "/" .. self.filename
|
|
self.is_daily_or_weekly = true
|
|
end
|
|
elseif opts.new_note_location == "same_as_current" then
|
|
local cwd = vim.fn.expand("%:p")
|
|
if #cwd > 0 then
|
|
self.root_dir = Path:new(cwd):parent():absolute()
|
|
if Path:new(self.root_dir):exists() then
|
|
-- check if potential subfolders in filename would end up in a non-existing directory
|
|
self.filepath = self.root_dir .. "/" .. self.filename
|
|
if not Path:new(self.filepath):parent():exists() then
|
|
print("Path " .. self.filepath .. " is invalid!")
|
|
-- self.filepath = opts.home .. "/" .. self.filename
|
|
end
|
|
else
|
|
print("Path " .. self.root_dir .. " is invalid!")
|
|
-- self.filepath = opts.home .. "/" .. self.filename
|
|
end
|
|
else
|
|
self.filepath = opts.home .. "/" .. self.filename
|
|
end
|
|
else
|
|
-- default fn for creation
|
|
self.filepath = opts.home .. "/" .. self.filename
|
|
end
|
|
|
|
-- final round, there still can be a subdir mess-up
|
|
if not Path:new(self.filepath):parent():exists() then
|
|
print("Path " .. self.filepath .. " is invalid!")
|
|
-- local fname_only = Path:new(self.filename):_split()
|
|
-- fname_only = fname_only[#fname_only]
|
|
-- self.filepath = opts.home .. "/" .. fname_only
|
|
self.filepath = ""
|
|
end
|
|
end
|
|
|
|
-- now work out subdir relative to root
|
|
self.sub_dir = self.filepath
|
|
:gsub(escape(self.root_dir .. "/"), "")
|
|
:gsub(escape(self.filename), "")
|
|
:gsub("/$", "")
|
|
:gsub("^/", "")
|
|
|
|
-- now suggest a template based on opts
|
|
self.template = M.note_type_templates.normal
|
|
if opts.template_handling == "prefer_new_note" then
|
|
self.template = M.note_type_templates.normal
|
|
elseif opts.template_handling == "always_ask" then
|
|
self.template = nil
|
|
elseif opts.template_handling == "smart" then
|
|
if self.is_daily then
|
|
self.template = M.note_type_templates.daily
|
|
elseif self.is_weekly then
|
|
self.template = M.note_type_templates.weekly
|
|
else
|
|
self.template = M.note_type_templates.normal
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
-- local function endswith(s, ending)
|
|
-- return ending == "" or s:sub(-#ending) == ending
|
|
-- end
|
|
|
|
local function file_extension(fname)
|
|
return fname:match("^.+(%..+)$")
|
|
end
|
|
|
|
local function filter_filetypes(flist, ftypes)
|
|
local new_fl = {}
|
|
ftypes = ftypes or { M.Cfg.extension }
|
|
|
|
local ftypeok = {}
|
|
for _, ft in pairs(ftypes) do
|
|
ftypeok[ft] = true
|
|
end
|
|
|
|
for _, fn in pairs(flist) do
|
|
if ftypeok[file_extension(fn)] then
|
|
table.insert(new_fl, fn)
|
|
end
|
|
end
|
|
return new_fl
|
|
end
|
|
|
|
local sourced_file = debug_utils.sourced_filepath()
|
|
M.base_directory = vim.fn.fnamemodify(sourced_file, ":h:h:h")
|
|
local media_files_base_directory = M.base_directory
|
|
.. "/telescope-media-files.nvim"
|
|
local defaulter = utils.make_default_callable
|
|
local media_preview = defaulter(function(opts)
|
|
local preview_cmd = media_files_base_directory .. "/scripts/vimg"
|
|
if vim.fn.executable(preview_cmd) == 0 then
|
|
print("Previewer not found: " .. preview_cmd)
|
|
return conf.file_previewer(opts)
|
|
end
|
|
return previewers.new_termopen_previewer({
|
|
get_command = opts.get_command or function(entry)
|
|
local tmp_table = vim.split(entry.value, "\t")
|
|
local preview = opts.get_preview_window()
|
|
opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd()
|
|
if vim.tbl_isempty(tmp_table) then
|
|
return { "echo", "" }
|
|
end
|
|
return {
|
|
preview_cmd,
|
|
tmp_table[1],
|
|
preview.col,
|
|
preview.line + 1,
|
|
preview.width,
|
|
preview.height,
|
|
}
|
|
end,
|
|
})
|
|
end, {})
|
|
|
|
-- note picker actions
|
|
local picker_actions = {}
|
|
|
|
-- find_files_sorted(opts)
|
|
-- like builtin.find_files, but:
|
|
-- - uses plenary.scan_dir synchronously instead of external jobs
|
|
-- - pre-sorts the file list in descending order (nice for dates, most recent first)
|
|
-- - filters for allowed file types by extension
|
|
-- - (also supports devicons)
|
|
-- - displays subdirs if necessary
|
|
-- - e.g. when searching for daily notes, no subdirs are displayed
|
|
-- - but when entering a date in find_notes, the daily/ and weekly/ subdirs are displayed
|
|
-- - optionally previews media (pdf, images, mp4, webm)
|
|
-- - this requires the telescope-media-files.nvim extension
|
|
local function find_files_sorted(opts)
|
|
opts = opts or {}
|
|
|
|
local file_list = scan.scan_dir(opts.cwd, {})
|
|
local filter_extensions = opts.filter_extensions or M.Cfg.filter_extensions
|
|
file_list = filter_filetypes(file_list, filter_extensions)
|
|
local sort_option = opts.sort or "filename"
|
|
if sort_option == "modified" then
|
|
table.sort(file_list, function(a, b)
|
|
return vim.fn.getftime(a) > vim.fn.getftime(b)
|
|
end)
|
|
else
|
|
table.sort(file_list, function(a, b)
|
|
return a > b
|
|
end)
|
|
end
|
|
|
|
local counts = nil
|
|
if opts.show_link_counts then
|
|
counts = linkutils.generate_backlink_map(M.Cfg)
|
|
end
|
|
|
|
-- display with devicons
|
|
local function iconic_display(display_entry)
|
|
local display_opts = {
|
|
path_display = function(_, e)
|
|
return e:gsub(escape(opts.cwd .. "/"), "")
|
|
end,
|
|
}
|
|
|
|
local hl_group
|
|
local display = utils.transform_path(display_opts, display_entry.value)
|
|
|
|
display, hl_group = utils.transform_devicons(
|
|
display_entry.value,
|
|
display,
|
|
false
|
|
)
|
|
|
|
if hl_group then
|
|
return display, { { { 1, 3 }, hl_group } }
|
|
else
|
|
return display
|
|
end
|
|
end
|
|
|
|
-- for media_files
|
|
local popup_opts = {}
|
|
opts.get_preview_window = function()
|
|
return popup_opts.preview
|
|
end
|
|
|
|
-- local width = config.width
|
|
-- or config.layout_config.width
|
|
-- or config.layout_config[config.layout_strategy].width
|
|
-- or vim.o.columns
|
|
-- local telescope_win_width
|
|
-- if width > 1 then
|
|
-- telescope_win_width = width
|
|
-- else
|
|
-- telescope_win_width = math.floor(vim.o.columns * width)
|
|
-- end
|
|
local displayer = entry_display.create({
|
|
separator = "",
|
|
items = {
|
|
{ width = 4 },
|
|
{ width = 4 },
|
|
{ remaining = true },
|
|
},
|
|
})
|
|
|
|
local function make_display(entry)
|
|
local fn = entry.value
|
|
local nlinks = counts.link_counts[fn] or 0
|
|
local nbacks = counts.backlink_counts[fn] or 0
|
|
|
|
if opts.show_link_counts then
|
|
local display, hl = iconic_display(entry)
|
|
|
|
return displayer({
|
|
{
|
|
"L" .. tostring(nlinks),
|
|
function()
|
|
return {
|
|
{ { 0, 1 }, "tkTagSep" },
|
|
{ { 1, 3 }, "tkTag" },
|
|
}
|
|
end,
|
|
},
|
|
{
|
|
"B" .. tostring(nbacks),
|
|
function()
|
|
return {
|
|
{ { 0, 1 }, "tkTagSep" },
|
|
{ { 1, 3 }, "DevIconMd" },
|
|
}
|
|
end,
|
|
},
|
|
{
|
|
display,
|
|
function()
|
|
return hl
|
|
end,
|
|
},
|
|
})
|
|
else
|
|
return iconic_display(entry)
|
|
end
|
|
end
|
|
|
|
local function entry_maker(entry)
|
|
local iconic_entry = {}
|
|
iconic_entry.value = entry
|
|
iconic_entry.path = entry
|
|
iconic_entry.ordinal = entry
|
|
if opts.show_link_counts then
|
|
iconic_entry.display = make_display
|
|
else
|
|
iconic_entry.display = iconic_display
|
|
end
|
|
return iconic_entry
|
|
end
|
|
|
|
local previewer = conf.file_previewer(opts)
|
|
if opts.preview_type == "media" then
|
|
previewer = media_preview.new(opts)
|
|
end
|
|
|
|
opts.attach_mappings = opts.attach_mappings
|
|
or function(_, _)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
end
|
|
|
|
local picker = pickers.new(opts, {
|
|
finder = finders.new_table({
|
|
results = file_list,
|
|
entry_maker = entry_maker,
|
|
}),
|
|
sorter = conf.generic_sorter(opts),
|
|
|
|
previewer = previewer,
|
|
})
|
|
|
|
-- local oc = picker.finder.close
|
|
--
|
|
-- picker.finder.close = function()
|
|
-- print('on close')
|
|
-- print(vim.inspect(picker:get_selection()))
|
|
-- -- unfortunately, no way to tell if the selection was confirmed or
|
|
-- -- canceled out
|
|
-- oc()
|
|
-- -- alternative: attach default mappings for <ESC> and <C-c>
|
|
-- -- if anyone quits with q!, it's their fault
|
|
-- end
|
|
|
|
-- for media_files:
|
|
local line_count = vim.o.lines - vim.o.cmdheight
|
|
if vim.o.laststatus ~= 0 then
|
|
line_count = line_count - 1
|
|
end
|
|
|
|
popup_opts = picker:get_window_options(vim.o.columns, line_count)
|
|
|
|
picker:find()
|
|
end
|
|
|
|
picker_actions.post_open = function()
|
|
if M.Cfg.auto_set_filetype then
|
|
vim.cmd("set ft=telekasten")
|
|
end
|
|
end
|
|
|
|
picker_actions.select_default = function(prompt_bufnr)
|
|
local ret = action_set.select(prompt_bufnr, "default")
|
|
picker_actions.post_open()
|
|
return ret
|
|
end
|
|
|
|
function picker_actions.close(opts)
|
|
opts = opts or {}
|
|
return function(prompt_bufnr)
|
|
actions.close(prompt_bufnr)
|
|
if opts.erase then
|
|
if file_exists(opts.erase_file) then
|
|
vim.fn.delete(opts.erase_file)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function picker_actions.paste_tag(opts)
|
|
return function(prompt_bufnr)
|
|
actions.close(prompt_bufnr)
|
|
local selection = action_state.get_selected_entry()
|
|
vim.api.nvim_put({ selection.value.tag }, "", true, true)
|
|
if opts.insert_after_inserting or opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end
|
|
end
|
|
|
|
function picker_actions.yank_tag(opts)
|
|
return function(prompt_bufnr)
|
|
opts = opts or {}
|
|
if opts.close_after_yanking then
|
|
actions.close(prompt_bufnr)
|
|
end
|
|
local selection = action_state.get_selected_entry()
|
|
vim.fn.setreg('"', selection.value.tag)
|
|
print("yanked " .. selection.value.tag)
|
|
end
|
|
end
|
|
|
|
function picker_actions.paste_link(opts)
|
|
opts = opts or {}
|
|
opts.subdirs_in_links = opts.subdirs_in_links or M.Cfg.subdirs_in_links
|
|
return function(prompt_bufnr)
|
|
actions.close(prompt_bufnr)
|
|
local selection = action_state.get_selected_entry()
|
|
local pinfo = Pinfo:new({
|
|
filepath = selection.filename or selection.value,
|
|
opts,
|
|
})
|
|
local title = "[[" .. pinfo.title .. "]]"
|
|
vim.api.nvim_put({ title }, "", true, true)
|
|
if opts.insert_after_inserting or opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end
|
|
end
|
|
|
|
function picker_actions.yank_link(opts)
|
|
return function(prompt_bufnr)
|
|
opts = opts or {}
|
|
opts.subdirs_in_links = opts.subdirs_in_links or M.Cfg.subdirs_in_links
|
|
if opts.close_after_yanking then
|
|
actions.close(prompt_bufnr)
|
|
end
|
|
local selection = action_state.get_selected_entry()
|
|
local pinfo = Pinfo:new({
|
|
filepath = selection.filename or selection.value,
|
|
opts,
|
|
})
|
|
local title = "[[" .. pinfo.title .. "]]"
|
|
vim.fn.setreg('"', title)
|
|
print("yanked " .. title)
|
|
end
|
|
end
|
|
|
|
function picker_actions.paste_img_link(opts)
|
|
return function(prompt_bufnr)
|
|
actions.close(prompt_bufnr)
|
|
local selection = action_state.get_selected_entry()
|
|
local fn = selection.value
|
|
fn = make_relative_path(vim.fn.expand("%:p"), fn, "/")
|
|
local imglink = ""
|
|
vim.api.nvim_put({ imglink }, "", true, true)
|
|
if opts.insert_after_inserting or opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end
|
|
end
|
|
|
|
function picker_actions.yank_img_link(opts)
|
|
return function(prompt_bufnr)
|
|
opts = opts or {}
|
|
if opts.close_after_yanking then
|
|
actions.close(prompt_bufnr)
|
|
end
|
|
local selection = action_state.get_selected_entry()
|
|
local fn = selection.value
|
|
fn = make_relative_path(vim.fn.expand("%:p"), fn, "/")
|
|
local imglink = ""
|
|
vim.fn.setreg('"', imglink)
|
|
print("yanked " .. imglink)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- FindDailyNotes:
|
|
-- ---------------
|
|
--
|
|
-- Select from daily notes
|
|
--
|
|
local function FindDailyNotes(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local today = os.date(dateformats.date)
|
|
local fname = M.Cfg.dailies .. "/" .. today .. M.Cfg.extension
|
|
local fexists = file_exists(fname)
|
|
if
|
|
(fexists ~= true)
|
|
and (
|
|
(opts.dailies_create_nonexisting == true)
|
|
or M.Cfg.dailies_create_nonexisting == true
|
|
)
|
|
then
|
|
create_note_from_template(today, _, fname, M.note_type_templates.daily)
|
|
opts.erase = true
|
|
opts.erase_file = fname
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Find daily note",
|
|
cwd = M.Cfg.dailies,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- FindWeeklyNotes:
|
|
-- ---------------
|
|
--
|
|
-- Select from daily notes
|
|
--
|
|
local function FindWeeklyNotes(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local title = os.date(dateformats.isoweek)
|
|
local fname = M.Cfg.weeklies .. "/" .. title .. M.Cfg.extension
|
|
local fexists = file_exists(fname)
|
|
if
|
|
(fexists ~= true)
|
|
and (
|
|
(opts.weeklies_create_nonexisting == true)
|
|
or M.Cfg.weeklies_create_nonexisting == true
|
|
)
|
|
then
|
|
create_note_from_template(title, _, fname, M.note_type_templates.weekly)
|
|
opts.erase = true
|
|
opts.erase_file = fname
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Find weekly note",
|
|
cwd = M.Cfg.weeklies,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- InsertLink:
|
|
-- -----------
|
|
--
|
|
-- Select from all notes and put a link in the current buffer
|
|
--
|
|
local function InsertLink(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
opts.subdirs_in_links = opts.subdirs_in_links or M.Cfg.subdirs_in_links
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Insert link to note",
|
|
cwd = M.Cfg.home,
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
local selection = action_state.get_selected_entry()
|
|
local pinfo = Pinfo:new({
|
|
filepath = selection.value,
|
|
opts,
|
|
})
|
|
vim.api.nvim_put(
|
|
{ "[[" .. pinfo.title .. "]]" },
|
|
"",
|
|
true,
|
|
true
|
|
)
|
|
if opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
find_command = M.Cfg.find_command,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
-- local function check_for_link_or_tag()
|
|
local function check_for_link_or_tag()
|
|
local line = vim.api.nvim_get_current_line()
|
|
local col = vim.fn.col(".")
|
|
return taglinks.is_tag_or_link_at(line, col, M.Cfg)
|
|
end
|
|
|
|
local function follow_url(url)
|
|
-- we just leave it to the OS's handler to deal with what kind of URL it is
|
|
local function format_command(cmd)
|
|
return 'call jobstart(["'
|
|
.. cmd
|
|
.. '", "'
|
|
.. url
|
|
.. '"], {"detach": v:true})'
|
|
end
|
|
|
|
local command
|
|
if vim.fn.has("mac") == 1 then
|
|
command = format_command("open")
|
|
vim.cmd(command)
|
|
elseif vim.fn.has("unix") then
|
|
command = format_command("xdg-open")
|
|
vim.cmd(command)
|
|
else
|
|
print("Cannot open URLs on your operating system")
|
|
end
|
|
end
|
|
|
|
--
|
|
-- PreviewImg:
|
|
-- -----------
|
|
--
|
|
-- preview media
|
|
--
|
|
local function PreviewImg(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
vim.cmd("normal yi)")
|
|
local fname = vim.fn.getreg('"0')
|
|
|
|
-- check if fname exists anywhere
|
|
local fexists = file_exists(M.Cfg.home .. "/" .. fname)
|
|
|
|
if fexists == true then
|
|
find_files_sorted({
|
|
prompt_title = "Preview image/media",
|
|
cwd = M.Cfg.home,
|
|
default_text = fname,
|
|
find_command = M.Cfg.find_command,
|
|
filter_extensions = {
|
|
".png",
|
|
".jpg",
|
|
".bmp",
|
|
".gif",
|
|
".pdf",
|
|
".mp4",
|
|
".webm",
|
|
},
|
|
preview_type = "media",
|
|
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
else
|
|
print("File not found: " .. M.Cfg.home .. "/" .. fname)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- BrowseImg:
|
|
-- -----------
|
|
--
|
|
-- preview media
|
|
--
|
|
local function BrowseImg(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Preview image/media",
|
|
cwd = M.Cfg.home,
|
|
find_command = M.Cfg.find_command,
|
|
filter_extensions = {
|
|
".png",
|
|
".jpg",
|
|
".bmp",
|
|
".gif",
|
|
".pdf",
|
|
".mp4",
|
|
".webm",
|
|
},
|
|
preview_type = "media",
|
|
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- FindFriends:
|
|
-- -----------
|
|
--
|
|
-- Find notes also linking to the link under cursor
|
|
--
|
|
local function FindFriends(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
vim.cmd("normal yi]")
|
|
local title = vim.fn.getreg('"0')
|
|
title = title:gsub("^(%[)(.+)(%])$", "%2")
|
|
|
|
builtin.live_grep({
|
|
prompt_title = "Notes referencing `" .. title .. "`",
|
|
cwd = M.Cfg.home,
|
|
default_text = "\\[\\[" .. title .. "\\]\\]",
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- YankLink:
|
|
-- -----------
|
|
--
|
|
-- Create and yank a [[link]] from the current note.
|
|
--
|
|
local function YankLink()
|
|
local title = "[["
|
|
.. Pinfo:new({ filepath = vim.fn.expand("%:p"), M.Cfg }).title
|
|
.. "]]"
|
|
vim.fn.setreg('"', title)
|
|
print("yanked " .. title)
|
|
end
|
|
|
|
--
|
|
-- RenameNote:
|
|
-- -----------
|
|
--
|
|
-- Prompt for new note title, rename the note and update all links.
|
|
--
|
|
local function RenameNote()
|
|
local oldfile = Pinfo:new({ filepath = vim.fn.expand("%:p"), M.Cfg })
|
|
|
|
local newname = vim.fn.input("New name: ")
|
|
newname = newname:gsub("(%" .. M.Cfg.extension .. ")$", "")
|
|
local newpath = newname:match("(.*/)") or ""
|
|
newpath = M.Cfg.home .. "/" .. newpath
|
|
|
|
-- If no subdir specified, place the new note in the same place as old note
|
|
if
|
|
M.Cfg.subdirs_in_links == true
|
|
and newpath == M.Cfg.home .. "/"
|
|
and oldfile.sub_dir ~= ""
|
|
then
|
|
newname = oldfile.sub_dir .. "/" .. newname
|
|
end
|
|
|
|
local fname = M.Cfg.home .. "/" .. newname .. M.Cfg.extension
|
|
local fexists = file_exists(fname)
|
|
if fexists then
|
|
print_error("File alreay exists. Renaming abandonned")
|
|
return
|
|
end
|
|
|
|
-- Savas newfile, delete buffer of old one and remove old file
|
|
if newname ~= "" and newname ~= oldfile.title then
|
|
if not (check_dir_and_ask(newpath, "Renamed file")) then
|
|
return
|
|
end
|
|
|
|
local oldTitle = oldfile.title:gsub(" ", "\\ ")
|
|
vim.cmd("saveas " .. M.Cfg.home .. "/" .. newname .. M.Cfg.extension)
|
|
vim.cmd("bdelete " .. oldTitle .. M.Cfg.extension)
|
|
os.execute("rm " .. M.Cfg.home .. "/" .. oldTitle .. M.Cfg.extension)
|
|
end
|
|
|
|
if M.Cfg.rename_update_links == true then
|
|
-- Only look for the first part of the link, so we do not touch to #heading or #^paragraph
|
|
-- Should use regex instead to ensure it is a proper link
|
|
local oldlink = "[[" .. oldfile.title
|
|
local newlink = "[[" .. newname
|
|
|
|
-- Save open telekasten buffers before looking for links to replace
|
|
if
|
|
#(vim.fn.getbufinfo({ bufmodified = 1 })) > 1
|
|
and M.Cfg.auto_set_filetype == true
|
|
then
|
|
local answer = vim.fn.input(
|
|
"Telekasten.nvim:"
|
|
.. "Save all telekasten buffers before updating links? [Y/n]"
|
|
)
|
|
answer = vim.fn.trim(answer)
|
|
if answer ~= "n" and answer ~= "N" then
|
|
save_all_tk_buffers()
|
|
end
|
|
end
|
|
|
|
recursive_substitution(M.Cfg.home, oldlink, newlink)
|
|
recursive_substitution(M.Cfg.dailies, oldlink, newlink)
|
|
recursive_substitution(M.Cfg.weeklies, oldlink, newlink)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- GotoDate:
|
|
-- ----------
|
|
--
|
|
-- find note for date and create it if necessary.
|
|
--
|
|
local function GotoDate(opts)
|
|
opts.dates = calculate_dates(opts.date_table)
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
opts.journal_auto_open = opts.journal_auto_open or M.Cfg.journal_auto_open
|
|
|
|
local word = opts.date or os.date(dateformats.date)
|
|
|
|
local fname = M.Cfg.dailies .. "/" .. word .. M.Cfg.extension
|
|
local fexists = file_exists(fname)
|
|
if
|
|
(fexists ~= true)
|
|
and (
|
|
(opts.follow_creates_nonexisting == true)
|
|
or M.Cfg.follow_creates_nonexisting == true
|
|
)
|
|
then
|
|
create_note_from_template(
|
|
word,
|
|
_,
|
|
fname,
|
|
M.note_type_templates.daily,
|
|
opts.dates
|
|
)
|
|
opts.erase = true
|
|
opts.erase_file = fname
|
|
end
|
|
|
|
if opts.journal_auto_open then
|
|
vim.cmd("e " .. fname)
|
|
else
|
|
find_files_sorted({
|
|
prompt_title = "Goto day",
|
|
cwd = M.Cfg.dailies,
|
|
default_text = word,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
|
|
-- open the new note
|
|
if opts.calendar == true then
|
|
vim.cmd("wincmd w")
|
|
end
|
|
vim.cmd("e " .. fname)
|
|
picker_actions.post_open()
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
--
|
|
-- GotoToday:
|
|
-- ----------
|
|
--
|
|
-- find today's daily note and create it if necessary.
|
|
--
|
|
local function GotoToday(opts)
|
|
opts = opts or {}
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local today = os.date(dateformats.date)
|
|
opts.date_table = os.date("*t")
|
|
opts.date = today
|
|
GotoDate(opts)
|
|
end
|
|
|
|
--
|
|
-- FindNotes:
|
|
-- ----------
|
|
--
|
|
-- Select from notes
|
|
--
|
|
local function FindNotes(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Find notes by name",
|
|
cwd = M.Cfg.home,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- InsertImgLink:
|
|
-- --------------
|
|
--
|
|
-- Insert link to image / media, with optional preview
|
|
--
|
|
local function InsertImgLink(opts)
|
|
opts = opts or {}
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Find image/media",
|
|
cwd = M.Cfg.home,
|
|
find_command = M.Cfg.find_command,
|
|
filter_extensions = {
|
|
".png",
|
|
".jpg",
|
|
".bmp",
|
|
".gif",
|
|
".pdf",
|
|
".mp4",
|
|
".webm",
|
|
},
|
|
preview_type = "media",
|
|
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
local selection = action_state.get_selected_entry()
|
|
local fn = selection.value
|
|
fn = make_relative_path(vim.fn.expand("%:p"), fn, "/")
|
|
vim.api.nvim_put({ "" }, "", true, true)
|
|
if opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_img_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_img_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_img_link(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- SearchNotes:
|
|
-- ------------
|
|
--
|
|
-- find the file linked to by the word under the cursor
|
|
--
|
|
local function SearchNotes(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
builtin.live_grep({
|
|
prompt_title = "Search in notes",
|
|
cwd = M.Cfg.home,
|
|
search_dirs = { M.Cfg.home },
|
|
default_text = opts.default_text or vim.fn.expand("<cword>"),
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- ShowBacklinks:
|
|
-- ------------
|
|
--
|
|
-- Find all notes linking to this one
|
|
--
|
|
local function ShowBacklinks(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local title = Pinfo:new({ filepath = vim.fn.expand("%:p"), M.Cfg }).title
|
|
-- or vim.api.nvim_buf_get_name(0)
|
|
builtin.live_grep({
|
|
results_title = "Backlinks to " .. title,
|
|
prompt_title = "Search",
|
|
cwd = M.Cfg.home,
|
|
search_dirs = { M.Cfg.home },
|
|
default_text = "\\[\\[" .. title .. "(#.+)*\\]\\]",
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
|
|
--
|
|
-- CreateNoteSelectTemplate()
|
|
-- --------------------------
|
|
--
|
|
-- Prompts for title, then pops up telescope for template selection,
|
|
-- creates the new note by template and opens it
|
|
|
|
local function on_create_with_template(opts, title)
|
|
if title == nil then
|
|
return
|
|
end
|
|
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
opts.new_note_location = opts.new_note_location or M.Cfg.new_note_location
|
|
opts.template_handling = opts.template_handling or M.Cfg.template_handling
|
|
|
|
local uuid = get_uuid(opts)
|
|
local pinfo = Pinfo:new({
|
|
title = concat_uuid_title(uuid, title),
|
|
opts,
|
|
})
|
|
local fname = pinfo.filepath
|
|
if pinfo.fexists == true then
|
|
-- open the new note
|
|
vim.cmd("e " .. fname)
|
|
picker_actions.post_open()
|
|
return
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Select template...",
|
|
cwd = M.Cfg.templates,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
actions.close(prompt_bufnr)
|
|
-- local template = M.Cfg.templates .. "/" .. action_state.get_selected_entry().value
|
|
local template = action_state.get_selected_entry().value
|
|
-- TODO: pass in the calendar_info returned from the pinfo
|
|
create_note_from_template(
|
|
title,
|
|
uuid,
|
|
fname,
|
|
template,
|
|
pinfo.calendar_info
|
|
)
|
|
-- open the new note
|
|
vim.cmd("e " .. fname)
|
|
picker_actions.post_open()
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
|
|
local function CreateNoteSelectTemplate(opts)
|
|
opts = opts or {}
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
-- vim.ui.input causes ppl problems - see issue #4
|
|
-- vim.ui.input({ prompt = "Title: " }, on_create_with_template)
|
|
local title = vim.fn.input("Title: ")
|
|
title = strip_extension(title, M.Cfg.extension)
|
|
if #title > 0 then
|
|
on_create_with_template(opts, title)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- CreateNote:
|
|
-- ------------
|
|
--
|
|
-- Prompts for title and creates note with default template
|
|
--
|
|
local function on_create(opts, title)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
opts.new_note_location = opts.new_note_location or M.Cfg.new_note_location
|
|
opts.template_handling = opts.template_handling or M.Cfg.template_handling
|
|
|
|
if title == nil then
|
|
return
|
|
end
|
|
|
|
local uuid = get_uuid(opts)
|
|
local pinfo = Pinfo:new({
|
|
title = concat_uuid_title(uuid, title),
|
|
opts,
|
|
})
|
|
local fname = pinfo.filepath
|
|
|
|
if pinfo.fexists ~= true then
|
|
-- TODO: pass in the calendar_info returned in pinfo
|
|
create_note_from_template(
|
|
title,
|
|
uuid,
|
|
fname,
|
|
pinfo.template,
|
|
pinfo.calendar_info
|
|
)
|
|
opts.erase = true
|
|
opts.erase_file = fname
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Created note...",
|
|
cwd = pinfo.root_dir,
|
|
default_text = concat_uuid_title(uuid, title),
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
|
|
local function CreateNote(opts)
|
|
opts = opts or {}
|
|
-- vim.ui.input causes ppl problems - see issue #4
|
|
-- vim.ui.input({ prompt = "Title: " }, on_create)
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
if M.Cfg.template_handling == "always_ask" then
|
|
return CreateNoteSelectTemplate(opts)
|
|
end
|
|
|
|
local title = vim.fn.input("Title: ")
|
|
title = strip_extension(title, M.Cfg.extension)
|
|
if #title > 0 then
|
|
on_create(opts, title)
|
|
end
|
|
end
|
|
|
|
--
|
|
-- FollowLink:
|
|
-- -----------
|
|
--
|
|
-- find the file linked to by the word under the cursor
|
|
--
|
|
local function FollowLink(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
|
|
opts.template_handling = opts.template_handling or M.Cfg.template_handling
|
|
opts.new_note_location = opts.new_note_location or M.Cfg.new_note_location
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local search_mode = "files"
|
|
local title
|
|
local filename_part = ""
|
|
|
|
-- first: check if we're in a tag or a link
|
|
local kind, atcol, tag
|
|
|
|
if opts.follow_tag ~= nil then
|
|
kind = "tag"
|
|
tag = opts.follow_tag
|
|
else
|
|
kind, atcol = check_for_link_or_tag()
|
|
end
|
|
|
|
if kind == "tag" then
|
|
if atcol ~= nil then
|
|
tag = taglinks.get_tag_at(
|
|
vim.api.nvim_get_current_line(),
|
|
atcol,
|
|
M.Cfg
|
|
)
|
|
end
|
|
search_mode = "tag"
|
|
title = tag
|
|
else
|
|
if kind == "link" then
|
|
-- we are in a link
|
|
vim.cmd("normal yi]")
|
|
title = vim.fn.getreg('"0')
|
|
title = title:gsub("^(%[)(.+)(%])$", "%2")
|
|
else
|
|
-- we are in an external [link]
|
|
vim.cmd("normal yi)")
|
|
local url = vim.fn.getreg('"0')
|
|
return follow_url(url)
|
|
end
|
|
|
|
local parts = vim.split(title, "#")
|
|
|
|
-- if there is a #
|
|
if #parts ~= 1 then
|
|
search_mode = "heading"
|
|
title = parts[2]
|
|
filename_part = parts[1]
|
|
parts = vim.split(title, "%^")
|
|
if #parts ~= 1 then
|
|
search_mode = "para"
|
|
title = parts[2]
|
|
end
|
|
end
|
|
|
|
-- this applies to heading and para search_mode
|
|
-- if we cannot find the file, revert to global heading search by
|
|
-- setting filename to empty string
|
|
if #filename_part > 0 then
|
|
local pinfo = Pinfo:new({ title = filename_part })
|
|
filename_part = pinfo.filepath
|
|
if pinfo.fexists == false then
|
|
-- print("error")
|
|
filename_part = ""
|
|
end
|
|
end
|
|
end
|
|
|
|
if search_mode == "files" then
|
|
-- check if fname exists anywhere
|
|
local pinfo = Pinfo:new({ title = title })
|
|
if
|
|
(pinfo.fexists ~= true)
|
|
and (
|
|
(opts.follow_creates_nonexisting == true)
|
|
or M.Cfg.follow_creates_nonexisting == true
|
|
)
|
|
then
|
|
if opts.template_handling == "always_ask" then
|
|
return on_create_with_template(opts, title)
|
|
end
|
|
|
|
if #pinfo.filepath > 0 then
|
|
local uuid = get_uuid(opts)
|
|
create_note_from_template(
|
|
title,
|
|
uuid,
|
|
pinfo.filepath,
|
|
pinfo.template,
|
|
pinfo.calendar_info
|
|
)
|
|
opts.erase = true
|
|
opts.erase_file = pinfo.filepath
|
|
end
|
|
end
|
|
|
|
find_files_sorted({
|
|
prompt_title = "Follow link to note...",
|
|
cwd = pinfo.root_dir,
|
|
default_text = title,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
sort = M.Cfg.sort,
|
|
})
|
|
end
|
|
|
|
if search_mode ~= "files" then
|
|
local search_pattern = title
|
|
local cwd = M.Cfg.home
|
|
|
|
opts.cwd = cwd
|
|
local counts = nil
|
|
if opts.show_link_counts then
|
|
counts = linkutils.generate_backlink_map(M.Cfg)
|
|
end
|
|
|
|
-- display with devicons
|
|
local function iconic_display(display_entry)
|
|
local display_opts = {
|
|
path_display = function(_, e)
|
|
return e:gsub(escape(opts.cwd .. "/"), "")
|
|
end,
|
|
}
|
|
|
|
local hl_group
|
|
local display = utils.transform_path(
|
|
display_opts,
|
|
display_entry.value
|
|
)
|
|
|
|
display_entry.filn = display_entry.filn or display:gsub(":.*", "")
|
|
display, hl_group = utils.transform_devicons(
|
|
display_entry.filn,
|
|
display,
|
|
false
|
|
)
|
|
|
|
if hl_group then
|
|
return display, { { { 1, 3 }, hl_group } }
|
|
else
|
|
return display
|
|
end
|
|
end
|
|
|
|
-- for media_files
|
|
local popup_opts = {}
|
|
opts.get_preview_window = function()
|
|
return popup_opts.preview
|
|
end
|
|
|
|
-- local width = config.width
|
|
-- or config.layout_config.width
|
|
-- or config.layout_config[config.layout_strategy].width
|
|
-- or vim.o.columns
|
|
-- local telescope_win_width
|
|
-- if width > 1 then
|
|
-- telescope_win_width = width
|
|
-- else
|
|
-- telescope_win_width = math.floor(vim.o.columns * width)
|
|
-- end
|
|
local displayer = entry_display.create({
|
|
separator = "",
|
|
items = {
|
|
{ width = 4 },
|
|
{ width = 4 },
|
|
{ remaining = true },
|
|
},
|
|
})
|
|
|
|
local function make_display(entry)
|
|
local fn = entry.filename
|
|
local nlinks = counts.link_counts[fn] or 0
|
|
local nbacks = counts.backlink_counts[fn] or 0
|
|
|
|
if opts.show_link_counts then
|
|
local display, hl = iconic_display(entry)
|
|
|
|
return displayer({
|
|
{
|
|
"L" .. tostring(nlinks),
|
|
function()
|
|
return {
|
|
{ { 0, 1 }, "tkTagSep" },
|
|
{ { 1, 3 }, "tkTag" },
|
|
}
|
|
end,
|
|
},
|
|
{
|
|
"B" .. tostring(nbacks),
|
|
function()
|
|
return {
|
|
{ { 0, 1 }, "tkTagSep" },
|
|
{ { 1, 3 }, "DevIconMd" },
|
|
}
|
|
end,
|
|
},
|
|
{
|
|
display,
|
|
function()
|
|
return hl
|
|
end,
|
|
},
|
|
})
|
|
else
|
|
return iconic_display(entry)
|
|
end
|
|
end
|
|
|
|
local lookup_keys = {
|
|
value = 1,
|
|
ordinal = 1,
|
|
}
|
|
|
|
local find = (function()
|
|
if Path.path.sep == "\\" then
|
|
return function(t)
|
|
local start, _, filn, lnum, col, text = string.find(
|
|
t,
|
|
[[([^:]+):(%d+):(%d+):(.*)]]
|
|
)
|
|
|
|
-- Handle Windows drive letter (e.g. "C:") at the beginning (if present)
|
|
if start == 3 then
|
|
filn = string.sub(t.value, 1, 3) .. filn
|
|
end
|
|
|
|
return filn, lnum, col, text
|
|
end
|
|
else
|
|
return function(t)
|
|
local _, _, filn, lnum, col, text = string.find(
|
|
t,
|
|
[[([^:]+):(%d+):(%d+):(.*)]]
|
|
)
|
|
return filn, lnum, col, text
|
|
end
|
|
end
|
|
end)()
|
|
|
|
local parse = function(t)
|
|
-- print("t: ", vim.inspect(t))
|
|
local filn, lnum, col, text = find(t.value)
|
|
|
|
local ok
|
|
ok, lnum = pcall(tonumber, lnum)
|
|
if not ok then
|
|
lnum = nil
|
|
end
|
|
|
|
ok, col = pcall(tonumber, col)
|
|
if not ok then
|
|
col = nil
|
|
end
|
|
|
|
t.filn = filn
|
|
t.lnum = lnum
|
|
t.col = col
|
|
t.text = text
|
|
|
|
return { filn, lnum, col, text }
|
|
end
|
|
|
|
local function entry_maker(_)
|
|
local mt_vimgrep_entry
|
|
|
|
opts = opts or {}
|
|
|
|
-- local disable_devicons = opts.disable_devicons
|
|
-- local disable_coordinates = opts.disable_coordinates or true
|
|
local only_sort_text = opts.only_sort_text
|
|
|
|
local execute_keys = {
|
|
path = function(t)
|
|
if Path:new(t.filename):is_absolute() then
|
|
return t.filename, false
|
|
else
|
|
return Path:new({ t.cwd, t.filename }):absolute(), false
|
|
end
|
|
end,
|
|
|
|
filename = function(t)
|
|
return parse(t)[1], true
|
|
end,
|
|
|
|
lnum = function(t)
|
|
return parse(t)[2], true
|
|
end,
|
|
|
|
col = function(t)
|
|
return parse(t)[3], true
|
|
end,
|
|
|
|
text = function(t)
|
|
return parse(t)[4], true
|
|
end,
|
|
}
|
|
|
|
-- For text search only, the ordinal value is actually the text.
|
|
if only_sort_text then
|
|
execute_keys.ordinal = function(t)
|
|
return t.text
|
|
end
|
|
end
|
|
|
|
mt_vimgrep_entry = {
|
|
cwd = vim.fn.expand(opts.cwd or vim.loop.cwd()),
|
|
|
|
__index = function(t, k)
|
|
local raw = rawget(mt_vimgrep_entry, k)
|
|
if raw then
|
|
return raw
|
|
end
|
|
|
|
local executor = rawget(execute_keys, k)
|
|
if executor then
|
|
local val, save = executor(t)
|
|
if save then
|
|
rawset(t, k, val)
|
|
end
|
|
return val
|
|
end
|
|
|
|
return rawget(t, rawget(lookup_keys, k))
|
|
end,
|
|
}
|
|
|
|
--
|
|
if opts.show_link_counts then
|
|
mt_vimgrep_entry.display = make_display
|
|
else
|
|
mt_vimgrep_entry.display = iconic_display
|
|
end
|
|
|
|
return function(line)
|
|
return setmetatable({ line }, mt_vimgrep_entry)
|
|
end
|
|
end
|
|
|
|
opts.entry_maker = entry_maker(opts)
|
|
|
|
local live_grepper = finders.new_job(
|
|
function(prompt)
|
|
if not prompt or prompt == "" then
|
|
return nil
|
|
end
|
|
|
|
local search_command = {
|
|
"rg",
|
|
"--vimgrep",
|
|
"-e",
|
|
"^#+\\s" .. prompt,
|
|
"--",
|
|
}
|
|
if search_mode == "para" then
|
|
search_command = {
|
|
"rg",
|
|
"--vimgrep",
|
|
"-e",
|
|
"\\^" .. prompt,
|
|
"--",
|
|
}
|
|
end
|
|
|
|
if search_mode == "tag" then
|
|
search_command = {
|
|
"rg",
|
|
"--vimgrep",
|
|
"-e",
|
|
prompt,
|
|
"--",
|
|
}
|
|
end
|
|
|
|
if #filename_part > 0 then
|
|
table.insert(search_command, filename_part)
|
|
else
|
|
table.insert(search_command, cwd)
|
|
end
|
|
|
|
local ret = vim.tbl_flatten({ search_command })
|
|
return ret
|
|
end,
|
|
opts.entry_maker or make_entry.gen_from_vimgrep(opts),
|
|
opts.max_results,
|
|
opts.cwd
|
|
)
|
|
|
|
-- builtin.live_grep({
|
|
local picker = pickers.new({
|
|
cwd = cwd,
|
|
prompt_title = "Notes referencing `" .. title .. "`",
|
|
default_text = search_pattern,
|
|
initial_mode = "insert",
|
|
-- link to specific file (a daily file): [[2021-02-22]]
|
|
-- link to heading in specific file (a daily file): [[2021-02-22#Touchpoint]]
|
|
-- link to heading globally [[#Touchpoint]]
|
|
-- link to heading in specific file (a daily file): [[The cool note#^xAcSh-xxr]]
|
|
-- link to paragraph globally [[#^xAcSh-xxr]]
|
|
finder = live_grepper,
|
|
previewer = conf.grep_previewer(opts),
|
|
sorter = sorters.highlighter_only(opts),
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("i", "<c-cr>", picker_actions.paste_link(opts))
|
|
map("n", "<c-cr>", picker_actions.paste_link(opts))
|
|
return true
|
|
end,
|
|
})
|
|
picker:find()
|
|
end
|
|
end
|
|
|
|
--
|
|
-- GotoThisWeek:
|
|
-- -------------
|
|
--
|
|
-- find this week's weekly note and create it if necessary.
|
|
--
|
|
local function GotoThisWeek(opts)
|
|
opts = opts or {}
|
|
opts.insert_after_inserting = opts.insert_after_inserting
|
|
or M.Cfg.insert_after_inserting
|
|
opts.close_after_yanking = opts.close_after_yanking
|
|
or M.Cfg.close_after_yanking
|
|
opts.journal_auto_open = opts.journal_auto_open or M.Cfg.journal_auto_open
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local dinfo = calculate_dates()
|
|
local title = dinfo.isoweek
|
|
local fname = M.Cfg.weeklies .. "/" .. title .. M.Cfg.extension
|
|
local fexists = file_exists(fname)
|
|
if
|
|
(fexists ~= true)
|
|
and (
|
|
(opts.weeklies_create_nonexisting == true)
|
|
or M.Cfg.weeklies_create_nonexisting == true
|
|
)
|
|
then
|
|
create_note_from_template(title, _, fname, M.note_type_templates.weekly)
|
|
opts.erase = true
|
|
opts.erase_file = fname
|
|
end
|
|
|
|
if opts.journal_auto_open then
|
|
vim.cmd("e " .. fname)
|
|
else
|
|
find_files_sorted({
|
|
prompt_title = "Goto this week:",
|
|
cwd = M.Cfg.weeklies,
|
|
default_text = title,
|
|
find_command = M.Cfg.find_command,
|
|
attach_mappings = function(_, map)
|
|
actions.select_default:replace(picker_actions.select_default)
|
|
map("i", "<c-y>", picker_actions.yank_link(opts))
|
|
map("i", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-y>", picker_actions.yank_link(opts))
|
|
map("n", "<c-i>", picker_actions.paste_link(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
--
|
|
-- Calendar Stuff
|
|
-- --------------
|
|
|
|
-- return if a daily 'note exists' indicator (sign) should be displayed for a particular day
|
|
local function CalendarSignDay(day, month, year)
|
|
local fn = M.Cfg.dailies
|
|
.. "/"
|
|
.. string.format("%04d-%02d-%02d", year, month, day)
|
|
.. M.Cfg.extension
|
|
if file_exists(fn) then
|
|
return 1
|
|
end
|
|
return 0
|
|
end
|
|
|
|
-- action on enter on a specific day:
|
|
-- preview in telescope, stay in calendar on cancel, open note in other window on accept
|
|
local function CalendarAction(day, month, year, _, _)
|
|
local opts = {}
|
|
opts.date = string.format("%04d-%02d-%02d", year, month, day)
|
|
opts.date_table = { year = year, month = month, day = day }
|
|
opts.calendar = true
|
|
GotoDate(opts)
|
|
end
|
|
|
|
local function ShowCalendar(opts)
|
|
local defaults = {}
|
|
defaults.cmd = "CalendarVR"
|
|
defaults.vertical_resize = 1
|
|
|
|
opts = opts or defaults
|
|
vim.cmd(opts.cmd)
|
|
if opts.vertical_resize then
|
|
vim.cmd("vertical resize +" .. opts.vertical_resize)
|
|
end
|
|
vim.cmd("set signcolumn=no")
|
|
end
|
|
|
|
-- set up calendar integration: forward to our lua functions
|
|
local function SetupCalendar(opts)
|
|
local defaults = M.Cfg.calendar_opts
|
|
opts = opts or defaults
|
|
|
|
local cmd = [[
|
|
function! MyCalSign(day, month, year)
|
|
return luaeval('require("telekasten").CalendarSignDay(_A[1], _A[2], _A[3])', [a:day, a:month, a:year])
|
|
endfunction
|
|
|
|
function! MyCalAction(day, month, year, weekday, dir)
|
|
" day : day
|
|
" month : month
|
|
" year year
|
|
" weekday : day of week (monday=1)
|
|
" dir : direction of calendar
|
|
return luaeval('require("telekasten").CalendarAction(_A[1], _A[2], _A[3], _A[4], _A[5])',
|
|
\ [a:day, a:month, a:year, a:weekday, a:dir])
|
|
endfunction
|
|
|
|
function! MyCalBegin()
|
|
" too early, windown doesn't exist yet
|
|
" cannot resize
|
|
endfunction
|
|
|
|
let g:calendar_sign = 'MyCalSign'
|
|
let g:calendar_action = 'MyCalAction'
|
|
" let g:calendar_begin = 'MyCalBegin'
|
|
|
|
let g:calendar_monday = {{calendar_monday}}
|
|
let g:calendar_mark = '{{calendar_mark}}'
|
|
let g:calendar_weeknm = {{weeknm}}
|
|
]]
|
|
|
|
for k, v in pairs(opts) do
|
|
cmd = cmd:gsub("{{" .. k .. "}}", v)
|
|
end
|
|
vim.cmd(cmd)
|
|
end
|
|
|
|
local function ToggleTodo(opts)
|
|
-- replace
|
|
-- by -
|
|
-- - by - [ ]
|
|
-- - [ ] by - [x]
|
|
-- - [x] by -
|
|
-- enter insert mode if opts.i == true
|
|
opts = opts or {}
|
|
local linenr = vim.api.nvim_win_get_cursor(0)[1]
|
|
local curline = vim.api.nvim_buf_get_lines(0, linenr - 1, linenr, false)[1]
|
|
local stripped = vim.trim(curline)
|
|
local repline
|
|
if
|
|
vim.startswith(stripped, "- ") and not vim.startswith(stripped, "- [")
|
|
then
|
|
repline = curline:gsub("%- ", "- [ ] ", 1)
|
|
else
|
|
if vim.startswith(stripped, "- [ ]") then
|
|
repline = curline:gsub("%- %[ %]", "- [x]", 1)
|
|
else
|
|
if vim.startswith(stripped, "- [x]") then
|
|
repline = curline:gsub("%- %[x%]", "-", 1)
|
|
else
|
|
repline = curline:gsub("(%S)", "- [ ] %1", 1)
|
|
end
|
|
end
|
|
end
|
|
vim.api.nvim_buf_set_lines(0, linenr - 1, linenr, false, { repline })
|
|
if opts.i then
|
|
vim.api.nvim_feedkeys("A", "m", false)
|
|
end
|
|
end
|
|
|
|
local function FindAllTags(opts)
|
|
opts = opts or {}
|
|
local i = opts.i
|
|
opts.cwd = M.Cfg.home
|
|
opts.tag_notation = M.Cfg.tag_notation
|
|
|
|
if not global_dir_check() then
|
|
return
|
|
end
|
|
|
|
local tag_map = tagutils.do_find_all_tags(opts)
|
|
local taglist = {}
|
|
|
|
local max_tag_len = 0
|
|
for k, v in pairs(tag_map) do
|
|
taglist[#taglist + 1] = { tag = k, details = v }
|
|
if #k > max_tag_len then
|
|
max_tag_len = #k
|
|
end
|
|
end
|
|
|
|
if M.Cfg.show_tags_theme == "get_cursor" then
|
|
opts = themes.get_cursor({
|
|
layout_config = {
|
|
height = math.min(math.floor(vim.o.lines * 0.8), #taglist),
|
|
},
|
|
})
|
|
elseif M.Cfg.show_tags_theme == "ivy" then
|
|
opts = themes.get_ivy({
|
|
layout_config = {
|
|
prompt_position = "top",
|
|
height = math.min(math.floor(vim.o.lines * 0.8), #taglist),
|
|
},
|
|
})
|
|
else
|
|
opts = themes.get_dropdown({
|
|
layout_config = {
|
|
prompt_position = "top",
|
|
height = math.min(math.floor(vim.o.lines * 0.8), #taglist),
|
|
},
|
|
})
|
|
end
|
|
-- re-apply
|
|
opts.cwd = M.Cfg.home
|
|
opts.tag_notation = M.Cfg.tag_notation
|
|
opts.i = i
|
|
pickers.new(opts, {
|
|
prompt_title = "Tags",
|
|
finder = finders.new_table({
|
|
results = taglist,
|
|
entry_maker = function(entry)
|
|
return {
|
|
value = entry,
|
|
-- display = entry.tag .. ' \t (' .. #entry.details .. ' matches)',
|
|
display = string.format(
|
|
"%" .. max_tag_len .. "s ... (%3d matches)",
|
|
entry.tag,
|
|
#entry.details
|
|
),
|
|
ordinal = entry.tag,
|
|
}
|
|
end,
|
|
}),
|
|
sorter = conf.generic_sorter(opts),
|
|
attach_mappings = function(prompt_bufnr, map)
|
|
actions.select_default:replace(function()
|
|
-- actions for insert tag, default action: search for tag
|
|
local selection = action_state.get_selected_entry().value.tag
|
|
local follow_opts = {
|
|
follow_tag = selection,
|
|
show_link_counts = true,
|
|
}
|
|
actions._close(prompt_bufnr, false)
|
|
vim.schedule(function()
|
|
FollowLink(follow_opts)
|
|
end)
|
|
end)
|
|
map("i", "<c-y>", picker_actions.yank_tag(opts))
|
|
map("i", "<c-i>", picker_actions.paste_tag(opts))
|
|
map("n", "<c-y>", picker_actions.yank_tag(opts))
|
|
map("n", "<c-i>", picker_actions.paste_tag(opts))
|
|
map("n", "<c-c>", picker_actions.close(opts))
|
|
map("n", "<esc>", picker_actions.close(opts))
|
|
return true
|
|
end,
|
|
}):find()
|
|
end
|
|
|
|
-- Setup(cfg)
|
|
--
|
|
-- Overrides config with elements from cfg. See top of file for defaults.
|
|
--
|
|
local function Setup(cfg)
|
|
cfg = cfg or {}
|
|
local debug = cfg.debug
|
|
for k, v in pairs(cfg) do
|
|
-- merge everything but calendar opts
|
|
-- they will be merged later
|
|
if k ~= "calendar_opts" then
|
|
if k == "home" then
|
|
v = CleanPath(v)
|
|
end
|
|
M.Cfg[k] = v
|
|
if debug then
|
|
print(
|
|
"Setup() setting `"
|
|
.. k
|
|
.. "` -> `"
|
|
.. tostring(v)
|
|
.. "`"
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- TODO: this is obsolete:
|
|
if vim.fn.executable("rg") == 1 then
|
|
M.Cfg.find_command = { "rg", "--files", "--sortr", "created" }
|
|
else
|
|
M.Cfg.find_command = nil
|
|
end
|
|
|
|
-- this looks a little messy
|
|
if M.Cfg.plug_into_calendar then
|
|
cfg.calendar_opts = cfg.calendar_opts or {}
|
|
M.Cfg.calendar_opts = M.Cfg.calendar_opts or {}
|
|
M.Cfg.calendar_opts.weeknm = cfg.calendar_opts.weeknm
|
|
or M.Cfg.calendar_opts.weeknm
|
|
or 1
|
|
M.Cfg.calendar_opts.calendar_monday = cfg.calendar_opts.calendar_monday
|
|
or M.Cfg.calendar_opts.calendar_monday
|
|
or 1
|
|
M.Cfg.calendar_opts.calendar_mark = cfg.calendar_opts.calendar_mark
|
|
or M.Cfg.calendar_opts.calendar_mark
|
|
or "left-fit"
|
|
SetupCalendar(M.Cfg.calendar_opts)
|
|
end
|
|
|
|
-- setup extensions to filter for
|
|
M.Cfg.filter_extensions = cfg.filter_extensions or { M.Cfg.extension }
|
|
|
|
-- provide fake filenames for template loading to fail silently if template is configured off
|
|
M.Cfg.template_new_note = M.Cfg.template_new_note or "none"
|
|
M.Cfg.template_new_daily = M.Cfg.template_new_daily or "none"
|
|
M.Cfg.template_new_weekly = M.Cfg.template_new_weekly or "none"
|
|
|
|
-- refresh templates
|
|
M.note_type_templates = {
|
|
normal = M.Cfg.template_new_note,
|
|
daily = M.Cfg.template_new_daily,
|
|
weekly = M.Cfg.template_new_weekly,
|
|
}
|
|
|
|
-- for previewers to pick up our syntax, we need to tell plenary to override `.md` with our syntax
|
|
if M.Cfg.auto_set_filetype then
|
|
filetype.add_file("telekasten")
|
|
end
|
|
-- setting the syntax moved into plugin/telekasten.vim
|
|
-- and does not work
|
|
|
|
if M.Cfg.take_over_my_home == true then
|
|
if M.Cfg.auto_set_filetype then
|
|
vim.cmd(
|
|
"au BufEnter "
|
|
.. M.Cfg.home
|
|
.. "/*"
|
|
.. M.Cfg.extension
|
|
.. " set ft=telekasten"
|
|
)
|
|
end
|
|
end
|
|
|
|
if debug then
|
|
print("Resulting config:")
|
|
print("-----------------")
|
|
print(vim.inspect(M.Cfg))
|
|
end
|
|
|
|
-- Convert all directories in full path
|
|
M.Cfg.image_subdir = make_config_path_absolute(M.Cfg.image_subdir)
|
|
M.Cfg.dailies = make_config_path_absolute(M.Cfg.dailies)
|
|
M.Cfg.weeklies = make_config_path_absolute(M.Cfg.weeklies)
|
|
M.Cfg.templates = make_config_path_absolute(M.Cfg.templates)
|
|
end
|
|
|
|
M.find_notes = FindNotes
|
|
M.find_daily_notes = FindDailyNotes
|
|
M.search_notes = SearchNotes
|
|
M.insert_link = InsertLink
|
|
M.follow_link = FollowLink
|
|
M.setup = Setup
|
|
M.goto_today = GotoToday
|
|
M.new_note = CreateNote
|
|
M.goto_thisweek = GotoThisWeek
|
|
M.find_weekly_notes = FindWeeklyNotes
|
|
M.yank_notelink = YankLink
|
|
M.rename_note = RenameNote
|
|
M.new_templated_note = CreateNoteSelectTemplate
|
|
M.show_calendar = ShowCalendar
|
|
M.CalendarSignDay = CalendarSignDay
|
|
M.CalendarAction = CalendarAction
|
|
M.paste_img_and_link = imgFromClipboard
|
|
M.toggle_todo = ToggleTodo
|
|
M.show_backlinks = ShowBacklinks
|
|
M.find_friends = FindFriends
|
|
M.insert_img_link = InsertImgLink
|
|
M.preview_img = PreviewImg
|
|
M.browse_media = BrowseImg
|
|
M.taglinks = taglinks
|
|
M.show_tags = FindAllTags
|
|
|
|
-- Telekasten command, completion
|
|
local TelekastenCmd = {
|
|
commands = function()
|
|
return {
|
|
{ "find notes", "find_notes", M.find_notes },
|
|
{ "find daily notes", "find_daily_notes", M.find_daily_notes },
|
|
{ "search in notes", "search_notes", M.search_notes },
|
|
{ "insert link", "insert_link", M.insert_link },
|
|
{ "follow link", "follow_link", M.follow_link },
|
|
{ "goto today", "goto_today", M.goto_today },
|
|
{ "new note", "new_note", M.new_note },
|
|
{ "goto thisweek", "goto_thisweek", M.goto_thisweek },
|
|
{ "find weekly notes", "find_weekly_notes", M.find_weekly_notes },
|
|
{ "yank link to note", "yank_notelink", M.yank_notelink },
|
|
{ "rename note", "rename_note", M.rename_note },
|
|
{
|
|
"new templated note",
|
|
"new_templated_note",
|
|
M.new_templated_note,
|
|
},
|
|
{ "show calendar", "show_calendar", M.show_calendar },
|
|
{
|
|
"paste image from clipboard",
|
|
"paste_img_and_link",
|
|
M.paste_img_and_link,
|
|
},
|
|
{ "toggle todo", "toggle_todo", M.toggle_todo },
|
|
{ "show backlinks", "show_backlinks", M.show_backlinks },
|
|
{ "find friend notes", "find_friends", M.find_friends },
|
|
{
|
|
"browse images, insert link",
|
|
"insert_img_link",
|
|
M.insert_img_link,
|
|
},
|
|
{ "preview image under cursor", "preview_img", M.preview_img },
|
|
{ "browse media", "browse_media", M.browse_media },
|
|
{ "panel", "panel", M.panel },
|
|
{ "show tags", "show_tags", M.show_tags },
|
|
}
|
|
end,
|
|
}
|
|
|
|
TelekastenCmd.command = function(subcommand)
|
|
local show = function(opts)
|
|
opts = opts or {}
|
|
pickers.new(opts, {
|
|
prompt_title = "Command palette",
|
|
finder = finders.new_table({
|
|
results = TelekastenCmd.commands(),
|
|
entry_maker = function(entry)
|
|
return {
|
|
value = entry,
|
|
display = entry[1],
|
|
ordinal = entry[2],
|
|
}
|
|
end,
|
|
}),
|
|
sorter = conf.generic_sorter(opts),
|
|
attach_mappings = function(prompt_bufnr, _)
|
|
actions.select_default:replace(function()
|
|
-- important: actions.close(bufnr) is not enough
|
|
-- it resulted in: preview_img NOT receiving the prompt as default text
|
|
-- apparently it has sth to do with keeping insert mode
|
|
actions._close(prompt_bufnr, true)
|
|
|
|
local selection = action_state.get_selected_entry().value[3]
|
|
selection()
|
|
end)
|
|
return true
|
|
end,
|
|
}):find()
|
|
end
|
|
if subcommand then
|
|
-- print("trying subcommand " .. "`" .. subcommand .. "`")
|
|
for _, entry in pairs(TelekastenCmd.commands()) do
|
|
if entry[2] == subcommand then
|
|
local selection = entry[3]
|
|
selection()
|
|
return
|
|
end
|
|
end
|
|
print("No such subcommand: `" .. subcommand .. "`")
|
|
else
|
|
local theme
|
|
|
|
if M.Cfg.command_palette_theme == "ivy" then
|
|
theme = themes.get_ivy()
|
|
else
|
|
theme = themes.get_dropdown({
|
|
layout_config = { prompt_position = "top" },
|
|
})
|
|
end
|
|
show(theme)
|
|
end
|
|
end
|
|
|
|
-- nvim completion function for completing :Telekasten sub-commands
|
|
TelekastenCmd.complete = function()
|
|
local candidates = {}
|
|
for k, v in pairs(TelekastenCmd.commands()) do
|
|
candidates[k] = v[2]
|
|
end
|
|
return candidates
|
|
end
|
|
|
|
M.panel = TelekastenCmd.command
|
|
M.Command = TelekastenCmd
|
|
|
|
return M
|