inkarkat / vim-AdvancedSorters

Sorting of certain areas or by special needs.
32 stars 1 forks source link

Feature: SortRangesWithinHeader #1

Open idbrii opened 2 years ago

idbrii commented 2 years ago

Motivation

SortRangesByHeader makes it easy to sort several blocks by their header, but I don't think the plugin provides a way to sort several blocks defined by headers. This seems like a useful inversion of the SortRangesBy commands.

For example, I work in lua a lot and it's common to have several blocks of data where each line is similar:

local data = {
    {
        { prefab="tree", x=-3.0, y=-10.0 },
        { prefab="tree", x=2.0, y=-4.0 },
        { prefab="flower", x=4.0, y=-9.0 },
        { prefab="flower", x=5.0, y=1.0 },
        { prefab="tree", x=9.0, y=-4.0 },
        { prefab="tree", x=11.0, y=1.0 },
    },
    {
        { prefab="flower", x=-6.0, y=-6.0 },
        { prefab="tree", x=-5.0, y=-2.0 },
        { prefab="tree", x=-2.0, y=-8.0 },
        { prefab="tree", x=-1.0, y=2.0 },
        { prefab="flower", x=2.0, y=-2.0 },
        { prefab="flower", x=3.0, y=-7.0 },
        { prefab="tree", x=6.0, y=3.0 },
        { prefab="tree", x=7.0, y=-6.0 },
    },
...
}

The order of the inner items doesn't matter, so I might want to sort the data by prefab or by the y value. I can't sort everything at once.

Request and Purpose

:[range]SortRangesWithinHeader[!] /{expr}/ [i][u][r][n][x][o] [/{pattern}/]

Calculates blocks similar to SortRangesByHeader, but instead of sorting blocks as a unit, it sorts each block. The headers don't move within the document -- only the content of the block moves.

SortRangesWithinHeader /^ {/ would sort each set of data. (That's four spaces despite what github displays.)

Alternatives

%g/^ {$/normal vi{:sort doesn't work because :sort isn't evaluated.

xnoremap so :sort<CR>|%g/^ {$/normal vi{so works, but it's pretty awkward. Using operator-sort mapped to so lets me do %g/^ {$/normal vi{Vso or soi{ to the first and then . to the others.

These commands are also awkward to iterate on when working on a selection since they use visual mode and clobber '<,'>. (So you can't just edit the previous line to tweak it and gv selects the last block instead of the whole thing.)

inkarkat commented 2 years ago

In your alternatives, you're just missing the "trick" to invoke an Ex command from normal mode - your intermediate mapping indeed is a clumsy workaround for that. In order to complete the Ex command, you need to supply the required newline as \<CR> and by wrapping the :normal in :execute and double quotes so that this special key notation can be used:

:g/^    {$/exe "normal! vi{:sort\<CR>"

I think in this particular case I would instead define the range not via the inner block text object, but simply by a range defined by two matches - one for the opening and closing curly braces (that conveniently are on separate lines here - in a more irregular syntax, the text object indeed would have been beneficial to use):

:g/^    {/+1,/^    }/-1sort
inkarkat commented 2 years ago

You mention that this is a useful inversion of the SortRangesBy commands, but the plugin so far is about sorting with lines temporarily joined.

The plugin's name intentionally is very broad, so this could be put in here; however, what you suggest can be generalized to any Ex command, not just sorting:

:[range]SortRangesWithinHeader[!] /{expr}/ [i][u][r][n][x][o] [/{pattern}/]

could just as well be generalized to

:[range]RangesFromHeaders /{expr}/ sort[!] [i][u][r][n][x][o] [/{pattern}/]

Therefore, I don't think that the suggested functionality should be put into this plugin. However, my PatternsOnText plugin already has a (somewhat related) :RangeDo command; I'll consider putting :RangesFromHeaders, and :RangesFromMatches commands in there.