From 9e97e8ac20ee30b02f597ae2d1f1f3051a6f31b9 Mon Sep 17 00:00:00 2001 From: Rene Schallner Date: Sat, 11 Dec 2021 06:47:37 +0100 Subject: [PATCH] show_tags --- BACKLOG.md | 3 +- README.md | 21 ++++-- doc/telekasten.txt | 9 +++ lua/taglinks/tagutils.lua | 123 ++++++++++++++++++++++++++++++++ lua/telekasten.lua | 146 ++++++++++++++++++++++++++++++++++---- 5 files changed, 282 insertions(+), 20 deletions(-) create mode 100644 lua/taglinks/tagutils.lua diff --git a/BACKLOG.md b/BACKLOG.md index 20aa381..2c7786c 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -2,7 +2,6 @@ - [ ] maybe a virtual line in the 1st line that shows number of backlinks and maybe other interesting stuff - or put it as an extmark at the end of the first line, meh. -- [ ] better support for #tags [see also this comment](https://github.com/renerocksai/telekasten.nvim/discussions/23#discussioncomment-1754511) - [ ] some cool buffer showing backlinks (and stuff?) [see also this comment](https://github.com/renerocksai/telekasten.nvim/discussions/23#discussioncomment-1754511) - maybe another one where we dot-render a graph of linked notes and display it via vimg from telescope_media_files or sth similar @@ -14,6 +13,8 @@ - [ ] yt video ## Dones +- [x] better support for #tags [see also this comment](https://github.com/renerocksai/telekasten.nvim/discussions/23#discussioncomment-1754511) + - at least we have a tag picker now - [x] follow external URLs - [x] telekasten filetype - [x] Telekasten command with completion, command palette diff --git a/README.md b/README.md index 55d3eb9..e488fe7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ mixing it with a journal, based on [telescope.nvim](https://github.com/nvim-tele #### Highlights: -- Find notes by name, daily and weekly notes by date +- Find notes by name, #tag, and daily, weekly notes by date - search within all notes - place and follow links to your notes or create new ones, with templates - current daily and weekly notes are (optionally) created if not present when searching for dailies or weeklies @@ -247,6 +247,10 @@ require('telekasten').setup({ -- 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", + }) END ``` @@ -285,6 +289,10 @@ END | `command_palette_theme` | theme (layout) of the command palette| ivy | | | - `ivy` (default): bottom panel overlay | | | | - `dropdown`: floating popup window || +| `show_tags_theme` | theme (layout) for the tag list| ivy | +| | - `ivy` (default): bottom panel overlay | | +| | - `dropdown`: floating popup window || +| | - `get_cursor`: floating popup window at cursor position || The calendar support has its own options, contained in `calendar_opts`: @@ -372,6 +380,7 @@ the list for a more detailed description: - `panel` : brings up the [command palette](#21-telekasten-command-palette) - `find_notes` : Find notes by title (filename) +- `show_tags` : Search through all tags - `find_daily_notes` : Find daily notes by title (date) - `search_notes` : Search (grep) in all notes - `insert_link` : Insert a link to a note @@ -484,6 +493,7 @@ The plugin defines the following functions: - **note**: this requires the `telescope-media-files.nvim` plugin to be installed. - `setup(opts)`: used for configuring paths, file extension, etc. - `panel()` : brings up the command palette +- `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 To use one of the functions above, just run them with the `:lua ...` command. @@ -719,15 +729,18 @@ nnoremap zF :lua require('telekasten').find_friends() nnoremap zI :lua require('telekasten').insert_img_link({ i=true }) nnoremap zp :lua require('telekasten').preview_img() nnoremap zm :lua require('telekasten').browse_media() +nnoremap za :lua require('telekasten').show_tags() +nnoremap # :lua require('telekasten').show_tags() " on hesitation, bring up the panel nnoremap z :lua require('telekasten').panel() " we could define [[ in **insert mode** to call insert link -" inoremap [[ :lua require('telekasten').insert_link() +" inoremap [[ :lua require('telekasten').insert_link() " alternatively: leader [ -inoremap [ :lua require('telekasten').insert_link({ i=true }) -inoremap zt :lua require('telekasten').toggle_todo({ i=true }) +inoremap [ :lua require('telekasten').insert_link({ i=true }) +inoremap zt :lua require('telekasten').toggle_todo({ i=true }) +inoremap # lua require('telekasten').show_tags({i = true}) " ----- the following are for syntax-coloring [[links]] and ==highlighted text== " ----- (see the section about coloring in README.md) diff --git a/doc/telekasten.txt b/doc/telekasten.txt index cd8c325..65b145b 100644 --- a/doc/telekasten.txt +++ b/doc/telekasten.txt @@ -113,6 +113,13 @@ telekasten.setup({opts}) -- 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", } < @@ -765,12 +772,14 @@ However, here are some suggestions: nnoremap zI :lua require('telekasten').insert_img_link({ i=true }) nnoremap zp :lua require('telekasten').preview_img() nnoremap zm :lua require('telekasten').browse_media() + nnoremap # :lua require('telekasten').show_tags() " we could define [[ in **insert mode** to call insert link " inoremap [[ :lua require('telekasten').insert_link() " alternatively: leader [ inoremap [ :lua require('telekasten').insert_link({ i=true }) inoremap zt :lua require('telekasten').toggle_todo({ i=true }) + inoremap # lua require('telekasten').show_tags({i = true}) " the following are for syntax-coloring [[links]] and ==highlighted text== " (see the section about coloring in README.md) diff --git a/lua/taglinks/tagutils.lua b/lua/taglinks/tagutils.lua new file mode 100644 index 0000000..8e006a5 --- /dev/null +++ b/lua/taglinks/tagutils.lua @@ -0,0 +1,123 @@ +local Job = require("plenary.job") + +local M = {} +local hashtag_re = "(^|\\s)#[a-zA-Z]+[a-zA-Z0-9/\\-_]*" +local colon_re = "(^|\\s):[a-zA-Z]+[a-zA-Z0-9/\\-_]*:" +local yaml_re = "(^|\\s)tags:\\s*\\[([a-zA-Z]+[a-zA-Z0-9/\\-_]*(,\\s)*)*]" + +local function command_find_all_tags(opts) + opts = opts or {} + opts.cwd = opts.cwd or "." + local re = hashtag_re + + if opts.tag_notation == ":tag:" then + re = colon_re + end + + if opts.tag_notation == "yaml-bare" then + re = yaml_re + end + + return "rg", { "--vimgrep", "-o", re, "--", opts.cwd } +end + +local function trim(s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + +local function insert_tag(tbl, tag, entry) + entry.t = tag + if tbl[tag] == nil then + tbl[tag] = { entry } + else + tbl[tag][#tbl[tag] + 1] = entry + end +end + +local function split(line, sep, n) + local startpos = 0 + local endpos + local ret = {} + for _ = 1, n - 1 do + endpos = line:find(sep, startpos + 1) + ret[#ret + 1] = line:sub(startpos + 1, endpos - 1) + startpos = endpos + end + -- now the remainder + ret[n] = line:sub(startpos + 1) + return ret +end + +local function yaml_to_tags(line, entry, ret) + local _, startpos = line:find("tags%s*:%s*%[") + local global_end = line:find("%]") + + line = line:sub(startpos + 1, global_end) + + local i = 1 + local j + local prev_i + local tag + while true do + i, j = line:find("%s*.*%s*,", i) + if i == nil then + tag = line:sub(prev_i) + tag = tag:gsub("%s*(.*)%s*", "%1") + else + tag = line:sub(i, j) + tag = tag:gsub("%s*(.*)%s*,", "%1") + end + + local new_entry = {} + new_entry.t = tag + new_entry.l = entry.l + new_entry.fn = entry.fn + new_entry.c = startpos + (i or prev_i) + insert_tag(ret, tag, new_entry) + if i == nil then + break + end + i = j + 1 + prev_i = i + end +end + +local function parse_entry(opts, line, ret) + local s = split(line, ":", 4) + local fn, l, c, t = s[1], s[2], s[3], s[4] + + t = trim(t) + local entry = { fn = fn, l = l, c = c } + + if opts.tag_notation == "yaml-bare" then + yaml_to_tags(t, entry, ret) + elseif opts.tag_notation == ":tag:" then + insert_tag(ret, t, entry) + else + insert_tag(ret, t, entry) + end +end + +M.do_find_all_tags = function(opts) + local cmd, args = command_find_all_tags(opts) + -- print(cmd .. " " .. vim.inspect(args)) + local ret = {} + local _ = Job + :new({ + command = cmd, + args = args, + enable_recording = true, + on_exit = function(j, return_val) + for _, line in pairs(j:result()) do + parse_entry(opts, line, ret) + end + end, + on_stderr = function(err, data, _) + print("error: " .. tostring(err) .. "data: " .. data) + end, + }) + :sync() + -- print("final results: " .. vim.inspect(ret)) + return ret +end +return M diff --git a/lua/telekasten.lua b/lua/telekasten.lua index 4267998..258d0db 100644 --- a/lua/telekasten.lua +++ b/lua/telekasten.lua @@ -13,6 +13,7 @@ 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") -- declare locals for the nvim api stuff to avoid more lsp warnings local vim = vim @@ -74,6 +75,10 @@ M.Cfg = { -- 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", } local function file_exists(fname) @@ -468,6 +473,29 @@ function picker_actions.close(opts) end end +function picker_actions.paste_tag(opts) + return function(prompt_bufnr) + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + vim.api.nvim_put({ selection.value.tag }, "", true, true) + if opts.insert_after_inserting or opts.i then + vim.api.nvim_feedkeys("A", "m", false) + end + end +end + +function picker_actions.yank_tag(opts) + return function(prompt_bufnr) + opts = opts or {} + if opts.close_after_yanking then + actions.close(prompt_bufnr) + end + local selection = action_state.get_selected_entry() + vim.fn.setreg('"', selection.value.tag) + print("yanked " .. selection.value.tag) + end +end + function picker_actions.paste_link(opts) return function(prompt_bufnr) actions.close(prompt_bufnr) @@ -475,7 +503,7 @@ function picker_actions.paste_link(opts) local fn = path_to_linkname(selection.value) local title = "[[" .. fn .. "]]" vim.api.nvim_put({ title }, "", true, true) - if opts.insert_after_inserting then + if opts.insert_after_inserting or opts.i then vim.api.nvim_feedkeys("A", "m", false) end end @@ -503,7 +531,7 @@ function picker_actions.paste_img_link(opts) fn = fn:gsub(M.Cfg.home .. "/", "") local imglink = "![](" .. fn .. ")" vim.api.nvim_put({ imglink }, "", true, true) - if opts.insert_after_inserting then + if opts.insert_after_inserting or opts.i then vim.api.nvim_feedkeys("A", "m", false) end end @@ -713,13 +741,11 @@ local function follow_url(url) if vim.fn.has("mac") == 1 then command = format_command("open") vim.cmd(command) + elseif vim.fn.has("unix") then + command = format_command("xdg-open") + vim.cmd(command) else - if vim.fn.has("unix") then - command = format_command("xdg-open") - vim.cmd(command) - else - print("Cannot open URLs on your operating system") - end + print("Cannot open URLs on your operating system") end end @@ -742,14 +768,23 @@ local function FollowLink(opts) local fexists -- first: check if we're in a tag or a link - local kind, atcol = check_for_link_or_tag() + local kind, atcol, tag + + if opts.follow_tag ~= nil then + kind = "tag" + tag = opts.follow_tag + else + kind, atcol = check_for_link_or_tag() + end if kind == "tag" then - local tag = taglinks.get_tag_at( - vim.api.nvim_get_current_line(), - atcol, - M.Cfg - ) + if atcol ~= nil then + tag = taglinks.get_tag_at( + vim.api.nvim_get_current_line(), + atcol, + M.Cfg + ) + end search_mode = "tag" title = tag else @@ -762,7 +797,6 @@ local function FollowLink(opts) -- we are in an external [link] vim.cmd("normal yi)") local url = vim.fn.getreg('"0') - print(url) return follow_url(url) end @@ -1528,6 +1562,86 @@ local function ToggleTodo(opts) end end +local function FindAllTags(opts) + opts = opts or {} + local i = opts.i + opts.cwd = M.Cfg.home + opts.tag_notation = M.Cfg.tag_notation + + local tag_map = tagutils.do_find_all_tags(opts) + local taglist = {} + + local max_tag_len = 0 + for k, v in pairs(tag_map) do + taglist[#taglist + 1] = { tag = k, details = v } + if #k > max_tag_len then + max_tag_len = #k + end + end + + if M.Cfg.show_tags_theme == "get_cursor" then + opts = themes.get_cursor({ + layout_config = { + height = math.min(math.floor(vim.o.lines * 0.8), #taglist), + }, + }) + elseif M.Cfg.show_tags_theme == "ivy" then + opts = themes.get_ivy({ + layout_config = { + prompt_position = "top", + height = math.min(math.floor(vim.o.lines * 0.8), #taglist), + }, + }) + else + opts = themes.get_dropdown({ + layout_config = { + prompt_position = "top", + height = math.min(math.floor(vim.o.lines * 0.8), #taglist), + }, + }) + end + -- re-apply + opts.cwd = M.Cfg.home + opts.tag_notation = M.Cfg.tag_notation + opts.i = i + pickers.new(opts, { + prompt_title = "Tags", + finder = finders.new_table({ + results = taglist, + entry_maker = function(entry) + return { + value = entry, + -- display = entry.tag .. ' \t (' .. #entry.details .. ' matches)', + display = string.format( + "%" .. max_tag_len .. "s ... (%3d matches)", + entry.tag, + #entry.details + ), + ordinal = entry.tag, + } + end, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + actions._close(prompt_bufnr, true) + + -- TODO actions for insert tag, default action: search for tag + local selection = action_state.get_selected_entry().value.tag + local follow_opts = { follow_tag = selection } + FollowLink(follow_opts) + 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() +end + -- Setup(cfg) -- -- Overrides config with elements from cfg. See top of file for defaults. @@ -1635,6 +1749,7 @@ M.insert_img_link = InsertImgLink M.preview_img = PreviewImg M.browse_media = BrowseImg M.taglinks = taglinks +M.show_tags = FindAllTags -- Telekasten command, completion local TelekastenCmd = { @@ -1672,6 +1787,7 @@ local TelekastenCmd = { { "preview image under cursor", "preview_img", M.preview_img }, { "browse media", "browse_media", M.browse_media }, { "panel", "panel", M.panel }, + { "show tags", "show_tags", M.show_tags }, } end, }