mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			701 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
			
		
		
	
	
			701 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			VimL
		
	
	
	
	
	
| if exists('g:loaded_shada_autoload')
 | ||
|   finish
 | ||
| endif
 | ||
| let g:loaded_shada_autoload = 1
 | ||
| 
 | ||
| ""
 | ||
| " If true keep the old header entry when editing existing ShaDa file.
 | ||
| "
 | ||
| " Old header entry will be kept only if it is listed in the opened file. To 
 | ||
| " remove old header entry despite of the setting just remove it from the 
 | ||
| " listing. Setting it to false makes plugin ignore all header entries. Defaults 
 | ||
| " to 1.
 | ||
| let g:shada#keep_old_header = get(g:, 'shada#keep_old_header', 1)
 | ||
| 
 | ||
| ""
 | ||
| " If true then first entry will be plugin’s own header entry.
 | ||
| let g:shada#add_own_header = get(g:, 'shada#add_own_header', 1)
 | ||
| 
 | ||
| ""
 | ||
| " Dictionary that maps ShaDa types to their names.
 | ||
| let s:SHADA_ENTRY_NAMES = {
 | ||
|   \1: 'header',
 | ||
|   \2: 'search_pattern',
 | ||
|   \3: 'replacement_string',
 | ||
|   \4: 'history_entry',
 | ||
|   \5: 'register',
 | ||
|   \6: 'variable',
 | ||
|   \7: 'global_mark',
 | ||
|   \8: 'jump',
 | ||
|   \9: 'buffer_list',
 | ||
|   \10: 'local_mark',
 | ||
|   \11: 'change',
 | ||
| \}
 | ||
| 
 | ||
| ""
 | ||
| " Dictionary that maps ShaDa names to corresponding types
 | ||
| let s:SHADA_ENTRY_TYPES = {}
 | ||
| call map(copy(s:SHADA_ENTRY_NAMES),
 | ||
|         \'extend(s:SHADA_ENTRY_TYPES, {v:val : +v:key})')
 | ||
| 
 | ||
| ""
 | ||
| " Map that maps entry names to lists of keys that can be used by this entry. 
 | ||
| " Only contains data for entries which are represented as mappings, except for 
 | ||
| " the header.
 | ||
| let s:SHADA_MAP_ENTRIES = {
 | ||
|   \'search_pattern': ['sp', 'sh', 'ss', 'sb', 'sm', 'sc', 'sl', 'se', 'so',
 | ||
|   \                   'su'],
 | ||
|   \'register': ['n', 'rc', 'rw', 'rt', 'ru'],
 | ||
|   \'global_mark': ['n', 'f', 'l', 'c'],
 | ||
|   \'local_mark': ['f', 'n', 'l', 'c'],
 | ||
|   \'jump': ['f', 'l', 'c'],
 | ||
|   \'change': ['f', 'l', 'c'],
 | ||
|   \'header': [],
 | ||
| \}
 | ||
| 
 | ||
| ""
 | ||
| " Like one of the values from s:SHADA_MAP_ENTRIES, but for a single buffer in 
 | ||
| " buffer list entry.
 | ||
| let s:SHADA_BUFFER_LIST_KEYS = ['f', 'l', 'c']
 | ||
| 
 | ||
| ""
 | ||
| " List of possible history types. Maps integer values that represent history 
 | ||
| " types to human-readable names.
 | ||
| let s:SHADA_HISTORY_TYPES = ['command', 'search', 'expression', 'input', 'debug']
 | ||
| 
 | ||
| ""
 | ||
| " Map that maps entry names to their descriptions. Only for entries which have 
 | ||
| " list as a data type. Description is a list of lists where each entry has item 
 | ||
| " description and item type.
 | ||
| let s:SHADA_FIXED_ARRAY_ENTRIES = {
 | ||
|   \'replacement_string': [[':s replacement string', 'bin']],
 | ||
|   \'history_entry': [
 | ||
|     \['history type', 'histtype'],
 | ||
|     \['contents', 'bin'],
 | ||
|     \['separator', 'intchar'],
 | ||
|   \],
 | ||
|   \'variable': [['name', 'bin'], ['value', 'any']],
 | ||
| \}
 | ||
| 
 | ||
| ""
 | ||
| " Dictionary that maps enum names to dictionary with enum values. Dictionary 
 | ||
| " with enum values maps enum human-readable names to corresponding values. Enums 
 | ||
| " are used as type names in s:SHADA_FIXED_ARRAY_ENTRIES and 
 | ||
| " s:SHADA_STANDARD_KEYS.
 | ||
| let s:SHADA_ENUMS = {
 | ||
|   \'histtype': {
 | ||
|     \'CMD': 0,
 | ||
|     \'SEARCH': 1,
 | ||
|     \'EXPR': 2,
 | ||
|     \'INPUT': 3,
 | ||
|     \'DEBUG': 4,
 | ||
|   \},
 | ||
|   \'regtype': {
 | ||
|     \'CHARACTERWISE': 0,
 | ||
|     \'LINEWISE': 1,
 | ||
|     \'BLOCKWISE': 2,
 | ||
|   \}
 | ||
| \}
 | ||
| 
 | ||
| ""
 | ||
| " Second argument to msgpack#eval.
 | ||
| let s:SHADA_SPECIAL_OBJS = {}
 | ||
| call map(values(s:SHADA_ENUMS),
 | ||
|         \'extend(s:SHADA_SPECIAL_OBJS, map(copy(v:val), "string(v:val)"))')
 | ||
| 
 | ||
| ""
 | ||
| " Like s:SHADA_ENUMS, but inner dictionary maps values to names and not names to 
 | ||
| " values.
 | ||
| let s:SHADA_REV_ENUMS = map(copy(s:SHADA_ENUMS), '{}')
 | ||
| call map(copy(s:SHADA_ENUMS),
 | ||
|         \'map(copy(v:val), '
 | ||
|           \. '"extend(s:SHADA_REV_ENUMS[" . string(v:key) . "], '
 | ||
|                   \. '{v:val : v:key})")')
 | ||
| 
 | ||
| ""
 | ||
| " Maximum length of ShaDa entry name. Used to arrange entries to the table.
 | ||
| let s:SHADA_MAX_ENTRY_LENGTH = max(
 | ||
|       \map(values(s:SHADA_ENTRY_NAMES), 'len(v:val)')
 | ||
|       \+ [len('unknown (0x)') + 16])
 | ||
| 
 | ||
| ""
 | ||
| " Object that marks required value.
 | ||
| let s:SHADA_REQUIRED = []
 | ||
| 
 | ||
| ""
 | ||
| " Dictionary that maps default key names to their description. Description is 
 | ||
| " a list that contains human-readable hint, key type and default value.
 | ||
| let s:SHADA_STANDARD_KEYS = {
 | ||
|   \'sm': ['magic value', 'boolean', g:msgpack#true],
 | ||
|   \'sc': ['smartcase value', 'boolean', g:msgpack#false],
 | ||
|   \'sl': ['has line offset', 'boolean', g:msgpack#false],
 | ||
|   \'se': ['place cursor at end', 'boolean', g:msgpack#false],
 | ||
|   \'so': ['offset value', 'integer', 0],
 | ||
|   \'su': ['is last used', 'boolean', g:msgpack#true],
 | ||
|   \'ss': ['is :s pattern', 'boolean', g:msgpack#false],
 | ||
|   \'sh': ['v:hlsearch value', 'boolean', g:msgpack#false],
 | ||
|   \'sp': ['pattern', 'bin', s:SHADA_REQUIRED],
 | ||
|   \'sb': ['search backward', 'boolean', g:msgpack#false],
 | ||
|   \'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE],
 | ||
|   \'rw': ['block width', 'uint', 0],
 | ||
|   \'rc': ['contents', 'binarray', s:SHADA_REQUIRED],
 | ||
|   \'ru': ['is_unnamed', 'boolean', g:msgpack#false],
 | ||
|   \'n':  ['name', 'intchar', char2nr('"')],
 | ||
|   \'l':  ['line number', 'uint', 1],
 | ||
|   \'c':  ['column', 'uint', 0],
 | ||
|   \'f':  ['file name', 'bin', s:SHADA_REQUIRED],
 | ||
| \}
 | ||
| 
 | ||
| ""
 | ||
| " Set of entry types containing entries which require `n` key.
 | ||
| let s:SHADA_REQUIRES_NAME = {'local_mark': 1, 'global_mark': 1, 'register': 1}
 | ||
| 
 | ||
| ""
 | ||
| " Maximum width of human-readable hint. Used to arrange data in table.
 | ||
| let s:SHADA_MAX_HINT_WIDTH = max(map(values(s:SHADA_STANDARD_KEYS),
 | ||
|                                     \'len(v:val[0])'))
 | ||
| 
 | ||
| ""
 | ||
| " Default mark name for the cases when it makes sense (i.e. for local marks).
 | ||
| let s:SHADA_DEFAULT_MARK_NAME = '"'
 | ||
| 
 | ||
| ""
 | ||
| " Mapping that maps timestamps represented using msgpack#string to strftime 
 | ||
| " output. Used by s:shada_strftime.
 | ||
| let s:shada_strftime_cache = {}
 | ||
| 
 | ||
| ""
 | ||
| " Mapping that maps strftime output from s:shada_strftime to timestamps.
 | ||
| let s:shada_strptime_cache = {}
 | ||
| 
 | ||
| ""
 | ||
| " Time format used for displaying ShaDa files.
 | ||
| let s:SHADA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
 | ||
| 
 | ||
| ""
 | ||
| " Wrapper around msgpack#strftime that caches its output.
 | ||
| "
 | ||
| " Format is hardcoded to s:SHADA_TIME_FORMAT.
 | ||
| function s:shada_strftime(timestamp) abort
 | ||
|   let key = msgpack#string(a:timestamp)
 | ||
|   if has_key(s:shada_strftime_cache, key)
 | ||
|     return s:shada_strftime_cache[key]
 | ||
|   endif
 | ||
|   let val = msgpack#strftime(s:SHADA_TIME_FORMAT, a:timestamp)
 | ||
|   let s:shada_strftime_cache[key] = val
 | ||
|   let s:shada_strptime_cache[val] = a:timestamp
 | ||
|   return val
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Wrapper around msgpack#strftime that uses cache created by s:shada_strftime().
 | ||
| "
 | ||
| " Also caches its own results. Format is hardcoded to s:SHADA_TIME_FORMAT.
 | ||
| function s:shada_strptime(string) abort
 | ||
|   if has_key(s:shada_strptime_cache, a:string)
 | ||
|     return s:shada_strptime_cache[a:string]
 | ||
|   endif
 | ||
|   let ts = msgpack#strptime(s:SHADA_TIME_FORMAT, a:string)
 | ||
|   let s:shada_strptime_cache[a:string] = ts
 | ||
|   return ts
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Check whether given value matches given type.
 | ||
| "
 | ||
| " @return Zero if value matches, error message string if it does not.
 | ||
| function s:shada_check_type(type, val) abort
 | ||
|   let type = msgpack#type(a:val)
 | ||
|   if type is# a:type
 | ||
|     return 0
 | ||
|   endif
 | ||
|   if has_key(s:SHADA_ENUMS, a:type)
 | ||
|     let msg = s:shada_check_type('uint', a:val)
 | ||
|     if msg isnot 0
 | ||
|       return msg
 | ||
|     endif
 | ||
|     if !has_key(s:SHADA_REV_ENUMS[a:type], a:val)
 | ||
|       let evals_msg = join(map(sort(items(s:SHADA_REV_ENUMS[a:type])),
 | ||
|                               \'v:val[0] . " (" . v:val[1] . ")"'), ', ')
 | ||
|       return 'Unexpected enum value: expected one of ' . evals_msg
 | ||
|     endif
 | ||
|     return 0
 | ||
|   elseif a:type is# 'uint'
 | ||
|     if type isnot# 'integer'
 | ||
|       return 'Expected integer'
 | ||
|     endif
 | ||
|     if !(type(a:val) == type({}) ? a:val._VAL[0] == 1 : a:val >= 0)
 | ||
|       return 'Value is negative'
 | ||
|     endif
 | ||
|     return 0
 | ||
|   elseif a:type is# 'bin'
 | ||
|     " Binary string without zero bytes
 | ||
|     if type isnot# 'binary'
 | ||
|       return 'Expected binary string'
 | ||
|     elseif (type(a:val) == type({})
 | ||
|            \&& !empty(filter(copy(a:val._VAL), 'stridx(v:val, "\n") != -1')))
 | ||
|       return 'Expected no NUL bytes'
 | ||
|     endif
 | ||
|     return 0
 | ||
|   elseif a:type is# 'intchar'
 | ||
|     let msg = s:shada_check_type('uint', a:val)
 | ||
|     if msg isnot# 0
 | ||
|       return msg
 | ||
|     endif
 | ||
|     return 0
 | ||
|   elseif a:type is# 'binarray'
 | ||
|     if type isnot# 'array'
 | ||
|       return 'Expected array value'
 | ||
|     elseif !empty(filter(copy(type(a:val) == type({}) ? a:val._VAL : a:val),
 | ||
|                         \'msgpack#type(v:val) isnot# "binary"'))
 | ||
|       return 'Expected array of binary strings'
 | ||
|     else
 | ||
|       for element in (type(a:val) == type({}) ? a:val._VAL : a:val)
 | ||
|         if (type(element) == type({})
 | ||
|            \&& !empty(filter(copy(element._VAL), 'stridx(v:val, "\n") != -1')))
 | ||
|           return 'Expected no NUL bytes'
 | ||
|         endif
 | ||
|         unlet element
 | ||
|       endfor
 | ||
|     endif
 | ||
|     return 0
 | ||
|   elseif a:type is# 'boolean'
 | ||
|     return 'Expected boolean'
 | ||
|   elseif a:type is# 'integer'
 | ||
|     return 'Expected integer'
 | ||
|   elseif a:type is# 'any'
 | ||
|     return 0
 | ||
|   endif
 | ||
|   return 'Internal error: unknown type ' . a:type
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert msgpack mapping object to a list of strings for 
 | ||
| " s:shada_convert_entry().
 | ||
| "
 | ||
| " @param[in]  map           Mapping to convert.
 | ||
| " @param[in]  default_keys  List of keys which have default value in this 
 | ||
| "                           mapping.
 | ||
| " @param[in]  name          Name of the converted entry.
 | ||
| function s:shada_convert_map(map, default_keys, name) abort
 | ||
|   let ret = []
 | ||
|   let keys = copy(a:default_keys)
 | ||
|   call map(sort(keys(a:map)), 'index(keys, v:val) == -1 ? add(keys, v:val) : 0')
 | ||
|   let descriptions = map(copy(keys),
 | ||
|                         \'get(s:SHADA_STANDARD_KEYS, v:val, ["", 0, 0])')
 | ||
|   let max_key_len = max(map(copy(keys), 'len(v:val)'))
 | ||
|   let max_desc_len = max(map(copy(descriptions),
 | ||
|                             \'v:val[0] is 0 ? 0 : len(v:val[0])'))
 | ||
|   if max_key_len < len('Key')
 | ||
|     let max_key_len = len('Key')
 | ||
|   endif
 | ||
|   let key_header = 'Key' . repeat('_', max_key_len - len('Key'))
 | ||
|   if max_desc_len == 0
 | ||
|     call add(ret, printf('  %% %s  %s', key_header, 'Value'))
 | ||
|   else
 | ||
|     if max_desc_len < len('Description')
 | ||
|       let max_desc_len = len('Description')
 | ||
|     endif
 | ||
|     let desc_header = ('Description'
 | ||
|                       \. repeat('_', max_desc_len - len('Description')))
 | ||
|     call add(ret, printf('  %% %s  %s  %s', key_header, desc_header, 'Value'))
 | ||
|   endif
 | ||
|   let i = 0
 | ||
|   for key in keys
 | ||
|     let [description, type, default] = descriptions[i]
 | ||
|     if a:name isnot# 'local_mark' && key is# 'n'
 | ||
|       unlet default
 | ||
|       let default = s:SHADA_REQUIRED
 | ||
|     endif
 | ||
|     let value = get(a:map, key, default)
 | ||
|     if (key is# 'n' && !has_key(s:SHADA_REQUIRES_NAME, a:name)
 | ||
|        \&& value is# s:SHADA_REQUIRED)
 | ||
|       " Do nothing
 | ||
|     elseif value is s:SHADA_REQUIRED
 | ||
|       call add(ret, '  # Required key missing: ' . key)
 | ||
|     elseif max_desc_len == 0
 | ||
|       call add(ret, printf('  + %-*s  %s',
 | ||
|                           \max_key_len, key,
 | ||
|                           \msgpack#string(value)))
 | ||
|     else
 | ||
|       if type isnot 0 && value isnot# default
 | ||
|         let msg = s:shada_check_type(type, value)
 | ||
|         if msg isnot 0
 | ||
|           call add(ret, '  # ' . msg)
 | ||
|         endif
 | ||
|       endif
 | ||
|       let strval = s:shada_string(type, value)
 | ||
|       if msgpack#type(value) is# 'array' && msg is 0
 | ||
|         let shift = 2 + 2 + max_key_len + 2 + max_desc_len + 2
 | ||
|         " Value:    1   2   3             4   5              6:
 | ||
|         " "  + Key  Description  Value"
 | ||
|         "  1122333445555555555566
 | ||
|         if shift + strdisplaywidth(strval, shift) > 80
 | ||
|           let strval = '@'
 | ||
|         endif
 | ||
|       endif
 | ||
|       call add(ret, printf('  + %-*s  %-*s  %s',
 | ||
|                           \max_key_len, key,
 | ||
|                           \max_desc_len, description,
 | ||
|                           \strval))
 | ||
|       if strval is '@'
 | ||
|         for v in value
 | ||
|           call add(ret, printf('  | - %s', msgpack#string(v)))
 | ||
|           unlet v
 | ||
|         endfor
 | ||
|       endif
 | ||
|     endif
 | ||
|     let i += 1
 | ||
|     unlet value
 | ||
|     unlet default
 | ||
|   endfor
 | ||
|   return ret
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Wrapper around msgpack#string() which may return string from s:SHADA_REV_ENUMS
 | ||
| function s:shada_string(type, v) abort
 | ||
|   if (has_key(s:SHADA_ENUMS, a:type) && type(a:v) == type(0)
 | ||
|      \&& has_key(s:SHADA_REV_ENUMS[a:type], a:v))
 | ||
|     return s:SHADA_REV_ENUMS[a:type][a:v]
 | ||
|   " Restricting a:v to be <= 127 is not necessary, but intchar constants are
 | ||
|   " normally expected to be either ASCII printable characters or NUL.
 | ||
|   elseif a:type is# 'intchar' && type(a:v) == type(0) && a:v >= 0 && a:v <= 127
 | ||
|     if a:v > 0 && strtrans(nr2char(a:v)) is# nr2char(a:v)
 | ||
|       return "'" . nr2char(a:v) . "'"
 | ||
|     else
 | ||
|       return "'\\" . a:v . "'"
 | ||
|     endif
 | ||
|   else
 | ||
|     return msgpack#string(a:v)
 | ||
|   endif
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Evaluate string obtained by s:shada_string().
 | ||
| function s:shada_eval(s) abort
 | ||
|   return msgpack#eval(a:s, s:SHADA_SPECIAL_OBJS)
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert one ShaDa entry to a list of strings suitable for setline().
 | ||
| "
 | ||
| " Returned format looks like this:
 | ||
| "
 | ||
| "     TODO
 | ||
| function s:shada_convert_entry(entry) abort
 | ||
|   if type(a:entry.type) == type({})
 | ||
|     " |msgpack-special-dict| may only be used if value does not fit into the 
 | ||
|     " default integer type. All known entry types do fit, so it is definitely 
 | ||
|     " unknown entry.
 | ||
|     let name = 'unknown_(' . msgpack#int_dict_to_str(a:entry.type) . ')'
 | ||
|   else
 | ||
|     let name = get(s:SHADA_ENTRY_NAMES, a:entry.type, 0)
 | ||
|     if name is 0
 | ||
|       let name = printf('unknown_(0x%x)', a:entry.type)
 | ||
|     endif
 | ||
|   endif
 | ||
|   let title = toupper(name[0]) . tr(name[1:], '_', ' ')
 | ||
|   let header = printf('%s with timestamp %s:', title,
 | ||
|                      \s:shada_strftime(a:entry.timestamp))
 | ||
|   let ret = [header]
 | ||
|   if name[:8] is# 'unknown_(' && name[-1:] is# ')'
 | ||
|     call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|   elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name)
 | ||
|     if type(a:entry.data) != type([])
 | ||
|       call add(ret, printf('  # Unexpected type: %s instead of array',
 | ||
|                           \msgpack#type(a:entry.data)))
 | ||
|       call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|       return ret
 | ||
|     endif
 | ||
|     let i = 0
 | ||
|     let max_desc_len = max(map(copy(s:SHADA_FIXED_ARRAY_ENTRIES[name]),
 | ||
|                               \'len(v:val[0])'))
 | ||
|     if max_desc_len < len('Description')
 | ||
|       let max_desc_len = len('Description')
 | ||
|     endif
 | ||
|     let desc_header = ('Description'
 | ||
|                       \. repeat('_', max_desc_len - len('Description')))
 | ||
|     call add(ret, printf('  @ %s  %s', desc_header, 'Value'))
 | ||
|     for value in a:entry.data
 | ||
|       let [desc, type] = get(s:SHADA_FIXED_ARRAY_ENTRIES[name], i, ['', 0])
 | ||
|       if (i == 2 && name is# 'history_entry'
 | ||
|          \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH)
 | ||
|         let [desc, type] = ['', 0]
 | ||
|       endif
 | ||
|       if type isnot 0
 | ||
|         let msg = s:shada_check_type(type, value)
 | ||
|         if msg isnot 0
 | ||
|           call add(ret, '  # ' . msg)
 | ||
|         endif
 | ||
|       endif
 | ||
|       call add(ret, printf('  - %-*s  %s', max_desc_len, desc,
 | ||
|                           \s:shada_string(type, value)))
 | ||
|       let i += 1
 | ||
|       unlet value
 | ||
|     endfor
 | ||
|     if (len(a:entry.data) < len(s:SHADA_FIXED_ARRAY_ENTRIES[name])
 | ||
|        \&& !(name is# 'history_entry'
 | ||
|             \&& len(a:entry.data) == 2
 | ||
|             \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH))
 | ||
|       call add(ret, '  # Expected more elements in list')
 | ||
|     endif
 | ||
|   elseif has_key(s:SHADA_MAP_ENTRIES, name)
 | ||
|     if type(a:entry.data) != type({})
 | ||
|       call add(ret, printf('  # Unexpected type: %s instead of map',
 | ||
|                           \msgpack#type(a:entry.data)))
 | ||
|       call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|       return ret
 | ||
|     endif
 | ||
|     if msgpack#special_type(a:entry.data) isnot 0
 | ||
|       call add(ret, '  # Entry is a special dict which is unexpected')
 | ||
|       call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|       return ret
 | ||
|     endif
 | ||
|     let ret += s:shada_convert_map(a:entry.data, s:SHADA_MAP_ENTRIES[name],
 | ||
|                                   \name)
 | ||
|   elseif name is# 'buffer_list'
 | ||
|     if type(a:entry.data) != type([])
 | ||
|       call add(ret, printf('  # Unexpected type: %s instead of array',
 | ||
|                           \msgpack#type(a:entry.data)))
 | ||
|       call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|       return ret
 | ||
|     elseif !empty(filter(copy(a:entry.data),
 | ||
|                         \'type(v:val) != type({}) '
 | ||
|                       \. '|| msgpack#special_type(v:val) isnot 0'))
 | ||
|       call add(ret, '  # Expected array of maps')
 | ||
|       call add(ret, '  = ' . msgpack#string(a:entry.data))
 | ||
|       return ret
 | ||
|     endif
 | ||
|     for bufdef in a:entry.data
 | ||
|       if bufdef isnot a:entry.data[0]
 | ||
|         call add(ret, '')
 | ||
|       endif
 | ||
|       let ret += s:shada_convert_map(bufdef, s:SHADA_BUFFER_LIST_KEYS, name)
 | ||
|     endfor
 | ||
|   else
 | ||
|     throw 'internal-unknown-type:Internal error: unknown type name: ' . name
 | ||
|   endif
 | ||
|   return ret
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Order of msgpack objects in one ShaDa entry. Each item in the list is name of 
 | ||
| " the key in dictionaries returned by shada#read().
 | ||
| let s:SHADA_ENTRY_OBJECT_SEQUENCE = ['type', 'timestamp', 'length', 'data']
 | ||
| 
 | ||
| ""
 | ||
| " Convert list returned by msgpackparse() to a list of ShaDa objects
 | ||
| "
 | ||
| " @param[in]  mpack  List of Vimscript objects returned by msgpackparse().
 | ||
| "
 | ||
| " @return List of dictionaries with keys type, timestamp, length and data. Each 
 | ||
| "         dictionary describes one ShaDa entry.
 | ||
| function shada#mpack_to_sd(mpack) abort
 | ||
|   let ret = []
 | ||
|   let i = 0
 | ||
|   for element in a:mpack
 | ||
|     let key = s:SHADA_ENTRY_OBJECT_SEQUENCE[
 | ||
|           \i % len(s:SHADA_ENTRY_OBJECT_SEQUENCE)]
 | ||
|     if key is# 'type'
 | ||
|       call add(ret, {})
 | ||
|     endif
 | ||
|     let ret[-1][key] = element
 | ||
|     if key isnot# 'data'
 | ||
|       if !msgpack#is_uint(element)
 | ||
|         throw printf('not-uint:Entry %i has %s element '.
 | ||
|                     \'which is not an unsigned integer',
 | ||
|                     \len(ret), key)
 | ||
|       endif
 | ||
|       if key is# 'type' && msgpack#equal(element, 0)
 | ||
|         throw printf('zero-uint:Entry %i has %s element '.
 | ||
|                     \'which is zero',
 | ||
|                     \len(ret), key)
 | ||
|       endif
 | ||
|     endif
 | ||
|     let i += 1
 | ||
|     unlet element
 | ||
|   endfor
 | ||
|   return ret
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert read ShaDa file to a list of lines suitable for setline()
 | ||
| "
 | ||
| " @param[in]  shada  List of ShaDa entries like returned by shada#mpack_to_sd().
 | ||
| "
 | ||
| " @return List of strings suitable for setline()-like functions.
 | ||
| function shada#sd_to_strings(shada) abort
 | ||
|   let ret = []
 | ||
|   for entry in a:shada
 | ||
|     let ret += s:shada_convert_entry(entry)
 | ||
|   endfor
 | ||
|   return ret
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert a readfile()-like list of strings to a list of lines suitable for 
 | ||
| " setline().
 | ||
| "
 | ||
| " @param[in]  binstrings  List of strings to convert.
 | ||
| "
 | ||
| " @return List of lines.
 | ||
| function shada#get_strings(binstrings) abort
 | ||
|   return shada#sd_to_strings(shada#mpack_to_sd(msgpackparse(a:binstrings)))
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert s:shada_convert_entry() output to original entry.
 | ||
| function s:shada_convert_strings(strings) abort
 | ||
|   let strings = copy(a:strings)
 | ||
|   let match = matchlist(
 | ||
|         \strings[0],
 | ||
|         \'\v\C^(.{-})\m with timestamp \(\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d\):$')
 | ||
|   if empty(match)
 | ||
|     throw 'invalid-header:Header has invalid format: ' . strings[0]
 | ||
|   endif
 | ||
|   call remove(strings, 0)
 | ||
|   let title = match[1]
 | ||
|   let name = tolower(title[0]) . tr(title[1:], ' ', '_')
 | ||
|   let ret = {}
 | ||
|   let empty_default = g:msgpack#nil
 | ||
|   if name[:8] is# 'unknown_(' && name[-1:] is# ')'
 | ||
|     let ret.type = +name[9:-2]
 | ||
|   elseif has_key(s:SHADA_ENTRY_TYPES, name)
 | ||
|     let ret.type = s:SHADA_ENTRY_TYPES[name]
 | ||
|     if has_key(s:SHADA_MAP_ENTRIES, name)
 | ||
|       unlet empty_default
 | ||
|       let empty_default = {}
 | ||
|     elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name) || name is# 'buffer_list'
 | ||
|       unlet empty_default
 | ||
|       let empty_default = []
 | ||
|     endif
 | ||
|   else
 | ||
|     throw 'invalid-type:Unknown type ' . name
 | ||
|   endif
 | ||
|   let ret.timestamp = s:shada_strptime(match[2])
 | ||
|   if empty(strings)
 | ||
|     let ret.data = empty_default
 | ||
|   else
 | ||
|     while !empty(strings)
 | ||
|       if strings[0][2] is# '='
 | ||
|         let data = s:shada_eval(strings[0][4:])
 | ||
|         call remove(strings, 0)
 | ||
|       elseif strings[0][2] is# '%'
 | ||
|         if name is# 'buffer_list' && !has_key(ret, 'data')
 | ||
|           let ret.data = []
 | ||
|         endif
 | ||
|         let match = matchlist(
 | ||
|               \strings[0],
 | ||
|               \'\m\C^  % \(Key_*\)\(  Description_*\)\?  Value')
 | ||
|         if empty(match)
 | ||
|           throw 'invalid-map-header:Invalid mapping header: ' . strings[0]
 | ||
|         endif
 | ||
|         call remove(strings, 0)
 | ||
|         let key_len = len(match[1])
 | ||
|         let desc_skip_len = len(match[2])
 | ||
|         let data = {'_TYPE': v:msgpack_types.map, '_VAL': []}
 | ||
|         while !empty(strings) && strings[0][2] is# '+'
 | ||
|           let line = remove(strings, 0)[4:]
 | ||
|           let key = substitute(line[:key_len - 1], '\v\C\ *$', '', '')
 | ||
|           let strval = line[key_len + desc_skip_len + 2:]
 | ||
|           if strval is# '@'
 | ||
|             let val = []
 | ||
|             while !empty(strings) && strings[0][2] is# '|'
 | ||
|               if strings[0][4] isnot# '-'
 | ||
|                 throw ('invalid-array:Expected hyphen-minus at column 5: '
 | ||
|                       \. strings)
 | ||
|               endif
 | ||
|               call add(val, s:shada_eval(remove(strings, 0)[5:]))
 | ||
|             endwhile
 | ||
|           else
 | ||
|             let val = s:shada_eval(strval)
 | ||
|           endif
 | ||
|           if (has_key(s:SHADA_STANDARD_KEYS, key)
 | ||
|              \&& s:SHADA_STANDARD_KEYS[key][2] isnot# s:SHADA_REQUIRED
 | ||
|              \&& msgpack#equal(s:SHADA_STANDARD_KEYS[key][2], val))
 | ||
|             unlet val
 | ||
|             continue
 | ||
|           endif
 | ||
|           call add(data._VAL, [{'_TYPE': v:msgpack_types.string, '_VAL': [key]},
 | ||
|                               \val])
 | ||
|           unlet val
 | ||
|         endwhile
 | ||
|       elseif strings[0][2] is# '@'
 | ||
|         let match = matchlist(
 | ||
|               \strings[0],
 | ||
|               \'\m\C^  @ \(Description_*  \)\?Value')
 | ||
|         if empty(match)
 | ||
|           throw 'invalid-array-header:Invalid array header: ' . strings[0]
 | ||
|         endif
 | ||
|         call remove(strings, 0)
 | ||
|         let desc_skip_len = len(match[1])
 | ||
|         let data = []
 | ||
|         while !empty(strings) && strings[0][2] is# '-'
 | ||
|           let val = remove(strings, 0)[4 + desc_skip_len :]
 | ||
|           call add(data, s:shada_eval(val))
 | ||
|         endwhile
 | ||
|       else
 | ||
|         throw 'invalid-line:Unrecognized line: ' . strings[0]
 | ||
|       endif
 | ||
|       if !has_key(ret, 'data')
 | ||
|         let ret.data = data
 | ||
|       elseif type(ret.data) == type([])
 | ||
|         call add(ret.data, data)
 | ||
|       else
 | ||
|         let ret.data = [ret.data, data]
 | ||
|       endif
 | ||
|       unlet data
 | ||
|     endwhile
 | ||
|   endif
 | ||
|   let ret._data = msgpackdump([ret.data])
 | ||
|   let ret.length = len(ret._data) - 1
 | ||
|   for s in ret._data
 | ||
|     let ret.length += len(s)
 | ||
|   endfor
 | ||
|   return ret
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert s:shada_sd_to_strings() output to a list of original entries.
 | ||
| function shada#strings_to_sd(strings) abort
 | ||
|   let strings = filter(copy(a:strings), 'v:val !~# ''\v^\s*%(\#|$)''')
 | ||
|   let stringss = []
 | ||
|   for string in strings
 | ||
|     if string[0] isnot# ' '
 | ||
|       call add(stringss, [])
 | ||
|     endif
 | ||
|     call add(stringss[-1], string)
 | ||
|   endfor
 | ||
|   return map(copy(stringss), 's:shada_convert_strings(v:val)')
 | ||
| endfunction
 | ||
| 
 | ||
| ""
 | ||
| " Convert a list of strings to list of strings suitable for writefile().
 | ||
| function shada#get_binstrings(strings) abort
 | ||
|   let entries = shada#strings_to_sd(a:strings)
 | ||
|   if !g:shada#keep_old_header
 | ||
|     call filter(entries, 'v:val.type != ' . s:SHADA_ENTRY_TYPES.header)
 | ||
|   endif
 | ||
|   if g:shada#add_own_header
 | ||
|     let data = {'version': v:version, 'generator': 'shada.vim'}
 | ||
|     let dumped_data = msgpackdump([data])
 | ||
|     let length = len(dumped_data) - 1
 | ||
|     for s in dumped_data
 | ||
|       let length += len(s)
 | ||
|     endfor
 | ||
|     call insert(entries, {
 | ||
|             \'type': s:SHADA_ENTRY_TYPES.header,
 | ||
|             \'timestamp': localtime(),
 | ||
|             \'length': length,
 | ||
|             \'data': data,
 | ||
|             \'_data': dumped_data,
 | ||
|           \})
 | ||
|   endif
 | ||
|   let mpack = []
 | ||
|   for entry in entries
 | ||
|     let mpack += map(copy(s:SHADA_ENTRY_OBJECT_SEQUENCE), 'entry[v:val]')
 | ||
|   endfor
 | ||
|   return msgpackdump(mpack)
 | ||
| endfunction
 | 
