2

Goal: write code to do block commenting/uncommenting while keeping the current indentation level. This has been solved, see here for my code

Current problem: use s/// to match a line that begins with spaces/tabs and then a comment character, which will depend on the file being modified. In this case, I want to move from

    // this is a comment that can be toggled on/off

to

    this is a comment that can be toggled on/off

With my cursor on the commented line above:

:let b:commentChar='//'
:s@(^\s*)\=b:commentChar\s*@\1@

I have tried escaping the parentheses, escaping or unescaping the =, and using \v which someone told me would be "magic".

This does not work. (I didn't want to use / or # as the delimiter because my variable can be // or # and didn't want to use : as I need that for scoping with b:commentChar).

1

1 Answer 1

2

You cannot use \= in the "search" portion of a :substitute command to introduce an expression. Actually, \= does have meaning in this context but it "matches 0 or 1 of the preceding atom, as many as possible," which is not what you want.

Instead, you should use <c-r>=b:commentChar<cr>. This means literal CTRL-R= to introduce an expression and then <cr> to end the expression (copy and pasting this won't work). Your substitute also uses () which means literal parentheses, not a group. Here is a working command:

:s/\v(^\s*)\V<c-r>=escape(b:commentChar, '\/')<cr>\v\s*/\1/

This command:

  • uses \v to make parentheses act as a group- not strictly necessary but it prevents the user's magic setting from interfering.
  • \V before the expression to allow literal text and \v afterwards.
  • escape() so we don't have to worry about any slashes in the expression so we can safely use s//. Using s@@ works too, but there is the (maybe remote) possibility that b:commentChar contains @. escape handles all cases.

Again <c-r> and <cr> are literal characters you must type. This is handled transparently in mappings, e.g., copy and pasting the following would work

nnoremap <leader>c :s/\v(^\s*)\V<c-r>=escape(b:commentChar, '\/')<cr>\v\s*/\1/<cr>

Alternatively, you could use execute:

execute 's/\v(^\s*)\V'.escape(b:commentChar, '\/').'\v\s*/\1/'
5
  • Will this command work in a function? I had <cr> in a function before and it nagged at me for trailing characters. Is there a command that will work in a function for this? Commented Apr 17, 2018 at 21:22
  • the last execute version will work in a function
    – Mass
    Commented Apr 17, 2018 at 21:47
  • Can you explain what the period does in the execute line? I'm trying to get an if statement to know if the line is commented. I currently have if match(getline(l:line), "^\s*".b:commentChar)>-1 but it doesn't work. Commented Apr 17, 2018 at 23:03
  • The periods mean string concatenation. It doesn't work because you are using double quotes which requires backslashes to be escaped. This works: match(getline(l:line), '^\s*'.b:commentChar)>-1. Note the single quotes.
    – Mass
    Commented Apr 17, 2018 at 23:45
  • It doesn't seem to work for me. I asked another question here vi.stackexchange.com/questions/15971/invalid-match-arguments . If you happen to have the time, any help you could provide would be awesome, but no worries if you're busy. Thanks again! Commented Apr 18, 2018 at 19:05

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.