Vim substitute() function flags order changes its behavior? - vim

I have the following line made of tab separated strings ; I have sometimes multiple sequential <Tab>:
zer<Tab><Tab>abc<Tab>def<Tab><Tab>iop<Tab><Tab>
I want to insert the 'null' string between 2 consecutive <Tab> ; I run the following command:
:s/\t\(\t\)\#=/\tnull/eg
which give me as I expected:
zer<Tab>null<Tab>abc<Tab>def<Tab>null<Tab>iop<Tab>null<Tab>
The equivalent substitute function to the above command is (I echoed its result):
:echo substitute(getline('.'),'\t\(\t\)\#=','\tnull','eg')
which insert a <Tab> only between the first two <Tab>s:
zer<Tab>null<Tab>abc<Tab>def<Tab><Tab>iop<Tab><Tab>
whereas if I change the order of the substitute flags in the substitute function call ('eg' replaced by 'ge'):
:echo substitute(getline('.'),'\t\(\t\)\#=','\tnull','ge')
Then I get the expected result:
zer<Tab>null<Tab>abc<Tab>def<Tab>null<Tab>iop<Tab>null<Tab>
It seems that the order of the flags in the substitute() function change its behavior while it has no effect for the substitute command.
Does anyone have an idea why that ?

From my limited understanding of C, it looks like Vim only cares about the {flags} argument if its first character is g:
do_all = (flags[0] == 'g');
[…]
if (!do_all)
break;
This may explain the fact that :help substitute() only mentions g when explaining {flags}:
When {flags} is "g", all matches of {pat} in {expr} are
replaced. Otherwise {flags} should be "".

The :substitute command can take many flags, but the substitute() function only supports the g flag. Flags like c (for interactivity) or e (for error suppression) do not apply to the low-level function.

Related

How to convert visual selection from unicode to the corresponding character in vim command?

I'm trying to convert multiple instances of Unicode codes to their corresponding characters.
I have some text with this format:
U+00A9
And I want to generate the following next to it:
©
I have tried to select the code in visual mode and use the selection range '<,'> in command mode as input for i_CTRL_V but I don't know how to use special keys on a command.
I haven't found anything useful in the manual with :help command-mode . I could solve this problem using other tools but I want to improve my vim knowledge. Any hint is appreciated.
Edit:
As #m_mlvx has pointed out my goal is to visually select, then run some command that looks up the Unicode and does the substitution. Manually input a substitution like :s/U+00A9/U+00A9 ©/g is not what I'm interested in as it would require manually typing each of the special characters on every substitution.
Any hint is appreciated.
Here are a whole lot of them…
:help i_ctrl-v is about insert mode and ranges matter in command-line mode so :help command-mode is totally irrelevant.
When they work on text, Ex commands only work on lines, not arbitrary text. This makes ranges like '<,'> irrelevant in this case.
After carefully reading :help i_ctrl-v_digit, linked from :help i_ctrl-v, we can conclude that it is supposed to be used:
with a lowercase u,
without the +,
without worrying about the case of the value.
So both of these should be correct:
<C-v>u00a9
<C-v>u00A9
But your input is U+00A9 so, even if you somehow manage to "capture" that U+00A9, you won't be able to use it as-is: it must be sanitized first. I would go with a substitution but, depending on how you want to use that value in the end, there are probably dozens of methods:
substitute('U+00A9', '\(\a\)+\(.*\)', '\L\1\2', '')
Explanation:
\(\a\) captures an alphabetic character.
+ matches a literal +.
\(.*\) captures the rest.
\L lowercases everything that comes after it.
\1\2 reuses the two capture groups above.
From there, we can imagine a substitution-based method. Assuming "And I want to generate the following next to it" means that you want to obtain:
U+00A9©
you could do:
v<motion>
y
:call feedkeys("'>a\<C-v>" . substitute(#", '\(\a\)+\(.*\)', '\L\1\2', '') . "\<Esc>")<CR>
Explanation:
v<motion> visually selects the text covered by <motion>.
y yanks it to the "unnamed register" #".
:help feedkeys() is used as low-level way to send a complex series of characters to Vim's input queue. It allows us to build the macro programatically before executing it.
'> moves the cursor to the end of the visual selection.
a starts insert mode after the cursor.
<C-v> + the output of the substitution inserts the appropriate character.
That snippet begs for being turned into a mapping, though.
In case you would like to just convert unicodes to corresponding characters, you could use such nr2char function:
:%s/U+\(\x\{4\}\)/\=nr2char('0x'.submatch(1))/g
Brief explanation
U+\(\x\{4\}\) - search for a specific pattern (U+ and four hexadecimal characters which are stored in group 1)
\= - substitute with result of expression
'0x'.submatch(1) - append 0x to our group (U+00A9 -> 0x00A9)
In case you would like to have unicode character next to text you need to modify slightly right side (use submatch(0) to get full match and . to append)
In case someone wonders how to compose the substitution command:
'<,'>s/\<[uU]+\(\x\+\)\>/\=submatch(0)..' '..nr2char(str2nr(submatch(1), 16), 1)/g
The regex is:
word start
Letter "U" or "u"
Literal "plus"
One or more hex digits (put into "capture group")
word end
Then substituted by (:h sub-replace-expression) concatenation of:
the whole matched string
single space
character by UTF-8 hex code taken from "capture group"
This is to be executed in Visual/command mode and works over selected line range.

Vim split function arguments

I found that I often do refactor:
def function(param1, param2, (...), paramK):
to
def function(
param1,
param2,
(...),
paramK,
):
And tried to write mapping for it.
I started with
command! Split normal! qqqf(a<cr><esc>qqf s<cr><esc>#qq#qf)i<cr><esc>
qqq - reset content of macro q.
f(a<cr><esc> - find bracket, make newline and return to normal mode.
qq - start to record macro q.
f s<cr><esc> - change space to newline.
#q - run macro recursively.
q#q - end recording and run.
f)i<cr><esc> - add last newline before closing bracket.
My idea was, that macro will fail when it won't find space, but something is wrong with that. It raised some questions.
1) How can I check if some motion succeed? e.g. How to check if there is a space in current line?
2) Is there better idea to achieve what I want? Maybe some plugin or clear function?
3) What is wrong with my idea? When I run this combination from hand it works, but while calling :Split it doesn't.
Regarding why it doesn't work quite like it does when you type it manually:
When you type normal! <esc>, Vim parses this as "type the letters <, e, s, c, >". You might be able to insert a literal escape key there by typing <c-v><esc>, but that can look a bit weird in the configuration. Instead, a better way is to use the :exe command (:help :exe):
exe "normal! \<esc>"
The \<esc> gets interpolated by the string to be a literal escape key. So, the exe "normal! ..." gets translated to an invocation to normal! with the keys you're looking for. You also need to escape <cr> the same way. I'd also use \<space> instead of , I'm not entirely sure if a normal space is going to work here. After that, hopefully, you should get the same results as when you type it manually.
As for the actual problem you're trying to solve, I do have a plugin for that: splitjoin. By default, it splits your example like this:
def function(param1,
param2,
(...),
paramK):
pass
But there's a setting you can change to adjust it to your liking. Alternatively, from Jordan Running's link, it seems you could also use the argwrap plugin, which might be more reliable for argument-splitting in particular (splitjoin handles a wider variety of cases, but maybe doesn't do as good with arguments? Not sure.)
Answers:
I don't understand the question. You can search for spaces with f or with the / syntax. Why do you want to do this?
Yes. See below.
The vimrc syntax is super different from normal vim syntax. I don't know why and I don't fully understand it.
Code:
nnoremap <C-r> :s/, */,\r /g<cr>:s/)/\r)<cr>:nohl<cr>
remaps ctrl+r to search for a comma followed by 0 or more spaces and replace that with a comma, newline, and tab. then searches for ) and replaces that with newline and ). then undoes the highlighting it just did.
To enter a literal tab instead of 4 spaces, you'll have to type CtrlVtab in place of the 4 spaces you see in the command

Challenge "One number per line" at VimGolf: How does the #82 solution work?

The 82-th solution of the challenge contains a fragment like the following:
V"=[<C-R><C-A>]<CR>p
I think here V enters the likewise visual mode, and then the help document says that
When typing the '=' after " or CTRL-R the cursor moves to the command-line,
where you can enter any expression (see |expression|). All normal
command-line editing commands are available, including a special history for
expressions. When you end the command-line by typing <CR>, Vim computes the
result of the expression. If you end it with <Esc>, Vim abandons the
expression. If you do not enter an expression, Vim uses the previous
expression (like with the "/" command).
Then I am confused: is that expression ended by an enter, and hence looks like [, following which we press ctrl-a?
If I repeat the sequence as above, some weird behavior happens that I don't think is what is intended. So I must have missed something important.
Thanks in advance for any help or reference.
The "= will evaluate the given expression, if you p(paste) the evaluated result would be converted into string. If you read the doc further, you will see:
The expression must evaluate to a String. A Number is always automatically
converted to a String. For the "p" and ":put" command, if the result is a
Float it's converted into a String. If the result is a List each element is
turned into a String and used as a line. A Dictionary or FuncRef results in
an error message (use string() to convert).
<c-r><c-a> will fill the WORD under cursor.
So the expression is [---...---2,3,5,..] what is this expression? It is a list. And as doc told us, when you p to paste, it will be converted into lines.
What is tricky here is the first element in the list, the -------...-2,
We have:
2 -> 2
-2 -> -2
--2 -> 2
---2 -> -2
.....
Now you can count, how many - before the 2, I think it must be even number. so we have 2 in first line, after you pasted.
I hope that now you understood it better.

How to efficiently switch arguments in vim

I come upon one scenario when editing a file in vim and I still haven't found a way to do it quickly in vim way. When editing a call of a function, I offently put my arguments in a wrong order.
anyFunction(arg2, arg1)
When arriving on this situation, I have to find arg2 / delete it / append it before the ')' / deal with the ', ' / etc.
Isn't it a better way to this task quickly ? I am open to any idea (macro/ shortcut / plugin) even if I'd rather have a 'vim only' way of doing this
You need two things:
A text object to quickly select an argument (as they aren't always that simple like in your example). argtextobj plugin (my improved fork here) does this.
Though you can use delete + visual mode + paste + go back + paste, a plugin to swap text makes this much easier. My SwapText plugin or the already mentioned exchange plugin both do that job.
put this mapping in your _vimrc.
" gw : Swap word with next word
nmap <silent> gw :s/\(\%#\w\+\)\(\_W\+\)\(\w\+\)/\3\2\1/<cr><c-o><c-l>
then in normal mode with the cursor anywhere in arg1 type gw to swap parameters
anyFunction(arg1, arg2)
Explanation:-
arg1 the separator (here a comma) and arg2 are put into regexp memories 1 2 3
the substitute reverses them to 3 2 1
Control-O return to last position
Control-L redraw the screen
Note that the separator is any non-alphanumeric character or string e,g whitespace
I actually made a plugin to deal with a exact situation called argumentative.vim. (Sorry for the plug.)
Argumentative.vim provides the following mappings:
[, and ], motions which will go to the previous or next argument
<, and >, to shift an argument left or right
i, and a, argument text objects. e.g. da,, ci, or yi,
So with this plugin you move to the argument in question and then do a <, or >, as many times as needed. It can also take a count e.g. 2>,.
If you have Tim Pope's excellent repeat.vim plugin installed <, and >, become repeatable with the . command.
I would recommend a plugin: vim-exchange for that:
https://github.com/tommcdo/vim-exchange
This is a perfect use for a regular expression search and replace.
You want to find "anyFunction(", then swap anything up to the ',' with anything from the ',' to the ')'. This is fairly straightforward, using [^,]* for "anything up to the ','" and [^)]* for "anything up to the ')'". Use \(...\) to capture each thing, and \1, \2 to refer to those things in the replacement:
:s#anyFunction(\s*\([^,]*\),\s*\([^)]*\)#anyFunction(\2, \1#g
Note how I use \s* to allow any whitespace between the "anyFunction(" and the first thing, and also between the ',' and the second thing.
If you want this to be able to span multiple lines, you can use \_s instead of \s, and capture the whitespace if you want to maintain the multi-line format:
:s#anyFunction(\(\_s*\)\([^,]*\),\(\_s*\)\([^)]*\)#anyFunction(\1\4,\3\2#g
There is also a multi-line variant of [...] collections, for example \_[^,] meaning "anything (even a new line) except for a ',' " which you could use in the pattern if your use case demands it.
For details, consult the help topics for: /\s, /\_s, /\1, /\(, and /[.
If you want a more general-purpose mapping to use at every location, you can use the cursor position in your regular expression, rather than keying off the function name. The cursor position in a regular expression is matched using \%# as demonstrated here: http://vim.wikia.com/wiki/Exchanging_adjacent_words
Similar to what Peter Rincker suggested (Argumentative), sideways also does what you describe.

How to do something in a function based on the character under the cursor in Vim?

I’m writing a function that edits a certain environment in LaTeX.
The environment basically looks like this:
\begin{quicktikz}
...some stuff...
\end{quicktikz}
or like this:
\begin*{quicktikz}
...some stuff...
\end{quicktikz}
I want to write a function that toggles between the two, when called from within the environment. Since my Vim knowledge ain’t all that, I’m coming up with a simple solution:
Get cursor position with let save_cursor=getpos(".").
Backward search for \begin{quicktikz} using: ?\\begin{quicktikz}\|\\begin\*{quicktikz}.
Search for the { and move left using: normal 0f{h.
Check if the item under cursor equals *:
if it does, do normal x;
if it doesn’t, do normal a*<esc>.
Restore cursor position using call setpos('.',save_cursor).
I know how to do all of this except for step 3. How can I check if the char under the cursor equals to * or not?
If you know a better way of doing this, sharing this would be welcome.
I think the easiest way to retrieve the char under cursor is:
getline(".")[col(".")-1]
Alternatively, you can do it with strpart()
strpart(getline("."), col(".")-1, 1)
The first expression first calls the function getline() passing "." as
argument which means the line where the cursor is positioned will be returned.
Then we use the so called expr8 or expr-[] (see the help) to retrieve a
single character. The number passed comes from another function, col()
which returns the current cursor column. As indexes start in 0, one is subtracted.
You can use it like
if getline(".")[col(".")-1] == '*'
...
Let me propose an alternative implementation of the technique you describe.
:?\\begin\>\zs\*\=?s//\='*'[submatch(0)!='']/|norm!``
The above command consists of two separate commands chained with | (see
:help :bar) in one line. The first one is a substitution (see :help :s)
performed for each line in the specified range,
?\\begin\>\zs\*\=?
According to the range syntax (see :help :range), this range specifies the
only line, that is the previous line where the \\begin\>\zs\*\= pattern
matches the word begin preceded with a backslash and followed by by optional
star character.1 The \zs atom between parts of the pattern
matching \begin and *, sets the start of the match there. So, the match
of the whole pattern is either empty or contains single star character. This
is not necessary for specifying a line in the range, it is useful for reusing
the same pattern later in the :substitute command, where only that star
character or its empty place should be replaced. For details about the
pattern's syntax see :help /\>, :help /\=, :help /\zs.
The substitution itself,
s//\='*'[submatch(0)!='']/
replaces the first occurrence of the last search pattern (which is set by the
backward search in the range) with a string to which the expression
'*'[submatch(0)!=''] evaluates (see :help sub-replace-\=). As the pattern
matches only an empty string or a star character, the subexpression
submatch(0)!='' evaluates to zero if there is no star after \begin, or to
one otherwise. Zero subscript from the string '*' results in a substring
containing the first character of that one-character string. Index one is
equal to the length of the string, therefore subscript results in an empty
string. Thus, when there is a star after \begin, it gets replaced with an
empty string, when a star is not present, zero-width interval just after
\begin is substituted with *.
The second command,
:norm!``
takes advantage of the fact that :substitute command stores the current
cursor position before it actually starts replacement. The `` movement
command jumps back to the position before the latest jump (which occurs in the
aforementioned substitution command) restoring position of the
cursor.2
1 Be careful with search, since in ranges, as usual, it wraps
around the end of file, when the wrapscan option is enabled (it is turned on
by default).
2 Do not confuse `` with the '' command which moves the
cursor to the first non-blank character in the line of the location before the
latest jump.

Resources