1

It's often the case that I want to move a code block from one place to another that has a different level of indent. Here's an example Haskell snippet:

someFunc = withSystemTempDirectory "test" $ \dir -> do
  foo <- someOtherFunc
  case foo of
    Just a -> do
      let bar = Thing { attr1 = dir,
                        attr2 = foo,
                        attr3 = bar
                      }
      somethingElse bar
    _ -> liftIO $ print "nope"

and I want to refactor this such that the let is above the case. So I kill the let, and navigate to the place I want it in:

someFunc = withSystemTempDirectory "test" $ \dir -> do
  foo <- someOtherFunc
  |
  case foo of
    Just a -> do
      somethingElse bar
    _ -> liftIO $ print "nope"

(| representing the cursor)

and here I want to execute the magic function to produce this buffer state:

someFunc = withSystemTempDirectory "test" $ \dir -> do
  foo <- someOtherFunc
  let bar = Thing { attr1 = dir,
                    attr2 = foo,
                    attr3 = bar
                  }|
  case foo of
    Just a -> do
      somethingElse bar
    _ -> liftIO $ print "nope"

With regular yank (C-y), I get this mess:

someFunc = withSystemTempDirectory "test" $ \dir -> do
  foo <- someOtherFunc
        let bar = Thing { attr1 = dir,
                        attr2 = foo,
                        attr3 = bar
                      }
|
  case foo of
    Just a -> do
      somethingElse bar
    _ -> liftIO $ print "nope"

As you can see, it did an extremely dumb paste job where the string was just inserted verbatim into the buffer. They still have the much greater indent of the code below. Additionally, the indent of the first line is one level greater than the others because the cursor was at +1 before pasting.

Every time I need to perform such a refactor, I need to manually choose the correct indent level for the let and indent all of its lines. If it were multiple statements, I'd have to choose the correct indent level for each of them which is a PITA. I don't want to have to do any of that.

Is there a pre-made package that I could include in my emacs config which contains this magical function I'm looking for?

Please note that I'm not looking for a function that does auto-indent on paste. These are usually wrong because their implementation is lacking or because there is no single correct indentation that could be determined as is the case in Haskell and some other indentation-dependant languages such as Python or YAML too.

Instead, it should work on indentation level alone; strip the indent of the yanked code and add new indentation based on the cursor position. Relative indent of multiple yanked lines must be preserved however.

1

1 Answer 1

2

The built-in command indent-rigidly (C-x TAB) does the job of reindenting a region while keeping its relative indents. So what's missing is to figure out by how much to reindent, and take care of the first line which has both the original indentation and the yanked indentation.

Here's a function that does that. I've only minimally tested it. You can call it after yanking or after any other function that inserts text and sets the mark at the other end of the inserted text.

(defun match-region-indentation (beg end)
  "Match the region's indentation to its starting point.

Call this function after yanking multiline indented text to match the yanked
text to the indentation of the place where it was yanked.

The first line of the yanked text must contain the indentation. Otherwise there
is no way to know by how much the other lines are indented, and you should call
\\[indent-rigidly] and manually specify by how much to reindent."
  (interactive "@*r")
  (save-excursion
    (goto-char beg)
    (let* ((target (current-column))
       (extra (progn
            (skip-syntax-forward "-" (line-end-position))
            (- (current-column) target))))
      (indent-rigidly (point) end (- target extra))
      (delete-region beg (point)))))

If you want this to be always done on a yank, define a command that does both and bind it to the same keys as yank.

(defun yank-and-match-indentation (&optional arg)
  "Yank (paste) the last killed text, matching the indentation at point.

This is equivalent to \\[yank] followed by \\[match-region-indentation]."
  (interactive "*P")
  (yank arg)
  ;; Uncomment the next line if you want undo immediately after yank to keep
  ;; the yank but undo the indentation (a second undo will undo the yank).
  ;; (undo-boundary)
  (match-region-indentation (mark) (point)))

(substitute-key-definition 'yank 'yank-and-match-indentation global-map)
5
  • Thank you so much for the answer! I was able to make it work by selecting the yanked region (C-x C-x and then calling this function on it. How would I make emacs run this function automatically whenever I yank?
    – Atemu
    Commented Apr 6 at 8:57
  • @Atemu You can define a command that does both. See my edit. Commented Apr 6 at 13:44
  • Or you can add a function to yank-transform-functions
    – rpluim
    Commented Apr 8 at 8:25
  • @rpluim In Emacs ≥29, yes. Commented Apr 8 at 10:46
  • How would one do that in this case?
    – Atemu
    Commented Apr 8 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.