7

I am using «sort-lines» to sort includes in C/C++ files before saving. The problem that arises is that a headers with " symbols sorted higher than ones with < symbols. But I want the reverse.

How do I change the order so, that the < character would have higher precedence than the " symbol?

  • 2
    You may need to write your own predicate function and call sort-subr directly. Or.. as a hack, you could advise sort-lines so that before the sort you swap " and < characters and then after the sort you swap them back. :-) – glucas Feb 5 '15 at 18:09
2

You can use sort-regexp-fields. Mark the region and do:

M-x sort-regexp-fields RET #include .\(.*\) RET \1 RET

The regular expression group will capture everything after #include " or #include < and sort on that instead of the whole line.

FYI, I believe the reason for the sorting you're seeing is because " is higher than < in ASCII.

  • 1
    Thank you, I already thought about sorting by the name of a headers and ignoring the opening <, " symbols. But I came to conclusion, that headers sorted together this way looks a bit messy, that's why I want just to change the order of the characters. – Hi-Angel Feb 5 '15 at 18:36
2

Meanwhile I wrote a code to implement the hack that @glucas mentioned. It perhaps only suits the purpose of sorting headers in C, but I hope someone find it useful.

(defun replace-char-after (character-number replacement)
  "Replaces char in the buffer after the `character-number' with `replacement'"
  (save-excursion
    (goto-char character-number)
    (delete-char 1)
    (insert-char replacement)))

(defun replace-delimiters (old-closing-char new-opening-char new-closing-char opening-point end-point)
  "Replaces delimiters between `opening-point' and the
`end-point'. Note, that the `opening-point' should point to the
opening symbol, thus the function seeks only the closing"
  (block replace-delimiters
    (let ((closing-point opening-point))
      (setq closing-point (+ 1 opening-point))
      (while (< closing-point end-point)
        (if (eq (char-after closing-point) ?\n) ;;no closing delimiter
            (progn
              (print "Err: no closing delimiter")
              (return-from replace-delimiters nil))
          (when (eq (char-after closing-point) old-closing-char)
            (progn
              (replace-char-after opening-point new-opening-char);;opening delimiter
              (replace-char-after closing-point new-closing-char);;closing delimiter
              (return-from replace-delimiters (+ 1 closing-point)))))
        (setq closing-point (+ closing-point 1))))))

(defun swap-<-and-quote-includes (beg end)
    "Swaps in the text between `beg' and `end' the matching «<» and
  «>» character to the \" quote, and vice versa. Mainly used
  before sorting to swap the order of these characters, next
  after the sort to restore the text."
  (block swap-<-and-quote-includes
    (let ((curr-point beg))
      (while (< curr-point end)
        (setq curr-point (+ curr-point 1))
        ;;first check «"»
        (if (eq (char-after curr-point) ?\")
            (progn
              (setq curr-point (replace-delimiters ?\" ?< ?> curr-point end))
              (if (eq curr-point nil)
                  (return-from swap-<-and-quote-includes t)))
          ;;else if «<»
          (if (eq (char-after curr-point) ?<)
              (progn
                (setq curr-point (replace-delimiters ?\> ?\" ?\" curr-point end))
                (if (eq curr-point nil)
                    (return-from swap-<-and-quote-includes t)))))))))

The function swap-<-and-quote-includes transforms every text like <foo> to "foo", and every "foo" to <foo> within the given range beg, end.

And here's the code to find and sort headers:

(defun sort-lines-nocase (reverse beg end)
  (let ((sort-fold-case t))
    (sort-lines reverse beg end)))

(defun c-sort-includes ()
  "Sorts #include statements"
  (interactive)
  (save-excursion
    (let (beg end orig-content sorted-content)
      (goto-char (point-min))
      (while (and (not (looking-at "#include "));;look for includes, if no then
                  (eq (forward-line 1) 0) ;;go one line down (if not EOF).
                  ))
      (setq beg (point))
      (while (and (looking-at "#include ")
                  (eq (forward-line 1) 0)));;to not hang cuz of EOF
      (setq end (point))
      (setq orig-content (buffer-substring-no-properties beg end))
      (setq sorted-content (with-temp-buffer
                             (insert orig-content)
                             (swap-<-and-quote-includes (point-min) (point-max)) ;;swap characters < and > in includes
                             (sort-lines-nocase (point-min) (point-max)) ;;sort
                             (swap-<-and-quote-includes (point-min) (point-max)) ;;swap the characters  back
                             (buffer-string)))
      (when (not (string= orig-content sorted-content))
        (kill-region beg end)
        (insert sorted-content))
      )))

The function c-sort-includes seeks for the first paragraph of «#include »s, and sorts it. I have it added to before-save-hook with a code to only run it for C and C++ modes. Known cons: α) only the first paragraph with includes will be sorted. It's because search till the end of a file can be expensive — e.g. at my job I recently found a .c file with — can you even imagine?! — ≈16000 lines! Correct solution would rather be a minor mode, which would track where header blocks in the file reside. β) In older Emacs, from ≈2015 year, function could hang — it was a bug that later was fixed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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