From b6f1315cbdc331f2f54e1801b03272915d344cfd Mon Sep 17 00:00:00 2001 From: Visuwesh Date: Fri, 22 Sep 2023 20:11:41 +0530 Subject: [PATCH] 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-media-image-save-type, org-dnd-default-attach-method): New defcustoms. (org--image-yank-media-handler, org--copied-files-yank-media-handler) (org--dnd-local-file-handler): Add yank-media and DND handlers. * etc/ORG-NEWS: Advertise the new features. --- etc/ORG-NEWS | 16 ++++++ lisp/org.el | 153 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 164 insertions(+), 5 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 252c5a9..f193c54 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -596,6 +596,22 @@ return a matplotlib Figure object to plot. For output results, the current figure (as returned by =pyplot.gcf()=) is cleared before evaluation, and then plotted afterwards. +*** Images and files in clipboard can be attached + +Org can now attach images in clipboard and files copied/cut to the +clipboard from file managers using the ~yank-media~ command which also +inserts a link to the attached file. + +Images can be saved to a separate directory instead of being attached, +customize ~org-media-image-save-type~. + +*** Files and images can be attached by dropping onto Emacs + +Attachment method other than ~org-attach-method~ for dropped files can +be specified using ~org-dnd-default-attach-method~. + +Images dropped also respect the value of ~org-media-image-save-type~. + ** New functions and changes in function arguments *** =TYPES= argument in ~org-element-lineage~ can now be a symbol diff --git a/lisp/org.el b/lisp/org.el index d0b2355..5c66f04 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -4999,7 +4999,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 @@ -15125,20 +15128,20 @@ INCREMENT-STEP divisor." (setq hour (mod hour 24)) (setq pos-match-group 1 new (format "-%02d:%02d" hour minute))) - + ((org-pos-in-match-range pos 6) ;; POS on "dmwy" repeater char. (setq pos-match-group 6 new (car (rassoc (+ nincrements (cdr (assoc (match-string 6 ts-string) idx))) idx)))) - + ((org-pos-in-match-range pos 5) ;; POS on X in "Xd" repeater. (setq pos-match-group 5 ;; Never drop below X=1. new (format "%d" (max 1 (+ nincrements (string-to-number (match-string 5 ts-string))))))) - + ((org-pos-in-match-range pos 9) ;; POS on "dmwy" repeater in warning interval. (setq pos-match-group 9 new (car (rassoc (+ nincrements (cdr (assoc (match-string 9 ts-string) idx))) idx)))) - + ((org-pos-in-match-range pos 8) ;; POS on X in "Xd" in warning interval. (setq pos-match-group 8 ;; Never drop below X=0. @@ -20254,6 +20257,146 @@ 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 '("^file:///" . org--dnd-local-file-handler) + dnd-protocol-alist)) + (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)) + +(defcustom org-media-image-save-type '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) (or (stringp x) (eq x 'attach)))) + +(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-media-image-save-type'. 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 (read-string "Insert filename for image: ")) + (filename (file-name-with-extension iname ext)) + (absname (expand-file-name + filename + (if (eq org-media-image-save-type 'attach) + temporary-file-directory + org-media-image-save-type))) + link) + (when (and (not (eq org-media-image-save-type 'attach)) + (not (file-directory-p org-media-image-save-type))) + (make-directory org-media-image-save-type t)) + (with-temp-file absname + (insert data)) + (if (null (eq org-media-image-save-type 'attach)) + (setq link (org-link-make-string + (concat "file:" (file-relative-name absname)) + filename)) + (require 'org-attach) + (org-attach-attach absname nil 'mv) + (setq link (org-link-make-string + (concat "attachment:" filename) + 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) + "Attach copied or cut files from file manager. +If the files were cut from the file manager, then the `mv' attach +method is used; `cp' otherwise. + +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." + (require 'org-attach) + (let* ((data (split-string data "[\0\n\r]" t "^file://")) + (files (cdr data)) + (method (if (equal (car data) "cut") + 'mv + 'cp))) + (dolist (f files) + (setq f (url-unhex-string f)) + (if (file-readable-p f) + (org-attach-attach f nil method) + (message "File `%s' is not readable, skipping" f))))) + +(defcustom org-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-media-image-save-type' 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 "Symbol link" lns))) + +(declare-function mailcap-file-name-to-mime-type "mailcap" (file-name)) +(defvar org-attach-method) + +(defun org--dnd-local-file-handler (url action) + "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 `vz/org-dnd-default-attach-action'. +The action `private' is always returned." + (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-media-image-save-type)))) + (method (pcase action + ('copy 'cp) + ('move 'mv) + ('ask (caddr (read-multiple-choice + "Attach using method" + '((?c "cp" cp) + (?m "mv" mv) + (?l "ln hard link" ln) + (?s "symbolic link" lns))))) + ('private (or org-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-media-image-save-type)) + (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-media-image-save-type) + (file-name-nondirectory filename))) + (file-name-nondirectory filename)) + "\n") + 'private)) + ;;; Other stuff (defvar reftex-docstruct-symbol) -- 2.40.1