Skip to content

Commit

Permalink
Frontmatter improvements and new features (#36)
Browse files Browse the repository at this point in the history
* Automatically add title as alias to frontmatter

* more improvements

* Close #34
  • Loading branch information
epwalsh authored Oct 14, 2022
1 parent 837c27f commit ce7e483
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 42 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- Added support for arbitrary fields in YAML frontmatter.
- Added configuration option `note_frontmatter_func` for customizing the YAML frontmatter of your notes. This can be set to a function that takes a single argument - an `obsidian.Note` object - and returns a YAML-serializable table.

### Changed

- Added folding and custom highlighting to backlinks window.
- Added folding and custom highlighting to backlinks window, and fixed window height.
- When the title of a note is changed, the title will automatically be added to note's aliases in the frontmatter on save.

### Fixed

- Fixed autocomplete functionality to be less sensitive to case.
- Made YAML frontmatter dumping functionality more robust.

## [v1.5.0](https://github.com/epwalsh/obsidian.nvim/releases/tag/v1.5.0) - 2022-10-12

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,24 @@ require("nvim-treesitter.configs").setup({
},
})
```

#### Customizing the automatically generated YAML frontmatter

By default the auto-generated YAML frontmatter will just contain `id`, `aliases`, and `tags`, as well as any other fields you add manually. If you want to customize this behavior, set the configuration option `note_frontmatter_func` to a function that takes an `obsidian.Note` object and returns a table.

For example, you can emulate the default functionality like this:

```lua
require("obsidian").setup({
dir = "~/my-vault",
note_frontmatter_func = function(note)
local out = { id = note.id, aliases = note.aliases, tags = note.tags }
if note.metadata ~= nil and util.table_length(note.metadata) > 0 then
for k, v in pairs(note.metadata) do
out[k] = v
end
end
return out
end,
})
```
1 change: 1 addition & 0 deletions lua/obsidian/backlinks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ backlinks.view = function(self)
-- Configure buffer.
vim.cmd "setlocal nonu"
vim.cmd "setlocal nornu"
vim.cmd "setlocal winfixheight"
vim.api.nvim_buf_set_option(0, "filetype", "ObsidianBacklinks")
vim.api.nvim_buf_set_option(0, "buftype", "nofile")
vim.api.nvim_buf_set_option(0, "swapfile", false)
Expand Down
2 changes: 2 additions & 0 deletions lua/obsidian/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local config = {}
---@field dir string
---@field notes_subdir string|?
---@field note_id_func function|?
---@field note_frontmatter_func function|?
---@field completion obsidian.config.CompletionOpts
---@field daily_notes obsidian.config.DailyNotesOpts
config.ClientOpts = {}
Expand All @@ -17,6 +18,7 @@ config.ClientOpts.default = function()
dir = vim.fs.normalize "./",
notes_subdir = nil,
note_id_func = nil,
note_frontmatter_func = nil,
completion = config.CompletionOpts.default(),
daily_notes = config.DailyNotesOpts.default(),
}
Expand Down
8 changes: 6 additions & 2 deletions lua/obsidian/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,12 @@ obsidian.setup = function(opts)
local bufnr = vim.api.nvim_get_current_buf()
local note = obsidian.note.from_buffer(bufnr, self.dir)
if note:should_save_frontmatter() then
local lines = note:frontmatter_lines()
vim.api.nvim_buf_set_lines(bufnr, 0, 0, true, lines)
local frontmatter = nil
if self.opts.note_frontmatter_func ~= nil then
frontmatter = self.opts.note_frontmatter_func(note)
end
local lines = note:frontmatter_lines(nil, frontmatter)
vim.api.nvim_buf_set_lines(bufnr, 0, note.frontmatter_end_line and note.frontmatter_end_line or 0, false, lines)
echo.info "Updated frontmatter"
end
end,
Expand Down
101 changes: 68 additions & 33 deletions lua/obsidian/note.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local Path = require "plenary.path"
local yaml = require "yaml"
local yaml = require "obsidian.yaml"
local util = require "obsidian.util"
local echo = require "obsidian.echo"

Expand All @@ -10,7 +10,9 @@ local SKIP_UPDATING_FRONTMATTER = { "README.md", "CONTRIBUTING.md", "CHANGELOG.m
---@field aliases string[]
---@field tags string[]
---@field path Path|?
---@field metadata table|?
---@field has_frontmatter boolean|?
---@field frontmatter_end_line integer|?
local note = {}

---Create new note.
Expand All @@ -26,6 +28,9 @@ note.new = function(id, aliases, tags, path)
self.aliases = aliases and aliases or {}
self.tags = tags and tags or {}
self.path = path and Path:new(path) or nil
self.metadata = nil
self.has_frontmatter = nil
self.frontmatter_end_line = nil
return self
end

Expand All @@ -50,7 +55,7 @@ end

note.should_save_frontmatter = function(self)
local fname = self:fname()
return (fname ~= nil and not util.contains(SKIP_UPDATING_FRONTMATTER, fname) and not self.has_frontmatter)
return (fname ~= nil and not util.contains(SKIP_UPDATING_FRONTMATTER, fname))
end

---Check if a note has a given alias.
Expand Down Expand Up @@ -163,6 +168,7 @@ note.from_lines = function(lines, path, root)
-- Iterate over lines in the file, collecting frontmatter and parsing the title.
local frontmatter_lines = {}
local has_frontmatter, in_frontmatter = false, false
local frontmatter_end_line = nil
local line_idx = 0
for line in lines() do
line_idx = line_idx + 1
Expand All @@ -180,6 +186,7 @@ note.from_lines = function(lines, path, root)
elseif has_frontmatter and in_frontmatter then
if note._is_frontmatter_boundary(line) then
in_frontmatter = false
frontmatter_end_line = line_idx
else
table.insert(frontmatter_lines, line)
end
Expand All @@ -198,18 +205,24 @@ note.from_lines = function(lines, path, root)
end

-- Parse the frontmatter YAML.
local metadata = nil
if #frontmatter_lines > 0 then
local frontmatter = table.concat(frontmatter_lines, "\n")
local ok, data = pcall(yaml.eval, frontmatter)
local ok, data = pcall(yaml.loads, frontmatter)
if ok then
if data.id then
id = data.id
end
if data.aliases then
aliases = data.aliases
end
if data.tags then
tags = data.tags
for k, v in pairs(data) do
if k == "id" then
id = v
elseif k == "aliases" then
aliases = v
elseif k == "tags" then
tags = v
else
if metadata == nil then
metadata = {}
end
metadata[k] = v
end
end
end
end
Expand All @@ -225,7 +238,9 @@ note.from_lines = function(lines, path, root)
end

local n = note.new(id, aliases, tags, path)
n.metadata = metadata
n.has_frontmatter = has_frontmatter
n.frontmatter_end_line = frontmatter_end_line
return n
end

Expand All @@ -245,29 +260,51 @@ note._parse_header = function(line)
return line:match "^#+ (.+)$"
end

---Get the frontmatter table to save.
---@return table
note.frontmatter = function(self)
local out = { id = self.id, aliases = self.aliases, tags = self.tags }
if self.metadata ~= nil and util.table_length(self.metadata) > 0 then
for k, v in pairs(self.metadata) do
out[k] = v
end
end
return out
end

---Get frontmatter lines that can be written to a buffer.
---
---@param eol boolean|?
---@param frontmatter table|?
---@return string[]
note.frontmatter_lines = function(self, eol)
local new_lines = { "---", ("id: %q"):format(self.id) }

if #self.aliases > 0 then
table.insert(new_lines, "aliases:")
else
table.insert(new_lines, "aliases: []")
end
for _, alias in pairs(self.aliases) do
table.insert(new_lines, (" - %q"):format(alias))
end

if #self.tags > 0 then
table.insert(new_lines, "tags:")
else
table.insert(new_lines, "tags: []")
end
for _, tag in pairs(self.tags) do
table.insert(new_lines, (" - %q"):format(tag))
note.frontmatter_lines = function(self, eol, frontmatter)
local new_lines = { "---" }

local frontmatter_ = frontmatter and frontmatter or self:frontmatter()
for _, line in
ipairs(yaml.dumps_lines(frontmatter_, function(a, b)
local a_idx = nil
local b_idx = nil
for i, k in ipairs { "id", "aliases", "tags" } do
if a == k then
a_idx = i
end
if b == k then
b_idx = i
end
end
if a_idx ~= nil and b_idx ~= nil then
return a_idx < b_idx
elseif a_idx ~= nil then
return true
elseif b_idx ~= nil then
return false
else
return a < b
end
end))
do
table.insert(new_lines, line)
end

table.insert(new_lines, "---")
Expand Down Expand Up @@ -303,7 +340,7 @@ note.save = function(self, path)
local self_f = io.open(tostring(self.path))
if self_f ~= nil then
local contents = self_f:read "*a"
for idx, line in pairs(vim.split(contents, "\n")) do
for idx, line in ipairs(vim.split(contents, "\n")) do
table.insert(lines, line .. "\n")
if idx == 1 then
if note._is_frontmatter_boundary(line) then
Expand All @@ -315,8 +352,6 @@ note.save = function(self, path)
end_idx = idx
in_frontmatter = false
end
else
break
end
end
self_f:close()
Expand Down
38 changes: 38 additions & 0 deletions lua/obsidian/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,42 @@ util.find_and_replace_refs = function(s, patterns)
return table.concat(pieces, ""), indices, refs
end

util.is_array = function(t)
if type(t) ~= "table" then
return false
end

--check if all the table keys are numerical and count their number
local count = 0
for k, _ in pairs(t) do
if type(k) ~= "number" then
return false
else
count = count + 1
end
end

--all keys are numerical. now let's see if they are sequential and start with 1
for i = 1, count do
--Hint: the VALUE might be "nil", in that case "not t[i]" isn't enough, that's why we check the type
if not t[i] and type(t[i]) ~= "nil" then
return false
end
end
return true
end

util.strip = function(s)
local out = string.gsub(s, "^%s+", "")
return out
end

util.table_length = function(x)
local n = 0
for _ in pairs(x) do
n = n + 1
end
return n
end

return util
Loading

0 comments on commit ce7e483

Please sign in to comment.