@@ -191,6 +191,29 @@ function Attach:get_node(file, cursor)
191191 return AttachNode .at_cursor (file , cursor )
192192end
193193
194+ --- Get attachment node pointed at in a window
195+ ---
196+ --- @param window ? integer | string window-ID , window number or any argument
197+ --- accepted by `winnr()`; if 0 or nil, use the
198+ --- current window
199+ --- @return OrgAttachNode
200+ function Attach :get_node_by_window (window )
201+ local winid
202+ if not window or window == 0 then
203+ winid = vim .api .nvim_get_current_win ()
204+ elseif type (window ) == ' string' then
205+ winid = vim .fn .win_getid (vim .fn .winnr (window ))
206+ elseif vim .fn .win_id2win (window ) ~= 0 then
207+ winid = window
208+ else
209+ winid = vim .fn .win_getid (window )
210+ end
211+ if winid == 0 then
212+ error ((' invalid window: %s' ):format (window ))
213+ end
214+ return self .core :get_node_by_winid (winid )
215+ end
216+
194217--- Return the directory associated with the current outline node.
195218---
196219--- First check for DIR property, then ID property.
@@ -567,6 +590,141 @@ function Attach:attach_lns(node)
567590 return self :attach (nil , { method = ' lns' , node = node })
568591end
569592
593+ --- @class orgmode.attach.attach_to_other_buffer.Options
594+ --- @inlinedoc
595+ --- @field window ? integer | string if passed , attach to the node pointed at in
596+ --- the given window; you can pass a window-ID, window number, or
597+ --- `winnr()`-style strings, e.g. `#` to use the previously
598+ --- active window. Pass 0 for the current window. It's an error
599+ --- if the window doesn't display an org file.
600+ --- @field ask ? ' always' | ' multiple' determines what to do if ` window` is nil ;
601+ --- if 'always', collect all nodes displayed in a window and ask the
602+ --- user to select one. If 'multiple', only ask if more than one
603+ --- node is displayed. If false or nil, never ask the user; accept
604+ --- the unambiguous choice or abort.
605+ --- @field prefer_recent ? ' ask' | ' buffer' | ' window' | boolean if not nil but
606+ --- `window` is nil, and more than one node is displayed,
607+ --- and one of them is more preferable than the others,
608+ --- this one is used without asking the user.
609+ --- Preferred nodes are those displayed in the current
610+ --- window's current buffer and alternate buffer, as well
611+ --- as the previous window's current buffer. Pass 'buffer'
612+ --- to prefer the alternate buffer over the previous
613+ --- window. Pass 'window' for the same vice versa. Pass
614+ --- 'ask' to ask the user in case of conflict. Pass 'true'
615+ --- to prefer only an unambiguous recent node over
616+ --- non-recent ones.
617+ --- @field include_hidden ? boolean If not nil , include not only displayed nodes ,
618+ --- but also those in hidden buffers; for those, the node
619+ --- pointed at by the `"` mark (position when last
620+ --- exiting the buffer) is chosen.
621+ --- @field visit_dir ? boolean if not nil , open the relevant attachment directory
622+ --- after attaching the file.
623+ --- @field method ? ' cp' | ' mv' | ' ln' | ' lns' The attachment method , same values
624+ --- as in `org_attach_method`.
625+
626+ --- @param file_or_files string | string[]
627+ --- @param opts ? orgmode.attach.attach_to_other_buffer.Options
628+ --- @return string | nil attachment_name
629+ function Attach :attach_to_other_buffer (file_or_files , opts )
630+ local files = utils .ensure_array (file_or_files ) --- @type string[]
631+ return self
632+ :find_other_node (opts )
633+ :next (function (node )
634+ if not node then
635+ return nil
636+ end
637+ return self :attach_many (files , {
638+ node = node ,
639+ method = opts and opts .method ,
640+ visit_dir = opts and opts .visit_dir ,
641+ })
642+ end )
643+ :wait (MAX_TIMEOUT )
644+ end
645+
646+ --- Helper to `Attach:attach_to_other_buffer`, unfortunately really complicated.
647+ --- @param opts ? orgmode.attach.attach_to_other_buffer.Options
648+ --- @return OrgPromise<OrgAttachNode | nil>
649+ function Attach :find_other_node (opts )
650+ local window = opts and opts .window
651+ local ask = opts and opts .ask
652+ local prefer_recent = opts and opts .prefer_recent
653+ local include_hidden = opts and opts .include_hidden or false
654+ if window then
655+ return Promise .resolve (self :get_node_by_window (window ))
656+ end
657+ if prefer_recent then
658+ local ok , node = pcall (self .core .get_current_node , self .core )
659+ if ok then
660+ return Promise .resolve (node )
661+ end
662+ local altbuf_nodes , altwin_node
663+ if prefer_recent == ' buffer' then
664+ altbuf_nodes = self .core :get_single_node_by_buffer (vim .fn .bufnr (' #' ))
665+ if altbuf_nodes then
666+ return Promise .resolve (altbuf_nodes )
667+ end
668+ ok , altwin_node = pcall (self .get_node_by_window , self , ' #' )
669+ if ok then
670+ return Promise .resolve (altwin_node )
671+ end
672+ elseif prefer_recent == ' window' then
673+ ok , altwin_node = pcall (self .get_node_by_window , self , ' #' )
674+ if ok then
675+ return Promise .resolve (altwin_node )
676+ end
677+ altbuf_nodes = self .core :get_single_node_by_buffer (vim .fn .bufnr (' #' ))
678+ if altbuf_nodes then
679+ return Promise .resolve (altbuf_nodes )
680+ end
681+ else
682+ local altbuf = vim .fn .bufnr (' #' )
683+ local altwin = vim .fn .win_getid (vim .fn .winnr (' #' ))
684+ -- altwin falls back to current window if previous window doesn't exist;
685+ -- that's fine, we've handled it earlier.
686+ ok , altwin_node = pcall (self .core .get_node_by_winid , self .core , altwin )
687+ altwin_node = ok and altwin_node or nil
688+ altbuf_nodes = self .core :get_nodes_by_buffer (altbuf )
689+ if altwin_node and (# altbuf_nodes == 0 or vim .api .nvim_win_get_buf (altwin ) == altbuf ) then
690+ return Promise .resolve (altwin_node )
691+ end
692+ if # altbuf_nodes == 1 and not altwin_node then
693+ return Promise .resolve (altbuf_nodes [1 ])
694+ end
695+ if prefer_recent == ' ask' then
696+ local candidates = altbuf_nodes
697+ if altwin_node then
698+ table.insert (candidates , 1 , altwin_node )
699+ end
700+ return ui .select_node (candidates )
701+ end
702+ -- More than one possible attachment location and not asking; fall back
703+ -- to regular behavior.
704+ end
705+ end
706+ local candidates = self .core :list_current_nodes ({ include_hidden = include_hidden })
707+ if # candidates == 0 then
708+ return Promise .reject (' nowhere to attach to' )
709+ end
710+ if ask == ' always' then
711+ return ui .select_node (candidates )
712+ end
713+ if ask == ' multiple' then
714+ if # candidates == 1 then
715+ return Promise .resolve (candidates [1 ])
716+ end
717+ return ui .select_node (candidates )
718+ end
719+ if ask then
720+ return Promise .reject ((' invalid value for ask: %s' ):format (ask ))
721+ end
722+ if # candidates == 1 then
723+ return Promise .resolve (candidates [1 ])
724+ end
725+ return Promise .reject (' more than one possible attachment location' )
726+ end
727+
570728--- Open the attachments directory via `vim.ui.open()`.
571729---
572730--- @param attach_dir ? string the directory to open
0 commit comments