Backlinks Not Recognized When Using Wikilinks with DisplayText/Alias and Without File Extension
🐛 Describe the bug
Description
The plugin fails to recognize backlinks if the link uses different display text or an alias without including the file extension. This behavior differs from Obsidian, which correctly identifies backlinks in such cases.
Steps to Reproduce
- Create a note, e.g.,
NoteA.md. - Create another note, e.g.,
NoteB.md, and link toNoteAusing[[NoteA|Display Text]]or[[NoteA#section|Display Text]](without the.mdextension). - The backlink to
NoteAis not recognized by the plugin.
Expected Behavior
The plugin should recognize backlinks even when the file extension is omitted and different display text or aliases are used, consistent with Obsidian's behavior.
Actual Behavior
The plugin only recognizes backlinks with display text or aliases when the file extension is included in the link.
| Link Type | Link to file Works | Backlinking Works | Notes |
|---|---|---|---|
[[dir/filename.md]] |
Yes | Yes | Full path with extension |
[[filename.md]] |
Yes | Yes | Filename with extension |
[[filename]] |
Yes | Yes | Filename without extension |
[[dir/filename.md|alias]] |
Yes | Yes | Full path with extension and alias |
[[filename.md|alias]] |
Yes | Yes | Filename with extension and alias |
[[filename|alias]] |
Yes | No | Filename without extension and alias |
I also noticed that if a wikilink is used with just the filename without extension and using an alias, and the filename used in the link matches the id in the frontmatter of the linked note, the backlink works. but if using a random id then it doesnt work.
Proposed Solution
i asked chatgpt and it came up with following fix which actully works:
Update the backlink search functionality to include variations of the file name without the file extension in the search terms when using different display text or aliases. This would involve modifying the search terms generation to handle links both with and without the file extension.
lua/obsidian/client.lua | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lua/obsidian/client.lua b/lua/obsidian/client.lua
index ff6e500..8bbbd78 100644
--- a/lua/obsidian/client.lua
+++ b/lua/obsidian/client.lua
@@ -1301,7 +1301,8 @@ Client.find_backlinks_async = function(self, note, callback, opts)
-- Prepare search terms.
local search_terms = {}
- for raw_ref in iter { tostring(note.id), note:fname(), self:vault_relative_path(note.path) } do
+ local filename_without_extension = note:fname():gsub("%.%w+$", "")
+ for raw_ref in iter { tostring(note.id), note:fname(), filename_without_extension, self:vault_relative_path(note.path) } do
for ref in
iter(util.tbl_unique {
raw_ref,
Config
{
"epwalsh/obsidian.nvim",
version = "*", -- recommended, use latest release instead of latest commit
lazy = true,
ft = "markdown",
-- Replace the above line with this if you only want to load obsidian.nvim for markdown files in your vault:
-- event = {
-- -- If you want to use the home shortcut '~' here you need to call 'vim.fn.expand'.
-- -- E.g. "BufReadPre " .. vim.fn.expand "~" .. "/my-vault/**.md"
-- "BufReadPre path/to/my-vault/**.md",
-- "BufNewFile path/to/my-vault/**.md",
-- },
dependencies = {
-- Required.
"nvim-lua/plenary.nvim",
-- see below for full list of optional dependencies 👇
},
opts = {
-- A list of workspace names, paths, and configuration overrides.
-- If you use the Obsidian app, the 'path' of a workspace should generally be
-- your vault root (where the `.obsidian` folder is located).
-- When obsidian.nvim is loaded by your plugin manager, it will automatically set
-- the workspace to the first workspace in the list whose `path` is a parent of the
-- current markdown file being edited.
workspaces = {
{
name = "notes",
path = "~/notes",
},
{
name = "apple-notes",
path = "~/apple-notes",
},
{
name = "test-vault",
path = "~/obsidian-test-vault",
},
},
-- 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 = "0_uncathegorised_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",
-- Optional, if you want to change the date format for the ID of daily notes.
date_format = "%Y-%m-%d",
-- Optional, if you want to change the date format of the default alias of daily notes.
alias_format = "%B %-d, %Y",
-- Optional, default tags to add to each new daily note created.
default_tags = { "daily-notes" },
-- 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.
nvim_cmp = true,
-- Trigger completion at 2 chars.
min_chars = 0,
},
-- Optional, configure key mappings. These are the defaults. If you don't want to set any keymappings this
-- way then set 'mappings = {}'.
mappings = {
-- Overrides the 'gf' mapping to work on markdown/wiki links within your vault.
["gf"] = {
action = function()
return require("obsidian").util.gf_passthrough()
end,
opts = { noremap = false, expr = true, buffer = true },
},
-- Toggle check-boxes.
["<leader>oo"] = {
action = function()
return require("obsidian").util.toggle_checkbox()
end,
opts = { buffer = true },
},
-- -- Smart action depending on context, either follow link or toggle checkbox.
-- ["<cr>"] = {
-- action = function()
-- return require("obsidian").util.smart_action()
-- end,
-- 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
-- note_id_func = function(title)
-- -- Create note IDs in a Zettelkasten format with a timestamp and a suffix.
-- -- In this case a note with the title 'My new note' will be given an ID that looks
-- -- like '1657296016-my-new-note', and therefore the file name '1657296016-my-new-note.md'
-- local suffix = ""
-- if title ~= nil then
-- -- If title is given, transform it into valid file name.
-- suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
-- else
-- -- If title is nil, just add 4 random uppercase letters to the suffix.
-- for _ = 1, 4 do
-- suffix = suffix .. string.char(math.random(65, 90))
-- end
-- 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.
note_path_func = function(spec)
-- -- This is equivalent to the default behavior.
-- local path = spec.dir / tostring(spec.id)
-- return path:with_suffix(".md")
local ltitle = spec.title
return ltitle .. ".md"
end,
-- -- TODO: change how wiki links are formatted to make it compatible with obsidian
-- -- 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]]'
-- -- * "prepend_note_path", e.g. '[[foo-bar.md|Foo Bar]]'
-- -- * "use_path_only", e.g. '[[foo-bar.md]]'
-- -- Or you can set it to a function that takes a table of options and returns a string, like this:
-- wiki_link_func = function(opts)
-- return require("obsidian.util").wiki_link_id_prefix(opts)
-- end,
wiki_link_func = "use_path_only",
-- function(opts)
-- ---@type string
-- local header_or_block = ""
-- if opts.anchor then
-- header_or_block = opts.anchor.anchor
-- elseif opts.block then
-- header_or_block = string.format("#%s", opts.block.id)
-- end
-- return string.format("[[%s%s]]", opts.path, header_or_block)
-- end
-- NOTE: same for markdown
-- -- 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, for templates (see below).
templates = {
folder = "1_templates",
date_format = "%Y-%m-%d",
time_format = "%H:%M",
-- A map for custom variables, the key should be the variable and the value a function
substitutions = {
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end,
-- the default id substitution is broken in templates
id = function()
-- Create note IDs in a Zettelkasten format with a timestamp and a suffix.
-- In this case a note with the title 'My new note' will be given an ID that looks
-- like '1657296016-my-new-note', and therefore the file name '1657296016-my-new-note.md'
local suffix = ""
if title ~= nil then
-- If title is given, transform it into valid file name.
suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
else
-- If title is nil, just add 4 random uppercase letters to the suffix.
for _ = 1, 4 do
suffix = suffix .. string.char(math.random(65, 90))
end
end
return tostring(os.time()) .. "-" .. suffix
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,
-- Set the maximum number of lines to read from notes on disk when performing certain searches.
search_max_lines = 2000,
-- Optional, configure additional syntax highlighting / extmarks.
-- This requires you have `conceallevel` set to 1 or 2. See `:help conceallevel` for more details.
ui = {
enable = true, -- set to false to disable all additional syntax features
update_debounce = 200, -- update delay after a text change (in milliseconds)
max_file_length = 5000, -- disable UI features for files with more than this many lines
-- Define how various check-boxes are displayed
checkboxes = {},
-- Use bullet marks for non-checkbox lists.
bullets = {}, -- NOTE: done by markdown.nvim
external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" }, -- NOTE: need to set conceallevel=2 in markdown.nvim to be visible
-- Replace the above with this if you don't have a patched font:
-- external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
reference_text = { hl_group = "ObsidianRefText" },
highlight_text = { hl_group = "ObsidianHighlightText" },
tags = { hl_group = "ObsidianTag" },
block_ids = { hl_group = "ObsidianBlockID" },
hl_groups = {
-- The options are passed directly to `vim.api.nvim_set_hl()`. See `:help nvim_set_hl`.
ObsidianTodo = { bold = true, fg = "#f78c6c" },
ObsidianDone = { bold = true, fg = "#89ddff" },
ObsidianRightArrow = { bold = true, fg = "#f78c6c" },
ObsidianTilde = { bold = true, fg = "#ff5370" },
ObsidianImportant = { bold = true, fg = "#d73128" },
ObsidianBullet = { bold = true, fg = "#89ddff" },
ObsidianRefText = { underline = true, fg = "#c792ea" },
ObsidianExtLinkIcon = { fg = "#c792ea" },
ObsidianTag = { italic = true, fg = "#89ddff" },
ObsidianBlockID = { italic = true, fg = "#89ddff" },
ObsidianHighlightText = { bg = "#75662e" },
},
},
}
}
Environment
NVIM v0.10.0
Build type: Release
LuaJIT 2.1.1716656478
Run "nvim -V1 -v" for more info
Error detected while processing /Users/name/.local/share/lunarvim/lvim/init.lua:
&termguicolors must be set
Obsidian.nvim v3.9.0 (2a401724579884d22bb584d06beba67aecb6a0ac)
Status:
• buffer directory: nil
• working directory: /Users/name
Dependencies:
✓ plenary.nvim: a3e3bc82a3f95c5ed0d7201546d5d2c19b20d683
✓ nvim-cmp: 5260e5e8ecadaf13e6b82cf867a909f54e15fd07
Integrations:
✓ picker: TelescopePicker()
✓ completion: enabled (nvim-cmp) ✗ refs, ✗ tags, ✗ new
all sources:
• copilot
• nvim_lsp
• path
• luasnip
• cmp_tabnine
• nvim_lua
• buffer
• calc
• emoji
• treesitter
• crates
• tmux
Tools:
✓ rg: ripgrep 14.1.0
Environment:
• operating system: Darwin
Config:
• notes_subdir: 0_uncathegorised_notes%