1

Assume you have the following block of text (yes: I am using vim-latex, but this is not relevant):

\begin{itemize}
 \item 1
 % [[[
 \item 2
 \item 3
 \item 4
 \item 5
 % ]]]
 \item 6
\end{itemize}

My aim is to transform it in the following block of text:

\begin{itemize}
 \item 1
 % [[[
 \item 3
 \item 4
 \item 5
 \item 2
 % ]]]
 \item 6
\end{itemize}

i.e. the first line of the block between the markers should become the last line of said block (markers are inessential: you can change them, if you like).

Clearly a cut-and-paste of one line would do it, but I would like to have a sequence of commands to achieve this in order to save them in a register, so that I can execute it automatically within a block, irrespective of the number of \item lines.

In the most general case, ideally each \item should be considered of variable dimension: the goal is to take the first \item of the block within the markers with all the lines up to the following \item and move them in the last position of the block.

As an intermediate problem between the "all \item are one line" and "complete generality", you can consider the case "all \item have fixed length (say x lines)"

4 Answers 4

2

I would do:

?% \[\[\[?+1
dd
/% ]]]/-1
p
  • ?% \[\[\[?+1 move to the beginning of the block (the line after the % [[[ marker)
  • dd cut the line
  • /% ]]]/-1 move to the end of the block (the line before the % ]]] marker)
  • p Paste the cut line

Or for the more complex one:

?% \[\[\[?+1
$
d/\\item/-1
/% ]]]/-1
p
  • ?% \[\[\[?+1 move to the beginning of the block (the line after the % [[[ marker)
  • $ move to the end of the line
  • d/\\item/-1 cut the lines to the next item (in line mode)
  • /% ]]]/-1 move to the end of the block (the line before the % ]]] marker)
  • p Paste the cut line
2
  • This looks quite close to what I am looking for. Two points: 1) in my test, the command /% ]]] +/-1 doesn't work, while the command /% ]]]/-1 does what you say. 2) If I do it on the file, with the change I said it works, but if I save it on a register in my .vimrc, then it doesn't. Clearly, I am saving it in a wrong way. How do I save it correctly?
    – brad
    Mar 13 at 17:37
  • Sorry for the misstyping :-|. I have corrected the answer. This one should work fine. Mar 13 at 18:22
2

There are two parts to your question:

  1. somehow select a "block" of variable length between two "markers",
  2. make the first line of the block the last.

This answer addresses the latter part.

Moving a given line after another given line is the job of :help :move:

:<fromline>m<toline>

If you have selected the lines to rotate, the most canonical approach would be to move the first line of the selection (:help '<) below the last line of the selection (:help '>):

:<C-u>'<m'>

which you can map to whatever works for you:

xnoremap <F5> :<C-u>'<m'><CR>
1
  • This solution requires me to select lines: it is not what I was looking for. What I am looking for is: a sequence of commands to be saved in a register (say @r) such that if the cursor is on the very first line, then hitting @r does the job
    – brad
    Mar 13 at 17:31
1

The simple version would be:

function! s:Rotate(fline, lline, ...) abort
  " Optional count extraction
  let nb = 1
  if a:0 > 0
    if a:1 =~ '\v^\d+$'
      let nb = a:1
    endif
  endif
  " The lines to rotate
  let lines = getline(a:fline, a:lline)
  " The rotation
  let updated_lines = lines[nb :] + lines[: (nb-1)]
  " The update
  call setline(a:fline, updated_lines)
endfunction

command! -range -nargs=* Rotate call s:Rotate(<line1>, <line2>, <f-args>)

Which could be called on a selected range with :'<,'>Rotate [optional-count]

At this point, we just work with single lines.

The problem is that \item may span on multiple lines. Let's expect an extra parameter: the leading text that tells what starts a group (I'll ignore things like embedded itemlist)

function! s:Rotate(fline, lline, ...) abort
  " echomsg "a:000 -> "..string(a:000)
  " The lines to rotate
  let lines = getline(a:fline, a:lline)

  " Optional count extraction
  let nb      = 1
  let pattern = ''
  if a:0 > 0 " TODO: simplify! Fix?
    if a:1 =~ '\v^-?\d+$'
      let nb = a:1
      if a:0 > 1
        let pattern = a:2
      endif
    else
      let pattern = a:1
    endif
  endif
  if !empty(pattern)
    " Let's identify groups
    let groups = lines
          \ ->copy()
          \ ->map({n, l -> [n,l]})
          \ ->filter({_, nl -> nl[1] =~ l:pattern})
    " echomsg "groups: " .. string(groups)
    if empty(groups)
      throw printf("No '%s' group found", pattern)
    endif
    let groups = groups[nb :] + groups[: (nb-1)]
    " echomsg "reordered groups: " .. string(groups)
    let nb = groups[0][0]
  endif
  " The rotation
  let updated_lines = lines[nb :] + lines[: (nb-1)]
  " The update
  call setline(a:fline, updated_lines)
endfunction

command! -range -nargs=* Rotate call s:Rotate(<line1>, <line2>, <f-args>)

From there, repeating though a macro (that searches for %[[[/% ]]]) should be quite easy.

NB: While this solution is definitively overkill (compared to :move approach from Romain's answer), this is the simpler one I see where we could handle \item entries spanning on several lines.

4
  • Wow, this is quite a lot to digest... I will study it. Yet, my aim is a little bit different (see my comment @romainl )
    – brad
    Mar 13 at 17:33
  • That's where the macro (& Vivian's answer) come in: qr/% \[\[\[/+1<cr>V/% \]\]\]/-1<cr>:Rotate \item<cr>q (where <cr> is you hitting enter/Return) and there, you have your @r Mar 13 at 17:44
  • Or more exactly as it seems V doesn't work as I remembered in this context: let @r = '/% \[\[\[/+1'.."\<cr>"..'m</% \]\]\]/-1'.."\<cr>"..'m>:''<,''>Rotate \item'."\<cr>" Mar 13 at 17:53
  • You helped me to save @Vivian's answer in a register and your answer is also good food for thought. Many thanks
    – brad
    Mar 13 at 20:18
1

Another :move version (from the line to be fixed):

:move /% ]]]/-

(assuming % [[[/% ]]] delimits the block`).

We can apply this to all the lines after % [[[ with

:global/\V% [[[/+move /% ]]]/-

where we use :global to select all the % [[[ lines (but we don't want to escape [ in the pattern, so \V) and then run :+move … (moving the line after the match).

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.