How can a line be sorted relative to other lines at the same indentation level?

This would be useful whenever adding an item to an ordered list of definitions (file lists or forward definitions).

Currently you need to:

  • Move to the start of the block
  • Start selecting.
  • Move to the end of the block.
  • Sort the items.

Being able to order a newly added item in a block would be a more efficient way to handle this.

Examples could include:

C struct's:

/* Structs, keep in order. */

struct FooBar;
struct FooBaz;
struct FooFoo;
struct AbcBaz;  /* <- cursor on this line. */

CMake file list:

  zaz.c  # <- cursor on this line.

Running this action would sort the current line in respect to surrounding lines.

  • 1
    I assume this is in org mode? Can you give some examples of what you want to be sorted, and what you don't want to be?
    – zck
    Nov 23 '19 at 3:50

Needs some helper functions. Requested command at bottom:

(setq empty-line-p-chars "^[ \t\r]*$")

(defun empty-line-p ()
  "Return t if cursor is at an empty line, nil otherwise."
    (looking-at empty-line-p-chars)))

(defun ar-beginning-of-indent ()
  "Go to the beginning of a section of equal indent."
  (let ((indent (current-indentation))
    (last (line-beginning-position)))
    (while (and (not (bobp))
        (progn (forward-line -1)
               (= indent (current-indentation)))
        (not (empty-line-p))
        (setq last (line-beginning-position))))
    (goto-char last)

(defun ar-end-of-indent ()
  "Go to the end of a section of equal indentation."
  (let ((last (line-end-position))
    (indent (current-indentation)))
    (while (and (not (eobp)) (progn (forward-line 1) (and (not (empty-line-p)) (= indent (current-indentation))))(setq last (line-end-position))))
    (goto-char last)

(defun ar-sort-indent ()
    (let ((beg (ar-beginning-of-indent))
      (end (ar-end-of-indent)))
      (when (and beg end)
      (narrow-to-region beg end)
      (sort-lines nil beg end))))))

  • This doesn't work for blocks of text that aren't indented.
    – ideasman42
    Nov 24 '19 at 23:26
  • May you provide a recipe which fails? Checked with your first example: /* Structs, keep in order. / struct AbcBaz; / <- cursor on this line. */ struct FooBar; struct FooBaz; struct FooFoo; Nov 25 '19 at 5:58
  • With the cursor over A, try this: ===\nZ\n\nB\nC\nA\n\nA\n--- (with newlines expanded). The === and --- are ordered even though they're not in the same block as the B\nC\nA\n block.
    – ideasman42
    Nov 25 '19 at 6:19
  • Could reproduce the bug, checked in a fix. Nov 25 '19 at 6:46
  • Another fix and simplification. Nov 25 '19 at 7:03

This is a solution that doesn't require the entire block to be sorted, instead it moved the line up/down until it's sorted compared to lines directly above/below it.

(defun sort-line-in-block ()
  "Sort the current line relative to surrounding lines in the same block.

Blocks are delimited by empty lines or buffer extents.
  (let* ((pt-init (point))
         (indent-init (current-indentation))
         (line-init-begin (line-beginning-position))
         (line-init-end (line-end-position))
         (line-init-length (- line-init-end line-init-begin))
           line-init-begin line-init-end))
         (pt-init-indent (- pt-init line-init-begin))
         (pt-found nil))

    (if (string-blank-p line-init)
        (message "Line empty, not sorting")

      (dolist (dir '(1 -1))
          (let ((keep-looking (null pt-found)))
            (while keep-looking
              (setq keep-looking nil)
              ;; Check forward-line has no line
              ;; remainder to account for buffer min/max.
              (when (and (eq 0 (forward-line dir))
                         (eq indent-init (current-indentation)))
                (let ((line-cmp
                        (line-beginning-position) (line-end-position))))

                  (when (not (string-blank-p line-cmp))
                        (if (eq dir 1)
                            (string< line-cmp line-init)
                          (string< line-init line-cmp))
                      (setq pt-found
                            (if (eq dir 1)
                      (setq keep-looking t)))))))))

      (if (not pt-found)
          (message "Line already sorted!")

        (goto-char pt-found)
        (if (< pt-init pt-found)
            (insert "\n" line-init)
          (insert line-init "\n"))
          ;; Offset based on position of new line.
          (if (< pt-init pt-found) 0 -1)
          ;; Back to the start of the line.
          (- line-init-length)
          ;; Match the initial indent level.

        (if (< pt-init pt-found)
             (+ 1 line-init-end))
           (+ line-init-length line-init-begin)
           (+ line-init-length 1 line-init-end)))))))

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.