Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,49 @@ std = "luajit"

-- Add Neovim globals
globals = {
"vim",
"vim",
}

-- Additional read-only globals for testing
read_globals = {
-- Busted testing framework
"describe", "it", "before_each", "after_each", "setup", "teardown",
"assert", "spy", "stub", "mock",
-- Plenary testing
"require",
-- Busted testing framework
"describe",
"it",
"before_each",
"after_each",
"setup",
"teardown",
"assert",
"spy",
"stub",
"mock",
-- Plenary testing
"require",
}

-- Files/directories to exclude
exclude_files = {
".luarocks/",
"doc/tags",
".luarocks/",
"doc/tags",
}

-- Ignore specific warnings
ignore = {
"212", -- Unused argument (common in Neovim plugins for callback functions)
"213", -- Unused loop variable (common in ipairs/pairs loops)
"212", -- Unused argument (common in Neovim plugins for callback functions)
"213", -- Unused loop variable (common in ipairs/pairs loops)
}

-- Maximum line length
max_line_length = 100
max_line_length = 120

-- Files and their specific configurations
files = {
["tests/"] = {
-- Allow longer lines in tests for readability
max_line_length = 120,
-- Additional testing globals
globals = {
"vim", -- vim is mocked in tests
}
}
}
["tests/"] = {
-- Allow longer lines in tests for readability
max_line_length = 120,
-- Additional testing globals
globals = {
"vim", -- vim is mocked in tests
},
},
}
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ That's it! You're ready to start building your knowledge base.
- **📅 Daily Notes** - Quick creation and navigation with automatic templating
- **📝 Template System** - Flexible templates with variable substitution (`{{date}}`, `{{time}}`, `{{title}}`, etc.)
- **🔗 Wiki-style Links** - Create and follow `[[note-name]]` links between notes
- **🔄 Smart Renaming** - Rename notes and automatically update all references
- **🔄 Smart Renaming** - Rename notes and automatically update all references with file preview
- **🔍 Powerful Search** - Find notes by filename or content with syntax highlighting
- **↩️ Backlinks** - Discover which notes reference the current note

Expand Down Expand Up @@ -136,7 +136,7 @@ All keybindings use `<leader>n` as the prefix for easy discovery:
| `<leader>np` | Insert template | Insert template at cursor |
| `<leader>ng` | Search tags | Find notes by frontmatter tags |
| `<leader>nb` | Show backlinks | Show notes linking to current note |
| `<leader>nr` | Rename note | Rename note and update all references |
| `<leader>nr` | Rename note | Rename note and update all references with preview |
| `<leader>nw` | Pick workspace | Switch between workspaces |
| `gf` | Follow link | Follow link under cursor |

Expand Down Expand Up @@ -185,6 +185,14 @@ gf → Follow the link under cursor
<leader>nr → Rename current note and update all references
```

**Smart Renaming**: When you rename a note that has links pointing to it, markdown-notes.nvim will:
1. Show you a preview of all files that will be updated
2. Let you browse through them with fzf-lua
3. Update all `[[note-name]]` and `[[note-name|display text]]` references automatically
4. Handle files in subdirectories correctly

> **💡 Tip:** You can disable the preview and use a simple confirmation dialog by setting `ui.show_rename_preview = false` in your configuration.

### Using Templates

Templates make your notes consistent and save time:
Expand Down Expand Up @@ -239,6 +247,11 @@ require("markdown-notes").setup({
-- Template settings
default_template = "basic", -- Auto-apply this template to new notes

-- UI behavior
ui = {
show_rename_preview = true, -- Show file preview when renaming notes with links
},

-- Custom template variables
template_vars = {
date = function() return os.date("%Y-%m-%d") end,
Expand Down
31 changes: 25 additions & 6 deletions doc/markdown-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Core Features~
- Daily Notes - Quick creation and navigation with automatic templating
- Template System - Flexible templates with variable substitution
- Wiki-style Links - Create and follow [[note-name]] links between notes
- Smart Renaming - Rename notes and automatically update all references
- Smart Renaming - Rename notes and automatically update all references with file preview
- Powerful Search - Find notes by filename or content with syntax highlighting
- Backlinks - Discover which notes reference the current note

Expand Down Expand Up @@ -158,9 +158,20 @@ Creating Links Between Notes: >
Following and Managing Links: >
gf → Follow the link under cursor
<leader>nb → Show all notes that link to current note (backlinks)
<leader>nr → Rename current note and update all references
<leader>nr → Rename current note and update all references with preview
<

Smart Renaming~
*markdown-notes-usage-smart-renaming*
When you rename a note that has links pointing to it, markdown-notes.nvim will:
1. Show you a preview of all files that will be updated
2. Let you browse through them with fzf-lua and file preview
3. Update all `[[note-name]]` and `[[note-name|display text]]` references
4. Handle files in subdirectories correctly

You can disable the preview and use a simple confirmation dialog by setting
`ui.show_rename_preview = false` in your configuration.

Using Templates~
*markdown-notes-usage-templates*
Templates make your notes consistent and save time: >
Expand Down Expand Up @@ -206,7 +217,7 @@ Note Management~
Links and Navigation~
`<leader>nl` Search for note and insert wiki-link
`<leader>nb` Show notes linking to current note
`<leader>nr` Rename note and update all references
`<leader>nr` Rename note and update all references with preview
`gf` Follow link under cursor

Templates and Tags~
Expand Down Expand Up @@ -235,6 +246,11 @@ Custom Configuration Options~
-- Template settings
default_template = "basic", -- Auto-apply this template to new notes

-- UI behavior
ui = {
show_rename_preview = true, -- Show file preview when renaming notes with links
},

-- Custom template variables
template_vars = {
date = function() return os.date("%Y-%m-%d") end,
Expand Down Expand Up @@ -416,7 +432,8 @@ Ex Commands~
*markdown-notes-ex-commands*
:MarkdownNotesRename [{name}] *:MarkdownNotesRename*
Rename current note and update all references. If {name} is not provided,
prompts for the new name. Shows confirmation dialog with file count.
prompts for the new name. Shows file preview with affected files or
confirmation dialog depending on configuration.

Function API~
*markdown-notes-function-commands*
Expand Down Expand Up @@ -452,10 +469,12 @@ require("markdown-notes.links").follow_link() *markdown-notes.follow_link*
require("markdown-notes.links").show_backlinks() *markdown-notes.show_backlinks*
Show backlinks to the current note.

require("markdown-notes.links").rename_note(new_name) *markdown-notes.rename_note*
require("markdown-notes.links").rename_note(new_name, opts) *markdown-notes.rename_note*
Rename the current note and automatically update all wiki-style link
references across the vault. Supports both `[[note]]` and `[[note|display]]`
formats. Shows confirmation dialog with file count before proceeding.
formats. Shows file preview with affected files by default, or simple
confirmation if ui.show_rename_preview is false. Optional {opts} table
accepts skip_ui boolean for programmatic usage.

require("markdown-notes.templates").pick_template() *markdown-notes.pick_template*
Pick and insert a template at cursor position.
Expand Down
1 change: 1 addition & 0 deletions doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ markdown-notes-usage-daily-notes markdown-notes.txt /*markdown-notes-usage-daily
markdown-notes-usage-guide markdown-notes.txt /*markdown-notes-usage-guide*
markdown-notes-usage-links markdown-notes.txt /*markdown-notes-usage-links*
markdown-notes-usage-note-management markdown-notes.txt /*markdown-notes-usage-note-management*
markdown-notes-usage-smart-renaming markdown-notes.txt /*markdown-notes-usage-smart-renaming*
markdown-notes-usage-templates markdown-notes.txt /*markdown-notes-usage-templates*
markdown-notes-workspace-commands markdown-notes.txt /*markdown-notes-workspace-commands*
markdown-notes-workspace-setup markdown-notes.txt /*markdown-notes-workspace-setup*
Expand Down
6 changes: 6 additions & 0 deletions lua/markdown-notes/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ M.defaults = {
-- Default workspace (optional)
default_workspace = nil,

-- UI behavior
ui = {
-- Show file preview when renaming notes that have links
show_rename_preview = true,
},

-- Key mappings
mappings = {
daily_note_today = "<leader>nd",
Expand Down
91 changes: 76 additions & 15 deletions lua/markdown-notes/links.lua
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ function M.show_backlinks()
local search_text = "[[" .. relative_path .. "]]"

-- Get all markdown files first, then check each one
local all_files_cmd = "cd " .. vim.fn.shellescape(vault_path) ..
" && find . -name '*.md' -type f -not -path '*/.*'"
local all_files_cmd = "cd " .. vim.fn.shellescape(vault_path) .. " && find . -name '*.md' -type f -not -path '*/.*'"
local all_files = vim.fn.systemlist(all_files_cmd)

-- Remove ./ prefix from paths
Expand Down Expand Up @@ -162,7 +161,8 @@ function M.show_backlinks()
})
end

function M.rename_note(new_name)
function M.rename_note(new_name, opts)
opts = opts or {}
local current_path = vim.fn.expand("%:p")
local options = config.get_current_config()
local vault_path = vim.fn.expand(options.vault_path)
Expand Down Expand Up @@ -211,8 +211,7 @@ function M.rename_note(new_name)
local link_with_display = "%[%[" .. vim.pesc(relative_path) .. "|"

-- Get all markdown files
local all_files_cmd = "cd " .. vim.fn.shellescape(vault_path) ..
" && find . -name '*.md' -type f -not -path '*/.*'"
local all_files_cmd = "cd " .. vim.fn.shellescape(vault_path) .. " && find . -name '*.md' -type f -not -path '*/.*'"
local all_files = vim.fn.systemlist(all_files_cmd)

-- Remove ./ prefix from paths
Expand Down Expand Up @@ -256,18 +255,65 @@ function M.rename_note(new_name)
new_relative_path = current_dir_relative .. "/" .. new_name
end

-- Ask for confirmation
local message = "Rename '" .. relative_path .. "' to '" .. new_name .. "'"
if #files_to_update > 0 then
message = message .. " and update " .. #files_to_update .. " files with links?"
-- Show affected files and ask for confirmation
if #files_to_update > 0 and not opts.skip_ui and options.ui.show_rename_preview then
local ok, fzf = pcall(require, "fzf-lua")
if not ok then
vim.notify("fzf-lua not available", vim.log.levels.ERROR)
return
end

-- Show files that will be updated
local file_list = {}
for _, file_info in ipairs(files_to_update) do
table.insert(file_list, file_info.file)
end

fzf.fzf_exec(file_list, {
prompt = "Files to update (" .. #files_to_update .. ") - Enter to rename, Esc to cancel > ",
cwd = vault_path,
previewer = "builtin",
actions = {
["default"] = function()
-- Proceed with rename directly (preview was the confirmation)
M._perform_rename(
current_path,
new_file_path,
files_to_update,
relative_path,
new_relative_path,
update_count
)
end,
},
})
else
message = message .. "?"
-- Skip UI for tests or when no files to update
local message = "Rename '" .. relative_path .. "' to '" .. new_name .. "'"
if #files_to_update > 0 then
message = message .. " and update " .. #files_to_update .. " files?"
else
message = message .. "?"
end
local confirm = opts.skip_ui and 1 or vim.fn.confirm(message, "&Yes\n&No", 2)
if confirm == 1 then
M._perform_rename(
current_path,
new_file_path,
files_to_update,
relative_path,
new_relative_path,
update_count
)
end
end
end

local confirm = vim.fn.confirm(message, "&Yes\n&No", 2)
if confirm ~= 1 then
return
end
-- Helper function to perform the actual rename operation
function M._perform_rename(current_path, new_file_path, files_to_update, relative_path, new_relative_path, update_count)
-- Check if we're renaming the current buffer's file (before renaming)
local current_bufnr = vim.fn.bufnr("%")
local is_current_buffer = current_bufnr ~= -1 and vim.fn.expand("%:p") == current_path

-- Update all linking files
for _, file_info in ipairs(files_to_update) do
Expand All @@ -293,7 +339,22 @@ function M.rename_note(new_name)
-- Rename the actual file
local success = vim.fn.rename(current_path, new_file_path)
if success == 0 then
vim.cmd("edit " .. vim.fn.fnameescape(new_file_path))
-- Handle buffer management if we renamed the current buffer's file
if is_current_buffer then
-- Update the buffer's filename to the new path
-- Use pcall to handle E95 error if buffer name already exists
local success_rename = pcall(vim.api.nvim_buf_set_name, current_bufnr, new_file_path)
if not success_rename then
-- If buffer name collision, force close conflicting buffer and retry
local conflicting_bufnr = vim.fn.bufnr(new_file_path)
if conflicting_bufnr ~= -1 then
vim.cmd("bwipeout! " .. conflicting_bufnr)
vim.api.nvim_buf_set_name(current_bufnr, new_file_path)
end
end
-- Reload the buffer to reflect the new filename
vim.cmd("edit!")
end
if update_count > 0 then
vim.notify("Renamed note and updated " .. update_count .. " files", vim.log.levels.INFO)
else
Expand Down
Loading