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:
parent
b1cb3b724a
commit
b77f991c02
|
@ -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.
|
||||
|
|
24
etc/ORG-NEWS
24
etc/ORG-NEWS
|
@ -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
|
||||
|
||||
|
|
248
lisp/org.el
248
lisp/org.el
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue