3

I'd like to find a regex that matches Setex-style headings like:

My Heading
==========

The headings text and the underline need to be of the same length. However, I don't know how to specify a regex that matches the above, but not

No Heading
====

Is there a way to require two groups of the match (first line and second line) to have the same number of characters?

  • 1
    If you don't require regex , you might want to do like this : find any number of =, go one line above and check the lengths are equal. You can do it using g – eyal karni Aug 16 at 21:58
  • 1
    @eyalkarni Ha. Look at my answer. Great minds think alike! ;) – B Layer Aug 17 at 1:01
  • Consider reframing the problem as "a non-blank line, followed by a line containing only =s, and at least 3 or 4 of them." That's most likely what the Setex parsers do anyways (I don't think they'll check for the same length.) If you're using this for syntax, it might be awkward if you edit the heading and it will only highlight it back when you adjust the following line with the =s... – filbranden Aug 17 at 4:50
  • @filbranden Actually asciidoc does take the number of characters into account, but allows a deviation of up to two characters. – radlan Aug 24 at 13:13
4

While you could use a regular expression to identify such lines with a specific number of characters, e.g. \v^%(.){6}\n%(\=){6}, there's no way to do so for an arbitrary number of characters with Vim regexes.1

Hopefully you'll be able to use a Vimscript solution. For example this one liner will save the line numbers of all line pairs that match your criteria:

g/^[=]\+$/ if strlen(getline(line(".") - 1)) == strlen(getline(".")) | call add(b:matches, line(".") - 1) | endif 

It requires existence of a list b:matches.

This one is a little easier to read and initializes the list and prints its contents...

let b:matchlines = []
g/^[=]\+$/ if strlen(getline(line(".") - 1)) == strlen(getline("."))
    call add(b:matches, line(".") - 1)
endif 
echo b:matchlines

Should be easy to tweak this as needed (e.g. if you wanted to save the actual matched text instead of line numbers). If you need some help with such a change let me know.

1 For those hungry for some red meat here's @perelo's comment on why this is so... Actually this is a well-known limitation of theoretical regular expressions. Although pattern matching engines allow more expressivity than th. R.E. (e.g. backreferences with (...)), they cannot match strings as a^nb^n for any n. In your case, 'a' is like . and 'b' is =. Intuitivelly, R.E. are like DFA, so they cannot count dynamically. Patterns such as {n} are obtained by adding O(n) states to the corresponding DFA. This is why we cannot syntactically check a program with a single regex : matching parentheses are like (^n )^n.

  • Or you could use Vimscript to build that pattern for i going from 1 to N (80? 100? 200?) and | them all together into a single regexp. This would probably be really inefficient, but it's the only way I could come up to match this. And it has a limit on the line length itself as well. – filbranden Aug 17 at 4:41
  • 1
    @filbranden Something similar crossed my mind but, yeah, not very efficient. – B Layer Aug 17 at 5:06
  • 3
    @radlan @B Layer, Actually this is a well-known limitation of theoretical regular expressions. Although pattern matching engines allow more expressivity than th. R.E. (e.g. backreferences with \(...\)), they cannot match strings as a^nb^n for any n. In your case, 'a' is like . and 'b' is =. Intuitivelly, R.E. are like DFA, so they cannot count dynamically. Patterns such as \{n} are obtained by adding O(n) states to the corresponding DFA. This is why we cannot syntactically check a program with a single regex : matching parentheses are like (^n )^n. – perelo Aug 17 at 18:32
  • 1
    @BLayer Thanks for the edit and for the great reply, btw :) I couldn't have thought of :global + if... – perelo Aug 18 at 10:45
  • 1
    Finally, it seems that a^nb^n can be matched with engines that allow recursive references. Vim's doesn't though and throw E65 Illegal back reference if you try something like \(\1\). – perelo Aug 18 at 10:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.