Skip to content

Commit c682921

Browse files
committed
feat(attach): add OrgAttach:sync()
This corresponds to the Emacs function `org-attach-sync`.
1 parent d80f317 commit c682921

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

docs/configuration.org

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,19 @@ Be careful when changing this setting. If you remove a function, previously
12401240
created attachment folders may be no longer mapped correctly and Org may be
12411241
unable to detect them.
12421242

1243+
*** org_attach_sync_delete_empty_dir
1244+
:PROPERTIES:
1245+
:CUSTOM_ID: org_attach_sync_delete_empty_dir
1246+
:END:
1247+
- Type: ='always'|'ask'|'never'=
1248+
- Default: ='ask'=
1249+
1250+
Determines whether to delete empty directories during [[https://orgmode.org/manual/Attachment-defaults-and-dispatcher.html*index-C_002dc-C_002da-z][org_attach_sync]].
1251+
1252+
- =never= - never delete empty directories
1253+
- =ask= - ask the user whether to delete
1254+
- =always= - delete empty directories without asking
1255+
12431256
** Mappings
12441257
:PROPERTIES:
12451258
:CUSTOM_ID: mappings

lua/orgmode/attach/core.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,4 +497,49 @@ function AttachCore:delete_all(node, recursive)
497497
end)
498498
end
499499

500+
---Return true if the directory contains any files without trailing `~` in
501+
---their name. Trailing `~` is Emacs convention for swap files.
502+
---
503+
---@param directory string
504+
---@return boolean
505+
local function has_any_non_litter_files(directory)
506+
---@param name string
507+
return fileops.iterdir(directory):any(function(name)
508+
return not vim.endswith(name, '~')
509+
end)
510+
end
511+
512+
---Synchronize the current outline node with its attachments.
513+
---
514+
---Useful after files have been added/removed externally. The Option
515+
---`org_attach_sync_delete_empty_dir` controls the behavior for empty
516+
---attachment directories. (This ignores files whose name ends with
517+
---a tilde `~`.)
518+
---
519+
---@param node OrgAttachNode
520+
---@param delete_empty_dir fun(): OrgPromise<boolean>
521+
---@return OrgPromise<string|nil> attach_dir_if_deleted
522+
function AttachCore:sync(node, delete_empty_dir)
523+
local attach_dir = self:get_dir_or_nil(node)
524+
if not attach_dir then
525+
self:untag(node)
526+
return Promise.resolve()
527+
end
528+
local non_empty = has_any_non_litter_files(attach_dir)
529+
if non_empty then
530+
node:add_auto_tag()
531+
return Promise.resolve()
532+
else
533+
node:remove_auto_tag()
534+
end
535+
return delete_empty_dir():next(function(do_delete)
536+
if not do_delete then
537+
return Promise.resolve()
538+
end
539+
return fileops.remove_directory(attach_dir, { recursive = true }):next(function()
540+
return attach_dir
541+
end)
542+
end)
543+
end
544+
500545
return AttachCore

lua/orgmode/attach/init.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ function Attach:prompt()
136136
return self:unset_directory()
137137
end,
138138
})
139+
menu:add_option({
140+
label = 'Synchronize this task with its attachment directory.',
141+
key = 'z',
142+
action = function()
143+
return self:sync()
144+
end,
145+
})
139146
menu:add_option({ label = 'Quit', key = 'q' })
140147
menu:add_separator({ icon = ' ', length = 1 })
141148

@@ -586,4 +593,30 @@ function Attach:delete_all(force, node)
586593
:wait(MAX_TIMEOUT)
587594
end
588595

596+
---Synchronize the current outline node with its attachments.
597+
---
598+
---Useful after files have been added/removed externally. The Option
599+
---`org_attach_sync_delete_empty_dir` controls the behavior for empty
600+
---attachment directories. (This ignores files whose name ends with
601+
---a tilde `~`.)
602+
---
603+
---@param node? OrgAttachNode
604+
---@return nil
605+
function Attach:sync(node)
606+
node = node or self.core:get_current_node()
607+
local function delete_empty_dir()
608+
local opt = config.org_attach_sync_delete_empty_dir
609+
if opt == 'always' then
610+
return Promise.resolve(true)
611+
elseif opt == 'never' then
612+
return Promise.resolve(false)
613+
elseif opt == 'ask' then
614+
return ui.yes_or_no_or_cancel_slow('Attachment directory is empty. Delete? ')
615+
else
616+
return Promise.reject(('invalid value for org_attach_sync_delete_empty_dir: %s'):format(opt))
617+
end
618+
end
619+
return self.core:sync(node, delete_empty_dir):wait(MAX_TIMEOUT)
620+
end
621+
589622
return Attach

0 commit comments

Comments
 (0)