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).

jeremysprofile
  • 387
  • 2
  • 11

1 Answers1

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/'
Mass
  • 14,080
  • 1
  • 22
  • 47
  • 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? – jeremysprofile Apr 17 '18 at 21:22
  • the last execute version will work in a function – Mass Apr 17 '18 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. – jeremysprofile Apr 17 '18 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 Apr 17 '18 at 23:45
  • It doesn't seem to work for me. I asked another question here https://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! – jeremysprofile Apr 18 '18 at 19:05