Como salvar e restaurar o resultado do comando 'set'?

7

De :h :setnós sabemos que

:se[t] Mostrar todas as opções que diferem do valor padrão.

Em um vimscript, eu gostaria de salvar todas essas opções que foram modificadas pelo usuário, modificá-las e depois restaurá-las para o valor definido pelo usuário.

Como posso fazer isso facilmente?

Eu tenho uma maneira de simplesmente armazenar o resultado :set(ou um comando semelhante) em uma variável que eu poderia originar ou passar para outro comando posteriormente para restaurar o que ele contém?

Ou tenho que analisar manualmente a saída do comando (o que posso fazer, mas é muito trabalhoso, se existir uma alternativa mais simples)?

statox
fonte
Já sabemos que as opções começam com o símbolo &. Podemos fornecer um comando detalhado de conjunto e, em seguida, registrar a saída em uma variável ou registro e, em seguida, dividir cada uma delas e armazenar seus valores em um dicionário e depois restaurar a partir disso.
SibiCoder
@SibiCoder: Sim, eu posso analisar o resultado manualmente, mas isso não é muito conveniente, minha pergunta era sobre encontrar uma maneira mais automática de fazer isso.
Statox
11
Até onde eu sei, não existe uma maneira padrão de fazer isso, e fazê-lo sem o suporte do Vim seria problemático na melhor das hipóteses. No entanto, há um :optioncomando e um OptionSetevento de comando automático, portanto, pode haver interesse suficiente para um recurso "salvar / restaurar estado". Pergunte vim_dev.
Sato Katsura
2
@SatoKatsura: Porra, eu não sabia o :optioncomando, é muito legal! Você está certo, talvez eu acerte vim_dev. @ Christian: Sim eu recebo que este não é um fluxo de trabalho commom, na verdade eu estou juste tentando implementar uma ideia minha, mas talvez isso não é tão boa :-)
statox
11
@ChristianBrabandt Ser capaz de descobrir quais opções (global e local) diferem dos padrões seria útil. Atualmente (1) não há como fazer com que o Vim descreva uma lista completa de opções (por exemplo, levando em consideração os modelos de compilação etc.) e (2) nem sequer está documentado quais opções têm versões locais (o Fi selectionpossui um local ? versão Um tem que ler as fontes para descobrir).
Sato Katsura

Respostas:

5

1 :mkexrc

A maneira mais fácil é usar o :mkexrccomando. Com este comando, podemos salvar todas as opções alteradas em um arquivo. Quando você precisar restaurar todas as opções salvas do arquivo, apenas :sourceisso.

mkexrc snapshot
source snapshot

2. :sete:redir

O :mkexrccomando necessariamente usa um arquivo para conter todas as opções (e também salva os mapeamentos de teclas atuais.) Quando não queremos usar nenhum arquivo, podemos redirecionar a saída do :setcomando para a variável vim com :redir.

redir => snapshot
silent set
redir END

Em seguida, podemos restaurar todas as opções do instantâneo da seguinte maneira.

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

A abordagem acima é rápida e fácil de usar. Mas a abordagem tem dois problemas. Primeiro, precisamos escapar de alguns caracteres, incluindo espaços em branco da captura instantânea ( :help option-backslash) . Caso contrário, poderemos encontrar erros quando esses caracteres não estiverem sendo escapados. Por exemplo,

set breakat=@ !+=

gera o erro 'E518' mas,

set breakat=@\ \!+=

funciona bem.

E o segundo problema é que todas as chaves especiais são alteradas para caracteres normais quando redirecionamos a saída para o instantâneo. Por exemplo, ^I(guia) é literalmente alterado para ^e I. A solução alternativa pode ser um pouco complicada.

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

Também podemos salvar uma opção de restauração manualmente com :setlocal.

:setlocalafeta apenas o buffer ou a janela atual. Se alterarmos as opções com :setlocal, :setlocal {option}<ou :set {option}<podemos restaurar as opções alteradas. O primeiro define o valor local de {option} do seu valor global e o último remove todas as opções locais para que todos os valores das opções retornem aos seus valores globais. Por exemplo,

setlocal ts=16
set ts<
MS.Kim
fonte
4

Você pode acessar o valor de uma configuração usando let option_val = &optionum script. O que eu vi feito é usar um dict para armazenar as opções que você deseja alterar. Você itera sobre o dict para armazenar o valor antigo e definir o novo.

Exemplo

Essa é uma abordagem mais direta. Eu usaria isso se as configurações fossem conhecidas antecipadamente.

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

A plugin_optionsvariável tem um escopo definido para a função, para que possa ser reutilizada para armazenar os valores das opções antigas.

Exemplo Avançado

O script abaixo permitirá que você altere as configurações em qualquer lugar do seu script, mantendo as configurações originais até você ligar 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()é um pouco semelhante a set. Ele salvará o valor da configuração original, se ainda não tiver sido salvo, e use o valor da configuração.

Ligar s:restore_settings()sem argumentos fará uma restauração completa de suas alterações. Com argumentos, ele os restaurará parcialmente. Se você decidir usar isso, vale a pena ligar s:restore_settings()antes da chamada inicial s:set(), caso o script tenha ocorrido um erro antes da restauração.

Tommy A
fonte
11
Isso não é tão automático quanto eu esperava, mas parece ser uma boa solução alternativa. Obrigado!
Statox
11
@statox Sim, mas é deliberado o que acho que pode ser melhor se você não estiver fazendo nada elaborado. Atualizarei a resposta com um script que é um pouco mais abstrato e não exige que você defina as configurações.
21430 Tommy A
@ TommA, podemos querer escapar de alguns caracteres, como espaços em branco, tubulação etc. dos valores das opções. Aqueles podem ser problemáticos.
MS.Kim
@ MS.Kim Isso é necessário apenas se estiver em execução 'set statusline='.a:value. Como está usando atribuição de variável, não é um problema. Você pode testá-lo com s:set('statusline', 'pipe | test')ous:set('errorformat', '%A testing %m%Z')
Tommy A
@ TommyA Você está certo. Eu pensei que let &{option}=a.valuepoderia causar problemas como 'set {option}'=a.valuepoderia. Mas sua menção ainda é valiosa para as pessoas (inclusive eu) que provavelmente usaram apenas 'set {option}='a.value.
MS.Kim
1

No lh-vim-lib , forneço uma lh#on#exit()função que permite restaurar várias coisas ao seu valor anterior.

Em relação às opções, em essência, apenas registra (para mais tarde) a exe 'let &opt='.&opt. Eu uso desta maneira:

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

Podemos registrar quantas restaurações desejarmos. Podemos direcionar explicitamente uma opção local tampão com, por exemplo: l:&efm. No entanto, não conheço nenhuma maneira de dizer: "retorno ao valor da opção global".

Observe que a finallycláusula é importante. Se alguma operação falhar, sem ela, a opção não será restaurada para a configuração do usuário final.

Não forneci uma maneira de restaurar nenhuma opção possível - consulte a resposta do @ MS.Kim para ver como podemos obter sua lista. Ou podemos ouvir a opção de evento alterado ou brincar com o velho c_CTRL-A_HOMEtruque (1). Mas, honestamente, eu realmente não entendo o ponto. Quando, localmente (em uma função), altero os valores das opções, sei exatamente quais opções ajustarei.

Note que eu também posso:

  • restaurar opções de plug-in (variáveis) que podem ser globais ou locais de buffer -> .restore_option(varname)
  • registrar ações -> .register(':q')
  • restaurar o mapeamento local do buffer (ou defini-los) -> .restore_buffer_mapping('<c-y>', 'i')

(1) Que eu fatorei em lh-vim-lib (como sempre) -> 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
Luc Hermitte
fonte
Parece um plugin muito interessante. Acho que você faz uma boa observação: 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.para ser justo, não me lembro por que precisava disso, mas concordo que é melhor mudar apenas as opções que você precisa alterar.
statox
Ah Desde o seu comentário, adicionei uma maneira de listar todas as opções existentes, graças à conclusão da linha de comando - truque sujo por dentro.
precisa
(PS: Se eu nunca assistir a outra tuppervim, basta perguntar)
Luc Hermitte
Ok, é assim que você reconhece um veterano vimista ;-) Eu não conhecia esse velho c_CTRL-A_HOMEtruque e precisarei de algum tempo para inserir seu código, mas muito obrigado por fornecer uma solução! (PS Eu ficaria feliz em conhecê-lo em uma próxima tuppervim!)
statox
11
É assim que estamos procedendo antes get(g:, - o que levou algum tempo para descobrir. É ainda útil para o material exposto não-oficialmente como opções, variáveis de ambiente, etc.
Luc Hermitte