Vim Tips Wiki
m (Q335r49 moved page Automatically Saving Lists to File to Back up Lists and Dictionaries to viminfo: No need to save to a separate file)
 
(14 intermediate revisions by 4 users not shown)
Line 1: Line 1:
  +
{{TipProposed
The "!" option in <code>set viminfo</code> will store string and number variables with all uppercase names to the viminfo file. It's possible to extend this behavior to dictionary and lists too, writing to a file other than viminfo.
 
  +
|id=0
  +
|previous=0
  +
|next=0
  +
|created=October 6, 2012
  +
|complexity=basic
  +
|author=Q335r49
  +
|version=7.0
  +
|subpage=/201210
  +
|category1=
  +
|category2=
  +
}}
  +
The "!" option in <code>set viminfo</code> stores string and number variables with all uppercase names to the viminfo file. A patch for vim 7.3 extends this behavior to lists and dictionaries, but apparently has problems with nested lists. This method doesn't have that limitation, and also has the advantage of working on earlier versions. It converts lists and dictionaries to a vimscript string, which then can be stored via the usual method in viminfo. The variables are recovered by executing that string.
   
  +
By the way, this doesn't preserve the identity of lists. Variables that point to the same list will become copies of different but equivalent lists after restoring.
There are a few interesting bits here:
 
   
 
There are a few bits I had to look up:
*<code>Ctrl-A</code>: The line <code>sil! exe "norm!...</code> gets a string containing the names of all global variables by exploiting the fact that Ctrl-A produces a string of all possible completions, in this case to <code>:let g:</code> -- all global variables. The remainder of the line manually changes this command to <code>:let varlist=...</code>
 
   
*<code>type()</code>: This checks whether the variable is a dictionary or a list (returning a 3 or a 4)
+
*<code>Ctrl-A</code> lists all possible completions. So you can get a list of all global variables via <code>:let g:<c-a></code>.
   
  +
*For some reason directly writing a variable containing "\n" produces errors when sourcing the file, Vim treats it as an actual line break. We have to substitute the \n char with the string <code>'."\n".'</code>
*<code>==#</code>: Case sensitive comparison, to see if the variable name matches the same name converted to all uppercase.
 
   
*<code>string()</code>: This basically converts a list or dictionary into the command used to produce it. Also, I always thought it interesting that writing the string <code>"let v=val"</code> rather than simply storing the raw data was an easy way to store variables in vim.
 
 
*<code>split(...,"\n")</code>: For some reason, writing a variable with "\n" in its contents produced errors for me when sourcing the file. I had to manually split the lines using split().
 
 
*<code>au VimLeavePre</code>: Automatically call this function when vim exits.
 
 
In order to read the vars again, simply source the file, by putting <code>:source vimlists</code> in your vimrc.
 
 
==Script==
 
==Script==
 
The following script can be placed in your [[vimrc]].
 
The following script can be placed in your [[vimrc]].
 
<pre>
 
<pre>
fun! WriteVars(filename)
+
fun! WriteVars()
sil! exe "norm! :let g:\<c-a>'\<c-b>\<right>\<right>
+
sil exe "norm! :let g:\<c-a>'\<c-b>\<right>\<right>\<right>\<right>v='\<cr>"
  +
let i=0
\\<right>\<right>varlist='\<cr>"
 
let saves=filter(split(varlist),
+
for name in split(v)
\'abs(type(eval(v:val))-3.5)<1 && v:val[2:]==#toupper(v:val[2:])')
+
if name[2:]==#toupper(name[2:]) && eval("type(".name.")")>1
  +
let g:VARSAV_{i}=substitute("let".name."=".eval("string(".name.")"),"\n",'''."\\n".''',"g")
let list=[]
 
  +
let i+=1
for key in saves
 
  +
en
if exists(key)
 
 
endfor
let splitlist=split('let '.key.'='.string(eval(key)),"\n")
 
  +
"If the number of variables saved is less than last time, clear the extra ones
call add(list,splitlist[0])
 
  +
if exists("g:VARSAVES") && i<g:VARSAVES
for line in splitlist[1:]
 
  +
exe "unlet! ".join(map(range(i,g:VARSAVES),"'g:VARSAV_'.v:val"))
call add(list,"\\".line)
 
  +
en
endfor
 
  +
let g:VARSAVES=i
en
 
endfor
 
call writefile(list,a:filename)
 
 
endfun
 
endfun
  +
fun! RestoreVars()
 
 
if exists("g:VARSAVES")
au VimLeavePre * call WriteVars('vimlistsave')
 
  +
for i in range(g:VARSAVES)
if filereadable('vimlistsave')
 
  +
exe VARSAV_{i}
source vimlistsave
 
 
endfor
endif
 
 
en
 
au VimLeavePre * call WriteVars()
 
</pre>
 
</pre>
  +
==References==
 
  +
To restore the variables, use <code>:call RestoreVars()</code>. Note that if you use this in your vimrc file, you'll have to use <code>:rviminfo</code> to manually read the viminfo file first since the vimrc file is processed before the viminfo.
<!-- Put any help links you want here in a list as follows: -->
 
   
 
==Comments==
 
==Comments==
  +
Does this script do anything not done by built-in Vim functionality, after [http://code.google.com/p/vim/source/detail?r=2f57d93bdbf64461c4eaeaacdba26548a2abb9cf&name=v7-3-030 version 7.3.30]? --[[User:Fritzophrenic|Fritzophrenic]] ([[User talk:Fritzophrenic|talk]]) 04:49, October 7, 2012 (UTC)
  +
  +
I'm still running 7.3 compiled by someone long ago for android -- I don't have the means to compile a newer version. Also, the Windows version linked to from the vim.org website isn't patched. I haven't tested the patched version, the help file diff says "Nested List and Dict items may not be read back correctly, you end up with a string representation instead.". I'm not sure what they mean by "may", but this method does nested lists just fine.
  +
  +
It still doesn't do "pointers to the same list" of course, which probably can't be done in vimscript and would definitely require a patch. In my own vimrc file, I've added something along the lines of
  +
<pre>
  +
if has_key(eval('dictname'),"reinitafterstorate")
  +
call add(list,"call ".dictname.".reinitafterstorage()")
  +
end
  +
</pre>
  +
  +
into WriteVar(), so that the reinitafterstorage function would reinitialize any lists in the dictionary so that they point to the same address. I remember being really confused the first time this happened. --[[User:Q335r49|Q335r49]] October 7, 2012

Latest revision as of 02:31, 23 July 2013

Proposed tip Please edit this page to improve it, or add your comments below (do not use the discussion page).

Please use new tips to discuss whether this page should be a permanent tip, or whether it should be merged to an existing tip.
created October 6, 2012 · complexity basic · author Q335r49 · version 7.0

The "!" option in set viminfo stores string and number variables with all uppercase names to the viminfo file. A patch for vim 7.3 extends this behavior to lists and dictionaries, but apparently has problems with nested lists. This method doesn't have that limitation, and also has the advantage of working on earlier versions. It converts lists and dictionaries to a vimscript string, which then can be stored via the usual method in viminfo. The variables are recovered by executing that string.

By the way, this doesn't preserve the identity of lists. Variables that point to the same list will become copies of different but equivalent lists after restoring.

There are a few bits I had to look up:

  • Ctrl-A lists all possible completions. So you can get a list of all global variables via :let g:<c-a>.
  • For some reason directly writing a variable containing "\n" produces errors when sourcing the file, Vim treats it as an actual line break. We have to substitute the \n char with the string '."\n".'

Script[]

The following script can be placed in your vimrc.

fun! WriteVars()
    sil exe "norm! :let g:\<c-a>'\<c-b>\<right>\<right>\<right>\<right>v='\<cr>"
    let i=0
    for name in split(v)
        if name[2:]==#toupper(name[2:])	&& eval("type(".name.")")>1
            let g:VARSAV_{i}=substitute("let".name."=".eval("string(".name.")"),"\n",'''."\\n".''',"g")
            let i+=1
        en
    endfor
    "If the number of variables saved is less than last time, clear the extra ones
    if exists("g:VARSAVES") && i<g:VARSAVES
        exe "unlet! ".join(map(range(i,g:VARSAVES),"'g:VARSAV_'.v:val"))
    en
    let g:VARSAVES=i
endfun
fun! RestoreVars()
    if exists("g:VARSAVES")
        for i in range(g:VARSAVES)
            exe VARSAV_{i}
    endfor
en
au VimLeavePre * call WriteVars()

To restore the variables, use :call RestoreVars(). Note that if you use this in your vimrc file, you'll have to use :rviminfo to manually read the viminfo file first since the vimrc file is processed before the viminfo.

Comments[]

Does this script do anything not done by built-in Vim functionality, after version 7.3.30? --Fritzophrenic (talk) 04:49, October 7, 2012 (UTC)

I'm still running 7.3 compiled by someone long ago for android -- I don't have the means to compile a newer version. Also, the Windows version linked to from the vim.org website isn't patched. I haven't tested the patched version, the help file diff says "Nested List and Dict items may not be read back correctly, you end up with a string representation instead.". I'm not sure what they mean by "may", but this method does nested lists just fine.

It still doesn't do "pointers to the same list" of course, which probably can't be done in vimscript and would definitely require a patch. In my own vimrc file, I've added something along the lines of

if has_key(eval('dictname'),"reinitafterstorate")
  call add(list,"call ".dictname.".reinitafterstorage()")
end

into WriteVar(), so that the reinitafterstorage function would reinitialize any lists in the dictionary so that they point to the same address. I remember being really confused the first time this happened. --Q335r49 October 7, 2012