Skip to content

Commit

Permalink
Implement roam origin (#19)
Browse files Browse the repository at this point in the history
* Implement roam origin functionality
* Add origin as option to node api
* Update expr_under_cursor, link_under_cursor, and node_under_cursor to support explicit window
* Add open config option for node view
* Update select-node to take exclusion list
* Update add_origin to exclude self
* Update node view to support navigating to origin
* Add goto_next_node and goto_prev_node api methods
  • Loading branch information
chipsenkbeil authored Apr 14, 2024
1 parent 8065e6d commit 8772079
Show file tree
Hide file tree
Showing 20 changed files with 671 additions and 143 deletions.
326 changes: 252 additions & 74 deletions DOCS.org

Large diffs are not rendered by default.

28 changes: 16 additions & 12 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,22 @@

** Bindings

| Name | Keybinding | Description |
|--------------------------+---------------+-------------------------------------------------------------------------|
| add_alias | =<Leader>naa= | Adds an alias to the node under cursor. |
| capture | =<Leader>nc= | Opens org-roam capture window. |
| complete_at_point | =<Leader>n.= | Completes the node under cursor. |
| find_node | =<Leader>nf= | Finds node and moves to it, creating it if it does not exist. |
| insert_node | =<Leader>ni= | Inserts node at cursor position, creating it if it does not exist. |
| insert_node_immediate | =<Leader>nm= | Same as =insert_node=, but skips opening capture buffer. |
| quickfix_backlinks | =<Leader>nq= | Opens the quickfix menu for backlinks to the current node under cursor. |
| remove_alias | =<Leader>nar= | Removes an alias from the node under cursor. |
| toggle_roam_buffer | =<Leader>nl= | Toggles the org-roam node-view buffer for the node under cursor. |
| toggle_roam_buffer_fixed | =<Leader>nb= | Toggles a fixed org-roam node-view buffer for a selected node. |
| Name | Keybinding | Description |
|--------------------------+---------------+---------------------------------------------------------------------------|
| add_alias | =<Leader>naa= | Adds an alias to the node under cursor. |
| add_origin | =<Leader>noa= | Adds an origin to the node under cursor. |
| capture | =<Leader>nc= | Opens org-roam capture window. |
| complete_at_point | =<Leader>n.= | Completes the node under cursor. |
| find_node | =<Leader>nf= | Finds node and moves to it, creating it if it does not exist. |
| goto_next_node | =<Leader>nn= | Goes to the next node in sequence (via origin) for the node under cursor. |
| goto_prev_node | =<Leader>np= | Goes to the prev node in sequence (via origin) for the node under cursor. |
| insert_node | =<Leader>ni= | Inserts node at cursor position, creating it if it does not exist. |
| insert_node_immediate | =<Leader>nm= | Same as =insert_node=, but skips opening capture buffer. |
| quickfix_backlinks | =<Leader>nq= | Opens the quickfix menu for backlinks to the current node under cursor. |
| remove_alias | =<Leader>nar= | Removes an alias from the node under cursor. |
| remove_origin | =<Leader>nor= | Removes the origin from the node under cursor. |
| toggle_roam_buffer | =<Leader>nl= | Toggles the org-roam node-view buffer for the node under cursor. |
| toggle_roam_buffer_fixed | =<Leader>nb= | Toggles a fixed org-roam node-view buffer for a selected node. |

** Documentation

Expand Down
5 changes: 5 additions & 0 deletions lua/org-roam/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@
local AliasApi = require("org-roam.api.alias")
local CompletionApi = require("org-roam.api.completion")
local NodeApi = require("org-roam.api.node")
local OriginApi = require("org-roam.api.origin")
local open_quickfix = require("org-roam.ui.quickfix")
local open_node_view = require("org-roam.ui.node-view")

---@class org-roam.Api
local M = {}

M.add_alias = AliasApi.add_alias
M.add_origin = OriginApi.add_origin
M.capture_node = NodeApi.capture
M.complete_node = CompletionApi.complete_node_under_cursor
M.find_node = NodeApi.find
M.goto_next_node = OriginApi.goto_next_node
M.goto_prev_node = OriginApi.goto_prev_node
M.insert_node = NodeApi.insert
M.open_node_buffer = open_node_view
M.open_quickfix_list = open_quickfix
M.remove_alias = AliasApi.remove_alias
M.remove_origin = OriginApi.remove_origin

return M
36 changes: 23 additions & 13 deletions lua/org-roam/api/node.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ end

---Construct org-roam template with custom expansions applied.
---@param template_opts OrgCaptureTemplateOpts
---@param opts? {title?:string}
---@param opts? {origin?:string, title?:string}
---@return OrgCaptureTemplate
local function build_template(template_opts, opts)
opts = opts or {}
Expand Down Expand Up @@ -134,9 +134,14 @@ local function build_template(template_opts, opts)
local prefix = {
":PROPERTIES:",
":ID: " .. require('orgmode.org.id').new(),
":END:",
}

if opts.origin then
table.insert(prefix, ":ROAM_ORIGIN: " .. opts.origin)
end

table.insert(prefix, ":END:")

-- Grab the title, which if it does not exist and we detect
-- that we need it, we will prompt for it
local title = opts.title
Expand Down Expand Up @@ -166,7 +171,7 @@ local function build_template(template_opts, opts)
end

---Construct org-roam templates with custom expansions applied.
---@param opts? {title?:string}
---@param opts? {origin?:string, title?:string}
---@return OrgCaptureTemplates
local function build_templates(opts)
opts = opts or {}
Expand Down Expand Up @@ -229,7 +234,7 @@ end

---Creates a node if it does not exist, and restores the current window
---configuration upon completion.
---@param opts? {immediate?:boolean, title?:string}
---@param opts? {immediate?:boolean, origin?:string, title?:string}
---@param cb? fun(id:org-roam.core.database.Id|nil)
function M.capture(opts, cb)
opts = opts or {}
Expand All @@ -238,7 +243,10 @@ function M.capture(opts, cb)
if opts.immediate then
M.__capture_immediate(opts, cb)
else
local templates = build_templates({ title = opts.title })
local templates = build_templates({
origin = opts.origin,
title = opts.title,
})
local on_pre_refile = make_on_pre_refile(opts)
local on_post_refile = make_on_post_refile(cb)
db:files():next(function(files)
Expand All @@ -256,15 +264,13 @@ function M.capture(opts, cb)
end

---@private
---@param opts {title?:string}
---@param opts {origin:string|nil, title:string|nil}
---@param cb fun(id:org-roam.core.database.Id|nil)
function M.__capture_immediate(opts, cb)
local template = build_template({
target = CONFIG.immediate.target,
template = CONFIG.immediate.template,
}, {
title = opts.title,
})
}, opts)

---@param content string[]|nil
template:compile():next(function(content)
Expand Down Expand Up @@ -320,7 +326,7 @@ end
---If `ranges` is provided, will replace the given ranges within the buffer
---versus inserting at point.
---where everything uses 1-based indexing and inclusive.
---@param opts? {immediate?:boolean, title?:string, ranges?:org-roam.utils.Range[]}
---@param opts? {immediate?:boolean, origin?:string, title?:string, ranges?:org-roam.utils.Range[]}
function M.insert(opts)
opts = opts or {}
local winnr = vim.api.nvim_get_current_win()
Expand Down Expand Up @@ -383,7 +389,11 @@ function M.insert(opts)
return
end

M.capture({ title = node.label, immediate = opts.immediate }, function(id)
M.capture({
immediate = opts.immediate,
origin = opts.origin,
title = node.label,
}, function(id)
if id then
insert_link(id)
return
Expand All @@ -393,7 +403,7 @@ function M.insert(opts)
end

---Creates a node if it does not exist, and visits the node.
---@param opts? {title?:string}
---@param opts? {origin?:string, title?:string}
function M.find(opts)
opts = opts or {}
local winnr = vim.api.nvim_get_current_win()
Expand Down Expand Up @@ -423,7 +433,7 @@ function M.find(opts)
return
end

M.capture({ title = node.label }, function(id)
M.capture({ origin = opts.origin, title = node.label }, function(id)
if id then
visit_node(id)
return
Expand Down
127 changes: 127 additions & 0 deletions lua/org-roam/api/origin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
-------------------------------------------------------------------------------
-- ORIGIN.LUA
--
-- Contains functionality tied to the roam origin api.
-------------------------------------------------------------------------------

local db = require("org-roam.database")
local select_node = require("org-roam.ui.select-node")
local utils = require("org-roam.utils")

local ORIGIN_PROP_NAME = "ROAM_ORIGIN"

local M = {}

---Adds an origin to the node under cursor.
---Will replace the existing origin.
---
---If no `origin` is specified, a prompt is provided.
---
---@param opts? {origin?:string}
function M.add_origin(opts)
opts = opts or {}
utils.node_under_cursor(function(node)
if not node then return end

db:load_file({ path = node.file }):next(function(results)
-- Get the OrgFile instance
local file = results.file

-- Look for a file or headline that matches our node
local entry = utils.find_id_match(file, node.id)

if entry and opts.origin then
entry:set_property(ORIGIN_PROP_NAME, opts.origin)
elseif entry then
-- If no origin specified, we load up a selection dialog
-- to pick a node other than the current one
select_node({ exclude = { node.id } }, function(selection)
if selection.id then
entry:set_property(ORIGIN_PROP_NAME, selection.id)
end
end)
end

return file
end)
end)
end

---Goes to the previous node in sequence for the node under cursor.
---Leverages a lookup of the node using the origin of the node under cursor.
---@param opts? {win?:integer}
function M.goto_prev_node(opts)
opts = opts or {}
local winnr = opts.win or vim.api.nvim_get_current_win()

---@param node org-roam.core.file.Node|nil
local function goto_node(node)
if not node then return end
utils.goto_node({ node = node, win = winnr })
end

utils.node_under_cursor(function(node)
if not node or not node.origin then return end
db:get(node.origin):next(goto_node)
end, { win = winnr })
end

---Goes to the next node in sequence for the node under cursor.
---Leverages a lookup of nodes whose origin match the node under cursor.
---@param opts? {win?:integer}
function M.goto_next_node(opts)
opts = opts or {}
local winnr = opts.win or vim.api.nvim_get_current_win()

---@param node org-roam.core.file.Node|nil
local function goto_node(node)
if not node then return end
utils.goto_node({ node = node, win = winnr })
end

utils.node_under_cursor(function(node)
if not node then return end
db:find_nodes_by_origin(node.id):next(function(nodes)
if #nodes == 0 then return nodes end
if #nodes == 1 then
goto_node(nodes[1])
return nodes
end

local ids = vim.tbl_map(function(n)
return n.id
end, nodes)

select_node({ include = ids }, function(selection)
if selection.id then
db:get(selection.id):next(goto_node)
end
end)

return nodes
end)
end, { win = winnr })
end

---Removes the origin from the node under cursor.
function M.remove_origin()
utils.node_under_cursor(function(node)
if not node then return end

db:load_file({ path = node.file }):next(function(results)
-- Get the OrgFile instance
local file = results.file

-- Look for a file or headline that matches our node
local entry = utils.find_id_match(file, node.id)

if entry then
entry:set_property(ORIGIN_PROP_NAME, nil)
end

return file
end)
end)
end

return M
20 changes: 20 additions & 0 deletions lua/org-roam/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ local config = setmetatable({
---Adds an alias to the node under cursor.
add_alias = "<Leader>naa",

---Adds an origin to the node under cursor.
add_origin = "<Leader>noa",

---Opens org-roam capture window.
capture = "<Leader>nc",

Expand All @@ -60,6 +63,15 @@ local config = setmetatable({
---Finds node and moves to it.
find_node = "<Leader>nf",

---Goes to the next node sequentially based on origin of the node under cursor.
---
---If more than one node has the node under cursor as its origin, a selection
---dialog is displayed to choose the node.
goto_next_node = "<Leader>nn",

---Goes to the previous node sequentially based on origin of the node under cursor.
goto_prev_node = "<Leader>np",

---Inserts node at cursor position.
insert_node = "<Leader>ni",

Expand All @@ -72,6 +84,9 @@ local config = setmetatable({
---Removes an alias from the node under cursor.
remove_alias = "<Leader>nar",

---Removes the origin from the node under cursor.
remove_origin = "<Leader>nor",

---Toggles the org-roam node-view buffer for the node under cursor.
toggle_roam_buffer = "<Leader>nl",

Expand Down Expand Up @@ -135,6 +150,11 @@ local config = setmetatable({
---@type boolean
highlight_previews = true,

---Configuration to open the node view window.
---Can be a string, or a function that returns the window handle.
---@type string|fun():integer
open = "botright vsplit | vertical resize 50",

---If true, will include a section covering available keybindings.
---@type boolean
show_keybindings = true,
Expand Down
23 changes: 13 additions & 10 deletions lua/org-roam/core/database.lua
Original file line number Diff line number Diff line change
Expand Up @@ -442,16 +442,19 @@ function M:reindex(opts)

-- For each value, we cache a pointer to the node's id
for _, value in ipairs(result) do
if type(self.__indexes[name][value]) == "nil" then
-- Create empty cache for value if doesn't exist yet
self.__indexes[name][value] = {}
end

-- Store the node's id in the lookup cache
if not should_remove then
self.__indexes[name][value][id] = true
else
self.__indexes[name][value][id] = nil
-- Protect against list with nil values
if type(value) ~= "nil" then
if type(self.__indexes[name][value]) == "nil" then
-- Create empty cache for value if doesn't exist yet
self.__indexes[name][value] = {}
end

-- Store the node's id in the lookup cache
if not should_remove then
self.__indexes[name][value][id] = true
else
self.__indexes[name][value][id] = nil
end
end
end
end
Expand Down
Loading

0 comments on commit 8772079

Please sign in to comment.