neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

shada.vim (23564B)


      1 if exists('g:loaded_shada_autoload')
      2  finish
      3 endif
      4 let 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.
     13 let 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.
     17 let g:shada#add_own_header = get(g:, 'shada#add_own_header', 1)
     18 
     19 ""
     20 " Dictionary that maps ShaDa types to their names.
     21 let 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
     37 let s:SHADA_ENTRY_TYPES = {}
     38 call 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.
     45 let 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.
     59 let 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.
     64 let 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.
     70 let 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.
     85 let 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.
    102 let s:SHADA_SPECIAL_OBJS = {}
    103 call 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.
    109 let s:SHADA_REV_ENUMS = map(copy(s:SHADA_ENUMS), '{}')
    110 call 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.
    117 let 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.
    123 let 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.
    128 let 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.
    151 let 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.
    155 let 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).
    160 let 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.
    165 let s:shada_strftime_cache = {}
    166 
    167 ""
    168 " Mapping that maps strftime output from s:shada_strftime to timestamps.
    169 let s:shada_strptime_cache = {}
    170 
    171 ""
    172 " Time format used for displaying ShaDa files.
    173 let 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.
    179 function 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
    188 endfunction
    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.
    194 function 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
    201 endfunction
    202 
    203 ""
    204 " Check whether given value matches given type.
    205 "
    206 " @return Zero if value matches, error message string if it does not.
    207 function 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# 'string'
    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# "string"'))
    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
    270 endfunction
    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.
    280 function 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
    353 endfunction
    354 
    355 ""
    356 " Wrapper around msgpack#string() which may return string from s:SHADA_REV_ENUMS
    357 function 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
    372 endfunction
    373 
    374 ""
    375 " Evaluate string obtained by s:shada_string().
    376 function s:shada_eval(s) abort
    377  return msgpack#eval(a:s, s:SHADA_SPECIAL_OBJS)
    378 endfunction
    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
    386 function 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
    480 endfunction
    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().
    485 let 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 Vimscript objects returned by msgpackparse().
    491 "
    492 " @return List of dictionaries with keys type, timestamp, length and data. Each 
    493 "         dictionary describes one ShaDa entry.
    494 function 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
    520 endfunction
    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.
    528 function 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
    534 endfunction
    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.
    543 function shada#get_strings(binstrings) abort
    544  return shada#sd_to_strings(shada#mpack_to_sd(msgpackparse(a:binstrings)))
    545 endfunction
    546 
    547 ""
    548 " Convert s:shada_convert_entry() output to original entry.
    549 function 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
    657 endfunction
    658 
    659 ""
    660 " Convert s:shada_sd_to_strings() output to a list of original entries.
    661 function 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)')
    671 endfunction
    672 
    673 ""
    674 " Convert a list of strings to list of strings suitable for writefile().
    675 function 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)
    700 endfunction