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