Vim stop comment continuation during abbrev expansion - vim

If your Vim settings include 'r' in 'formatoptions' (default) it will automatically insert the current comment leader after hitting [Enter] in insert mode, but this can be undesirable for abbrevs that expand to a single-line comment in languages that don't have separate single/multi-line comment characters.
How do I stop getting this when I press [Enter] after typing the abbrev characters?
# ========
#

This is what I did:
" Eat one character matching the given regex from the input stream, e.g. at
" the end of an abbr expansion. (From :help abbreviations)
func Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunc
set pastetoggle=<F12>
iabbr <silent> sep # ========<F12><CR><F12><C-R>=Eatchar('\r')<CR>
This replaces the user's [Enter] key press with a generated one surrounded by generated key presses that turn paste mode off before and back on afterwards. The final <C-R> completes the <C-R>= sequence.

Related

How to run commands against block-wise visual mode selection?

I have a testing file with the content:
var a = f
ff
fff
Then I moved the cursor on the f character in line 1, ctrl+v selected the two f below it (■ means selection).
var a = ■
■f
■ff
I want to change the text to this:
var a = "f"
"ff"
"fff"
So I executed this command:
:normal i"ctrl+vEscA"
But the quotes were added to the whole line. Is it possible to do operations on only block-wise visual selection (not the whole line)? Note that this example was made only for discussing Vim skills. I'm not trying to solving any problems, just want to learn Vim skills.
The problem is that all :-commands can take linewise range only. Recall that after pressing : in Visual mode you got :'<,'>_ which is a hint strong enough (see :h mark-motions in case you don't remember about backtick vs. single-quote).
So you can't process visually selected text directly but have to yank it into register first.
One solution is to yank-put the text into the lines of its own. Then you can apply your command(s) and move the new text where it should be.
This is how it's done in that "vis" plugin mentioned in the comment by #phd and the linked answer. But it's so big and bloated that we'd better implement such functionality ourselves.
" apply command to a visual selection
" a:cmd - any "range" command
" a:mods - :h command-modifiers
" a:trim - true if we need to trim trailing spaces from selection text
function s:block(cmd, mods, trim) abort
" save last line no
let l:last = line('$')
" yank selected text into the register "9"
silent normal! gv"9y
" put it at buffer's end
call append(l:last, getreg(9, 1, 1))
" trim trailing spaces
if a:trim
execute 'keepj keepp' l:last..'+,$s/\s\+$//e'
endif
" apply our command
" note: we assume that the command never enters Visual mode
execute a:mods l:last..'+,$' a:cmd
" get the changed text back into the register "9"
call setreg(9, getline(l:last + 1, '$'), visualmode())
" clean up
call deletebufline('%', l:last + 1, '$')
" replace the selection
" note: the old text goes into the register "1", "1" into "2", and so on.
" old register "9" is lost anyway, see :h v_p
silent normal! gv"9p
endfunction
" our interface is a user command
command! -bang -range -nargs=1 -complete=command B call s:block(<q-args>, <q-mods>, <bang>0)
Now you can select your block (do not forget about extending it until lines' ends!), and execute:
:'<,'>B! normal! i"^CA"
Note: ^C stands for Ctrl-VCtrl-C
On possible solution:
:'<,'>norm $ciw"ctrl-v ctrl-r""
OBS: Ctrl-v Ctrl-r should be typed literally
Another solution:
:'<,'>s/\w\+$/"&"

Surround visually selected texted with x at beginning and y at end

I know I can surround visually selected text with this macro:
qa " record a macro in buffer a
x " cut the selected text
i " enter insert mode
prefix " type the desired prefix
<esc> " exit insert mode
p " paste the cut text
a " enter insert mode after the pasted text
postfix " type the desired postfix
<esc> " exit insert mode
q " stop recording
I used it to surround a few words with the prefix {{c1:: and postfix :}}. I was then able to repeat the macro with #a.
My question is, how can I permanently map this macro to a command, so that surrounding text in this way will be available to me across sessions? What would I add to .vimrc or surround.vim?
This seems to be more complicated than other related questions in that I want to surround the text with different strings at the beginning and end of the selected text, and also that the selected text will be unique - I am not wanting to surround each instance of a particular string.
I would create a function and map it to some key (F3 in my example below):
vnoremap <F3> :call Surround("prfx_", "_psfx")<Enter>
function! Surround(prefix, postfix)
" get the selection
let selection = #*
" remove selected text
normal gv"xx
" inserting text with prefix and postfix
execute "normal i" . a:prefix . selection . a:postfix
endfunction
So function Surround accepts two arguments:
1st - prefix (the default is currently set to "prfx_")
2nd - postfix (the default is set to "_psfx")
If you want to be able to enter function arguments each time you press F3 then just remove <Enter> from key mapping:
vnoremap <F3> :call Surround("prfx_", "_psfx")

How do I move to the next capital letter?

In vim I can use f followed by a character to go to the next occurrence of that character on the current line. For example, if I have the following (cursor position marked with |):
m|akeBinExprNode = undefined
I can use fB to move to B and dtE to delete until before the E, leaving me with:
make|ExprNode = undefined
I wonder if there's a way to do this that doesn't involve typing the exact character, i.e. some kind of motion that means "go to the next capital letter" and/or "go to right before the next capital letter".
When I searched for that I would be happy to just have the "native" solution: just enter in command mode:
/\u
which stands for "search for an uppercase letter". After that just move between capital letters with n and N (shift + n).
I would recommand the following script : camelcasemotion. It allows you to jump , delete inner 'camel case words', using ,+ normal navigation [w,b,e] etc...
I have found this vim tip for moving within CamelCaseWords that might be useful:
" Use one of the following to define the camel characters.
" Stop on capital letters.
let g:camelchar = "A-Z"
" Also stop on numbers.
let g:camelchar = "A-Z0-9"
" Include '.' for class member, ',' for separator, ';' end-statement,
" and <[< bracket starts and "'` quotes.
let g:camelchar = "A-Z0-9.,;:{([`'\""
nnoremap <silent><C-Left> :<C-u>call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%^','bW')<CR>
nnoremap <silent><C-Right> :<C-u>call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%$','W')<CR>
inoremap <silent><C-Left> <C-o>:call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%^','bW')<CR>
inoremap <silent><C-Right> <C-o>:call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%$','W')<CR>
vnoremap <silent><C-Left> :<C-U>call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%^','bW')<CR>v`>o
vnoremap <silent><C-Right> <Esc>`>:<C-U>call search('\C\<\<Bar>\%(^\<Bar>[^'.g:camelchar.']\#<=\)['.g:camelchar.']\<Bar>['.g:camelchar.']\ze\%([^'.g:camelchar.']\&\>\#!\)\<Bar>\%$','W')<CR>v`<o
wilhelmtell's answer will work unless 'ignorecase' parameter is set. If 'smartcase' is activated or 'noignorecase' then it is okay.
However a pattern that can replace [A-Z] is \u (see :help /\u or more globally :help pattern). Therefore you can replace your mapping with:
:nnoremap <leader>C /\u<CR>:nohlsearch<CR>
:nmap <leader>C /[A-Z]<CR>:nohlsearch<CR>
Then in normal mode <leader>C (which by default means \C)

Identifying Identical Blocks of Code

Say I have multiple blocks of the following code in a file (spaces is irrelavent):
sdgfsdg dfg
dfgdfgf ddfg
dfgdfgdfg dfgfdg
How do you find/highlight all the occurrences?
What I ideally want to do is to visually select the code block and then press search to find all occurrences.
Maybe you should look at :
Search for visually selected text
I've taken it from here
Try this. Include this script somewhere in your runtimepath (see :help runtimepath). A simple option would be to put it in your vimrc. Visually select the thing you want to search for and press ,/ (the comma key and then the forward-slash key).
" Search for other instances of the current visual range
" This works by:
" <ESC> Cancel the visual range (it's location is remembered)
" / Start the search
" <C-R>= Insert the result of an expression on
" the search line (see :help c_CTRL-R_= )
" GetVisualRange()<CR> Call the function created below
" <CR> Run the search
vmap ,/ <ESC>/<C-R>=GetVisualRange()<CR><CR>
" Create the function that extracts the contents of the visual range
function! GetVisualRange()
" Get the start and end positions of the current range
let StartPosition = getpos("'<")
let EndPosition = getpos("'>")
" Prefix the range with \V to disable "magic"
" See :help \V
let VisualRange = '\V'
" If the start and end of the range are on the same line
if StartPosition[1] == EndPosition[1]
" Just extract the relevant part of the line
let VisualRange .= getline(StartPosition[1])[StartPosition[2]-1:EndPosition[2]-1]
else
" Otherwise, get the end of the first line
let VisualRange .= getline(StartPosition[1])[StartPosition[2]-1:]
" Then the all of the intermediate lines
for LineNum in range(StartPosition[1]+1, EndPosition[1]-1)
let VisualRange .= '\n' . getline(LineNum)
endfor
" Then the start of the last line
let VisualRange .= '\n' . getline(EndPosition[1])[:EndPosition[2]-1]
endif
" Replace legitimate backslashes with double backslashes to prevent
" a literal \t being interpreted as a tab
let VisualRange = substitute(VisualRange, '\\[nV]\#!', '\\\\', "g")
" Return the result
return VisualRange
endfunction
The text being searched for is stored in the / register. You can't yank or delete directly into this register, but you can assign to it using `let'.
Try this:
Use visual mode to highlight the code you want to search for
Type "ay to yank that highlighted selection into register a
Type :let #/ = #a to copy register a into the search register /
At this point, all code matching your selection will be highlighted, and you can navigate through occurrences using n/N just as you would a regular search.
Of course, you can use any temporary register instead of a. And it shouldn't be too difficult to get this command sequence mapped for easy use.
Quick and dirty partial solution:
:set hlsearch
*
The hlsearch option (on by default in some vim configs, but I always turn it off) makes vim highlight all found instances of the current search. Pressing * in normal mode searches for the word under the cursor. So this will highlight all instances of the word under the cursor.

How to "apply" backspace characters within a text file (ideally in vim)

I have a log file with backspace characters in it (^H). I'm looking through the file in Vim and it can be quite hard to see what's going on.
Ideally I'd like to be able to "apply" all the ^H on a given line/range so that I can see the final result.
I'd much rather do this within Vim on a line-by-line basis, but a solution which converts the whole file is better than nothing.
Turn on the 'paste' option (using :set paste), and then press dd i <CTRL-R> 1 <ESC> on each line that you want to apply the backspaces to. This also works if you delete multiple lines, or even the whole file.
The key here is that you are using <CTRL-R> 1 in insert mode to 'type out' the contents of register 1 (where your deleted lines just got put), and 'paste' option prevents Vim from using any mappings or abbreviations.
I googled this while trying to remember the command I had used before to `apply' backspaces, and then I remembered it: col -b - here is the manpage. (It does a little more and comes from BSD or more exactly AT&T UNIX as the manpage says, so if you are on Linux you may need to install an additional package, on debian its in bsdmainutils.)
Simplistic answer:
:%s/[^^H]^H//g
where ^^H is:
Literal ^ character
Ctrl-V Ctrl-H
and repeat it couple of times (until vim will tell you that no substitutions have been made
If you want without repetition, and you don't mind using %!perl:
%!perl -0pe 's{([^\x08]+)(\x08+)}{substr$1,0,-length$2}eg'
All characters are literal - i.e. you don't have to do ctrl-v ... anywhere in above line.
Should work in most cases.
All right, here is a bare-metal solution.
Copy this code into a file named crush.c:
#include <stdio.h>
// crush out x^H sequences
// there was a program that did this, once
// cja, 16 nov 09
main()
{
int c, lc = 0;
while ((c = getchar()) != EOF) {
if (c == '\x08')
lc = '\0';
else {
if (lc)
putchar(lc);
lc = c;
}
}
if (lc)
putchar(lc);
}
Compile this code with your favorite compiler:
gcc crush.c -o crush
Then use it like this to crush out those bothersome sequences:
./crush <infilename >outfilename
Or use it in a pipeline ("say" is a speech-to-text app on the Mac)
man date | ./crush | say
You can copy crush to your favorite executable directory (/usr/local/bin, or some such) and then reference it as follows
man date | crush | say
Just delete all occurrences of .^H (where . is the regex interpretation of .):
:s/.^H//g
(insert ^H literally by entering Ctrl-V Ctrl-H)
That will apply to the current line. Use whatever range you want if you want to apply it to other lines.
Once you done one :s... command, you can repeat on another line by just typing :sg (you need to g on the end to re-apply to all occurrences on the current line).
How about the following function? I've used \%x08 instead of ^H as it's easier to copy and paste the resulting code. You could type it in and use Ctrl-V Ctrl-H if you prefer, but I thought \%x08 might be easier. This also attempts to handle backspaces at the start of the line (it just deletes them).
" Define a command to make it easier to use (default range is whole file)
command! -range=% ApplyBackspaces <line1>,<line2>call ApplyBackspaces()
" Function that does the work
function! ApplyBackspaces() range
" For each line in the selected lines
for index in range(a:firstline, a:lastline)
" Get the line as a string
let thisline = getline(index)
" Remove backspaces at the start of the line
let thisline = substitute(thisline, '^\%x08*', '', '')
" Repeatedly apply backspaces until there are none left
while thisline =~ '.\%x08'
" Substitute any character followed by backspace with nothing
let thisline = substitute(thisline, '.\%x08', '', 'g')
endwhile
" Remove any backspaces left at the start of the line
let thisline = substitute(thisline, '^\%x08*', '', '')
" Write the line back
call setline(index, thisline)
endfor
endfunction
Use with:
" Whole file:
:ApplyBackspaces
" Whole file (explicitly requested):
:%ApplyBackspaces
" Visual range:
:'<,'>ApplyBackspaces
For more information, see:
:help command
:help command-range
:help function
:help function-range-example
:help substitute()
:help =~
:help \%x
Edit
Note that if you want to work on a single line, you could do something like this:
" Define the command to default to the current line rather than the whole file
command! -range ApplyBackspaces <line1>,<line2>call ApplyBackspaces()
" Create a mapping so that pressing ,b in normal mode deals with the current line
nmap ,b :ApplyBackspaces<CR>
or you could just do:
nmap ,b :.ApplyBackspaces<CR>
Here's a much faster Awk filter that does the same:
#!/usr/bin/awk -f
function crushify(data) {
while (data ~ /[^^H]^H/) {
gsub(/[^^H]^H/, "", data)
}
print data
}
crushify($0)
Note that where ^^H appears, the first caret in ^^H is a caret (shift-6) and the second caret with H is entered (into vim) by typing CTRL-v CTRL-H
Here's a Bash-based filter you can use to process the whole file:
#!/bin/bash
while read LINE; do
while [[ "$LINE" =~ '^H' ]]; do
LINE="${LINE/[^^H]^H/}"
done
echo "$LINE"
done
Note that where ^H appears, it is entered into vim using CTRL-v CTRL-h, and the ^^H is entered as SHIFT-6 CTRL-v CTRL-h.

Resources