aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelipe Morales <hel.sheep@gmail.com>2015-11-23 00:27:18 +0100
committerFelipe Morales <hel.sheep@gmail.com>2015-11-23 00:27:18 +0100
commit321db59ca1dc304feb3e00c10ca3e89c1de616e7 (patch)
tree825ba69b12a717faf011dc1b828438b5ea70c31d
parent1fbb56795d16783f9a97e25e3b71ab3ac2a644dc (diff)
parente773ffe8094041dba1b9b258dfa45104dc321522 (diff)
downloadrneovim-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.vim820
-rw-r--r--runtime/autoload/shada.vim694
-rw-r--r--runtime/doc/filetype.txt143
-rw-r--r--runtime/doc/pi_msgpack.txt139
-rw-r--r--runtime/ftplugin/shada.vim18
-rw-r--r--runtime/plugin/shada.vim39
-rw-r--r--runtime/syntax/shada.vim125
-rw-r--r--test/functional/plugin/helpers.lua41
-rw-r--r--test/functional/plugin/msgpack_spec.lua699
-rw-r--r--test/functional/plugin/shada_spec.lua2833
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)