Skip to content

Instantly share code, notes, and snippets.

@PeterRincker
Last active March 17, 2024 04:51
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save PeterRincker/582ea9be24a69e6dd8e237eb877b8978 to your computer and use it in GitHub Desktop.
Save PeterRincker/582ea9be24a69e6dd8e237eb877b8978 to your computer and use it in GitHub Desktop.
Sort groups of lines in vim
" :[range]SortGroup[!] [n|f|o|b|x] /{pattern}/
" e.g. :SortGroup /^header/
" e.g. :SortGroup n /^header/
" See :h :sort for details
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>)
@Konfekt
Copy link

Konfekt commented Aug 24, 2020

Thank you for the clarification on the term group in :SortGroup.

@pbnj
Copy link

pbnj commented Aug 6, 2022

@Benhgift

This is remarkably helpful, thank you. I've been working with yaml files a lot lately and it's a real pain to sort certain sections of them. This works perfectly.

For sorting YAML use-case, command line YAML parsers, like yq and dasel, can do this.

@astier
Copy link

astier commented Mar 10, 2023

Maybe this could become a plugin.

@pbnj
Copy link

pbnj commented Mar 10, 2023

@astier - you can install this gist as a plugin with vim-plug:

Plug 'https://gist.github.com/PeterRincker/582ea9be24a69e6dd8e237eb877b8978', { 'dir': '~/.vim/plugin' }

@PeterRincker
Copy link
Author

@astier I am not really interested in maintaining this as a plugin. Feel free to wrap it up into a plugin if you wish. There is also Ingo Karkat's inkarkat/vim-AdvancedSorters which does far more than this gist

@astier
Copy link

astier commented Mar 14, 2023

No problem. AdvancedSorters looks good. Thank you.

@nogweii
Copy link

nogweii commented Aug 5, 2023

Hey @PeterRincker , what license is this code under? I assume that you don't care much about it, so it's probably a as-close-to-public-domain-as-possible choice (0BSD would be that), but I want to verify. :)

@PeterRincker
Copy link
Author

@nogweii You can use it however, you wish, as-is. I do not see a license option for gists. I imagine i would choose MIT or maybe something like a BSD license. I really have no desire to maintain this

@Unixware
Copy link

interesting plugin, as I understand it is only limited to 'letter' only headers , so failing to sort eg markdown (headers with # , ## , ### etc)

@PeterRincker
Copy link
Author

@Unixware, :SortGroup attempts to work similar to :sort. The pattern determines which lines are considers a header. The matched portion is used for the sort. This means \zs can be used to set where the match starts. e.g. /^## \zs means for text ## foo the foo is the matched text. This might be better with an example

# foo a
1
# baz c
3
# foo b
2

Running :SortGroup /^#/ would yield:

# baz c
3
# foo a
1
# foo b
2

With \zs you can sort differently. :SortGroup /^# ... \zs/ would yield:

# foo a
1
# foo b
2
# baz c
3

Hopefully that make sense.

... as I understand it is only limited to 'letter' only headers ...

:SortGroup takes an optional n, f, o, b, x to change to sort by decimal number, float, octal, binary, or hex. See :h :sort for more details

For more help see:

:h :sort
:h /\zs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment