Two weeks ago I wrote about copying stuff from Emacs to the system clipboard, converting from Org-mode to markdown along the way. Even earlier, I wrote a snippet of code to convert double spaces to single ones when copying. Let’s continue the thread of transforming stuff while copying it from Emacs.
I often need to copy some snippet from the code I’m writing (or reading) to send it to a teammate. One issue I have with that is that those snippets are often taken from the middle of a function etc., and hence they are indented way too much. Take this classic piece of JavaScript:
function greet(name) { console.log(`Hello ${name}!`); }
If I want to copy just the console.log
, I don’t really need the tab in front of it. (And while at that, converting all these tabs to spaces would also be very useful – pasting text with tabs to various places like chat, Jira tasks etc. may or may not be a good idea, while spaces will work everywhere.) Wouldn’t it be great if Emacs had a feature to delete the unnecessary indentation and convert tabs to spaces? But wait, it’s Emacs – even if it doesn’t have such a feature, we can code it ourselves!
Writing a function to remove the common whitespace prefix from every line is probably a nice exercise, but it turns out there’s no need for it – Org-mode has the org-remove-indentation
function which does exactly this!
(org-remove-indentation " hello\n world")
yields
hello world
One drawback is that it doesn’t work with tabs like I want it to do:
(insert (org-remove-indentation " hello\n\t\tworld"))
gives
__hello ########______world
where _
denotes a space and ########
is a tab character.
Elisp has the untabify
function which converts tabs to spaces, taking the tab-width
variable into consideration. One minor complication with this is that it acts on a buffer, not a string. (Which is actually a good thing.) So, what we need to do is to copy the string we already have to a temporary buffer and run untabify
. While at that, a glance at the source code of org-remove-indentation
tells us that what we really need is org-do-remove-indentation
, which does the same but on a buffer instead of on a string (and is called by org-remove-indentation
after putting the string into a temp buffer).
(defun copy-snippet-deindented (begin end) "Copy region, untabifying and removing indentation." (interactive "r") (let ((region (buffer-substring-no-properties begin end))) (with-temp-buffer (insert region) (untabify (point-min) (point-max)) (org-do-remove-indentation) (kill-new (buffer-string)))))
A small problem with this code is that untabify
uses the tab-width
setting from the temp buffer (i.e., the default one) instead of the one from the buffer the copy-snippet-deindented
command was invoked. This is easy to fix using the following trick.
(defun copy-snippet-deindented (begin end) "Copy region, untabifying and removing indentation." (interactive "r") (let ((orig-tab-width tab-width) (region (buffer-substring-no-properties begin end))) (with-temp-buffer (setq tab-width orig-tab-width) (insert region) (untabify (point-min) (point-max)) (org-do-remove-indentation) (kill-new (buffer-string)))))
Now it seems I have enough ways to copy text to the kill ring (or the system clipboard) that I should probably make a hydra to easily select the one I need!
CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode