A minimal knowledge layer for Markdown in Neovim.
Ma.nvim lets structure emerge from filenames rather than directories. Notes live in a flat filesystem, while hierarchy is inferred through dot-separated segments and hyphenated words.
Inspired by Dendron and Obsidian, Ma provides vault-scoped navigation and safe file operations without imposing a folder-based structure.
Ma (間) is a Japanese concept often translated as interval or space between.
It does not mean emptiness as absence. It refers to meaningful space: the gap that makes relationships possible.
In Ma.nvim, structure is not imposed through folders. It emerges in the intervals:
- Dots define hierarchy.
- Hyphens define words.
- Meaning lives in the space between segments.
Ma also implies time: the pause between actions, the space we take to write, reflect, and connect ideas in plain text.
architecture.norman-foster.apple-piazza-liberty.md
Ma interprets this as a hierarchy:
📁 architecture
└ 📁 norman-foster
└ 📄 apple-piazza-liberty
Required:
- nvim-lua/plenary.nvim
- nvim-telescope/telescope.nvim for navigator and vault picker
Optional:
- nvim-tree/nvim-web-devicons for filetype-aware file icons in the
iconscolumn. If missing, Ma falls back to built-in defaults (for folders,for files). - telescope-fzf-native.nvim for faster picker matching.
- Marksman (Markdown LSP) for
gdon links, semantic link resolution, and document symbols.
Pinning to the latest release tag is recommended.
With lazy.nvim:
{
"gmcusaro/ma.nvim",
version = "*",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-telescope/telescope.nvim",
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
{ "nvim-tree/nvim-web-devicons", opts = {} },
},
opts = {
vaults = {
{ name = "My Brain", path = "~/notes" },
},
},
}require("ma").setup({
vaults = {
{ name = "My Brain", path = "~/notes" },
},
respect_gitignore = true,
autochdir = "lcd",
depth = nil,
delete_to_trash = true,
picker_actions = {
{ "c", "create" },
{ "r", "rename" },
{ "d", "delete" },
},
date_format_frontmatter = "%Y %b %d - %H:%M:%S",
telescope = {},
columns = { "git", "icons" },
sort = { by = "name", order = "asc" },
daily_notes = {
date_format = nil,
locale = nil,
}
})Open the Telescope navigator for the active root. Use it to browse notes as a dot-based tree and run picker actions.
Pick and switch the active vault.
:Ma vaultopens the vault picker and switches the active vault.:Ma vault <name>switches directly to an existing configured vault by name.
When a vault is selected, Ma sets it as active and reopens navigation in that vault. The active root is the currently selected vault path, or Neovim's current working directory if no valid vault is configured.
Use when:
- You keep separate note collections (personal, work, research).
Create or open a note from the command line.
Behavior:
- If the note exists, Ma opens it.
- If it does not exist, Ma prompts for file name, title, and optional description, writes frontmatter, then opens it.
Name handling:
.mdis added automatically if omitted.- Existing
.mdor.markdownsuffixes are normalized and not duplicated. /is normalized to..- Spaces are normalized to
-.
The final segment of the name always creates a file. Ma does not create standalone "folders"; hierarchy exists conceptually through dot-separated segments.
Use when:
- You want a quick create/open flow outside the Telescope picker.
Create or open the daily note. Use it to keep daily journal/log notes.
Builds daily.<formatted-date>.md using daily_notes options.
Rename the current managed note. Works only for markdown files under the active root. This command renames only the current note file.
Use when:
- You want to rename the current note without opening the picker.
Picker folder rename (multi-file):
- In the navigator picker, renaming a folder segment updates all notes sharing that segment prefix.
Example:
- Renaming a folder segment
architecture.foster.apple-piazza-libertyintoarchitecture.norman-foster.apple-piazza-liberty - Also renames notes such as
architecture.foster.30-st-mary-axearchitecture.foster.millennium-bridgeintoarchitecture.norman-foster.30-st-mary-axearchitecture.norman-foster.millennium-bridge
Delete the current note with confirmation.
Works only for managed markdown notes under the active root.
In the navigator picker, multi-select deletion is supported via Telescope multi-select.
Uses trash-first or hard-delete mode (see delete_to_trash).
Create a markdown link from the current visual selection.
Prompts for target note, opens/creates it, and replaces selection with [label](target.md).
The target note name is normalized and lowercased.
Important:
- Run this from visual mode inside a managed markdown note.
Use when:
- You are writing and want fast create-and-link behavior.
Enter: open folder/file.Backspace(normal mode): go up when prompt is empty.-(normal mode): force go up.Ctrl-h(insert mode): force go up.- Action keys come from
picker_actionsin normal mode. - Multi-select + delete is supported in the picker delete action.
List of vault roots Ma can use as note workspaces.
Type:
{ { name?: string, path: string } } | nil
Default:
{}
Behavior:
- Invalid/non-existing paths are ignored
- Duplicate paths are deduplicated.
- Missing/empty
namefalls back to folder basename. - First valid vault becomes active by default.
- If no valid vault exists, Ma.nvim uses current working directory.
Example:
vaults = {
{ name = "Personal", path = "~/notes/personal" },
{ name = "Work", path = "~/notes/work" },
}Important:
- If no valid vault exists, Ma uses Neovim's current working directory.
Use when:
- You want explicit, switchable note roots.
Control whether scans exclude files ignored by .gitignore.
Type:
boolean
Default:
true
Example:
respect_gitignore = falseUse when:
- Set to
falseonly if you intentionally want ignored markdown files included.
Control whether Ma changes Neovim's working directory to the active root before major actions.
Type:
false | "lcd" | "tcd" | "cd"
Default:
"lcd"
Behavior:
false: no directory change."lcd": window-local cwd."tcd": tab-local cwd."cd": global cwd.
Example:
autochdir = "tcd"Important:
- Applied when navigating, linking, creating, and opening daily notes.
Use when:
- You want Ma's root and your relative-path tools to stay aligned.
Maximum directory recursion depth during note scanning.
Type:
integer | nil
Default:
nil
Behavior:
nil: unlimited recursion.- integer: scan only up to that depth.
Example:
depth = 3Use when:
- Your vault is large and you want faster scans.
Choose trash-first deletion or direct deletion.
Type:
boolean
Default:
true
Behavior:
- When true, Ma tries OS trash tools first (
osascripton macOS,gio,trash-put,kioclient5) and falls back to file removal.
Example:
delete_to_trash = trueImportant:
- Even with trash mode, fallback behavior can permanently delete files if no trash backend is available.
Use when:
- You want safer delete operations.
Keybindings available inside the navigator picker.
Type:
{ { string, "create"|"rename"|"delete" } } | { [string]: "create"|"rename"|"delete" } | false
Default:
{
{ "c", "create" },
{ "r", "rename" },
{ "d", "delete" },
}Behavior:
falsedisables picker actions.- List order is preserved.
- Legacy map forms are still accepted.
Example:
picker_actions = {
{ "n", "create" },
{ "x", "delete" },
}Use when:
- You want custom action keys in Telescope.
Date format for created and updated frontmatter fields.
Type:
string
Default:
"%Y %b %d - %H:%M:%S"
Behavior:
Used for created/updated timestamps in frontmatter.
updated is refreshed only if:
- frontmatter exists at top of file
- frontmatter already has an
updated:key
Example:
date_format_frontmatter = "%Y-%m-%d %H:%M"Important:
- Ma updates
updated:only if frontmatter exists at top of file and already contains anupdatedkey.
Use when:
- You need a specific timestamp style across notes.
Override Telescope picker options specifically for Ma.
Type:
table
Default:
{}
Behavior:
- Supported keys are:
prompt_prefix,selection_caret,initial_mode, andlayout_config. initial_modeaccepts only"insert"or"normal".- Other Telescope keys are ignored by Ma.
Supported options are documented in the Telescope README.
Example:
telescope = {
prompt_prefix = " ",
selection_caret = "| ",
initial_mode = "normal",
layout_config = {
prompt_position = "top",
width = 0.9,
height = 0.9,
preview_width = 0.6,
mirror = false,
preview_cutoff = 120,
},
}Use when:
- You want Ma pickers to match your Telescope UX.
Choose which navigator columns are shown and in what order.
Type:
table
Default:
{ "git", "icons" }Behavior:
- Supported column names:
"git","icons". - Order in the array controls display order.
- If
gitcolumn is not active, Ma skips Git status computation.
Use when:
- You want simpler or more informative picker rows.
Nested key inside columns used to override Git status symbols for the git column.
Type:
table
Default:
nil(unset)
Built-in symbols (used when git column is enabled):
{
clean = " ",
modified = "M ",
added = "A ",
deleted = "D ",
renamed = "R ",
copied = "C ",
untracked = "? ",
ignored = "! ",
conflicted = "U ",
unknown = "~ ",
}Behavior:
- Providing
git = {...}auto-enables thegitcolumn if missing. - If you override symbols, statuses not explicitly set may render as blank.
Example:
columns = {
git = {
modified = "✱ ",
untracked = "… ",
conflicted = "‼ ",
},
"icons",
}Nested key inside columns used to customize folder/file icons in the icons column.
Type:
{ folder?: string, file?: string }
Behavior:
icons = {...}auto-enables the icons column if missing.- A non-empty
icons.folderoricons.filevalue is used directly. - Without a custom
foldericon, Ma uses fallback. - Without a custom
fileicon, Ma usesnvim-web-deviconswhen available, else fallback.
Examples:
-- default
columns = { "git", "icons" }
-- custom fixed icons (dependency optional)
columns = {
"git",
icons = {
folder = "F ",
file = "md",
},
}Use when:
- You want explicit icon behavior with or without
nvim-web-devicons.
Sort strategy for entries in each picker level.
Type:
{ by?: "name"|"update"|"creation", order?: "asc"|"desc" }
Default:
{ by = "name", order = "asc" }Behavior:
name: case-insensitive by segment text.update: by file mtime.creation: by birthtime/ctime fallback.- Ties fall back to full note path for deterministic order.
Example:
sort = { by = "update", order = "desc" }Use when:
- You want recency-first or chronology-first navigation.
Enable/disable daily note generation and control date formatting.
Type:
table | false
Default:
{ date_format = nil, locale = nil }Behavior:
false: disables:Ma daily.- table: creates/opens
daily.<date>.md.
Example:
daily_notes = {
date_format = "%Y.%b.%d",
locale = "en_US.UTF-8",
}Example output: daily.2026.Mar.03.md
Use when:
- You maintain date-based daily notes.
Date token pattern used in daily note filename and title.
Type:
string | nil
Default:
"%Y.%b-%d"(when nil)
Behavior:
- Uses Lua
os.date/strftime-style tokens.
Example:
daily_notes = { date_format = "%Y-%m-%d" }Important:
- Since this value becomes part of the note name, separators (
.and-) affect hierarchy and readability.
Use when:
- You want ISO-like names or locale-friendly names.
Locale used while formatting daily note dates.
Type:
string | nil
Default:
nil(current process locale)
Behavior:
- Temporarily applies
os.setlocale(locale, "time"), formats the date, then restores previous locale.
Example:
daily_notes = { locale = "it_IT.UTF-8" }Use when:
- You want month/day names in a specific language.
When creating a new note via :Ma create or :Ma daily,
Ma generates YAML frontmatter with this structure:
---
id: <23-char random id>
title: <title>
desc: <desc>
updated: <formatted timestamp>
created: <formatted timestamp>
---Behavior:
- Frontmatter is written only when a note file is newly created; opening an existing note does not insert or rewrite frontmatter.
createdandupdatedusedate_format_frontmatter.updatedis refreshed onBufWritePrefor managed markdown files under the active root.updatedis only modified if frontmatter exists and contains anupdated:field.- Notes without frontmatter are left untouched.
idis generated automatically and is not currently used for indexing or linking.
All contributions are welcome! Just open a pull request.

