13

From :h :set we know that

:se[t] Show all options that differ from their default value.

In a vimscript I'd like to save all these options which have been modified by the user, modify them and later restore them to the value set by the user.

How can I do that easily?

Do I have a way to simply store the result of :set (or a similar command) into a variable that I could source or pass to another command later to restore what it contains?

Or do I have to manually parse the output of the command (which I can do but is a lot of work for nothing if a simpler alternative exists)?

19
  • We already know that options start with & symbol. We can give a verbose set command and then record the output in a variable or register and then split each of them and store their values in a dictionary and then restore from that .
    – SibiCoder
    Jul 8, 2016 at 12:04
  • @SibiCoder: Yes I can parse the result manually but that's not really convenient, my question was about finding a more automatic way to do that.
    – statox
    Jul 8, 2016 at 12:16
  • 1
    As far as I know there is no standard way to do that, and doing it without support from Vim would be problematic at best. However, there is an :option command and an OptionSet autocommand event, so there might be enough interest for a "save / restore state" feature. Ask on vim_dev. Jul 8, 2016 at 18:50
  • 2
    @SatoKatsura: Damn I didn't know the :option command it is pretty cool! You're right maybe I'll hit vim_dev. @ Christian: Yes I get that this isn't a commom workflow, actually I'm juste trying to implement an idea of mine but maybe that's not such a good one :-)
    – statox
    Jul 9, 2016 at 11:07
  • 1
    @ChristianBrabandt Being able to find which options (global and local) differ from defaults would be useful. Currently (1) there is no way to make Vim spill out a complete list of options (i.e. taking into account compilation models etc.), and (2) it isn't even documented which options have local versions (F.i. does selection have a local version? One has to read the sources to find out.) Jul 9, 2016 at 14:54

4 Answers 4

7

1. :mkexrc

The easiest way is to use :mkexrc command. With this command, we can save all changed options into a file. When you need to restore back all the saved options from the file, just:source it.

mkexrc snapshot
source snapshot

2. :set and :redir

The :mkexrc command necessarily uses a file to hold all options.(And it also saves current key mappings.) When we do not want to use any file, we can redirect output of :set command into vim variable with :redir.

redir => snapshot
silent set
redir END

Then, we can restore back all the options from snapshot as follows.

for opt in split(snapshot,'\n')[1:]
    exe "silent set " . opt
endfor

The above approach is quick and easy to use. But the approach has two problems. First, we have to escape some characters including white spaces from the snapshot(:help option-backslash). Otherwise we might meet errors when these characters not being escaped. For instance,

set breakat=@ !+=

raises the error 'E518' but,

set breakat=@\ \!+=

works well.

And second problem is that all the special keys are changed to normal characters when we redirect the output to snapshot. For example, ^I(tab) is literally changed to ^ and I. The workaround could be complicated a little bit.

let g:optionDict = {}

function SaveOpts()
    redir => snapshot
    " We make a snapshot of options which differ from their default value.
    " If we want to make a snapshot of all options, do set all.
    silent set
    redir END

    for opt in split(snapshot, '\W\+')
        if strlen(opt) > 3
            if exists('&' . opt)
                exe 'let g:optionDict.'. opt . '=&' . opt
            elseif opt[0:1] == 'no' && exists('&' . opt[2:])
                exe 'let g:optionDict.'. opt[2:]. '=&' . opt[2:]
            endif
        endif
    endfor
endfunc

function RestoreOpts()
    for [opt, val] in items(g:optionDict)
        try
            exe 'silent set ' . opt . '=' . escape(val, " \t|\\\"")
        catch /:E474/ " Invalid argument, do set {option} or no{option}
            if val == '1'
                exe 'silent set ' . opt
            elseif val == '0'
                exe 'silent set no' . opt
            endif
        endtry
    endfor
endfunc

3. :setlocal

We can also save an restore option manually with :setlocal.

:setlocal only affects current buffer or window. If we change options with :setlocal, :setlocal {option}< or :set {option}< can restore back the changed options. The former one sets the local value of {option} from its global value and the latter one remove all the local options so all the option values return to their global values. For instance,

setlocal ts=16
set ts<
4

You can access a setting's value using let option_val = &option in a script. What I've seen done is using a dict to store the options that you want to change. You iterate over the dict to store the old value and set the new.

Example

This is a more direct approach. I would use this if the settings are known ahead of time.

function! s:do_something() abort
  " Options needed by the plugin
  let plugin_options = {
        \ 'list': 0,
        \ 'winfixwidth': 1,
        \ 'autoindent': 0,
        \ 'filetype': 'ini',
        \ }

  echo 'desired:' plugin_options

  for option in keys(plugin_options)
    execute 'let old_val = &'.option
    execute 'let &'.option.' = plugin_options[option]'
    let plugin_options[option] = old_val
  endfor

  echo 'changed:' plugin_options

  for option in keys(plugin_options)
    execute 'let &'.option.' = plugin_options[option]'
  endfor
endfunction


call s:do_something()

The plugin_options variable is scoped to the function, so it can be reused to store the old option values.

Advanced Example

The script below will allow you to change the settings anywhere within your script while maintaining the original settings until you call s:restore_settings()

function! s:set(option, value) abort
  if !exists('b:_saved_settings')
    let b:_saved_settings = {}
  endif

  " Only save the original values
  if !has_key(b:_saved_settings, a:option)
    execute 'let b:_saved_settings[a:option] = &'.a:option
  endif

  execute 'let &'.a:option.' = a:value'
endfunction

function! s:restore_settings(...) abort
  if !exists('b:_saved_settings')
    return
  endif

  if a:0
    for option in a:000
      if has_key(b:_saved_settings, option)
        execute 'let &'.option.' = b:_saved_settings[option]'
        call remove(b:_saved_settings, option)
      endif
    endfor
  else
    for option in keys(b:_saved_settings)
      execute 'let &'.option.' = b:_saved_settings[option]'
    endfor

    unlet! b:_saved_settings
  endif
endfunction


" Options needed by the plugin
call s:set('list', 0)
call s:set('winfixwidth', 1)
call s:set('autoindent', 0)
call s:set('filetype', 'ini')

echo 'original:' b:_saved_settings
call s:restore_settings('list', 'filetype')
echo 'partial restore:' b:_saved_settings

call s:restore_settings()

s:set() is somewhat similar to set. It will save the original setting's value if it hasn't been saved yet, then use your setting's value.

Calling s:restore_settings() without arguments will do a full restore of your changes. With arguments, it will partially restore them. If you decide to use this, it would be worth calling s:restore_settings() before initially calling s:set() in case your script previously ran into an error before restoration.

5
  • 1
    That is not as automatic as I would have expected but that seems to be a good workaround. Thanks!
    – statox
    Jul 8, 2016 at 12:35
  • 1
    @statox Yeah, but it's deliberate which I think can be better if you aren't doing anything elaborate. I'll update the answer with a script that's a bit more abstract and doesn't require you to prime the settings.
    – Tommy A
    Jul 8, 2016 at 14:34
  • @TommA, We might want to escape some characters such as white spaces, pipe, etc from option values. Those one might be problematic.
    – MS.Kim
    Jul 11, 2016 at 16:30
  • @MS.Kim That's only needed if it was executing 'set statusline='.a:value. Since it's using variable assignment, it's not an issue. You can test it with s:set('statusline', 'pipe | test') or s:set('errorformat', '%A testing %m%Z')
    – Tommy A
    Jul 11, 2016 at 22:35
  • @TommyA You're right. I thought let &{option}=a.value could cause problems as 'set {option}'=a.value could. But your mention is still valuable for the people (including me) who likely have used only 'set {option}='a.value.
    – MS.Kim
    Jul 12, 2016 at 7:51
2

In lh-vim-lib, I provide a lh#on#exit() function that permits to restore various things to their previous value.

Regarding options, in essence, it just registers (for later) a exe 'let &opt='.&opt. I use it this way:

let cleanup = lh#on#exit()
      \.restore('&efm')
try
   ... change &efm here ...
   do stuff that may fail
finally
   call cleanup.finalize()
endtry

We can register as many restorations as we wish. We can explicitly target a buffer local option with for instance: l:&efm. However I'm not aware of any way to say: "fall-back to the global option value".

Note that the finally clause is important. If any operation may fail, without it, the option won't be restored to end-user's setting.

I haven't provided a way to restore any possible option -- see @MS.Kim's answer to see how we can obtain their list. Or we could listen the option changed event, or play with the old c_CTRL-A_HOME trick (1). But, honestly, I really don't see the point. When, locally (in a function), I change option values, I exactly know which options I'll tweak.

Note that I can also:

  • restore plugin options (variables) that can be global or buffer local -> .restore_option(varname)
  • register actions -> .register(':q')
  • restore buffer local mapping (or undefine them) -> .restore_buffer_mapping('<c-y>', 'i')

(1) That I've factorized in lh-vim-lib (as usual) -> echo lh#command#matching_askvim('option', '')

" Function: lh#command#matching_askvim(what, lead) {{{3
function! lh#command#matching_askvim(what, lead) abort
  let cleanup = lh#on#exit()
        \.register('delcom LHAskVimMatchingCompletion')
  try
    exe 'command! -complete='.a:what.' -nargs=* LHAskVimMatchingCompletion :echo "<args>"'
    silent! exe "norm! :LHAskVimMatchingCompletion ".a:lead."\<c-a>\"\<home>let\ cmds=\"\<cr>"
    return split(cmds, ' ')[1:]
  finally
    call cleanup.finalize()
  endtry
endfunction
5
  • That looks like a very interesting plugin. I think you make a pretty good point here: I haven't provided a way to restore any possible option [...]. I don't see the point. When, locally (in a function), I change option values, I exactly know which options I'll tweak. to be fair I can't remember why I needed that but I agree that it is better to switch only the options you need to change.
    – statox
    Nov 22, 2016 at 11:18
  • Ah. Since your comment, I've added a way to list all existing options thanks to command line completion -- dirty trick inside. Nov 22, 2016 at 11:20
  • (PS: If I ever attend another tuppervim, just ask) Nov 22, 2016 at 11:25
  • Ok that's how you recognize a veteran vimmist ;-) I didn't know this old c_CTRL-A_HOME trick and I'll need some time to grok your code but thank you very much for providing a solution! (PS I'd gladly meet you in a next tuppervim!)
    – statox
    Nov 22, 2016 at 11:26
  • 1
    That's how we've been proceeding before get(g:, -- which took me some time to discover. It's still useful for non-officially exposed stuff like options, environment variables, etc. Nov 22, 2016 at 11:29
2

If we just limit to 1 aspect and we map it (instead of all user changes as question asks, and for vimscript instead of map), we can save-set-doStuff-reset easily:

let str_zzz="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget libero quis mi gravida sollicitudin sit amet in sem."
nnoremap <buffer> <leader>zzz
\ :let tw_zzz=&tw
\ <bar> :set tw=0<CR><Esc>
\ :execute 'normal! o' . str_zzz<CR>
\ :set tw=&tw_zzz<CR>

Explanation. This map pastes a very long string without slicing it every textwidth (:set tw&) characters.

  • let tw_zzz=&tw saves current textwidth
  • set tw=0 anullates it (line width as long as needed)
  • execute 'normal! o' . str_zzz pastes the [long] string
  • set tw=&tw_zzz resets the previous textwidth
  • set tw& would reset to default (in that filetype), instead to the previous textwidth (as previous command did). Thus, with this option no need of let tw_zzz=&tw

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.