1

I'm not a tech person. I've been using Neovim for the last year or so for writing, and I love it. But I'm considering making the switch to Emacs for the ability to use non-mono fonts. I use markdown because I collaborate with others.

In Neovim, I frequently use a vimscript function that I cannot figure out how to replicate in Emacs. It allows me to sort lines while maintaining grouping, both with headers and lists.

For example, it'd sort

  • B
    • 2
  • A
    • 1

to

  • A
    • 1
  • B
    • 2

Does anyone know how to replicate this in emacs? Here is the vimscript function.

function! s:sort_by_header(bang, pat) range
  let pat = a:pat
  let opts = ""
  if pat =~ '^\s*[nfxbo]\s'
    let opts = matchstr(pat, '^\s*\zs[nfxbo]')
    let pat = matchstr(pat, '^\s*[nfxbo]\s*\zs.*')
  endif
  let pat = substitute(pat, '^\s*', '', '')
  let pat = substitute(pat, '\s*$', '', '')
  let sep = '/'
  if len(pat) > 0 && pat[0] == matchstr(pat, '.$') && pat[0] =~ '\W'
    let [sep, pat] = [pat[0], pat[1:-2]]
  endif
  if pat == ''
    let pat = @/
  endif

  let ranges = []
  execute a:firstline . ',' . a:lastline . 'g' . sep . pat . sep . 'call add(ranges, line("."))'

  let converters = {
        \ 'n': {s-> str2nr(matchstr(s, '-\?\d\+.*'))},
        \ 'x': {s-> str2nr(matchstr(s, '-\?\%(0[xX]\)\?\x\+.*'), 16)},
        \ 'o': {s-> str2nr(matchstr(s, '-\?\%(0\)\?\x\+.*'), 8)},
        \ 'b': {s-> str2nr(matchstr(s, '-\?\%(0[bB]\)\?\x\+.*'), 2)},
        \ 'f': {s-> str2float(matchstr(s, '-\?\d\+.*'))},
        \ }
  let arr = []
  for i in range(len(ranges))
    let end = max([get(ranges, i+1, a:lastline+1) - 1, ranges[i]])
    let line = getline(ranges[i])
    let d = {}
    let d.key = call(get(converters, opts, {s->s}), [strpart(line, match(line, pat))])
    let d.group = getline(ranges[i], end)
    call add(arr, d)
  endfor
  call sort(arr, {a,b -> a.key == b.key ? 0 : (a.key < b.key ? -1 : 1)})
  if a:bang
    call reverse(arr)
  endif
  let lines = []
  call map(arr, 'extend(lines, v:val.group)')
  let start = max([a:firstline, get(ranges, 0, 0)])
  call setline(start, lines)
  call setpos("'[", start)
  call setpos("']", start+len(lines)-1)
endfunction
command! -range=% -bang -nargs=+ SortGroup <line1>,<line2>call <SID>sort_by_header(<bang>0, <q-args>)
1
  • Good question!...
    – Drew
    Jul 7, 2022 at 14:57

2 Answers 2

1
  1. Copy the following to test.md
# Before `org-sort-list RET a`

- B
  - 600
  - 700
  - 500
    
- A
  - Velit reprehenderit ex nostrud enim veniam, commodo
  - Laboris ipsum culpa officia sunt sint
  - Veniam, aliqua commodo irure officia non ex

# After `org-sort-list RET a`    

- B
  - 600
  - 700
  - 500
    
- A
  - Velit reprehenderit ex nostrud enim veniam, commodo
  - Laboris ipsum culpa officia sunt sint
  - Veniam, aliqua commodo irure officia non ex

  1. C-x C-f test.md, and M-x orgalist-mode RET
  2. Put the cursor at the beggining of the first level list, in second heading above and do M-x sort-list RET a
  3. This is what you will end up with
# Before `org-sort-list RET a`

- B
  - 600
  - 700
  - 500
    
- A
  - Velit reprehenderit ex nostrud enim veniam, commodo
  - Laboris ipsum culpa officia sunt sint
  - Veniam, aliqua commodo irure officia non ex

# After `org-sort-list RET a`    

- A
  - Velit reprehenderit ex nostrud enim veniam, commodo
  - Laboris ipsum culpa officia sunt sint
  - Veniam, aliqua commodo irure officia non ex
    
- B
  - 600
  - 700
  - 500



For further exploration,

M-x apropos-command RET org sort RET

gives me

Type RET on an entry to view its full documentation.

org-sort              M-x ... RET
  Call `org-sort-entries', `org-table-sort-lines' or `org-sort-list'.

org-sort-entries          M-x ... RET
  Sort entries on a certain level of an outline tree.

org-sort-list             M-x ... RET
  Sort list items.

org-table-sort-lines          M-x ... RET
  Sort table lines according to the column at point.

orgalist-sort-items       <menu-bar> <orgalist> <Sort items>
  Sort list items.


1
  • Thanks! This works for sorting markdown lists.
    – Euth38348
    Jul 8, 2022 at 6:26
0

There are several predefined commands for sorting expanses of text. See the Emacs manual, node Sorting. (You can access the manual from within Emacs with C-h r.)

See standard library sort.el for their definitions and more info. Use C-h f sort-lines` to get a direct link to that library.

Typically such a command's definition uses workhorse function sort-subr, passing it (1) a function that skips some delimiting text and (2) a function that goes forward over the text to be moved during sorting (i.e., sorted as a group/block).

Here's one such definition, for example, for command sort-pages:

(defun sort-pages (reverse beg end)
  "Sort pages in region alphabetically; argument means descending order.
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") ; Prefix arg reverses.  Others define region to sort.
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (sort-subr reverse ; Pass the prefix arg, to reverse.
         ;; Groups to sort are delimited by newline chars (Control-L).
         (function (lambda () (skip-chars-forward "\n")))
         ;; This function defines the group (by moving over it).
         ;; In this case, the group is a page of text (delimited by C-l char).
         'forward-page))))

Maybe one of the predefined commands will work for you directly, depending on how the groups you want to sort are separated (delimited).

If not (more likely), you can write your own command to do what you want. For that, you just need to be able to specify functions to (1) move past the delimiting text and (2) move past the text that constitutes a group to be sorted.

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.