@@ -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.
@@ -556,6 +579,141 @@ function Attach:attach_lns(node)
556
579
return self :attach (nil , { method = ' lns' , node = node })
557
580
end
558
581
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
+
559
717
--- Open the attachments directory via `vim.ui.open()`.
560
718
---
561
719
--- @param attach_dir ? string the directory to open
0 commit comments