aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/shada.vim694
-rw-r--r--test/functional/plugin/shada_spec.lua2082
2 files changed, 2776 insertions, 0 deletions
diff --git a/runtime/autoload/shada.vim b/runtime/autoload/shada.vim
new file mode 100644
index 0000000000..234f35398b
--- /dev/null
+++ b/runtime/autoload/shada.vim
@@ -0,0 +1,694 @@
+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', 'sm', 'sc', 'sl', 'se', 'so', 'su'],
+ \'register': ['n', 'rc', 'rw', 'rt'],
+ \'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],
+ \'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE],
+ \'rw': ['block width', 'uint', 0],
+ \'rc': ['contents', 'binarray', s:SHADA_REQUIRED],
+ \'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
+ if a:val > 0 || a:val < 1
+ 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]
+ elseif (a:type is# 'intchar' && type(a:v) == type(0)
+ \&& strtrans(nr2char(a:v)) is# nr2char(a:v))
+ return "'" . nr2char(a:v) . "'"
+ 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 VimL 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
diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua
new file mode 100644
index 0000000000..7595517d25
--- /dev/null
+++ b/test/functional/plugin/shada_spec.lua
@@ -0,0 +1,2082 @@
+local helpers = require('test.functional.helpers')
+local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs =
+ helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec,
+ helpers.funcs
+
+local msgpack = require('MessagePack')
+
+local plugin_helpers = require('test.functional.plugin.helpers')
+local reset = plugin_helpers.reset
+
+describe('In autoload/shada.vim', function()
+ local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0)
+ before_each(function()
+ reset()
+ nvim_command([[
+ function ModifyVal(val)
+ if type(a:val) == type([])
+ if len(a:val) == 2 && type(a:val[0]) == type('') && a:val[0][0] is# '!' && has_key(v:msgpack_types, a:val[0][1:])
+ return {'_TYPE': v:msgpack_types[ a:val[0][1:] ], '_VAL': a:val[1]}
+ else
+ return map(copy(a:val), 'ModifyVal(v:val)')
+ endif
+ elseif type(a:val) == type({})
+ let keys = sort(keys(a:val))
+ let ret = {'_TYPE': v:msgpack_types.map, '_VAL': []}
+ for key in keys
+ let k = {'_TYPE': v:msgpack_types.string, '_VAL': split(key, "\n", 1)}
+ let v = ModifyVal(a:val[key])
+ call add(ret._VAL, [k, v])
+ unlet v
+ endfor
+ return ret
+ elseif type(a:val) == type('')
+ return {'_TYPE': v:msgpack_types.binary, '_VAL': split(a:val, "\n", 1)}
+ else
+ return a:val
+ endif
+ endfunction
+ ]])
+ end)
+
+ local sp = function(typ, val)
+ return ('{"_TYPE": v:msgpack_types.%s, "_VAL": %s}'):format(typ, val)
+ end
+
+ local st_meta = {
+ __pairs=function(table)
+ local ret = {}
+ local next_key = nil
+ local num_keys = 0
+ while true do
+ next_key = next(table, next_key)
+ if next_key == nil then
+ break
+ end
+ num_keys = num_keys + 1
+ ret[num_keys] = {next_key, table[next_key]}
+ end
+ table.sort(ret, function(a, b)
+ return a[1] < b[1]
+ end)
+ local state = {i=0}
+ return (function(state, var)
+ state.i = state.i + 1
+ if ret[state.i] then
+ return table.unpack(ret[state.i])
+ end
+ end), state
+ end
+ }
+
+ local st = function(table)
+ return setmetatable(table, st_meta)
+ end
+
+ describe('function shada#mpack_to_sd', function()
+ local mpack2sd = function(arg)
+ return ('shada#mpack_to_sd(%s)'):format(arg)
+ end
+
+ it('works', function()
+ eq({}, nvim_eval(mpack2sd('[]')))
+ eq({{type=1, timestamp=5, length=1, data=7}},
+ nvim_eval(mpack2sd('[1, 5, 1, 7]')))
+ eq({{type=1, timestamp=5, length=1, data=7},
+ {type=1, timestamp=10, length=1, data=5}},
+ nvim_eval(mpack2sd('[1, 5, 1, 7, 1, 10, 1, 5]')))
+ eq('zero-uint:Entry 1 has type element which is zero',
+ exc_exec('call ' .. mpack2sd('[0, 5, 1, 7]')))
+ eq('zero-uint:Entry 1 has type element which is zero',
+ exc_exec('call ' .. mpack2sd(('[%s, 5, 1, 7]'):format(
+ sp('integer', '[1, 0, 0, 0]')))))
+ eq('not-uint:Entry 1 has timestamp element which is not an unsigned integer',
+ exc_exec('call ' .. mpack2sd('[1, -1, 1, 7]')))
+ eq('not-uint:Entry 1 has length element which is not an unsigned integer',
+ exc_exec('call ' .. mpack2sd('[1, 1, -1, 7]')))
+ eq('not-uint:Entry 1 has type element which is not an unsigned integer',
+ exc_exec('call ' .. mpack2sd('["", 1, -1, 7]')))
+ end)
+ end)
+
+ describe('function shada#sd_to_strings', function()
+ local sd2strings_eq = function(expected, arg)
+ if type(arg) == 'table' then
+ eq(expected, funcs['shada#sd_to_strings'](arg))
+ else
+ eq(expected, nvim_eval(('shada#sd_to_strings(%s)'):format(arg)))
+ end
+ end
+
+ it('works with empty input', function()
+ sd2strings_eq({}, '[]')
+ end)
+
+ it('works with unknown items', function()
+ sd2strings_eq({
+ 'Unknown (0x64) with timestamp ' .. epoch .. ':',
+ ' = 100'
+ }, {{type=100, timestamp=0, length=1, data=100}})
+
+ sd2strings_eq({
+ 'Unknown (0x4000001180000006) with timestamp ' .. epoch .. ':',
+ ' = 100'
+ }, ('[{"type": %s, "timestamp": 0, "length": 1, "data": 100}]'):format(
+ sp('integer', '[1, 1, 35, 6]')
+ ))
+ end)
+
+ it('works with multiple unknown items', function()
+ sd2strings_eq({
+ 'Unknown (0x64) with timestamp ' .. epoch .. ':',
+ ' = 100',
+ 'Unknown (0x65) with timestamp ' .. epoch .. ':',
+ ' = 500',
+ }, {{type=100, timestamp=0, length=1, data=100},
+ {type=101, timestamp=0, length=1, data=500}})
+ end)
+
+ it('works with header items', function()
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ }, {{type=1, timestamp=0, data={generator='test'}}})
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + a 1',
+ ' + b 2',
+ ' + c column 3',
+ ' + d 4',
+ }, {{type=1, timestamp=0, data=st({a=1, b=2, c=3, d=4})}})
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Value',
+ ' + t "test"',
+ }, {{type=1, timestamp=0, data={t='test'}}})
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=1, timestamp=0, data={1, 2, 3}}})
+ end)
+
+ it('processes standard keys correctly, even in header', function()
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + c column 0',
+ ' + f file name "/tmp/foo"',
+ ' + l line number 10',
+ ' + n name \'@\'',
+ ' + rc contents ["abc", "def"]',
+ ' + rt type CHARACTERWISE',
+ ' + rw block width 10',
+ ' + sc smartcase value FALSE',
+ ' + se place cursor at end TRUE',
+ ' + sh v:hlsearch value TRUE',
+ ' + sl has line offset FALSE',
+ ' + sm magic value TRUE',
+ ' + so offset value 10',
+ ' + sp pattern "100"',
+ ' + ss is :s pattern TRUE',
+ ' + su is last used FALSE',
+ }, ([[ [{'type': 1, 'timestamp': 0, 'data': {
+ 'sm': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sc': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'sl': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'se': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'so': 10,
+ 'su': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'ss': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sh': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sp': '100',
+ 'rt': 0,
+ 'rw': 10,
+ 'rc': ['abc', 'def'],
+ 'n': 0x40,
+ 'l': 10,
+ 'c': 0,
+ 'f': '/tmp/foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description____ Value',
+ ' # Expected integer',
+ ' + c column "abc"',
+ ' # Expected no NUL bytes',
+ ' + f file name "abc\\0def"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + n name -64',
+ ' # Expected array value',
+ ' + rc contents "10"',
+ ' # Unexpected enum value: expected one of '
+ .. '0 (CHARACTERWISE), 1 (LINEWISE), 2 (BLOCKWISE)',
+ ' + rt type 10',
+ ' # Expected boolean',
+ ' + sc smartcase value NIL',
+ ' # Expected boolean',
+ ' + sm magic value "TRUE"',
+ ' # Expected integer',
+ ' + so offset value "TRUE"',
+ ' # Expected binary string',
+ ' + sp pattern ="abc"',
+ }, ([[ [{'type': 1, 'timestamp': 0, 'data': {
+ 'sm': 'TRUE',
+ 'sc': {'_TYPE': v:msgpack_types.nil, '_VAL': 0},
+ 'so': 'TRUE',
+ 'sp': {'_TYPE': v:msgpack_types.string, '_VAL': ["abc"]},
+ 'rt': 10,
+ 'rc': '10',
+ 'n': -0x40,
+ 'l': -10,
+ 'c': 'abc',
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "abc\\0def"',
+ ' # Expected array of binary strings',
+ ' + rc contents ["abc", ="abc"]',
+ ' # Expected integer',
+ ' + rt type "ABC"',
+ }, ([[ [{'type': 1, 'timestamp': 0, 'data': {
+ 'rt': 'ABC',
+ 'rc': ["abc", {'_TYPE': v:msgpack_types.string, '_VAL': ["abc"]}],
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + rc contents ["abc", "a\\nd\\0"]',
+ }, ([[ [{'type': 1, 'timestamp': 0, 'data': {
+ 'rc': ["abc", {'_TYPE': v:msgpack_types.binary, '_VAL': ["a", "d\n"]}],
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with search pattern items', function()
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=2, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern "abc"',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ 'sp': 'abc',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern "abc"',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ ' + sX NIL',
+ ' + sY NIL',
+ ' + sZ NIL',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ 'sp': 'abc',
+ 'sZ': {'_TYPE': v:msgpack_types.nil, '_VAL': 0},
+ 'sY': {'_TYPE': v:msgpack_types.nil, '_VAL': 0},
+ 'sX': {'_TYPE': v:msgpack_types.nil, '_VAL': 0},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern "abc"',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ 'sp': 'abc',
+ 'sh': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'ss': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'sm': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sc': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'sl': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'se': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'so': 0,
+ 'su': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' # Required key missing: sp',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern ""',
+ ' + sh v:hlsearch value TRUE',
+ ' + ss is :s pattern TRUE',
+ ' + sm magic value FALSE',
+ ' + sc smartcase value TRUE',
+ ' + sl has line offset TRUE',
+ ' + se place cursor at end TRUE',
+ ' + so offset value -10',
+ ' + su is last used FALSE',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ 'sp': '',
+ 'sh': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'ss': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sm': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ 'sc': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'sl': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'se': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1},
+ 'so': -10,
+ 'su': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' # Expected binary string',
+ ' + sp pattern 0',
+ ' # Expected boolean',
+ ' + sh v:hlsearch value 0',
+ ' # Expected boolean',
+ ' + ss is :s pattern 0',
+ ' # Expected boolean',
+ ' + sm magic value 0',
+ ' # Expected boolean',
+ ' + sc smartcase value 0',
+ ' # Expected boolean',
+ ' + sl has line offset 0',
+ ' # Expected boolean',
+ ' + se place cursor at end 0',
+ ' # Expected integer',
+ ' + so offset value ""',
+ ' # Expected boolean',
+ ' + su is last used 0',
+ }, ([[ [{'type': 2, 'timestamp': 0, 'data': {
+ 'sp': 0,
+ 'sh': 0,
+ 'ss': 0,
+ 'sm': 0,
+ 'sc': 0,
+ 'sl': 0,
+ 'se': 0,
+ 'so': '',
+ 'su': 0,
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with replacement string items', function()
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ }, {{type=3, timestamp=0, data={a={10}}}})
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' # Expected more elements in list'
+ }, ([[ [{'type': 3, 'timestamp': 0, 'data': [
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' # Expected binary string',
+ ' - :s replacement string 0',
+ }, ([[ [{'type': 3, 'timestamp': 0, 'data': [
+ 0,
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' # Expected no NUL bytes',
+ ' - :s replacement string "abc\\0def"',
+ }, ([[ [{'type': 3, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ }, ([[ [{'type': 3, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc", "def"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ ' - 0',
+ }, ([[ [{'type': 3, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc", "def"]},
+ 0,
+ ]}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with history entry items', function()
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ }, {{type=4, timestamp=0, data={a={10}}}})
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Expected more elements in list'
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Expected integer',
+ ' - history type ""',
+ ' # Expected more elements in list'
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Unexpected enum value: expected one of 0 (CMD), 1 (SEARCH), '
+ .. '2 (EXPR), 3 (INPUT), 4 (DEBUG)',
+ ' - history type 5',
+ ' - contents ""',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 5,
+ ''
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Unexpected enum value: expected one of 0 (CMD), 1 (SEARCH), '
+ .. '2 (EXPR), 3 (INPUT), 4 (DEBUG)',
+ ' - history type 5',
+ ' - contents ""',
+ ' - 32',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 5,
+ '',
+ 0x20
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type CMD',
+ ' - contents ""',
+ ' - 32',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 0,
+ '',
+ 0x20
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents ""',
+ ' - separator \' \'',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 1,
+ '',
+ 0x20
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents ""',
+ ' # Expected more elements in list',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 1,
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type EXPR',
+ ' - contents ""',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 2,
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type INPUT',
+ ' - contents ""',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 3,
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type DEBUG',
+ ' - contents ""',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 4,
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type DEBUG',
+ ' # Expected binary string',
+ ' - contents 10',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 4,
+ 10,
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type DEBUG',
+ ' # Expected no NUL bytes',
+ ' - contents "abc\\0def"',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 4,
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents "abc"',
+ ' # Expected integer',
+ ' - separator ""',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 1,
+ 'abc',
+ '',
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents "abc"',
+ ' # Value is negative',
+ ' - separator -1',
+ }, ([[ [{'type': 4, 'timestamp': 0, 'data': [
+ 1,
+ 'abc',
+ -1,
+ ]}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with register items', function()
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=5, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: n',
+ ' # Required key missing: rc',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' # Required key missing: rc',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents ["abc", "def"]',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': ["abc", "def"],
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'],
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'],
+ 'rw': 0,
+ 'rt': 0,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 5',
+ ' + rt type LINEWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'],
+ 'rw': 5,
+ 'rt': 1,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' # Expected integer',
+ ' + rw block width ""',
+ ' + rt type BLOCKWISE',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'],
+ 'rw': "",
+ 'rt': 2,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' # Expected array value',
+ ' + rc contents 0',
+ ' # Value is negative',
+ ' + rw block width -1',
+ ' # Unexpected enum value: expected one of 0 (CHARACTERWISE), '
+ .. '1 (LINEWISE), 2 (BLOCKWISE)',
+ ' + rt type 10',
+ }, ([[ [{'type': 5, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'rc': 0,
+ 'rw': -1,
+ 'rt': 10,
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with variable items', function()
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ }, {{type=6, timestamp=0, data={a={10}}}})
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' # Expected more elements in list'
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' # Expected binary string',
+ ' - name 1',
+ ' # Expected more elements in list',
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ 1
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' # Expected no NUL bytes',
+ ' - name "\\0"',
+ ' # Expected more elements in list',
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' # Expected more elements in list',
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' - value NIL',
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]},
+ {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]},
+ ]}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' - value NIL',
+ ' - NIL',
+ }, ([[ [{'type': 6, 'timestamp': 0, 'data': [
+ {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]},
+ {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]},
+ {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]},
+ ]}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with global mark items', function()
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=7, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: n',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected integer',
+ ' + n name "foo"',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: n',
+ ' + f file name "foo"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Value is negative',
+ ' + n name -10',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': -10,
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name 20',
+ ' + f file name "foo"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': 20,
+ 'f': 'foo',
+ 'l': -10,
+ 'c': -10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name 20',
+ ' + f file name "foo"',
+ ' # Expected integer',
+ ' + l line number "FOO"',
+ ' # Expected integer',
+ ' + c column "foo"',
+ ' + mX 10',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': 20,
+ 'f': 'foo',
+ 'l': 'FOO',
+ 'c': 'foo',
+ 'mX': 10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + n name \'A\'',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ }, ([[ [{'type': 7, 'timestamp': 0, 'data': {
+ 'n': char2nr('A'),
+ 'f': 'foo',
+ 'l': 2,
+ 'c': 200,
+ 'mX': 10,
+ 'mYYYYYYYYYY': 10,
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with jump items', function()
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=8, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ ' # Expected integer',
+ ' + n name "foo"',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'n': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' + l line number 1',
+ ' + c column 0',
+ ' # Value is negative',
+ ' + n name -10',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'n': -10,
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ 'l': -10,
+ 'c': -10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' # Expected integer',
+ ' + l line number "FOO"',
+ ' # Expected integer',
+ ' + c column "foo"',
+ ' + mX 10',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ 'l': 'FOO',
+ 'c': 'foo',
+ 'mX': 10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ ' + n name \' \'',
+ }, ([[ [{'type': 8, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'f': 'foo',
+ 'l': 2,
+ 'c': 200,
+ 'mX': 10,
+ 'mYYYYYYYYYY': 10,
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with buffer list items', function()
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ }, {{type=9, timestamp=0, data={a={10}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' # Expected array of maps',
+ ' = [[], []]',
+ }, {{type=9, timestamp=0, data={{}, {}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' # Expected array of maps',
+ ' = [{="a": 10}, []]',
+ }, {{type=9, timestamp=0, data={{a=10}, {}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ ' + a 10',
+ }, {{type=9, timestamp=0, data={{a=10}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' # Expected integer',
+ ' + l line number "10"',
+ ' # Expected integer',
+ ' + c column "10"',
+ ' + a 10',
+ }, {{type=9, timestamp=0, data={{l='10', c='10', a=10}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 10',
+ ' + c column 10',
+ ' + a 10',
+ }, {{type=9, timestamp=0, data={{l=10, c=10, a=10}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ }, {{type=9, timestamp=0, data={{l=-10, c=-10}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "abc"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, {{type=9, timestamp=0, data={{f='abc'}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 10',
+ ' + l line number 1',
+ ' + c column 0',
+ '',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 20',
+ ' + l line number 1',
+ ' + c column 0',
+ }, {{type=9, timestamp=0, data={{f=10}, {f=20}}}})
+ sd2strings_eq({
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 10',
+ ' + l line number 1',
+ ' + c column 0',
+ '',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 9, 'timestamp': 0, 'data': [
+ {'f': 10},
+ {'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}},
+ ]}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with local mark items', function()
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=10, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + n name \'"\'',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' # Expected integer',
+ ' + n name "foo"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'n': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' + n name \'"\'',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' # Value is negative',
+ ' + n name -10',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'n': -10,
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' + n name 20',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'n': 20,
+ 'f': 'foo',
+ 'l': -10,
+ 'c': -10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' + n name 20',
+ ' # Expected integer',
+ ' + l line number "FOO"',
+ ' # Expected integer',
+ ' + c column "foo"',
+ ' + mX 10',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'n': 20,
+ 'f': 'foo',
+ 'l': 'FOO',
+ 'c': 'foo',
+ 'mX': 10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + f file name "foo"',
+ ' + n name \'a\'',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ }, ([[ [{'type': 10, 'timestamp': 0, 'data': {
+ 'n': char2nr('a'),
+ 'f': 'foo',
+ 'l': 2,
+ 'c': 200,
+ 'mX': 10,
+ 'mYYYYYYYYYY': 10,
+ }}] ]]):gsub('\n', ''))
+ end)
+
+ it('works with change items', function()
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ }, {{type=11, timestamp=0, data={1, 2, 3}}})
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ ' # Expected integer',
+ ' + n name "foo"',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'n': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' + l line number 1',
+ ' + c column 0',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' + l line number 1',
+ ' + c column 0',
+ ' # Value is negative',
+ ' + n name -10',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'n': -10,
+ 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]},
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ 'l': -10,
+ 'c': -10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "foo"',
+ ' # Expected integer',
+ ' + l line number "FOO"',
+ ' # Expected integer',
+ ' + c column "foo"',
+ ' + mX 10',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'f': 'foo',
+ 'l': 'FOO',
+ 'c': 'foo',
+ 'mX': 10,
+ }}] ]]):gsub('\n', ''))
+ sd2strings_eq({
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ ' + n name \' \'',
+ }, ([[ [{'type': 11, 'timestamp': 0, 'data': {
+ 'n': 0x20,
+ 'f': 'foo',
+ 'l': 2,
+ 'c': 200,
+ 'mX': 10,
+ 'mYYYYYYYYYY': 10,
+ }}] ]]):gsub('\n', ''))
+ end)
+ end)
+
+ describe('function shada#get_strings', function()
+ it('works', function()
+ eq({
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Value',
+ }, nvim_eval('shada#get_strings(msgpackdump([1, 0, 0, {}]))'))
+ end)
+ end)
+
+ describe('function shada#strings_to_sd', function()
+
+ local strings2sd_eq = function(expected, input)
+ nvim('set_var', '__input', input)
+ nvim_command('let g:__actual = map(shada#strings_to_sd(g:__input), '
+ .. '"filter(v:val, \\"v:key[0] isnot# \'_\' '
+ .. '&& v:key isnot# \'length\'\\")")')
+ -- print()
+ if type(expected) == 'table' then
+ nvim('set_var', '__expected', expected)
+ nvim_command('let g:__expected = ModifyVal(g:__expected)')
+ expected = 'g:__expected'
+ -- print(nvim_eval('msgpack#string(g:__expected)'))
+ end
+ -- print(nvim_eval('msgpack#string(g:__actual)'))
+ eq(1, nvim_eval(('msgpack#equal(%s, g:__actual)'):format(expected)))
+ if type(expected) == 'table' then
+ nvim_command('unlet g:__expected')
+ end
+ nvim_command('unlet g:__input')
+ nvim_command('unlet g:__actual')
+ end
+
+ assert:set_parameter('TableFormatLevel', 100)
+
+ it('works with multiple items', function()
+ strings2sd_eq({{
+ type=11, timestamp=0, data={
+ f='foo',
+ l=2,
+ c=200,
+ mX=10,
+ mYYYYYYYYYY=10,
+ n=(' '):byte(),
+ }
+ }, {
+ type=1, timestamp=0, data={
+ c='abc',
+ f={'!binary', {'abc\ndef'}},
+ l=-10,
+ n=-64,
+ rc='10',
+ rt=10,
+ sc={'!nil', 0},
+ sm='TRUE',
+ so='TRUE',
+ sp={'!string', {'abc'}},
+ }
+ }}, {
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ ' + n name \' \'',
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description____ Value',
+ ' # Expected integer',
+ ' + c column "abc"',
+ ' # Expected no NUL bytes',
+ ' + f file name "abc\\0def"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + n name -64',
+ ' # Expected array value',
+ ' + rc contents "10"',
+ ' # Unexpected enum value: expected one of '
+ .. '0 (CHARACTERWISE), 1 (LINEWISE), 2 (BLOCKWISE)',
+ ' + rt type 10',
+ ' # Expected boolean',
+ ' + sc smartcase value NIL',
+ ' # Expected boolean',
+ ' + sm magic value "TRUE"',
+ ' # Expected integer',
+ ' + so offset value "TRUE"',
+ ' # Expected binary string',
+ ' + sp pattern ="abc"',
+ })
+ end)
+
+ it('works with empty list', function()
+ strings2sd_eq({}, {})
+ end)
+
+ it('works with header items', function()
+ strings2sd_eq({{type=1, timestamp=0, data={
+ generator='test',
+ }}}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ })
+ strings2sd_eq({{type=1, timestamp=0, data={
+ 1, 2, 3,
+ }}}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=1, timestamp=0, data={
+ a=1, b=2, c=3, d=4,
+ }}}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + a 1',
+ ' + b 2',
+ ' + c column 3',
+ ' + d 4',
+ })
+ strings2sd_eq({{type=1, timestamp=0, data={
+ c='abc',
+ f={'!binary', {'abc\ndef'}},
+ l=-10,
+ n=-64,
+ rc='10',
+ rt=10,
+ sc={'!nil', 0},
+ sm='TRUE',
+ so='TRUE',
+ sp={'!string', {'abc'}},
+ }}}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key Description____ Value',
+ ' # Expected integer',
+ ' + c column "abc"',
+ ' # Expected no NUL bytes',
+ ' + f file name "abc\\0def"',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + n name -64',
+ ' # Expected array value',
+ ' + rc contents "10"',
+ ' # Unexpected enum value: expected one of '
+ .. '0 (CHARACTERWISE), 1 (LINEWISE), 2 (BLOCKWISE)',
+ ' + rt type 10',
+ ' # Expected boolean',
+ ' + sc smartcase value NIL',
+ ' # Expected boolean',
+ ' + sm magic value "TRUE"',
+ ' # Expected integer',
+ ' + so offset value "TRUE"',
+ ' # Expected binary string',
+ ' + sp pattern ="abc"',
+ })
+ end)
+
+ it('works with search pattern items', function()
+ strings2sd_eq({{type=2, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=2, timestamp=0, data={
+ sp='abc',
+ }}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern "abc"',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ })
+ strings2sd_eq({{type=2, timestamp=0, data={
+ sp='abc',
+ sX={'!nil', 0},
+ sY={'!nil', 0},
+ sZ={'!nil', 0},
+ }}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern "abc"',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ ' + sX NIL',
+ ' + sY NIL',
+ ' + sZ NIL',
+ })
+ strings2sd_eq({{type=2, timestamp=0, data={'!map', {
+ }}}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' # Required key missing: sp',
+ ' + sh v:hlsearch value FALSE',
+ ' + ss is :s pattern FALSE',
+ ' + sm magic value TRUE',
+ ' + sc smartcase value FALSE',
+ ' + sl has line offset FALSE',
+ ' + se place cursor at end FALSE',
+ ' + so offset value 0',
+ ' + su is last used TRUE',
+ })
+ strings2sd_eq({{type=2, timestamp=0, data={
+ sp='',
+ sh={'!boolean', 1},
+ ss={'!boolean', 1},
+ sc={'!boolean', 1},
+ sl={'!boolean', 1},
+ se={'!boolean', 1},
+ sm={'!boolean', 0},
+ su={'!boolean', 0},
+ so=-10,
+ }}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' + sp pattern ""',
+ ' + sh v:hlsearch value TRUE',
+ ' + ss is :s pattern TRUE',
+ ' + sm magic value FALSE',
+ ' + sc smartcase value TRUE',
+ ' + sl has line offset TRUE',
+ ' + se place cursor at end TRUE',
+ ' + so offset value -10',
+ ' + su is last used FALSE',
+ })
+ strings2sd_eq({{type=2, timestamp=0, data={
+ sp=0,
+ sh=0,
+ ss=0,
+ sc=0,
+ sl=0,
+ se=0,
+ sm=0,
+ su=0,
+ so='',
+ }}}, {
+ 'Search pattern with timestamp ' .. epoch .. ':',
+ ' % Key Description________ Value',
+ ' # Expected binary string',
+ ' + sp pattern 0',
+ ' # Expected boolean',
+ ' + sh v:hlsearch value 0',
+ ' # Expected boolean',
+ ' + ss is :s pattern 0',
+ ' # Expected boolean',
+ ' + sm magic value 0',
+ ' # Expected boolean',
+ ' + sc smartcase value 0',
+ ' # Expected boolean',
+ ' + sl has line offset 0',
+ ' # Expected boolean',
+ ' + se place cursor at end 0',
+ ' # Expected integer',
+ ' + so offset value ""',
+ ' # Expected boolean',
+ ' + su is last used 0',
+ })
+ end)
+
+ it('works with replacement string items', function()
+ strings2sd_eq({{type=3, timestamp=0, data={
+ a={10}
+ }}}, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ })
+ strings2sd_eq({{type=3, timestamp=0, data={
+ }}}, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' # Expected more elements in list'
+ })
+ strings2sd_eq({{type=3, timestamp=0, data={
+ 0
+ }}}, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' # Expected binary string',
+ ' - :s replacement string 0',
+ })
+ strings2sd_eq({{type=3, timestamp=0, data={
+ 'abc\ndef', 0,
+ }}}, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ ' - 0',
+ })
+ strings2sd_eq({{type=3, timestamp=0, data={
+ 'abc\ndef',
+ }}}, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ })
+ end)
+
+ it('works with history entry items', function()
+ strings2sd_eq({{type=4, timestamp=0, data={
+ a={10},
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Expected more elements in list'
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ '',
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Expected integer',
+ ' - history type ""',
+ ' # Expected more elements in list'
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 5, '',
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Unexpected enum value: expected one of 0 (CMD), 1 (SEARCH), '
+ .. '2 (EXPR), 3 (INPUT), 4 (DEBUG)',
+ ' - history type 5',
+ ' - contents ""',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 5, '', 32,
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' # Unexpected enum value: expected one of 0 (CMD), 1 (SEARCH), '
+ .. '2 (EXPR), 3 (INPUT), 4 (DEBUG)',
+ ' - history type 5',
+ ' - contents ""',
+ ' - 32',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 0, '', 32,
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type CMD',
+ ' - contents ""',
+ ' - 32',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 1, '', 32,
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents ""',
+ ' - separator \' \'',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 1, '',
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type SEARCH',
+ ' - contents ""',
+ ' # Expected more elements in list',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 2, '',
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type EXPR',
+ ' - contents ""',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 3, ''
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type INPUT',
+ ' - contents ""',
+ })
+ strings2sd_eq({{type=4, timestamp=0, data={
+ 4, ''
+ }}}, {
+ 'History entry with timestamp ' .. epoch .. ':',
+ ' @ Description_ Value',
+ ' - history type DEBUG',
+ ' - contents ""',
+ })
+ end)
+
+ it('works with register items', function()
+ strings2sd_eq({{type=5, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={'!map', {
+ }}}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: n',
+ ' # Required key missing: rc',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte()
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' # Required key missing: rc',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte(), rc={'abc', 'def'}
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents ["abc", "def"]',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte(),
+ rc={'abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'},
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 0',
+ ' + rt type CHARACTERWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte(),
+ rc={'abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'},
+ rw=5,
+ rt=1,
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 5',
+ ' + rt type LINEWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte(),
+ rc={'abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'},
+ rw=5,
+ rt=2,
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' + rc contents @',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' | - "abcdefghijklmnopqrstuvwxyz"',
+ ' + rw block width 5',
+ ' + rt type BLOCKWISE',
+ })
+ strings2sd_eq({{type=5, timestamp=0, data={
+ n=(' '):byte(),
+ rc=0,
+ rw=-1,
+ rt=10,
+ }}}, {
+ 'Register with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + n name \' \'',
+ ' # Expected array value',
+ ' + rc contents 0',
+ ' # Value is negative',
+ ' + rw block width -1',
+ ' # Unexpected enum value: expected one of 0 (CHARACTERWISE), '
+ .. '1 (LINEWISE), 2 (BLOCKWISE)',
+ ' + rt type 10',
+ })
+ end)
+
+ it('works with variable items', function()
+ strings2sd_eq({{type=6, timestamp=0, data={
+ a={10}
+ }}}, {
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ })
+ strings2sd_eq({{type=6, timestamp=0, data={
+ }}}, {
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' # Expected more elements in list'
+ })
+ strings2sd_eq({{type=6, timestamp=0, data={
+ 'foo',
+ }}}, {
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' # Expected more elements in list',
+ })
+ strings2sd_eq({{type=6, timestamp=0, data={
+ 'foo', {'!nil', 0},
+ }}}, {
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' - value NIL',
+ })
+ strings2sd_eq({{type=6, timestamp=0, data={
+ 'foo', {'!nil', 0}, {'!nil', 0}
+ }}}, {
+ 'Variable with timestamp ' .. epoch .. ':',
+ ' @ Description Value',
+ ' - name "foo"',
+ ' - value NIL',
+ ' - NIL',
+ })
+ end)
+
+ it('works with global mark items', function()
+ strings2sd_eq({{type=7, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=7, timestamp=0, data={
+ n=('A'):byte(), f='foo', l=2, c=200, mX=10, mYYYYYYYYYY=10,
+ }}}, {
+ 'Global mark with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + n name \'A\'',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ })
+ end)
+
+ it('works with jump items', function()
+ strings2sd_eq({{type=8, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=8, timestamp=0, data={
+ n=('A'):byte(), f='foo', l=2, c=200, mX=10, mYYYYYYYYYY=10,
+ }}}, {
+ 'Jump with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + n name \'A\'',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ })
+ end)
+
+ it('works with buffer list items', function()
+ strings2sd_eq({{type=9, timestamp=0, data={
+ a={10}
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: map instead of array',
+ ' = {="a": [10]}',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {a=10}, {}
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' # Expected array of maps',
+ ' = [{="a": 10}, []]',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {a=10},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 1',
+ ' + c column 0',
+ ' + a 10',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {l='10', c='10', a=10},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' # Expected integer',
+ ' + l line number "10"',
+ ' # Expected integer',
+ ' + c column "10"',
+ ' + a 10',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {l=10, c=10, a=10},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' + l line number 10',
+ ' + c column 10',
+ ' + a 10',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {l=-10, c=-10},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Required key missing: f',
+ ' # Value is negative',
+ ' + l line number -10',
+ ' # Value is negative',
+ ' + c column -10',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {f='abc'},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' + f file name "abc"',
+ ' + l line number 1',
+ ' + c column 0',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {f=10}, {f=20},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 10',
+ ' + l line number 1',
+ ' + c column 0',
+ '',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 20',
+ ' + l line number 1',
+ ' + c column 0',
+ })
+ strings2sd_eq({{type=9, timestamp=0, data={
+ {f=10}, {f={'!binary', {'\n'}}},
+ }}}, {
+ 'Buffer list with timestamp ' .. epoch .. ':',
+ ' % Key Description Value',
+ ' # Expected binary string',
+ ' + f file name 10',
+ ' + l line number 1',
+ ' + c column 0',
+ '',
+ ' % Key Description Value',
+ ' # Expected no NUL bytes',
+ ' + f file name "\\0"',
+ ' + l line number 1',
+ ' + c column 0',
+ })
+ end)
+
+ it('works with local mark items', function()
+ strings2sd_eq({{type=10, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=10, timestamp=0, data={
+ n=('A'):byte(), f='foo', l=2, c=200, mX=10, mYYYYYYYYYY=10,
+ }}}, {
+ 'Local mark with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + n name \'A\'',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ })
+ end)
+
+ it('works with change items', function()
+ strings2sd_eq({{type=11, timestamp=0, data={
+ 1, 2, 3
+ }}}, {
+ 'Change with timestamp ' .. epoch .. ':',
+ ' # Unexpected type: array instead of map',
+ ' = [1, 2, 3]',
+ })
+ strings2sd_eq({{type=11, timestamp=0, data={
+ n=('A'):byte(), f='foo', l=2, c=200, mX=10, mYYYYYYYYYY=10,
+ }}}, {
+ 'Change with timestamp ' .. epoch .. ':',
+ ' % Key________ Description Value',
+ ' + n name \'A\'',
+ ' + f file name "foo"',
+ ' + l line number 2',
+ ' + c column 200',
+ ' + mX 10',
+ ' + mYYYYYYYYYY 10',
+ })
+ end)
+ end)
+
+ describe('function shada#get_binstrings', function()
+ local getbstrings_eq = function(expected, input)
+ local result = funcs['shada#get_binstrings'](input)
+ for i, s in ipairs(result) do
+ result[i] = s:gsub('\n', '\0')
+ end
+ local mpack_result = table.concat(result, '\n')
+
+ local mpack_keys = {'type', 'timestamp', 'length', 'value'}
+
+ local unpacker = msgpack.unpacker(mpack_result)
+ local actual = {}
+ local cur
+ local i = 0
+ while true do
+ local off, val = unpacker()
+ if not off then break end
+ if i % 4 == 0 then
+ cur = {}
+ actual[#actual + 1] = cur
+ end
+ local key = mpack_keys[(i % 4) + 1]
+ if key ~= 'length' then
+ if key == 'timestamp' and math.abs(val - os.time()) < 2 then
+ val = 'current'
+ end
+ cur[key] = val
+ end
+ i = i + 1
+ end
+ eq(expected, actual)
+ end
+
+ it('works', function()
+ getbstrings_eq({{timestamp='current', type=1, value={
+ generator='shada.vim',
+ version=704,
+ }}}, {})
+ getbstrings_eq({
+ {timestamp='current', type=1, value={
+ generator='shada.vim', version=704
+ }},
+ {timestamp=0, type=1, value={generator='test'}}
+ }, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ })
+ nvim('set_var', 'shada#add_own_header', 1)
+ getbstrings_eq({{timestamp='current', type=1, value={
+ generator='shada.vim',
+ version=704,
+ }}}, {})
+ getbstrings_eq({
+ {timestamp='current', type=1, value={
+ generator='shada.vim', version=704
+ }},
+ {timestamp=0, type=1, value={generator='test'}}
+ }, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ })
+ nvim('set_var', 'shada#add_own_header', 0)
+ getbstrings_eq({}, {})
+ getbstrings_eq({{timestamp=0, type=1, value={generator='test'}}}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ })
+ nvim('set_var', 'shada#keep_old_header', 0)
+ getbstrings_eq({}, {
+ 'Header with timestamp ' .. epoch .. ':',
+ ' % Key______ Value',
+ ' + generator "test"',
+ })
+ getbstrings_eq({
+ {type=3, timestamp=0, value={'abc\ndef'}},
+ {type=3, timestamp=0, value={'abc\ndef'}},
+ {type=3, timestamp=0, value={'abc\ndef'}},
+ }, {
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ 'Replacement string with timestamp ' .. epoch .. ':',
+ ' @ Description__________ Value',
+ ' - :s replacement string "abc\\ndef"',
+ })
+ end)
+ end)
+end)