tags:

views:

509

answers:

5

The built-in VIM :sort command sorts lines of text. I want to sort words in a single line, e.g. transform the line

b a d c e f

to

a b c d e f

Currently I accomplish this by selecting the line and then using :!tr ' ' '\n' | sort | tr '\n' ' ', but I'm sure there's a better, simpler, quicker way. Is there?

Note that I use bash so if there's a shorter and more elegant bash command for doing this it's also fine.

EDIT: My use-case is that I have a line that says SOME_VARIABLE="one two three four etc" and I want the words in that variable to be sorted, i.e. I want to have SOME_VARIABLE="etc four one three two".

The end result should preferably be mappable to a shortcut key as this is something I find myself needing quite often.

A: 
:!perl -ne '$,=" ";print sort split /\s+/'

Not sure if it requires explanation, but if yes:

perl -ne ''

runs whatever is within '' for every line in input - putting the line in default variable $_.

$,=" ";

Sets list output separator to space. For example:

=> perl -e 'print 1,2,3'
123

=> perl -e '$,=" ";print 1,2,3'
1 2 3

=> perl -e '$,=", ";print 1,2,3'
1, 2, 3

Pretty simple.

print sort split /\s+/

Is shortened version of:

print( sort( split( /\s+/, $_ ) ) )

($_ at the end is default variable).

split - splits $_ to array using given regexp, sort sorts given list, print - prints it.

depesz
I'd hardly say a convoluted Perl script is an improvement…
mikl
+1  A: 

Maybe you preffer Python:

!python -c "import sys; print ' '.join(sorted(sys.stdin.read().split()))"

Visual select text, and execute this line.

iElectric
Still new to some of the language integration into Vim, but does this use compiled-in Python support or use an external executable?
technomalogical
To clarify, it does not use compiled-in python support in vim. It uses the external python binary as a shell command.
Lucas Oman
Ah, sorry. I wasn't aware we are talking about this one-liner. I thought we are talking about Vim at general.
iElectric
+5  A: 

In pure vim, you could do this:

call setline('.', join(sort(split(getline('.'), ' ')), " "))

Edit

To do this so that it works over a range that is less than one line is a little more complicated (this allows either sorting multiple lines individually or sorting part of one line, depending on the visual selection):

command! -nargs=0 -range SortWords call SortWords()
" Add a mapping, go to your string, then press vi",s
" vi" selects everything inside the quotation
" ,s calls the sorting algorithm
vmap ,s :SortWords<CR>
" Normal mode one: ,s to select the string and sort it
nmap ,s vi",s
function! SortWords()
    " Get the visual mark points
    let StartPosition = getpos("'<")
    let EndPosition = getpos("'>")

    if StartPosition[0] != EndPosition[0]
        echoerr "Range spans multiple buffers"
    elseif StartPosition[1] != EndPosition[1]
        " This is a multiple line range, probably easiest to work line wise

        " This could be made a lot more complicated and sort the whole
        " lot, but that would require thoughts on how many
        " words/characters on each line, so that can be an exercise for
        " the reader!
        for LineNum in range(StartPosition[1], EndPosition[1])
            call setline(LineNum, join(sort(split(getline('.'), ' ')), " "))
        endfor
    else
        " Single line range, sort words
        let CurrentLine = getline(StartPosition[1])

        " Split the line into the prefix, the selected bit and the suffix

        " The start bit
        if StartPosition[2] > 1
            let StartOfLine = CurrentLine[:StartPosition[2]-2]
        else
            let StartOfLine = ""
        endif
        " The end bit
        if EndPosition[2] < len(CurrentLine)
            let EndOfLine = CurrentLine[EndPosition[2]:]
        else
            let EndOfLine = ""
        endif
        " The middle bit
        let BitToSort = CurrentLine[StartPosition[2]-1:EndPosition[2]-1]

        " Move spaces at the start of the section to variable StartOfLine
        while BitToSort[0] == ' '
            let BitToSort = BitToSort[1:]
            let StartOfLine .= ' '
        endwhile
        " Move spaces at the end of the section to variable EndOfLine
        while BitToSort[len(BitToSort)-1] == ' '
            let BitToSort = BitToSort[:len(BitToSort)-2]
            let EndOfLine = ' ' . EndOfLine
        endwhile

        " Sort the middle bit
        let Sorted = join(sort(split(BitToSort, ' ')), ' ')
        " Reform the line
        let NewLine = StartOfLine . Sorted . EndOfLine
        " Write it out
        call setline(StartPosition[1], NewLine)
    endif
endfunction
Al
How would I modify this to sort the words of the selected text instead of the whole line?
spatz
@spatz - it does sort words
depesz
@depesz - Sure, but it sorts the entire line, I asked how to sort the selected text.
spatz
@spatz: Sorting part of a line is rather complicated as commands can't take sub-line ranges, but I've added an example of how to do this.
Al
+3  A: 

Here's the equivalent in pure vimscript:

 :call setline('.',join(sort(split(getline('.'),' ')),' '))

It's no shorter or simpler, but if this is something you do often, you can run it across a range of lines:

 :%call setline('.',join(sort(split(getline('.'),' ')),' '))

Or make a command

 :command -nargs=0 -range SortLine <line1>,<line2>call setline('.',join(sort(split(getline('.'),' ')),' '))

Which you can use with

:SortLine
:'<,'>SortLine
:%SortLine

etc etc

rampion
As @spatz mentioned in the comment for answer from @Al, this sorts the whole line, not a selection on the line.
technomalogical
A: 

Using great ideas from your answers, especially Al's answer, I eventually came up with the following:

:vnoremap <F2> d:execute 'normal i' . join(sort(split(getreg('"'))), ' ')<CR>

This maps the F2 button in visual mode to delete the selected text, split, sort and join it and then re-insert it. When the selection spans multiple lines this will sort the words in all of them and output one sorted line, which I can quickly fix using gqq.

I'll be glad to hear suggestions on how this can be further improved.

Many thanks, I've learned a lot :)

EDIT: Changed '<C-R>"' to getreg('"') to handle text with the char ' in it.

spatz