3

There are cases where I wish to sort data that has been wrapped across multiple lines, making a simple line-sort not work usefully e.g.

'("x" "b" "c" "a lot of text")
'("a" "y" "z" "a lot of text") 

This sorts into:

'("a" "y" "z" "a lot of text")
'("x" "b" "c" "a lot of text")

However I would like the same outcome if the lines are wrapped. e.g.

'("a" "y" "z"
  "a lot of text")
'("x" "b" "c"
  "a lot of text")

Is there a way to sort structured text that respects structure?

2
  • It seems to me that your problem here is that you have undelimited Lisp data, or that you are using text delimiters (line ends) instead of Lisp delimiters (parentheses). Can you perhaps expound a little on why you (think you) need to do this? What would not work if your listed sexps were themselves enclosed (without their respective leading quote marks) in a single list? That is, '(("x" "b" ...) ("a" "y" ...))? Apr 6, 2023 at 16:56
  • The lisp is just an example - it could be C++ or Python. Too (the actual case I ran into was Python). Another way to look at this is - I want to sort lines - as if the auto-formatter had nor wrapped them). I could of course auto-format them to be longer, sort, then auto-format back, but in practice that's quite inconvenient.
    – ideasman42
    Apr 7, 2023 at 3:52

2 Answers 2

1

See the various sort commands, such as sort-pages, sort-paragraphs, and sort-regexp-fields.

The first two require the things you want to sort to be what Emacs recognizes as pages and paragraphs, respectively. E.g., pages are separated by C-l characters.

The third one lets you define the things to sort by the text that matches a regexp you supply. It sounds like that one will work for your use case, which seems to be sorting lists (matching corresponding ( and ) chars).

See the Elisp manual, node Sorting.

C-h f sort-regexp-fields`:

sort-regexp-fields is an interactive autoloaded Lisp function in sort.el.

It is bound to menu-bar edit sort sort-regexp-fields.

(sort-regexp-fields REVERSE RECORD-REGEXP KEY-REGEXP BEG END)

Sort the text in the region lexicographically.

If called interactively, prompt for two regular expressions, RECORD-REGEXP and KEY-REGEXP.

RECORD-REGEXP specifies the textual units to be sorted. For example, to sort lines, RECORD-REGEXP would be "^.*$".

KEY-REGEXP specifies the part of each record (i.e. each match for RECORD-REGEXPto be used for sorting. If it is "\\digit", use the digit'th "\\(...\\)" match field specified by RECORD-REGEXP. If it is "\\&", use the whole record. Otherwise, KEY-REGEXP should be a regular expression with which to search within the record. If a match for KEY-REGEXP is not found within a record, that record is ignored.

With a negative prefix arg, sort in reverse order.

The variable sort-fold-case determines whether alphabetic case affects the sort order.

For example: to sort lines in the region by the first word on each line starting with the letter "f", RECORD-REGEXP would be "^.*$" and KEY would be "\\<f\\w*\\>"

1

The description of your question suggests that your structured blocks are actually sexps which contain sexps.
You can easily employ the driver function sort-subr for sorting those.

One has to provide a number of functions as arguments of sort-subr:
We use

  • (comment-forward most-positive-fixnum) to go to the beginning of the next sexpr
  • a function sort-sexps-end to go from the middle of a top-level sexpr to its end
  • a function sort-sexps-goto-subexpr to go from the start of the sexpr to the start of the inner sexps that serves as sorting key
  • forward-sexp to go to the end of the sorting key
;; -*- lexical-binding: t -*-

(defun sort-sexps-end ()
  "Skip to the end of the top level sexp."
  (interactive)
  (let ((state (parse-partial-sexp (point-min) (point))))
    (parse-partial-sexp (point) (point-max) 0 nil state)
    ))

(defun sort-sexps-goto-subexpr (n)
  "Enter the list at point and go to subexpression N."
  (down-list)
  (forward-sexp n)
  (comment-forward most-positive-fixnum))

(defun sort-sexps (reverse beg end &optional subexpr)
  "Sort sexps in region at top level with sorting key SUBEXPR.
SUBEXPR is the index of the subexpression of the sort-key.
Called from a program, there are three arguments:
REVERSE (non-nil means reverse order), BEG and END (region to sort).
The variable `sort-fold-case' determines whether alphabetic case affects
the sort order."
  (interactive "P\nr\nnIndex of key subexpression (starting at 0): ")
  (unless subexpr (setq subexpr 0))
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
    (sort-subr reverse
           (lambda () (comment-forward most-positive-fixnum))
           #'sort-sexps-end
           (lambda () (sort-sexps-goto-subexpr subexpr))
           #'forward-sexp
           ))))

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.