0

I often set or modify a shell variable in Bash for the scope of a single command only:

$ FOO=BAR <command>

Is it possible to set or modify a variable in Vim for the scope of a single Ex command only?

Suppose I want to modify the path variable for a single :find command. I don't want to remember the old value and reset it afterwards using :set path=.

3
  • How about to wrap the single command in a function, and in it store the old value, set the new, run the command in question, and restore the variable? Or look into unlet. If this sounds OK, I could write a more detailed answer
    – d.k
    Jun 15, 2020 at 6:24
  • @user907860 - If that's the proper way to do it in Vim, I guess it's OK. I'm only semi-experienced with Vim.
    – Shuzheng
    Jun 15, 2020 at 6:25
  • 1
    There are some predefined modifiers like verbose, hide and such which essentially do a very similar thing. Not applicable to user stuff though.
    – Matt
    Jun 15, 2020 at 6:38

1 Answer 1

1

Unfortunately there is nothing like that.

In my library plugin, I've implemented a generic feature that takes care of restoring options, variables (where restoring may means :unlet), mappings, or trigger some other actions at the end of a sequence of operations. But it's not meant to be used on the fly. It's too verbose for that.

  let cleanup = lh#on#exit()
        \.restore('&l:statusline')
        \.restore('&isk')
        \.restore('b:varname')
        \.register('au! SomeAuGroup')
        \.restore_cursor()
        \.restore_buffer_mapping('<c-y>', 'i')
        .....
  try
     actions...
  finally
     call cleanup.finalize()
  endtry

Back to your concern, I guess you'd like something simple to use. We could imagine something like :MyExec -protect:path=tmp\ value :the old ex-command. The problem I see is parsing the temporary value sent to path. I've tried several times to have commands able to reconstruct strings and take other parameters, it's a plain headache. At best we need some unique separator. Something like -- maybe ?

This could give

command! -nargs=1 ProtectExec call s:ProtectExec(<f-args>)

function! s:ProtectExec(cmdline) abort
  " Only accepts a single -override parameter
  let parts = split(a:cmdline, '\s\+--\s\+')
  if len(parts) < 2
    throw "ProtectExec: Don't forget to separate the ex-command from the overriden variable/option with '--'"
  endif
  let cleanup = lh#on#exit()
  try
    if parts[0] =~ '\v-over%[ride]:'
      let [all, var, val; dummy] = matchlist(parts[0], '\v-over%[ride]:([^=]+)\=(.*)')
      call cleanup.restore(var)
      if empty(var)
        throw "ProtectExec: Cannot extract variable name and value from `".parts[0]."`"
      endif
      " If only options and only one option would be supported,
      " then varname could be used in the the finally clause instead 
      " of the generic on#exit() object.
      let varname = lh#option#is_set_locally(var) ? '&l:'.var[1:] : var
      exe 'let '.varname.' = '.val
    endif
    exe parts[1]
  finally
    call cleanup.finalize()
  endtry
endfunction

That is used with:

:ProtectExec -override:&path='toto' -- echo "tmp:".&path

Note that lh#on#exit().restore() relies on another function: lh#option#is_set_locally() that I just had to fix in order to correctly recognize global-local options that haven't been locally overridden. This part has been a little tricky (we need to know how the option shall be restored: with :set or :setlocal ).

let s:k_option_fullname = {
      \ 'ai': 'autoindent',
      \ 'bs': 'backspace',
      \ 'efm': 'errorformat',
      \ 'et': 'expandtab',
      \ 'ft': 'filetype',
      \ 'isk': 'iskeyword',
      \ 'rtp': 'runtimepath',
      \ 'sw': 'shiftwidth',
      \ 'ts': 'tabstop',
      \ 'tw': 'textwidth'
      \ }
function! lh#option#is_set_locally(option_name, ...) abort
  let bufid = get(a:, 1, '%')
  if a:option_name =~ '^&\(\([lg]:\)\@!.\)*$'
    " options with no explicit scope
    let options = getbufvar(bufid, '&')
    " Before 7.4.434, getbufvar() returns an empty string instead of an
    " empty dict when nothing is found
    " Also, older version of vim don't return local options with
    " getbufvar(bid, '&')
    " In global-local options case, an empty local option is an option that
    " hasn't been overriden
    let option_name = get(s:k_option_fullname, a:option_name[1:], a:option_name[1:])
    if !empty(options) && has_key(options, option_name)
      if type(options[option_name]) == type('')
        return !empty(getbufvar(bufid, '&l:'.option_name))
      else
        return getbufvar(bufid, '&l:'.a:option_name[1:]) != getbufvar(bufid, '&g:'.option_name)
      endif
    endif
  endif
  return 0
endfunction

IMO, unless you have many cases of commands and options you'd like to override, you'd better off with a dedicated command for searching with a temporary path -- but don't forget to check whether 'path' is currently a local or a global option.

Your Answer

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

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