Simple Memo for Neovim
Timestamped memos with wiki-style linking, right in your editor.
Features β’ Quick Start β’ Configuration β’ Picker Integration β’ API
- Timestamped memos β Files named
YYYYMMDD_HHMMSS_{title}.md - YAML frontmatter β Tags, creation date, and metadata
- Wiki-style linking β Connect memos with
[[links]] - Floating window UI β Distraction-free editing
- Git auto-tagging β Automatically tag memos with repository name
- Picker-agnostic β Works with fzf-lua, snacks.nvim, mini.pick, or any picker
{
"Cassin01/sm.nvim",
config = function()
require("sm").setup({
-- memos_dir = "~/.cache/nvim/sm/memos",
-- state_file = "~/.cache/nvim/sm/state.json",
-- date_format = "%Y%m%d_%H%M%S",
-- auto_tag_git_repo = false,
-- copilot_integration = false,
-- window = { width = 80, height = 30, border = "rounded", style = "minimal" },
})
-- Buffer-local keymaps for memo files
local group = vim.api.nvim_create_augroup("sm_user_keymaps", { clear = true })
vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, {
group = group,
pattern = require("sm").autocmd_pattern(),
callback = function(args)
local buf = args.buf
vim.keymap.set("n", "<leader>mt", require("sm").buf_add_tag, { buffer = buf, desc = "Add tag" })
vim.keymap.set("n", "<leader>mf", require("sm").buf_follow_link, { buffer = buf, desc = "Follow link" })
end,
})
end,
keys = {
{ "<Leader>mn", function() require("sm").create() end, desc = "New memo" },
{ "<Leader>mo", function() require("sm").open_last() end, desc = "Open last memo" },
},
}:SmNew meeting notes
:SmOpen
That's it! Your memos are saved to ~/.cache/nvim/sm/memos/ by default.
lazy.nvim
{
"Cassin01/sm.nvim",
config = function()
require("sm").setup({
-- your options here
})
end,
}require("sm").setup({
memos_dir = "~/.cache/nvim/sm/memos", -- Where memos are stored
auto_tag_git_repo = true, -- Auto-tag with git repo name
window = {
width = 80,
height = 30,
border = "rounded",
},
})| Option | Default | Description |
|---|---|---|
memos_dir |
~/.cache/nvim/sm/memos |
Directory where memos are stored |
state_file |
~/.cache/nvim/sm/state.json |
JSON file for persistent state (last edited, recent memos) |
date_format |
"%Y%m%d_%H%M%S" |
Format string for timestamp in filenames (see os.date()) |
auto_tag_git_repo |
false |
Auto-tag new memos with git repo name (sanitized: "sm.nvim" β "sm-nvim") |
copilot_integration |
false |
Attach Copilot to memo buffers (requires copilot.vim) |
template |
See below | Template for new memo content (supports %date%, %title%, %tags% placeholders) |
window |
{width=80, height=30, border="rounded", style="minimal"} |
Floating window configuration |
| Command | Description |
|---|---|
:SmNew [title] |
Create new memo (prompts if no title) |
:SmOpen |
Open last edited memo |
:SmAddTag [tag] |
Add tag to current memo |
:SmFollowLink |
Follow wiki link under cursor |
:SmMetaMemo |
Display self-aware memo statistics (easter egg) |
---
tags: [work, ideas]
created: 2026-01-17T14:30:52
---
# Meeting Notes
Link to another memo: [[20260115_project-ideas]]
Your content here...In memo buffers, place your cursor on [[memo-name]] and press gf (or :SmFollowLink) to follow the link. Links match by partial filename (case-insensitive).
sm.nvim provides a picker-agnostic API that works with any picker. See the API reference below.
fzf-lua example
local api = require("sm.api")
local fzf = require("fzf-lua")
local function create_memo_previewer(lookup)
local builtin = require("fzf-lua.previewer.builtin")
local MemoPreview = builtin.buffer_or_file:extend()
function MemoPreview:new(o, opts, fzf_win)
o = o or {}
o.render_markdown = false
MemoPreview.super.new(self, o, opts, fzf_win)
setmetatable(self, MemoPreview)
return self
end
function MemoPreview:parse_entry(entry_str)
local path = lookup[entry_str]
if not path then
return {}
end
return { path = path, line = 1, col = 1 }
end
return MemoPreview
end
-- List and open memos
vim.keymap.set("n", "<Leader>ml", function()
local entries = api.get_memos()
local items = {}
local lookup = {}
for _, entry in ipairs(entries) do
table.insert(items, entry.text)
lookup[entry.text] = entry.value
end
fzf.fzf_exec(items, {
prompt = "Memos> ",
previewer = create_memo_previewer(lookup),
actions = {
["default"] = function(selected)
if selected[1] then
api.open_memo(lookup[selected[1]])
end
end,
},
})
end, { desc = "[sm] List memos" })
-- Grep within memos directory
vim.keymap.set("n", "<Leader>mg", function()
fzf.live_grep({ cwd = api.get_memos_dir() })
end, { desc = "Grep memos" })
-- Browse memos by tag
vim.keymap.set("n", "<Leader>mt", function()
local entries = api.get_tags()
local items = {}
local lookup = {}
for _, entry in ipairs(entries) do
table.insert(items, entry.text)
lookup[entry.text] = entry.value
end
fzf.fzf_exec(items, {
prompt = "Tags> ",
actions = {
["default"] = function(selected)
if selected[1] then
local tag = lookup[selected[1]]
local memo_entries = api.get_memos_by_tag(tag)
local memo_items = {}
local memo_lookup = {}
for _, e in ipairs(memo_entries) do
table.insert(memo_items, e.text)
memo_lookup[e.text] = e.value
end
fzf.fzf_exec(memo_items, {
prompt = "Memos [" .. tag .. "]> ",
previewer = create_memo_previewer(memo_lookup),
actions = {
["default"] = function(sel)
if sel[1] then
api.open_memo(memo_lookup[sel[1]])
end
end,
},
})
end
end,
},
})
end, { desc = "[sm] Browse tags" })
-- Insert wiki-style link
vim.keymap.set("n", "<Leader>mi", function()
local entries = api.get_memos_for_link()
local items = {}
local lookup = {}
for _, entry in ipairs(entries) do
table.insert(items, entry.text)
lookup[entry.text] = entry.value
end
fzf.fzf_exec(items, {
prompt = "Insert Link> ",
actions = {
["default"] = function(selected)
if selected[1] then
api.insert_link(lookup[selected[1]])
end
end,
},
})
end, { desc = "[sm] Insert link" })snacks.nvim example
local api = require("sm.api")
local Snacks = require("snacks")
vim.keymap.set("n", "<Leader>ml", function()
Snacks.picker.pick({
title = "Memos",
items = api.get_memos(),
format = function(item) return item.text end,
on_select = function(item) api.open_memo(item.value) end,
})
end, { desc = "List memos" })mini.pick example
local api = require("sm.api")
local MiniPick = require("mini.pick")
vim.keymap.set("n", "<Leader>ml", function()
local entries = api.get_memos()
MiniPick.start({
source = {
items = entries,
name = "Memos",
show = function(buf_id, items, query)
local lines = vim.tbl_map(function(item) return item.text end, items)
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
end,
choose = function(item) api.open_memo(item.value) end,
},
})
end, { desc = "List memos" })local sm = require("sm")
sm.create(title?) -- Create new memo
sm.open_last() -- Open last edited memo
sm.buf_add_tag(tag?) -- Add tag to current buffer's memo
sm.buf_follow_link() -- Follow wiki link under cursor
sm.list_all_tags() -- Get all tags (for completion)
sm.autocmd_pattern() -- Get pattern for autocommands (BufNewFile/BufRead)local api = require("sm.api")
-- Data functions (return picker-ready entries)
api.get_memos() -- All memos: {value, text, ordinal, info, tags}
api.get_tags() -- All tags: {value, text, ordinal, count}
api.get_memos_by_tag(tag) -- Filtered memos: {value, text, ordinal, info}
api.get_memos_for_link() -- For links: {value, text, ordinal, filepath}
-- Action functions
api.open_memo(filepath) -- Open memo in floating window
api.insert_link(filename) -- Insert [[filename]] at cursor
-- Utility
api.get_memos_dir() -- Get memos directory pathUse autocmd_pattern() with native autocommands to set buffer-local keymaps for memo files:
local group = vim.api.nvim_create_augroup("sm_user_keymaps", { clear = true })
vim.api.nvim_create_autocmd({ "BufNewFile", "BufRead" }, {
group = group,
pattern = require("sm").autocmd_pattern(),
callback = function(args)
local buf = args.buf
vim.keymap.set("n", "<leader>mt", require("sm").buf_add_tag, { buffer = buf, desc = "Add tag" })
vim.keymap.set("n", "<leader>mf", require("sm").buf_follow_link, { buffer = buf, desc = "Follow link" })
end,
})| Event | Trigger |
|---|---|
BufNewFile |
New memo created via create() |
BufRead |
Existing memo opened via api.open_memo(), open_last() |
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This plugin is written in Fennel, a Lisp that compiles to Lua.
Apache-2.0 Β© Cassin01
