diff options
-rw-r--r-- | runtime/autoload/shada.vim | 694 | ||||
-rw-r--r-- | test/functional/plugin/shada_spec.lua | 2082 |
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) |