Skip to content

Commit 0a1d911

Browse files
committed
feat(attach): add config org_attach_dir_relative
1 parent 988d26f commit 0a1d911

File tree

7 files changed

+108
-7
lines changed

7 files changed

+108
-7
lines changed

docs/configuration.org

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,16 @@ be inherited.
11781178
The directory where attachments are stored. If this is a relative path, it
11791179
will be interpreted relative to the directory where the Org file lives.
11801180

1181+
*** org_attach_dir_relative
1182+
:PROPERTIES:
1183+
:CUSTOM_ID: org_attach_dir_relative
1184+
:END:
1185+
- Type: =boolean=
1186+
- Default: =false=
1187+
1188+
If =true=, whenever you add a =DIR= property to a headline, it is added as
1189+
a relative path. The default is to only add absolute paths.
1190+
11811191
*** org_attach_auto_tag
11821192
:PROPERTIES:
11831193
:CUSTOM_ID: org_attach_auto_tag
@@ -3013,8 +3023,9 @@ file).
30133023
Attaching a file puts it in a directory associated with the attachment node.
30143024
Based on [[#org_attach_preferred_new_method][org_attach_preferred_new_method]], this either uses the =ID= or
30153025
the =DIR= property. See also [[#org_attach_id_dir][org_attach_id_dir]],
3016-
[[#org_attach_id_to_path_function_list][org_attach_id_to_path_function_list]] and [[#org_attach_use_inheritance][org_attach_use_inheritance]] on how
3017-
to further customize the attachments directory.
3026+
[[#org_attach_dir_relative][org_attach_dir_relative]], [[#org_attach_id_to_path_function_list][org_attach_id_to_path_function_list]] and
3027+
[[#org_attach_use_inheritance][org_attach_use_inheritance]] on how to further customize the attachments
3028+
directory.
30183029

30193030
Attachment links are supported. A link like =[[attachment:file.txt]]=
30203031
looks up =file.txt= in the current node's attachments directory and opens

lua/orgmode/attach/core.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ end
115115
---Set the DIR node property and ask to move files there.
116116
---
117117
---The property defines the directory that is used for attachments
118-
---of the entry.
118+
---of the entry. Creates relative links if `org_attach_dir_relative'
119+
---is true.
119120
---
120121
---@param node OrgAttachNode
121122
---@param new_dir string

lua/orgmode/attach/init.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ end
247247
---Set the DIR node property and ask to move files there.
248248
---
249249
---The property defines the directory that is used for attachments
250-
---of the entry.
250+
---of the entry. Creates relative links if `org_attach_dir_relative'
251+
---is true.
251252
---
252253
---@param node? OrgAttachNode
253254
---@return string | nil new_dir

lua/orgmode/attach/node.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,19 @@ end
178178

179179
---Set the attachment directory on the current node.
180180
---
181-
---In addition to `set_property()`, this also ensures that the path is always
182-
---absolute.
181+
---In addition to `set_property()`, this also adjusts the path to be relative,
182+
---if required by `org_attach_dir_relative`.
183183
---
184184
---@param dir string
185185
---@return string new_dir absolute attachment directory
186186
---@overload fun(): nil
187187
function AttachNode:set_dir(dir)
188188
if dir then
189-
dir = vim.fn.fnamemodify(dir, ':p')
189+
if config.org_attach_dir_relative then
190+
dir = fs_utils.make_relative(dir, vim.fs.dirname(self.file.filename))
191+
else
192+
dir = vim.fn.fnamemodify(dir, ':p')
193+
end
190194
end
191195
self:set_property('DIR', dir)
192196
return dir and self:_make_absolute(dir)

lua/orgmode/config/defaults.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ local DefaultConfig = {
7373
[':noweb'] = 'no',
7474
},
7575
org_attach_id_dir = './data/',
76+
org_attach_dir_relative = false,
7677
org_attach_auto_tag = 'ATTACH',
7778
org_attach_preferred_new_method = 'id',
7879
org_attach_method = 'cp',

lua/orgmode/utils/fs.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,39 @@ function M.trim_common_root(paths)
9090
return result
9191
end
9292

93+
---Return a path to the same file as `filepath` but relative to `base`.
94+
---Starting with nvim 0.11, we can replace this with `vim.fs.relpath()`.
95+
---@param filepath string an absolute path
96+
---@param base string an absolute path to an ancestor of filepath;
97+
--- here, `'.'` represents the current working directory, and
98+
--- *not* the current file's directory.
99+
---@return string filepath_relative_to_base
100+
function M.make_relative(filepath, base)
101+
vim.validate({
102+
filepath = { filepath, 'string', false },
103+
base = { base, 'string', false },
104+
})
105+
filepath = vim.fn.fnamemodify(filepath, ':p')
106+
base = vim.fn.fnamemodify(base, ':p')
107+
if base:sub(-1) ~= '/' then
108+
base = base .. '/'
109+
end
110+
local levels_up = 0
111+
for parent in vim.fs.parents(base) do
112+
if parent:sub(-1) ~= '/' then
113+
parent = parent .. '/'
114+
end
115+
if vim.startswith(filepath, parent) then
116+
filepath = filepath:sub(parent:len() + 1)
117+
if levels_up > 0 then
118+
return vim.fs.joinpath(string.rep('..', levels_up, '/'), filepath)
119+
end
120+
return vim.fs.joinpath('.', filepath)
121+
end
122+
levels_up = levels_up + 1
123+
end
124+
-- No common root, just return the absolute path.
125+
return filepath
126+
end
127+
93128
return M

tests/plenary/utils/fs_spec.lua

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,51 @@ describe('trim_common_root', function()
137137
}, result)
138138
end)
139139
end)
140+
141+
describe('make_relative', function()
142+
local path = fs_utils.get_real_path(file.filename) ---@cast path string
143+
local basename = vim.fs.basename(path)
144+
local dirname = vim.fs.dirname(path)
145+
local dirname_slash = vim.fs.joinpath(dirname, '')
146+
local root = path
147+
for parent in vim.fs.parents(path) do
148+
root = parent
149+
end
150+
151+
it('gets the basename', function()
152+
local expected = vim.fs.joinpath('.', basename)
153+
local actual = fs_utils.make_relative(path, dirname)
154+
assert.are.same(expected, actual)
155+
end)
156+
157+
it('gets the basename with trailing slash', function()
158+
local expected = vim.fs.joinpath('.', basename)
159+
local actual = fs_utils.make_relative(path, dirname_slash)
160+
assert.are.same(expected, actual)
161+
end)
162+
163+
it('works one level up', function()
164+
local parent_name = vim.fs.basename(dirname)
165+
local expected = vim.fs.joinpath('.', parent_name, basename)
166+
local actual = fs_utils.make_relative(path, vim.fs.dirname(dirname))
167+
assert.are.same(expected, actual)
168+
end)
169+
170+
it('works one level up with trailing slash', function()
171+
local parent_name = vim.fs.basename(dirname)
172+
local expected = vim.fs.joinpath('.', parent_name, basename)
173+
local actual = fs_utils.make_relative(path, vim.fs.dirname(dirname) .. '/')
174+
assert.are.same(expected, actual)
175+
end)
176+
177+
it('produces a relative path even at the root', function()
178+
local relpath = fs_utils.make_relative(path, root)
179+
assert(vim.endswith(path, relpath))
180+
end)
181+
182+
it('climbs up via ..', function()
183+
local relpath = fs_utils.make_relative(root, path)
184+
local only_cdup = vim.regex('\\V\\(../\\)\\+')
185+
assert(only_cdup:match_str(relpath))
186+
end)
187+
end)

0 commit comments

Comments
 (0)