variablesvimglobal

vimscript for managing windows need some review


Could someone check my vimscript below. I tried to make a key mapping that does:

1. Closes tagbar and/or nerdtree if exist
2. Do <C-W>H or <C-W>J or <C-W>K or <C-W>L
3. Recover tagbar and/or nerdtree if originally exited

For a window that has file content in it this key-mapping works well. Only for a window that has no file content in it(this kind of window can be created by <C-W>n) this code works but nerdtree and tagbar does not reappear.

Recovering those plugin window is the role of the function RecoverThe2(). I cannot debug this script. I can only IMAGINE what is going on in my brain. But I cannot figure out.

  1. I'd like to get some advice on this problem.
  2. I'd like to get some advice about the usage of the global variables - g:nerdtree_open and g:tagbar_open. I am not certain about the correct usage of the global variable in vimscript. Am I using global variable correctly?
let g:nerdtree_open = 0                                                          
let g:tagbar_open = 0                                                            
                                                                                 
function! CloseThe2()                                                            
  " Detect which plugins are open                                                
  if exists('t:NERDTreeBufName')                                                 
    let g:nerdtree_open = bufwinnr(t:NERDTreeBufName) != -1                      
  else                                                                           
    let g:nerdtree_open = 0                                                      
  endif                                                                          
  let g:tagbar_open = bufwinnr('__Tagbar__') != -1                               
                                                                                 
  " Perform the appropriate action                                               
  if g:nerdtree_open                                                             
    NERDTreeClose                                                                
  endif                                                                          
  if g:tagbar_open                                                               
    TagbarClose                                                                  
  endif                                                                          
endfunction                                                                      
                                                                                 
function! RecoverThe2()                                                          
  " Perform the appropriate action                                               
  if g:nerdtree_open                                                             
    NERDTree                                                                     
    let g:nerdtree_open = 0                                                      
  endif                                                                          
  if g:tagbar_open                                                               
    TagbarOpen                                                                   
    let g:tagbar_open = 0                                                        
  endif                                                                          
endfunction                                                                      
                                                                                 
noremap <C-W>HH :call CloseThe2()<cr><C-W>H :call RecoverThe2()<cr>              
noremap <C-W>JJ :call CloseThe2()<cr><C-W>J :call RecoverThe2()<cr>              
noremap <C-W>KK :call CloseThe2()<cr><C-W>K :call RecoverThe2()<cr>              
noremap <C-W>LL :call CloseThe2()<cr><C-W>L :call RecoverThe2()<cr>

F.Y.I. I tested this on Ubuntu 22.04 Gnome Terminal

You can find my vimrc here


Solution

  • The right-hand side of a mapping is a macro, and thus a series of commands. In your case, the RHS can be subdivided in 4 normal mode commands:

    :call CloseThe2()<cr><C-W>H :call RecoverThe2()<cr>
    AAAAAAAAAAAAAAAAAAAAA
                         BBBBBB
                               C
                                DDDDDDDDDDDDDDDDDDDDDDD
    

    In a macro, when one of the commands throws an error, the remaining commands are not executed. Therefore, it is important to make sure that none of the commands can throw an error if you absolutely want all your commands to be executed.

    Now, let's see what those four commands do:

    • Command A calls a function.
    • Command B moves the current window.
    • Command C moves the cursor one character to the right.
    • Command D calls a function.

    And here lies your problem: that space you added between commands B and D is interpreted as a legitimate normal mode command C: :help <space>. <Space> works when there is some text on the right of the cursor but it throws an error when that's not the case. Since your buffer is empty, command C throws and error and prevents the execution of command D.

    An easy fix would thus be to remove that space:

    :call CloseThe2()<cr><C-W>H:call RecoverThe2()<cr>
    

    Note that the sample in your question has lots of trailing spaces, too. You should make sure they are not in your vimrc.

    Now, since those two functions are always called the same way, sandwiching a window movement, you could probably wrap the whole thing into a single function in order to keep your mappings simple:

    function! Whatever(direction)
        call CloseThe2()
        execute "wincmd " .. a:direction
        call RecoverThe2()
    endfunction
    nnoremap <C-W>HH <Cmd>call Whatever("H")<cr>
    

    or even throw away those two functions, which are not really useful outside of that context, and put everything together in a single one. Note that it has the added benefit of keeping everything local and thus preventing unnecessary pollution of the global scope:

    function! Whatever(direction)
        " the setup
        let l:nerdtree_open = 0
        let l:tagbar_open = 0
    
        " the first step
        if exists('t:NERDTreeBufName')
        […]
    
        " the second step
        execute "wincmd " .. a:direction
    
        " the third step
        if l:nerdtree_open
        […]
    endfunction
    nnoremap <C-W>HH <Cmd>call Whatever("H")<cr>
    

    FWIW, see :help get() for a handy way to get a value from a scope, l: in this case, without throwing an error, and with a default value.

    See :help :wincmd.