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