I often find myself copying a region of code that is deep inside the indented structure. Since I don't want it to end up in a codeblock with superfluous indentation (given that the context is missing), I find myself remembering that and having to go back to select the region, then press <
a couple times (I use evil) to shift the region left until leading indentation is removed, before I go ahead and copy again, then I have to undo the indentation by pressing u
an equivalent number of times.
I was therefore thinking of automating this by creating a command for it and a bind to go with it. I tried searching to see if something like this already existed (I figured it surely would) but I guess I'm not having any luck with the way I'm formulating my queries.
What I'm curious about is if there's already a command or package for this.
Short of that, if there's a reliable, consistent way to remove the "leading" indentation, which I guess means removing the minimum indentation (of all lines, potentially 0 if already not indented) from each line in a region.
For example, this:
(defun my-dots-search ()
(interactive)
(let ((helm-ag--extra-options
"--hidden --ignore-dir .git --ignore .gitignore --ignore .projectile"))
(helm-do-ag my-dots-path)))
That has 2 spaces of leading indentation, determined by mapping each line to its leading indentation and taking the minimum of that. So removing that leading indentation would involve removing 2 spaces from each line, to arrive at this:
(defun my-dots-search ()
(interactive)
(let ((helm-ag--extra-options
"--hidden --ignore-dir .git --ignore .gitignore --ignore .projectile"))
(helm-do-ag my-dots-path)))
If there isn't a function for this, I guess I could write it myself, so I would appreciate some pointers on things to look out for that I may be overlooking.
I imagine I would call such a function within a save-excursion
to easily "undo" things once I'm done copying the region.
EDIT: This is my rough, first pass. I'm wondering if I'm making it unnecessarily complicated, doing something wrong, or if there's something I'm overlooking.
(defun my--copy-without-leading-indentation (region &optional indent-to)
(let* ((lines (s-lines region))
(indent-lengths (--map (or (cdar (s-matched-positions-all "^[[:space:]]+" it))
0)
lines))
(indent-lengths-and-lines (-zip indent-lengths lines))
(non-empty-line-indents (--map (car it) (--filter (s-present? (cdr it)) indent-lengths-and-lines)))
(min-indent (if non-empty-line-indents (-min non-empty-line-indents) 0))
(unindented-lines (--map (if (or (= min-indent 0) (= (car it) 0))
(cdr it)
(substring (cdr it) min-indent))
indent-lengths-and-lines))
(indented-lines (when (and indent-to (> indent-to 0))
(--map (if (s-matches? "^[[:space:]]*$" it)
it
(concat (s-repeat indent-to " ") it))
unindented-lines)))
(resulting-lines (or indented-lines unindented-lines))
(joined (s-join "\n" resulting-lines)))
(kill-new joined)))
(defun my-copy-without-leading-indentation (arg start end)
"Copy the region without any leading indentation.
With argument, after removing leading indentation, indent to 4 spaces.
This is useful for Markdown codeblocks, for example."
(interactive "P\nr")
(let ((region (filter-buffer-substring start end)))
(my--copy-without-leading-indentation region (when arg 4))))
When given the prefix argument it indents by 4 spaces, useful for unfenced markdown codeblocks. I'm gonna try to polish and clean it up, and maybe optimize it some more.