Search code examples
replacevim

Vim: search and replace every occurences of a word appearing between two other specified words


I am currently writing a math text in LaTeX using Vim, and this involves a lot of matrices. The crux of my problem is that the command \dots, which is supposed to be used everywhere in math environments for printing 3 consecutive dots (), and deal automatically with alignments, does not look great (I think) whenever it is used in matrix like environments.

I would therefore like to change all instances of \dots occurring inside all matrix-like environment into \cdots.

All the matrix-like environments I use end with the word matrix, so I tried the following:

:%s/\(\\begin{.*matrix}.*\)\\dots\(.*\\end{.*matrix}\)/\1\\cdots\2/g

However, it does not work as I hoped, since it only changes one instance at a time (in each matrix environment), and it seems that the pattern matching only happens on a single line, preventing all instances written like

\begin{pmatrix}
  1 & 2 & 3 & \dots & n   \\
  2 & 1 & 2 & \dots & n-1 \\
  3 & 2 & 1 & \dots & n-2 \\
  \vdots & \vdots & \vdots & \ddots &\vdots \\
  n & n-1 & n-2 & \dots & 1
\end{pmatrix}

to be corrected...

I am not very experienced in Vim, but I suspect the second problem I mentioned should be fairly standard to solve, and that the first one might be doable using some workarounds?

Solving only the second problem would be accepted as I guess I won't be writing matrices with more than 10 rows, so I could simply run the command 10 times to deal with the first issue. However, I would greatly appreciate a way to solve both (in a finite number of steps that does not depend on the number of occurrences of the word that we search), because this would be interesting for other cases (like replaces all occurrence of a word by another in parentheses).


Solution

  • Is :s%/… a typo or is it what you actually typed? I would expect :%s/… instead.

    Also, the (fixed?) command:

    :%s/\(\\begin{.*matrix}.*\)\\dots\(.*\\end{.*matrix}\)/\1\\cdots\2/g
    

    throws a:

    E486: Pattern not found: \(\\begin{.*matrix}.*\)\\dots\(.*\\end{.*matrix}\)
    

    here.

    Anyway, a single substitution doesn't seem appropriate to me, in this case, because your requirements are a bit too hard to express as a single pattern. Instead, I would do it with :help :global, which helps me split the problem into manageable chunks:

    :g/\\begin{.*matrix/.,/\\end{.*matrix/s/\\dots/\\cdots/g
    

    where:

    • :g/<pattern>/<command> executes <command> on each line where <pattern> matches,
    • \\begin{.*matrix is the <pattern> that matches the beginning of each matrix,
    • .,/\\end{.*matrix/s/\\dots/\\cdots/g is the <command> executed on each matched line,
    • .,/\\end{.*matrix/ is the :help :range given to :help :s, covering every line from the matched line (.) to the end of the matrix (/\\end{.*matrix/),
    • the rest is a pretty straightforward substitution.

    --- EDIT ---

    How would you make sure only the occurrences of \dots inside the matrix environment would be changed in case where the begin and end statement are on the same line ?

    I would make a first pass without giving a range to :s:

    :g/\\begin{.*matrix/s/\\dots/\\cdots/g
    

    followed by a second pass…

    • with a more carefully crafted <pattern>, anchored to the EOL,

    • with the range for :s:

      :g/\begin{.matrix}$/.,/\end{.*matrix/s/\dots/\cdots/g

    NOTE: if all your matrices are called pmatrix, vmatrix, etc., you should probably use \a or something like that instead of the overly broad . and the overly greedy .*:

    :g/\\begin{\amatrix/s/\\dots/\\cdots/g
    :g/\\begin{\amatrix}$/.,/\\end{\amatrix/s/\\dots/\\cdots/g
    

    Do you see an easy way to make sure the begin and end environment match (for instance, you may have a matrix of matrices, with the big matrix in a pmatrix environment, and the smaller nested matrices in smallmatrix environment)?

    Nothing comes to mind except using the appropriate names in the pattern.