0

Is there a Elisp function that deletes "redundant" leading whitespace of the region while maintaining relative indentation.

This behavior is like textwrap.dedent in python. https://docs.python.org/2/library/textwrap.html#textwrap.dedent

Stefan
  • 26,154
  • 3
  • 46
  • 84
FunkyBaby
  • 767
  • 1
  • 4
  • 10
  • Not real sure what you mean, but if this is about outdenting (removing the same amount of indentation from) all lines (or all lines in the region) then you can use `C-x TAB` with a numeric prefix arg (for the number of columns to outdent). – Drew Nov 13 '17 at 15:19
  • I'm also not clear on what you mean. But if you want a more programatic solution, check `whitespace-mode`. – xmonk Nov 13 '17 at 23:26
  • Based on the Python link, my understanding is that OP is looking for a function which deletes the greatest common whitespace prefix of some region of text. – Basil Nov 14 '17 at 00:06
  • @Basil yes. a simple elisp function. I might come up with my own. – FunkyBaby Nov 14 '17 at 00:56
  • 2
    Possible duplicate of [copy region without leading indentation](https://emacs.stackexchange.com/questions/34966/copy-region-without-leading-indentation) – phils Nov 14 '17 at 00:59

2 Answers2

2

N.B. See the answer by phils and its linked inspiration for a similar but more general solution.

The command indent-rigidly comes quite close to the desired functionality:

indent-rigidly is an interactive compiled Lisp function in
‘indent.el’.

It is bound to C-x TAB.

(indent-rigidly START END ARG &optional INTERACTIVE)

Indent all lines starting in the region.
If called interactively with no prefix argument, activate a
transient mode in which the indentation can be adjusted interactively
by typing <left>, <right>, <S-left>, or <S-right>.
Typing any other key deactivates the transient mode.

If called from a program, or interactively with prefix ARG,
indent all lines starting in the region forward by ARG columns.
If called from a program, START and END specify the beginning and
end of the text to act on, in place of the region.

Negative values of ARG indent backward, so you can remove all
indentation by specifying a large negative ARG.

but unfortunately a large enough ARG will change the relative indentation of lines. A workaround is to dedent by (i.e. pass a negative ARG of absolute value equal to) the smallest indentation level in the region:

(defun my-dedent (&optional start end)
  "Delete all common indentation between START and END.
If called interactively, START and END are the start/end of the
region if the mark is active, or of the buffer's accessible
portion if the mark is inactive.

Blank lines are ignored."
  (interactive (and (use-region-p)
                    (list (region-beginning)
                          (region-end))))
  (setq start (or start (point-min)))
  (setq end   (or end   (point-max)))
  (undo-boundary)
  (indent-rigidly start end
                  (- (indent-rigidly--current-indentation start end))))
Basil
  • 12,019
  • 43
  • 69
  • 1
    I hadn't noticed `indent-rigidly--current-indentation`. That seems like a function which shouldn't really be marked as internal. – phils Nov 14 '17 at 01:14
  • @phils Sorry, I wouldn't have posted a separate answer if I had seen your comment and answer in time, as our basic ideas are the same and your answer is more general. I agree that `indent-rigidly--current-indentation` seems too useful to be marked as internal, but the fact of the matter is it is, so one should use it with caution. If there are no objections, I'll leave my answer open purely as an example of an alternative approach. – Basil Nov 14 '17 at 01:27
  • 1
    No need to apologise! (Especially as you actually posted yours first :) but even if you hadn't I'd still be glad of your answer. – phils Nov 14 '17 at 02:13
  • I notice that `indent-rigidly--current-indentation` calls `current-indentation`, whereas I was using `current-column` in my answer. I haven't tested, but my gut tells me that the former is going to be more correct, given that we're both calling `indent-rigidly`. – phils Nov 14 '17 at 04:18
  • @phils Indeed, without being too familiar with the subtleties of their implementations and purpose, it looks like `current-column` would not DTRT in certain edge cases, as it handles more scenarios. You could probably use `current-indentation` as a drop-in replacement for `current-column` in your answer, though, no? – Basil Nov 14 '17 at 12:05
1

This is a variant of my answer to copy region without leading indentation which acts directly in the original buffer.

(defun my-unindent-region (pad beginning end)
  "Un-indent the region by the length of its minimum indent.

If numeric prefix argument PAD is supplied, indent the resulting
text by that amount."
  (interactive "P\nr")
  (save-excursion
    ;; Establish the minimum level of indentation.
    (let ((indent nil))
      (goto-char beginning)
      (while (and (re-search-forward "^[[:space:]\n]*" nil :noerror)
                  (< (point) end))
        (let ((length (current-column)))
          (when (or (not indent) (< length indent))
            (setq indent length)))
        (forward-line 1))
      (if (not indent)
          (error "Region is entirely whitespace")
        ;; Un-indent the buffer contents by the length of the minimum
        ;; indent level.
        (when pad
          (setq indent (- indent (prefix-numeric-value pad))))
        (indent-rigidly beginning end (- indent))))))
phils
  • 48,657
  • 3
  • 76
  • 115