@@ -191,6 +191,29 @@ function Attach:get_node(file, cursor)
191
191
return AttachNode .at_cursor (file , cursor )
192
192
end
193
193
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
+
194
217
--- Return the directory associated with the current outline node.
195
218
---
196
219
--- First check for DIR property, then ID property.
@@ -559,6 +582,141 @@ function Attach:attach_lns(node)
559
582
return self :attach (nil , { method = ' lns' , node = node })
560
583
end
561
584
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
+
562
720
--- Open the attachments directory via `vim.ui.open()`.
563
721
---
564
722
--- @param attach_dir ? string the directory to open
0 commit comments