From 441312776aa2859dea04a008f18066121f771d30 Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Thu, 17 Oct 2024 00:20:56 -0400 Subject: [PATCH 1/7] Add the ObsidianTags command --- CHANGELOG.md | 1 + README.md | 2 + doc/obsidian.txt | 88 ++++++++++++++++--------------- lua/obsidian/commands/aliases.lua | 62 ++++++++++++++++++++++ lua/obsidian/commands/init.lua | 3 ++ 5 files changed, 113 insertions(+), 43 deletions(-) create mode 100644 lua/obsidian/commands/aliases.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index b585ed0e2..fd984b136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `opts.follow_img_func` option for customizing how to handle image paths. - Added better handling for undefined template fields, which will now be prompted for. +- Added `:ObsidianAliases` command. ### Changed diff --git a/README.md b/README.md index d5d382b62..25661b9b0 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ _Keep in mind this plugin is not meant to replace Obsidian, but to complement it - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. +- `:ObsidianAliases` for getting a picker list of aliases of all notes in the vault. + - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to yesterday's note. Unlike `:ObsidianYesterday` and `:ObsidianTomorrow` this command does not differentiate between weekdays and weekends. - `:ObsidianYesterday` to open/create the daily note for the previous working day. diff --git a/doc/obsidian.txt b/doc/obsidian.txt index fc0539948..ddb5a8720 100644 --- a/doc/obsidian.txt +++ b/doc/obsidian.txt @@ -76,6 +76,8 @@ COMMANDS *obsidian-commands* buffer. - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. +- `:ObsidianAliases` for getting a picker list of aliases of all notes in the + vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to yesterday’s note. Unlike `:ObsidianYesterday` and `:ObsidianTomorrow` this @@ -184,7 +186,7 @@ USING LAZY.NVIM ~ dependencies = { -- Required. "nvim-lua/plenary.nvim", - + -- see below for full list of optional dependencies 👇 }, opts = { @@ -198,7 +200,7 @@ USING LAZY.NVIM ~ path = "~/vaults/work", }, }, - + -- see below for full list of options 👇 }, } @@ -214,7 +216,7 @@ USING PACKER.NVIM ~ requires = { -- Required. "nvim-lua/plenary.nvim", - + -- see below for full list of optional dependencies 👇 }, config = function() @@ -229,7 +231,7 @@ USING PACKER.NVIM ~ path = "~/vaults/work", }, }, - + -- see below for full list of options 👇 }) end, @@ -295,18 +297,18 @@ carefully and customize it to your needs: }, }, }, - + -- Alternatively - and for backwards compatibility - you can set 'dir' to a single path instead of -- 'workspaces'. For example: -- dir = "~/vaults/work", - + -- Optional, if you keep notes in a specific subdirectory of your vault. notes_subdir = "notes", - + -- Optional, set the log level for obsidian.nvim. This is an integer corresponding to one of the log -- levels defined by "vim.log.levels.*". log_level = vim.log.levels.INFO, - + daily_notes = { -- Optional, if you keep daily notes in a separate directory. folder = "notes/dailies", @@ -319,7 +321,7 @@ carefully and customize it to your needs: -- Optional, if you want to automatically insert a template from your template directory like 'daily.md' template = nil }, - + -- Optional, completion of wiki links, local markdown links, and tags using nvim-cmp. completion = { -- Set to false to disable completion. @@ -327,7 +329,7 @@ carefully and customize it to your needs: -- Trigger completion at 2 chars. min_chars = 2, }, - + -- Optional, configure key mappings. These are the defaults. If you don't want to set any keymappings this -- way then set 'mappings = {}'. mappings = { @@ -353,12 +355,12 @@ carefully and customize it to your needs: opts = { buffer = true, expr = true }, } }, - + -- Where to put new notes. Valid options are -- * "current_dir" - put new notes in same directory as the current buffer. -- * "notes_subdir" - put new notes in the default notes subdirectory. new_notes_location = "notes_subdir", - + -- Optional, customize how note IDs are generated given an optional title. ---@param title string|? ---@return string @@ -378,7 +380,7 @@ carefully and customize it to your needs: end return tostring(os.time()) .. "-" .. suffix end, - + -- Optional, customize how note file names are generated given the ID, target directory, and title. ---@param spec { id: string, dir: obsidian.Path, title: string|? } ---@return string|obsidian.Path The full path to the new note. @@ -387,7 +389,7 @@ carefully and customize it to your needs: local path = spec.dir / tostring(spec.id) return path:with_suffix(".md") end, - + -- Optional, customize how wiki links are formatted. You can set this to one of: -- * "use_alias_only", e.g. '[[Foo Bar]]' -- * "prepend_note_id", e.g. '[[foo-bar|Foo Bar]]' @@ -397,19 +399,19 @@ carefully and customize it to your needs: wiki_link_func = function(opts) return require("obsidian.util").wiki_link_id_prefix(opts) end, - + -- Optional, customize how markdown links are formatted. markdown_link_func = function(opts) return require("obsidian.util").markdown_link(opts) end, - + -- Either 'wiki' or 'markdown'. preferred_link_style = "wiki", - + -- Optional, boolean or a function that takes a filename and returns a boolean. -- `true` indicates that you don't want obsidian.nvim to manage frontmatter. disable_frontmatter = false, - + -- Optional, alternatively you can customize the frontmatter data. ---@return table note_frontmatter_func = function(note) @@ -417,9 +419,9 @@ carefully and customize it to your needs: if note.title then note:add_alias(note.title) end - + local out = { id = note.id, aliases = note.aliases, tags = note.tags } - + -- `note.metadata` contains any manually added fields in the frontmatter. -- So here we just make sure those fields are kept in the frontmatter. if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then @@ -427,10 +429,10 @@ carefully and customize it to your needs: out[k] = v end end - + return out end, - + -- Optional, for templates (see below). templates = { folder = "templates", @@ -439,7 +441,7 @@ carefully and customize it to your needs: -- A map for custom variables, the key should be the variable and the value a function substitutions = {}, }, - + -- Optional, by default when you use `:ObsidianFollowLink` on a link to an external -- URL it will be ignored but you can customize this behavior here. ---@param url string @@ -450,7 +452,7 @@ carefully and customize it to your needs: -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows -- vim.ui.open(url) -- need Neovim 0.10.0+ end, - + -- Optional, by default when you use `:ObsidianFollowLink` on a link to an image -- file it will be ignored but you can customize this behavior here. ---@param img string @@ -459,14 +461,14 @@ carefully and customize it to your needs: -- vim.fn.jobstart({"xdg-open", url}) -- linux -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows end, - + -- Optional, set to true if you use the Obsidian Advanced URI plugin. -- https://github.com/Vinzent03/obsidian-advanced-uri use_advanced_uri = false, - + -- Optional, set to true to force ':ObsidianOpen' to bring the app to the foreground. open_app_foreground = false, - + picker = { -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', or 'mini.pick'. name = "telescope.nvim", @@ -485,49 +487,49 @@ carefully and customize it to your needs: insert_tag = "", }, }, - + -- Optional, sort search results by "path", "modified", "accessed", or "created". -- The recommend value is "modified" and `true` for `sort_reversed`, which means, for example, -- that `:ObsidianQuickSwitch` will show the notes sorted by latest modified time sort_by = "modified", sort_reversed = true, - + -- Set the maximum number of lines to read from notes on disk when performing certain searches. search_max_lines = 1000, - + -- Optional, determines how certain commands open notes. The valid options are: -- 1. "current" (the default) - to always open in the current window -- 2. "vsplit" - to open in a vertical split if there's not already a vertical split -- 3. "hsplit" - to open in a horizontal split if there's not already a horizontal split open_notes_in = "current", - + -- Optional, define your own callbacks to further customize behavior. callbacks = { -- Runs at the end of `require("obsidian").setup()`. ---@param client obsidian.Client post_setup = function(client) end, - + -- Runs anytime you enter the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note enter_note = function(client, note) end, - + -- Runs anytime you leave the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note leave_note = function(client, note) end, - + -- Runs right before writing the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note pre_write_note = function(client, note) end, - + -- Runs anytime the workspace is set/changed. ---@param client obsidian.Client ---@param workspace obsidian.Workspace post_set_workspace = function(client, workspace) end, }, - + -- Optional, configure additional syntax highlighting / extmarks. -- This requires you have `conceallevel` set to 1 or 2. See `:help conceallevel` for more details. ui = { @@ -545,7 +547,7 @@ carefully and customize it to your needs: -- Replace the above with this if you don't have a patched font: -- [" "] = { char = "☐", hl_group = "ObsidianTodo" }, -- ["x"] = { char = "✔", hl_group = "ObsidianDone" }, - + -- You can also add more custom ones... }, -- Use bullet marks for non-checkbox lists. @@ -572,21 +574,21 @@ carefully and customize it to your needs: ObsidianHighlightText = { bg = "#75662e" }, }, }, - + -- Specify how to handle attachments. attachments = { -- The default folder to place images in via `:ObsidianPasteImg`. -- If this is a relative path it will be interpreted as relative to the vault root. -- You can always override this per image by passing a full path to the command instead of just a filename. img_folder = "assets/imgs", -- This is the default - + -- Optional, customize the default name or prefix when pasting images via `:ObsidianPasteImg`. ---@return string img_name_func = function() -- Prefix image names with timestamp. return string.format("%s-", os.time()) end, - + -- A function that determines the text to insert in the note when pasting an image. -- It takes two arguments, the `obsidian.Client` and an `obsidian.Path` to the image file. -- This is the default implementation. @@ -782,7 +784,7 @@ are supported out-of-the-box. For example, with the following configuration >lua { -- other fields ... - + templates = { folder = "my-templates-folder", date_format = "%Y-%m-%d-%a", @@ -795,7 +797,7 @@ and the file `~/my-vault/my-templates-folder/note template.md`: >markdown # {{title}} - + Date created: {{date}} < @@ -804,7 +806,7 @@ will insert >markdown # Configuring Neovim - + Date created: 2023-03-01-Wed < diff --git a/lua/obsidian/commands/aliases.lua b/lua/obsidian/commands/aliases.lua new file mode 100644 index 000000000..0d2d0bd8c --- /dev/null +++ b/lua/obsidian/commands/aliases.lua @@ -0,0 +1,62 @@ +local log = require "obsidian.log" +local util = require "obsidian.util" + +---@param client obsidian.Client +---@param callback fun(alias_to_notes: table) +local find_alias = function(client, callback) + client:find_notes_async("", function(notes) + local alias_to_notes = {} + for _, note in ipairs(notes) do + for _, alias in ipairs(note.aliases) do + alias_to_notes[alias] = alias_to_notes[alias] or {} + table.insert(alias_to_notes[alias], note) + end + end + callback(alias_to_notes) + end) +end + +---@param note obsidian.Note +--- +---@return obsidian.PickerEntry +local function convert_note_to_picker_entry(note) + return { + value = note, + display = note:display_name(), + ordinal = note:display_name(), + filename = tostring(note.path), + } +end + +---@param client obsidian.Client +return function(client) + local picker = client:picker() + if not picker then + log.err "No picker configured" + return + end + + find_alias(client, function(alias_to_notes) + vim.schedule(function() + picker:pick(vim.tbl_keys(alias_to_notes), { + prompt_title = "Aliases", + callback = function(alias) + local notes = alias_to_notes[alias] + if #notes == 1 then + util.open_buffer(notes[1].path) + else + local entries = vim.tbl_map(convert_note_to_picker_entry, notes) + vim.schedule(function() + picker:pick(entries, { + prompt_title = alias, + callback = function(note) + util.open_buffer(note.path) + end, + }) + end) + end + end, + }) + end) + end) +end diff --git a/lua/obsidian/commands/init.lua b/lua/obsidian/commands/init.lua index f31eaf8ea..7e5c4e0b2 100644 --- a/lua/obsidian/commands/init.lua +++ b/lua/obsidian/commands/init.lua @@ -13,6 +13,7 @@ local command_lookups = { ObsidianBacklinks = "obsidian.commands.backlinks", ObsidianSearch = "obsidian.commands.search", ObsidianTags = "obsidian.commands.tags", + ObsidianAliases = "obsidian.commands.aliases", ObsidianTemplate = "obsidian.commands.template", ObsidianNewFromTemplate = "obsidian.commands.new_from_template", ObsidianQuickSwitch = "obsidian.commands.quick_switch", @@ -149,6 +150,8 @@ M.register("ObsidianBacklinks", { opts = { nargs = 0, desc = "Collect backlinks" M.register("ObsidianTags", { opts = { nargs = "*", range = true, desc = "Find tags" } }) +M.register("ObsidianAliases", { opts = { nargs = "*", range = true, desc = "Find aliases" } }) + M.register("ObsidianSearch", { opts = { nargs = "?", desc = "Search vault" } }) M.register("ObsidianTemplate", { opts = { nargs = "?", desc = "Insert a template" } }) From fcbf8b73909d70ea9b02bcdd3578d595fb292f1d Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Thu, 17 Oct 2024 00:25:54 -0400 Subject: [PATCH 2/7] Revert format changes --- doc/obsidian.txt | 88 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/doc/obsidian.txt b/doc/obsidian.txt index ddb5a8720..ffbefdc2e 100644 --- a/doc/obsidian.txt +++ b/doc/obsidian.txt @@ -76,7 +76,7 @@ COMMANDS *obsidian-commands* buffer. - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. -- `:ObsidianAliases` for getting a picker list of aliases of all notes in the +- `:ObsidianAliases` for getting a picker list of aliases of all notes in the vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to @@ -186,7 +186,7 @@ USING LAZY.NVIM ~ dependencies = { -- Required. "nvim-lua/plenary.nvim", - + -- see below for full list of optional dependencies 👇 }, opts = { @@ -200,7 +200,7 @@ USING LAZY.NVIM ~ path = "~/vaults/work", }, }, - + -- see below for full list of options 👇 }, } @@ -216,7 +216,7 @@ USING PACKER.NVIM ~ requires = { -- Required. "nvim-lua/plenary.nvim", - + -- see below for full list of optional dependencies 👇 }, config = function() @@ -231,7 +231,7 @@ USING PACKER.NVIM ~ path = "~/vaults/work", }, }, - + -- see below for full list of options 👇 }) end, @@ -297,18 +297,18 @@ carefully and customize it to your needs: }, }, }, - + -- Alternatively - and for backwards compatibility - you can set 'dir' to a single path instead of -- 'workspaces'. For example: -- dir = "~/vaults/work", - + -- Optional, if you keep notes in a specific subdirectory of your vault. notes_subdir = "notes", - + -- Optional, set the log level for obsidian.nvim. This is an integer corresponding to one of the log -- levels defined by "vim.log.levels.*". log_level = vim.log.levels.INFO, - + daily_notes = { -- Optional, if you keep daily notes in a separate directory. folder = "notes/dailies", @@ -321,7 +321,7 @@ carefully and customize it to your needs: -- Optional, if you want to automatically insert a template from your template directory like 'daily.md' template = nil }, - + -- Optional, completion of wiki links, local markdown links, and tags using nvim-cmp. completion = { -- Set to false to disable completion. @@ -329,7 +329,7 @@ carefully and customize it to your needs: -- Trigger completion at 2 chars. min_chars = 2, }, - + -- Optional, configure key mappings. These are the defaults. If you don't want to set any keymappings this -- way then set 'mappings = {}'. mappings = { @@ -355,12 +355,12 @@ carefully and customize it to your needs: opts = { buffer = true, expr = true }, } }, - + -- Where to put new notes. Valid options are -- * "current_dir" - put new notes in same directory as the current buffer. -- * "notes_subdir" - put new notes in the default notes subdirectory. new_notes_location = "notes_subdir", - + -- Optional, customize how note IDs are generated given an optional title. ---@param title string|? ---@return string @@ -380,7 +380,7 @@ carefully and customize it to your needs: end return tostring(os.time()) .. "-" .. suffix end, - + -- Optional, customize how note file names are generated given the ID, target directory, and title. ---@param spec { id: string, dir: obsidian.Path, title: string|? } ---@return string|obsidian.Path The full path to the new note. @@ -389,7 +389,7 @@ carefully and customize it to your needs: local path = spec.dir / tostring(spec.id) return path:with_suffix(".md") end, - + -- Optional, customize how wiki links are formatted. You can set this to one of: -- * "use_alias_only", e.g. '[[Foo Bar]]' -- * "prepend_note_id", e.g. '[[foo-bar|Foo Bar]]' @@ -399,19 +399,19 @@ carefully and customize it to your needs: wiki_link_func = function(opts) return require("obsidian.util").wiki_link_id_prefix(opts) end, - + -- Optional, customize how markdown links are formatted. markdown_link_func = function(opts) return require("obsidian.util").markdown_link(opts) end, - + -- Either 'wiki' or 'markdown'. preferred_link_style = "wiki", - + -- Optional, boolean or a function that takes a filename and returns a boolean. -- `true` indicates that you don't want obsidian.nvim to manage frontmatter. disable_frontmatter = false, - + -- Optional, alternatively you can customize the frontmatter data. ---@return table note_frontmatter_func = function(note) @@ -419,9 +419,9 @@ carefully and customize it to your needs: if note.title then note:add_alias(note.title) end - + local out = { id = note.id, aliases = note.aliases, tags = note.tags } - + -- `note.metadata` contains any manually added fields in the frontmatter. -- So here we just make sure those fields are kept in the frontmatter. if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then @@ -429,10 +429,10 @@ carefully and customize it to your needs: out[k] = v end end - + return out end, - + -- Optional, for templates (see below). templates = { folder = "templates", @@ -441,7 +441,7 @@ carefully and customize it to your needs: -- A map for custom variables, the key should be the variable and the value a function substitutions = {}, }, - + -- Optional, by default when you use `:ObsidianFollowLink` on a link to an external -- URL it will be ignored but you can customize this behavior here. ---@param url string @@ -452,7 +452,7 @@ carefully and customize it to your needs: -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows -- vim.ui.open(url) -- need Neovim 0.10.0+ end, - + -- Optional, by default when you use `:ObsidianFollowLink` on a link to an image -- file it will be ignored but you can customize this behavior here. ---@param img string @@ -461,14 +461,14 @@ carefully and customize it to your needs: -- vim.fn.jobstart({"xdg-open", url}) -- linux -- vim.cmd(':silent exec "!start ' .. url .. '"') -- Windows end, - + -- Optional, set to true if you use the Obsidian Advanced URI plugin. -- https://github.com/Vinzent03/obsidian-advanced-uri use_advanced_uri = false, - + -- Optional, set to true to force ':ObsidianOpen' to bring the app to the foreground. open_app_foreground = false, - + picker = { -- Set your preferred picker. Can be one of 'telescope.nvim', 'fzf-lua', or 'mini.pick'. name = "telescope.nvim", @@ -487,49 +487,49 @@ carefully and customize it to your needs: insert_tag = "", }, }, - + -- Optional, sort search results by "path", "modified", "accessed", or "created". -- The recommend value is "modified" and `true` for `sort_reversed`, which means, for example, -- that `:ObsidianQuickSwitch` will show the notes sorted by latest modified time sort_by = "modified", sort_reversed = true, - + -- Set the maximum number of lines to read from notes on disk when performing certain searches. search_max_lines = 1000, - + -- Optional, determines how certain commands open notes. The valid options are: -- 1. "current" (the default) - to always open in the current window -- 2. "vsplit" - to open in a vertical split if there's not already a vertical split -- 3. "hsplit" - to open in a horizontal split if there's not already a horizontal split open_notes_in = "current", - + -- Optional, define your own callbacks to further customize behavior. callbacks = { -- Runs at the end of `require("obsidian").setup()`. ---@param client obsidian.Client post_setup = function(client) end, - + -- Runs anytime you enter the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note enter_note = function(client, note) end, - + -- Runs anytime you leave the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note leave_note = function(client, note) end, - + -- Runs right before writing the buffer for a note. ---@param client obsidian.Client ---@param note obsidian.Note pre_write_note = function(client, note) end, - + -- Runs anytime the workspace is set/changed. ---@param client obsidian.Client ---@param workspace obsidian.Workspace post_set_workspace = function(client, workspace) end, }, - + -- Optional, configure additional syntax highlighting / extmarks. -- This requires you have `conceallevel` set to 1 or 2. See `:help conceallevel` for more details. ui = { @@ -547,7 +547,7 @@ carefully and customize it to your needs: -- Replace the above with this if you don't have a patched font: -- [" "] = { char = "☐", hl_group = "ObsidianTodo" }, -- ["x"] = { char = "✔", hl_group = "ObsidianDone" }, - + -- You can also add more custom ones... }, -- Use bullet marks for non-checkbox lists. @@ -574,21 +574,21 @@ carefully and customize it to your needs: ObsidianHighlightText = { bg = "#75662e" }, }, }, - + -- Specify how to handle attachments. attachments = { -- The default folder to place images in via `:ObsidianPasteImg`. -- If this is a relative path it will be interpreted as relative to the vault root. -- You can always override this per image by passing a full path to the command instead of just a filename. img_folder = "assets/imgs", -- This is the default - + -- Optional, customize the default name or prefix when pasting images via `:ObsidianPasteImg`. ---@return string img_name_func = function() -- Prefix image names with timestamp. return string.format("%s-", os.time()) end, - + -- A function that determines the text to insert in the note when pasting an image. -- It takes two arguments, the `obsidian.Client` and an `obsidian.Path` to the image file. -- This is the default implementation. @@ -784,7 +784,7 @@ are supported out-of-the-box. For example, with the following configuration >lua { -- other fields ... - + templates = { folder = "my-templates-folder", date_format = "%Y-%m-%d-%a", @@ -797,7 +797,7 @@ and the file `~/my-vault/my-templates-folder/note template.md`: >markdown # {{title}} - + Date created: {{date}} < @@ -806,7 +806,7 @@ will insert >markdown # Configuring Neovim - + Date created: 2023-03-01-Wed < From b7ce3103ec67f510cdb38055921930180a0b979e Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Thu, 17 Oct 2024 10:57:54 -0400 Subject: [PATCH 3/7] Rename ObsidianAliases to ObsidianTitles --- CHANGELOG.md | 2 +- README.md | 2 +- doc/obsidian.txt | 4 +- lua/obsidian/commands/aliases.lua | 62 -------------------------- lua/obsidian/commands/init.lua | 4 +- lua/obsidian/commands/titles.lua | 74 +++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 68 deletions(-) delete mode 100644 lua/obsidian/commands/aliases.lua create mode 100644 lua/obsidian/commands/titles.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index fd984b136..8085c2f85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `opts.follow_img_func` option for customizing how to handle image paths. - Added better handling for undefined template fields, which will now be prompted for. -- Added `:ObsidianAliases` command. +- Added `:ObsidianTitles` command. ### Changed diff --git a/README.md b/README.md index 25661b9b0..7bf5613b1 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ _Keep in mind this plugin is not meant to replace Obsidian, but to complement it - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. -- `:ObsidianAliases` for getting a picker list of aliases of all notes in the vault. +- `:ObsidianTitles` for getting a picker list of titles and aliases of all notes in the vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to yesterday's note. Unlike `:ObsidianYesterday` and `:ObsidianTomorrow` this command does not differentiate between weekdays and weekends. diff --git a/doc/obsidian.txt b/doc/obsidian.txt index ffbefdc2e..60a0d0b5b 100644 --- a/doc/obsidian.txt +++ b/doc/obsidian.txt @@ -76,8 +76,8 @@ COMMANDS *obsidian-commands* buffer. - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. -- `:ObsidianAliases` for getting a picker list of aliases of all notes in the - vault. +- `:ObsidianTitles` for getting a picker list of titles and aliases of all notes + in the vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to yesterday’s note. Unlike `:ObsidianYesterday` and `:ObsidianTomorrow` this diff --git a/lua/obsidian/commands/aliases.lua b/lua/obsidian/commands/aliases.lua deleted file mode 100644 index 0d2d0bd8c..000000000 --- a/lua/obsidian/commands/aliases.lua +++ /dev/null @@ -1,62 +0,0 @@ -local log = require "obsidian.log" -local util = require "obsidian.util" - ----@param client obsidian.Client ----@param callback fun(alias_to_notes: table) -local find_alias = function(client, callback) - client:find_notes_async("", function(notes) - local alias_to_notes = {} - for _, note in ipairs(notes) do - for _, alias in ipairs(note.aliases) do - alias_to_notes[alias] = alias_to_notes[alias] or {} - table.insert(alias_to_notes[alias], note) - end - end - callback(alias_to_notes) - end) -end - ----@param note obsidian.Note ---- ----@return obsidian.PickerEntry -local function convert_note_to_picker_entry(note) - return { - value = note, - display = note:display_name(), - ordinal = note:display_name(), - filename = tostring(note.path), - } -end - ----@param client obsidian.Client -return function(client) - local picker = client:picker() - if not picker then - log.err "No picker configured" - return - end - - find_alias(client, function(alias_to_notes) - vim.schedule(function() - picker:pick(vim.tbl_keys(alias_to_notes), { - prompt_title = "Aliases", - callback = function(alias) - local notes = alias_to_notes[alias] - if #notes == 1 then - util.open_buffer(notes[1].path) - else - local entries = vim.tbl_map(convert_note_to_picker_entry, notes) - vim.schedule(function() - picker:pick(entries, { - prompt_title = alias, - callback = function(note) - util.open_buffer(note.path) - end, - }) - end) - end - end, - }) - end) - end) -end diff --git a/lua/obsidian/commands/init.lua b/lua/obsidian/commands/init.lua index 7e5c4e0b2..9b06d0121 100644 --- a/lua/obsidian/commands/init.lua +++ b/lua/obsidian/commands/init.lua @@ -13,7 +13,7 @@ local command_lookups = { ObsidianBacklinks = "obsidian.commands.backlinks", ObsidianSearch = "obsidian.commands.search", ObsidianTags = "obsidian.commands.tags", - ObsidianAliases = "obsidian.commands.aliases", + ObsidianTitles = "obsidian.commands.titles", ObsidianTemplate = "obsidian.commands.template", ObsidianNewFromTemplate = "obsidian.commands.new_from_template", ObsidianQuickSwitch = "obsidian.commands.quick_switch", @@ -150,7 +150,7 @@ M.register("ObsidianBacklinks", { opts = { nargs = 0, desc = "Collect backlinks" M.register("ObsidianTags", { opts = { nargs = "*", range = true, desc = "Find tags" } }) -M.register("ObsidianAliases", { opts = { nargs = "*", range = true, desc = "Find aliases" } }) +M.register("ObsidianTitles", { opts = { nargs = 0, desc = "Collect titles and aliases" } }) M.register("ObsidianSearch", { opts = { nargs = "?", desc = "Search vault" } }) diff --git a/lua/obsidian/commands/titles.lua b/lua/obsidian/commands/titles.lua new file mode 100644 index 000000000..907998c40 --- /dev/null +++ b/lua/obsidian/commands/titles.lua @@ -0,0 +1,74 @@ +local log = require "obsidian.log" +local util = require "obsidian.util" + +---@param note obsidian.Note +--- +---@return obsidian.PickerEntry +local function convert_note_to_picker_entry(note) + return { + value = note, + display = note:display_name(), + ordinal = note:display_name(), + filename = tostring(note.path), + } +end + +---@param notes obsidian.Note[] +--- +---@return table +local function map_title_to_notes(notes) + local title_to_notes = {} + for _, note in ipairs(notes) do + local title = note.title + if title then + title_to_notes[title] = title_to_notes[title] or {} + table.insert(title_to_notes[title], note) + end + for _, alias in ipairs(note.aliases) do + if alias ~= title then + title_to_notes[alias] = title_to_notes[alias] or {} + table.insert(title_to_notes[alias], note) + end + end + end + + for title, notes_share_title in pairs(title_to_notes) do + title_to_notes[title] = util.tbl_unique(notes_share_title) + end + + return title_to_notes +end + +---@param client obsidian.Client +return function(client) + local picker = client:picker() + if not picker then + log.err "No picker configured" + return + end + + client:find_notes_async("", function(notes) + local title_to_notes = map_title_to_notes(notes) + vim.schedule(function() + picker:pick(vim.tbl_keys(title_to_notes), { + prompt_title = "Titles", + callback = function(title) + local selected_notes = title_to_notes[title] + if #selected_notes == 1 then + util.open_buffer(selected_notes[1].path) + else + local entries = vim.tbl_map(convert_note_to_picker_entry, selected_notes) + vim.schedule(function() + picker:pick(entries, { + prompt_title = title, + callback = function(value) + util.open_buffer(value.path) + end, + }) + end) + end + end, + }) + end) + end) +end From 8474103c6ec0a5c6e935e353de39317ff2d3efe1 Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Thu, 17 Oct 2024 11:03:27 -0400 Subject: [PATCH 4/7] Do not deduplicate aliases in a single note --- lua/obsidian/commands/titles.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/obsidian/commands/titles.lua b/lua/obsidian/commands/titles.lua index 907998c40..9c6d92dfb 100644 --- a/lua/obsidian/commands/titles.lua +++ b/lua/obsidian/commands/titles.lua @@ -32,10 +32,6 @@ local function map_title_to_notes(notes) end end - for title, notes_share_title in pairs(title_to_notes) do - title_to_notes[title] = util.tbl_unique(notes_share_title) - end - return title_to_notes end From 2e8205e359ef4cdee666f3414dcb43a23e7c9934 Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Thu, 17 Oct 2024 22:00:34 -0400 Subject: [PATCH 5/7] Add preview to title selection --- lua/obsidian/commands/titles.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lua/obsidian/commands/titles.lua b/lua/obsidian/commands/titles.lua index 9c6d92dfb..8fa19699d 100644 --- a/lua/obsidian/commands/titles.lua +++ b/lua/obsidian/commands/titles.lua @@ -17,6 +17,7 @@ end --- ---@return table local function map_title_to_notes(notes) + ---@type table local title_to_notes = {} for _, note in ipairs(notes) do local title = note.title @@ -45,8 +46,18 @@ return function(client) client:find_notes_async("", function(notes) local title_to_notes = map_title_to_notes(notes) + ---@type obsidian.PickerEntry[] + local items = {} + for title, notes_with_title in pairs(title_to_notes) do + table.insert(items, { + value = title, + display = title, + ordinal = title, + filename = tostring(notes_with_title[1].path), + }) + end vim.schedule(function() - picker:pick(vim.tbl_keys(title_to_notes), { + picker:pick(items, { prompt_title = "Titles", callback = function(title) local selected_notes = title_to_notes[title] @@ -57,8 +68,8 @@ return function(client) vim.schedule(function() picker:pick(entries, { prompt_title = title, - callback = function(value) - util.open_buffer(value.path) + callback = function(note) + util.open_buffer(note.path) end, }) end) From 9a75cd3e897ea76b45e9614d9f01c3a1866cf42d Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Fri, 18 Oct 2024 21:55:54 -0400 Subject: [PATCH 6/7] Some simplification --- README.md | 2 +- doc/obsidian.txt | 2 +- lua/obsidian/commands/titles.lua | 72 +++++++++++--------------------- 3 files changed, 26 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 7bf5613b1..8f1d0fd1a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ _Keep in mind this plugin is not meant to replace Obsidian, but to complement it - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. -- `:ObsidianTitles` for getting a picker list of titles and aliases of all notes in the vault. +- `:ObsidianTitles` for getting a picker list of all titles and aliases of notes in the vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to yesterday's note. Unlike `:ObsidianYesterday` and `:ObsidianTomorrow` this command does not differentiate between weekdays and weekends. diff --git a/doc/obsidian.txt b/doc/obsidian.txt index 60a0d0b5b..fb7af820c 100644 --- a/doc/obsidian.txt +++ b/doc/obsidian.txt @@ -76,7 +76,7 @@ COMMANDS *obsidian-commands* buffer. - `:ObsidianTags [TAG ...]` for getting a picker list of all occurrences of the given tags. -- `:ObsidianTitles` for getting a picker list of titles and aliases of all notes +- `:ObsidianTitles` for getting a picker list of all titles and aliases of notes in the vault. - `:ObsidianToday [OFFSET]` to open/create a new daily note. This command also takes an optional offset in days, e.g. use `:ObsidianToday -1` to go to diff --git a/lua/obsidian/commands/titles.lua b/lua/obsidian/commands/titles.lua index 8fa19699d..a64457824 100644 --- a/lua/obsidian/commands/titles.lua +++ b/lua/obsidian/commands/titles.lua @@ -1,39 +1,38 @@ local log = require "obsidian.log" local util = require "obsidian.util" ----@param note obsidian.Note ---- ----@return obsidian.PickerEntry -local function convert_note_to_picker_entry(note) - return { - value = note, - display = note:display_name(), - ordinal = note:display_name(), - filename = tostring(note.path), - } -end - ---@param notes obsidian.Note[] --- ----@return table -local function map_title_to_notes(notes) - ---@type table - local title_to_notes = {} +---@return obsidian.PickerEntry[] +local function convert_notes_to_picker_entries(notes) + ---@type obsidian.PickerEntry[] + local entries = {} + for _, note in ipairs(notes) do local title = note.title + if title then - title_to_notes[title] = title_to_notes[title] or {} - table.insert(title_to_notes[title], note) + entries[#entries + 1] = { + value = note, + display = title, + ordinal = note:display_name(), + filename = tostring(note.path), + } end + for _, alias in ipairs(note.aliases) do if alias ~= title then - title_to_notes[alias] = title_to_notes[alias] or {} - table.insert(title_to_notes[alias], note) + entries[#entries + 1] = { + value = note, + display = alias, + ordinal = note:display_name(), + filename = tostring(note.path), + } end end end - return title_to_notes + return entries end ---@param client obsidian.Client @@ -45,36 +44,13 @@ return function(client) end client:find_notes_async("", function(notes) - local title_to_notes = map_title_to_notes(notes) - ---@type obsidian.PickerEntry[] - local items = {} - for title, notes_with_title in pairs(title_to_notes) do - table.insert(items, { - value = title, - display = title, - ordinal = title, - filename = tostring(notes_with_title[1].path), - }) - end vim.schedule(function() - picker:pick(items, { + picker:pick(convert_notes_to_picker_entries(notes), { prompt_title = "Titles", - callback = function(title) - local selected_notes = title_to_notes[title] - if #selected_notes == 1 then - util.open_buffer(selected_notes[1].path) - else - local entries = vim.tbl_map(convert_note_to_picker_entry, selected_notes) - vim.schedule(function() - picker:pick(entries, { - prompt_title = title, - callback = function(note) - util.open_buffer(note.path) - end, - }) - end) - end + callback = function(note) + util.open_buffer(note.path) end, + allow_multiple = true, }) end) end) From b071178c7ce4e30cd79518e160b6d44101382442 Mon Sep 17 00:00:00 2001 From: Longwu Ou Date: Fri, 18 Oct 2024 22:46:52 -0400 Subject: [PATCH 7/7] Handle multiple choices --- lua/obsidian/commands/titles.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lua/obsidian/commands/titles.lua b/lua/obsidian/commands/titles.lua index a64457824..ccaca0b01 100644 --- a/lua/obsidian/commands/titles.lua +++ b/lua/obsidian/commands/titles.lua @@ -15,7 +15,7 @@ local function convert_notes_to_picker_entries(notes) entries[#entries + 1] = { value = note, display = title, - ordinal = note:display_name(), + ordinal = note:display_name() .. " " .. title, filename = tostring(note.path), } end @@ -25,7 +25,7 @@ local function convert_notes_to_picker_entries(notes) entries[#entries + 1] = { value = note, display = alias, - ordinal = note:display_name(), + ordinal = note:display_name() .. " " .. alias, filename = tostring(note.path), } end @@ -47,8 +47,10 @@ return function(client) vim.schedule(function() picker:pick(convert_notes_to_picker_entries(notes), { prompt_title = "Titles", - callback = function(note) - util.open_buffer(note.path) + callback = function(...) + for _, note in ipairs { ... } do + util.open_buffer(note.path) + end end, allow_multiple = true, })