2

I often find I want to "mask" text in a text file, such as overwriting a secret value in an example file.

For example, I want to change SECRET_PASSWORD=123abc456 to SECRET_PASSWORD=XXXXXXXXX.

I can't find any way to do this very easily in Emacs.

I've tried writing a function to do this, where a marked region gets replaced with a character interactively:

(defun mask-text (start end)
  "Overwrite a selected region with the selected mask character"
  (interactive "r")
  (let ((region-length (- end start))
        (mask-char (read-char "Enter mask character")))
    (forward-char (- region-length))
    (delete-char region-length)
    (insert-char mask-char region-length)))

This kind of works, but fails if the mark is earlier in the text than the start of the marked region. I could hack the function around to make it more complicated, but I wondered if there was a nicer way to achieve this.

1 Answer 1

2

I wouldn't bother to write a function for this: I'd just use replace-regexp to replace any character (except newlines) with X or whatever. Select the region to mask, then

M-x replace-regexp RET . RET X RET

or

C-M-% . RET X RET !

If you do want to make a Lisp function, you can make a trivial wrapper around replace-regexp. The documentation discourages this, because it's somewhat slow and it changes the point and the mark data, but that's not really a concern in this particular case. Still, your approach is cleaner (doesn't refresh unduly, doesn't change the match data, ...). It could just use a few tweaks:

  • Obtain the replacement character through an interactive form.
  • Error out early if the buffer is read-only.
  • Apply to the clicked window if bound to a mouse event.
  • Don't make an assumption of where the point is originally: start at start. And since you're just going to delete something there, you might as well call delete-region directly.
  • As an improvement, leave the point on the same side of the region. (Or more generally, if called from Lisp and the point is neither start nor end, leave the point wherever it was, even in the middle of the replaced region.)
(defun mask-text (start end mask-char)
  "Overwrite the region with the selected mask character."
  (interactive "@*r\ncMasking character: ")
  (let ((region-length (- end start))
    (original-point (point)))
    (delete-region start end)
    (goto-char start)
    (insert-char mask-char region-length)
    (goto-char original-point)))
5
  • Thanks! Learned some stuff - I don't usually use replace-regexp but I probably should get more used to it. I didn't realise interactive could do more stuff. Your version definitely an improvement, especially using delete-region. Would using save-excursion make sense here?
    – Rich Smith
    Commented Apr 7, 2022 at 9:28
  • OK - never mind, tried save-excursion, and it doesn't work here.
    – Rich Smith
    Commented Apr 7, 2022 at 9:34
  • 2
    @RichSmith save-excursion is the normal way to preserve the cursor position around a block of code that moves around. It preserves the “logical” position of the cursor: for example, if the point was originally at position 100 and you run (save-excursion (goto-char 1) (delete-char)), the point will end up at position 99 since one character was deleted before it. (cont.) Commented Apr 7, 2022 at 9:38
  • (cont.) This is a rare exception where save-excursion would be suboptimal: if the point was in the middle of the deleted-and-reinserted region, save-excursion would just move it to the end of the region, since it doesn't know that we want a 1:1 correspondence between deleted and inserted characters. Because of this 1:1 correspondence, restoring the numerical value of the point is the right thing. Commented Apr 7, 2022 at 9:38
  • Thanks! (btw: you need a newline in that triple backtick block or the function definition gets swallowed; SE won't let me suggest an edit of less than 6 chars!)
    – Rich Smith
    Commented Apr 7, 2022 at 9:42

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.