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