More detailed advice on advanced mapping - vim

I've found substantial use for commands that will find differences in two lists of words, and the best (and really only) solution to this I've found is this command:
g/^/kl |if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') |'ld
I want to make a mapping in my _vimrc file that will execute this line whenever I hit F2 for example, but I haven't been able to make it work.
If someone could explain character by character what this line actually does, I think it would make all the difference in the world. I've seen a dozen or so articles on vim mapping and none of them explain what things like / or ^ or \.*{}^$/' do in contexts like this one.
When are : needed in mappings? I've seen some examples with and most without.
When is <CR> needed?
Thanks in advance

This sounds like a job for Awk
:%!awk '!a[$0]{a[$0]=1;print}'
However you asking a two questions:
What does :g/^/kl |if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') |'ld do?
How can I make a mapping to this?
The Mapping
Let's start with "How can I make a mapping to this?":
:nnoremap <f2> :g/^/kl<bar>if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW')<bar>'ld<cr>
The trick is to use <bar> instead of | and to actually execute the command with <cr>. See :h keycodes.
What does this do?
It goes over every line of the buffer with :g/^/ and deletes lines that are the same as a line from above, if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') and d. The confusing parts to me are the following:
Using marks needlessly i.e. :k and range with the :d command.
A complicated building of the regex for the search() function. By using the \V (very nomagic) we can reduce the line noise in the regex: '\V\^'.escape(getline('.'),'\').'\$'
Why are you doing an O(N^2) operation when you can do an O(N)?
Simplify the command
g/^/if search('\V\^'.escape(getline('.'),'\').'\$','bWn') | d | endif
We remove the needless marks and simplify the escaping. I also added the endif to show the end of the if statement (this can be optionally left off because it will be assumed).
:g/{pat}/{cmd} The :global command runs {cmd} on ever line matching {pat}
:g/^/ is a common idiom to run a command on every line, since all lins have a beginning, ^.
if {expr} | {cmds} | endif. Execute {cmds} if the expression, {expr}, evaluates to true
search({pat}, {flags}) search for {pat} in the buffer. {flag} alter the search behavior
search() returns the line number of a match or 0 for no match found
b flag for search() means search backwards
W flag means do not wrap around the buffer when searching
n do not move the cursor
escape({str}, {chars}) escape {chars} with \
\V pattern uses very no magic meaning all regex meta characters need to be escaped
\^ and \$ are escaped for start and end of line because of \V option
:delete or :d for short delete the current line
I suggest you use the awk solution at the start of this answer.
:nnoremap <f2> :%!awk '!a[$0]{a[$0]=1;print}'<cr>
For more help see the following:
:h :range!
:h :g
:h :d
:h :l
:h :if
:h search(
:h escape(
:h /\V

Related

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

Remove single line breaks but keep empty lines with Vim

Is it possible to remove single line breaks but to keep empty lines (double line breaks I guess?) in Vim? I'm using Vim's HardPencil plugin to do some word processing, and I like the hard line breaks because it makes it easier to bounce around the file. However, I need to share this information in LibreOffice Writer or Word, and hard line breaks won't work well if others edit the file. To clarify, this is what I have:
Line1.
Line2.
Line3.
Line4.
Line5.
And this is what I would like:
Line1. Line2. Line3.
Line4. Line5.
Essentially hard line breaks should be replaced with a space, and double line breaks should be preserved.
I've tried variations of :%s/\n but can't get the double line breaks to be preserved. I've found similar questions here and here, but these are not Vim specific.
You can replace line+line break with just line, and use negative look ahead \(\n\)\#! to make sure it's not followed by an empty line:
:%s/\(.\+\)\n\(\n\)\#!/\1 /
#Peter_Rincker's cleaner solution:
:%s/.\+\zs\n\ze./ /
Use gw (or gq) with a motion (like G).
gwG
gw is Vim's paragraph formatting operator which "reflows" text to the desired 'textwidth'. You may want to temporarily set 'textwidth' to 0 for unlimited text width (:set tw=0).
There some other ways as well do this as well:
Use :%!fmt- same idea as above but use fmt external tool and a filter.
:v/^$/,//-j - squeezes non-empty lines together. Fails if the end of the range does not have a blank line
:g/\v(.+\zs\n)+/,-//j - Also squeezes non-empty lines together, but avoids the range error of the previous method.
A substitution - See #Fabricator solution for an example
For more help see:
:h gw
:h operator
:h 'tw'
:h filter
:h :range!
:h :range
:h :v
:h :j
:h :g
:h magic
:h /\zs

Vim: How to delete the same block of text over the whole file

I'm reviewing some logs with Java exception spam. The spam is getting is making it hard to see the other errors.
Is is possible in vim to select a block of text, using visual mode. Delete that block every place it occurs in the file.
If vim can't do it, I know silly question, vim can do everything. What other Unix tools might do it?
Sounds like you are looking for the :global command
:g/pattern/d
The :global command takes the form :g/{pat}/{cmd}. Read it as: run command, {cmd}, on every line matching pattern, {pat}.
You can even supply a range to the :delete (:d for short) command. examples:
:,+3d
:,/end_pattern/d
Put this togehter with the :global command and you can accomplish a bunch. e.g. :g/pat/,/end_pat/d
For more help see:
:h :g
:h :d
:h :range
Vim
To delete all matching lines:
:g/regex/d
To only delete the matches themselves:
:%s/regex//g
In either case, you can copy the visual selection to the command line by yanking it and then inserting it with <C-r>". For example, if your cursor (|) is positioned as follows:
hello wo|rld
Then you can select world with viw, yank the selection with y, and then :g/<C-r>"/d.
sed
To delete all matching lines:
$ sed '/regex/d' file
To only delete the matches themselves:
$ sed 's/regex//g' file
grep
To delete all matching lines:
$ grep -v 'regex' file
grep only operates line-wise, so it's not possible to only delete matches within lines.
you can try this in vim
:g/yourText/ d
Based on our discussion in the comments, I guess a "block" means several complete lines. If the first and last lines are distinctive, then the method you gave in the comments should work. (By "distinctive" I mean that there is no danger that these lines occur anywhere else in your log file.)
For simplifications, I would use "ay$ to yank the first line into register a and "by$ to yank the last line into register b instead of using Visual mode. (I was going to suggest "ayy and "byy, but that wold capture the newlines)
To be on the safe side, I would anchor the patterns: /^{text}$/ just in case the log file contains a line like "Note that {text} marks the start of the Java exception." On the command line, I would use <C-R>a and <C-R>b to paste in the contents of the two registers, as you suggested.
:g/^<C-R>a$/,/^<C-R>b$/d
What if the yanked text includes characters with special meaning for search patterns? To be on the really safe side, I would use the \V (very non-magic) modifier and escape any slashes and backslashes:
:g/\V\^<C-R>=escape(#a, '/\')<CR>\$/,/\V\^<C-R>=escape(#b, '/\')<CR>\$/d
Note that <C-R>= puts you on a fresh command line, and you return to the main one with <CR>.
It is too bad that \V was not available when matchit was written. It has to deal with text from the buffer in a search pattern, much like this.

preventing trailing whitespace when using vim abbreviations

I am a new user of vim (gvim in windows), and have found abbreviations a nice time saver - however they would be even better if i could stop the trailing whitespace at times.
I have some directories that i use a lot, and so i added some abbreviation/path pairs to my _vimrc:
:ab diR1 C:/dirA/dira/dir1/
:ab diR2 C:/dirA/dirb/dir2/
etc ...
Now when i type diR1 <space> i get C:/dirA/dira/dir1/[]| where the whitespace is represented by [] and the cursor is the | character. I would like to get rid of the [] == whitespace.
This is a minor complaint: however you seem to be able to customise everthing else in Vim so i figured i'd ask -- is it possible to avoid the trailing whitespace when one uses abbreviations in vim?
An alternate tool used within Vim is a good answer - my objective is to save re-typing frequently used directory structures, but to have the cursor handy as i would almost always add something to the end, such as myFile.txt.
The trailing white space (doubtless due to the fact that the space triggered the abbreviation) which i backspace over before adding myFile.txt to the end is less annoying than typing the whole thing over and over, but it would be ideal if i could avoid doing so ...
pb2q answer is exactly what you want in your current scenario, but does not fully answer the question presented in the title. This exact problem is addressed in the vim help file. See :helpgrep Eatchar. The example it gives is this:
You can even do more complicated things. For example, to consume the space
typed after an abbreviation: >
func Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunc
iabbr <silent> if if ()<Left><C-R>=Eatchar('\s')<CR>
You would put the Eatchar function in your ~/.vimrc file and then use like so in your abbreviations:
iabbr <silent> diR1 C:/dirA/dira/dir1/<c-r>=Eatchar('\m\s\<bar>/')<cr>
This would "eat" any trailing white space character or a slash. Note that I used iabbr instead of just abbr, because it is rare to actually want abbreviations to expand in command line mode. You must be careful with abbreviations in command line mode as they will expand in unexpected places such as searches and input() commands.
For more information see:
:h abbreviations
:helpgrep Eatchar
:h :helpgrep
This is possible, without more customization than just abbrev.
The abbreviation is being triggered by the space character, as you know. The space is a non-keyword character, and remains after the abbreviation is expanded.
But there are other ways to trigger the expansion, such as other non-keyword characters, including /. So if you instead define your abbreviations like this:
:ab diR1 C:/dirA/dira/dir1
That is, without the trailing path separator, then you can type diR1/, have the abbreviation expand for you because of the slash /, and continue typing, appending to your path with a file name.
Alternately, you can force abbreviation expansion using Ctrl-]. That is, type the abbreviation: diR1, with no following space or other non-keyword character, and then type Ctrl-]. The abbreviation will be expanded and you'll remain in insert mode, and can append your file name to the expanded path.
Check out :help abbreviations, there may be something else useful for you there, including more complicated constructions for always consuming e.g. the space character that triggered the abbreviation.
Instead of abbreviations, you could use mappings. They're expanded as soon as you have typed the last character of the mapping, so there won't be a trailing space:
:inoremap diR1 c:/dirA/dira/dir1
The downside for this approach is that the letters you type while a mapping could be expanded are not displayed until the mapping is finished. This takes some using used to.

Counting occurrences in Vim without marking the buffer changed

In order to know how many times a pattern exists in current buffer, I do:
:%s/pattern-here/pattern-here/g
It gives the number of occurrences of the pattern, but is obviously cumbersome and also has the side-effect of setting the 'changed' status.
Is there a more elegant way to count?
To avoid the substitution, leave the second pattern empty, and add the ā€œnā€ flag:
:%s/pattern-here//gn
This is described as an official tip.
:help count-items
In VIM 6.3, here's how you do it.
:set report=0
:%s/your_word/&/g # returns the count without substitution
In VIM 7.2, here's how you'd do it:
:%s/your_word/&/gn # returns the count, n flag avoids substitution
:!cat %| grep -c "pattern"
It's not exactly vim command, but it will give you what you need from vim.
You can map it to the command if you need to use it frequently.
The vimscript IndexedSearch enhances the Vim search commands to display "At match #N out of M matches".
Put the cursor on the word you want to count and execute the following.
:%s/<c-r><c-w>//gn
See :h c_ctrl-r_ctrl-w
vimgrep is your friend here:
vimgrep pattern %
Shows:
(1 of 37)

Resources