Skip to content

Commit 5a7853b

Browse files
committed
feat(attach): add attachment links
1 parent 5b8b86d commit 5a7853b

File tree

5 files changed

+116
-5
lines changed

5 files changed

+116
-5
lines changed

lua/orgmode/attach/core.lua

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ local utils = require('orgmode.utils')
66

77
---@class OrgAttachCore
88
---@field files OrgFiles
9+
---@field links OrgLinks
910
local AttachCore = {}
1011
AttachCore.__index = AttachCore
1112

12-
---@param opts {files:OrgFiles}
13+
---@param opts {files:OrgFiles, links:OrgLinks}
1314
function AttachCore.new(opts)
1415
local data = {
1516
files = opts and opts.files,
17+
links = opts and opts.links,
1618
}
1719
return setmetatable(data, AttachCore)
1820
end
@@ -427,6 +429,8 @@ function AttachCore:attach(node, file, opts)
427429
return nil
428430
end
429431
node:add_auto_tag()
432+
local link = self.links:store_link_to_attachment({ attach_dir = attach_dir, original = file })
433+
vim.fn.setreg(vim.v.register, link)
430434
return basename
431435
end)
432436
end)
@@ -453,6 +457,14 @@ function AttachCore:attach_buffer(node, bufnr, opts)
453457
local data = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), '\n')
454458
return utils.writefile(attach_file, data, { excl = true }):next(function()
455459
node:add_auto_tag()
460+
-- Ignore all errors here, this is just to determine whether we can store
461+
-- a link to `bufname`.
462+
local bufname_exists = vim.uv.fs_stat(bufname)
463+
local link = self.links:store_link_to_attachment({
464+
attach_dir = attach_dir,
465+
original = bufname_exists and bufname or attach_file,
466+
})
467+
vim.fn.setreg(vim.v.register, link)
456468
return basename
457469
end)
458470
end)
@@ -480,7 +492,10 @@ function AttachCore:attach_many(node, files, opts)
480492
.mapSeries(function(to_be_attached)
481493
local basename = basename_safe(to_be_attached)
482494
local attach_file = vim.fs.joinpath(attach_dir, basename)
483-
return attach(to_be_attached, attach_file)
495+
return attach(to_be_attached, attach_file):next(function(success)
496+
self.links:store_link_to_attachment({ attach_dir = attach_dir, original = to_be_attached })
497+
return success
498+
end)
484499
end, files)
485500
---@param successes boolean[]
486501
:next(function(successes)

lua/orgmode/attach/init.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ local MAX_TIMEOUT = 2 ^ 31
1414
local Attach = {}
1515
Attach.__index = Attach
1616

17-
---@param opts {files:OrgFiles}
17+
---@param opts {files:OrgFiles, links:OrgLinks}
1818
function Attach:new(opts)
1919
local data = setmetatable({ core = Core.new(opts) }, self)
20+
data.core.links:add_type(require('orgmode.org.links.types.attachment'):new({ attach = data }))
2021
return data
2122
end
2223

lua/orgmode/init.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function Org:init()
5757
})
5858
:load_sync(true, 20000)
5959
self.links = require('orgmode.org.links'):new({ files = self.files })
60-
self.attach = require('orgmode.attach'):new({ files = self.files })
60+
self.attach = require('orgmode.attach'):new({ files = self.files, links = self.links })
6161
self.agenda = require('orgmode.agenda'):new({
6262
files = self.files,
6363
highlighter = self.highlighter,

lua/orgmode/org/links/init.lua

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,11 @@ function OrgLinks:autocomplete(link)
7676
end
7777

7878
---@param headline OrgHeadline
79+
---@return string url
7980
function OrgLinks:store_link_to_headline(headline)
80-
self.stored_links[self:get_link_to_headline(headline)] = headline:get_title()
81+
local url = self:get_link_to_headline(headline)
82+
self.stored_links[url] = headline:get_title()
83+
return url
8184
end
8285

8386
---@param headline OrgHeadline
@@ -110,6 +113,41 @@ function OrgLinks:get_link_to_file(file)
110113
return ('file:%s::*%s'):format(file.filename, title)
111114
end
112115

116+
---@param params {attach_dir: string, original: string}
117+
---@return string | nil url
118+
function OrgLinks:store_link_to_attachment(params)
119+
local url = self:get_link_to_attachment(params)
120+
if url then
121+
self.stored_links[url] = vim.fs.basename(params.original)
122+
end
123+
return url
124+
end
125+
126+
---@param params {attach_dir: string, original: string}
127+
---@return string | nil url
128+
function OrgLinks:get_link_to_attachment(params)
129+
vim.validate({
130+
attach_dir = { params.attach_dir, 'string' },
131+
original = { params.original, 'string' },
132+
})
133+
local basename = vim.fs.basename(params.original)
134+
local choice = config.org_attach_store_link_p
135+
if choice == 'attached' then
136+
return string.format('attachment:%s', basename)
137+
elseif choice == 'file' then
138+
local attach_file = vim.fs.joinpath(params.attach_dir, basename)
139+
return string.format('file:%s', attach_file)
140+
elseif choice == 'original' then
141+
-- Sanity check: `original` might be a URL. Check for that and return it
142+
-- unmodified if yes.
143+
if params.original:match('^[A-Za-z]+://') then
144+
return params.original
145+
end
146+
return string.format('file:%s', params.original)
147+
end
148+
return nil
149+
end
150+
113151
---@param link_location string
114152
function OrgLinks:insert_link(link_location, desc)
115153
local selected_link = OrgHyperlink:new(link_location)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---@class OrgLinkAttachment:OrgLinkType
2+
---@field private attach OrgAttach
3+
local OrgLinkAttachment = {}
4+
OrgLinkAttachment.__index = OrgLinkAttachment
5+
6+
---@param opts { attach: OrgAttach }
7+
function OrgLinkAttachment:new(opts)
8+
local this = setmetatable({
9+
attach = opts.attach,
10+
}, OrgLinkAttachment)
11+
return this
12+
end
13+
14+
---@return string
15+
function OrgLinkAttachment:get_name()
16+
return 'attachment'
17+
end
18+
19+
---@param link string
20+
---@return boolean
21+
function OrgLinkAttachment:follow(link)
22+
local opts = self:_parse(link)
23+
if not opts then
24+
return false
25+
end
26+
self.attach:open(opts.basename, opts.node)
27+
return true
28+
end
29+
30+
---@param link string
31+
---@return string[]
32+
function OrgLinkAttachment:autocomplete(link)
33+
local opts = self:_parse(link)
34+
if not opts then
35+
return {}
36+
end
37+
local complete = self.attach:make_completion({ node = opts.node })
38+
return vim.tbl_map(function(name)
39+
return 'attachment:' .. name
40+
end, complete(opts.basename))
41+
end
42+
43+
---@private
44+
---@param link string
45+
---@return { node: OrgAttachNode, basename: string } | nil
46+
function OrgLinkAttachment:_parse(link)
47+
local basename = link:match('^attachment:(.+)$')
48+
if not basename then
49+
return nil
50+
end
51+
return {
52+
node = self.attach:get_current_node(),
53+
basename = basename,
54+
}
55+
end
56+
57+
return OrgLinkAttachment

0 commit comments

Comments
 (0)