Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
95b52bb
cache init commit
kostabekre Apr 27, 2025
10dfbac
rejected using sqlite, added basic vault indexing, filewatch
kostabekre May 4, 2025
7f7a3eb
A note can be searched by alias
kostabekre May 5, 2025
95854da
Merge remote-tracking branch 'upstream/main'
kostabekre May 6, 2025
e334d68
Updated the filewatch
kostabekre May 6, 2025
c109e6d
working version
kostabekre May 9, 2025
921ef52
Merge remote-tracking branch 'upstream/main'
kostabekre May 9, 2025
aa48f82
last todo
kostabekre May 9, 2025
dda1188
updated read me, fixed test
kostabekre May 9, 2025
3037db7
fix readme
kostabekre May 9, 2025
e8052e7
Updated changelog
kostabekre May 9, 2025
043ec03
moved changes in changelog to unreleased
kostabekre May 9, 2025
a39d9d1
removed 3.12 from changelog
kostabekre May 9, 2025
b80f913
added semicolon
kostabekre May 9, 2025
8e3df02
Updated config option to enable cache
kostabekre May 11, 2025
a16912e
Squash merge add-cache into main
kostabekre May 11, 2025
b496be5
code update
kostabekre May 11, 2025
d19bd80
Merge branch 'add-cache'
kostabekre May 11, 2025
7da49dd
Added log when a note is updated as a warning
kostabekre May 11, 2025
0a7f300
Merge branch 'main' into add-cache
kostabekre May 11, 2025
9dc7df8
Added a test warning
kostabekre May 11, 2025
f7f2aa5
checking get_cache_notes for error
kostabekre May 13, 2025
9714bc3
more description of errors
kostabekre May 13, 2025
64f914e
fix error
kostabekre May 13, 2025
2a2a40d
changed log warning to debug
kostabekre May 19, 2025
6d06a65
Merge branch 'add-cache' into develop
kostabekre May 21, 2025
bd14b1a
temp changes
kostabekre May 21, 2025
8d80abb
file watch sends multiple files but didn't test
kostabekre May 22, 2025
040e766
Fixed errors
kostabekre Jun 2, 2025
d353656
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jun 2, 2025
2a37f1d
Updated Cache config
kostabekre Jun 2, 2025
b19e938
Added the file handle for root on Linux
kostabekre Jun 15, 2025
9da52d3
github code review changes
kostabekre Jun 15, 2025
a5f58d7
Merge branch 'add-cache'
kostabekre Jun 15, 2025
9dda489
Merge remote-tracking branch 'upstream/main'
kostabekre Jun 15, 2025
5bef2a6
github code review
kostabekre Jun 15, 2025
8084864
Merge remote-tracking branch 'upstream/main'
kostabekre Jun 23, 2025
ace8863
Merge branch 'main' into add-cache
kostabekre Jun 25, 2025
593d6a1
added tags, fixed bug with plenary (too many files), style fix
kostabekre Jun 25, 2025
e29f7ad
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jun 25, 2025
78023b5
Apply suggestions from code review
neo451 Jun 25, 2025
d3b5366
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jun 25, 2025
ea8bfa6
Merge branch 'add-cache' of github.com:kostabekre/obsidian.nvim into …
kostabekre Jun 25, 2025
2721082
Code review changes
kostabekre Jun 25, 2025
151d792
fix update errors
kostabekre Jun 25, 2025
2aec269
used joinpath for cross platform, added an option to show or hide tag…
kostabekre Jun 26, 2025
75c4cd9
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jul 6, 2025
e7cdbd2
fix: dropped abc in cache, cache doesn't use client
kostabekre Jul 6, 2025
53d167a
fix: forgot to use Obsidian.opts in quick_switch
kostabekre Jul 6, 2025
6992c30
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jul 12, 2025
0233ee6
A lock file is created for a workspace, when enabling filewatch
kostabekre Jul 16, 2025
d4d1931
.gitignore or .ignore is respected in filewatch and cache
kostabekre Jul 20, 2025
6dd1a7b
lock file fix: use pid to check if the neovim isntance, which created…
kostabekre Jul 20, 2025
65a8cc1
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Jul 20, 2025
f43d2dc
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Aug 1, 2025
b52a176
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Aug 12, 2025
9ead762
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Aug 13, 2025
ea5af9f
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Aug 17, 2025
5fe0083
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Aug 25, 2025
3a6ce80
moved lock file to the state folder, set the workspace based on the c…
kostabekre Sep 1, 2025
89dbe34
moved lock file from state to state/obsidian
kostabekre Sep 1, 2025
df75256
Merge remote-tracking branch 'upstream/main' into add-cache
kostabekre Sep 1, 2025
a46c848
use obsidian.async
kostabekre Sep 1, 2025
c5e8ba8
disable warning that a string should be passed instead of a number wh…
kostabekre Sep 1, 2025
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fixed types in `_snacks.lua`

## [v3.12.0](https://github.com/obsidian-nvim/obsidian.nvim/releases/tag/v3.11.0)

### Added

- Added file-watch: code that tracks changed notes in the vault.
- Added cache: a JSON file, which stores aliases, last update date and path to the note. Updated using file-watch.
- Added a new configuration option - `cache`, which is disabled by default.

### Changed

- In telescope an option was added to search notes by aliases.

## [v3.11.0](https://github.com/obsidian-nvim/obsidian.nvim/releases/tag/v3.11.0) - 2025-05-04

### Added
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ These default keymaps will only be set if you are in a valid workspace and a mar

- `:Obsidian yesterday` to open/create the daily note for the previous working day.

- `:Obsidian index_vault` to manually updated the cache of the vault.

### Demo

[![2024-01-31 14 22 52](https://github.com/epwalsh/obsidian.nvim/assets/8812459/2986e1d2-13e8-40e2-9c9e-75691a3b662e)](https://github.com/epwalsh/obsidian.nvim/assets/8812459/2986e1d2-13e8-40e2-9c9e-75691a3b662e)
Expand All @@ -134,6 +136,9 @@ These default keymaps will only be set if you are in a valid workspace and a mar
- **MacOS** users need [`pngpaste`](https://github.com/jcsalterego/pngpaste) (`brew install pngpaste`) for `:Obsidian paste_img`.
- **Linux** users need xclip (X11) or wl-clipboard (Wayland) for `:Obsidian paste_img`.

- Optional dependencies:
- Backend: [fd](https://github.com/sharkdp/fd), see [fd#installation](https://github.com/sharkdp/fd#installation) for `:Obsidian index_vault`

### Plugin dependencies

The only **required** plugin dependency is [plenary.nvim](https://github.com/nvim-lua/plenary.nvim), but there are a number of optional dependencies that enhance the obsidian.nvim experience.
Expand Down Expand Up @@ -603,6 +608,12 @@ require("obsidian").setup {
enabled = true,
format = "{{properties}} properties {{backlinks}} backlinks {{words}} words {{chars}} chars",
},

-- Experimental feature, disabled by default.
cache = {
use_cache = false,
cache_path = "./.cache.json",
}
}
```

Expand Down
10 changes: 6 additions & 4 deletions doc/obsidian_api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Class ~
Fields ~
{current_workspace} `(obsidian.Workspace)` The current workspace.
{dir} `(obsidian.Path)` The root of the vault for the current workspace.
{cache} `(obsidian.Cache)`
{opts} `(obsidian.config.ClientOpts)` The client config.
{buf_dir} `(obsidian.Path|? The)` parent directory of the current buffer.
{callback_manager} `(obsidian.CallbackManager)`
Expand Down Expand Up @@ -834,10 +835,11 @@ Return ~
Class ~
{obsidian.note.LoadOpts}
Fields ~
{max_lines} `(integer|)`?
{load_contents} `(boolean|)`?
{collect_anchor_links} `(boolean|)`?
{collect_blocks} `(boolean|)`?
{max_lines} `(integer|? Stops)` reading file if reached max lines.
{read_only_frontmatter} `(boolean|? Stops)` reading file if reached end of frontmatter.
{load_contents} `(boolean|? Save)` contents of the file if not frontmatter or block to obsidian.Note.contents. Still reads whole file if other options are true.
{collect_anchor_links} `(boolean|? Save)` anchor_links of the file if not in frontmatter or block to obsidian.Note.anchor_links. Still reads whole file if other options are true.
{collect_blocks} `(boolean|? Save)` code blocks to obsidian.Note.blocks. Still reads whole file if other options are true.

------------------------------------------------------------------------------
*obsidian.Note.from_file()*
Expand Down
282 changes: 282 additions & 0 deletions lua/obsidian/cache.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
local abc = require "obsidian.abc"
local search = require "obsidian.search"
local Note = require "obsidian.note"
local log = require "obsidian.log"
local EventTypes = require("obsidian.filewatch").EventTypes
local uv = vim.uv

---This class allows you to find the notes in your vault more quickly.
---It scans your vault and saves the founded metadata to the default cache file ".cache.json"
---in the root of your vault or in the path you specified.
---For example, this allows to search for alises of your
---notes in much shorter time.
---@class obsidian.Cache : obsidian.ABC
---
---@field client obsidian.Client
local Cache = abc.new_class()

---Contains some information from the metadata of your note plus additional info.
---@class obsidian.cache.CacheNote
---
---@field absolute_path string The full path to the note.
---@field relative_path string The relative path to the root of the vault.
---@field aliases string[] The alises of the note founded in the frontmatter.
---@field last_updated number The last time the note was updated in seconds since epoch.

---Converts the links to json and saves to the file at the given path.
---@param links obsidian.cache.CacheNote[]
---@param note_path string|obsidian.Note
local save_cache_notes_to_file = function(links, note_path)
local save_path

if type(note_path) == "obsidian.Note" then
save_path = note_path.path.filename
else
save_path = note_path
end

local file, err = io.open(save_path, "w")

if file then
file:write(vim.fn.json_encode(links))
file:close()
else
error(table.concat { "Couldn't write vault index to the file: ", save_path, ". Description: ", err })
end
end

---@param self obsidian.Cache
---@return fun (absolute_path: string, event_type: obsidian.filewatch.EventType, stat: uv.fs_stat.result)
local create_on_file_change_callback = function(self)
return function(filename, event_type, stat)
vim.schedule(function()
local founded = false

local links = self:get_cache_notes_from_file()
for i, v in ipairs(links) do
if v.absolute_path == filename then
if event_type == EventTypes.deleted then
table.remove(links, i)
else
local note = Note.from_file(filename, { read_only_frontmatter = true })

local relative_path = note.path.filename:gsub(self.client.dir.filename .. "/", "")

links[i] = {
absolute_path = filename,
aliases = note.aliases,
relative_path = relative_path,
last_updated = stat.mtime.sec,
}
end

founded = true
break
end
end

if not founded then
-- Unknown file that was deleted is not in the cache, so we don't need to do anything.
if event_type == EventTypes.deleted then
return
end

local new_note = Note.from_file(filename, { read_only_frontmatter = true })

local relative_path = new_note.path.filename:gsub(self.client.dir.filename .. "/", "")

local new_cache = {
absolute_path = filename,
aliases = new_note.aliases,
relative_path = relative_path,
last_updated = stat.mtime.sec,
}

table.insert(links, new_cache)
end

save_cache_notes_to_file(links, self.client.opts.cache.cache_path)
end)
end
end

---Checks for note cache that were updated outside the vault
---@param self obsidian.Cache
local check_cache_notes_are_fresh = function(self)
local note_cache_list = self:get_cache_notes_from_file()

local completed = 0
local total = #note_cache_list
local updated = {}
local on_done = function()
completed = completed + 1

if completed == total then
vim.schedule(function()
save_cache_notes_to_file(updated, self.client.opts.cache.cache_path)
end)
end
end

for _, note_cache in ipairs(note_cache_list) do
uv.fs_stat(note_cache.absolute_path, function(err, stat)
if err then
err("Couldn't get stat from the file " .. note_cache.relative_path .. " when performing reindex: " .. err)
end

local aliases
if note_cache.last_updated ~= stat.mtime.sec then
local note = Note.from_file(note_cache.absolute_path, { read_only_frontmatter = true })
aliases = note.aliases
else
aliases = note_cache.aliases
end

---@type obsidian.cache.CacheNote
table.insert(updated, {
absolute_path = note_cache.absolute_path,
last_updated = stat.mtime.sec,
aliases = aliases,
relative_path = note_cache.relative_path,
})

on_done()
end)
end
end

---Checks that file exits
---@param path string
---@param callback fun (result: boolean)
local check_file_exists = function(path, callback)
uv.fs_stat(path, function(err, _)
if not err then
callback(true)
else
callback(false)
end
end)
end

---Watches the vault for changes.
---@param self obsidian.Cache
local enable_filewatch = function(self)
local handlers = require("obsidian.filewatch").watch(self.client.dir.filename, create_on_file_change_callback(self))

vim.api.nvim_create_autocmd({ "QuitPre", "ExitPre" }, {
callback = function()
for _, handle in ipairs(handlers) do
if handle then
handle:stop()
if not handle.is_closing then
handle:close()
end
end
end
end,
})
end

---@param self obsidian.Cache
local check_vault_cache = function(self)
check_file_exists(self.client.opts.cache.cache_path, function(exists)
if exists then
vim.schedule(function()
check_cache_notes_are_fresh(self)
end)
else
vim.schedule(function()
self:index_vault()
end)
end
end)
end

---@param client obsidian.Client
Cache.new = function(client)
local self = Cache.init()
self.client = client

if client.opts.cache.use_cache then
enable_filewatch(self)

check_vault_cache(self)
end

return self
end

--- Reads all notes in the vaults and returns the founded data.
---@param client obsidian.Client
---@return obsidian.cache.CacheNote[]
local get_cache_notes_from_vault = function(client)
local interator = search.find(client.dir, "", nil)

---@type obsidian.cache.CacheNote[]
local created_note_caches = {}

local notepath = interator()

--TODO add indexing progress
while notepath do
local note = Note.from_file(notepath, { read_only_frontmatter = true })

local absolute_path = note.path.filename
local relative_path = absolute_path:gsub(client.dir.filename .. "/", "")

local file_stat = uv.fs_stat(absolute_path)
local last_updated

if type(file_stat) ~= "table" then
log.err(table.concat { "couldn't get file stat from file ", absolute_path })
last_updated = 0
else
last_updated = file_stat.mtime.sec
end

---@type obsidian.cache.CacheNote
local note_cache = {
absolute_path = absolute_path,
aliases = note.aliases,
relative_path = relative_path,
last_updated = last_updated,
}

table.insert(created_note_caches, note_cache)

notepath = interator()
end

return created_note_caches
end

--- Reads all notes in the vaults and saves them to the cache file.
---@param self obsidian.Cache
Cache.index_vault = function(self)
if not self.client.opts.cache.use_cache then
log.error "The cache is disabled. Cannot index vault."
end

local founded_links = get_cache_notes_from_vault(self.client)

save_cache_notes_to_file(founded_links, self.client.opts.cache.cache_path)

log.info "Vault was indexed succesfully."
end

---Reads the cache file from client.opts.cache.cache_path and returns founded note cache.
---@param self obsidian.Cache
---@return obsidian.cache.CacheNote[]
Cache.get_cache_notes_from_file = function(self)
local file, err = io.open(self.client.opts.cache.cache_path, "r")

if file then
local links_json = file:read()
file:close()
return vim.fn.json_decode(links_json)
else
error("couldn't read vault index from file: " .. err)
end
end

return Cache
3 changes: 3 additions & 0 deletions lua/obsidian/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ end
---
---@field current_workspace obsidian.Workspace The current workspace.
---@field dir obsidian.Path The root of the vault for the current workspace.
---@field cache obsidian.Cache
---@field opts obsidian.config.ClientOpts The client config.
---@field buf_dir obsidian.Path|? The parent directory of the current buffer.
---@field callback_manager obsidian.CallbackManager
Expand Down Expand Up @@ -98,6 +99,8 @@ Client.new = function(opts)

self:set_workspace(workspace)

self.cache = require("obsidian.cache").new(self)

return self
end

Expand Down
5 changes: 5 additions & 0 deletions lua/obsidian/commands/index_vault.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---@param client obsidian.Client
---@param data CommandArgs
return function(client, data)
client.cache:index_vault()
end
3 changes: 3 additions & 0 deletions lua/obsidian/commands/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ local cmds = {
"open",
"paste_img",
"quick_switch",
"index_vault",
"rename",
"search",
"tags",
Expand Down Expand Up @@ -225,6 +226,8 @@ M.register("new_from_template", { nargs = "*" })

M.register("quick_switch", { nargs = "?" })

M.register("index_vault", { nargs = "?" })

M.register("link_new", { nargs = "?", range = true })

M.register("link", { nargs = "?", range = true, complete = M.note_complete })
Expand Down
Loading
Loading