├── .gitignore ├── README.org ├── org-rich-yank.el └── org-rich-yank.gif /.gitignore: -------------------------------------------------------------------------------- 1 | /org-rich-yank.elc 2 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: org-rich-yank 2 | 3 | [[https://melpa.org/#/org-rich-yank][https://melpa.org/packages/org-rich-yank-badge.svg]] 4 | 5 | Do you often yank source code into your org files, manually 6 | surrounding it in =#+BEGIN_SRC= blocks? This package will give you a 7 | new way of pasting that automatically surrounds the snippet in blocks, 8 | marked with the major mode of where the code came from, and adds a 9 | link to the source file after the block. 10 | 11 | #+ATTR_HTML: :alt org-rich-yank demo 12 | [[file:org-rich-yank.gif][file:org-rich-yank.gif]] 13 | 14 | * Installation 15 | 16 | ** MELPA 17 | If you use [[https://melpa.org/][MELPA]], you can just do =M-x list-packages=, find 18 | =org-rich-yank= in the list and hit =i x=. 19 | 20 | ** Manual 21 | Just put =org-rich-yank.el= somewhere in =load-path=. 22 | 23 | 24 | * Usage 25 | 26 | ** Manual, loading on startup: 27 | 28 | To use, require and bind whatever keys you prefer to the 29 | interactive function: 30 | 31 | #+BEGIN_SRC emacs-lisp 32 | (require 'org-rich-yank) 33 | (define-key org-mode-map (kbd "C-M-y") #'org-rich-yank) 34 | #+END_SRC 35 | 36 | ** With use-package, enabled after org: 37 | 38 | If you prefer =use-package=, the above settings would be: 39 | 40 | #+BEGIN_SRC emacs-lisp 41 | (use-package org-rich-yank 42 | :ensure t 43 | :demand t 44 | :bind (:map org-mode-map 45 | ("C-M-y" . org-rich-yank))) 46 | #+END_SRC 47 | 48 | The =:demand t= in there is because we never know when the user will 49 | hit =C-M-y=, so we always have to store the current buffer on 50 | kills. You can remove the =:demand t= and have lazy/deferred loading, 51 | but then the first time you hit =C-M-y= after startup, you'll get a 52 | message that you have to kill the selection again. 53 | 54 | * Configuration 55 | 56 | ** Image support 57 | 58 | If you have =org-download= installed and you copy image contents, 59 | =org-rich-yank= will defer to =org-download-clipboard=. You can turn 60 | this feature off by setting =org-rich-yank-download-image= to =nil=. 61 | 62 | ** Changing the link/block format 63 | 64 | If you want to change how the source block or link is formatted, you 65 | can do so by setting =org-rich-yank-format-paste= to a function. For 66 | example, links to local files might be useful in your org document but 67 | not so useful in exported content, so you may want to make such a link 68 | a /comment/ line. 69 | 70 | #+begin_src emacs-lisp :tangle no 71 | (defun my-org-rich-yank-format-paste (language contents link) 72 | "Based on `org-rich-yank--format-paste-default'." 73 | (format "#+BEGIN_SRC %s\n%s\n#+END_SRC\n#+comment: %s" 74 | language 75 | (org-rich-yank--trim-nl contents) 76 | link)) 77 | (customize-set-variable 'org-rich-yank-format-paste #'my-org-rich-yank-format-paste) 78 | #+end_src 79 | 80 | Configuring the variable as above results in the following content being pasted: 81 | 82 | #+begin_example 83 | ,#+BEGIN_SRC emacs-lisp 84 | ;; URL: https://github.com/unhammer/org-rich-yank 85 | ;; Package-Requires: ((emacs "24.4")) 86 | ;; Keywords: convenience, hypermedia, org 87 | ,#+END_SRC 88 | ,#+comment: [[file:~/src/org-rich-yank/org-rich-yank.el]] 89 | #+end_example 90 | 91 | * See also 92 | 93 | - [[https://github.com/jkitchin/ox-clip][ox-clip]] solves the other direction, it gives you a command for 94 | *copying* your org-mode as rich text (or any source code, using 95 | htmlize.el), letting you paste into non-Emacs rich text editors 96 | -------------------------------------------------------------------------------- /org-rich-yank.el: -------------------------------------------------------------------------------- 1 | ;;; org-rich-yank.el --- Paste with org-mode markup and link to source -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2018-2025 Kevin Brubeck Unhammer 4 | 5 | ;; Author: Kevin Brubeck Unhammer 6 | ;; Version: 0.3.2 7 | ;; URL: https://github.com/unhammer/org-rich-yank 8 | ;; Package-Requires: ((emacs "25.1")) 9 | ;; Keywords: convenience, hypermedia, org 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 2, or (at your option) 16 | ;; any later version. 17 | ;; 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | ;; 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;;; Do you often yank source code into your org files, manually 29 | ;;; surrounding it in #+BEGIN_SRC blocks? This package will give you a 30 | ;;; new way of pasting that automatically surrounds the snippet in 31 | ;;; blocks, marked with the language of the major mode of where the 32 | ;;; code came from, and adds a link to the source file after the 33 | ;;; block. 34 | 35 | ;;; To use, require and bind whatever keys you prefer to the 36 | ;;; interactive functions: 37 | ;;; 38 | ;;; (require 'org-rich-yank) 39 | ;;; (eval-after-load 'org 40 | ;;; '(define-key org-mode-map (kbd "C-M-y") #'org-rich-yank))) 41 | ;;; 42 | 43 | ;;; If you prefer `use-package', the above settings would be: 44 | ;;; 45 | ;;; (use-package org-rich-yank 46 | ;;; :ensure t 47 | ;;; :config 48 | ;;; (eval-after-load 'org 49 | ;;; '(define-key org-mode-map (kbd "C-M-y") #'org-rich-yank))) 50 | ;;; 51 | ;;; Note that we eagerly load `org-rich-yank', so we can capture yanks 52 | ;;; that happen before `org' is loaded. 53 | 54 | 55 | ;;; Code: 56 | 57 | (autoload 'org-store-link "ol") 58 | (autoload 'org-escape-code-in-string "org-src") 59 | (autoload 'org-src--on-datum-p "org-src") 60 | (autoload 'org-element-at-point "org-element") 61 | (autoload 'org-element-type "org-element") 62 | (autoload 'org-element-property "org-element") 63 | (autoload 'org-download-clipboard "org-download") 64 | (autoload 'yank-media-types--format "yank-media") ; optional; appeared in emacs-29.0.90 65 | 66 | (defgroup org-rich-yank nil 67 | "Options for org-rich-yank." 68 | :tag "org-rich-yank" 69 | :group 'org) 70 | 71 | (defcustom org-rich-yank-add-target-indent t 72 | "Give all lines of paste the same indentation as the first one. 73 | If this variable is non-nil and point is indented before pasting, 74 | all lines below will also get that indentation." 75 | :group 'org-rich-yank 76 | :type 'boolean) 77 | 78 | (defcustom org-rich-yank-format-paste #'org-rich-yank--format-paste-default 79 | "A function to format current paste as an org source block. 80 | See `org-rich-yank--format-paste-default' for example and expected arguments." 81 | :group 'org-rich-yank 82 | :type 'function) 83 | 84 | (defcustom org-rich-yank-download-image t 85 | "Whether to use `org-download-clipboard' when clipboard contains image." 86 | :group 'org-rich-yank 87 | :type 'boolean) 88 | 89 | (defcustom org-rich-yank--clipboard-link-mime-types 90 | '(text/x-moz-url-priv 91 | chromium/x-source-url 92 | application/x-openoffice-link\;windows_formatname=\"Link\" 93 | text/uri-list) 94 | "Mime types used by common programs to store link/url metadata in X CLIPBOARD." 95 | :group 'org-rich-yank 96 | :type '(repeat symbol)) 97 | 98 | (defvar org-rich-yank--buffer nil 99 | "The buffer of the most recent `kill-ring' text.") 100 | 101 | (defvar org-rich-yank--lang nil 102 | "Language of the most recent `kill-ring' text. 103 | Often but not always the language of buffer major mode; see 104 | `org-rich-yank--get-lang'.") 105 | 106 | (defun org-rich-yank--get-lang () 107 | "Find source language of current kill. 108 | Typically language of buffer major mode, but org source blocks 109 | should for example use the mode of their block, instead of 110 | \"org\"." 111 | (if-let* ((element (and (eq major-mode 'org-mode) 112 | (org-element-at-point))) 113 | (type (and (org-src--on-datum-p element) ; o/w takes effect after #+end_src too 114 | (org-element-type element))) 115 | (lang (and (eq type 'src-block) 116 | (org-element-property :language element)))) 117 | lang 118 | (replace-regexp-in-string "-mode$" "" (symbol-name major-mode)))) 119 | 120 | (defun org-rich-yank--store (&rest _args) 121 | "Store current buffer in `org-rich-yank--buffer'. 122 | ARGS ignored." 123 | (setq org-rich-yank--buffer (current-buffer)) 124 | (setq org-rich-yank--lang (org-rich-yank--get-lang))) 125 | 126 | ;;;###autoload 127 | (defun org-rich-yank-enable () 128 | "Add the advices that store the buffer of the current kill." 129 | (advice-add #'kill-append :after #'org-rich-yank--store) 130 | (advice-add #'kill-new :after #'org-rich-yank--store)) 131 | 132 | ;; Always do this on load – safe to run multiple times 133 | (org-rich-yank-enable) 134 | 135 | (defun org-rich-yank-disable () 136 | "Remove the advices that store the buffer of the current kill." 137 | (advice-remove #'kill-append #'org-rich-yank--store) 138 | (advice-remove #'kill-new #'org-rich-yank--store)) 139 | 140 | (defun org-rich-yank--trim-nl (str) 141 | "Trim surrounding newlines from STR." 142 | (replace-regexp-in-string "\\`[\n\r]+\\|[\n\r]+\\'" 143 | "" 144 | str)) 145 | 146 | (declare-function diff-goto-source "diff-mode") 147 | (declare-function gnus-article-show-summary "gnus-art") 148 | 149 | (defun org-rich-yank--store-link () 150 | "Store the link using `org-store-link' without erroring out." 151 | (with-demoted-errors "Error in org-rich-yank--store-link: %S" 152 | (cond ((and (eq major-mode 'gnus-article-mode) 153 | (fboundp #'gnus-article-show-summary)) 154 | ;; Workaround for possible bug in org-gnus-store-link: If 155 | ;; you've moved point in the summary, org-store-link from 156 | ;; the article will give the wrong link 157 | (save-window-excursion (gnus-article-show-summary) 158 | (org-store-link nil))) 159 | ((and (eq major-mode 'diff-mode)) 160 | (save-window-excursion 161 | (diff-goto-source) 162 | (org-store-link nil))) 163 | ;; org-store-link doesn't do eww-mode yet as of 8.2.10 at least: 164 | ((and (eq major-mode 'eww-mode) 165 | (boundp 'eww-data) 166 | (plist-get eww-data :url)) 167 | (format "[[%s][%s]]" 168 | (plist-get eww-data :url) 169 | (or (plist-get eww-data :title) 170 | (plist-get eww-data :url)))) 171 | (t (org-store-link nil))))) 172 | 173 | (defun org-rich-yank--get-X-clipboard-link () 174 | "Search X gui CLIPBOARD selection for data with an url mime type. 175 | Common url mime types defined in `org-rich-yank--clipboard-link-mime-types'." 176 | (when-let* ((data-types (gui-get-selection 'CLIPBOARD 'TARGETS)) 177 | (data-type (and (vectorp data-types) 178 | (seq-find 179 | (lambda (dt) (memq dt org-rich-yank--clipboard-link-mime-types)) 180 | data-types))) 181 | (data (gui-get-selection 'CLIPBOARD data-type)) 182 | ;; TODO: Can/should we use yank-media-preferred-types or yank-media-autoselect-function? 183 | (link-data (and (fboundp 'yank-media-types--format) 184 | (yank-media-types--format data-type data)))) 185 | ;; TODO: if pandoc is installed, we could use the text/html target and do something like 186 | ;; `xclip -o -selection clipboard -t text/html | pandoc -f html -t org` 187 | ;; TODO: Customizable list of GUI link-cleaner functions: 188 | (if (eq data-type 'application/x-openoffice-link\;windows_formatname=\"Link\") 189 | (when-let ((path (nth 1 (split-string link-data "\0")))) 190 | (format "[[file://%s]]" path)) 191 | link-data))) 192 | 193 | (defun org-rich-yank--link () 194 | "Get an org-link to the current kill." 195 | (or 196 | (org-rich-yank--get-X-clipboard-link) 197 | (with-current-buffer org-rich-yank--buffer 198 | (let ((link (org-rich-yank--store-link))) 199 | ;; TODO: make it (file-relative-name … dir-of-org-file) if 200 | ;; they're in the same projectile-project 201 | (when link (concat link "\n")))) 202 | ;; Don't insert "nil" if no link found: 203 | "")) 204 | 205 | (defun org-rich-yank-indent (paste) 206 | "Prepend current indentation to each line of PASTE." 207 | (let* ((s (buffer-substring (line-beginning-position) (point))) 208 | (indent (progn (string-match "\\s *$" s) 209 | (match-string 0 s)))) 210 | (replace-regexp-in-string "\n" 211 | (concat "\n" indent) 212 | paste))) 213 | 214 | (defun org-rich-yank--format-paste-default (language contents link) 215 | "Format LANGUAGE, CONTENTS and LINK as an `org-mode' source block." 216 | (format "#+BEGIN_SRC %s\n%s\n#+END_SRC\n%s" 217 | language 218 | (org-rich-yank--trim-nl contents) 219 | link)) 220 | 221 | (defun org-rich-yank--treat-as-image () 222 | "Non-nil if clipboard contents contain image, and `org-download' feature enabled." 223 | (and org-rich-yank-download-image 224 | (require 'org-download nil 'noerror) 225 | (gui-backend-get-selection 'CLIPBOARD 'image/png))) 226 | 227 | ;;;###autoload 228 | (defun org-rich-yank () 229 | "Yank, surrounded by #+BEGIN_SRC block with major mode of originating buffer." 230 | (interactive) 231 | (cond ((org-rich-yank--treat-as-image) 232 | (org-download-clipboard)) 233 | ((and org-rich-yank--buffer 234 | org-rich-yank--lang) 235 | (let* ((escaped-kill (org-escape-code-in-string (current-kill 0))) 236 | (needs-initial-newline 237 | (save-excursion 238 | (re-search-backward "\\S " (line-beginning-position) 'noerror))) 239 | (link (org-rich-yank--link)) 240 | (paste (funcall org-rich-yank-format-paste 241 | org-rich-yank--lang 242 | escaped-kill 243 | link))) 244 | (when needs-initial-newline 245 | (insert "\n")) 246 | (insert 247 | (if org-rich-yank-add-target-indent 248 | (org-rich-yank-indent paste) 249 | paste)))) 250 | (t 251 | (message "`org-rich-yank' doesn't know the source buffer – please `kill-ring-save' and try again.")))) 252 | 253 | 254 | (provide 'org-rich-yank) 255 | ;;; org-rich-yank.el ends here 256 | -------------------------------------------------------------------------------- /org-rich-yank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unhammer/org-rich-yank/8f73e833eac9c0eb686416962d5bdd369d80c1e8/org-rich-yank.gif --------------------------------------------------------------------------------