-
-
Notifications
You must be signed in to change notification settings - Fork 90
/
telekasten.lua
2847 lines (2550 loc) · 89.6 KB
/
telekasten.lua
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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
-- ----------------------------------------------------------------------------
-- DEFAULT CONFIG
-- ----------------------------------------------------------------------------
local home = vim.fn.expand("~/zettelkasten")
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",
-- following a link to a non-existing note will create it
follow_creates_nonexisting = true,
dailies_create_nonexisting = true,
weeklies_create_nonexisting = true,
-- 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_subdir/xxxxx.png)
image_link_style = "markdown",
-- 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 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 -l -t markdown '"
.. old
.. "' "
.. dir
.. " | xargs "
.. 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
-- ----------------------------------------------------------------------------
-- image stuff
local function imgFromClipboard()
if not global_dir_check() then
return
end
if vim.fn.executable("xclip") == 0 then
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 pngpath = M.Cfg.home .. "/" .. pngname
local relpath = pngname
if M.Cfg.image_subdir then
relpath = Path:new(M.Cfg.image_subdir):make_relative(M.Cfg.home)
.. "/"
.. pngname
pngpath = M.Cfg.image_subdir .. "/" .. pngname
end
os.execute("xclip -selection clipboard -t image/png -o > " .. pngpath)
if file_exists(pngpath) then
if M.Cfg.image_link_style == "markdown" then
vim.api.nvim_put({ "![](" .. relpath .. ")" }, "", true, true)
else
vim.api.nvim_put({ "![[" .. pngname .. "]]" }, "", true, true)
end
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)
if dates == nil then
dates = calculate_dates()
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,
}
for k, v in pairs(substs) do
line = line:gsub("{{" .. k .. "}}", v)
end
return line
end
local function create_note_from_template(
title,
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) .. "\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 order_numeric(a, b)
return a > b
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)
table.sort(file_list, order_numeric)
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),