1if exists('g:loaded_shada_autoload')
2  finish
3endif
4let g:loaded_shada_autoload = 1
5
6""
7" If true keep the old header entry when editing existing ShaDa file.
8"
9" Old header entry will be kept only if it is listed in the opened file. To
10" remove old header entry despite of the setting just remove it from the
11" listing. Setting it to false makes plugin ignore all header entries. Defaults
12" to 1.
13let g:shada#keep_old_header = get(g:, 'shada#keep_old_header', 1)
14
15""
16" If true then first entry will be plugin’s own header entry.
17let g:shada#add_own_header = get(g:, 'shada#add_own_header', 1)
18
19""
20" Dictionary that maps ShaDa types to their names.
21let s:SHADA_ENTRY_NAMES = {
22  \1: 'header',
23  \2: 'search_pattern',
24  \3: 'replacement_string',
25  \4: 'history_entry',
26  \5: 'register',
27  \6: 'variable',
28  \7: 'global_mark',
29  \8: 'jump',
30  \9: 'buffer_list',
31  \10: 'local_mark',
32  \11: 'change',
33\}
34
35""
36" Dictionary that maps ShaDa names to corresponding types
37let s:SHADA_ENTRY_TYPES = {}
38call map(copy(s:SHADA_ENTRY_NAMES),
39        \'extend(s:SHADA_ENTRY_TYPES, {v:val : +v:key})')
40
41""
42" Map that maps entry names to lists of keys that can be used by this entry.
43" Only contains data for entries which are represented as mappings, except for
44" the header.
45let s:SHADA_MAP_ENTRIES = {
46  \'search_pattern': ['sp', 'sh', 'ss', 'sb', 'sm', 'sc', 'sl', 'se', 'so',
47  \                   'su'],
48  \'register': ['n', 'rc', 'rw', 'rt', 'ru'],
49  \'global_mark': ['n', 'f', 'l', 'c'],
50  \'local_mark': ['f', 'n', 'l', 'c'],
51  \'jump': ['f', 'l', 'c'],
52  \'change': ['f', 'l', 'c'],
53  \'header': [],
54\}
55
56""
57" Like one of the values from s:SHADA_MAP_ENTRIES, but for a single buffer in
58" buffer list entry.
59let s:SHADA_BUFFER_LIST_KEYS = ['f', 'l', 'c']
60
61""
62" List of possible history types. Maps integer values that represent history
63" types to human-readable names.
64let s:SHADA_HISTORY_TYPES = ['command', 'search', 'expression', 'input', 'debug']
65
66""
67" Map that maps entry names to their descriptions. Only for entries which have
68" list as a data type. Description is a list of lists where each entry has item
69" description and item type.
70let s:SHADA_FIXED_ARRAY_ENTRIES = {
71  \'replacement_string': [[':s replacement string', 'bin']],
72  \'history_entry': [
73    \['history type', 'histtype'],
74    \['contents', 'bin'],
75    \['separator', 'intchar'],
76  \],
77  \'variable': [['name', 'bin'], ['value', 'any']],
78\}
79
80""
81" Dictionary that maps enum names to dictionary with enum values. Dictionary
82" with enum values maps enum human-readable names to corresponding values. Enums
83" are used as type names in s:SHADA_FIXED_ARRAY_ENTRIES and
84" s:SHADA_STANDARD_KEYS.
85let s:SHADA_ENUMS = {
86  \'histtype': {
87    \'CMD': 0,
88    \'SEARCH': 1,
89    \'EXPR': 2,
90    \'INPUT': 3,
91    \'DEBUG': 4,
92  \},
93  \'regtype': {
94    \'CHARACTERWISE': 0,
95    \'LINEWISE': 1,
96    \'BLOCKWISE': 2,
97  \}
98\}
99
100""
101" Second argument to msgpack#eval.
102let s:SHADA_SPECIAL_OBJS = {}
103call map(values(s:SHADA_ENUMS),
104        \'extend(s:SHADA_SPECIAL_OBJS, map(copy(v:val), "string(v:val)"))')
105
106""
107" Like s:SHADA_ENUMS, but inner dictionary maps values to names and not names to
108" values.
109let s:SHADA_REV_ENUMS = map(copy(s:SHADA_ENUMS), '{}')
110call map(copy(s:SHADA_ENUMS),
111        \'map(copy(v:val), '
112          \. '"extend(s:SHADA_REV_ENUMS[" . string(v:key) . "], '
113                  \. '{v:val : v:key})")')
114
115""
116" Maximum length of ShaDa entry name. Used to arrange entries to the table.
117let s:SHADA_MAX_ENTRY_LENGTH = max(
118      \map(values(s:SHADA_ENTRY_NAMES), 'len(v:val)')
119      \+ [len('unknown (0x)') + 16])
120
121""
122" Object that marks required value.
123let s:SHADA_REQUIRED = []
124
125""
126" Dictionary that maps default key names to their description. Description is
127" a list that contains human-readable hint, key type and default value.
128let s:SHADA_STANDARD_KEYS = {
129  \'sm': ['magic value', 'boolean', g:msgpack#true],
130  \'sc': ['smartcase value', 'boolean', g:msgpack#false],
131  \'sl': ['has line offset', 'boolean', g:msgpack#false],
132  \'se': ['place cursor at end', 'boolean', g:msgpack#false],
133  \'so': ['offset value', 'integer', 0],
134  \'su': ['is last used', 'boolean', g:msgpack#true],
135  \'ss': ['is :s pattern', 'boolean', g:msgpack#false],
136  \'sh': ['v:hlsearch value', 'boolean', g:msgpack#false],
137  \'sp': ['pattern', 'bin', s:SHADA_REQUIRED],
138  \'sb': ['search backward', 'boolean', g:msgpack#false],
139  \'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE],
140  \'rw': ['block width', 'uint', 0],
141  \'rc': ['contents', 'binarray', s:SHADA_REQUIRED],
142  \'ru': ['is_unnamed', 'boolean', g:msgpack#false],
143  \'n':  ['name', 'intchar', char2nr('"')],
144  \'l':  ['line number', 'uint', 1],
145  \'c':  ['column', 'uint', 0],
146  \'f':  ['file name', 'bin', s:SHADA_REQUIRED],
147\}
148
149""
150" Set of entry types containing entries which require `n` key.
151let s:SHADA_REQUIRES_NAME = {'local_mark': 1, 'global_mark': 1, 'register': 1}
152
153""
154" Maximum width of human-readable hint. Used to arrange data in table.
155let s:SHADA_MAX_HINT_WIDTH = max(map(values(s:SHADA_STANDARD_KEYS),
156                                    \'len(v:val[0])'))
157
158""
159" Default mark name for the cases when it makes sense (i.e. for local marks).
160let s:SHADA_DEFAULT_MARK_NAME = '"'
161
162""
163" Mapping that maps timestamps represented using msgpack#string to strftime
164" output. Used by s:shada_strftime.
165let s:shada_strftime_cache = {}
166
167""
168" Mapping that maps strftime output from s:shada_strftime to timestamps.
169let s:shada_strptime_cache = {}
170
171""
172" Time format used for displaying ShaDa files.
173let s:SHADA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
174
175""
176" Wrapper around msgpack#strftime that caches its output.
177"
178" Format is hardcoded to s:SHADA_TIME_FORMAT.
179function s:shada_strftime(timestamp) abort
180  let key = msgpack#string(a:timestamp)
181  if has_key(s:shada_strftime_cache, key)
182    return s:shada_strftime_cache[key]
183  endif
184  let val = msgpack#strftime(s:SHADA_TIME_FORMAT, a:timestamp)
185  let s:shada_strftime_cache[key] = val
186  let s:shada_strptime_cache[val] = a:timestamp
187  return val
188endfunction
189
190""
191" Wrapper around msgpack#strftime that uses cache created by s:shada_strftime().
192"
193" Also caches its own results. Format is hardcoded to s:SHADA_TIME_FORMAT.
194function s:shada_strptime(string) abort
195  if has_key(s:shada_strptime_cache, a:string)
196    return s:shada_strptime_cache[a:string]
197  endif
198  let ts = msgpack#strptime(s:SHADA_TIME_FORMAT, a:string)
199  let s:shada_strptime_cache[a:string] = ts
200  return ts
201endfunction
202
203""
204" Check whether given value matches given type.
205"
206" @return Zero if value matches, error message string if it does not.
207function s:shada_check_type(type, val) abort
208  let type = msgpack#type(a:val)
209  if type is# a:type
210    return 0
211  endif
212  if has_key(s:SHADA_ENUMS, a:type)
213    let msg = s:shada_check_type('uint', a:val)
214    if msg isnot 0
215      return msg
216    endif
217    if !has_key(s:SHADA_REV_ENUMS[a:type], a:val)
218      let evals_msg = join(map(sort(items(s:SHADA_REV_ENUMS[a:type])),
219                              \'v:val[0] . " (" . v:val[1] . ")"'), ', ')
220      return 'Unexpected enum value: expected one of ' . evals_msg
221    endif
222    return 0
223  elseif a:type is# 'uint'
224    if type isnot# 'integer'
225      return 'Expected integer'
226    endif
227    if !(type(a:val) == type({}) ? a:val._VAL[0] == 1 : a:val >= 0)
228      return 'Value is negative'
229    endif
230    return 0
231  elseif a:type is# 'bin'
232    " Binary string without zero bytes
233    if type isnot# 'binary'
234      return 'Expected binary string'
235    elseif (type(a:val) == type({})
236           \&& !empty(filter(copy(a:val._VAL), 'stridx(v:val, "\n") != -1')))
237      return 'Expected no NUL bytes'
238    endif
239    return 0
240  elseif a:type is# 'intchar'
241    let msg = s:shada_check_type('uint', a:val)
242    if msg isnot# 0
243      return msg
244    endif
245    return 0
246  elseif a:type is# 'binarray'
247    if type isnot# 'array'
248      return 'Expected array value'
249    elseif !empty(filter(copy(type(a:val) == type({}) ? a:val._VAL : a:val),
250                        \'msgpack#type(v:val) isnot# "binary"'))
251      return 'Expected array of binary strings'
252    else
253      for element in (type(a:val) == type({}) ? a:val._VAL : a:val)
254        if (type(element) == type({})
255           \&& !empty(filter(copy(element._VAL), 'stridx(v:val, "\n") != -1')))
256          return 'Expected no NUL bytes'
257        endif
258        unlet element
259      endfor
260    endif
261    return 0
262  elseif a:type is# 'boolean'
263    return 'Expected boolean'
264  elseif a:type is# 'integer'
265    return 'Expected integer'
266  elseif a:type is# 'any'
267    return 0
268  endif
269  return 'Internal error: unknown type ' . a:type
270endfunction
271
272""
273" Convert msgpack mapping object to a list of strings for
274" s:shada_convert_entry().
275"
276" @param[in]  map           Mapping to convert.
277" @param[in]  default_keys  List of keys which have default value in this
278"                           mapping.
279" @param[in]  name          Name of the converted entry.
280function s:shada_convert_map(map, default_keys, name) abort
281  let ret = []
282  let keys = copy(a:default_keys)
283  call map(sort(keys(a:map)), 'index(keys, v:val) == -1 ? add(keys, v:val) : 0')
284  let descriptions = map(copy(keys),
285                        \'get(s:SHADA_STANDARD_KEYS, v:val, ["", 0, 0])')
286  let max_key_len = max(map(copy(keys), 'len(v:val)'))
287  let max_desc_len = max(map(copy(descriptions),
288                            \'v:val[0] is 0 ? 0 : len(v:val[0])'))
289  if max_key_len < len('Key')
290    let max_key_len = len('Key')
291  endif
292  let key_header = 'Key' . repeat('_', max_key_len - len('Key'))
293  if max_desc_len == 0
294    call add(ret, printf('  %% %s  %s', key_header, 'Value'))
295  else
296    if max_desc_len < len('Description')
297      let max_desc_len = len('Description')
298    endif
299    let desc_header = ('Description'
300                      \. repeat('_', max_desc_len - len('Description')))
301    call add(ret, printf('  %% %s  %s  %s', key_header, desc_header, 'Value'))
302  endif
303  let i = 0
304  for key in keys
305    let [description, type, default] = descriptions[i]
306    if a:name isnot# 'local_mark' && key is# 'n'
307      unlet default
308      let default = s:SHADA_REQUIRED
309    endif
310    let value = get(a:map, key, default)
311    if (key is# 'n' && !has_key(s:SHADA_REQUIRES_NAME, a:name)
312       \&& value is# s:SHADA_REQUIRED)
313      " Do nothing
314    elseif value is s:SHADA_REQUIRED
315      call add(ret, '  # Required key missing: ' . key)
316    elseif max_desc_len == 0
317      call add(ret, printf('  + %-*s  %s',
318                          \max_key_len, key,
319                          \msgpack#string(value)))
320    else
321      if type isnot 0 && value isnot# default
322        let msg = s:shada_check_type(type, value)
323        if msg isnot 0
324          call add(ret, '  # ' . msg)
325        endif
326      endif
327      let strval = s:shada_string(type, value)
328      if msgpack#type(value) is# 'array' && msg is 0
329        let shift = 2 + 2 + max_key_len + 2 + max_desc_len + 2
330        " Value:    1   2   3             4   5              6:
331        " "  + Key  Description  Value"
332        "  1122333445555555555566
333        if shift + strdisplaywidth(strval, shift) > 80
334          let strval = '@'
335        endif
336      endif
337      call add(ret, printf('  + %-*s  %-*s  %s',
338                          \max_key_len, key,
339                          \max_desc_len, description,
340                          \strval))
341      if strval is '@'
342        for v in value
343          call add(ret, printf('  | - %s', msgpack#string(v)))
344          unlet v
345        endfor
346      endif
347    endif
348    let i += 1
349    unlet value
350    unlet default
351  endfor
352  return ret
353endfunction
354
355""
356" Wrapper around msgpack#string() which may return string from s:SHADA_REV_ENUMS
357function s:shada_string(type, v) abort
358  if (has_key(s:SHADA_ENUMS, a:type) && type(a:v) == type(0)
359     \&& has_key(s:SHADA_REV_ENUMS[a:type], a:v))
360    return s:SHADA_REV_ENUMS[a:type][a:v]
361  " Restricting a:v to be <= 127 is not necessary, but intchar constants are
362  " normally expected to be either ASCII printable characters or NUL.
363  elseif a:type is# 'intchar' && type(a:v) == type(0) && a:v >= 0 && a:v <= 127
364    if a:v > 0 && strtrans(nr2char(a:v)) is# nr2char(a:v)
365      return "'" . nr2char(a:v) . "'"
366    else
367      return "'\\" . a:v . "'"
368    endif
369  else
370    return msgpack#string(a:v)
371  endif
372endfunction
373
374""
375" Evaluate string obtained by s:shada_string().
376function s:shada_eval(s) abort
377  return msgpack#eval(a:s, s:SHADA_SPECIAL_OBJS)
378endfunction
379
380""
381" Convert one ShaDa entry to a list of strings suitable for setline().
382"
383" Returned format looks like this:
384"
385"     TODO
386function s:shada_convert_entry(entry) abort
387  if type(a:entry.type) == type({})
388    " |msgpack-special-dict| may only be used if value does not fit into the
389    " default integer type. All known entry types do fit, so it is definitely
390    " unknown entry.
391    let name = 'unknown_(' . msgpack#int_dict_to_str(a:entry.type) . ')'
392  else
393    let name = get(s:SHADA_ENTRY_NAMES, a:entry.type, 0)
394    if name is 0
395      let name = printf('unknown_(0x%x)', a:entry.type)
396    endif
397  endif
398  let title = toupper(name[0]) . tr(name[1:], '_', ' ')
399  let header = printf('%s with timestamp %s:', title,
400                     \s:shada_strftime(a:entry.timestamp))
401  let ret = [header]
402  if name[:8] is# 'unknown_(' && name[-1:] is# ')'
403    call add(ret, '  = ' . msgpack#string(a:entry.data))
404  elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name)
405    if type(a:entry.data) != type([])
406      call add(ret, printf('  # Unexpected type: %s instead of array',
407                          \msgpack#type(a:entry.data)))
408      call add(ret, '  = ' . msgpack#string(a:entry.data))
409      return ret
410    endif
411    let i = 0
412    let max_desc_len = max(map(copy(s:SHADA_FIXED_ARRAY_ENTRIES[name]),
413                              \'len(v:val[0])'))
414    if max_desc_len < len('Description')
415      let max_desc_len = len('Description')
416    endif
417    let desc_header = ('Description'
418                      \. repeat('_', max_desc_len - len('Description')))
419    call add(ret, printf('  @ %s  %s', desc_header, 'Value'))
420    for value in a:entry.data
421      let [desc, type] = get(s:SHADA_FIXED_ARRAY_ENTRIES[name], i, ['', 0])
422      if (i == 2 && name is# 'history_entry'
423         \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH)
424        let [desc, type] = ['', 0]
425      endif
426      if type isnot 0
427        let msg = s:shada_check_type(type, value)
428        if msg isnot 0
429          call add(ret, '  # ' . msg)
430        endif
431      endif
432      call add(ret, printf('  - %-*s  %s', max_desc_len, desc,
433                          \s:shada_string(type, value)))
434      let i += 1
435      unlet value
436    endfor
437    if (len(a:entry.data) < len(s:SHADA_FIXED_ARRAY_ENTRIES[name])
438       \&& !(name is# 'history_entry'
439            \&& len(a:entry.data) == 2
440            \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH))
441      call add(ret, '  # Expected more elements in list')
442    endif
443  elseif has_key(s:SHADA_MAP_ENTRIES, name)
444    if type(a:entry.data) != type({})
445      call add(ret, printf('  # Unexpected type: %s instead of map',
446                          \msgpack#type(a:entry.data)))
447      call add(ret, '  = ' . msgpack#string(a:entry.data))
448      return ret
449    endif
450    if msgpack#special_type(a:entry.data) isnot 0
451      call add(ret, '  # Entry is a special dict which is unexpected')
452      call add(ret, '  = ' . msgpack#string(a:entry.data))
453      return ret
454    endif
455    let ret += s:shada_convert_map(a:entry.data, s:SHADA_MAP_ENTRIES[name],
456                                  \name)
457  elseif name is# 'buffer_list'
458    if type(a:entry.data) != type([])
459      call add(ret, printf('  # Unexpected type: %s instead of array',
460                          \msgpack#type(a:entry.data)))
461      call add(ret, '  = ' . msgpack#string(a:entry.data))
462      return ret
463    elseif !empty(filter(copy(a:entry.data),
464                        \'type(v:val) != type({}) '
465                      \. '|| msgpack#special_type(v:val) isnot 0'))
466      call add(ret, '  # Expected array of maps')
467      call add(ret, '  = ' . msgpack#string(a:entry.data))
468      return ret
469    endif
470    for bufdef in a:entry.data
471      if bufdef isnot a:entry.data[0]
472        call add(ret, '')
473      endif
474      let ret += s:shada_convert_map(bufdef, s:SHADA_BUFFER_LIST_KEYS, name)
475    endfor
476  else
477    throw 'internal-unknown-type:Internal error: unknown type name: ' . name
478  endif
479  return ret
480endfunction
481
482""
483" Order of msgpack objects in one ShaDa entry. Each item in the list is name of
484" the key in dictionaries returned by shada#read().
485let s:SHADA_ENTRY_OBJECT_SEQUENCE = ['type', 'timestamp', 'length', 'data']
486
487""
488" Convert list returned by msgpackparse() to a list of ShaDa objects
489"
490" @param[in]  mpack  List of VimL objects returned by msgpackparse().
491"
492" @return List of dictionaries with keys type, timestamp, length and data. Each
493"         dictionary describes one ShaDa entry.
494function shada#mpack_to_sd(mpack) abort
495  let ret = []
496  let i = 0
497  for element in a:mpack
498    let key = s:SHADA_ENTRY_OBJECT_SEQUENCE[
499          \i % len(s:SHADA_ENTRY_OBJECT_SEQUENCE)]
500    if key is# 'type'
501      call add(ret, {})
502    endif
503    let ret[-1][key] = element
504    if key isnot# 'data'
505      if !msgpack#is_uint(element)
506        throw printf('not-uint:Entry %i has %s element '.
507                    \'which is not an unsigned integer',
508                    \len(ret), key)
509      endif
510      if key is# 'type' && msgpack#equal(element, 0)
511        throw printf('zero-uint:Entry %i has %s element '.
512                    \'which is zero',
513                    \len(ret), key)
514      endif
515    endif
516    let i += 1
517    unlet element
518  endfor
519  return ret
520endfunction
521
522""
523" Convert read ShaDa file to a list of lines suitable for setline()
524"
525" @param[in]  shada  List of ShaDa entries like returned by shada#mpack_to_sd().
526"
527" @return List of strings suitable for setline()-like functions.
528function shada#sd_to_strings(shada) abort
529  let ret = []
530  for entry in a:shada
531    let ret += s:shada_convert_entry(entry)
532  endfor
533  return ret
534endfunction
535
536""
537" Convert a readfile()-like list of strings to a list of lines suitable for
538" setline().
539"
540" @param[in]  binstrings  List of strings to convert.
541"
542" @return List of lines.
543function shada#get_strings(binstrings) abort
544  return shada#sd_to_strings(shada#mpack_to_sd(msgpackparse(a:binstrings)))
545endfunction
546
547""
548" Convert s:shada_convert_entry() output to original entry.
549function s:shada_convert_strings(strings) abort
550  let strings = copy(a:strings)
551  let match = matchlist(
552        \strings[0],
553        \'\v\C^(.{-})\m with timestamp \(\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d\):$')
554  if empty(match)
555    throw 'invalid-header:Header has invalid format: ' . strings[0]
556  endif
557  call remove(strings, 0)
558  let title = match[1]
559  let name = tolower(title[0]) . tr(title[1:], ' ', '_')
560  let ret = {}
561  let empty_default = g:msgpack#nil
562  if name[:8] is# 'unknown_(' && name[-1:] is# ')'
563    let ret.type = +name[9:-2]
564  elseif has_key(s:SHADA_ENTRY_TYPES, name)
565    let ret.type = s:SHADA_ENTRY_TYPES[name]
566    if has_key(s:SHADA_MAP_ENTRIES, name)
567      unlet empty_default
568      let empty_default = {}
569    elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name) || name is# 'buffer_list'
570      unlet empty_default
571      let empty_default = []
572    endif
573  else
574    throw 'invalid-type:Unknown type ' . name
575  endif
576  let ret.timestamp = s:shada_strptime(match[2])
577  if empty(strings)
578    let ret.data = empty_default
579  else
580    while !empty(strings)
581      if strings[0][2] is# '='
582        let data = s:shada_eval(strings[0][4:])
583        call remove(strings, 0)
584      elseif strings[0][2] is# '%'
585        if name is# 'buffer_list' && !has_key(ret, 'data')
586          let ret.data = []
587        endif
588        let match = matchlist(
589              \strings[0],
590              \'\m\C^  % \(Key_*\)\(  Description_*\)\?  Value')
591        if empty(match)
592          throw 'invalid-map-header:Invalid mapping header: ' . strings[0]
593        endif
594        call remove(strings, 0)
595        let key_len = len(match[1])
596        let desc_skip_len = len(match[2])
597        let data = {'_TYPE': v:msgpack_types.map, '_VAL': []}
598        while !empty(strings) && strings[0][2] is# '+'
599          let line = remove(strings, 0)[4:]
600          let key = substitute(line[:key_len - 1], '\v\C\ *$', '', '')
601          let strval = line[key_len + desc_skip_len + 2:]
602          if strval is# '@'
603            let val = []
604            while !empty(strings) && strings[0][2] is# '|'
605              if strings[0][4] isnot# '-'
606                throw ('invalid-array:Expected hyphen-minus at column 5: '
607                      \. strings)
608              endif
609              call add(val, s:shada_eval(remove(strings, 0)[5:]))
610            endwhile
611          else
612            let val = s:shada_eval(strval)
613          endif
614          if (has_key(s:SHADA_STANDARD_KEYS, key)
615             \&& s:SHADA_STANDARD_KEYS[key][2] isnot# s:SHADA_REQUIRED
616             \&& msgpack#equal(s:SHADA_STANDARD_KEYS[key][2], val))
617            unlet val
618            continue
619          endif
620          call add(data._VAL, [{'_TYPE': v:msgpack_types.string, '_VAL': [key]},
621                              \val])
622          unlet val
623        endwhile
624      elseif strings[0][2] is# '@'
625        let match = matchlist(
626              \strings[0],
627              \'\m\C^  @ \(Description_*  \)\?Value')
628        if empty(match)
629          throw 'invalid-array-header:Invalid array header: ' . strings[0]
630        endif
631        call remove(strings, 0)
632        let desc_skip_len = len(match[1])
633        let data = []
634        while !empty(strings) && strings[0][2] is# '-'
635          let val = remove(strings, 0)[4 + desc_skip_len :]
636          call add(data, s:shada_eval(val))
637        endwhile
638      else
639        throw 'invalid-line:Unrecognized line: ' . strings[0]
640      endif
641      if !has_key(ret, 'data')
642        let ret.data = data
643      elseif type(ret.data) == type([])
644        call add(ret.data, data)
645      else
646        let ret.data = [ret.data, data]
647      endif
648      unlet data
649    endwhile
650  endif
651  let ret._data = msgpackdump([ret.data])
652  let ret.length = len(ret._data) - 1
653  for s in ret._data
654    let ret.length += len(s)
655  endfor
656  return ret
657endfunction
658
659""
660" Convert s:shada_sd_to_strings() output to a list of original entries.
661function shada#strings_to_sd(strings) abort
662  let strings = filter(copy(a:strings), 'v:val !~# ''\v^\s*%(\#|$)''')
663  let stringss = []
664  for string in strings
665    if string[0] isnot# ' '
666      call add(stringss, [])
667    endif
668    call add(stringss[-1], string)
669  endfor
670  return map(copy(stringss), 's:shada_convert_strings(v:val)')
671endfunction
672
673""
674" Convert a list of strings to list of strings suitable for writefile().
675function shada#get_binstrings(strings) abort
676  let entries = shada#strings_to_sd(a:strings)
677  if !g:shada#keep_old_header
678    call filter(entries, 'v:val.type != ' . s:SHADA_ENTRY_TYPES.header)
679  endif
680  if g:shada#add_own_header
681    let data = {'version': v:version, 'generator': 'shada.vim'}
682    let dumped_data = msgpackdump([data])
683    let length = len(dumped_data) - 1
684    for s in dumped_data
685      let length += len(s)
686    endfor
687    call insert(entries, {
688            \'type': s:SHADA_ENTRY_TYPES.header,
689            \'timestamp': localtime(),
690            \'length': length,
691            \'data': data,
692            \'_data': dumped_data,
693          \})
694  endif
695  let mpack = []
696  for entry in entries
697    let mpack += map(copy(s:SHADA_ENTRY_OBJECT_SEQUENCE), 'entry[v:val]')
698  endfor
699  return msgpackdump(mpack)
700endfunction
701