4

Is there a way to define a sorting precedence regarding the line's pattern and then use it with :sort?

For example, if I have these lines:

1 some text (foo) some other text
2 some text (bar) some other text

I'd like to define a sorting precedence which says that a line containing foo is greater than a line containing bar, so when I execute :sort, it will sort the lines like:

1 some text (bar) some other text
2 some text (foo) some other text

I know adding a pattern after the command and the option r will sort the line using the matched string on the lines, but still these strings are sorted by their lexicographical order and that doesn't work for me.

1 Answer 1

3

I read a little bit more, and there's a function in vimscript called sort() which can receive a function to compare both items. Here's the help page:

sort({list} [, {func} [, {dict}]]) sort() E702

            Sort the items in {list} in-place.  Returns {list}.  If you
            want a list to remain unmodified make a copy first:
                    :let sortedlist = sort(copy(mylist))
            Uses the string representation of each item to sort on.
            Numbers sort after Strings, Lists after Numbers.
            For sorting text in the current buffer use :sort.
            When {func} is given and it is one then case is ignored.
            {dict} is for functions with the "dict" attribute.  It will be
            used to set the local variable "self". Dictionary-function
            When {func} is a Funcref or a function name, this function
            is called to compare items.  The function is invoked with two
            items as argument and must return zero if they are equal, 1 or
            bigger if the first one sorts after the second one, -1 or
            smaller if the first one sorts before the second one.
            Example:
                    func MyCompare(i1, i2)
                       return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1           
                    endfunc
                    let sortedlist = sort(mylist, "MyCompare")
            A shorter compare version for this specific simple case, which
            ignores overflow:
                    func MyCompare(i1, i2)
                       return a:i1 - a:i2
                    endfunc

So, something like

" Compare function
fu! MyCompare(i1, i2)
    let l:score1 = i1 =~ '.\{-}(foo)' ? 2 : i1 =~ '.\{-}(bar)' ? 1 : 0
    let l:score2 = i2 =~ '.\{-}(foo)' ? 2 : i2 =~ '.\{-}(bar)' ? 1 : 0

    return l:score1 == l:score2 ? 0 : l:score1 > l:score2 ? 1 : -1    
endfu

"Function to sort the lines
fu! SortLines(linestart, lineend)
    let l:lines = getline(a:linestart, a:lineend)
    sort(l:lines,"MyCompare")

    " Put the sorted lines in the file
    let l:nlines = a:lineend - a:linestart
    exe 'normal! '.a:linestart.'G'.l:nlines.'dd'
    let @l = join(l:lines, "\n")
    exe 'normal! '.a:linestart.'G"lP'
endfu

" Calling the function
:call SortLines(1,2)
2
  • 1
    Yep, that's how it's done. You could pass the range without explicit arguments, see :h function-range-example, and use setline() instead of deleting and using registers. Commented Apr 11, 2015 at 18:31
  • Thank you @IngoKarkat. Vim has a lot of functions that save you from doing a lot of work and it's quite difficult to find them if you didn't use them before.
    – Jcao02
    Commented Apr 11, 2015 at 19:36

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.