Respace

The two seemingly trivial and unimportant problems of what kind of whitespaces and how to use them are still not solved. Some still use hard-coded tabs in their source code, and because they set tabs to be two spaces wide in their favorite editor, they expect the rest of the planet to have done so. The result is that spacing will break in another person’s editor, and the code will look like it’s been written by a four years old. Also, when tabs and spaces are mixed, and randomly interpreted, the indentation, the general aspect of how the code is presented, is broken.

turbo-napkin

While marking assignments, I encountered a number of such pieces of code. So I decided to fix that with a simple Emacs command.

The original code looks like this:

Number                             operator+(Number numberToUse1, Number numberToUse2)
{
  int32_t                          numeratorToSet = numberToUse1.getNumerator() * numberToUse2.getDenominator()
    + numberToUse2.getNumerator() * numberToUse1.getDenominator();
  int32_t                          denominatorToSet = numberToUse1.getDenominator() * numberToUse2.getDenominator();

  Number number3(numeratorToSet, denominatorToSet);

  number3.findPGCD();
  return (number3);
}

Maybe its author wrote it like that because he uses Visual Studio, or some other editor that allows collapsing of regions so that it shows only function headers, and with that weird spacing, all function names line up just fine. In every other editor, it’s a big WTF.

Emacs, my favorite editor, allows (relatively) easy programming of your own functions, especially that it already sports an impressive number of primitives. I already use indent-region, bound to a short-cut.

For a function like this to be useful, it should at least offer two variants, one for the whole buffer, and one only for the current function/block/class.

(defun whitespace-respace()
  "Replaces all (non-line changing) series of whitespaces by a single space, then reindent"
  (interactive)
  (save-excursion
    (mark-whole-buffer)
    (replace-regexp "[[:space:]]+" " ")
    (indent-region (region-beginning) (region-end) nil)
    )
  )

(defun whitespace-respace-function()
  "Replaces all (non-line changing) series of whitespaces by a single space, then reindent"
  (interactive)
  (save-excursion
    ; from https://www.emacswiki.org/emacs/BasicNarrowing
    (save-restriction
      ; narrows regexp replace on region
      (c-mark-function)
      (narrow-to-region (region-beginning) (region-end))
      (goto-char (point-min))
      (while (re-search-forward "[[:space:]]+" nil t)
        (replace-match " " nil nil)
        )
      )
    (c-mark-function)
    (indent-region (region-beginning) (region-end) nil)
    )
  )

Let’s have a look on how it works. The first variant respaces/reindent the whole buffer. save-excursion saves the cursor position, executes the statements listed, then restores it. mark-whole-buffer selects the whole buffer. replace-regexp replaces the regular expression on the whole buffer, without deactivating, or rather, while ignoring, the selection. At last, indent-region reindents the region marked by its region-beginning and its region-end.

The second variant is a bit more difficult: replace-regexp must be limited to the selection, which in turns depend on the function/class/etc, as selected by c-mark-function. I used the Emacs Wiki article on narrowing to get started. Basically, we have to iterate find-and-replace manually over the selection. Everything else is pretty much as in the first variant.

Applying the whitespace-respace-function on the previous code snippet yields:

Number operator+(Number numberToUse1, Number numberToUse2)
{
  int32_t numeratorToSet = numberToUse1.getNumerator() * numberToUse2.getDenominator()
   + numberToUse2.getNumerator() * numberToUse1.getDenominator();
  int32_t denominatorToSet = numberToUse1.getDenominator() * numberToUse2.getDenominator();

  Number number3(numeratorToSet, denominatorToSet);

  number3.findPGCD();
  return (number3);
}

*
* *

I used the “namespace” whitespace because that’s were I saw I could place it and remember where I placed it. The code also isn’t overly specific. Except for c-mark-function, it can be easily applied to other source languages because indent-function is supposed, I think, to be supplied by your major mode.

9 Responses to Respace

  1. Lindydancer says:

    Hi! Nice little tool! However, I would recommend that you check that the spaces you remove aren’t in a string, that way you could accidentally change the behavior of the program. A `(unless (nth 3 (syntax-ppss) …)` should do it.

    • I’ll have to look into it. I’m *not* an Emacs Lisp expert, and I have never used parser states… but hey, il y a un début partout.

      • Lindydancer says:

        It’s not as advanced as it looks, really. (Well, it is, under the hood, but that’s another story.) `syntax-ppss` describes the context of the point and element 3 (counting from 0) tells you if you’re in a string. That’s it.

  2. Noam Postavsky says:

    You don’t need to use narrowing, just pass (region-end) as the 2nd parameter (BOUND) to re-search-forward.

  3. justusc says:

    Thats a nice script. I can’t tell you how many times I have encountered the tab/space problem.

    Have you looked at the clang-format tool? It’s very configurable, so you can restrict it to just fix spacing if that is what you want/need. It can also be integrated into Vim, Emacs, and BBEdit.

    http://clang.llvm.org/docs/ClangFormat.html

    • No, I haven’t. I haven’t had time to try all the proposed modifications either. (It’s finals’ week and I’ve been marking exams and assignments for the last few days.)

  4. […] of line. Of course, they lose points, but that doesn’t make the code any easier to read. In a previous installment, I proposed something to rebuild the whitespaces only. Now, let’s see how we can repair as […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: