3

I am trying to implement a way to get automatic completion when typing Latex environments. Consider the following situation:

\begin{|}
\end{}

where | stands for the position of the cursor. Then I type the name of the environment, say tabular and press Enter and I would like it to automatically fill the brackets after \end. I originally thought of using double cursors but then I read the plugins for multiple cursors are not so sturdy. By the way, is it really so?

Regardless, I came up with the following solution. I have a plugin (namely quicktex) that allows me to enter some keys in insert mode and substitute them with arbitrary commands, so I set <Space><Space> to be replaced by

"\\begin{}\<CR>\\end{}\<CR><++>\<ESC>2?}\<CR>:call InEnv(1)\<CR>i"

and I put the following code in my tex.vim file:

" Am I in environment mode?
    let s:env = 0

" Enter/exit environment mode
    function! InEnv(on)
        let s:env = a:on
        if a:on ==? 1
            inoremap <CR> <ESC>:call BuildEnv()<CR>A<CR>
        elseif a:on ==? 0
            iunmap <CR>
        endif
    endfunction

" Finds out the length of the environment name (when cursor is inside the brackets relative to \begin)
    function! EnvNameLength()
        call search("}", "cz")
        execute "normal! h"
        let s:end = col(".")
        call search("{", "b")
        return s:end - col(".")
    endfunction

" Copies environment name and pastes it
    function! CopyEnvName()
        let s:len = EnvNameLength()
        execute "normal! lv" . (s:len-1) . "ly"
        call search("end{", "e")
        execute "normal! p"
    endfunction

" Builds environment
    function! BuildEnv()
        if s:env ==? 1
            call CopyEnvName()
            call search("begin", "b")
        endif
        call InEnv(0)
    endfunction

This works perfectly but I feel it's a bit clumsy because of the continuous mapping/unmapping of the <CR> key. I would like to avoid remapping any keys. Is there any way to accomplish that?

3
  • 2
    Have a look at Ultisnips. It supports mirrors, which seems to do what you want. Snipmate also supports mirrors, though I personally have not used that plugin before
    – husB
    Feb 12, 2020 at 14:12
  • 2
    I think that what you are really looking for is a snippet engine: Create a snippet with both lines and two placeholders replaced by the same text and you're good to go. You might be interested in this question to help you choose which one to use.
    – statox
    Feb 12, 2020 at 14:12
  • I had look at some snippet engines, including Ultisnips, but I still think a more minimal solution would be better. Not to mention the fact that a snippet engine would make Quicktex redundant, if not harmful, while I really like it since it is really fast and it is intended for writing maths, which is most of my Latex activity.
    – fresh
    Feb 13, 2020 at 1:08

3 Answers 3

2

I've actually looked at what your code is intended to do, now, and have come up with a couple of alternatives:

A simple solution

How about something a bit simpler that simply asks for the environment?

function EnvThing() abort
  let env = input('Enter env: ')
  return '\begin{'.x."}\<CR>end{".x.'}'
endfunction

inoremap <expr> <Space><Space> EnvThing()

A fully automated solution

Instead of using mappings this autocommand automatically updates the nearest \end{} whenever you edit the \begin{} in insert mode:

function! EnviroThing() abort
  let state = winsaveview()
  try
    let l = getline('.')
    let c = col('.')
    if l[:c - 1] =~# '\\begin{[^}]*' && l[c - 1:] =~# '[^}]*}'
      norm!yi}
      call search('\\end{', 'eW')
      execute "norm!ci}\<C-R>0"
    endif
  finally
    call winrestview(state)
  endtry
endfunction

augroup EnviroThing
  autocmd!
  autocmd TextChangedI *.latex call EnviroThing()
  autocmd TextChanged *.latex call EnviroThing()
augroup END

There are a number of ways this could be polished further, e.g. it doesn't work well with mismatched begin/ends, so you're probably going to want to keep your quicktex shortcut or an iabbrev in place so that you never enter a \begin{} without a corresponding end, and it might be nice if it worked in the other direction, updating the \begin{} when you change the content of the \end{}. I also don't really know LaTeX well enough to vouch for its robustness in all documents.

However, it works quite well for simple cases, and should be enough to get you started on a more complete solution.

1

I think you should be able to address your specific issue of remapping the <CR> by using an expression map.

This is untested, but it (or something similar) might work for you:

inoremap <expr> <CR> (s:env ==? 1) ? '<ESC>:call BuildEnv()<CR>A<CR>' : '<CR>'

It's slightly different from your existing code in that it changes the behaviour of Enter based on the current value of s:env rather than the value that existed at the time the mapping was made.

I haven't studied your code in enough detail to figure out whether this is a drop in replacement for your existing if block, or if you might need to rework your code a little.

0
1

I use the following insert mode mappping in my ftplugin/tex.vim:

inoremap <buffer> <C-B> <Esc>yypk$<C-V>jA}<Esc>^i\begin{<Esc>j^i\end{<Esc>O

This means that if I type enumerate then press Ctrl-B it will automatically add the \begin{}/\end{} parts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.