What does this VimL regular expression mean? %[MRHWY] - vim

I see some viml code like
if &foo =~# '%[MRHWY]' && &foo !~# '%[mrhwy]'
What do those strings '%[MRHWY]' and '%[mrhwy]' mean? Do they mean something in context of the =~# comparison that may be different from another context?

Case sensitive (=~#) pattern matching to a collection, []. In this instance, it is matching on % and a collection of letters which may show up in one's statusline. Specifically:
%[MRHWY] can match '%M', '%R', '%H', '%W', or '%Y'.
What may distract you, as it did for me, is that there is also a sequence pattern match, %[], which matches as much of the collection as it can; its routine stops at the first atom that doesn't match (see help %[] vs. [] for more examples and details). However, this is just not the case here, since there is a quote before %, in front of a collection.
With a quick search, I see this line of code is used in the fugitive.vim package where it checks the status line for the sequence of letters 'MRHWY':
function! fugitive#statusline(...) abort
if !exists('b:git_dir')
return ''
endif
let status = ''
if s:buffer().commit() != ''
let status .= ':' . s:buffer().commit()[0:7]
endif
let status .= '('.fugitive#head(7).')'
if &statusline =~# '%[MRHWY]' && &statusline !~# '%[mrhwy]'
return ',GIT'.status
else
return '[Git'.status.']'
endif
endfunction
(BTW: If you want to see what these letters each mean, see help :statusline. An example: M is modified text flag, R is readonly flag, etc.)

Related

Is there a way to use abbreviation in vim that ignore special characters?

I'm writing an SQL database script, and I use iab to upper case special words such as DATABASE or TABLE. I have three questions:
Is there a better tool to use? I prefer to avoid using a plugin as they usually come with functionality that bothers me.
Is there a way to make iab be case insensitive? I mean that it will correct both set and Set into SET?
Is there a way to make iab work when there are special or certain characters following/preceding the word? For example, I want exec and exec( to be changed into EXEC and EXEC( respectively. I can create two entries, but the question is if I can do this in a single line.
Better ways: No, short of a plugin I can't see what else would be as useful as abbreviations in this context. I note that sql_iabbr.vim is at least easy to read, and doesn't seem to contain much in the way of hidden gotchas. It's essentially a whole bunch of this:
inoreabbr <silent> <buffer> select <C-R>=SqlIab_ReplaceConditionally('select', 'SELECT')<CR>
with the following function:
function! SqlIab_ReplaceConditionally(original, replacement)
" only replace outside of comments or strings (which map to constant)
let elesyn = synIDtrans(synID(line("."), col(".") - 1, 0))
if elesyn != hlID('Comment') && elesyn != hlID('Constant')
let word = a:replacement
else
let word = a:original
endif
let g:UndoBuffer = a:original
return word
endfunction
I think it's also worth pointing out Abolish for more powerful abbreviations.
Case insensitive: again, I think the answer is 'No' (based on a reasonably thorough check of the help.
Special/certain characters: sorry, if you need that done in one abbreviation I think you're out of luck again!
So many questions:
Is there a better tool to use? I prefer to avoid using a plugin as they usually come with functionality that bothers me.
Tim Pope's Abolish.vim is the closest plugin which fits your needs. However with the more advanced expansions I feel like it too will fall short. I am not sure about your past plugin experiences, but Tim's plugins are usually well behaving and narrowly focused.
Is there a way to make iab be case insensitive? I mean that it will correct both set and Set into SET?
No, there is no native way that I am aware of. I would recommend using Abolish if you do this regularly and have simpler expansions. Or go all the way and use a snippet plugin.
Is there a way to make iab work when there are special or certain characters following/preceding the word? For example, I want exec and exec( to be changed into EXEC and EXEC( respectively. I can create two entries, but the question is if I can do this in a single line.
This can be done with a fancier expansion. It is in fact very similar to what Rails.vim does with it's abbreviations.
Add the following to your ~/.vim/after/ftplugin/sql.vim:
function! s:selective_expand(root, good, ...)
let [pat, extra; _] = a:0 ? a:000 : ['', '']
let c = nr2char(getchar(0))
if c == "" || c == "\t"
return a:good
elseif c =~ '\s'
return a:good . c
elseif pat != '' && c =~# pat
return a:good . extra
else
return a:root . c
endif
endfunction
function! s:paren_expand(root, good)
return s:selective_expand(a:root, a:good, '[(]', "()\<left>")
endfunction
function! s:sql_expand(root, ...)
let good = a:0 ? a:1 : toupper(a:root)
let good = substitute(good, '[\"|]', '\\&', "g")
let good = substitute(good, '<', '\\<lt>', "g")
let f = 'selective_expand'
let root = a:root
if root =~ '($' && (!a:0 || good !~ '($')
let root = substitute(root, '($', '', '')
let good = substitute(good, '($', '', '')
let f = 'paren_expand'
endif
let root = substitute(root, '[\"|]', '\\&', "g")
let root = substitute(root, '<', '\\<lt>', "g")
let roots = [root]
if root !~# '\u' && root =~# '^\l'
call add(roots, substitute(root, '^\l', '\u&', ''))
endif
for r in roots
execute "iabbr <buffer> " . r . " <c-r>=<SID>" . f . "(\"" . r . "\", \"" . good . "\")<cr>"
endfor
endfunction
command! -nargs=* Sqlabbrev call <SID>sql_expand(<f-args>)
Now you can create abbreviations in your ~/.vim/after/sql.vim file like so:
Sqlabbrev select
Sqlabbrev join INNER\ JOIN
Sqlabbrev exec(
Sqlabbrev Takes 2 arguments similar to iabbrev. However behaves slightly differently:
Abbreviations will always be local to the buffer
If only 1 argument then the expansion will be the same as the first argument but made uppercase
Using <tab>/<c-]> will make the expansion without any following spaces
Using abbreviation followed by space will expand and add space
If the abbreviation ends with ( then expand with ending of () and place cursor inside the parens
You must escape spaces via \ or <space>. Can use keycodes
Will create some case expansions if no uppercase letter is found in abbrevation. e.g. Sqlabbrev select will expand select and Select.

Ignore specific characters in syntax matching

I'm writing a vim syntax highlighting file for a specific language. For some keywords, specific characters are ignored (, and -and _). In other words, these are exactly the same:
foo f_oo f-o,o
Is there a way I can ignore these characters when using eg. syn match ?
There's no special option for this; you have you make a pattern which optionnally allows the specific chars between EACH char; example with foo:
f[,_-]*o[,_-]*o
Note that - has to be placed at the end of the [] block (see :h /[]).
As it is fastidious to write, you can create a function to make it for you:
func! CreatePattern(word)
let l:s = ''
let l:first = 1
for i in range(len(a:word))
if !l:first
let l:s .= '[,_-]*'
else
let l:first = 0
endif
let l:s .= a:word[i]
endfor
return l:s
endf
After this, we have:
:echo CreatePattern('bar')
b[,_-]*a[,_-]*r
Then, you can use :syn match with the help of :execute :
:exe 'syn match MyHighlightGroup /'.CreatePattern('foo').'/'
or using matchadd() :
:call matchadd('MyHighlightGroup', CreatePattern('foo'))
Please note that the function above will only work with plain words, but it will break any pattern with contains special pattern chars. You may have to write a better function for such needs.

vim: complete depending on previous character

I want to create a mapping that will changed the ins-completion depending on the character just before the cursor. If the character is { then I want tag completion, if its : i want normal completion (that depends on the complete option) and if the characters is a backslash plus some word (\w+) I want dictionary completion. I have the following in my ftplugin/tex/latex_settings.vim file:
setlocal dictionary=$DOTVIM/ftplugin/tex/tex_dictionary
setlocal complete=.,k
setlocal tags=./bibtags;
function! MyLatexComplete()
let character = strpart(getline('.'), col('.') - 1, col('.'))
if character == '{'
return "\<C-X>\<C-]>"
elseif character == ':'
return "\<C-X>\<C-N>"
else
return "\<C-X>\<C-K>"
endif
endfunction
inoremap <C-n> <c-r>=MyLatexComplete()<CR>
This doesn't work and I don't know how to fix it.
Edit: This seems to work but I'm want a conditional that checks for \w+ (backslash plus any word) and a final one that gives a message "No match found".
function! MyLatexComplete()
let line = getline('.')
let pos = col('.') - 1
" Citations (comma for multiple ones)
if line[pos - 1] == '{' || line[pos - 1] == ','
return "\<C-X>\<C-]>"
" Sections, equations, etc
elseif line[pos - 1] == ':'
return "\<C-X>\<C-N>"
else
" Commands (such as \delta)
return "\<C-X>\<C-K>"
endif
endfunction
In your original function you have mistakes:
strpart() takes string, offset and length arguments, while you supplied two offsets.
col('.') is one character past the end-of-line. I.e. len(getline('.'))==col('.')+1 meaning that strpart(getline('.'), col('.')-1) is always empty.
You have fixed these issues in the second variant. But if you want conditional check for \command you need not just last character. Thus I would suggest matching slice
let line=getline('.')[:col('.')-2]
if line[-1:] is# '{' || line[-1:] is# ','
return "\<C-X>\<C-]>"
elseif line[-1:] is# ':'
return "\<C-X>\<C-N>"
elseif line =~# '\v\\\w+$'
return "\<C-X>\<C-K>"
else
echohl ErrorMsg
echomsg 'Do not know how to complete: use after {, comma or \command'
echohl None
return ''
endif
. Note some things:
Never use == for string comparison without # or ? attached. This does not matter in this case, but you should make yourself used. ==# and ==? both ignore value of 'ignorecase' setting (first acts as if 'noignorecase' was set, second as if 'ignorecase' was set). I use even stricter is#: a is# b is like type(a)==type(b) && a ==# b.
Same for =~: use =~#.
Due to backwards compatibility string[-1] (string[any_negative_integer]) is always empty. Thus I have to use line[-1:].
Never use plain :echoerr. It is unstable: in terms that you cannot say for sure whether or not this will break execution flaw (:echoerr breaks execution if put inside :try block and does not do so otherwise). echohl ErrorMsg|echomsg …|echohl None never breaks execution, throw … and try|echoerr …|endtry break always.
To spot preceding LaTeX commands you can use the following regular expression on your line variable:
line =~ '\\\w\+$'
(as you can see, the regex is similar to the Perl expression you guessed at, but requires some the characters to be escaped).
To echo a "No match found" message, you could return an appropriate :echoerr command:
return "\<C-o>:echoerr 'No match found'\<CR>"
But this has the side-effect of hijacking insert-mode for a moment... maybe it's cleaner just to return no matches as an empty string?
So your final function would look something like this:
function! MyLatexComplete()
let line = getline('.')
let pos = col('.') - 1
" Citations (comma for multiple ones)
if line[pos - 1] == '{' || line[pos - 1] == ','
return "\<C-X>\<C-]>"
" Sections, equations, etc
elseif line[pos - 1] == ':'
return "\<C-X>\<C-N>"
elseif line =~ '\\\w\+$'
" Commands (such as \delta)
return "\<C-X>\<C-K>"
else
" Echo an error:
return "\<C-o>:echoe 'No match found'\<CR>"
endif
endfunction

Yanking all marked lines in vim

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.

How to make Vim's global command :g/ work on per occurrence basis

Typically Vim's global command :g// works on per line basis. Is it possible to make it work on per occurrence basis as there could be more than one occurrence on a line.
Not a direct answer, but you could use something like :rubydo, which will run some ruby scriptlet per line of code. Combining that with gsub in ruby should get you the ability to do just about anything per occurrence of a match. Of course, you will need to do it with ruby code, which may not give you access to everything that you might need without hassle (register appending would be annoying for instance)
:[range]rubyd[o] {cmd} Evaluate Ruby command {cmd} for each line in the
[range], with $_ being set to the text of each line in
turn, without a trailing <EOL>. Setting $_ will change
the text, but note that it is not possible to add or
delete lines using this command.
The default for [range] is the whole file: "1,$".
You can try:
command! -bang -nargs=1 -range OGlobal
\ <line1>,<line2>call s:Global("<bang>", <f-args>)
function! s:Global(bang, param) range
let inverse = a:bang == '!'
" obtain the separator character
let sep = a:param[0]
" obtain all fields in the initial command
let fields = split(a:param, sep)
" todo: handle inverse
let l = a:firstline
while 1
let l = search(fields[0], 'W')
if l == -1 || l > a:lastline
break
endif
exe fields[1]
endwhile
endfunction
Which you can use as :global, except that the command name is different (and that the bang option as not been implemented yet)

Resources