4

Unnecessary Context:

I want to write a function that closes open HTML tags, because closetag.vim seems complicated, and I like to understand all the stuff I run in Vim.
My plan for this is to match the most recent previous <\w+ segment, grab whatever is \w+, and then output </\1> at the cursor location when I ran this function.


Question:

How do I set a variable in my function equal to the most recent previous instance of \w\+ that comes after a <? I can do the search with ?<\(\w\+\), but nothing I've found will honor the capture group usage.

I have found matches and getmatches in :help (and searchpos and getline on SO), but it certainly feels like there should be an easier way than using two builtins together. I know about the search register, but that only contains the literal regex above, not what it has matched to.

It looks like a similar question was asked 5 months ago but the only answer is about matching tags on the same line, and I would like to do more. I found this question that says how to visually highlight the whole match, but can't handle capture groups, though it looks like the best method if I give up on the capture group limitation.

TL;DR: Is there a simple way to get the contents of a capture group search result into a variable?

4
  • Does submatch not work here or am I missing something?
    – D. Ben Knoble
    Commented Aug 26, 2020 at 11:39
  • From reading the :help it looks like that's only valid for substitutions, and I don't plan on changing the text I'm trying to match. If I'm incorrect, I'd love to see an answer that shows what you mean! Commented Aug 26, 2020 at 15:05
  • Hm, maybe you could use the 'n' flag to not substitute and set a variable? I’ll have to dig
    – D. Ben Knoble
    Commented Aug 26, 2020 at 15:06
  • Note: for cases where you do want to immediately do a substitution :s^^\=submatch(0).'</'.submatch(1).'>'^g is adequate. For cases where you do not want to do an immediate substitution, the problem is a bit more complicated.
    – dreftymac
    Commented Nov 18, 2022 at 0:33

1 Answer 1

2

I ended up combining these three answers to make my function.

No, there is not a way to get just a capture group from a search, but if your goal is just one capture group, you can use \zs and/or \ze as your group start and end points to determine highlighting and where your cursor lands.

" Ex: change <\(\w\+\) to to <\zs\w\+\ze (replace capture group with \zs and \ze)
function! GetMatch(pattern)
    let l:cursor_pos = getpos(".")
    let @/ = a:pattern
    " preserve old register content
    let savereg = @f
    " empty "f in case we don't find a match
    " if we skipped this, "f would keep whatever was in there before.
    let @f = ""
    normal "fygN
    let match = @f
    " restore register content
    let @f = l:savereg
    call setpos(".", l:cursor_pos)
    echo l:match
    return l:match
endfunction

I get the initial cursor_position and set the /search register.

From there, I can temporarily grab the results of my search into an arbitrary register (f for function is what I'm going with), then set a variable to hold the contents of that register and then reset the cursor_position to where it originally was.

I'm eventually going to hardcode the pattern to search for <\zs\w\+ but this was useful for testing purposes, and should showcase how to do this for anyone searching.

5
  • 3
    Great answer! A related question came up recently and gn was the solution there too... A couple of nitpicks: 1) Use :normal! to avoid any problems if any of y or gN are remapped. 2) When you save and restore the register, use getregtype() to save its type, then restore it with setreg() which takes an additional argument with the type. Otherwise saving/restoring a register coming from a visual block will break it. (Oh and a tiny minor one, in <\zs\w\+\ze, you don't need the \ze if that's the end of the pattern anyways... Just the \zs is enough to skip >.)
    – filbranden
    Commented Aug 25, 2020 at 21:08
  • 1
    Yeah, I link to the gn answer in the question - it was very helpful. I know that only \zs is necessary, I use it that way in the last sentence of my answer, but I figured for people who want to "lift and shift" for their purposes I should include both. 1. That is good to know about normal!, I was unaware, thanks! for getregtype I'd need another variable to store the type, right? There's no way to save a register and its type in one command? Commented Aug 25, 2020 at 21:54
  • 1
    I’d probably have just implemented the searchpos/getline version you rejected in the question, but this is a really nifty solution! +1
    – Rich
    Commented Aug 25, 2020 at 22:02
  • @Rich, I'd be interested in seeing another way of doing this, but no worries if you're busy. I was looking for the shortest way of doing this, but the fact that I have to save and restore the register contents and type means this way isn't as concise as I originally wanted. Commented Aug 25, 2020 at 22:05
  • 1
    @jeremysprofile Yes, use two separate variables, one for the contents (either use @f or getreg('f') as they're equivalent) and the second for the type. The setreg() call takes three arguments, the first with the name, second with the value, and the third (optional) argument is the type, which you can take directly from getregtype().
    – filbranden
    Commented Aug 25, 2020 at 22:41

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.