diff options
author | Felipe Morales <hel.sheep@gmail.com> | 2015-11-23 00:27:18 +0100 |
---|---|---|
committer | Felipe Morales <hel.sheep@gmail.com> | 2015-11-23 00:27:18 +0100 |
commit | 321db59ca1dc304feb3e00c10ca3e89c1de616e7 (patch) | |
tree | 825ba69b12a717faf011dc1b828438b5ea70c31d | |
parent | 1fbb56795d16783f9a97e25e3b71ab3ac2a644dc (diff) | |
parent | e773ffe8094041dba1b9b258dfa45104dc321522 (diff) | |
download | rneovim-321db59ca1dc304feb3e00c10ca3e89c1de616e7.tar.gz rneovim-321db59ca1dc304feb3e00c10ca3e89c1de616e7.tar.bz2 rneovim-321db59ca1dc304feb3e00c10ca3e89c1de616e7.zip |
Merge pull request #3270 from ZyX-I/shada-support
Add plugin for editing ShaDa files
-rw-r--r-- | runtime/autoload/msgpack.vim | 820 | ||||
-rw-r--r-- | runtime/autoload/shada.vim | 694 | ||||
-rw-r--r-- | runtime/doc/filetype.txt | 143 | ||||
-rw-r--r-- | runtime/doc/pi_msgpack.txt | 139 | ||||
-rw-r--r-- | runtime/ftplugin/shada.vim | 18 | ||||
-rw-r--r-- | runtime/plugin/shada.vim | 39 | ||||
-rw-r--r-- | runtime/syntax/shada.vim | 125 | ||||
-rw-r--r-- | test/functional/plugin/helpers.lua | 41 | ||||
-rw-r--r-- | test/functional/plugin/msgpack_spec.lua | 699 | ||||
-rw-r--r-- | test/functional/plugin/shada_spec.lua | 2833 |
10 files changed, 5551 insertions, 0 deletions
diff --git a/runtime/autoload/msgpack.vim b/runtime/autoload/msgpack.vim new file mode 100644 index 0000000000..e6022922fe --- /dev/null +++ b/runtime/autoload/msgpack.vim @@ -0,0 +1,820 @@ +if exists('g:loaded_msgpack_autoload') + finish +endif +let g:loaded_msgpack_autoload = 1 + +"" +" Check that given value is an integer. Respects |msgpack-special-dict|. +function msgpack#is_int(v) abort + return type(a:v) == type(0) || ( + \type(a:v) == type({}) && get(a:v, '_TYPE') is# v:msgpack_types.integer) +endfunction + +"" +" Check that given value is an unsigned integer. Respects +" |msgpack-special-dict|. +function msgpack#is_uint(v) abort + return msgpack#is_int(a:v) && (type(a:v) == type(0) + \? a:v >= 0 + \: a:v._VAL[0] > 0) +endfunction + +"" +" True if s:msgpack_init_python() function was already run. +let s:msgpack_python_initialized = 0 + +"" +" Cached return of s:msgpack_init_python() used when +" s:msgpack_python_initialized is true. +let s:msgpack_python_type = 0 + +"" +" Create Python functions that are necessary for work. Also defines functions +" s:msgpack_dict_strftime(format, timestamp) and s:msgpack_dict_strptime(format, +" string). +" +" @return Zero in case no Python is available, empty string if Python-2 is +" available and string `"3"` if Python-3 is available. +function s:msgpack_init_python() abort + if s:msgpack_python_initialized + return s:msgpack_python_type + endif + let s:msgpack_python_initialized = 1 + for suf in ['', '3'] + try + execute 'python' . suf + \. "def shada_dict_strftime():\n" + \. " import datetime\n" + \. " import vim\n" + \. " fmt = vim.eval('a:format')\n" + \. " timestamp = vim.eval('a:timestamp')\n" + \. " timestamp = [int(v) for v in timestamp['_VAL']]\n" + \. " timestamp = timestamp[0] * (timestamp[1] << 62\n" + \. " | timestamp[2] << 31\n" + \. " | timestamp[3])\n" + \. " time = datetime.datetime.fromtimestamp(timestamp)\n" + \. " return time.strftime(fmt)\n" + \. "def shada_dict_strptime():\n" + \. " import datetime\n" + \. " import vim\n" + \. " fmt = vim.eval('a:format')\n" + \. " timestr = vim.eval('a:string')\n" + \. " timestamp = datetime.datetime.strptime(timestr, fmt)\n" + \. " timestamp = int(timestamp.timestamp())\n" + \. " if timestamp > 2 ** 31:\n" + \. " tsabs = abs(timestamp)" + \. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n" + \. " + '\"_VAL\": [{sign},{v1},{v2},{v3}]}').format(\n" + \. " sign=1 if timestamp >= 0 else -1,\n" + \. " v1=((tsabs >> 62) & 0x3),\n" + \. " v2=((tsabs >> 31) & (2 ** 31 - 1)),\n" + \. " v3=(tsabs & (2 ** 31 - 1)))\n" + \. " else:\n" + \. " return str(timestamp)\n" + execute "function s:msgpack_dict_strftime(format, timestamp) abort\n" + \. " return py" . suf . "eval('shada_dict_strftime()')\n" + \. "endfunction\n" + \. "function s:msgpack_dict_strptime(format, string)\n" + \. " return eval(py" . suf . "eval('shada_dict_strptime()'))\n" + \. "endfunction\n" + let s:msgpack_python_type = suf + return suf + catch + continue + endtry + endfor + + "" + " strftime() function for |msgpack-special-dict| values. + " + " @param[in] format String according to which time should be formatted. + " @param[in] timestamp Timestamp (seconds since epoch) to format. + " + " @return Formatted timestamp. + " + " @warning Without +python or +python3 this function does not work correctly. + " The VimL code contains “reference” implementation which does not + " really work because of precision loss. + function s:msgpack_dict_strftime(format, timestamp) + return msgpack#strftime(a:format, +msgpack#int_dict_to_str(a:timestamp)) + endfunction + + "" + " Function that parses given string according to given format. + " + " @param[in] format String according to which string was formatted. + " @param[in] string Time formatted according to format. + " + " @return Timestamp. + " + " @warning Without +python or +python3 this function is able to work only with + " 31-bit (32-bit signed) timestamps that have format + " `%Y-%m-%dT%H:%M:%S`. + function s:msgpack_dict_strptime(format, string) + let fmt = '%Y-%m-%dT%H:%M:%S' + if a:format isnot# fmt + throw 'notimplemented-format:Only ' . fmt . ' format is supported' + endif + let match = matchlist(a:string, + \'\v\C^(\d+)\-(\d+)\-(\d+)T(\d+)\:(\d+)\:(\d+)$') + if empty(match) + throw 'invalid-string:Given string does not match format ' . a:format + endif + call map(match, 'str2nr(v:val, 10)') + let [year, month, day, hour, minute, second] = match[1:6] + " Bisection start and end: + " + " Start: 365 days in year, 28 days in month, -12 hours tz offset. + let bisect_ts_start = (((((year - 1970) * 365 + \+ (month - 1) * 28 + \+ (day - 1)) * 24 + \+ hour - 12) * 60 + \+ minute) * 60 + \+ second) + if bisect_ts_start < 0 + let bisect_ts_start = 0 + endif + let start_string = strftime(fmt, bisect_ts_start) + if start_string is# a:string + return bisect_ts_start + endif + " End: 366 days in year, 31 day in month, +14 hours tz offset. + let bisect_ts_end = (((((year - 1970) * 366 + \+ (month - 1) * 31 + \+ (day - 1)) * 24 + \+ hour + 14) * 60 + \+ minute) * 60 + \+ second) + let end_string = strftime(fmt, bisect_ts_end) + if end_string is# a:string + return bisect_ts_end + endif + if start_string ># end_string + throw 'internal-start-gt:Internal error: start > end' + endif + if start_string is# end_string + throw printf('internal-start-eq:Internal error: ' + \. 'start(%u)==end(%u), but start(%s)!=string(%s)', + \bisect_ts_start, bisect_ts_end, + \string(start_string), string(a:string)) + endif + if start_string ># a:string + throw 'internal-start-string:Internal error: start > string' + endif + if end_string <# a:string + throw 'internal-end-string:Internal error: end < string' + endif + while 1 + let bisect_ts_middle = (bisect_ts_start/2) + (bisect_ts_end/2) + let middle_string = strftime(fmt, bisect_ts_middle) + if a:string is# middle_string + return bisect_ts_middle + elseif a:string ># middle_string + if bisect_ts_middle == bisect_ts_start + let bisect_ts_start += 1 + else + let bisect_ts_start = bisect_ts_middle + endif + else + if bisect_ts_middle == bisect_ts_end + let bisect_ts_end -= 1 + else + let bisect_ts_end = bisect_ts_middle + endif + endif + if bisect_ts_start >= bisect_ts_end + throw 'not-found:Unable to find timestamp' + endif + endwhile + endfunction + + return 0 +endfunction + +"" +" Wrapper for strftime() that respects |msgpack-special-dict|. May actually use +" non-standard strftime() implementations for |msgpack-special-dict| values. +" +" @param[in] format Format string. +" @param[in] timestamp Formatted timestamp. +function msgpack#strftime(format, timestamp) abort + if type(a:timestamp) == type({}) + call s:msgpack_init_python() + return s:msgpack_dict_strftime(a:format, a:timestamp) + else + return strftime(a:format, a:timestamp) + endif +endfunction + +"" +" Parse string according to the format. +" +" Requires +python available. If it is not then only supported format is +" `%Y-%m-%dT%H:%M:%S` because this is the format used by ShaDa plugin. Also in +" this case bisection will be used (timestamps tried with strftime() up until +" result matches the string) and only 31-bit (signed 32-bit: with negative +" timestamps being useless this leaves 31 bits) timestamps will be supported. +" +" @param[in] format Time format. +" @param[in] string Parsed time string. Must match given format. +" +" @return Timestamp. Possibly as |msgpack-special-dict|. +function msgpack#strptime(format, string) abort + call s:msgpack_init_python() + return s:msgpack_dict_strptime(a:format, a:string) +endfunction + +let s:MSGPACK_HIGHEST_BIT = 1 +let s:MSGPACK_HIGHEST_BIT_NR = 0 +while s:MSGPACK_HIGHEST_BIT * 2 > 0 + let s:MSGPACK_HIGHEST_BIT = s:MSGPACK_HIGHEST_BIT * 2 + let s:MSGPACK_HIGHEST_BIT_NR += 1 +endwhile + +"" +" Shift given number by given amount of bits +function s:shift(n, s) abort + if a:s == 0 + return a:n + elseif a:s < 0 + let ret = a:n + for _ in range(-a:s) + let ret = ret / 2 + endfor + return ret + else + let ret = a:n + for i in range(a:s) + let new_ret = ret * 2 + if new_ret < ret + " Overflow: remove highest bit + let ret = xor(s:MSGPACK_HIGHEST_BIT, ret) * 2 + endif + let ret = new_ret + endfor + return ret + endif +endfunction + +let s:msgpack_mask_cache = { + \s:MSGPACK_HIGHEST_BIT_NR : s:MSGPACK_HIGHEST_BIT - 1} + +"" +" Apply a mask where first m bits are ones and other are zeroes to a given +" number +function s:mask1(n, m) abort + if a:m > s:MSGPACK_HIGHEST_BIT_NR + 1 + let m = s:MSGPACK_HIGHEST_BIT_NR + 1 + else + let m = a:m + endif + if !has_key(s:msgpack_mask_cache, m) + let p = 0 + for _ in range(m) + let p = p * 2 + 1 + endfor + let s:msgpack_mask_cache[m] = p + endif + return and(a:n, s:msgpack_mask_cache[m]) +endfunction + +"" +" Convert |msgpack-special-dict| that represents integer value to a string. Uses +" hexadecimal representation starting with 0x because it is the easiest to +" convert to. +function msgpack#int_dict_to_str(v) abort + let v = a:v._VAL + " 64-bit number: + " 0000000001111111111222222222233333333334444444444555555555566666 + " 1234567890123456789012345678901234567890123456789012345678901234 + " Split in _VAL: + " 0000000001111111111222222222233 3333333344444444445555555555666 66 + " 1234567890123456789012345678901 2345678901234567890123456789012 34 + " Split by hex digits: + " 0000 0000 0111 1111 1112 2222 2222 2333 3333 3334 4444 4444 4555 5555 5556 6666 + " 1234 5678 9012 3456 7890 1234 5678 9012 3456 7890 1234 5678 9012 3456 7890 1234 + " + " Total split: + " _VAL[3] _VAL[2] _VAL[1] + " ______________________________________ _______________________________________ __ + " 0000 0000 0111 1111 1112 2222 2222 233 3 3333 3334 4444 4444 4555 5555 5556 66 66 + " 1234 5678 9012 3456 7890 1234 5678 901 2 3456 7890 1234 5678 9012 3456 7890 12 34 + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ + " g4 g3 g2 g1 + " ********************************** *** * ********************************** ** ** + " 1 2 3 4 5 6 + " 1: s:mask1(v[3], 28): first 28 bits of _VAL[3] + " 2: s:shift(v[3], -28): last 3 bits of _VAL[3] + " 3: s:mask1(v[2], 1): first bit of _VAL[2] + " 4: s:mask1(s:shift(v[2], -1), 28): bits 2 .. 29 of _VAL[2] + " 5: s:shift(v[2], -29): last 2 bits of _VAL[2] + " 6: s:shift(v[1], 2): _VAL[1] + let g4 = printf('%07x', s:mask1(v[3], 28)) + let g3 = printf('%01x', or(s:shift(v[3], -28), s:shift(s:mask1(v[2], 1), 3))) + let g2 = printf('%07x', s:mask1(s:shift(v[2], -1), 28)) + let g1 = printf('%01x', or(s:shift(v[2], -29), s:shift(v[1], 2))) + return ((v[0] < 0 ? '-' : '') . '0x' . g1 . g2 . g3 . g4) +endfunction + +"" +" True boolean value. +let g:msgpack#true = {'_TYPE': v:msgpack_types.boolean, '_VAL': 1} +lockvar! g:msgpack#true + +"" +" False boolean value. +let g:msgpack#false = {'_TYPE': v:msgpack_types.boolean, '_VAL': 0} +lockvar! g:msgpack#false + +"" +" NIL value. +let g:msgpack#nil = {'_TYPE': v:msgpack_types.nil, '_VAL': 0} +lockvar! g:msgpack#nil + +"" +" Deduce type of |msgpack-special-dict|. +" +" @return zero if given dictionary is not special or name of the key in +" v:msgpack_types dictionary. +function msgpack#special_type(v) abort + if type(a:v) != type({}) || !has_key(a:v, '_TYPE') + return 0 + endif + for [k, v] in items(v:msgpack_types) + if a:v._TYPE is v + return k + endif + endfor + return 0 +endfunction + +"" +" Mapping that maps type() output to type names. +let s:MSGPACK_STANDARD_TYPES = { + \type(0): 'integer', + \type(0.0): 'float', + \type(''): 'binary', + \type([]): 'array', + \type({}): 'map', +\} + +"" +" Deduce type of one of items returned by msgpackparse(). +" +" @return Name of a key in v:msgpack_types. +function msgpack#type(v) abort + let special_type = msgpack#special_type(a:v) + if special_type is 0 + return s:MSGPACK_STANDARD_TYPES[type(a:v)] + endif + return special_type +endfunction + +"" +" Dump nil value. +function s:msgpack_dump_nil(v) abort + return 'NIL' +endfunction + +"" +" Dump boolean value. +function s:msgpack_dump_boolean(v) abort + return a:v._VAL ? 'TRUE' : 'FALSE' +endfunction + +"" +" Dump integer msgpack value. +function s:msgpack_dump_integer(v) abort + if type(a:v) == type({}) + return msgpack#int_dict_to_str(a:v) + else + return string(a:v) + endif +endfunction + +"" +" Dump floating-point value. +function s:msgpack_dump_float(v) abort + return string(type(a:v) == type({}) ? a:v._VAL : a:v) +endfunction + +"" +" Dump |msgpack-special-dict| that represents a string. If any additional +" parameter is given then it dumps binary string. +function s:msgpack_dump_string(v, ...) abort + let ret = [a:0 ? '"' : '="'] + for v in a:v._VAL + call add( + \ret, + \substitute( + \substitute(v, '["\\]', '\\\0', 'g'), + \'\n', '\\0', 'g')) + call add(ret, '\n') + endfor + let ret[-1] = '"' + return join(ret, '') +endfunction + +"" +" Dump binary string. +function s:msgpack_dump_binary(v) abort + if type(a:v) == type({}) + return s:msgpack_dump_string(a:v, 1) + else + return s:msgpack_dump_string({'_VAL': split(a:v, "\n", 1)}, 1) + endif +endfunction + +"" +" Dump array value. +function s:msgpack_dump_array(v) abort + let val = type(a:v) == type({}) ? a:v._VAL : a:v + return '[' . join(map(val[:], 'msgpack#string(v:val)'), ', ') . ']' +endfunction + +"" +" Dump dictionary value. +function s:msgpack_dump_map(v) abort + let ret = ['{'] + if msgpack#special_type(a:v) is 0 + for [k, v] in items(a:v) + let ret += [s:msgpack_dump_string({'_VAL': split(k, "\n", 1)}), + \': ', + \msgpack#string(v), + \', '] + unlet v + endfor + if !empty(a:v) + call remove(ret, -1) + endif + else + for [k, v] in sort(copy(a:v._VAL)) + let ret += [msgpack#string(k), + \': ', + \msgpack#string(v), + \', '] + unlet k + unlet v + endfor + if !empty(a:v._VAL) + call remove(ret, -1) + endif + endif + let ret += ['}'] + return join(ret, '') +endfunction + +"" +" Dump extension value. +function s:msgpack_dump_ext(v) abort + return printf('+(%i)%s', a:v._VAL[0], + \s:msgpack_dump_string({'_VAL': a:v._VAL[1]}, 1)) +endfunction + +"" +" Convert msgpack object to a string, like string() function does. Result of the +" conversion may be passed to msgpack#eval(). +function msgpack#string(v) abort + if type(a:v) == type({}) + let type = msgpack#special_type(a:v) + if type is 0 + let type = 'map' + endif + else + let type = get(s:MSGPACK_STANDARD_TYPES, type(a:v), 0) + if type is 0 + throw printf('msgpack:invtype: Unable to convert value %s', string(a:v)) + endif + endif + return s:msgpack_dump_{type}(a:v) +endfunction + +"" +" Copy msgpack object like deepcopy() does, but leave types intact +function msgpack#deepcopy(obj) abort + if type(a:obj) == type([]) + return map(copy(a:obj), 'msgpack#deepcopy(v:val)') + elseif type(a:obj) == type({}) + let special_type = msgpack#special_type(a:obj) + if special_type is 0 + return map(copy(a:obj), 'msgpack#deepcopy(v:val)') + else + return { + \'_TYPE': v:msgpack_types[special_type], + \'_VAL': msgpack#deepcopy(a:obj._VAL) + \} + endif + else + return copy(a:obj) + endif +endfunction + +"" +" Convert an escaped character to needed value +function s:msgpack_eval_str_sub(ch) abort + if a:ch is# 'n' + return '", "' + elseif a:ch is# '0' + return '\n' + else + return '\' . a:ch + endif +endfunction + +let s:MSGPACK_SPECIAL_OBJECTS = { + \'NIL': '{''_TYPE'': v:msgpack_types.nil, ''_VAL'': 0}', + \'TRUE': '{''_TYPE'': v:msgpack_types.boolean, ''_VAL'': 1}', + \'FALSE': '{''_TYPE'': v:msgpack_types.boolean, ''_VAL'': 0}', + \'nan': '(-(1.0/0.0-1.0/0.0))', + \'inf': '(1.0/0.0)', +\} + +"" +" Convert msgpack object dumped by msgpack#string() to a VimL object suitable +" for msgpackdump(). +" +" @param[in] s String to evaluate. +" @param[in] special_objs Additional special objects, in the same format as +" s:MSGPACK_SPECIAL_OBJECTS. +" +" @return Any value that msgpackparse() may return. +function msgpack#eval(s, special_objs) abort + let s = a:s + let expr = [] + let context = [] + while !empty(s) + let s = substitute(s, '^\s*', '', '') + if s[0] =~# '\v^\h$' + let name = matchstr(s, '\v\C^\w+') + if has_key(s:MSGPACK_SPECIAL_OBJECTS, name) + call add(expr, s:MSGPACK_SPECIAL_OBJECTS[name]) + elseif has_key(a:special_objs, name) + call add(expr, a:special_objs[name]) + else + throw 'name-unknown:Unknown name ' . name . ': ' . s + endif + let s = s[len(name):] + elseif (s[0] is# '-' && s[1] =~# '\v^\d$') || s[0] =~# '\v^\d$' + let sign = 1 + if s[0] is# '-' + let s = s[1:] + let sign = -1 + endif + if s[0:1] is# '0x' + " See comment in msgpack#int_dict_to_str(). + let s = s[2:] + let hexnum = matchstr(s, '\v\C^\x+') + if empty(hexnum) + throw '0x-empty:Must have number after 0x: ' . s + elseif len(hexnum) > 16 + throw '0x-long:Must have at most 16 hex digits: ' . s + endif + let s = s[len(hexnum):] + let hexnum = repeat('0', 16 - len(hexnum)) . hexnum + let g1 = str2nr(hexnum[0], 16) + let g2 = str2nr(hexnum[1:7], 16) + let g3 = str2nr(hexnum[8], 16) + let g4 = str2nr(hexnum[9:15], 16) + let v1 = s:shift(g1, -2) + let v2 = or(or(s:shift(s:mask1(g1, 2), 29), s:shift(g2, 1)), + \s:mask1(s:shift(g3, -3), 1)) + let v3 = or(s:shift(s:mask1(g3, 3), 28), g4) + call add(expr, printf('{''_TYPE'': v:msgpack_types.integer, '. + \'''_VAL'': [%i, %u, %u, %u]}', + \sign, v1, v2, v3)) + else + let num = matchstr(s, '\v\C^\d+') + let s = s[len(num):] + if sign == -1 + call add(expr, '-') + endif + call add(expr, num) + if s[0] is# '.' + let dec = matchstr(s, '\v\C^\.\d+%(e[+-]?\d+)?') + if empty(dec) + throw '0.-nodigits:Decimal dot must be followed by digit(s): ' . s + endif + let s = s[len(dec):] + call add(expr, dec) + endif + endif + elseif s =~# '-\?\%(inf\|nan\)' + if s[0] is# '-' + call add(expr, '-') + let s = s[1:] + endif + call add(expr, s:MSGPACK_SPECIAL_OBJECTS[s[0:2]]) + let s = s[3:] + elseif stridx('="+', s[0]) != -1 + let match = matchlist(s, '\v\C^(\=|\+\((\-?\d+)\)|)(\"%(\\.|[^\\"]+)*\")') + if empty(match) + throw '"-invalid:Invalid string: ' . s + endif + call add(expr, '{''_TYPE'': v:msgpack_types.') + if empty(match[1]) + call add(expr, 'binary') + elseif match[1] is# '=' + call add(expr, 'string') + else + call add(expr, 'ext') + endif + call add(expr, ', ''_VAL'': [') + if match[1][0] is# '+' + call add(expr, match[2] . ', [') + endif + call add(expr, substitute(match[3], '\v\C\\(.)', + \'\=s:msgpack_eval_str_sub(submatch(1))', 'g')) + if match[1][0] is# '+' + call add(expr, ']') + endif + call add(expr, ']}') + let s = s[len(match[0]):] + elseif s[0] is# '{' + call add(context, 'map') + call add(expr, '{''_TYPE'': v:msgpack_types.map, ''_VAL'': [') + call add(expr, '[') + let s = s[1:] + elseif s[0] is# '[' + call add(context, 'array') + call add(expr, '[') + let s = s[1:] + elseif s[0] is# ':' + call add(expr, ',') + let s = s[1:] + elseif s[0] is# ',' + if context[-1] is# 'array' + call add(expr, ',') + else + call add(expr, '], [') + endif + let s = s[1:] + elseif s[0] is# ']' + call remove(context, -1) + call add(expr, ']') + let s = s[1:] + elseif s[0] is# '}' + call remove(context, -1) + if expr[-1] is# "\x5B" + call remove(expr, -1) + else + call add(expr, ']') + endif + call add(expr, ']}') + let s = s[1:] + elseif s[0] is# '''' + let char = matchstr(s, '\m\C^''\zs.\ze''') + if empty(char) + throw 'char-invalid:Invalid integer character literal format: ' . s + endif + call add(expr, char2nr(char)) + let s = s[len(char) + 2:] + else + throw 'unknown:Invalid non-space character: ' . s + endif + endwhile + if empty(expr) + throw 'empty:Parsed string is empty' + endif + return eval(join(expr, '')) +endfunction + +"" +" Check whether two msgpack values are equal +function msgpack#equal(a, b) + let atype = msgpack#type(a:a) + let btype = msgpack#type(a:b) + if atype isnot# btype + return 0 + endif + let aspecial = msgpack#special_type(a:a) + let bspecial = msgpack#special_type(a:b) + if aspecial is# bspecial + if aspecial is# 0 + if type(a:a) == type({}) + if len(a:a) != len(a:b) + return 0 + endif + if !empty(filter(keys(a:a), '!has_key(a:b, v:val)')) + return 0 + endif + for [k, v] in items(a:a) + if !msgpack#equal(v, a:b[k]) + return 0 + endif + unlet v + endfor + return 1 + elseif type(a:a) == type([]) + if len(a:a) != len(a:b) + return 0 + endif + let i = 0 + for asubval in a:a + if !msgpack#equal(asubval, a:b[i]) + return 0 + endif + let i += 1 + unlet asubval + endfor + return 1 + elseif type(a:a) == type(0.0) + return (a:a == a:a ? a:a == a:b : string(a:a) ==# string(a:b)) + else + return a:a ==# a:b + endif + elseif aspecial is# 'map' || aspecial is# 'array' + if len(a:a._VAL) != len(a:b._VAL) + return 0 + endif + let alist = aspecial is# 'map' ? sort(copy(a:a._VAL)) : a:a._VAL + let blist = bspecial is# 'map' ? sort(copy(a:b._VAL)) : a:b._VAL + let i = 0 + for asubval in alist + let bsubval = blist[i] + if aspecial is# 'map' + if !(msgpack#equal(asubval[0], bsubval[0]) + \&& msgpack#equal(asubval[1], bsubval[1])) + return 0 + endif + else + if !msgpack#equal(asubval, bsubval) + return 0 + endif + endif + let i += 1 + unlet asubval + unlet bsubval + endfor + return 1 + elseif aspecial is# 'nil' + return 1 + elseif aspecial is# 'float' + return (a:a._VAL == a:a._VAL + \? (a:a._VAL == a:b._VAL) + \: (string(a:a._VAL) ==# string(a:b._VAL))) + else + return a:a._VAL ==# a:b._VAL + endif + else + if atype is# 'array' + let a = aspecial is 0 ? a:a : a:a._VAL + let b = bspecial is 0 ? a:b : a:b._VAL + return msgpack#equal(a, b) + elseif atype is# 'binary' + let a = (aspecial is 0 ? split(a:a, "\n", 1) : a:a._VAL) + let b = (bspecial is 0 ? split(a:b, "\n", 1) : a:b._VAL) + return a ==# b + elseif atype is# 'map' + if aspecial is 0 + let akeys = copy(a:a) + if len(a:b._VAL) != len(akeys) + return 0 + endif + for [k, v] in a:b._VAL + if msgpack#type(k) isnot# 'string' + " Non-special mapping cannot have non-string keys + return 0 + endif + if (empty(k._VAL) + \|| k._VAL ==# [""] + \|| !empty(filter(copy(k._VAL), 'stridx(v:val, "\n") != -1'))) + " Non-special mapping cannot have zero byte in key or an empty key + return 0 + endif + let kstr = join(k._VAL, "\n") + if !has_key(akeys, kstr) + " Protects from both missing and duplicate keys + return 0 + endif + if !msgpack#equal(akeys[kstr], v) + return 0 + endif + call remove(akeys, kstr) + unlet k + unlet v + endfor + return 1 + else + return msgpack#equal(a:b, a:a) + endif + elseif atype is# 'float' + let a = aspecial is 0 ? a:a : a:a._VAL + let b = bspecial is 0 ? a:b : a:b._VAL + return (a == a ? a == b : string(a) ==# string(b)) + elseif atype is# 'integer' + if aspecial is 0 + let sign = a:a >= 0 ? 1 : -1 + let a = sign * a:a + let v1 = s:mask1(s:shift(a, -62), 2) + let v2 = s:mask1(s:shift(a, -31), 31) + let v3 = s:mask1(a, 31) + return [sign, v1, v2, v3] == a:b._VAL + else + return msgpack#equal(a:b, a:a) + endif + else + throw printf('internal-invalid-type: %s == %s, but special %s /= %s', + \atype, btype, aspecial, bspecial) + endif + endif +endfunction 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/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index d1f8b1de4c..baf7550948 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -557,6 +557,149 @@ Since the text for this plugin is rather long it has been put in a separate file: |pi_spec.txt|. +SHADA *ft-shada* + +Allows editing binary |shada-file|s in a nice way. Opened binary files are +displayed in the following format: > + + Type with timestamp YYYY-mm-ddTHH:MM:SS: + % Key__ Description___ Value + + fooba foo bar baz fo {msgpack-value} + + abcde abc def ghi jk {msgpack-value} + Other type with timestamp YYYY-mm-ddTHH:MM:SS: + @ Description__ Value + - foo bar baz t {msgpack-value} + # Expected more elements in list + Some other type with timestamp YYYY-mm-ddTHH:MM:SS: + # Unexpected type: type instead of map + = {msgpack-value} + +Filetype plugin defines all |Cmd-event|s. Defined |SourceCmd| event makes +"source file.shada" be equivalent to "|:rshada| file.shada". |BufWriteCmd|, +|FileWriteCmd| and |FileAppendCmd| events are affected by the following +settings: + +*g:shada#keep_old_header* Boolean, if set to false all header entries + are ignored when writing. Defaults to 1. +*g:shada#add_own_header* Boolean, if set to true first written entry + will always be header entry with two values in + a map with attached data: |v:version| attached + to "version" key and "shada.vim" attached to + "generator" key. Defaults to 1. + +Format description: + +1. `#` starts a comment. Lines starting with space characters and then `#` + are ignored. Plugin may only add comment lines to indicate some errors in + ShaDa format. Lines containing no non-whitespace characters are also + ignored. +2. Each entry starts with line that has format "{type} with timestamp + {timestamp}:". {timestamp} is |strftime()|-formatted string representing + actual UNIX timestamp value. First strftime() argument is equal to + `%Y-%m-%dT%H:%M:%S`. When writing this timestamp is parsed using + |msgpack#strptime()|, with caching (it remembers which timestamp produced + particular strftime() output and uses this value if you did not change + timestamp). {type} is one of + 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 + * - Unknown (0x{type-hex}) + + Each type may be represented using Unknown entry: "Jump with timestamp ..." + is the same as "Unknown (0x8) with timestamp ....". +3. After header there is one of the following lines: + 1. " % Key__ Description__ Value": map header. After mapping header + follows a table which may contain comments and lines consisting of + " +", key, description and |{msgpack-value}|. Key is separated by at + least two spaces with description, description is separated by at least + two spaces with the value. Each key in the map must be at most as wide + as "Key__" header: "Key" allows at most 3-byte keys, "Key__" allows at + most 5-byte keys. If keys actually occupy less bytes then the rest is + filled with spaces. Keys cannot be empty, end with spaces, contain two + consequent spaces inside of them or contain multibyte characters (use + "=" format if you need this). Descriptions have the same restrictions + on width and contents, except that empty descriptions are allowed. + Description column may be omitted. + + When writing description is ignored. Keys with values |msgpack#equal| + to default ones are ignored. Order of keys is preserved. All keys are + treated as strings (not binary strings). + + Note: specifically for buffer list entries it is allowed to have more + then one map header. Each map header starts a new map entry inside + buffer list because ShaDa buffer list entry is an array of maps. I.e. > + + Buffer list with timestamp 1970-01-01T00:00:00: + % Key Description Value + + f file name "/foo" + + l line number 1 + + c column 10 +< + is equivalent to > + + Buffer list with timestamp 1970-01-01T00:00:00: + = [{="f": "/foo", ="c": 10}] +< + and > + + Buffer list with timestamp 1970-01-01T00:00:00: + % Key Description Value + + f file name "/foo" + + % Key Description Value + + f file name "/bar" +< + is equivalent to > + + Buffer list with timestamp 1970-01-01T00:00:00: + = [{="f": "/foo"}, {="f": "/bar"}] +< + Note 2: specifically for register entries special syntax for arrays was + designed: > + + Register with timestamp 1970-01-01T00:00:00: + % Key Description Value + + rc contents @ + | - "line1" + | - "line2" +< + This is equivalent to > + + Register with timestamp 1970-01-01T00:00:00: + % Key Description Value + + rc contents ["line1", "line2"] +< + Such syntax is automatically used if array representation appears to be + too lengthy. + 2. " @ Description__ Value": array header. Same as map, but key is + omitted and description cannot be omitted. Array entries start with + " -". Example: > + + History entry with timestamp 1970-01-01T00:00:00: + @ Description_ Value + - history type SEARCH + - contents "foo" + - separator '/' +< + is equivalent to > + + History entry with timestamp 1970-01-01T00:00:00: + = [SEARCH, "foo", '/'] +< + Note: special array syntax for register entries is not recognized here. + 3. " = {msgpack-value}": raw values. |{msgpack-value}| in this case may + have absolutely any type. Special array syntax for register entries is + not recognized here as well. + + SQL *ft-sql* Since the text for this plugin is rather long it has been put in a separate diff --git a/runtime/doc/pi_msgpack.txt b/runtime/doc/pi_msgpack.txt new file mode 100644 index 0000000000..95d6ff7467 --- /dev/null +++ b/runtime/doc/pi_msgpack.txt @@ -0,0 +1,139 @@ +*pi_msgpack.txt* For NeoVim version 0.1. + +Author: Nikolay Pavlov <kp-pav@yandex.ru> +Copyright: (c) 2015 by Nikolay Pavlov + +The Apache license applies to the files in this package, including +runtime/autoload/msgpack.vim, runtime/doc/pi_msgpack.txt and +test/functional/plugin/msgpack_spec.lua. Like anything else that's free, +msgpack.vim and its associated files are provided *as is* and comes with no +warranty of any kind, either expressed or implied. No guarantees of +merchantability. No guarantees of suitability for any purpose. By using this +plugin, you agree that in no event will the copyright holder be liable for any +damages resulting from the use of this software. Use at your own risk! + +============================================================================== +1. Contents *msgpack.vim-contents* + + 1. Contents..............................: |msgpack.vim-contents| + 2. Msgpack.vim introduction..............: |msgpack.vim-intro| + 3. Msgpack.vim manual....................: |msgpack.vim-manual| + Function arguments....................: |msgpack.vim-arguments| + msgpack#is_int function...............: |msgpack#is_int()| + msgpack#is_uint function..............: |msgpack#is_uint()| + msgpack#strftime function.............: |msgpack#strftime()| + msgpack#strptime function.............: |msgpack#strptime()| + msgpack#int_dict_to_str function......: |msgpack#int_dict_to_str()| + msgpack#special_type function.........: |msgpack#special_type()| + msgpack#type function.................: |msgpack#type()| + msgpack#deepcopy function.............: |msgpack#deepcopy()| + msgpack#string function...............: |msgpack#string()| + msgpack#eval function.................: |msgpack#eval()| + msgpack#equal function................: |msgpack#equal()| + + +============================================================================== +2. Msgpack.vim introduction *msgpack.vim-intro* + +This plugin contains utility functions to be used in conjunction with +|msgpackdump()| and |msgpackparse()| functions. + +============================================================================== +3. Msgpack.vim manual *msgpack.vim-manual* + +FUNCTION ARGUMENTS *msgpack.vim-arguments* + +Disambiguation of arguments described below. Note: if e.g. function is listed +as accepting |{msgpack-integer}| (or anything else) it means that function +does not check whether argument matches its description. + +*{msgpack-value}* Either |msgpack-special-dict| or a regular value, but + not function reference. +*{msgpack-integer}* Any value for which |msgpack#type| will return + "integer". +*{msgpack-special-int}* |msgpack-special-dict| representing integer. + +msgpack#is_int({msgpack-value}) *msgpack#is_int()* + Returns 1 if given {msgpack-value} is integer value, 0 otherwise. + +msgpack#is_uint({msgpack-value}) *msgpack#is_uint()* + Returns 1 if given {msgpack-value} is integer value greater or equal + to zero, 0 otherwise. + + *msgpack#strftime* +msgpack#strftime({format}, {msgpack-integer}) *msgpack#strftime()* + Same as |strftime()|, but second argument may be + |msgpack-special-dict|. Requires |+python| or |+python3| to really + work with |msgpack-special-dict|s. + + *msgpack#strptime* +msgpack#strptime({format}, {time}) *msgpack#strptime()* + Reverse of |msgpack#strptime()|: for any time and format + |msgpack#equal|( |msgpack#strptime|(format, |msgpack#strftime|(format, + time)), time) be true. Requires |+python| or |+python3|, without it + only supports non-|msgpack-special-dict| nonnegative times and format + equal to `%Y-%m-%dT%H:%M:%S`. + +msgpack#int_dict_to_str({msgpack-special-int}) *msgpack#int_dict_to_str()* + Function which converts |msgpack-special-dict| integer value to + a hexadecimal value like 0x1234567890ABCDEF (always returns exactly 16 + hexadecimal digits). + +msgpack#special_type({msgpack-value}) *msgpack#special_type()* + Returns zero if {msgpack-value} is not |msgpack-special-dict|. If it + is it returns name of the key in |v:msgpack_types| which represents + {msgpack-value} type. + +msgpack#type({msgpack-value}) *msgpack#type()* + Returns name of the key in |v:msgpack_types| that represents + {msgpack-value} type. Never returns zero: this function returns + msgpack type which will be dumped by |msgpackdump()| should it receive + a list with singe {msgpack-value} as input. + +msgpack#deepcopy({msgpack-value}) *msgpack#deepcopy()* + Like |deepcopy()|, but works correctly with |msgpack-special-dict| + values. Plain |deepcopy()| will destroy all types in + |msgpack-special-dict| values because it will copy _TYPE key values, + while they should be preserved. + +msgpack#string({msgpack-value}) *msgpack#string()* + Like |string()|, but saves information about msgpack types. Values + dumped by msgpack#string may be read back by |msgpack#eval()|. + Returns is the following: + + - Dictionaries are dumped as "{key1: value1, key2: value2}". Note: + msgpack allows any values in keys, so with some + |msgpack-special-dict| values |msgpack#string()| may produce even + "{{1: 2}: 3, [4]: 5}". + - Lists are dumped as "[value1, value2]". + - Strings are dumped as + 1. `"abc"`: binary string. + 2. `="abc"`: string. + 3. `+(10)"ext"`: extension strings (10 may be replaced with any + 8-bit signed integer). + Inside strings the following escape sequences may be present: "\0" + (represents NUL byte), "\n" (represents line feed) and "\"" + (represents double quote). + - Floating-point and integer values are dumped using |string()| or + |msgpack#int_dict_to_str()|. + - Booleans are dumped as "TRUE" or "FALSE". + - Nil values are dumped as "NIL". + +msgpack#eval({string}, {dict}) *msgpack#eval()* + Transforms string created by |msgpack#string()| into a value suitable + for |msgpackdump()|. Second argument allows adding special values + that start with head characters (|/\h|) and contain only word + characters (|/\w|). Built-in special values are "TRUE", "FALSE", + "NIL", "nan" and "inf" and they cannot be overridden. Map values are + always evaluated to |msgpack-special-dict| values, as well as + hexadecimal digits. When evaluating maps order of keys is preserved. + + *msgpack#equal* +msgpack#equal({msgpack-value}, {msgpack-value}) *msgpack#equal()* + Returns 1 if given values are equal, 0 otherwise. When comparing + msgpack map values order of keys is ignored. Comparing + |msgpack-special-dict| with equivalent non-special-dict value + evaluates to 1. + +============================================================================== +vim:tw=78:ts=8:ft=help:fdm=marker diff --git a/runtime/ftplugin/shada.vim b/runtime/ftplugin/shada.vim new file mode 100644 index 0000000000..4f908f4701 --- /dev/null +++ b/runtime/ftplugin/shada.vim @@ -0,0 +1,18 @@ +if exists('b:did_ftplugin') + finish +endif + +let b:did_ftplugin = 1 + +function! ShaDaIndent(lnum) + if a:lnum == 1 || getline(a:lnum) =~# '\mwith timestamp.*:$' + return 0 + else + return shiftwidth() + endif +endfunction + +setlocal expandtab tabstop=2 softtabstop=2 shiftwidth=2 +setlocal indentexpr=ShaDaIndent(v:lnum) indentkeys=<:>,o,O + +let b:undo_ftplugin = 'setlocal et< ts< sts< sw< indentexpr< indentkeys<' diff --git a/runtime/plugin/shada.vim b/runtime/plugin/shada.vim new file mode 100644 index 0000000000..ae08e01dcb --- /dev/null +++ b/runtime/plugin/shada.vim @@ -0,0 +1,39 @@ +if exists('g:loaded_shada_plugin') + finish +endif +let g:loaded_shada_plugin = 1 + +augroup ShaDaCommands + autocmd! + autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] + \ :if !empty(v:cmdarg)|throw '++opt not supported'|endif + \ |call setline('.', shada#get_strings(readfile(expand('<afile>'),'b'))) + \ |setlocal filetype=shada + autocmd FileReadCmd *.shada,*.shada.tmp.[a-z] + \ :if !empty(v:cmdarg)|throw '++opt not supported'|endif + \ |call append("'[", shada#get_strings(readfile(expand('<afile>'), 'b'))) + autocmd BufWriteCmd *.shada,*.shada.tmp.[a-z] + \ :if !empty(v:cmdarg)|throw '++opt not supported'|endif + \ |if writefile(shada#get_binstrings(getline(1, '$')), + \expand('<afile>'), 'b') == 0 + \ | let &l:modified = (expand('<afile>') is# bufname(+expand('<abuf>')) + \? 0 + \: stridx(&cpoptions, '+') != -1) + \ |endif + autocmd FileWriteCmd *.shada,*.shada.tmp.[a-z] + \ :if !empty(v:cmdarg)|throw '++opt not supported'|endif + \ |call writefile( + \shada#get_binstrings(getline(min([line("'["), line("']")]), + \max([line("'["), line("']")]))), + \expand('<afile>'), + \'b') + autocmd FileAppendCmd *.shada,*.shada.tmp.[a-z] + \ :if !empty(v:cmdarg)|throw '++opt not supported'|endif + \ |call writefile( + \shada#get_binstrings(getline(min([line("'["), line("']")]), + \max([line("'["), line("']")]))), + \expand('<afile>'), + \'ab') + autocmd SourceCmd *.shada,*.shada.tmp.[a-z] + \ :execute 'rshada' fnameescape(expand('<afile>')) +augroup END diff --git a/runtime/syntax/shada.vim b/runtime/syntax/shada.vim new file mode 100644 index 0000000000..e5325af5b0 --- /dev/null +++ b/runtime/syntax/shada.vim @@ -0,0 +1,125 @@ +if exists("b:current_syntax") + finish +endif + +syntax match ShaDaEntryHeader + \ '^\u.\{-} with timestamp \d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d:$' +syntax match ShaDaEntryName '^\u.\{-}\ze with' contained + \ containedin=ShaDaEntryHeader +syntax match ShaDaEntryTimestamp 'timestamp \zs\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d' + \ contained containedin=ShaDaEntryHeader +syntax match ShaDaEntryTimestampNumber '\d\+' contained + \ containedin=ShaDaEntryTimestamp + +syntax match ShaDaComment '^\s*#.*$' + +syntax region ShaDaEntryMapLong start='^ % Key_* Description_* Value$' + \ end='^ %\|^\S'me=s-1 contains=ShaDaComment,ShaDaEntryMapLongEntryStart +syntax region ShaDaEntryMapShort start='^ % Key_* Value$' + \ end='^ %\|^\S'me=s-1 contains=ShaDaComment,ShaDaEntryMapShortEntryStart +syntax match ShaDaEntryMapHeader '^ % Key_* \(Description_* \)\?Value$' + \ contained containedin=ShaDaEntryMapLong,ShaDaEntryMapShort +syntax match ShaDaEntryMapLongEntryStart '^ + 'hs=e-2,he=e-1 + \ nextgroup=ShaDaEntryMapLongKey +syntax match ShaDaEntryMapLongKey '\S\+ \+\ze\S'he=e-2 contained + \ nextgroup=ShaDaEntryMapLongDescription +syntax match ShaDaEntryMapLongDescription '.\{-} \ze\S'he=e-2 contained + \ nextgroup=@ShaDaEntryMsgpackValue +syntax match ShaDaEntryMapShortEntryStart '^ + 'hs=e-2,he=e-1 contained + \ nextgroup=ShaDaEntryMapShortKey +syntax match ShaDaEntryMapShortKey '\S\+ \+\ze\S'he=e-2 contained + \ nextgroup=@ShaDaEntryMsgpackValue +syntax match ShaDaEntryMapBinArrayStart '^ | - 'hs=e-4,he=e-1 contained + \ containedin=ShaDaEntryMapLong,ShaDaEntryMapShort + \ nextgroup=@ShaDaEntryMsgpackValue + +syntax region ShaDaEntryArray start='^ @ Description_* Value$' + \ end='^\S'me=s-1 keepend + \ contains=ShaDaComment,ShaDaEntryArrayEntryStart,ShaDaEntryArrayHeader +syntax match ShaDaEntryArrayHeader '^ @ Description_* Value$' contained +syntax match ShaDaEntryArrayEntryStart '^ - 'hs=e-2,he=e-1 + \ nextgroup=ShaDaEntryArrayDescription +syntax match ShaDaEntryArrayDescription '.\{-} \ze\S'he=e-2 contained + \ nextgroup=@ShaDaEntryMsgpackValue + +syntax match ShaDaEntryRawMsgpack '^ = ' nextgroup=@ShaDaEntryMsgpackValue + +syntax cluster ShaDaEntryMsgpackValue + \ add=ShaDaMsgpackKeyword,ShaDaMsgpackShaDaKeyword + \ add=ShaDaMsgpackInteger,ShaDaMsgpackCharacter,ShaDaMsgpackFloat + \ add=ShaDaMsgpackBinaryString,ShaDaMsgpackString,ShaDaMsgpackExt + \ add=ShaDaMsgpackArray,ShaDaMsgpackMap + \ add=ShaDaMsgpackMultilineArray +syntax keyword ShaDaMsgpackKeyword contained NIL TRUE FALSE +syntax keyword ShaDaMsgpackShaDaKeyword contained + \ CMD SEARCH EXPR INPUT DEBUG + \ CHARACTERWISE LINEWISE BLOCKWISE +syntax region ShaDaMsgpackBinaryString matchgroup=ShaDaMsgpackStringQuotes + \ start='"' skip='\\"' end='"' contained keepend +syntax match ShaDaMsgpackBinaryStringEscape '\\[\\0n"]' + \ contained containedin=ShaDaMsgpackBinaryString +syntax match ShaDaMsgpackString '=' contained nextgroup=ShaDaMsgpackBinaryString +syntax match ShaDaMsgpackExt '+(-\?\d\+)' contained + \ nextgroup=ShaDaMsgpackBinaryString +syntax match ShaDaMsgpackExtType '-\?\d\+' contained containedin=ShaDaMsgpackExt +syntax match ShaDaMsgpackCharacter /'.'/ contained +syntax match ShaDaMsgpackInteger '-\?\%(0x\x\{,16}\|\d\+\)' contained +syntax match ShaDaMsgpackFloat '-\?\d\+\.\d\+\%(e[+-]\?\d\+\)\?' contained +syntax region ShaDaMsgpackArray matchgroup=ShaDaMsgpackArrayBraces + \ start='\[' end='\]' contained + \ contains=@ShaDaEntryMsgpackValue,ShaDaMsgpackComma +syntax region ShaDaMsgpackMap matchgroup=ShaDaMsgpackMapBraces + \ start='{' end='}' contained + \ contains=@ShaDaEntryMsgpackValue,ShaDaMsgpackComma,ShaDaMsgpackColon +syntax match ShaDaMsgpackComma ',' contained +syntax match ShaDaMsgpackColon ':' contained +syntax match ShaDaMsgpackMultilineArray '@' contained + +hi def link ShaDaComment Comment +hi def link ShaDaEntryNumber Number +hi def link ShaDaEntryTimestamp Operator +hi def link ShaDaEntryName Keyword + +hi def link ShaDaEntryMapHeader PreProc + +hi def link ShaDaEntryMapEntryStart Label +hi def link ShaDaEntryMapLongEntryStart ShaDaEntryMapEntryStart +hi def link ShaDaEntryMapShortEntryStart ShaDaEntryMapEntryStart +hi def link ShaDaEntryMapBinArrayStart ShaDaEntryMapEntryStart +hi def link ShaDaEntryArrayEntryStart ShaDaEntryMapEntryStart + +hi def link ShaDaEntryMapKey String +hi def link ShaDaEntryMapLongKey ShaDaEntryMapKey +hi def link ShaDaEntryMapShortKey ShaDaEntryMapKey + +hi def link ShaDaEntryMapDescription Comment +hi def link ShaDaEntryMapLongDescription ShaDaEntryMapDescription +hi def link ShaDaEntryMapShortDescription ShaDaEntryMapDescription + +hi def link ShaDaEntryArrayHeader PreProc + +hi def link ShaDaEntryArrayDescription ShaDaEntryMapDescription + +hi def link ShaDaMsgpackKeyword Keyword +hi def link ShaDaMsgpackShaDaKeyword ShaDaMsgpackKeyword +hi def link ShaDaMsgpackCharacter Character +hi def link ShaDaMsgpackInteger Number +hi def link ShaDaMsgpackFloat Float + +hi def link ShaDaMsgpackBinaryString String +hi def link ShaDaMsgpackBinaryStringEscape SpecialChar +hi def link ShaDaMsgpackExtType Typedef + +hi def link ShaDaMsgpackStringQuotes Operator +hi def link ShaDaMsgpackString ShaDaMsgpackStringQuotes +hi def link ShaDaMsgpackExt ShaDaMsgpackStringQuotes + +hi def link ShaDaMsgpackMapBraces Operator +hi def link ShaDaMsgpackArrayBraces ShaDaMsgpackMapBraces + +hi def link ShaDaMsgpackComma Operator +hi def link ShaDaMsgpackColon ShaDaMsgpackComma + +hi def link ShaDaMsgpackMultilineArray Operator + +let b:current_syntax = "shada" diff --git a/test/functional/plugin/helpers.lua b/test/functional/plugin/helpers.lua new file mode 100644 index 0000000000..9762ca314e --- /dev/null +++ b/test/functional/plugin/helpers.lua @@ -0,0 +1,41 @@ +local paths = require('test.config.paths') + +local helpers = require('test.functional.helpers') +local spawn, set_session, nvim_prog, merge_args = + helpers.spawn, helpers.set_session, helpers.nvim_prog, helpers.merge_args + +local additional_cmd = '' + +local function nvim_argv(shada_file) + local rtp_value = ('\'%s/runtime\''):format( + paths.test_source_path:gsub('\'', '\'\'')) + local nvim_argv = {nvim_prog, '-u', 'NORC', '-i', shada_file or 'NONE', '-N', + '--cmd', 'set shortmess+=I background=light noswapfile', + '--cmd', 'let &runtimepath=' .. rtp_value, + '--cmd', additional_cmd, + '--embed'} + if helpers.prepend_argv then + return merge_args(helpers.prepend_argv, nvim_argv) + else + return nvim_argv + end +end + +local session = nil + +local reset = function(...) + if session then + session:exit(0) + end + session = spawn(nvim_argv(...)) + set_session(session) +end + +local set_additional_cmd = function(s) + additional_cmd = s +end + +return { + reset=reset, + set_additional_cmd=set_additional_cmd, +} diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua new file mode 100644 index 0000000000..18ff0f5156 --- /dev/null +++ b/test/functional/plugin/msgpack_spec.lua @@ -0,0 +1,699 @@ +local helpers = require('test.functional.helpers') +local eq, nvim_eval, nvim_command, exc_exec = + helpers.eq, helpers.eval, helpers.command, helpers.exc_exec + +local plugin_helpers = require('test.functional.plugin.helpers') +local reset = plugin_helpers.reset + +describe('In autoload/msgpack.vim', function() + before_each(reset) + + local sp = function(typ, val) + return ('{"_TYPE": v:msgpack_types.%s, "_VAL": %s}'):format(typ, val) + end + local mapsp = function(...) + local val = '' + for i=1,(select('#', ...)/2) do + val = ('%s[%s,%s],'):format(val, select(i * 2 - 1, ...), + select(i * 2, ...)) + end + return sp('map', '[' .. val .. ']') + end + + local nan = -(1.0/0.0-1.0/0.0) + local minus_nan = 1.0/0.0-1.0/0.0 + local inf = 1.0/0.0 + local minus_inf = -(1.0/0.0) + local has_minus_nan = tostring(nan) ~= tostring(minus_nan) + + describe('function msgpack#equal', function() + local msgpack_eq = function(expected, a, b) + eq(expected, nvim_eval(('msgpack#equal(%s, %s)'):format(a, b))) + if a ~= b then + eq(expected, nvim_eval(('msgpack#equal(%s, %s)'):format(b, a))) + end + end + it('compares raw integers correctly', function() + msgpack_eq(1, '1', '1') + msgpack_eq(0, '1', '0') + end) + it('compares integer specials correctly', function() + msgpack_eq(1, sp('integer', '[-1, 1, 0, 0]'), + sp('integer', '[-1, 1, 0, 0]')) + msgpack_eq(0, sp('integer', '[-1, 1, 0, 0]'), + sp('integer', '[ 1, 1, 0, 0]')) + end) + it('compares integer specials with raw integer correctly', function() + msgpack_eq(1, sp('integer', '[-1, 0, 0, 1]'), '-1') + msgpack_eq(0, sp('integer', '[-1, 0, 0, 1]'), '1') + msgpack_eq(0, sp('integer', '[ 1, 0, 0, 1]'), '-1') + msgpack_eq(1, sp('integer', '[ 1, 0, 0, 1]'), '1') + end) + it('compares integer with float correctly', function() + msgpack_eq(0, '0', '0.0') + end) + it('compares raw binaries correctly', function() + msgpack_eq(1, '"abc\\ndef"', '"abc\\ndef"') + msgpack_eq(0, '"abc\\ndef"', '"abc\\nghi"') + end) + it('compares binary specials correctly', function() + msgpack_eq(1, sp('binary', '["abc\\n", "def"]'), + sp('binary', '["abc\\n", "def"]')) + msgpack_eq(0, sp('binary', '["abc", "def"]'), + sp('binary', '["abc\\n", "def"]')) + end) + it('compares binary specials with raw binaries correctly', function() + msgpack_eq(1, sp('binary', '["abc", "def"]'), '"abc\\ndef"') + msgpack_eq(0, sp('binary', '["abc", "def"]'), '"abcdef"') + end) + it('compares string specials correctly', function() + msgpack_eq(1, sp('string', '["abc\\n", "def"]'), + sp('string', '["abc\\n", "def"]')) + msgpack_eq(0, sp('string', '["abc", "def"]'), + sp('string', '["abc\\n", "def"]')) + end) + it('compares string specials with binary correctly', function() + msgpack_eq(0, sp('string', '["abc\\n", "def"]'), + sp('binary', '["abc\\n", "def"]')) + msgpack_eq(0, sp('string', '["abc", "def"]'), '"abc\\ndef"') + msgpack_eq(0, sp('binary', '["abc\\n", "def"]'), + sp('string', '["abc\\n", "def"]')) + msgpack_eq(0, '"abc\\ndef"', sp('string', '["abc", "def"]')) + end) + it('compares ext specials correctly', function() + msgpack_eq(1, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]')) + msgpack_eq(0, sp('ext', '[2, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]')) + msgpack_eq(0, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "abc"]]')) + end) + it('compares raw maps correctly', function() + msgpack_eq(1, '{"a": 1, "b": 2}', '{"b": 2, "a": 1}') + msgpack_eq(1, '{}', '{}') + msgpack_eq(0, '{}', '{"a": 1}') + msgpack_eq(0, '{"a": 2}', '{"a": 1}') + msgpack_eq(0, '{"a": 1}', '{"b": 1}') + msgpack_eq(0, '{"a": 1}', '{"a": 1, "b": 1}') + msgpack_eq(0, '{"a": 1, "b": 1}', '{"b": 1}') + end) + it('compares map specials correctly', function() + msgpack_eq(1, mapsp(), mapsp()) + msgpack_eq(1, mapsp(sp('binary', '[""]'), '""'), + mapsp(sp('binary', '[""]'), '""')) + msgpack_eq(1, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('1', '1'), mapsp('1', '1'))) + msgpack_eq(0, mapsp(), mapsp('1', '1')) + msgpack_eq(0, mapsp(sp('binary', '["a"]'), '""'), + mapsp(sp('binary', '[""]'), '""')) + msgpack_eq(0, mapsp(sp('binary', '[""]'), '"a"'), + mapsp(sp('binary', '[""]'), '""')) + msgpack_eq(0, mapsp(sp('binary', '["a"]'), '"a"'), + mapsp(sp('binary', '[""]'), '""')) + msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(sp('binary', '[""]'), mapsp('1', '1'))) + msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('2', '1'), mapsp('1', '1'))) + msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('1', '2'), mapsp('1', '1'))) + msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('1', '1'), mapsp('2', '1'))) + msgpack_eq(0, mapsp(mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('1', '1'), mapsp('1', '2'))) + msgpack_eq(1, mapsp(mapsp('2', '1'), mapsp('1', '1'), + mapsp('1', '1'), mapsp('1', '1')), + mapsp(mapsp('1', '1'), mapsp('1', '1'), + mapsp('2', '1'), mapsp('1', '1'))) + end) + it('compares map specials with raw maps correctly', function() + msgpack_eq(1, mapsp(), '{}') + msgpack_eq(1, mapsp(sp('string', '["1"]'), '1'), '{"1": 1}') + msgpack_eq(1, mapsp(sp('string', '["1"]'), sp('integer', '[1, 0, 0, 1]')), + '{"1": 1}') + msgpack_eq(0, mapsp(sp('integer', '[1, 0, 0, 1]'), sp('string', '["1"]')), + '{1: "1"}') + msgpack_eq(0, mapsp('"1"', sp('integer', '[1, 0, 0, 1]')), + '{"1": 1}') + msgpack_eq(0, + mapsp(sp('string', '["1"]'), '1', sp('string', '["2"]'), '2'), + '{"1": 1}') + msgpack_eq(0, mapsp(sp('string', '["1"]'), '1'), '{"1": 1, "2": 2}') + end) + it('compares raw arrays correctly', function() + msgpack_eq(1, '[]', '[]') + msgpack_eq(0, '[]', '[1]') + msgpack_eq(1, '[1]', '[1]') + msgpack_eq(1, '[[[1]]]', '[[[1]]]') + msgpack_eq(0, '[[[2]]]', '[[[1]]]') + end) + it('compares array specials correctly', function() + msgpack_eq(1, sp('array', '[]'), sp('array', '[]')) + msgpack_eq(0, sp('array', '[]'), sp('array', '[1]')) + msgpack_eq(1, sp('array', '[1]'), sp('array', '[1]')) + msgpack_eq(1, sp('array', '[[[1]]]'), sp('array', '[[[1]]]')) + msgpack_eq(0, sp('array', '[[[1]]]'), sp('array', '[[[2]]]')) + end) + it('compares array specials with raw arrays correctly', function() + msgpack_eq(1, sp('array', '[]'), '[]') + msgpack_eq(0, sp('array', '[]'), '[1]') + msgpack_eq(1, sp('array', '[1]'), '[1]') + msgpack_eq(1, sp('array', '[[[1]]]'), '[[[1]]]') + msgpack_eq(0, sp('array', '[[[1]]]'), '[[[2]]]') + end) + it('compares raw floats correctly', function() + msgpack_eq(1, '0.0', '0.0') + msgpack_eq(1, '(1.0/0.0-1.0/0.0)', '(1.0/0.0-1.0/0.0)') + if has_minus_nan then + msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '-(1.0/0.0-1.0/0.0)') + end + msgpack_eq(1, '-(1.0/0.0-1.0/0.0)', '-(1.0/0.0-1.0/0.0)') + msgpack_eq(1, '1.0/0.0', '1.0/0.0') + msgpack_eq(1, '-(1.0/0.0)', '-(1.0/0.0)') + msgpack_eq(1, '0.0', '0.0') + msgpack_eq(0, '0.0', '1.0') + msgpack_eq(0, '0.0', '(1.0/0.0-1.0/0.0)') + msgpack_eq(0, '0.0', '1.0/0.0') + msgpack_eq(0, '0.0', '-(1.0/0.0)') + msgpack_eq(0, '1.0/0.0', '-(1.0/0.0)') + msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '-(1.0/0.0)') + msgpack_eq(0, '(1.0/0.0-1.0/0.0)', '1.0/0.0') + end) + it('compares float specials with raw floats correctly', function() + msgpack_eq(1, sp('float', '0.0'), '0.0') + msgpack_eq(1, sp('float', '(1.0/0.0-1.0/0.0)'), '(1.0/0.0-1.0/0.0)') + if has_minus_nan then + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '-(1.0/0.0-1.0/0.0)') + msgpack_eq(0, sp('float', '-(1.0/0.0-1.0/0.0)'), '(1.0/0.0-1.0/0.0)') + end + msgpack_eq(1, sp('float', '-(1.0/0.0-1.0/0.0)'), '-(1.0/0.0-1.0/0.0)') + msgpack_eq(1, sp('float', '1.0/0.0'), '1.0/0.0') + msgpack_eq(1, sp('float', '-(1.0/0.0)'), '-(1.0/0.0)') + msgpack_eq(1, sp('float', '0.0'), '0.0') + msgpack_eq(0, sp('float', '0.0'), '1.0') + msgpack_eq(0, sp('float', '0.0'), '(1.0/0.0-1.0/0.0)') + msgpack_eq(0, sp('float', '0.0'), '1.0/0.0') + msgpack_eq(0, sp('float', '0.0'), '-(1.0/0.0)') + msgpack_eq(0, sp('float', '1.0/0.0'), '-(1.0/0.0)') + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '-(1.0/0.0)') + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), '1.0/0.0') + end) + it('compares float specials correctly', function() + msgpack_eq(1, sp('float', '0.0'), sp('float', '0.0')) + msgpack_eq(1, sp('float', '(1.0/0.0-1.0/0.0)'), + sp('float', '(1.0/0.0-1.0/0.0)')) + msgpack_eq(1, sp('float', '1.0/0.0'), sp('float', '1.0/0.0')) + msgpack_eq(1, sp('float', '-(1.0/0.0)'), sp('float', '-(1.0/0.0)')) + msgpack_eq(1, sp('float', '0.0'), sp('float', '0.0')) + msgpack_eq(0, sp('float', '0.0'), sp('float', '1.0')) + msgpack_eq(0, sp('float', '0.0'), sp('float', '(1.0/0.0-1.0/0.0)')) + msgpack_eq(0, sp('float', '0.0'), sp('float', '1.0/0.0')) + msgpack_eq(0, sp('float', '0.0'), sp('float', '-(1.0/0.0)')) + msgpack_eq(0, sp('float', '1.0/0.0'), sp('float', '-(1.0/0.0)')) + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), sp('float', '-(1.0/0.0)')) + if has_minus_nan then + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), + sp('float', '-(1.0/0.0-1.0/0.0)')) + end + msgpack_eq(1, sp('float', '-(1.0/0.0-1.0/0.0)'), + sp('float', '-(1.0/0.0-1.0/0.0)')) + msgpack_eq(0, sp('float', '(1.0/0.0-1.0/0.0)'), sp('float', '1.0/0.0')) + end) + it('compares boolean specials correctly', function() + msgpack_eq(1, sp('boolean', '1'), sp('boolean', '1')) + msgpack_eq(0, sp('boolean', '1'), sp('boolean', '0')) + end) + it('compares nil specials correctly', function() + msgpack_eq(1, sp('nil', '1'), sp('nil', '0')) + end) + it('compares nil, boolean and integer values with each other correctly', + function() + msgpack_eq(0, sp('boolean', '1'), '1') + msgpack_eq(0, sp('boolean', '1'), sp('nil', '0')) + msgpack_eq(0, sp('boolean', '1'), sp('nil', '1')) + msgpack_eq(0, sp('boolean', '0'), sp('nil', '0')) + msgpack_eq(0, sp('boolean', '0'), '0') + msgpack_eq(0, sp('boolean', '0'), sp('integer', '[1, 0, 0, 0]')) + msgpack_eq(0, sp('boolean', '0'), sp('integer', '[1, 0, 0, 1]')) + msgpack_eq(0, sp('boolean', '1'), sp('integer', '[1, 0, 0, 1]')) + msgpack_eq(0, sp('nil', '0'), sp('integer', '[1, 0, 0, 0]')) + msgpack_eq(0, sp('nil', '0'), '0') + end) + end) + + describe('function msgpack#is_int', function() + it('works', function() + eq(1, nvim_eval('msgpack#is_int(1)')) + eq(1, nvim_eval('msgpack#is_int(-1)')) + eq(1, nvim_eval(('msgpack#is_int(%s)'):format( + sp('integer', '[1, 0, 0, 1]')))) + eq(1, nvim_eval(('msgpack#is_int(%s)'):format( + sp('integer', '[-1, 0, 0, 1]')))) + eq(0, nvim_eval(('msgpack#is_int(%s)'):format( + sp('float', '0.0')))) + eq(0, nvim_eval(('msgpack#is_int(%s)'):format( + sp('boolean', '0')))) + eq(0, nvim_eval(('msgpack#is_int(%s)'):format( + sp('nil', '0')))) + eq(0, nvim_eval('msgpack#is_int("")')) + end) + end) + + describe('function msgpack#is_uint', function() + it('works', function() + eq(1, nvim_eval('msgpack#is_uint(1)')) + eq(0, nvim_eval('msgpack#is_uint(-1)')) + eq(1, nvim_eval(('msgpack#is_uint(%s)'):format( + sp('integer', '[1, 0, 0, 1]')))) + eq(0, nvim_eval(('msgpack#is_uint(%s)'):format( + sp('integer', '[-1, 0, 0, 1]')))) + eq(0, nvim_eval(('msgpack#is_uint(%s)'):format( + sp('float', '0.0')))) + eq(0, nvim_eval(('msgpack#is_uint(%s)'):format( + sp('boolean', '0')))) + eq(0, nvim_eval(('msgpack#is_uint(%s)'):format( + sp('nil', '0')))) + eq(0, nvim_eval('msgpack#is_uint("")')) + end) + end) + + describe('function msgpack#strftime', function() + it('works', function() + local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + eq(epoch, nvim_eval('msgpack#strftime("%Y-%m-%dT%H:%M:%S", 0)')) + eq(epoch, nvim_eval( + ('msgpack#strftime("%%Y-%%m-%%dT%%H:%%M:%%S", %s)'):format(sp( + 'integer', '[1, 0, 0, 0]')))) + end) + end) + + describe('function msgpack#strptime', function() + it('works', function() + for _, v in ipairs({0, 10, 100000, 204, 1000000000}) do + local time = os.date('%Y-%m-%dT%H:%M:%S', v) + eq(v, nvim_eval('msgpack#strptime("%Y-%m-%dT%H:%M:%S", ' + .. '"' .. time .. '")')) + end + end) + end) + + describe('function msgpack#type', function() + local type_eq = function(expected, val) + eq(expected, nvim_eval(('msgpack#type(%s)'):format(val))) + end + + it('works for special dictionaries', function() + type_eq('string', sp('string', '[""]')) + type_eq('binary', sp('binary', '[""]')) + type_eq('ext', sp('ext', '[1, [""]]')) + type_eq('array', sp('array', '[]')) + type_eq('map', sp('map', '[]')) + type_eq('integer', sp('integer', '[1, 0, 0, 0]')) + type_eq('float', sp('float', '0.0')) + type_eq('boolean', sp('boolean', '0')) + type_eq('nil', sp('nil', '0')) + end) + + it('works for regular values', function() + type_eq('binary', '""') + type_eq('array', '[]') + type_eq('map', '{}') + type_eq('integer', '1') + type_eq('float', '0.0') + type_eq('float', '(1.0/0.0)') + type_eq('float', '-(1.0/0.0)') + type_eq('float', '(1.0/0.0-1.0/0.0)') + end) + end) + + describe('function msgpack#special_type', function() + local sp_type_eq = function(expected, val) + eq(expected, nvim_eval(('msgpack#special_type(%s)'):format(val))) + end + + it('works for special dictionaries', function() + sp_type_eq('string', sp('string', '[""]')) + sp_type_eq('binary', sp('binary', '[""]')) + sp_type_eq('ext', sp('ext', '[1, [""]]')) + sp_type_eq('array', sp('array', '[]')) + sp_type_eq('map', sp('map', '[]')) + sp_type_eq('integer', sp('integer', '[1, 0, 0, 0]')) + sp_type_eq('float', sp('float', '0.0')) + sp_type_eq('boolean', sp('boolean', '0')) + sp_type_eq('nil', sp('nil', '0')) + end) + + it('works for regular values', function() + sp_type_eq(0, '""') + sp_type_eq(0, '[]') + sp_type_eq(0, '{}') + sp_type_eq(0, '1') + sp_type_eq(0, '0.0') + sp_type_eq(0, '(1.0/0.0)') + sp_type_eq(0, '-(1.0/0.0)') + sp_type_eq(0, '(1.0/0.0-1.0/0.0)') + end) + end) + + describe('function msgpack#string', function() + local string_eq = function(expected, val) + eq(expected, nvim_eval(('msgpack#string(%s)'):format(val))) + end + + it('works for special dictionaries', function() + string_eq('=""', sp('string', '[""]')) + string_eq('="\\n"', sp('string', '["", ""]')) + string_eq('="ab\\0c\\nde"', sp('string', '["ab\\nc", "de"]')) + string_eq('""', sp('binary', '[""]')) + string_eq('"\\n"', sp('binary', '["", ""]')) + string_eq('"ab\\0c\\nde"', sp('binary', '["ab\\nc", "de"]')) + string_eq('+(2)""', sp('ext', '[2, [""]]')) + string_eq('+(2)"\\n"', sp('ext', '[2, ["", ""]]')) + string_eq('+(2)"ab\\0c\\nde"', sp('ext', '[2, ["ab\\nc", "de"]]')) + string_eq('[]', sp('array', '[]')) + string_eq('[[[[{}]]]]', sp('array', '[[[[{}]]]]')) + string_eq('{}', sp('map', '[]')) + string_eq('{2: 10}', sp('map', '[[2, 10]]')) + string_eq('{{1: 1}: {1: 1}, {2: 1}: {1: 1}}', + mapsp(mapsp('2', '1'), mapsp('1', '1'), + mapsp('1', '1'), mapsp('1', '1'))) + string_eq('{{1: 1}: {1: 1}, {2: 1}: {1: 1}}', + mapsp(mapsp('1', '1'), mapsp('1', '1'), + mapsp('2', '1'), mapsp('1', '1'))) + string_eq('{[1, 2, {{1: 2}: 1}]: [1, 2, {{1: 2}: 1}]}', + mapsp(('[1, 2, %s]'):format(mapsp(mapsp('1', '2'), '1')), + ('[1, 2, %s]'):format(mapsp(mapsp('1', '2'), '1')))) + string_eq('0x0000000000000000', sp('integer', '[1, 0, 0, 0]')) + string_eq('-0x0000000100000000', sp('integer', '[-1, 0, 2, 0]')) + string_eq('0x123456789abcdef0', + sp('integer', '[ 1, 0, 610839793, 448585456]')) + string_eq('-0x123456789abcdef0', + sp('integer', '[-1, 0, 610839793, 448585456]')) + string_eq('0xf23456789abcdef0', + sp('integer', '[ 1, 3, 1684581617, 448585456]')) + string_eq('-0x723456789abcdef0', + sp('integer', '[-1, 1, 1684581617, 448585456]')) + string_eq('0.0', sp('float', '0.0')) + string_eq('inf', sp('float', '(1.0/0.0)')) + string_eq('-inf', sp('float', '-(1.0/0.0)')) + if has_minus_nan then + string_eq('-nan', sp('float', '(1.0/0.0-1.0/0.0)')) + end + string_eq('nan', sp('float', '-(1.0/0.0-1.0/0.0)')) + string_eq('FALSE', sp('boolean', '0')) + string_eq('TRUE', sp('boolean', '1')) + string_eq('NIL', sp('nil', '0')) + end) + + it('works for regular values', function() + string_eq('""', '""') + string_eq('"\\n"', '"\\n"') + string_eq('[]', '[]') + string_eq('[[[{}]]]', '[[[{}]]]') + string_eq('{}', '{}') + string_eq('{="2": 10}', '{2: 10}') + string_eq('{="2": [{}]}', '{2: [{}]}') + string_eq('1', '1') + string_eq('0.0', '0.0') + string_eq('inf', '(1.0/0.0)') + string_eq('-inf', '-(1.0/0.0)') + if has_minus_nan then + string_eq('-nan', '(1.0/0.0-1.0/0.0)') + end + string_eq('nan', '-(1.0/0.0-1.0/0.0)') + end) + end) + + describe('function msgpack#deepcopy', function() + it('works for special dictionaries', function() + nvim_command('let sparr = ' .. sp('array', '[[[]]]')) + nvim_command('let spmap = ' .. mapsp('"abc"', '[[]]')) + nvim_command('let spint = ' .. sp('integer', '[1, 0, 0, 0]')) + nvim_command('let spflt = ' .. sp('float', '1.0')) + nvim_command('let spext = ' .. sp('ext', '[2, ["abc", "def"]]')) + nvim_command('let spstr = ' .. sp('string', '["abc", "def"]')) + nvim_command('let spbin = ' .. sp('binary', '["abc", "def"]')) + nvim_command('let spbln = ' .. sp('boolean', '0')) + nvim_command('let spnil = ' .. sp('nil', '0')) + + nvim_command('let sparr2 = msgpack#deepcopy(sparr)') + nvim_command('let spmap2 = msgpack#deepcopy(spmap)') + nvim_command('let spint2 = msgpack#deepcopy(spint)') + nvim_command('let spflt2 = msgpack#deepcopy(spflt)') + nvim_command('let spext2 = msgpack#deepcopy(spext)') + nvim_command('let spstr2 = msgpack#deepcopy(spstr)') + nvim_command('let spbin2 = msgpack#deepcopy(spbin)') + nvim_command('let spbln2 = msgpack#deepcopy(spbln)') + nvim_command('let spnil2 = msgpack#deepcopy(spnil)') + + eq('array', nvim_eval('msgpack#type(sparr2)')) + eq('map', nvim_eval('msgpack#type(spmap2)')) + eq('integer', nvim_eval('msgpack#type(spint2)')) + eq('float', nvim_eval('msgpack#type(spflt2)')) + eq('ext', nvim_eval('msgpack#type(spext2)')) + eq('string', nvim_eval('msgpack#type(spstr2)')) + eq('binary', nvim_eval('msgpack#type(spbin2)')) + eq('boolean', nvim_eval('msgpack#type(spbln2)')) + eq('nil', nvim_eval('msgpack#type(spnil2)')) + + nvim_command('call add(sparr._VAL, 0)') + nvim_command('call add(sparr._VAL[0], 0)') + nvim_command('call add(sparr._VAL[0][0], 0)') + nvim_command('call add(spmap._VAL, [0, 0])') + nvim_command('call add(spmap._VAL[0][1], 0)') + nvim_command('call add(spmap._VAL[0][1][0], 0)') + nvim_command('let spint._VAL[1] = 1') + nvim_command('let spflt._VAL = 0.0') + nvim_command('let spext._VAL[0] = 3') + nvim_command('let spext._VAL[1][0] = "gh"') + nvim_command('let spstr._VAL[0] = "gh"') + nvim_command('let spbin._VAL[0] = "gh"') + nvim_command('let spbln._VAL = 1') + nvim_command('let spnil._VAL = 1') + + eq({_TYPE={}, _VAL={{{}}}}, nvim_eval('sparr2')) + eq({_TYPE={}, _VAL={{'abc', {{}}}}}, nvim_eval('spmap2')) + eq({_TYPE={}, _VAL={1, 0, 0, 0}}, nvim_eval('spint2')) + eq({_TYPE={}, _VAL=1.0}, nvim_eval('spflt2')) + eq({_TYPE={}, _VAL={2, {'abc', 'def'}}}, nvim_eval('spext2')) + eq({_TYPE={}, _VAL={'abc', 'def'}}, nvim_eval('spstr2')) + eq({_TYPE={}, _VAL={'abc', 'def'}}, nvim_eval('spbin2')) + eq({_TYPE={}, _VAL=0}, nvim_eval('spbln2')) + eq({_TYPE={}, _VAL=0}, nvim_eval('spnil2')) + + nvim_command('let sparr._TYPE = []') + nvim_command('let spmap._TYPE = []') + nvim_command('let spint._TYPE = []') + nvim_command('let spflt._TYPE = []') + nvim_command('let spext._TYPE = []') + nvim_command('let spstr._TYPE = []') + nvim_command('let spbin._TYPE = []') + nvim_command('let spbln._TYPE = []') + nvim_command('let spnil._TYPE = []') + + eq('array', nvim_eval('msgpack#special_type(sparr2)')) + eq('map', nvim_eval('msgpack#special_type(spmap2)')) + eq('integer', nvim_eval('msgpack#special_type(spint2)')) + eq('float', nvim_eval('msgpack#special_type(spflt2)')) + eq('ext', nvim_eval('msgpack#special_type(spext2)')) + eq('string', nvim_eval('msgpack#special_type(spstr2)')) + eq('binary', nvim_eval('msgpack#special_type(spbin2)')) + eq('boolean', nvim_eval('msgpack#special_type(spbln2)')) + eq('nil', nvim_eval('msgpack#special_type(spnil2)')) + end) + + it('works for regular values', function() + nvim_command('let arr = [[[]]]') + nvim_command('let map = {1: {}}') + nvim_command('let int = 1') + nvim_command('let flt = 2.0') + nvim_command('let bin = "abc"') + + nvim_command('let arr2 = msgpack#deepcopy(arr)') + nvim_command('let map2 = msgpack#deepcopy(map)') + nvim_command('let int2 = msgpack#deepcopy(int)') + nvim_command('let flt2 = msgpack#deepcopy(flt)') + nvim_command('let bin2 = msgpack#deepcopy(bin)') + + eq('array', nvim_eval('msgpack#type(arr2)')) + eq('map', nvim_eval('msgpack#type(map2)')) + eq('integer', nvim_eval('msgpack#type(int2)')) + eq('float', nvim_eval('msgpack#type(flt2)')) + eq('binary', nvim_eval('msgpack#type(bin2)')) + + nvim_command('call add(arr, 0)') + nvim_command('call add(arr[0], 0)') + nvim_command('call add(arr[0][0], 0)') + nvim_command('let map.a = 1') + nvim_command('let map.1.a = 1') + nvim_command('let int = 2') + nvim_command('let flt = 3.0') + nvim_command('let bin = ""') + + eq({{{}}}, nvim_eval('arr2')) + eq({['1']={}}, nvim_eval('map2')) + eq(1, nvim_eval('int2')) + eq(2.0, nvim_eval('flt2')) + eq('abc', nvim_eval('bin2')) + end) + end) + + describe('function msgpack#eval', function() + local eval_eq = function(expected_type, expected_val, str, ...) + nvim_command(('let g:__val = msgpack#eval(\'%s\', %s)'):format(str:gsub( + '\'', '\'\''), select(1, ...) or '{}')) + eq(expected_type, nvim_eval('msgpack#type(g:__val)')) + local expected_val_full = expected_val + if (not (({float=true, integer=true})[expected_type] + and type(expected_val) ~= 'table') + and expected_type ~= 'array') then + expected_val_full = {_TYPE={}, _VAL=expected_val_full} + end + if expected_val_full == expected_val_full then + eq(expected_val_full, nvim_eval('g:__val')) + else + eq(tostring(expected_val_full), tostring(nvim_eval('g:__val'))) + end + nvim_command('unlet g:__val') + end + + it('correctly loads binary strings', function() + eval_eq('binary', {'abcdef'}, '"abcdef"') + eval_eq('binary', {'abc', 'def'}, '"abc\\ndef"') + eval_eq('binary', {'abc\ndef'}, '"abc\\0def"') + eval_eq('binary', {'\nabc\ndef\n'}, '"\\0abc\\0def\\0"') + eval_eq('binary', {'abc\n\n\ndef'}, '"abc\\0\\0\\0def"') + eval_eq('binary', {'abc\n', '\ndef'}, '"abc\\0\\n\\0def"') + eval_eq('binary', {'abc', '', '', 'def'}, '"abc\\n\\n\\ndef"') + eval_eq('binary', {'abc', '', '', 'def', ''}, '"abc\\n\\n\\ndef\\n"') + eval_eq('binary', {'', 'abc', '', '', 'def'}, '"\\nabc\\n\\n\\ndef"') + eval_eq('binary', {''}, '""') + eval_eq('binary', {'"'}, '"\\""') + end) + + it('correctly loads strings', function() + eval_eq('string', {'abcdef'}, '="abcdef"') + eval_eq('string', {'abc', 'def'}, '="abc\\ndef"') + eval_eq('string', {'abc\ndef'}, '="abc\\0def"') + eval_eq('string', {'\nabc\ndef\n'}, '="\\0abc\\0def\\0"') + eval_eq('string', {'abc\n\n\ndef'}, '="abc\\0\\0\\0def"') + eval_eq('string', {'abc\n', '\ndef'}, '="abc\\0\\n\\0def"') + eval_eq('string', {'abc', '', '', 'def'}, '="abc\\n\\n\\ndef"') + eval_eq('string', {'abc', '', '', 'def', ''}, '="abc\\n\\n\\ndef\\n"') + eval_eq('string', {'', 'abc', '', '', 'def'}, '="\\nabc\\n\\n\\ndef"') + eval_eq('string', {''}, '=""') + eval_eq('string', {'"'}, '="\\""') + end) + + it('correctly loads ext values', function() + eval_eq('ext', {0, {'abcdef'}}, '+(0)"abcdef"') + eval_eq('ext', {0, {'abc', 'def'}}, '+(0)"abc\\ndef"') + eval_eq('ext', {0, {'abc\ndef'}}, '+(0)"abc\\0def"') + eval_eq('ext', {0, {'\nabc\ndef\n'}}, '+(0)"\\0abc\\0def\\0"') + eval_eq('ext', {0, {'abc\n\n\ndef'}}, '+(0)"abc\\0\\0\\0def"') + eval_eq('ext', {0, {'abc\n', '\ndef'}}, '+(0)"abc\\0\\n\\0def"') + eval_eq('ext', {0, {'abc', '', '', 'def'}}, '+(0)"abc\\n\\n\\ndef"') + eval_eq('ext', {0, {'abc', '', '', 'def', ''}}, + '+(0)"abc\\n\\n\\ndef\\n"') + eval_eq('ext', {0, {'', 'abc', '', '', 'def'}}, + '+(0)"\\nabc\\n\\n\\ndef"') + eval_eq('ext', {0, {''}}, '+(0)""') + eval_eq('ext', {0, {'"'}}, '+(0)"\\""') + + eval_eq('ext', {-1, {'abcdef'}}, '+(-1)"abcdef"') + eval_eq('ext', {-1, {'abc', 'def'}}, '+(-1)"abc\\ndef"') + eval_eq('ext', {-1, {'abc\ndef'}}, '+(-1)"abc\\0def"') + eval_eq('ext', {-1, {'\nabc\ndef\n'}}, '+(-1)"\\0abc\\0def\\0"') + eval_eq('ext', {-1, {'abc\n\n\ndef'}}, '+(-1)"abc\\0\\0\\0def"') + eval_eq('ext', {-1, {'abc\n', '\ndef'}}, '+(-1)"abc\\0\\n\\0def"') + eval_eq('ext', {-1, {'abc', '', '', 'def'}}, '+(-1)"abc\\n\\n\\ndef"') + eval_eq('ext', {-1, {'abc', '', '', 'def', ''}}, + '+(-1)"abc\\n\\n\\ndef\\n"') + eval_eq('ext', {-1, {'', 'abc', '', '', 'def'}}, + '+(-1)"\\nabc\\n\\n\\ndef"') + eval_eq('ext', {-1, {''}}, '+(-1)""') + eval_eq('ext', {-1, {'"'}}, '+(-1)"\\""') + end) + + it('correctly loads floats', function() + eval_eq('float', inf, 'inf') + eval_eq('float', minus_inf, '-inf') + eval_eq('float', nan, 'nan') + eval_eq('float', minus_nan, '-nan') + eval_eq('float', 1.0e10, '1.0e10') + eval_eq('float', 1.0e10, '1.0e+10') + eval_eq('float', -1.0e10, '-1.0e+10') + eval_eq('float', 1.0, '1.0') + eval_eq('float', -1.0, '-1.0') + eval_eq('float', 1.0e-10, '1.0e-10') + eval_eq('float', -1.0e-10, '-1.0e-10') + end) + + it('correctly loads integers', function() + eval_eq('integer', 10, '10') + eval_eq('integer', -10, '-10') + eval_eq('integer', { 1, 0, 610839793, 448585456}, ' 0x123456789ABCDEF0') + eval_eq('integer', {-1, 0, 610839793, 448585456}, '-0x123456789ABCDEF0') + eval_eq('integer', { 1, 3, 1684581617, 448585456}, ' 0xF23456789ABCDEF0') + eval_eq('integer', {-1, 1, 1684581617, 448585456}, '-0x723456789ABCDEF0') + eval_eq('integer', { 1, 0, 0, 0x100}, '0x100') + eval_eq('integer', {-1, 0, 0, 0x100}, '-0x100') + + eval_eq('integer', ('a'):byte(), '\'a\'') + eval_eq('integer', 0xAB, '\'«\'') + end) + + it('correctly loads constants', function() + eval_eq('boolean', 1, 'TRUE') + eval_eq('boolean', 0, 'FALSE') + eval_eq('nil', 0, 'NIL') + eval_eq('nil', 0, 'NIL', '{"NIL": 1, "nan": 2, "T": 3}') + eval_eq('float', nan, 'nan', + '{"NIL": "1", "nan": "2", "T": "3"}') + eval_eq('integer', 3, 'T', '{"NIL": "1", "nan": "2", "T": "3"}') + eval_eq('integer', {1, 0, 0, 0}, 'T', + ('{"NIL": "1", "nan": "2", "T": \'%s\'}'):format( + sp('integer', '[1, 0, 0, 0]'))) + end) + + it('correctly loads maps', function() + eval_eq('map', {}, '{}') + eval_eq('map', {{{_TYPE={}, _VAL={{1, 2}}}, {_TYPE={}, _VAL={{3, 4}}}}}, + '{{1: 2}: {3: 4}}') + eval_eq('map', {{{_TYPE={}, _VAL={{1, 2}}}, {_TYPE={}, _VAL={{3, 4}}}}, + {1, 2}}, + '{{1: 2}: {3: 4}, 1: 2}') + end) + + it('correctly loads arrays', function() + eval_eq('array', {}, '[]') + eval_eq('array', {1}, '[1]') + eval_eq('array', {{_TYPE={}, _VAL=1}}, '[TRUE]') + eval_eq('array', {{{_TYPE={}, _VAL={{1, 2}}}}, {_TYPE={}, _VAL={{3, 4}}}}, + '[[{1: 2}], {3: 4}]') + end) + + it('errors out when needed', function() + eq('empty:Parsed string is empty', + exc_exec('call msgpack#eval("", {})')) + eq('unknown:Invalid non-space character: ^', + exc_exec('call msgpack#eval("^", {})')) + eq('char-invalid:Invalid integer character literal format: \'\'', + exc_exec('call msgpack#eval("\'\'", {})')) + eq('char-invalid:Invalid integer character literal format: \'ab\'', + exc_exec('call msgpack#eval("\'ab\'", {})')) + eq('char-invalid:Invalid integer character literal format: \'', + exc_exec('call msgpack#eval("\'", {})')) + eq('"-invalid:Invalid string: "', + exc_exec('call msgpack#eval("\\"", {})')) + eq('"-invalid:Invalid string: ="', + exc_exec('call msgpack#eval("=\\"", {})')) + eq('"-invalid:Invalid string: +(0)"', + exc_exec('call msgpack#eval("+(0)\\"", {})')) + eq('0.-nodigits:Decimal dot must be followed by digit(s): .e1', + exc_exec('call msgpack#eval("0.e1", {})')) + eq('0x-long:Must have at most 16 hex digits: FEDCBA98765432100', + exc_exec('call msgpack#eval("0xFEDCBA98765432100", {})')) + eq('0x-empty:Must have number after 0x: ', + exc_exec('call msgpack#eval("0x", {})')) + eq('name-unknown:Unknown name FOO: FOO', + exc_exec('call msgpack#eval("FOO", {})')) + end) + end) +end) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua new file mode 100644 index 0000000000..407f13a55b --- /dev/null +++ b/test/functional/plugin/shada_spec.lua @@ -0,0 +1,2833 @@ +local helpers = require('test.functional.helpers') +local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = + helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, + helpers.funcs, helpers.feed, helpers.curbuf +local neq = helpers.neq + +local msgpack = require('MessagePack') + +local plugin_helpers = require('test.functional.plugin.helpers') +local reset = plugin_helpers.reset + +local shada_helpers = require('test.functional.shada.helpers') +local get_shada_rw = shada_helpers.get_shada_rw +local read_shada_file = shada_helpers.read_shada_file + +local mpack_eq = function(expected, mpack_result) + 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 + +local wshada, _, fname = get_shada_rw('Xtest-functional-plugin-shada.shada') + +local wshada_tmp, _, fname_tmp = + get_shada_rw('Xtest-functional-plugin-shada.shada.tmp.f') + +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') + return mpack_eq(expected, mpack_result) + 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) + +describe('In plugin/shada.vim', function() + local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + before_each(function() + reset() + os.remove(fname) + os.remove(fname .. '.tst') + os.remove(fname_tmp) + end) + + local shada_eq = function(expected, fname) + local fd = io.open(fname) + local mpack_result = fd:read('*a') + fd:close() + mpack_eq(expected, mpack_result) + end + + describe('event BufReadCmd', function() + it('works', function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + wshada_tmp('\004\000\009\147\000\196\002ab\196\001b') + nvim_command('edit ' .. fname) + eq({ + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "a"', + }, nvim_eval('getline(1, "$")')) + eq(false, curbuf('get_option', 'modified')) + eq('shada', curbuf('get_option', 'filetype')) + nvim_command('edit ' .. fname_tmp) + eq({ + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "b"', + }, nvim_eval('getline(1, "$")')) + eq(false, curbuf('get_option', 'modified')) + eq('shada', curbuf('get_option', 'filetype')) + eq('++opt not supported', exc_exec('edit ++enc=latin1 ' .. fname)) + neq({ + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "a"', + }, nvim_eval('getline(1, "$")')) + neq(true, curbuf('get_option', 'modified')) + end) + end) + + describe('event FileReadCmd', function() + it('works', function() + wshada('\004\000\009\147\000\196\002ab\196\001a') + wshada_tmp('\004\000\009\147\000\196\002ab\196\001b') + nvim_command('$read ' .. fname) + eq({ + '', + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "a"', + }, nvim_eval('getline(1, "$")')) + eq(true, curbuf('get_option', 'modified')) + neq('shada', curbuf('get_option', 'filetype')) + nvim_command('1,$read ' .. fname_tmp) + eq({ + '', + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "a"', + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "b"', + }, nvim_eval('getline(1, "$")')) + eq(true, curbuf('get_option', 'modified')) + neq('shada', curbuf('get_option', 'filetype')) + curbuf('set_option', 'modified', false) + eq('++opt not supported', exc_exec('$read ++enc=latin1 ' .. fname)) + eq({ + '', + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "a"', + 'History entry with timestamp ' .. epoch .. ':', + ' @ Description_ Value', + ' - history type CMD', + ' - contents "ab"', + ' - "b"', + }, nvim_eval('getline(1, "$")')) + neq(true, curbuf('get_option', 'modified')) + end) + end) + + describe('event BufWriteCmd', function() + it('works', function() + nvim('set_var', 'shada#add_own_header', 0) + curbuf('set_line_slice', 0, 0, true, true, { + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + }) + nvim_command('w ' .. fname .. '.tst') + nvim_command('w ' .. fname) + nvim_command('w ' .. fname_tmp) + eq('++opt not supported', exc_exec('w! ++enc=latin1 ' .. fname)) + eq(table.concat({ + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + shada_eq({{ + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }}, fname) + shada_eq({{ + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }}, fname_tmp) + end) + end) + + describe('event FileWriteCmd', function() + it('works', function() + nvim('set_var', 'shada#add_own_header', 0) + curbuf('set_line_slice', 0, 0, true, true, { + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + }) + nvim_command('1,3w ' .. fname .. '.tst') + nvim_command('1,3w ' .. fname) + nvim_command('1,3w ' .. fname_tmp) + eq('++opt not supported', exc_exec('1,3w! ++enc=latin1 ' .. fname)) + eq(table.concat({ + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + shada_eq({{ + timestamp=0, + type=8, + value={n=('A'):byte()}, + }}, fname) + shada_eq({{ + timestamp=0, + type=8, + value={n=('A'):byte()}, + }}, fname_tmp) + end) + end) + + describe('event FileAppendCmd', function() + it('works', function() + nvim('set_var', 'shada#add_own_header', 0) + curbuf('set_line_slice', 0, 0, true, true, { + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + }) + funcs.writefile({''}, fname .. '.tst', 'b') + funcs.writefile({''}, fname, 'b') + funcs.writefile({''}, fname_tmp, 'b') + nvim_command('1,3w >> ' .. fname .. '.tst') + nvim_command('1,3w >> ' .. fname) + nvim_command('1,3w >> ' .. fname_tmp) + nvim_command('w >> ' .. fname .. '.tst') + nvim_command('w >> ' .. fname) + nvim_command('w >> ' .. fname_tmp) + eq('++opt not supported', exc_exec('1,3w! ++enc=latin1 >> ' .. fname)) + eq(table.concat({ + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + }, '\n') .. '\n', io.open(fname .. '.tst'):read('*a')) + shada_eq({{ + timestamp=0, + type=8, + value={n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }}, fname) + shada_eq({{ + timestamp=0, + type=8, + value={n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }, { + timestamp=0, + type=8, + value={c=-200, f={'foo'}, l=2, n=('A'):byte()}, + }}, fname_tmp) + end) + end) + + describe('event SourceCmd', function() + before_each(function() + reset(fname) + end) + it('works', function() + wshada('\004\000\006\146\000\196\002ab') + wshada_tmp('\004\001\006\146\000\196\002bc') + eq(0, exc_exec('source ' .. fname)) + eq(0, exc_exec('source ' .. fname_tmp)) + eq('bc', funcs.histget(':', -1)) + eq('ab', funcs.histget(':', -2)) + end) + end) +end) + +describe('ftplugin/shada.vim', function() + local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + before_each(reset) + + it('sets indentexpr correctly', function() + nvim_command('filetype plugin indent on') + nvim_command('setlocal filetype=shada') + funcs.setline(1, { + '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', + 'Register with timestamp ' .. epoch .. ':', + '% Key Description Value', + '+ n name \' \'', + '+ rc contents @', + '| - "abcdefghijklmnopqrstuvwxyz"', + '| - "abcdefghijklmnopqrstuvwxyz"', + '+ rw block width 0', + '+ rt type CHARACTERWISE', + 'Replacement string with timestamp ' .. epoch .. ':', + ' @ Description__________ Value', + ' - :s replacement string "abc\\ndef"', + ' Buffer list with timestamp ' .. epoch .. ':', + ' # Expected array of maps', + '= [{="a": 10}, []]', + ' 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', + }) + nvim_command('normal! gg=G') + eq({ + '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', + 'Register with timestamp ' .. epoch .. ':', + ' % Key Description Value', + ' + n name \' \'', + ' + rc contents @', + ' | - "abcdefghijklmnopqrstuvwxyz"', + ' | - "abcdefghijklmnopqrstuvwxyz"', + ' + rw block width 0', + ' + rt type CHARACTERWISE', + 'Replacement string with timestamp ' .. epoch .. ':', + ' @ Description__________ Value', + ' - :s replacement string "abc\\ndef"', + 'Buffer list with timestamp ' .. epoch .. ':', + ' # Expected array of maps', + ' = [{="a": 10}, []]', + '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', + }, funcs.getline(1, funcs.line('$'))) + end) + + it('sets options correctly', function() + nvim_command('filetype plugin indent on') + nvim_command('setlocal filetype=shada') + eq(true, curbuf('get_option', 'expandtab')) + eq(2, curbuf('get_option', 'tabstop')) + eq(2, curbuf('get_option', 'softtabstop')) + eq(2, curbuf('get_option', 'shiftwidth')) + end) + + it('sets indentkeys correctly', function() + nvim_command('filetype plugin indent on') + nvim_command('setlocal filetype=shada') + funcs.setline(1, ' Replacement with timestamp ' .. epoch) + nvim_feed('ggA:\027') + eq('Replacement with timestamp ' .. epoch .. ':', curbuf('get_line', 0)) + nvim_feed('o-\027') + eq(' -', curbuf('get_line', 1)) + nvim_feed('ggO+\027') + eq('+', curbuf('get_line', 0)) + nvim_feed('GO*\027') + eq(' *', curbuf('get_line', 2)) + nvim_feed('ggO /\027') + eq(' /', curbuf('get_line', 0)) + nvim_feed('ggOx\027') + eq('x', curbuf('get_line', 0)) + end) +end) + +describe('syntax/shada.vim', function() + local epoch = os.date('%Y-%m-%dT%H:%M:%S', 0) + before_each(reset) + + it('works', function() + nvim_command('syntax on') + nvim_command('setlocal syntax=shada') + curbuf('set_line_slice', 0, 0, true, true, { + 'Header with timestamp ' .. epoch .. ':', + ' % Key Value', + ' + t "test"', + 'Jump with timestamp ' .. epoch .. ':', + ' % Key________ Description Value', + ' + n name \'A\'', + ' + f file name ["foo"]', + ' + l line number 2', + ' + c column -200', + 'Register with timestamp ' .. epoch .. ':', + ' % Key Description Value', + ' + rc contents @', + ' | - {"abcdefghijklmnopqrstuvwxyz": 1.0}', + ' + rt type CHARACTERWISE', + ' + rt type LINEWISE', + ' + rt type BLOCKWISE', + 'Replacement string with timestamp ' .. epoch .. ':', + ' @ Description__________ Value', + ' - :s replacement string CMD', + ' - :s replacement string SEARCH', + ' - :s replacement string EXPR', + ' - :s replacement string INPUT', + ' - :s replacement string DEBUG', + 'Buffer list with timestamp ' .. epoch .. ':', + ' # Expected array of maps', + ' = [{="a": +(10)"ac\\0df\\ngi\\"tt\\.", TRUE: FALSE}, [NIL, +(-10)""]]', + 'Buffer list with timestamp ' .. epoch .. ':', + ' % Key Description Value', + '', + ' % Key Description Value', + 'Header with timestamp ' .. epoch .. ':', + ' % Key Description________ Value', + ' + se place cursor at end TRUE', + }) + nvim_command([[ + function GetSyntax() + let lines = [] + for l in range(1, line('$')) + let columns = [] + let line = getline(l) + for c in range(1, col([l, '$']) - 1) + let synstack = map(synstack(l, c), 'synIDattr(v:val, "name")') + if !empty(columns) && columns[-1][0] ==# synstack + let columns[-1][1] .= line[c - 1] + else + call add(columns, [ synstack, line[c - 1] ]) + endif + endfor + call add(lines, columns) + endfor + return lines + endfunction + ]]) + local hname = function(s) return {{'ShaDaEntryHeader', 'ShaDaEntryName'}, + s} end + local h = function(s) return {{'ShaDaEntryHeader'}, s} end + local htsnum = function(s) return { + {'ShaDaEntryHeader', 'ShaDaEntryTimestamp', 'ShaDaEntryTimestampNumber'}, + s + } end + local synhtssep = function(s) + return {{'ShaDaEntryHeader', 'ShaDaEntryTimestamp'}, s} + end + local synepoch = { + year = htsnum(os.date('%Y', 0)), + month = htsnum(os.date('%m', 0)), + day = htsnum(os.date('%d', 0)), + hour = htsnum(os.date('%H', 0)), + minute = htsnum(os.date('%M', 0)), + second = htsnum(os.date('%S', 0)), + } + local msh = function(s) return {{'ShaDaEntryMapShort', + 'ShaDaEntryMapHeader'}, s} end + local mlh = function(s) return {{'ShaDaEntryMapLong', + 'ShaDaEntryMapHeader'}, s} end + local ah = function(s) return {{'ShaDaEntryArray', + 'ShaDaEntryArrayHeader'}, s} end + local mses = function(s) return {{'ShaDaEntryMapShort', + 'ShaDaEntryMapShortEntryStart'}, s} end + local mles = function(s) return {{'ShaDaEntryMapLong', + 'ShaDaEntryMapLongEntryStart'}, s} end + local act = funcs.GetSyntax() + local ms = function(syn) + return { + {'ShaDaEntryMap' .. syn, 'ShaDaEntryMap' .. syn .. 'EntryStart'}, ' + ' + } + end + local as = function() + return {{'ShaDaEntryArray', 'ShaDaEntryArrayEntryStart'}, ' - '} + end + local ad = function(s) return {{'ShaDaEntryArray', + 'ShaDaEntryArrayDescription'}, s} end + local mbas = function(syn) + return { + {'ShaDaEntryMap' .. syn, 'ShaDaEntryMapBinArrayStart'}, + ' | - ' + } + end + local msk = function(s) return {{'ShaDaEntryMapShort', + 'ShaDaEntryMapShortKey'}, s} end + local mlk = function(s) return {{'ShaDaEntryMapLong', + 'ShaDaEntryMapLongKey'}, s} end + local mld = function(s) return {{'ShaDaEntryMapLong', + 'ShaDaEntryMapLongDescription'}, s} end + local c = function(s) return {{'ShaDaComment'}, s} end + local exp = { + { + hname('Header'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + msh(' % Key Value'), + }, + { + ms('Short'), msk('t '), + {{'ShaDaEntryMapShort', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaEntryMapShort', 'ShaDaMsgpackBinaryString'}, 'test'}, + {{'ShaDaEntryMapShort', 'ShaDaMsgpackStringQuotes'}, '"'}, + }, + { + hname('Jump'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + mlh(' % Key________ Description Value'), + }, + { + ms('Long'), mlk('n '), mld('name '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackCharacter'}, '\'A\''}, + }, + { + ms('Long'), mlk('f '), mld('file name '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackArray', + 'ShaDaMsgpackArrayBraces'}, '['}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackArray', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackArray', 'ShaDaMsgpackBinaryString'}, + 'foo'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackArray', 'ShaDaMsgpackStringQuotes'}, + '"'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackArrayBraces'}, ']'}, + }, + { + ms('Long'), mlk('l '), mld('line number '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackInteger'}, '2'}, + }, + { + ms('Long'), mlk('c '), mld('column '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackInteger'}, '-200'}, + }, + { + hname('Register'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + mlh(' % Key Description Value'), + }, + { + ms('Long'), mlk('rc '), mld('contents '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMultilineArray'}, '@'}, + }, + { + mbas('Long'), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackMapBraces'}, + '{'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'abcdefghijklmnopqrstuvwxyz'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackStringQuotes'}, + '"'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackColon'}, ':'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap'}, ' '}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMap', 'ShaDaMsgpackFloat'}, '1.0'}, + {{'ShaDaEntryMapLong', 'ShaDaMsgpackMapBraces'}, '}'}, + }, + { + ms('Long'), mlk('rt '), mld('type '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackShaDaKeyword'}, 'CHARACTERWISE'}, + }, + { + ms('Long'), mlk('rt '), mld('type '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackShaDaKeyword'}, 'LINEWISE'}, + }, + { + ms('Long'), mlk('rt '), mld('type '), + {{'ShaDaEntryMapLong', 'ShaDaMsgpackShaDaKeyword'}, 'BLOCKWISE'}, + }, + { + hname('Replacement string'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + ah(' @ Description__________ Value'), + }, + { + as(), ad(':s replacement string '), + {{'ShaDaEntryArray', 'ShaDaMsgpackShaDaKeyword'}, 'CMD'}, + }, + { + as(), ad(':s replacement string '), + {{'ShaDaEntryArray', 'ShaDaMsgpackShaDaKeyword'}, 'SEARCH'}, + }, + { + as(), ad(':s replacement string '), + {{'ShaDaEntryArray', 'ShaDaMsgpackShaDaKeyword'}, 'EXPR'}, + }, + { + as(), ad(':s replacement string '), + {{'ShaDaEntryArray', 'ShaDaMsgpackShaDaKeyword'}, 'INPUT'}, + }, + { + {{'ShaDaEntryArrayEntryStart'}, ' - '}, + {{'ShaDaEntryArrayDescription'}, ':s replacement string '}, + {{'ShaDaMsgpackShaDaKeyword'}, 'DEBUG'}, + }, + { + hname('Buffer list'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + c(' # Expected array of maps'), + }, + { + {{'ShaDaEntryRawMsgpack'}, ' = '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArrayBraces'}, '['}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackMapBraces'}, + '{'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackString'}, '='}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'a'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackStringQuotes'}, + '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackColon'}, ':'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap'}, ' '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackExt'}, '+('}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackExt', + 'ShaDaMsgpackExtType'}, '10'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackExt'}, ')'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'ac'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackBinaryStringEscape'}, + '\\0'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'df'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackBinaryStringEscape'}, + '\\n'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'gi'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackBinaryStringEscape'}, + '\\"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackBinaryString'}, + 'tt\\.'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackStringQuotes'}, + '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackComma'}, ','}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap'}, ' '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackKeyword'}, + 'TRUE'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackColon'}, ':'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap'}, ' '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackKeyword'}, + 'FALSE'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackMapBraces'}, '}'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackComma'}, ','}, + {{'ShaDaMsgpackArray'}, ' '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackArrayBraces'}, + '['}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackKeyword'}, + 'NIL'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackComma'}, ','}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray'}, ' '}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackExt'}, '+('}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackExt', + 'ShaDaMsgpackExtType'}, '-10'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackExt'}, ')'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackBinaryString', + 'ShaDaMsgpackStringQuotes'}, '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArray', 'ShaDaMsgpackStringQuotes'}, + '"'}, + {{'ShaDaMsgpackArray', 'ShaDaMsgpackArrayBraces'}, ']'}, + {{'ShaDaMsgpackArrayBraces'}, ']'}, + }, + { + hname('Buffer list'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + mlh(' % Key Description Value'), + }, + { + }, + { + mlh(' % Key Description Value'), + }, + { + hname('Header'), h(' with timestamp '), + synepoch.year, synhtssep('-'), synepoch.month, synhtssep('-'), + synepoch.day, synhtssep('T'), synepoch.hour, synhtssep(':'), + synepoch.minute, synhtssep(':'), synepoch.second, h(':'), + }, + { + mlh(' % Key Description________ Value'), + }, + { + {{'ShaDaEntryMapLongEntryStart'}, ' + '}, + {{'ShaDaEntryMapLongKey'}, 'se '}, + {{'ShaDaEntryMapLongDescription'}, 'place cursor at end '}, + {{'ShaDaMsgpackKeyword'}, 'TRUE'}, + }, + } + eq(exp, act) + end) +end) |