I want to be able to write \bit and have it expand to something in vim. How do I encode a backslash in the left-hand side of an abbreviation, though?
I tried all of these:
:iab \bit replacement_text
:iab <Bslash>bit replacement_text
:iab <bs>bit replacement_text
but got E474: Invalid argument for all of these.
The map_backslash help-topic suggests <Bslash>, but this doesn't seem to work.
You can define your abbreviation on "bit", and then test if it is preceded by "", if so, return the new text, or "bit" otherwise.
function! s:Expr(default, repl) expr
if getline('.')[col('.')-2]=='\'
return "\<bs>".a:repl
else
return a:default
endif
endfunction
:inoreab bit <c-r>=<sid>Expr('bit', 'foobar')<cr>
That's the kind of tricks I used in MapNoContext().
EDIT: see :h abbreviations for the reasons why what you asked can't be achieved directly.
EDIT2: It can be easily encapsulated this way:
function! s:DefIab(nore, ...) abort
let opt = ''
let i = 0
while i != len(a:000)
let arg = a:000[i]
if arg !~? '<buffer>\|<silent>'
break
endif
let opt .= ' '.arg
let i += 1
endwhile
if i+2 != len(a:000)
throw "Invalid number of arguments"
endif
let lhs = a:000[i]
let rhs = a:000[i+1]
exe 'i'.a:nore.'ab'.opt.' '.lhs.' <c-r>=<sid>Expr('.string(lhs).', '.string(rhs).')<cr>'
endfunction
command! -nargs=+ InoreabBSlash call s:DefIab('nore', <f-args>)
And used with a simple:
InoreabBSlash <buffer> locbit foobar
or
InoreabBSlash bit foobar
I suggest using backslash on both sides, vim is happy that way:
inoreabbr \bit\ replacement_text
Note that I am using the "nore" version of abbr, better to be clear if you don't intend a recursive expansion. I have been using the below abbreviations for a long time and they work great:
inoreabbr \time\ <C-R>=strftime("%d-%b-%Y # %H:%M")<CR>
inoreabbr \date\ <C-R>=strftime("%d-%b-%Y")<CR>
:set iskeyword+=\
in vimrc_tex (or just vimrc) works perfectly.
you could
inoremap \bit replacementtext
Also if you dont like the lag an alternative leader like backtick ` (above the tab for me)
:iab `f foobar
if you are not using them in your code often
You can only use a backslash as a prefix for an abbreviation if it's only got a single character following it, so :iab \b replacementtext will work.
Related
Is there a way to make the 'f' and 't' command wrap around the line? For example, if I have
Hello, my name is _intz,
where _ denotes my cursor position, I would like to be able to press fl for vim to place my cursor on the first l on the line.
Similarly, I would ideally like the , and ; commands to also wrap on the current line.
Thank you
No, this is not possible without implementing the feature yourself.
Note that fF are universally expected to mean "next on the line" and tT to mean "previous on the line", both of which being extremely useful in their own right. Instead of changing their meaning, and thus reducing the overall usefulness of Vim, you should consider making new commands.
Something like these quick and dirty mappings:
" move the cursor on first occurrence of character on the line
nnoremap <expr> <key> '0f' . nr2char(getchar())
" move the cursor before first occurrence of character on the line
nnoremap <expr> <key> '0t' . nr2char(getchar())
See :help <expr>, :help nr2char(), :help getchar().
With the help of this answer https://vi.stackexchange.com/questions/29167/determine-if-there-is-a-matching-character-on-the-current-line-past-the-cursor, the following maps <c-f> to allow that gives it the functionality of f with same line wrapping.
function!Neweff()
let character = nr2char(getchar())
let beforejump = getpos('.')
execute 'norm! f'.character.''
let afterjump = getpos('.')
if beforejump == afterjump
let firstcharacter = getline(".")[0]
execute 'norm! 0'
if character !=# firstcharacter
execute 'norm! f'.character.''
endif
endif
endfunction
nnoremap <c-f> :call Neweff()<CR>
I want to be able to write \bit and have it expand to something in vim. How do I encode a backslash in the left-hand side of an abbreviation, though?
I tried all of these:
:iab \bit replacement_text
:iab <Bslash>bit replacement_text
:iab <bs>bit replacement_text
but got E474: Invalid argument for all of these.
The map_backslash help-topic suggests <Bslash>, but this doesn't seem to work.
You can define your abbreviation on "bit", and then test if it is preceded by "", if so, return the new text, or "bit" otherwise.
function! s:Expr(default, repl) expr
if getline('.')[col('.')-2]=='\'
return "\<bs>".a:repl
else
return a:default
endif
endfunction
:inoreab bit <c-r>=<sid>Expr('bit', 'foobar')<cr>
That's the kind of tricks I used in MapNoContext().
EDIT: see :h abbreviations for the reasons why what you asked can't be achieved directly.
EDIT2: It can be easily encapsulated this way:
function! s:DefIab(nore, ...) abort
let opt = ''
let i = 0
while i != len(a:000)
let arg = a:000[i]
if arg !~? '<buffer>\|<silent>'
break
endif
let opt .= ' '.arg
let i += 1
endwhile
if i+2 != len(a:000)
throw "Invalid number of arguments"
endif
let lhs = a:000[i]
let rhs = a:000[i+1]
exe 'i'.a:nore.'ab'.opt.' '.lhs.' <c-r>=<sid>Expr('.string(lhs).', '.string(rhs).')<cr>'
endfunction
command! -nargs=+ InoreabBSlash call s:DefIab('nore', <f-args>)
And used with a simple:
InoreabBSlash <buffer> locbit foobar
or
InoreabBSlash bit foobar
I suggest using backslash on both sides, vim is happy that way:
inoreabbr \bit\ replacement_text
Note that I am using the "nore" version of abbr, better to be clear if you don't intend a recursive expansion. I have been using the below abbreviations for a long time and they work great:
inoreabbr \time\ <C-R>=strftime("%d-%b-%Y # %H:%M")<CR>
inoreabbr \date\ <C-R>=strftime("%d-%b-%Y")<CR>
:set iskeyword+=\
in vimrc_tex (or just vimrc) works perfectly.
you could
inoremap \bit replacementtext
Also if you dont like the lag an alternative leader like backtick ` (above the tab for me)
:iab `f foobar
if you are not using them in your code often
You can only use a backslash as a prefix for an abbreviation if it's only got a single character following it, so :iab \b replacementtext will work.
I typed :autocmd FileType python :iabbrev <buffer> iff if:<left> as this tutorial told.
The output was
if :
Why is there a space between if and ":"?
I assume you're using the space bar after you type iff? If so, it's because of the <left>. This is positioning your cursor one to the left, i.e. between the f and the ":". Once the space bar is accepted your cursor is in between the two characters so it puts a space between them. You can try the command without the <left> and see if that does what you need. If not, you'll need to let us know exactly what output you're looking for us to be able to help you. Also see: :help abbrev if you haven't already.
Abbreviation's are triggered by non-keyword (e.g. ., <cr>, <space>, etc), <esc>, or <c-]>. Typing iff alone will is not enough to expand the abbreviation. You typed iff<space> which is enough to expand the abbreviation and puts the <space> inside your expanded abbreviation. You can use <c-]> to expand abbreviations without inserting any extra characters. e.g. iff<c-]>
Eatchar
I however find using <c-]> to be unappealing. Vim's documentation gives us an alternative, the Eatchar function. This function will consume a key matching some pattern and not output it.
function! Eatchar(pat)
let c = nr2char(getchar(0))
return (c =~ a:pat) ? '' : c
endfunction
iabbr <buffer> iff if:<left><c-r>=Eatchar('\s')<cr>
Rails.vim like abbreviations
You can take this even further and make Rails.vim-esque abbreviations which only expand on <tab> or a supplied pattern. Think of these as lightweight snippets.
function! RailsExpand(root, good, ...)
let c = nr2char(getchar(0))
if c == "" || c =~ (a:0 ? a:1 : "\t")
return a:good
else
return a:root . c
endif
endfunction
iabbr <buffer> iff <c-r>=RailsExpand('iff', "if:\<left>")<cr>
Now iff<tab> will expand properly. However defining abbreviations like this is a mess.
function! Railsabbrev(root, good)
let good = substitute(a:good, '[\"|]', '\\&', "g")
let good = substitute(good, '<', '\\<lt>', "g")
let root = substitute(a:root, '[\"|]', '\\&', "g")
let root = substitute(root, '<', '\\<lt>', "g")
execute "iabbr <buffer> " . a:root . " <c-r>=RailsExpand(\"" . root . "\", \"" . good . "\")<cr>"
endfunction
command! -nargs=* Railsabbrev call Railsabbrev(<f-args>)
Now you can use :Railsabbrev to define your <tab> expanding abbreviation. Example:
Railsabbrev iff if:<left>
Snippets
Sometimes abbreviations are just too simple or too tricky to maintain for multiline expansions. If this is the case I suggest you look for a good snippet plugin. Good choices are UltiSnips or vim-snipmate. Look at their documentation on how to expand and create your own snippets.
More help
:h Abbreviations
:helpg Eatchar
Often times when reviewing log files in vim, I'll highlight interesting lines using marks. At some point, I'd like to be able to copy all of the interesting lines (either all marked lines, or a list of marks) to either a register or another file (it doesn't really matter which; the goal is to facilitate writing a summary). I haven't been able to find any built in way to do this; is it possible in vim?
I suppose it's probably a fairly straightforward function; probably looking something like this, but my vimscript abilities are very weak:
for cur_mark in list_of_marks
goto mark
yank current line and append to register
Has anyone ever written anything similar that they can point me to?
Thanks
EDIT: I posted the accepted solution at https://github.com/mikeage/vim-yankmarks
As always, there are few things that are more motivating than asking for help. Here's what I came up with; feedback welcome.
function! Yankmark()
let save_cursor = getpos(".")
let n = 0
" I should really make this a parameter...
let marks_to_yank="abcdefghijklmnopqrstuvwxyz"
let nummarks = strlen(marks_to_yank)
" Clear the a register
let #a=''
while n < nummarks
let c = strpart(marks_to_yank, n, 1)
" Is the mark defined
if getpos("'".c)[2] != 0
" using g' instead of ' doesn't mess with the jumplist
exec "normal g'".c
normal "Ayy
endif
let n = n + 1
endwhile
call setpos('.', save_cursor)
endfunction
Mikeage had a great idea; here's a more refined version of his function turned into a command:
":YankMarks [{marks}] [{register}]
" Yank all marked (with [a-z] / {marks} marks) lines into
" the default register / {register} (in the order of the
" marks).
function! s:YankMarks( ... )
let l:marks = 'abcdefghijklmnopqrstuvwxyz'
let l:register = '"'
if a:0 > 2
echohl ErrorMsg
echomsg 'Too many arguments'
echohl None
return
elseif a:0 == 2
let l:marks = a:1
let l:register = a:2
elseif a:0 == 1
if len(a:1) == 1
let l:register = a:1
else
let l:marks = a:1
endif
endif
let l:lines = ''
let l:yankedMarks = ''
for l:mark in split(l:marks, '\zs')
let l:lnum = line("'" . l:mark)
if l:lnum > 0
let l:yankedMarks .= l:mark
let l:lines .= getline(l:lnum) . "\n"
endif
endfor
call setreg(l:register, l:lines, 'V')
echomsg printf('Yanked %d line%s from mark%s %s',
\ len(l:yankedMarks),
\ len(l:yankedMarks) == 1 ? '' : 's',
\ len(l:yankedMarks) == 1 ? '' : 's',
\ l:yankedMarks
\) . (l:register ==# '"' ? '' : ' into register ' . l:register)
endfunction
command! -bar -nargs=* YankMarks call <SID>YankMarks(<f-args>)
A different way of accomplishing this might be using the :global command. The global command takes the form :g/{pattern}/{cmd}. The command, {cmd}, will be executed on all lines matching {pattern}.
Append lines matching a pattern to a register:
:g/pattern/yank A
Append matching line to a log file:
:g/pattern/w >> file.log
Of course if you want to find line matching a mark you can match it in your pattern. The following pattern matches a line with mark m.
:g/\%'m/w >> file.log
To do something like this. (Note: I am using \v to turn on very magic)
:g/\v(%'a|%'b|%'m)/yank A
Of course if a pattern won't work you can do this by hand. Instead of marking the lines just build up the lines as you go. Just yank a line to an uppercase register to append.
"Ayy
Or do a write append with a range of a single line
:.w >> file.log
For more help see
:h :g
:h :w_a
:h /\%'m
:h /\v
You can do something like:
:redir #a
:silent marks XYZN
:redir END
"ap
That way the output of the :marks command will be redirected to the a register. Note, that it will only lists (in the above case) the X, Y, Z and N marks (as the arguments), and if there was an a register, it will be deleted/overwritten.
Also note, that it might not give the desired output, but gives you a starting point...
I like the solution from Mikeage, though I would probably solve this with the multiselect - Create multiple selections and operate plugin. This also has the benefit that you don't run out of marks.
With the plugin, you can select lines with <Leader>msa or :MSAdd. Finally, yank all lines with:
:let #a=''
:MSExecCmd yank A
If you use an upper-case register name when yanking into a specific register, Vim will append the yanked content instead of overwriting the register's value.
So, for example:
"ayy - yank current line to register a, overwriting
[move]
"Ayy - append this line to register a
[move]
"ap - paste all yanked material
See :help quotea for more details.
Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.
According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.
This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.
s/^ /\t/
Or do it using vim's builtin functionality:
:set tabstop=4 "four spaces will make up for one tab
:set noexpandtab "tell vim to keep tabs instead of inserting spaces
:retab "let vim handle your case
By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).
I've written a simple func for it. Anyway it will work only for 4-space tab.
fu! Fixspaces()
while search('^\t* \{4}') != 0
execute ':%s/^\t*\zs \{4}/\t/g'
endwhile
endfu
You can suggest better solution, if exists, and I will use it with pleasure.
The issue is that this func replaces spaces in strings as well.
David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:
<SPACE><SPACE><TAB>something...
you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...
function! TabsToSpaces(...)
let ts = &tabstop
let pos = getpos('.')
while search('^ *\zs\t', "w") != 0
let l:curpos = getcharpos('.')
" The number of spaces needed depends upon the position of the <TAB>
let numsp = 1 + ts - ( curpos[2] % ts )
if numsp == 9
let numsp = 1
endif
silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
endwhile
if a:0 == 0
echo 'Changed leading tabs to spaces'
endif
call setpos('.', pos)
endfunction
function! SpacesToTabs(...)
let ts = &tabstop
let pos = getpos('.')
" First normalize all tabs to spaces
call TabsToSpaces("quiet")
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
if a:0 == 0
echo 'Changed leading spaces to tabs'
endif
call setpos('.', pos)
endfunction
" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>
I took Martin's answer and improved on it a bit if anyone's interested:
function Fixspaces()
let ts = &tabstop
let pos = getpos('.')
if &expandtab
while search('^ *\t') != 0
silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
endwhile
echo 'Changed tabs to spaces'
else
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
echo 'Changed spaces to tabs'
endif
call setpos('.', pos)
endfunction
This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.