using wildcards in vimrc abbreviations - vim

I'm setting up my .vimrc for writing some HTML and currently have these lines:
abb <buffer> <h1> <h1></h1><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h2> <h2></h2><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h3> <h3></h3><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h4> <h4></h4><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h5> <h5></h5><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
abb <buffer> <h6> <h6></h6><ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
Which automatically adds a closing tag to any opening h-tag I write, moves the cursor to the middle of them and gets rid of the whitespace with the function from :helpgrep Eatchar
My qestion is, if there is a more elgant way of doing this, by telling vim to take take <h[1-6]> and adding a closing tag with the same number, instead of having to define each case specifically?
Or for that matter, is there a way to define this whole process of adding a closing tag and moving the cursor back as a function and then just specifying all the tags I want it to be applied to? Because below this I have a quite a few lines for a bunch of other tags that look almost identical.

I've written a function to satisfy my similar requirement recently. This feature can be found in other tools like VSCode.
When I type >, it'll try to look back to check if > is in a tag like <div>. If so, it will complete it and set cursor at <div>|</div>.
Compared with emmet or snippets, this way feels more straightforward.
function! CompleteTag()
let line = getline('.')
let end = col('.') - 1
let begin = end - 1
let tagname_regexp = '[a-zA-Z0-9-]'
while begin > 0
if line[begin] !~ tagname_regexp
break
endif
let begin -= 1
endwhile
if line[begin] == '<'
let tagname = line[begin+1:end-1]
let move = MoveBack(len(tagname)+3)
return '></'.tagname.'>'.move
endif
return '>'
endfunction
function! MoveBack(n)
let move = ''
for i in range(0, a:n-1)
let move .= "\<Left>"
endfor
return move
endfunction
autocmd FileType jsx,html,vue,svelte inoremap<buffer> > <c-r>=CompleteTag()<cr>
autocmd FileType jsx,html,vue,svelte inoremap<buffer> <c-cr> <cr><esc>O

Neither :help abbreviations nor :help mapping can use a wildcard on the left hand side. One solution to that kind of problem is to loop over heading levels and create your abbreviations programatically:
for level in range(1, 6)
execute "abbreviate <buffer> <h" .. level .. "> <h" .. level .. "></h" .. level .. "><ESC>?<<CR>i<C-R>=Eatchar('\\m\\s\\<bar>/')<CR>"
endfor
It certainly is a tad more clever but I wonder if the loss of legibility is really worth it.

I'm not sure how to parameterize a single abbreviation; I suspect you'll need to delve into functions for that. You can, however, define a map consisting of the trailing part:
inoremap nowsp <ESC>?<<CR>i<C-R>=Eatchar('\m\s\<bar>/')<CR>
then use that map in your abbreviations
abb <buffer> <h1> <h1></h1>nowsp
abb <buffer> <h2> <h2></h2>nowsp
" etc

Related

In vim, how to split a word and flip the halves? FooBar => BarFoo

I sometimes write a multi-word identifier in one order, then decide the other order makes more sense. Sometimes there is a separator character, sometimes there is case boundary, and sometimes the separation is positional. For example:
$foobar becomes $barfoo
$FooBar becomes $BarFoo
$foo_bar becomes $bar_foo
How would I accomplish this in vim? I want to put my cursor on the word, hit a key combo that cuts the first half, then appends it to the end of the current word. Something like cw, but also yanking into the cut buffer and then appending to the current word (eg ea).
Nothing general and obvious comes to mind. This is more a novelty question than one of daily practical use, but preference is given to shortest answer with fewest plugins. (Hmm, like code golf for vim.)
You can use this function, it swaps any word of the form FooBar, foo_bar, or fooBar:
function! SwapWord()
" Swap the word under the cursor, ex:
" 'foo_bar' --> 'bar_foo',
" 'FooBar' --> 'BarFoo',
" 'fooBar' --> 'barFoo' (keeps case style)
let save_cursor = getcurpos()
let word = expand("<cword>")
let match_ = match(word, '_')
if match_ != -1
let repl = strpart(word, match_ + 1) . '_' . strpart(word, 0, match_)
else
let matchU = match(word, '\u', 1)
if matchU != -1
let was_lower = (match(word, '^\l') != -1)
if was_lower
let word = substitute(word, '^.', '\U\0', '')
endif
let repl = strpart(word, matchU) . strpart(word, 0, matchU)
if was_lower
let repl = substitute(repl, '^.', '\L\0', '')
endif
else
return
endif
endif
silent exe "normal ciw\<c-r>=repl\<cr>"
call setpos('.', save_cursor)
endf
Mapping example:
noremap <silent> gs :call SwapWord()<cr>
Are you talking about a single instance, globally across a file, or generically?
I would tend to just do a global search and replace, e.g.:
:1,$:s/$foobar/$barfoo/g
(for all lines, change $foobar to $barfoo, every instance on each line)
EDIT (single occurrence with cursor on the 'f'):
3xep
3xep (had some ~ in there before the re-edit of the question)
4xea_[ESC]px
Best I got for now. :)
nnoremap <Leader>s dwbP
Using Leader, s should now work.
dw : cut until the end of the word from cursor position
b : move cursor at the beginning of the word
P : paste the previously cut part at the front
It won't work for you last example though, you have to add another mapping to deal with _ .
(If you don't know what Leader is, see :help mapleader)

Mapping/macro to 'smartly' auto-create pairs of apostrophes in vim (and ignore contractions)

I'm currently using closepairs for my auto-closing needs, and it works pretty well. However, there is one caveat -- apostrophes. Don't get me wrong, I need apostrophes closed all the time. I don't want to just disable them. But whenever I type in plain text, whenever there are any contractions (I'm, Don't, Can't)...these apostrophes get made.
Now I could just type to delete them as soon as they can, but doing it every time is a bit impractical.
Does anyone know how I can possibly modify the closepairs script to only autoclose single quotes/apostrophes if they are the start of a word? That is, they are preceded by a whitespace character?
Here is the current code:
inoremap <expr> " <SID>pairquotes('"')
inoremap <expr> ' <SID>pairquotes("'")
function! s:pairquotes(pair)
let l:col = col('.')
let l:line = getline('.')
let l:chr = l:line[l:col-1]
if a:pair == l:chr
return "\<right>"
else
return a:pair.a:pair."\<left>"
endf
I don't know closepairs, but the AutoClose - Inserts matching bracket, paren, brace or quote plugin handles this well. You'll find a list of plugin alternatives on the Vim Tips Wiki.
Are you sure you want to autocomplete only after whitespace? In that case, something like function('string') would not autocomplete after the parenthesis.
Regardless, you can check the previous character against some regex. For example, to avoid autocompletion after letters:
function! s:pairquotes(pair)
let l:line = getline('.')
let l:col = col('.')
let l:chr = l:line[l:col - 1]
let l:prev = l:line[l:col - 2]
if l:chr == a:pair
return "\<right>"
elseif l:prev !~ "[A-Za-z]"
return a:pair . a:pair . "\<left>"
else
return a:pair
endif
endfunction
Note that there are exceptions even with this conservative example, like typing r'regex' in Python, so it might also make sense to define filetype-specific behavior.

Jump to the end of a long list of repeated pattern

I have a big file with a lot of lines that share the same pattern, something like this:
dbn.py:206 ... (some other text) <-- I am here
dbn.py:206 ... (some other text)
...
(something I don't know) <-- I want to jump here
Is there a quick way in Vim to jump to the place where the succession of dbp.py:206 ends?
/^\(dbn.py\)\#!
Matches first line which does not start with the text inside the escaped parentheses.
If you want quick access to this you could add a vmap which yanks the visually selected text and inserts it in the right spot (but first escaping it with escape(var, '/').
Try this vmap: vmap <leader>n "hy<Esc>/^\(<C-R>=escape(#h,'/')<CR>\)\#!<CR>
Press n when visually selecting the text you wish to skip and you should be placed on the next first line which does not begin with the selection.
I just write a function to select identical lines:
nnoremap vii :call SelectIdenticalLines()<CR>
fun! SelectIdenticalLines()
let s = getline('.')
let n = line('.')
let i = n
let j = n
while getline(i)==s && i>0
let i-=1
endwhile
while getline(j)==s && j<=line('$')
let j+=1
endwhile
call cursor(i+1, 0)
norm V
call cursor(j-1, 0)
endfun
type vii to select identical lines (feel free to change the key-binding)
type zf to fold them.
type za to toggle folding
It's handy when you want to squeeze several empty line.
It acts like C-x C-o in emacs.
One option is to go to the bottom of the file and search backwards for the last line you want, then go down one:
G ?^dbn\.py:206?+1

How to define a new Vim operator with a parameter?

I have been looking to map a new operator in Vim that takes an extra parameter.
For example, we know that ciw will “cut inside word” and will put you into Insert mode. What I am looking for is having a custom action to replace c (for example, s) that takes movements like iw, but requires an extra parameter.
A trivial example would be:
Given a line in a text file
Execute siw* in Normal mode (assuming the cursor is on the first column) for it to surround the first word with * like so:
*Given* a line in a text file
I know, this is what the most excellent surround.vim plugin does. But I am just giving an example here, and looking for an answer as to how to get the mappings so that the above work.
I tried playing with onoremap and opfunc, but can’t seem to get them to play the way I want.
So, what I am looking for is a combination of motions plus operator pending mappings.
Here is an example implementation of the command described
in the question, for illustrative purposes.
nnoremap <silent> s :set opfunc=Surround<cr>g#
vnoremap <silent> s :<c-u>call Surround(visualmode(), 1)<cr>
function! Surround(vt, ...)
let s = InputChar()
if s =~ "\<esc>" || s =~ "\<c-c>"
return
endif
let [sl, sc] = getpos(a:0 ? "'<" : "'[")[1:2]
let [el, ec] = getpos(a:0 ? "'>" : "']")[1:2]
if a:vt == 'line' || a:vt == 'V'
call append(el, s)
call append(sl-1, s)
elseif a:vt == 'block' || a:vt == "\<c-v>"
exe sl..','..el 's/\%'..sc..'c\|\%'..ec..'c.\zs/\=s/g|norm!``'
else
exe el 's/\%'..ec..'c.\zs/\=s/|norm!``'
exe sl 's/\%'..sc..'c/\=s/|norm!``'
endif
endfunction
To get user input, the function InputChar() is used, assuming that
the required argument is a single character.
function! InputChar()
let c = getchar()
return type(c) == type(0) ? nr2char(c) : c
endfunction
If it is necessary to accept a string argument, change the call to
InputChar() in Surround() to the call to input(), instead.
The title of the question might cause misunderstanding. What you want to do is to define a new operator like y, d and c, neither motions nor text objects, isn't it?
:help :map-operator describes how to define a new operator. To take a parameter like the surround plugin, use getchar() in your 'operatorfunc'.
Though :help :map-operator describes the basics, it's a bit troublesome to deal with arguments passed to 'operatorfunc'. You can use vim-operator-user to simplify the handling of arguments. With this plugin, surround-like operator can be written as follows:
function! OperatorSurround(motion_wise)
let _c = getchar()
let c = type(_c) == type(0) ? nr2char(_c) : _c
if c ==# "\<Esc>" || c == "\<C-c>"
return
endif
let bp = getpos("'[")
let ep = getpos("']")
if a:motion_wise ==# 'char'
call setpos('.', ep)
execute "normal! \"=c\<Return>p"
call setpos('.', bp)
execute "normal! \"=c\<Return>P"
elseif a:motion_wise ==# 'line'
let indent = matchstr(getline('.'), '^\s*')
call append(ep[1], indent . c)
call append(bp[1] - 1, indent . c)
elseif a:motion_wise ==# 'block'
execute bp[1].','.ep[1].'substitute/\%'.ep[2].'c.\zs/\=c/'
execute bp[1].','.ep[1].'substitute/\%'.bp[2].'c\zs/\=c/'
call setpos('.', bp)
else
endif
endfunction
call operator#user#define('surround', 'OperatorSurround')
map s <Plug>(operator-surround)
If you really want to define your own text objects, please consider vim-textobj-user.
Consider one of the plugins for writing custom text objects. For example:
https://github.com/kana/vim-textobj-user

vim - set auto indent to fill the leading space with space or tabstop

It seems if we enable 'ai', vim will fill the the leading space with tabstop.
I can make it fill with just space with 'et'. I don't like a C file mixed with space and tabstop.
My vimrc:
set ts=4 et
set ai
set hlsearch
syntax on
filetype plugin indent on
autocmd FileType make setlocal noexpandtab
However, in some condition I do need to input tabstop when I hit the 'TAB' on keyboard, for example, in makefile and some others.
The 'autocmd FileType' command is not good: I can't add every file type in vimrc.
What I want is simple:
autoindent to fill leading area with
space;
when hit 'TAB' on keyboard, tabstop
input, not space (so no 'et')
How to do it?
inoremap <expr> <tab> ((getline('.')[:col('.')-2]=~'\S')?("\<C-v>\t"):(repeat(' ', &ts-((virtcol('.')-1)%&ts))))
It does the same as #Lynch answer if I read it correctly.
You can also use <C-v><Tab>: this will insert <Tab> without invoking any mappings and ignores expandtab unless you remapped <C-v> or <C-v><Tab> for some reason.
If you want to just insert tab do
inoremap <Tab> <C-v><Tab>
It will ignore expandtab setting.
I did it using a function. I tested it, but maybe in some particular case you will have to fix some bugs. Try adding this to your vimrc:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
if pos != 1 && substitute(before, "[ \t]", "", "g") != ""
let insert = "\t"
else
let insert = " "
endif
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
Basicaly it does remap your key in visual mode to the function Inserttab(). Also note that if you change ts for something other than 4 it will still output 4 spaces instead of two because the value is hard coded.
Also im not very familiar with vim scripts, but I think all the variables used will be global which is a bad thing.
I forgot to mention that to "see" white spaces you can use set list. You disable this with set nolist. Also in normal mode you can use ga to see information about the character your cursor is on.
Edit
I realise that you may want to insert tab at the beginin of the line. My script insert space at the begining and tab anywhere else.
If you really want a tab every time you hit tab key you could simply use this:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
let insert = "\t"
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
But I dont see the point, with this version you will never be able to indent manually from insert mode.
One way to do it is
:set sw=4 (or whatever you want)
:set ts=46 (or some large number)
Then autoindent will not insert tabs unless you reach 46 spaces, in which case you can put in a higher number.
Only drag about this is if someone else is using tabs, then you have to reset ts to agree with the file you are editing. On the other hand, it will make the tabs immediately obvious, which can be desirable as well.

Resources