diff --git a/README.md b/README.md index 57d95a1..acf5c78 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ also just insert a link to the note or yank a link instead of opening the note. ![](img/tags-linkcounts.png) - ## Contents @@ -76,6 +75,7 @@ also just insert a link to the note or yank a link instead of opening the note. - [Hard-coded stuff](#hard-coded-stuff) ## Requirements + Telekasten requires Neovim v0.6.0 or higher. Besides that, its only mandatory dependency of is [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim), which acts @@ -115,7 +115,6 @@ telekasten.requirements`). -
Vim-plug @@ -136,8 +135,8 @@ telekasten.requirements`).
- ### Base setup + In order to use Telekasten, you need to first require its setup function somewhere in your `init.lua`. Take this opportunity to indicate the path for your notes directory. If you do not specify anything, the plugin will ask you to @@ -148,6 +147,7 @@ require('telekasten').setup({ home = vim.fn.expand("~/zettelkasten"), -- Put the name of your notes directory here }) ``` + **NOTE:** For Windows users, please indicate the path as `C:/Users/username/zettelkasten/`. See `:h telekasten.windows` for more details about the specificities for Windows. @@ -155,26 +155,29 @@ about the specificities for Windows. ### Suggested dependencies #### Calendar + Telekasten interacts very nicely with [calendar-vim](https://github.com/renerocksai/calendar-vim). Installing this plugin will allow you to create journal entries for the selected dates and highlight dates with attached entries. #### Image preview + Various plugins or external tools can be used as image previewers to help you pick the correct illustrations for your note. + - [telescope-media-files.nvim](https://github.com/nvim-telescope/telescope-media-files.nvim) - [catimg](https://github.com/posva/catimg) - [viu](https://github.com/atanunq/viu) #### Image pasting + - [xclip](https://github.com/astrand/xclip) - [wl-clipboard](https://github.com/bugaevc/wl-clipboard) _Image pasting is supported by default on MacOS, it is not necessary to install any other tool._ - #### Other useful resources/plugins While they do not interact directly with Telekasten, the following plugins @@ -191,15 +194,18 @@ greatly improve the note-taking experience. generate a table of contents for your markdown documents - [synctodo](https://github.com/cnshsliu/synctodo): bash script to sync todos among Telekasten, Mac and iPhone reminders. - +- [telescope-all-recent](https://github.com/prochri/telescope-all-recent.nvim): + shows files you have recently opened. ## Usage The simplest way to use the plugin is to call directly the related Telekasten command: + ```vim :Telekasten ``` +
Advanced use Each sub-command is implemented by a specific lua function. While high-level @@ -211,14 +217,13 @@ custom mappings. :lua require('telekasten').search_notes() ``` -See the [wiki](https://github.com/renerocksai/telekasten.nvim/wiki/Mappings#advanced-key-mappings) for more details regarding advanced usage.
### Commands The following sub-commands are defined: -- `panel` : brings up the [command palette](command-palette) +- `panel` : brings up the [command palette](#command-palette) - `find_notes` : Find notes by title (filename) - `show_tags` : brings up the tag list. From there you can select a tag to search for tagged notes - or yank or insert the tag - `find_daily_notes` : Find daily notes by title (date) @@ -243,8 +248,7 @@ The following sub-commands are defined: - `switch_vault` : switch the vault. Brings up a picker. See the `vaults` config option for more. - -### Command palette +### Command palette Telekasten comes with a small helper command palette that let the user browse the different commands available. This feature is quite similar to the excellent @@ -252,14 +256,15 @@ the different commands available. This feature is quite similar to the excellent limited to Telekasten. You can call this panel using + ```vim :Telekasten panel ``` + This can be especially useful if all your Telekasten mappings start with the same prefix. In that case, bind the command panel to the prefix only and it will pop-up when you hesitate to complete the mapping. - ## Customization ### Highlights @@ -268,6 +273,7 @@ Telekasten.nvim allows you to color your `[[links]]` and `#tags` by providing the following syntax groups: - `tkLink` : the link title inside the brackets +- `tkAliasedLink` : the concealed portion of `[[concealed link|link alias]]` - `tkBrackets` : the brackets surrounding the link title - `tkHighlight` : ==highlighted== text (non-standard markdown) - `tkTag` : well, tags @@ -275,7 +281,6 @@ the following syntax groups: An additional `CalNavi` group is defined to tweak the appearance of the calendar navigation button. - ```vim " Example hi tkLink ctermfg=Blue cterm=bold,underline guifg=blue gui=bold,underline @@ -289,7 +294,6 @@ workflow even smoother. A good idea is to take advantage of the [command palette][#command-palette] and start all your mappings with the same prefix (`z`, for `Z`ettelkasten for instance). - ```lua -- Launch panel if nothing is typed after z vim.keymap.set("n", "z", "Telekasten panel") @@ -309,18 +313,16 @@ vim.keymap.set("i", "[[", "Telekasten insert_link") ``` - #### Advanced mappings + Each Telekasten command is bound to a specific lua function. As lua functions can accept arguments, it is possible to craft special mappings to tailor the execution of a function to your specific need. -See the [wiki](https://github.com/renerocksai/telekasten.nvim/wiki/Mappings#advanced-key-mappings) for more details regarding advanced key mappings. - - ## Features ### Vaults + Telekasten allows the user to have completely separated note collections and switch between them easily. Simply add data to the `vaults` table in the configuration and configure each vault as you wish. @@ -356,15 +358,14 @@ these can be previewed. See the documentation for more details regarding the different types of links (`:h telekasten.link_notation`). - ### Tag notation Telekasten supports the following tag notations: 1. `#tag` -2. `:tag:` -3. `yaml-bare`: bare tags in a tag collection in the yaml metadata: - +2. `@tag` +3. `:tag:` +4. `yaml-bare`: bare tags in a tag collection in the yaml metadata: See the documentation for more details regarding the tag syntax (`:h telekasten.tag_notation`). diff --git a/doc/telekasten.txt b/doc/telekasten.txt index 1b136a6..019a798 100644 --- a/doc/telekasten.txt +++ b/doc/telekasten.txt @@ -69,8 +69,8 @@ telekasten.setup({opts}) -- Main paths home = '/path/to/directory', -- path to main notes folder - daily = '/path/to/directory', -- path to daily notes - weekly = '/path/to/directory', -- path to weekly notes + dailies = '/path/to/directory', -- path to daily notes + weeklies = '/path/to/directory', -- path to weekly notes templates = '/path/to/directory', -- path to templates -- Specific note templates @@ -103,6 +103,9 @@ telekasten.setup({opts}) dailies_create_nonexisting = true, -- create non-existing dailies weeklies_create_nonexisting = true, -- create non-existing weeklies + -- skip telescope prompt for goto_today and goto_thisweek + journal_auto_open = false, + -- Image link style", -- wiki: ![[image name]] -- markdown: ![](image_subdir/xxxxx.png) @@ -114,7 +117,7 @@ telekasten.setup({opts}) -- Make syntax available to markdown buffers and telescope previewers install_syntax = true, - -- Tag notation: '#tag', ':tag:', 'yaml-bare' + -- Tag notation: '#tag', '@tag', ':tag:', 'yaml-bare' tag_notation = "#tag", -- When linking to a note in subdir/, create a [[subdir/title]] link @@ -125,7 +128,9 @@ telekasten.setup({opts}) command_palette_theme = "ivy", -- Tag list theme: - -- get_cursor: small tag list at cursor; ivy and dropdown like above + -- get_cursor (small tag list at cursor) + -- dropdown (window) + -- ivy (bottom panel) show_tags_theme = "ivy", -- Previewer for media files (images mostly) @@ -134,6 +139,20 @@ telekasten.setup({opts}) -- "viu-previewer" if you have viu installed media_previewer = "telescope-media-files", + -- Customize insert image and preview image files list. This is useful + -- to add optional filetypes into filtered list (for example + -- telescope-media-files optionally supporting svg preview) + media_extensions = { + ".png", + ".jpg", + ".bmp", + ".gif", + ".pdf", + ".mp4", + ".webm", + ".webp", + }, + -- Calendar integration plug_into_calendar = true, -- use calendar integration calendar_opts = { @@ -162,6 +181,8 @@ telekasten.setup({opts}) } }, + -- Specify a clipboard program to use + clipboard_program = "", -- xsel, xclip, wl-paste, osascript } < @@ -331,6 +352,14 @@ telekasten.setup({opts}) Default: true + *telekasten.settings.journal_auto_open* + journal_auto_open: ~ + Flag that determines whether the telescope picker should be used when + opening the daily and weekly notes, or if they should be directly + created/opened. + + Default: false + *telekasten.settings.template_new_note* template_new_note: ~ Markdown template for new notes. Set to `nil` if you don't want a @@ -369,6 +398,7 @@ telekasten.setup({opts}) Valid options are: - `'#tag'` + - `'@tag'` - `':tag:'` - `'yaml-bare'` @@ -428,7 +458,7 @@ telekasten.setup({opts}) *telekasten.settings.media_previewer* media_previewer:~ - Previewer used for viewing media / image files. There are two + Previewer used for viewing media / image files. There are three options: - `'telescope-media-files'` : use the telescope-media-files.nvim plugin @@ -446,6 +476,14 @@ telekasten.setup({opts}) Default: `nil` Example: `"call jobstart('firefox --new-window {{url}}')"` + + *telekasten.settings.enable_create_new* + enable_create_new: ~ + Flag that determines if creating new notes with in enabled when + using the `find_notes` picker. + + Default: true + *telekasten.calendar_opts* ----------------------------------- Valid keys for {opts.calendar_opts} @@ -496,10 +534,7 @@ groups: - `tkBrackets` : the brackets surrounding the link title - `tkHighlight` : ==highlighted== text (non-standard markdown) - `tkTag` : well, tags -- `tkAliasedLink` : link with alias -- `tkHighlightedAliasedLink` : link with alias in `tkAliasedLink` -- `tkLinkAlias` : body of the link (left part of `[[aaa|bbb]]`) -- `tkLinkBody` : alias of the link (right part of `[[aaa|bbb]]`) +- `tkAliasedLink` : the concealed part of the link (left part of `[[aaa|bbb]]`) `tkHighlight`, has nothing to do with links but I added it anyway, since I like highlighting text when taking notes. @@ -530,6 +565,12 @@ You can assign colors to the new syntax groups in your `init.vim`: hi tkTag ctermfg=175 guifg=#d3869B < +NOTE: Users configuring the following highlight groups prior to 09/2023, be aware they are deprecated: +- `tkHighlightedAliasLink`: this syntax group is now covered by `tkHighlight` +- `tkLinkBody`: this syntax group is renamed to `tkAliasedLink` +- `tkAliasedLink`: this group used to cover the whole link, it is now equivalent to the former `tkLinkBody` +- `tkLinkAlias`: this rule is deprecated + ================================================================================ Section 3: Usage *telekasten.usage* @@ -666,7 +707,7 @@ telekasten.insert_link({opts})~ Default: `nil` *telekasten.follow_link()* -telekasten.insert_link()~ +telekasten.follow_link()~ Take the text between the brackets of a link pointing to a linked note, or of a tag and open a Telescope file finder with it. @@ -817,8 +858,9 @@ Section 3.2: Tag Notation *telekasten.tag_notation* Telekasten supports the following tag notations: 1. `#tag` -2. `:tag:` -3. bare tags in a tag collection in the yaml metadata: +2. `@tag` +3. `:tag:` +4. bare tags in a tag collection in the yaml metadata: > --- title: My awesome note diff --git a/lua/telekasten.lua b/lua/telekasten.lua index b763c8d..ce6fa93 100644 --- a/lua/telekasten.lua +++ b/lua/telekasten.lua @@ -59,40 +59,47 @@ local function make_config_path_absolute(path) if not (Path:new(path):is_absolute()) and path ~= nil then ret = config.options.home .. "/" .. path end - return ret:gsub("/$", "") + + if ret ~= nil then + ret = ret:gsub("/$", "") + end + + return ret end local function recursive_substitution(dir, old, new) - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - if vim.fn.executable("sed") == 0 then - vim.api.nvim_err_write("Sed not installed!\n") - return - end + if vim.fn.executable("sed") == 0 then + vim.api.nvim_err_write("Sed not installed!\n") + return + end - old = tkutils.grep_escape(old) - new = tkutils.grep_escape(new) + old = tkutils.grep_escape(old) + new = tkutils.grep_escape(new) - local sedcommand = "sed -i" - if vim.fn.has("mac") == 1 then - sedcommand = "sed -i ''" - end - - -- 's|\(\[\[foo\)\([]#|\]\)|\[\[MYTEST\2|g' - local replace_cmd = "rg -0 -l -t markdown '" - .. old - .. "' " - .. dir - .. " | xargs -0 " - .. sedcommand - .. " 's|\\(" - .. old - .. "\\)\\([]#|]\\)|" - .. new - .. "\\2|g' >/dev/null 2>&1" - os.execute(replace_cmd) + local sedcommand = "sed -i" + if vim.fn.has("mac") == 1 then + sedcommand = "sed -i ''" + end + + -- 's|\(\[\[foo\)\([]#|\]\)|\[\[MYTEST\2|g' + local replace_cmd = "rg -0 -l -t markdown '" + .. old + .. "' " + .. dir + .. " | xargs -0 " + .. sedcommand + .. " 's|\\(" + .. old + .. "\\)\\([]#|]\\)|" + .. new + .. "\\2|g' >/dev/null 2>&1" + os.execute(replace_cmd) + end) end local function save_all_mod_buffers() @@ -160,24 +167,33 @@ local function make_relative_path(bufferpath, imagepath, sep) end local function imgFromClipboard() - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local get_paste_command - if vim.fn.executable("xclip") == 1 then - get_paste_command = function(dir, filename) + local paste_command = {} + paste_command["xsel"] = function(dir, filename) + local _image_path = vim.fn.system("xsel --clipboard --output") + local image_path = _image_path:gsub("file://", "") + if + vim.fn + .system("file --mime-type -b " .. image_path) + :gsub("%s+", "") + == "image/png" + then + return "cp " .. image_path .. " " .. dir .. "/" .. filename + else + return "" + end + end + paste_command["xclip"] = function(dir, filename) return "xclip -selection clipboard -t image/png -o > " .. dir .. "/" .. filename end - elseif vim.fn.executable("wl-paste") == 1 then - get_paste_command = function(dir, filename) - return "wl-paste -n -t image/png > " .. dir .. "/" .. filename - end - elseif vim.fn.executable("osascript") == 1 then - get_paste_command = function(dir, filename) + paste_command["osascript"] = 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\\"})"', @@ -185,68 +201,95 @@ local function imgFromClipboard() 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` + paste_command["wl-paste"] = function(dir, filename) + return "wl-paste -n -t image/png > " .. dir .. "/" .. filename + end - -- 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 = config.options.image_subdir or config.options.home - local png = Path:new(pngdir, pngname).filename - local relpath = make_relative_path(vim.fn.expand("%:p"), png, "/") - - local output = vim.fn.system(get_paste_command(pngdir, pngname)) - if output ~= "" then - -- Remove empty file created by previous command if failed - vim.fn.system("rm " .. png) - vim.api.nvim_err_writeln( - string.format( - "Unable to write image %s.\nIs there an image on the clipboard?\nSee also issue 131", - png + local get_paste_command + if paste_command[config.options.clipboard_program] ~= nil then + if vim.fn.executable(config.options.clipboard_program) ~= 1 then + vim.api.nvim_err_write( + "The clipboard program specified [`" + .. config.options.clipboard_program + .. "`] is not executable or not in your $PATH\n" + ) + end + get_paste_command = paste_command[config.options.clipboard_program] + elseif vim.fn.executable("xsel") == 1 then + get_paste_command = paste_command["xsel"] + elseif vim.fn.executable("xclip") == 1 then + get_paste_command = paste_command["xclip"] + elseif vim.fn.executable("wl-paste") == 1 then + get_paste_command = paste_command["wl-paste"] + elseif vim.fn.executable("osascript") == 1 then + get_paste_command = paste_command["osascript"] + else + vim.api.nvim_err_write( + "No clipboard programs found!\nChecked executables: xsel, xclip, wl-paste, osascript\n" ) - ) - end + 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 = config.options.image_subdir + and config.options.image_subdir + or config.options.home + local png = Path:new(pngdir, pngname).filename + local relpath = make_relative_path(vim.fn.expand("%:p"), png, "/") + + local output = vim.fn.system(get_paste_command(pngdir, pngname)) + if output ~= "" then + -- Remove empty file created by previous command if failed + vim.fn.system("rm " .. png) + vim.api.nvim_err_writeln( + string.format( + "Unable to write image %s.\nIs there an image on the clipboard?\nSee also issue 131", + png + ) + ) + end - if fileutils.file_exists(png) then - if config.options.image_link_style == "markdown" then - vim.api.nvim_put({ "![](" .. relpath .. ")" }, "", true, true) + if fileutils.file_exists(png) then + if config.options.image_link_style == "markdown" then + vim.api.nvim_put({ "![](" .. relpath .. ")" }, "", true, true) + else + vim.api.nvim_put({ "![[" .. pngname .. "]]" }, "", true, true) + end else - vim.api.nvim_put({ "![[" .. pngname .. "]]" }, "", true, true) + vim.api.nvim_err_writeln("Unable to write image " .. png) end - else - vim.api.nvim_err_writeln("Unable to write image " .. png) - end + end) end -- end of image stuff @@ -256,7 +299,8 @@ local function create_note_from_template( uuid, filepath, templatefn, - calendar_info + calendar_info, + callback ) -- first, read the template file local lines = {} @@ -267,21 +311,34 @@ local function create_note_from_template( 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( - templates.subst_templated_values( - line, - title, - calendar_info, - uuid, - config.options.calendar_opts.calendar_monday - ) .. "\n" - ) - end + local file_dir = filepath:match("(.*/)") or "" + fileutils.check_dir_and_ask( + file_dir, + "Create weekly dir", + function(dir_succeed) + if dir_succeed == false then + return + end - ofile:flush() - ofile:close() + local ofile = io.open(filepath, "a") + + for _, line in pairs(lines) do + ofile:write( + templates.subst_templated_values( + line, + title, + calendar_info, + uuid, + config.options.calendar_opts.calendar_monday + ) .. "\n" + ) + end + + ofile:flush() + ofile:close() + callback() + end + ) end --- Pinfo @@ -534,10 +591,6 @@ function Pinfo:resolve_link(title, opts) -- 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 @@ -649,8 +702,11 @@ local picker_actions = {} -- - this requires the telescope-media-files.nvim extension local function find_files_sorted(opts) opts = opts or {} + local search_pattern = opts.search_pattern or nil + local search_depth = opts.search_depth or nil + local scan_opts = { search_pattern = search_pattern, depth = search_depth } - local file_list = scan.scan_dir(opts.cwd, {}) + local file_list = scan.scan_dir(opts.cwd, scan_opts) local filter_extensions = opts.filter_extensions or config.options.filter_extensions file_list = filter_filetypes(file_list, filter_extensions) @@ -940,49 +996,62 @@ local function FindDailyNotes(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end - - local today = os.date(dateutils.dateformats.date) - local fname = config.options.dailies - .. "/" - .. today - .. config.options.extension - local fexists = fileutils.file_exists(fname) - if - (fexists ~= true) - and ( - (opts.dailies_create_nonexisting == true) - or config.options.dailies_create_nonexisting == true - ) - then - create_note_from_template( - today, - nil, - fname, - M.note_type_templates.daily - ) - opts.erase = true - opts.erase_file = fname - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - find_files_sorted({ - prompt_title = "Find daily note", - cwd = config.options.dailies, - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - sort = config.options.sort, - }) + local today = os.date(dateutils.dateformats.date) + local fname = config.options.dailies + .. "/" + .. today + .. config.options.extension + local fexists = fileutils.file_exists(fname) + local function picker() + find_files_sorted({ + prompt_title = "Find daily note", + cwd = config.options.dailies, + find_command = config.options.find_command, + search_pattern = "%d%d%d%d%-%d%d%-%d%d", + search_depth = 1, + attach_mappings = function(_, map) + actions.select_default:replace( + picker_actions.select_default + ) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + sort = config.options.sort, + }) + end + if + (fexists ~= true) + and ( + (opts.dailies_create_nonexisting == true) + or config.options.dailies_create_nonexisting == true + ) + then + create_note_from_template( + today, + nil, + fname, + M.note_type_templates.daily, + nil, + function() + opts.erase = true + opts.erase_file = fname + picker() + end + ) + return + end + picker() + end) end -- @@ -998,49 +1067,64 @@ local function FindWeeklyNotes(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local title = os.date(dateutils.dateformats.isoweek) - local fname = config.options.weeklies - .. "/" - .. title - .. config.options.extension - local fexists = fileutils.file_exists(fname) - if - (fexists ~= true) - and ( - (opts.weeklies_create_nonexisting == true) - or config.options.weeklies_create_nonexisting == true - ) - then - create_note_from_template( - title, - nil, - fname, - M.note_type_templates.weekly - ) - opts.erase = true - opts.erase_file = fname - end + local title = os.date(dateutils.dateformats.isoweek) + local fname = config.options.weeklies + .. "/" + .. title + .. config.options.extension + local fexists = fileutils.file_exists(fname) + + local function picker() + find_files_sorted({ + prompt_title = "Find weekly note", + cwd = config.options.weeklies, + find_command = config.options.find_command, + search_pattern = "%d%d%d%d%-W%d+", + search_depth = 1, + attach_mappings = function(_, map) + actions.select_default:replace( + picker_actions.select_default + ) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + sort = config.options.sort, + }) + end - find_files_sorted({ - prompt_title = "Find weekly note", - cwd = config.options.weeklies, - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - sort = config.options.sort, - }) + if + (fexists ~= true) + and ( + (opts.weeklies_create_nonexisting == true) + or config.options.weeklies_create_nonexisting == true + ) + then + create_note_from_template( + title, + nil, + fname, + M.note_type_templates.weekly, + nil, + function() + opts.erase = true + opts.erase_file = fname + picker() + end + ) + return + end + picker() + end) end -- @@ -1058,52 +1142,62 @@ local function InsertLink(opts) opts.subdirs_in_links = opts.subdirs_in_links or config.options.subdirs_in_links - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local cwd = config.options.home - local find_command = config.options.find_command - local sort = config.options.sort - local 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.filename or selection.value, - opts, - }) - vim.api.nvim_put({ "[[" .. pinfo.title .. "]]" }, "", false, true) - if opts.i then - vim.api.nvim_feedkeys("a", "m", false) - end - end) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - return true - end + local cwd = config.options.home + local find_command = config.options.find_command + local sort = config.options.sort + local attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + if selection == nil then + selection = { filename = action_state.get_current_line() } + end + local pinfo = Pinfo:new({ + filepath = selection.filename or selection.value, + opts, + }) + vim.api.nvim_put( + { "[[" .. pinfo.title .. "]]" }, + "", + false, + true + ) + if opts.i then + vim.api.nvim_feedkeys("a", "m", false) + end + end) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + return true + end - if opts.with_live_grep then - builtin.live_grep({ - prompt_title = "Insert link to note with live grep", - cwd = cwd, - attach_mappings = attach_mappings, - find_command = find_command, - sort = sort, - }) - else - find_files_sorted({ - prompt_title = "Insert link to note", - cwd = cwd, - attach_mappings = attach_mappings, - find_command = find_command, - sort = sort, - }) - end + if opts.with_live_grep then + builtin.live_grep({ + prompt_title = "Insert link to note with live grep", + cwd = cwd, + attach_mappings = attach_mappings, + find_command = find_command, + sort = sort, + }) + else + find_files_sorted({ + prompt_title = "Insert link to note", + cwd = cwd, + attach_mappings = attach_mappings, + find_command = find_command, + sort = sort, + }) + end + end) end -- local function check_for_link_or_tag() @@ -1154,32 +1248,71 @@ local function PreviewImg(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end + + local saved_reg = vim.fn.getreg('"0') + vim.cmd("normal yi)") + local fname = vim.fn.getreg('"0'):gsub("^img/", "") + vim.fn.setreg('"0', saved_reg) + + -- check if fname exists anywhere + local imageDir = config.options.image_subdir or config.options.home + local fexists = fileutils.file_exists(imageDir .. "/" .. fname) + + if fexists == true then + find_files_sorted({ + prompt_title = "Preview image/media", + cwd = imageDir, + default_text = fname, + find_command = config.options.find_command, + filter_extensions = config.options.media_extensions, + preview_type = "media", + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions.close(prompt_bufnr) + end) + map("i", "", picker_actions.yank_img_link(opts)) + map("i", "", picker_actions.paste_img_link(opts)) + map("n", "", picker_actions.yank_img_link(opts)) + map("n", "", picker_actions.paste_img_link(opts)) + map("i", "", picker_actions.paste_img_link(opts)) + map("n", "", picker_actions.paste_img_link(opts)) + return true + end, + sort = config.options.sort, + }) + else + print("File not found: " .. config.options.home .. "/" .. fname) + end + end) +end - vim.cmd("normal yi)") - local fname = vim.fn.getreg('"0'):gsub("^img/", "") +-- +-- BrowseImg: +-- ----------- +-- +-- preview media +-- +local function BrowseImg(opts) + opts = opts or {} + opts.insert_after_inserting = opts.insert_after_inserting + or config.options.insert_after_inserting + opts.close_after_yanking = opts.close_after_yanking + or config.options.close_after_yanking - -- check if fname exists anywhere - local imageDir = config.options.image_subdir or config.options.home - local fexists = fileutils.file_exists(imageDir .. "/" .. fname) + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - if fexists == true then find_files_sorted({ prompt_title = "Preview image/media", - cwd = imageDir, - default_text = fname, + cwd = config.options.home, find_command = config.options.find_command, - filter_extensions = { - ".png", - ".jpg", - ".bmp", - ".gif", - ".pdf", - ".mp4", - ".webm", - }, + filter_extensions = config.options.media_extensions, preview_type = "media", attach_mappings = function(prompt_bufnr, map) actions.select_default:replace(function() @@ -1195,103 +1328,59 @@ local function PreviewImg(opts) end, sort = config.options.sort, }) - else - print("File not found: " .. config.options.home .. "/" .. fname) - end + end) end -- --- BrowseImg: +-- FindFriends: -- ----------- -- --- preview media +-- Find notes also linking to the link under cursor -- -local function BrowseImg(opts) +local function FindFriends(opts) opts = opts or {} opts.insert_after_inserting = opts.insert_after_inserting or config.options.insert_after_inserting opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - find_files_sorted({ - prompt_title = "Preview image/media", - cwd = config.options.home, - find_command = config.options.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", "", picker_actions.yank_img_link(opts)) - map("i", "", picker_actions.paste_img_link(opts)) - map("n", "", picker_actions.yank_img_link(opts)) - map("n", "", picker_actions.paste_img_link(opts)) - map("i", "", picker_actions.paste_img_link(opts)) - map("n", "", picker_actions.paste_img_link(opts)) - return true - end, - sort = config.options.sort, - }) + local saved_reg = vim.fn.getreg('"0') + vim.cmd("normal yi]") + local title = vim.fn.getreg('"0') + vim.fn.setreg('"0', saved_reg) + + title = linkutils.remove_alias(title) + title = title:gsub("^(%[)(.+)(%])$", "%2") + + builtin.live_grep({ + prompt_title = "Notes referencing `" .. title .. "`", + cwd = config.options.home, + default_text = "\\[\\[" .. title .. "([#|].+)?\\]\\]", + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace(picker_actions.select_default) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + return true + end, + }) + end) end -- --- FindFriends: +-- YankLink: -- ----------- -- --- 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 config.options.insert_after_inserting - opts.close_after_yanking = opts.close_after_yanking - or config.options.close_after_yanking - - if not fileutils.global_dir_check() then - return - end - - vim.cmd("normal yi]") - local title = vim.fn.getreg('"0') - title = linkutils.remove_alias(title) - title = title:gsub("^(%[)(.+)(%])$", "%2") - - builtin.live_grep({ - prompt_title = "Notes referencing `" .. title .. "`", - cwd = config.options.home, - default_text = "\\[\\[" .. title .. "([#|].+)?\\]\\]", - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - return true - end, - }) -end - --- --- YankLink: --- ----------- --- --- Create and yank a [[link]] from the current note. +-- Create and yank a [[link]] from the current note. -- local function YankLink() local title = "[[" @@ -1301,6 +1390,31 @@ local function YankLink() print("yanked " .. title) end +local function rename_update_links(oldfile, newname) + if config.options.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 buffers before looking for links to replace + if #(vim.fn.getbufinfo({ bufmodified = 1 })) > 1 then + vim.ui.select({ "Yes (default)", "No" }, { + prompt = "Telekasten.nvim: " + .. "Save all modified buffers before updating links?", + }, function(answer) + if answer ~= "No" then + save_all_mod_buffers() + end + end) + end + + recursive_substitution(config.options.home, oldlink, newlink) + recursive_substitution(config.options.dailies, oldlink, newlink) + recursive_substitution(config.options.weeklies, oldlink, newlink) + end +end + -- -- RenameNote: -- ----------- @@ -1339,55 +1453,37 @@ local function RenameNote() -- Savas newfile, delete buffer of old one and remove old file if newname ~= "" and newname ~= oldfile.title then - if - not (fileutils.check_dir_and_ask(newpath, "Renamed file")) - then - return - end - - local oldTitle = oldfile.title:gsub(" ", "\\ ") - vim.cmd( - "saveas " - .. config.options.home - .. "/" - .. newname - .. config.options.extension - ) - vim.cmd("bdelete " .. oldTitle .. config.options.extension) - os.execute( - "rm " - .. config.options.home - .. "/" - .. oldTitle - .. config.options.extension - ) - end - - if config.options.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 buffers before looking for links to replace - if #(vim.fn.getbufinfo({ bufmodified = 1 })) > 1 then - vim.ui.select({ "Yes (default)", "No" }, { - prompt = "Telekasten.nvim: " - .. "Save all modified buffers before updating links?", - }, function(answer) - if answer ~= "No" then - save_all_mod_buffers() + fileutils.check_dir_and_ask( + newpath, + "Renamed file", + function(success) + if not success then + return end - end) - end - recursive_substitution(config.options.home, oldlink, newlink) - recursive_substitution(config.options.dailies, oldlink, newlink) - recursive_substitution( - config.options.weeklies, - oldlink, - newlink + local oldTitle = oldfile.title:gsub(" ", "\\ ") + vim.cmd( + "saveas " + .. config.options.home + .. "/" + .. newname + .. config.options.extension + ) + vim.cmd( + "bdelete " .. oldTitle .. config.options.extension + ) + os.execute( + "rm " + .. config.options.home + .. "/" + .. oldTitle + .. config.options.extension + ) + rename_update_links(oldfile, newname) + end ) + else + rename_update_links(oldfile, newname) end end ) @@ -1418,6 +1514,42 @@ local function GotoDate(opts) .. word .. config.options.extension local fexists = fileutils.file_exists(fname) + local function picker() + if opts.journal_auto_open then + if opts.calendar == true then + -- ensure that the calendar window is not improperly overwritten + vim.cmd("wincmd w") + end + vim.cmd("e " .. fname) + else + find_files_sorted({ + prompt_title = "Goto day", + cwd = config.options.dailies, + default_text = word, + find_command = config.options.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", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + }) + end + end + if (fexists ~= true) and ( @@ -1430,41 +1562,17 @@ local function GotoDate(opts) nil, fname, M.note_type_templates.daily, - opts.dates + opts.dates, + function() + opts.erase = true + opts.erase_file = fname + picker() + end ) - opts.erase = true - opts.erase_file = fname + return end - if opts.journal_auto_open then - vim.cmd("e " .. fname) - else - find_files_sorted({ - prompt_title = "Goto day", - cwd = config.options.dailies, - default_text = word, - find_command = config.options.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", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - }) - end + picker() end -- @@ -1476,15 +1584,17 @@ end local function GotoToday(opts) opts = opts or {} - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local today = os.date(dateutils.dateformats.date) - opts.date_table = os.date("*t") - opts.date = today - opts.dailies_create_nonexisting = true -- Always use template for GotoToday - GotoDate(opts) + local today = os.date(dateutils.dateformats.date) + opts.date_table = os.date("*t") + opts.date = today + opts.dailies_create_nonexisting = true -- Always use template for GotoToday + GotoDate(opts) + end) end -- @@ -1500,43 +1610,47 @@ local function FindNotes(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local cwd = config.options.home - local find_command = config.options.find_command - local sort = config.options.sort - local attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.create_new(opts)) - map("n", "", picker_actions.create_new(opts)) - return true - end + local cwd = config.options.home + local find_command = config.options.find_command + local sort = config.options.sort + local attach_mappings = function(_, map) + actions.select_default:replace(picker_actions.select_default) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + if config.options.enable_create_new then + map("i", "", picker_actions.create_new(opts)) + map("n", "", picker_actions.create_new(opts)) + end + return true + end - if opts.with_live_grep then - builtin.live_grep({ - prompt_title = "Find notes by live grep", - cwd = cwd, - find_command = find_command, - attach_mappings = attach_mappings, - sort = sort, - }) - else - find_files_sorted({ - prompt_title = "Find notes by name", - cwd = cwd, - find_command = find_command, - attach_mappings = attach_mappings, - sort = sort, - }) - end + if opts.with_live_grep then + builtin.live_grep({ + prompt_title = "Find notes by live grep", + cwd = cwd, + find_command = find_command, + attach_mappings = attach_mappings, + sort = sort, + }) + else + find_files_sorted({ + prompt_title = "Find notes by name", + cwd = cwd, + find_command = find_command, + attach_mappings = attach_mappings, + sort = sort, + }) + end + end) end -- @@ -1548,45 +1662,39 @@ end local function InsertImgLink(opts) opts = opts or {} - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - find_files_sorted({ - prompt_title = "Find image/media", - cwd = config.options.home, - find_command = config.options.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({ "![](" .. fn .. ")" }, "", true, true) - if opts.i then - vim.api.nvim_feedkeys("A", "m", false) - end - end) - map("i", "", picker_actions.yank_img_link(opts)) - map("i", "", picker_actions.paste_img_link(opts)) - map("n", "", picker_actions.yank_img_link(opts)) - map("n", "", picker_actions.paste_img_link(opts)) - map("i", "", picker_actions.paste_img_link(opts)) - map("n", "", picker_actions.paste_img_link(opts)) - return true - end, - sort = config.options.sort, - }) + find_files_sorted({ + prompt_title = "Find image/media", + cwd = config.options.home, + find_command = config.options.find_command, + filter_extensions = config.options.media_extensions, + 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({ "![](" .. fn .. ")" }, "", true, true) + if opts.i then + vim.api.nvim_feedkeys("A", "m", false) + end + end) + map("i", "", picker_actions.yank_img_link(opts)) + map("i", "", picker_actions.paste_img_link(opts)) + map("n", "", picker_actions.yank_img_link(opts)) + map("n", "", picker_actions.paste_img_link(opts)) + map("i", "", picker_actions.paste_img_link(opts)) + map("n", "", picker_actions.paste_img_link(opts)) + return true + end, + sort = config.options.sort, + }) + end) end -- @@ -1602,27 +1710,29 @@ local function SearchNotes(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - builtin.live_grep({ - prompt_title = "Search in notes", - cwd = config.options.home, - search_dirs = { config.options.home }, - default_text = opts.default_text or vim.fn.expand(""), - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - return true - end, - }) + builtin.live_grep({ + prompt_title = "Search in notes", + cwd = config.options.home, + search_dirs = { config.options.home }, + default_text = opts.default_text or vim.fn.expand(""), + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace(picker_actions.select_default) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + return true + end, + }) + end) end -- @@ -1638,31 +1748,37 @@ local function ShowBacklinks(opts) opts.close_after_yanking = opts.close_after_yanking or config.options.close_after_yanking - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local title = - Pinfo:new({ filepath = vim.fn.expand("%:p"), config.options }).title - -- or vim.api.nvim_buf_get_name(0) - builtin.live_grep({ - results_title = "Backlinks to " .. title, - prompt_title = "Search", - cwd = config.options.home, - search_dirs = { config.options.home }, - default_text = "\\[\\[" .. title .. "([#|].+)?\\]\\]", - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - return true - end, - }) + local title = + Pinfo:new({ filepath = vim.fn.expand("%:p"), config.options }).title + -- or vim.api.nvim_buf_get_name(0) + + local escaped_title = string.gsub(title, "%(", "\\(") + escaped_title = string.gsub(escaped_title, "%)", "\\)") + + builtin.live_grep({ + results_title = "Backlinks to " .. title, + prompt_title = "Search", + cwd = config.options.home, + search_dirs = { config.options.home }, + default_text = "\\[\\[" .. escaped_title .. "([#|].+)?\\]\\]", + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace(picker_actions.select_default) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + return true + end, + }) + end) end -- @@ -1716,11 +1832,13 @@ local function on_create_with_template(opts, title) uuid, fname, template, - pinfo.calendar_info + pinfo.calendar_info, + function() + -- open the new note + vim.cmd("e " .. fname) + picker_actions.post_open() + end ) - -- open the new note - vim.cmd("e " .. fname) - picker_actions.post_open() end) map("i", "", picker_actions.yank_link(opts)) map("i", "", picker_actions.paste_link(opts)) @@ -1734,12 +1852,21 @@ end local function CreateNoteSelectTemplate(opts) opts = opts or {} - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - fileutils.prompt_title(config.options.extension, nil, function(title) - on_create_with_template(opts, title) + -- get the current working directory + local current_dir = vim.fn.getcwd() + -- change the cwd to the configured home directory, so tab completion + -- works for the folders in that directory + vim.fn.chdir(config.options.home) + fileutils.prompt_title(config.options.extension, nil, function(title) + on_create_with_template(opts, title) + end) + -- change back to the original directory + vim.fn.chdir(current_dir) end) end @@ -1772,6 +1899,24 @@ local function on_create(opts, title) }) local fname = pinfo.filepath + local function picker() + find_files_sorted({ + prompt_title = "Created note...", + cwd = pinfo.root_dir, + default_text = generate_note_filename(uuid, title), + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace(picker_actions.select_default) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + }) + end if pinfo.fexists ~= true then -- TODO: pass in the calendar_info returned in pinfo create_note_from_template( @@ -1779,43 +1924,41 @@ local function on_create(opts, title) uuid, fname, pinfo.template, - pinfo.calendar_info + pinfo.calendar_info, + function() + opts.erase = true + opts.erase_file = fname + picker() + end ) - opts.erase = true - opts.erase_file = fname + return end - find_files_sorted({ - prompt_title = "Created note...", - cwd = pinfo.root_dir, - default_text = generate_note_filename(uuid, title), - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - }) + picker() end local function CreateNote(opts) opts = opts or {} - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - if config.options.template_handling == "always_ask" then - return CreateNoteSelectTemplate(opts) - end + if config.options.template_handling == "always_ask" then + return CreateNoteSelectTemplate(opts) + end - fileutils.prompt_title(config.options.extension, nil, function(title) - on_create(opts, title) + -- get the current working directory + local current_dir = vim.fn.getcwd() + -- change the cwd to the configured home directory, so tab completion + -- works for the folders in that directory + vim.fn.chdir(config.options.home) + fileutils.prompt_title(config.options.extension, nil, function(title) + on_create(opts, title) + end) + -- change back to the original directory + vim.fn.chdir(current_dir) end) end @@ -1838,426 +1981,444 @@ local function FollowLink(opts) or config.options.new_note_location local uuid_type = opts.uuid_type or config.options.uuid_type - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local search_mode = "files" - local title - local filename_part = "" + local search_mode = "files" + local title + local filename_part = "" - -- first: check if we're in a tag or a link - local kind, atcol, tag - local globArg = "" + -- first: check if we're in a tag or a link + local kind + local globArg = "" - if opts.follow_tag ~= nil then - kind = "tag" - tag = opts.follow_tag - if opts.templateDir ~= nil then - globArg = "--glob=!" .. "**/" .. opts.templateDir .. "/*.md" + if opts.follow_tag ~= nil then + kind = "tag" + title = opts.follow_tag + if opts.templateDir ~= nil then + globArg = "--glob=!" .. "**/" .. opts.templateDir .. "/*.md" + end + else + kind, _ = check_for_link_or_tag() end - 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, - config.options - ) - 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") - title = linkutils.remove_alias(title) + if kind == "tag" then + search_mode = "tag" + if title == nil then + local saved_reg = vim.fn.getreg('"0') + vim.cmd("normal yiw") + title = vim.fn.getreg('"0') + vim.fn.setreg('"0', saved_reg) + end else - -- we are in an external [link] - vim.cmd("normal yi)") - local url = vim.fn.getreg('"0') - return follow_url(url) - end + local saved_reg = vim.fn.getreg('"0') + if kind == "link" then + -- we are in a link + vim.cmd("normal yi]") + title = vim.fn.getreg('"0') + title = title:gsub("^(%[)(.+)(%])$", "%2") + title = title:gsub("%s*\n", " ") + title = linkutils.remove_alias(title) + else + -- we are in an external [link] + vim.cmd("normal yi)") + local url = vim.fn.getreg('"0') + vim.fn.setreg('"0', saved_reg) + return follow_url(url) + end + vim.fn.setreg('"0', saved_reg) - local parts = vim.split(title, "#") + 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 there is a # if #parts ~= 1 then - search_mode = "para" + 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 - 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 = "" + -- 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 - end - if search_mode == "files" then - -- check if subdir exists - local filepath = title:match("(.*/)") or "" - filepath = config.options.home .. "/" .. filepath - fileutils.check_dir_and_ask(filepath, "") + if search_mode == "files" then + -- check if subdir exists + local filepath = title:match("(.*/)") or "" + filepath = config.options.home .. "/" .. filepath + fileutils.check_dir_and_ask(filepath, "", function() + -- check if fname exists anywhere + local pinfo = Pinfo:new({ title = title }) + local function picker() + find_files_sorted({ + prompt_title = "Follow link to note...", + cwd = pinfo.root_dir, + default_text = title, + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace( + picker_actions.select_default + ) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + sort = config.options.sort, + }) + end - -- check if fname exists anywhere - local pinfo = Pinfo:new({ title = title }) - if - (pinfo.fexists ~= true) - and ( - (opts.follow_creates_nonexisting == true) - or config.options.follow_creates_nonexisting == true - ) - then - if opts.template_handling == "always_ask" then - return on_create_with_template(opts, title) - end + if + (pinfo.fexists ~= true) + and ( + (opts.follow_creates_nonexisting == true) + or config.options.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 = fileutils.new_uuid(uuid_type) - create_note_from_template( - title, - uuid, - pinfo.filepath, - pinfo.template, - pinfo.calendar_info - ) - opts.erase = true - opts.erase_file = pinfo.filepath - end - end + if #pinfo.filepath > 0 then + local uuid = fileutils.new_uuid(uuid_type) + create_note_from_template( + title, + uuid, + pinfo.filepath, + pinfo.template, + pinfo.calendar_info, + function() + opts.erase = true + opts.erase_file = pinfo.filepath + picker() + end + ) + return + end + end - find_files_sorted({ - prompt_title = "Follow link to note...", - cwd = pinfo.root_dir, - default_text = title, - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - sort = config.options.sort, - }) - end + picker() + end) + end - if search_mode ~= "files" then - local search_pattern = title - local cwd = config.options.home + if search_mode ~= "files" then + local search_pattern = title + local cwd = config.options.home - opts.cwd = cwd - local counts = nil - if opts.show_link_counts then - counts = linkutils.generate_backlink_map(config.options) - end + opts.cwd = cwd + local counts = nil + if opts.show_link_counts then + counts = linkutils.generate_backlink_map(config.options) + end - -- display with devicons - local function iconic_display(display_entry) - local display_opts = { - path_display = function(_, e) - return e:gsub(tkutils.escape(opts.cwd .. "/"), "") - end, - } + -- display with devicons + local function iconic_display(display_entry) + local display_opts = { + path_display = function(_, e) + return e:gsub(tkutils.escape(opts.cwd .. "/"), "") + end, + } - local hl_group - local display = - utils.transform_path(display_opts, display_entry.value) + 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) + 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 + if hl_group then + return display, { { { 1, 3 }, hl_group } } + else + return display + end 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 }, - }, - }) + -- for media_files + local popup_opts = {} + opts.get_preview_window = function() + return popup_opts.preview + end - 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 + -- 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 }, + }, + }) - 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) + 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 - end - local lookup_keys = { - value = 1, - ordinal = 1, - } + 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+):(.*)]]) + 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 + -- 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 + 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 - else - return function(t) - local _, _, filn, lnum, col, text = - string.find(t, [[([^:]+):(%d+):(%d+):(.*)]]) - return filn, lnum, col, text + 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 - end - end)() - local parse = function(t) - -- print("t: ", vim.inspect(t)) - local filn, lnum, col, text = find(t.value) + ok, col = pcall(tonumber, col) + if not ok then + col = nil + end - local ok - ok, lnum = pcall(tonumber, lnum) - if not ok then - lnum = nil - end + t.filn = filn + t.lnum = lnum + t.col = col + t.text = text - ok, col = pcall(tonumber, col) - if not ok then - col = nil + return { filn, lnum, col, text } 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 - local function entry_maker(_) - local mt_vimgrep_entry + opts = opts or {} - 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 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, + } - 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 + -- 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, - 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 + 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) + 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 val - end - return rawget(t, rawget(lookup_keys, k)) - 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 + -- + 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) + return function(line) + return setmetatable({ line }, mt_vimgrep_entry) + end end - end - opts.entry_maker = entry_maker(opts) + opts.entry_maker = entry_maker(opts) - local live_grepper = finders.new_job( - function(prompt) - if not prompt or prompt == "" then - return nil - end + 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 = { + local search_command = { "rg", "--vimgrep", "-e", - "\\^" .. prompt, + "^#+\\s" .. prompt, "--", } - end + if search_mode == "para" then + search_command = { + "rg", + "--vimgrep", + "-e", + "\\^" .. prompt, + "--", + } + end - if search_mode == "tag" then - search_command = { - "rg", - "--vimgrep", - globArg, - "-e", - prompt, - "--", - } - end + if search_mode == "tag" then + search_command = { + "rg", + "--vimgrep", + globArg, + "-e", + prompt, + "--", + } + end - if #filename_part > 0 then - table.insert(search_command, filename_part) - else - table.insert(search_command, cwd) - 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 - ) + 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(opts, { - 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", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - return true - end, - }) - picker:find() - end + -- builtin.live_grep({ + local picker = pickers.new(opts, { + 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", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + return true + end, + }) + picker:find() + end + end) end -- @@ -2275,57 +2436,74 @@ local function GotoThisWeek(opts) opts.journal_auto_open = opts.journal_auto_open or config.options.journal_auto_open - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local dinfo = dateutils.calculate_dates( - nil, - config.options.calendar_opts.calendar_monday - ) - local title = dinfo.isoweek - local fname = config.options.weeklies - .. "/" - .. title - .. config.options.extension - local fexists = fileutils.file_exists(fname) - if - (fexists ~= true) - and ( - (opts.weeklies_create_nonexisting == true) - or config.options.weeklies_create_nonexisting == true - ) - then - create_note_from_template( - title, + local dinfo = dateutils.calculate_dates( nil, - fname, - M.note_type_templates.weekly + config.options.calendar_opts.calendar_monday ) - opts.erase = true - opts.erase_file = fname - end + local title = dinfo.isoweek + local fname = config.options.weeklies + .. "/" + .. title + .. config.options.extension + local fexists = fileutils.file_exists(fname) + local function picker() + if opts.journal_auto_open then + if opts.calendar == true then + -- ensure that the calendar window is not improperly overwritten + vim.cmd("wincmd w") + end + vim.cmd("e " .. fname) + else + find_files_sorted({ + prompt_title = "Goto this week:", + cwd = config.options.weeklies, + default_text = title, + find_command = config.options.find_command, + attach_mappings = function(_, map) + actions.select_default:replace( + picker_actions.select_default + ) + map("i", "", picker_actions.yank_link(opts)) + map("i", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.yank_link(opts)) + map("n", "", picker_actions.paste_link(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + }) + end + end - if opts.journal_auto_open then - vim.cmd("e " .. fname) - else - find_files_sorted({ - prompt_title = "Goto this week:", - cwd = config.options.weeklies, - default_text = title, - find_command = config.options.find_command, - attach_mappings = function(_, map) - actions.select_default:replace(picker_actions.select_default) - map("i", "", picker_actions.yank_link(opts)) - map("i", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.yank_link(opts)) - map("n", "", picker_actions.paste_link(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - }) - end + if + (fexists ~= true) + and ( + (opts.weeklies_create_nonexisting == true) + or config.options.weeklies_create_nonexisting == true + ) + then + create_note_from_template( + title, + nil, + fname, + M.note_type_templates.weekly, + nil, + function() + opts.erase = true + opts.erase_file = fname + picker() + end + ) + return + end + + picker() + end) end -- @@ -2481,90 +2659,92 @@ local function FindAllTags(opts) opts.templateDir = templateDir opts.rg_pcre = config.options.rg_pcre - if not fileutils.global_dir_check() then - return - end + fileutils.global_dir_check(function(dir_check) + if not dir_check then + return + end - local tag_map = tagutils.do_find_all_tags(opts) - local taglist = {} + 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 + 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 - end - if config.options.show_tags_theme == "get_cursor" then - opts = themes.get_cursor({ - layout_config = { - height = math.min(math.floor(vim.o.lines * 0.8), #taglist), - }, - }) - elseif config.options.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 = config.options.home - opts.tag_notation = config.options.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, - templateDir = templateDir, - } - actions._close(prompt_bufnr, false) - vim.schedule(function() - FollowLink(follow_opts) + if config.options.show_tags_theme == "get_cursor" then + opts = themes.get_cursor({ + layout_config = { + height = math.min(math.floor(vim.o.lines * 0.8), #taglist), + }, + }) + elseif config.options.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 = config.options.home + opts.tag_notation = config.options.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 = false, + templateDir = templateDir, + } + actions._close(prompt_bufnr, false) + vim.schedule(function() + FollowLink(follow_opts) + end) end) - end) - map("i", "", picker_actions.yank_tag(opts)) - map("i", "", picker_actions.paste_tag(opts)) - map("n", "", picker_actions.yank_tag(opts)) - map("n", "", picker_actions.paste_tag(opts)) - map("n", "", picker_actions.close(opts)) - map("n", "", picker_actions.close(opts)) - return true - end, - }) - :find() + map("i", "", picker_actions.yank_tag(opts)) + map("i", "", picker_actions.paste_tag(opts)) + map("n", "", picker_actions.yank_tag(opts)) + map("n", "", picker_actions.paste_tag(opts)) + map("n", "", picker_actions.close(opts)) + map("n", "", picker_actions.close(opts)) + return true + end, + }) + :find() + end) end -- Setup(cfg) @@ -2666,17 +2846,18 @@ local function Setup(cfg) config.options.rg_pcre = true end config.options.media_previewer = config.options.media_previewer + config.options.media_extensions = config.options.media_extensions end local function _setup(cfg) if cfg.vaults ~= nil and cfg.default_vault ~= nil then M.vaults = cfg.vaults cfg.vaults = nil - Setup(cfg.vaults[cfg.default_vault]) + Setup(M.vaults[cfg.default_vault]) elseif cfg.vaults ~= nil and cfg.vaults["default"] ~= nil then M.vaults = cfg.vaults cfg.vaults = nil - Setup(cfg.vaults["default"]) + Setup(M.vaults["default"]) elseif cfg.home ~= nil then M.vaults = cfg.vaults or {} cfg.vaults = nil diff --git a/lua/telekasten/config.lua b/lua/telekasten/config.lua index 76c2bd2..a855230 100644 --- a/lua/telekasten/config.lua +++ b/lua/telekasten/config.lua @@ -75,7 +75,22 @@ local function get_defaults(home) new_note_location = "smart", rename_update_links = true, media_previewer = "telescope-media-files", + media_extensions = { + ".png", + ".jpg", + ".bmp", + ".gif", + ".pdf", + ".mp4", + ".webm", + ".webp", + }, + -- A customizable fallback handler for urls. follow_url_fallback = nil, + -- Enable creation new notes with Ctrl-n when finding notes + enable_create_new = true, + -- Specify a clipboard program to use + clipboard_program = "", -- xsel, xclip, wl-paste, osascript } return { options = opts } end diff --git a/lua/telekasten/utils/files.lua b/lua/telekasten/utils/files.lua index 191cbb8..cd437dc 100644 --- a/lua/telekasten/utils/files.lua +++ b/lua/telekasten/utils/files.lua @@ -75,7 +75,7 @@ function M.new_uuid(uuid_style) return uuid end -function M.check_dir_and_ask(dir, purpose) +function M.check_dir_and_ask(dir, purpose, callback) local ret = false if dir ~= nil and Path:new(dir):exists() == false then vim.ui.select({ "No (default)", "Yes" }, { @@ -97,30 +97,39 @@ function M.check_dir_and_ask(dir, purpose) -- unreachable: plenary.Path:mkdir() will error out tkutils.print_error("Could not create directory " .. dir) ret = false + callback(ret) end end end) else ret = true + if callback ~= nil then + callback(ret) + end end return ret end -function M.global_dir_check() +function M.global_dir_check(callback) local ret if config.options.home == nil then tkutils.print_error("Telekasten.nvim: home is not configured!") ret = false - else - ret = M.check_dir_and_ask(config.options.home, "home") + callback(ret) end - - ret = ret and M.check_dir_and_ask(config.options.dailies, "dailies") - ret = ret and M.check_dir_and_ask(config.options.weeklies, "weeklies") - ret = ret and M.check_dir_and_ask(config.options.templates, "templates") - ret = ret and M.check_dir_and_ask(config.options.image_subdir, "images") - - return ret + local check = M.check_dir_and_ask + -- nested callbacks to handle asynchronous vim.ui.select + -- looks a little confusing but execution is sequential from top to bottom + check(config.options.home, "home", function() + check(config.options.dailies, "dailies", function() + check(config.options.weeklies, "weeklies", function() + check(config.options.templates, "templates", function() + -- Note the `callback` in this last function call + check(config.options.image_subdir, "images", callback) + end) + end) + end) + end) end return M diff --git a/lua/telekasten/utils/taglinks.lua b/lua/telekasten/utils/taglinks.lua index 28ea5e6..54aed5c 100644 --- a/lua/telekasten/utils/taglinks.lua +++ b/lua/telekasten/utils/taglinks.lua @@ -52,6 +52,9 @@ M.is_tag_or_link_at = function(line, col, opts) if char == "#" then seen_hashtag = true end + if char == "@" then + seen_hashtag = true + end -- Tags should have a space before #, if not we are likely in a link if char == " " and seen_hashtag and opts.tag_notation == "#tag" then if not cannot_be_tag then diff --git a/lua/telekasten/utils/tags.lua b/lua/telekasten/utils/tags.lua index 6abcf44..e1c2708 100644 --- a/lua/telekasten/utils/tags.lua +++ b/lua/telekasten/utils/tags.lua @@ -1,14 +1,20 @@ local Job = require("plenary.job") local M = {} -local hashtag_re = "(^|\\s|'|\")#[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\\-_]*" + +local hashtag_re = + "(^|\\s|'|\")#[a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*" -- PCRE hashtag allows to remove the hex color codes from hastags -local hashtag_re_pcre = - "(^|\\s|'|\")((?!(#[a-fA-F0-9]{3})(\\W|$)|(#[a-fA-F0-9]{6})(\\W|$))#[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\\-_]*)" -local colon_re = "(^|\\s):[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\\-_]*:" +local hashtag_re_pcre = "(^|\\s|'|\")((?!(#[a-fA-F0-9]{3})(\\W|$)|(#[a-fA-F0-9]{6})(\\W|$))" + .. "#[a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*)" +local atsign_re = + "(^|\\s|'|\")@[a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*" +local atsign_re_pcre = "(^|\\s|'|\")((?!(@[a-fA-F0-9]{3})(\\W|$)|(@[a-fA-F0-9]{6})(\\W|$))" + .. "@[a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*)" +local colon_re = + "(^|\\s):[a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*:" local yaml_re = - "(^|\\s)tags:\\s*\\[\\s*([a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\\-_]*(,\\s*)*)*\\s*]" - + "(^|\\s)tags:\\s*\\[\\s*([a-zA-ZÀ-ÿ\\p{Script=Han}]+[a-zA-ZÀ-ÿ0-9/\\-_\\p{Script=Han}]*(,\\s*)*)*\\s*]" local function command_find_all_tags(opts) opts = opts or {} opts.cwd = opts.cwd or "." @@ -23,11 +29,11 @@ local function command_find_all_tags(opts) local re = hashtag_re - if opts.tag_notation == ":tag:" then + if opts.tag_notation == "@tag" then + re = atsign_re + elseif opts.tag_notation == ":tag:" then re = colon_re - end - - if opts.tag_notation == "yaml-bare" then + elseif opts.tag_notation == "yaml-bare" then re = yaml_re end @@ -41,18 +47,32 @@ local function command_find_all_tags(opts) } -- PCRE engine allows to remove hex color codes from #hastags - if opts.rg_pcre and (re == hashtag_re) then - re = hashtag_re_pcre - - rg_args = { - "--vimgrep", - "--pcre2", - globArg, - "-o", - re, - "--", - opts.cwd, - } + if opts.rg_pcre then + if re == hashtag_re then + re = hashtag_re_pcre + + rg_args = { + "--vimgrep", + "--pcre2", + globArg, + "-o", + re, + "--", + opts.cwd, + } + elseif re == atsign_re then + re = atsign_re_pcre + + rg_args = { + "--vimgrep", + "--pcre2", + globArg, + "-o", + re, + "--", + opts.cwd, + } + end end return "rg", rg_args diff --git a/syntax/telekasten.vim b/syntax/telekasten.vim index 600e486..52f7838 100644 --- a/syntax/telekasten.vim +++ b/syntax/telekasten.vim @@ -8,8 +8,10 @@ unlet b:current_syntax syn region Comment matchgroup=Comment start="" contains=tkTag keepend -syntax region tkLink matchgroup=tkBrackets start=/\[\[/ end=/\]\]/ display oneline -syntax region tkHighlight matchgroup=tkBrackets start=/==/ end=/==/ display oneline contains=tkHighlightedAliasedLink +syntax region tkLink matchgroup=tkBrackets start=/\[\[/ end=/\]\]/ keepend display contains=tkAliasedLink +syntax match tkAliasedLink "[^\[\]]\+|" contained conceal + +syntax region tkHighlight matchgroup=tkBrackets start=/==/ end=/==/ display contains=tkAliasedLink syntax match tkTag "\v#[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\-_]*" syntax match tkTag "\v:[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\-_]*:" @@ -17,10 +19,6 @@ syntax match tkTag "\v:[a-zA-ZÀ-ÿ]+[a-zA-ZÀ-ÿ0-9/\-_]*:" syntax match tkTagSep "\v\s*,\s*" contained syntax region tkTag matchgroup=tkBrackets start=/^tags\s*:\s*\[\s*/ end=/\s*\]\s*$/ contains=tkTagSep display oneline -syntax region tkAliasedLink start="\[\[[^\]]\+|" end="\]\]" keepend oneline contains=tkLinkAlias,tkLinkBody -syntax region tkHighlightedAliasedLink start="\[\[[^\]]\+|" end="\]\]" keepend oneline contained contains=tkLinkAlias,tkLinkBody -syntax region tkLinkAlias start="|"ms=s+1 end=".+\]\]"me=e-2 keepend contained -syntax region tkLinkBody start="\[\["ms=s+2 end="|" keepend contained conceal let b:current_syntax = 'telekasten' @@ -38,4 +36,4 @@ let b:current_syntax = 'telekasten' " " " Tags " hi tkTagSep ctermfg=gray -" hi tkTag ctermfg=magenta +" hi tkTag ctermfg=magenta \ No newline at end of file