vim: complete depending on previous character - vim

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

Related

How to grouping some character in atom in vimscript?

Here I want to minify my code:
let s:next_col = getline('.')[col('.') - 1]
if s:next_col is# "'" || s:next_col is# '"' || s:next_col is# '`' || ')' || s:next_col is# ']' || s:next_col is# '}' || s:next_col is# '>'
return "\<right>"
endif
The above code work as expected. But when I try convert it to this:
let s:next_col = getline('.')[col('.') - 1]
" I also try to add / before \V, not sure if its correct but that also won't work
if s:next_col is# '\V\("\|`\|)\|]\|}\|>\)' || s:next_col is# "'"
return "\<right>"
endif
Now, the only it work will be if the next column is '.
I wonder how I can grouping these character ' " ) ] } > and check if one of those matching with my variable?
is and its variants are only useful for comparing lists or dictionaries. If you want to compare strings, then the right operators would be ==, !=, =~, !~ and their case variants.
Here, you can simply use =~ to compare with a regular expression pattern and, since punctuation marks don't have casing, there is no need for # or ?:
let s:next_col = getline('.')[col('.') - 1]
if s:next_col =~ "['\"`)\\]}>]"
return "\<right>"
endif
[...] is a collection. See :help /[].
', `, ), }, and > are all used as-is because they have no special meaning in Vim's regex dialect.
" is escaped with a \ because double quotes are used for quoting the pattern.
] is escaped once because ] closes the collection, which makes it special in Vim's regex dialect, and a second time because \ is special in double quotes. See :help expr-".

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

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.)

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 do I search the open buffers in Vim?

I'd like to search for text in all files currently open in vim and display all results in a single place. There are two problems, I guess:
I can't pass the list of open files to :grep/:vim, especially the names of files that aren't on the disk;
The result of :grep -C 1 text doesn't look good in the quickfix window.
Here is a nice example of multiple file search in Sublime Text 2:
Any ideas?
Or
:bufdo vimgrepadd threading % | copen
The quickfix window may not look good for you but it's a hell of a lot more functional than ST2's "results panel" if only because you can keep it open and visible while jumping to locations and interact with it if it's not there.
ack and Ack.vim handle this problem beautifully. You can also use :help :vimgrep. For example:
:bufdo AckAdd -n threading
will create a nice quickfix window that lets you hop to the cursor position.
Like the answer of Waz, I have written custom commands for that, published in my GrepCommands plugin. It allows to search over buffers (:BufGrep), visible windows (:WinGrep), tabs, and arguments.
(But like all the other answers, it doesn't handle unnamed buffers yet.)
I really liked romainl's answer, but there were a few sticky edges that made it awkward to use in practice.
The following in your .vimrc file introduces a user command Gall (Grep all) that addresses the issues that I found irksome.
funct! GallFunction(re)
cexpr []
execute 'silent! noautocmd bufdo vimgrepadd /' . a:re . '/j %'
cw
endfunct
command! -nargs=1 Gall call GallFunction(<q-args>)
This will allow case-sensitive searches like this:
:Gall Error\C
and case-insensitive:
:Gall error
and with spaces:
:Gall fn run
Pros
It will only open the Quickfix window, nothing else.
It will clear the Quickfix window first before vimgrepadd-ing results from each buffer.
The Quickfix window will contain the locations of all matches throughout the open buffers, not just the last visited.
Use :Gall repeatedly without any special housekeeping between calls.
Doesn't wait on errors and displays results immediately.
Doesn't allow any autocmd to run, speeding up the overall operation.
Ambivalent features
Doesn't preemptively jump to any occurrence in the list. :cn gets second result or CTRL-w b <enter> to get to the first result directly.
Cons
If there's only one result, you'll have to navigate to it manually with CTRL-w b <enter>.
To navigate to a result in any buffer quickly:
:[count]cn
or
:[count]cp
E.g. :6cn to skip 6 results down the list, and navigate to the correct buffer and line in the "main" window.
Obviously, window navigation is essential:
Ctrl-w w "next window (you'll need this at a bare minimum)
Ctrl-w t Ctrl-w o "go to the top window then close everything else
Ctrl-w c "close the current window, i.e. usually the Quickfix window
:ccl "close Quickfix window
If you close the Quickfix window, then need the results again, just use:
:cw
or
:copen
to get it back.
I made this function a long time ago, and I'm guessing it's probably not the cleanest of solutions, but it has been useful for me:
" Looks for a pattern in the open buffers.
" If list == 'c' then put results in the quickfix list.
" If list == 'l' then put results in the location list.
function! GrepBuffers(pattern, list)
let str = ''
if (a:list == 'l')
let str = 'l'
endif
let str = str . 'vimgrep /' . a:pattern . '/'
for i in range(1, bufnr('$'))
let str = str . ' ' . fnameescape(bufname(i))
endfor
execute str
execute a:list . 'w'
endfunction
" :GrepBuffers('pattern') puts results into the quickfix list
command! -nargs=1 GrepBuffers call GrepBuffers(<args>, 'c')
" :GrepBuffersL('pattern') puts results into the location list
command! -nargs=1 GrepBuffersL call GrepBuffers(<args>, 'l')
An improved (on steroids) version of Waz's answer, with better buffer searching and special case handling, can be found below (The moderators wouldn't let me update Waz's answer anymore :D).
A more fleshed out version with binds for arrow keys to navigate the QuickFix list and F3 to close the QuickFix window can be found here: https://pastebin.com/5AfbY8sm
(When i feel like figuring out how to make a plugin i'll update this answer. I wanted to expedite sharing it for now)
" Looks for a pattern in the buffers.
" Usage :GrepBuffers [pattern] [matchCase] [matchWholeWord] [prefix]
" If pattern is not specified then usage instructions will get printed.
" If matchCase = '1' then exclude matches that do not have the same case. If matchCase = '0' then ignore case.
" If prefix == 'c' then put results in the QuickFix list. If prefix == 'l' then put results in the location list for the current window.
function! s:GrepBuffers(...)
if a:0 > 4
throw "Too many arguments"
endif
if a:0 >= 1
let l:pattern = a:1
else
echo 'Usage :GrepBuffers [pattern] [matchCase] [matchWholeWord] [prefix]'
return
endif
let l:matchCase = 0
if a:0 >= 2
if a:2 !~ '^\d\+$' || a:2 > 1 || a:2 < 0
throw "ArgumentException: matchCase value '" . a:2 . "' is not in the bounds [0,1]."
endif
let l:matchCase = a:2
endif
let l:matchWholeWord = 0
if a:0 >= 3
if a:3 !~ '^\d\+$' || a:3 > 1 || a:3 < 0
throw "ArgumentException: matchWholeWord value '" . a:3 . "' is not in the bounds [0,1]."
endif
let l:matchWholeWord = a:3
endif
let l:prefix = 'c'
if a:0 >= 4
if a:4 != 'c' && a:4 != 'l'
throw "ArgumentException: prefix value '" . a:4 . "' is not 'c' or 'l'."
endif
let l:prefix = a:4
endif
let ignorecase = &ignorecase
let &ignorecase = l:matchCase == 0
try
if l:prefix == 'c'
let l:vimgrep = 'vimgrep'
elseif l:prefix == 'l'
let l:vimgrep = 'lvimgrep'
endif
if l:matchWholeWord
let l:pattern = '\<' . l:pattern . '\>'
endif
let str = 'silent ' . l:vimgrep . ' /' . l:pattern . '/'
for buf in getbufinfo()
if buflisted(buf.bufnr) " Skips unlisted buffers because they are not used for normal editing
if !bufexists(buf.bufnr)
throw 'Buffer does not exist: "' . buf.bufnr . '"'
elseif empty(bufname(buf.bufnr)) && getbufvar(buf.bufnr, '&buftype') != 'quickfix'
if len(getbufline(buf.bufnr, '2')) != 0 || strlen(getbufline(buf.bufnr, '1')[0]) != 0
echohl warningmsg | echomsg 'Skipping unnamed buffer: [' . buf.bufnr . ']' | echohl normal
endif
else
let str = str . ' ' . fnameescape(bufname(buf.bufnr))
endif
endif
endfor
try
execute str
catch /^Vim\%((\a\+)\)\=:E\%(683\|480\):/ "E683: File name missing or invalid pattern --- E480: No match:
" How do you want to handle this exception?
echoerr v:exception
return
endtry
execute l:prefix . 'window'
"catch /.*/
finally
let &ignorecase = ignorecase
endtry
endfunction

How do I run a vim script that alters the current buffer?

I'm trying to write a beautify.vim script that makes C-like code adhere to a standard that I can easily read.
My file contains only substitution commands that all begin with %s/...
However, when I try to run the script with my file open, in the manner :source beautify.vim, or :runtime beautify.vim, it runs but all the substitute commands state that their pattern wasn't found (patterns were tested by entering them manually and should work).
Is there some way to make vim run the commands in the context of the current buffer?
beautify.vim:
" add spaces before open braces
sil! :%s/\%>1c\s\#<!{/ {/g
" beautify for
sil! :%s/for *( *\([^;]*\) *; *\([^;]*\) *; *\([^;]*\) *)/for (\1; \2; \3)/
" add spaces after commas
sil! :%s/,\s\#!/, /g
In my tests the first :s command should match (it matches when applied manually).
I just recently wrote a similar beautifier script but I implemented it in what I think is a more flexible way; plus, I tried to come up with a mechanism to avoid substituting stuff within strings.
" {{{ regex silly beautifier (avoids strings, works with ranges)
function! Foo_SillyRegexBeautifier(start, end)
let i = a:start
while i <= a:end
let line = getline(i)
" ignore preprocessor directives
if match(line, '^\s*#') == 0
let i += 1
continue
endif
" ignore content of strings, splitting at double quotes characters not
" preceded by escape characters
let chunks = split(line, '\(\([^\\]\|^\)\\\(\\\\\)*\)\#<!"', 1)
let c = 0
for c in range(0, len(chunks), 2)
let chunk = chunks[c]
" add whitespace in couples
let chunk = substitute(chunk, '[?({\[,]', '\0 ', 'g')
let chunk = substitute(chunk, '[?)}\]]', ' \0', 'g')
" continue like this by calling substitute() on chunk and
" reassigning it
" ...
let chunks[c] = chunk
endfor
let line = join(chunks, '"')
" remove spaces at the end of the line
let line = substitute(line, '\s\+$', '', '')
call setline(i, line)
let i += 1
endw
endfunction
" }}}
Then I define a mapping that affects the whole file in normal mode, and only the selected lines in visual mode. This is good when you have some carefully formatted parts of the file that you don't want to touch.
nnoremap ,bf :call Foo_SillyRegexBeautifier(0, line('$'))<CR>
vnoremap ,bf :call Foo_SillyRegexBeautifier(line("'<"), line("'>"))<CR>

Resources