Add support for yank-media and DND

* lisp/org.el (org-mode): Call the setup function for yank-media and
DND.
(org-setup-yank-dnd-handlers): Register yank-media-handler and DND
handler.
(org-yank-image-save-method, org-yank-image-file-name-function)
(org-yank-dnd-method, org-yank-dnd-default-attach-method): New
defcustoms.
(org--image-yank-media-handler, org--copied-files-yank-media-handler)
(org--dnd-rmc, org--dnd-multi-local-file-handler)
(org--dnd-local-file-handler, org--dnd-attach-file, org--dnd-xds-method)
(org--dnd-xds-function): Add yank-media and DND handlers.

* doc/org-manual.org: (Drag and Drop & ~yank-media~): Describe the new
feature in the manual.

* etc/ORG-NEWS: Advertise the new features.
This commit is contained in:
Visuwesh 2023-09-22 20:11:41 +05:30 committed by Ihor Radchenko
parent b1cb3b724a
commit b77f991c02
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
3 changed files with 316 additions and 1 deletions

View File

@ -21141,6 +21141,51 @@ most recent since the mobile application searches files that were last
pulled. To get an updated agenda view with changes since the last
pull, pull again.
** Drag and Drop & ~yank-media~
:PROPERTIES:
:DESCRIPTION: Dropping and pasting files and images
:END:
#+cindex: dropping files
#+cindex: dragging files
#+cindex: drag and drop
#+cindex: dnd
#+vindex: org-yank-dnd-method
Org mode supports drag and drop (DnD) of files. By default, Org asks
the user what must be done with the dropped file: attach it, insert
=file:= link, or open the file. Customize ~org-yank-dnd-method~ to
set the default DnD action.
When DnD method is "attach", Org mode first consults DnD metadata to
decide the attach method. For example, when file/files are dragged
from a file manager, Org may attach by copying or by moving.
#+vindex: org-yank-dnd-default-attach-method
If Org cannot figure out which attachment method to use from the
metadata, it defaults to ~org-yank-dnd-default-attach-method~ [fn::By
default, ~org-yank-dnd-default-attach-method~ is set to nil -- use the same
value as ~org-attach-method~ (~cp~ by default).]
#+cindex: pasting files, images from clipboard
Starting from Emacs 29, Org mode supports ~yank-media~ command to yank
images from the clipboard and files from a file manager.
#+vindex: org-yank-image-save-method
When yanking images from clipboard, Org saves the image on disk and
inserts the image link to Org buffer. Images are either saved as
attachments to heading (default) or to a globally defined directory.
The save location is controlled by ~org-yank-image-save-method~.
#+vindex: org-yank-image-file-name-function
The yanked images are saved under automatically generated name. You
can customize ~org-yank-image-file-name-function~ to make Org query
the image names or change the naming scheme.
When yanking files copied from a file manager, Org respects the value
of ~org-yank-dnd-method~. Image files pasted this way also respect
the value of ~org-yank-image-save-method~ when the action to perform
is =attach=.
* Hacking
:PROPERTIES:
:DESCRIPTION: How to hack your way around.

View File

@ -700,6 +700,30 @@ Maxima's graphics packages (~draw~ or ~plot~); the default remains
~plot~. The graphics terminal is now determined from the file-ending
of the file-name set in the ~:file~ header argument.
*** Images and files in clipboard can be pasted
Org asks the user what must be done when pasting images and files
copied to the clipboard from a file manager using the ~yank-media~
command. The default action can be set by customizing
~org-yank-dnd-method~. The ~yank-media~ command was added in Emacs
29.
Images can be saved to a separate directory instead of being attached,
customize ~org-yank-image-save-method~.
Image filename chosen can be customized by setting
~org-yank-image-file-name-function~ which by default autogenerates a
filename based on the current time.
*** Files and images can be attached by dropping onto Emacs
By default, Org asks the user what to do with the dropped file like
for pasted files. The same user option ~org-yank-dnd-method~ is
respected.
Images dropped also respect the value of ~org-yank-image-save-method~
when ~org-yank-dnd-method~ is =attach=.
** New functions and changes in function arguments
*** ~org-fold-hide-drawer-all~ is now interactive

View File

@ -4997,7 +4997,10 @@ The following commands are available:
(org--set-faces-extend '(org-block-begin-line org-block-end-line)
org-fontify-whole-block-delimiter-line)
(org--set-faces-extend org-level-faces org-fontify-whole-heading-line)
(setq-local org-mode-loading nil))
(setq-local org-mode-loading nil)
;; `yank-media' handler and DND support.
(org-setup-yank-dnd-handlers))
;; Update `customize-package-emacs-version-alist'
(add-to-list 'customize-package-emacs-version-alist
@ -20266,6 +20269,249 @@ it has a `diary' type."
(org-format-timestamp timestamp fmt t))
(org-format-timestamp timestamp fmt (eq boundary 'end)))))))
;;; Yank media handler and DND
(defun org-setup-yank-dnd-handlers ()
"Setup the `yank-media' and DND handlers for buffer."
(setq-local dnd-protocol-alist
(cons (cons "^file:///"
(if (>= emacs-major-version 30)
#'org--dnd-multi-local-file-handler
#'org--dnd-local-file-handler))
dnd-protocol-alist))
(when (fboundp 'yank-media-handler)
(yank-media-handler "image/.*" #'org--image-yank-media-handler)
;; Looks like different DEs go for different handler names,
;; https://larsee.com/blog/2019/05/clipboard-files/.
(yank-media-handler "x/special-\\(?:gnome\|KDE\|mate\\)-files"
#'org--copied-files-yank-media-handler))
(when (boundp 'x-dnd-direct-save-function)
(setq-local x-dnd-direct-save-function #'org--dnd-xds-function)))
(defcustom org-yank-image-save-method 'attach
"Method to save images yanked from clipboard and dropped to Emacs.
It can be the symbol `attach' to add it as an attachment, or a
directory name to copy/cut the image to that directory."
:group 'org
:package-version '(Org . "9.7")
:type '(choice (const :tag "Add it as attachment" attach)
(directory :tag "Save it in directory"))
:safe (lambda (x) (eq x 'attach)))
(defcustom org-yank-image-file-name-function #'org-yank-image-autogen-filename
"Function to generate filename for image yanked from clipboard.
By default, this autogenerates a filename based on the current
time.
It is called with no arguments and should return a string without
any extension which is used as the filename."
:group 'org
:package-version '(Org . "9.7")
:type '(radio (function-item :doc "Autogenerate filename"
org-yank-image-autogen-filename)
(function-item :doc "Ask for filename"
org-yank-image-read-filename)
function))
(defun org-yank-image-autogen-filename ()
"Autogenerate filename for image in clipboard."
(format-time-string "clipboard-%Y%m%dT%H%M%S.%6N"))
(defun org-yank-image-read-filename ()
"Read filename for image in clipboard."
(read-string "Basename for image file without extension: "))
(declare-function org-attach-attach "org-attach" (file &optional visit-dir method))
(defun org--image-yank-media-handler (mimetype data)
"Save image DATA of mime-type MIMETYPE and insert link at point.
It is saved as per `org-yank-image-save-method'. The name for the
image is prompted and the extension is automatically added to the
end."
(let* ((ext (symbol-name (mailcap-mime-type-to-extension mimetype)))
(iname (funcall org-yank-image-file-name-function))
(filename (file-name-with-extension iname ext))
(absname (expand-file-name
filename
(if (eq org-yank-image-save-method 'attach)
temporary-file-directory
org-yank-image-save-method)))
link)
(when (and (not (eq org-yank-image-save-method 'attach))
(not (file-directory-p org-yank-image-save-method)))
(make-directory org-yank-image-save-method t))
(with-temp-file absname
(insert data))
(if (null (eq org-yank-image-save-method 'attach))
(setq link (org-link-make-string (concat "file:" (file-relative-name absname))))
(require 'org-attach)
(org-attach-attach absname nil 'mv)
(setq link (org-link-make-string (concat "attachment:" filename))))
(insert link)))
;; I cannot find a spec for this but
;; https://indigo.re/posts/2021-12-21-clipboard-data.html and pcmanfm
;; suggests that this is the format.
(defun org--copied-files-yank-media-handler (_mimetype data)
"Handle copied or cut files from file manager.
They are handled as per `org-yank-dnd-method'.
DATA is a string where the first line is the operation to
perform: copy or cut. Rest of the lines are file: links to the
concerned files."
;; pcmanfm adds a null byte at the end for some reason.
(let* ((data (split-string data "[\0\n\r]" t))
(files (cdr data))
(action (if (equal (car data) "cut")
'copy
'move))
(sep (if (= (length files) 1) "" " ")))
(dolist (f files)
(if (file-readable-p f)
(org--dnd-local-file-handler f action sep)
(message "File `%s' is not readable, skipping" f)))))
(defcustom org-yank-dnd-method 'ask
"Action to perform on the dropped and the pasted files.
When the value is the symbol,
. `attach' -- attach dropped/pasted file
. `open' -- visit/open dropped/pasted file in Emacs
. `file-link' -- insert file: link to dropped/pasted file
. `ask' -- ask what to do out of the above."
:group 'org
:package-version '(Org . "9.7")
:type '(choice (const :tag "Attach" attach)
(const :tag "Open/Visit file" open)
(const :tag "Insert file: link" file-link)
(const :tag "Ask what to do" ask)))
(defcustom org-yank-dnd-default-attach-method nil
"Default attach method to use when DND action is unspecified.
This attach method is used when the DND action is `private'.
This is also used when `org-yank-image-save-method' is nil.
When nil, use `org-attach-method'."
:group 'org
:package-version '(Org . "9.7")
:type '(choice (const :tag "Default attach method" nil)
(const :tag "Copy" cp)
(const :tag "Move" mv)
(const :tag "Hard link" ln)
(const :tag "Symbolic link" lns)))
(declare-function mailcap-file-name-to-mime-type "mailcap" (file-name))
(defvar org-attach-method)
(defun org--dnd-rmc (prompt choices)
(if (null (use-dialog-box-p))
(caddr (read-multiple-choice prompt choices))
(setq choices
(mapcar
(pcase-lambda (`(_key ,message ,val))
(cons (capitalize message) val))
choices))
(x-popup-menu t (list prompt (cons "" choices)))))
(defun org--dnd-multi-local-file-handler (urls action)
"Handle file URLS as per ACTION.
URLS is a list of file URL."
(let ((sep (if (= (length urls) 1) "" " ")))
(dolist (u urls)
(org--dnd-local-file-handler u action sep))))
(put 'org--dnd-multi-local-file-handler 'dnd-multiple-handler t)
(defun org--dnd-local-file-handler (url action &optional separator)
"Handle file URL as per ACTION.
SEPARATOR is the string to insert after each link. It may be nil
in which case, space is inserted."
(unless separator
(setq separator " "))
(let ((method (if (eq org-yank-dnd-method 'ask)
(org--dnd-rmc
"What to do with file?"
'((?a "attach" attach)
(?o "open" open)
(?f "insert file: link" file-link)))
org-yank-dnd-method)))
(pcase method
(`attach (org--dnd-attach-file url action separator))
(`open (dnd-open-local-file url action))
(`file-link
(let ((filename (dnd-get-local-file-name url)))
(insert (org-link-make-string (concat "file:" filename)) separator))))))
(defun org--dnd-attach-file (url action separator)
"Attach filename given by URL using method pertaining to ACTION.
If ACTION is `move', use `mv' attach method.
If `copy', use `cp' attach method.
If `ask', ask the user.
If `private', use the method denoted in `org-yank-dnd-default-attach-method'.
The action `private' is always returned.
SEPARATOR is the string to insert after each link."
(require 'mailcap)
(let* ((filename (dnd-get-local-file-name url))
(mimetype (mailcap-file-name-to-mime-type filename))
(separatep (and (string-prefix-p "image/" mimetype)
(not (eq 'attach org-yank-image-save-method))))
(method (pcase action
('copy 'cp)
('move 'mv)
('ask (org--dnd-rmc
"Attach using method"
'((?c "copy" cp)
(?m "move" mv)
(?l "hard link" ln)
(?s "symbolic link" lns))))
('private (or org-yank-dnd-default-attach-method
org-attach-method)))))
(if separatep
(funcall
(pcase method
('cp #'copy-file)
('mv #'rename-file)
('ln #'add-name-to-file)
('lns #'make-symbolic-link))
filename
(expand-file-name (file-name-nondirectory filename)
org-yank-image-save-method))
(org-attach-attach filename nil method))
(insert
(org-link-make-string
(concat (if separatep
"file:"
"attachment:")
(if separatep
(expand-file-name (file-name-nondirectory filename)
org-yank-image-save-method)
(file-name-nondirectory filename))))
separator)
'private))
(defvar-local org--dnd-xds-method nil
"The method to use for dropped file.")
(defun org--dnd-xds-function (need-name filename)
"Handle file with FILENAME dropped via XDS protocol.
When NEED-NAME is t, FILNAME is the base name of the file to be
saved.
When NEED-NAME is nil, the drop is complete."
(if need-name
(let ((method (if (eq org-yank-dnd-method 'ask)
(org--dnd-rmc
"What to do with dropped file?"
'((?a "attach" attach)
(?o "open" open)
(?f "insert file: link" file-link)))
org-yank-dnd-method)))
(setq-local org--dnd-xds-method method)
(pcase method
(`attach (expand-file-name filename (org-attach-dir 'create)))
(`open (expand-file-name (make-temp-name "emacs.") temporary-file-directory))
(`file-link (read-file-name "Write file to: " nil nil nil filename))))
(pcase org--dnd-xds-method
(`attach (insert (org-link-make-string
(concat "attachment:" (file-name-nondirectory filename)))))
(`file-link (insert (org-link-make-string (concat "file:" filename))))
(`open (find-file filename)))
(setq-local org--dnd-xds-method nil)))
;;; Other stuff
(defvar reftex-docstruct-symbol)