shada.vim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. if exists('g:loaded_shada_autoload')
  2. finish
  3. endif
  4. let g:loaded_shada_autoload = 1
  5. ""
  6. " If true keep the old header entry when editing existing ShaDa file.
  7. "
  8. " Old header entry will be kept only if it is listed in the opened file. To
  9. " remove old header entry despite of the setting just remove it from the
  10. " listing. Setting it to false makes plugin ignore all header entries. Defaults
  11. " to 1.
  12. let g:shada#keep_old_header = get(g:, 'shada#keep_old_header', 1)
  13. ""
  14. " If true then first entry will be plugin’s own header entry.
  15. let g:shada#add_own_header = get(g:, 'shada#add_own_header', 1)
  16. ""
  17. " Dictionary that maps ShaDa types to their names.
  18. let s:SHADA_ENTRY_NAMES = {
  19. \1: 'header',
  20. \2: 'search_pattern',
  21. \3: 'replacement_string',
  22. \4: 'history_entry',
  23. \5: 'register',
  24. \6: 'variable',
  25. \7: 'global_mark',
  26. \8: 'jump',
  27. \9: 'buffer_list',
  28. \10: 'local_mark',
  29. \11: 'change',
  30. \}
  31. ""
  32. " Dictionary that maps ShaDa names to corresponding types
  33. let s:SHADA_ENTRY_TYPES = {}
  34. call map(copy(s:SHADA_ENTRY_NAMES),
  35. \'extend(s:SHADA_ENTRY_TYPES, {v:val : +v:key})')
  36. ""
  37. " Map that maps entry names to lists of keys that can be used by this entry.
  38. " Only contains data for entries which are represented as mappings, except for
  39. " the header.
  40. let s:SHADA_MAP_ENTRIES = {
  41. \'search_pattern': ['sp', 'sh', 'ss', 'sb', 'sm', 'sc', 'sl', 'se', 'so',
  42. \ 'su'],
  43. \'register': ['n', 'rc', 'rw', 'rt', 'ru'],
  44. \'global_mark': ['n', 'f', 'l', 'c'],
  45. \'local_mark': ['f', 'n', 'l', 'c'],
  46. \'jump': ['f', 'l', 'c'],
  47. \'change': ['f', 'l', 'c'],
  48. \'header': [],
  49. \}
  50. ""
  51. " Like one of the values from s:SHADA_MAP_ENTRIES, but for a single buffer in
  52. " buffer list entry.
  53. let s:SHADA_BUFFER_LIST_KEYS = ['f', 'l', 'c']
  54. ""
  55. " List of possible history types. Maps integer values that represent history
  56. " types to human-readable names.
  57. let s:SHADA_HISTORY_TYPES = ['command', 'search', 'expression', 'input', 'debug']
  58. ""
  59. " Map that maps entry names to their descriptions. Only for entries which have
  60. " list as a data type. Description is a list of lists where each entry has item
  61. " description and item type.
  62. let s:SHADA_FIXED_ARRAY_ENTRIES = {
  63. \'replacement_string': [[':s replacement string', 'bin']],
  64. \'history_entry': [
  65. \['history type', 'histtype'],
  66. \['contents', 'bin'],
  67. \['separator', 'intchar'],
  68. \],
  69. \'variable': [['name', 'bin'], ['value', 'any']],
  70. \}
  71. ""
  72. " Dictionary that maps enum names to dictionary with enum values. Dictionary
  73. " with enum values maps enum human-readable names to corresponding values. Enums
  74. " are used as type names in s:SHADA_FIXED_ARRAY_ENTRIES and
  75. " s:SHADA_STANDARD_KEYS.
  76. let s:SHADA_ENUMS = {
  77. \'histtype': {
  78. \'CMD': 0,
  79. \'SEARCH': 1,
  80. \'EXPR': 2,
  81. \'INPUT': 3,
  82. \'DEBUG': 4,
  83. \},
  84. \'regtype': {
  85. \'CHARACTERWISE': 0,
  86. \'LINEWISE': 1,
  87. \'BLOCKWISE': 2,
  88. \}
  89. \}
  90. ""
  91. " Second argument to msgpack#eval.
  92. let s:SHADA_SPECIAL_OBJS = {}
  93. call map(values(s:SHADA_ENUMS),
  94. \'extend(s:SHADA_SPECIAL_OBJS, map(copy(v:val), "string(v:val)"))')
  95. ""
  96. " Like s:SHADA_ENUMS, but inner dictionary maps values to names and not names to
  97. " values.
  98. let s:SHADA_REV_ENUMS = map(copy(s:SHADA_ENUMS), '{}')
  99. call map(copy(s:SHADA_ENUMS),
  100. \'map(copy(v:val), '
  101. \. '"extend(s:SHADA_REV_ENUMS[" . string(v:key) . "], '
  102. \. '{v:val : v:key})")')
  103. ""
  104. " Maximum length of ShaDa entry name. Used to arrange entries to the table.
  105. let s:SHADA_MAX_ENTRY_LENGTH = max(
  106. \map(values(s:SHADA_ENTRY_NAMES), 'len(v:val)')
  107. \+ [len('unknown (0x)') + 16])
  108. ""
  109. " Object that marks required value.
  110. let s:SHADA_REQUIRED = []
  111. ""
  112. " Dictionary that maps default key names to their description. Description is
  113. " a list that contains human-readable hint, key type and default value.
  114. let s:SHADA_STANDARD_KEYS = {
  115. \'sm': ['magic value', 'boolean', g:msgpack#true],
  116. \'sc': ['smartcase value', 'boolean', g:msgpack#false],
  117. \'sl': ['has line offset', 'boolean', g:msgpack#false],
  118. \'se': ['place cursor at end', 'boolean', g:msgpack#false],
  119. \'so': ['offset value', 'integer', 0],
  120. \'su': ['is last used', 'boolean', g:msgpack#true],
  121. \'ss': ['is :s pattern', 'boolean', g:msgpack#false],
  122. \'sh': ['v:hlsearch value', 'boolean', g:msgpack#false],
  123. \'sp': ['pattern', 'bin', s:SHADA_REQUIRED],
  124. \'sb': ['search backward', 'boolean', g:msgpack#false],
  125. \'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE],
  126. \'rw': ['block width', 'uint', 0],
  127. \'rc': ['contents', 'binarray', s:SHADA_REQUIRED],
  128. \'ru': ['is_unnamed', 'boolean', g:msgpack#false],
  129. \'n': ['name', 'intchar', char2nr('"')],
  130. \'l': ['line number', 'uint', 1],
  131. \'c': ['column', 'uint', 0],
  132. \'f': ['file name', 'bin', s:SHADA_REQUIRED],
  133. \}
  134. ""
  135. " Set of entry types containing entries which require `n` key.
  136. let s:SHADA_REQUIRES_NAME = {'local_mark': 1, 'global_mark': 1, 'register': 1}
  137. ""
  138. " Maximum width of human-readable hint. Used to arrange data in table.
  139. let s:SHADA_MAX_HINT_WIDTH = max(map(values(s:SHADA_STANDARD_KEYS),
  140. \'len(v:val[0])'))
  141. ""
  142. " Default mark name for the cases when it makes sense (i.e. for local marks).
  143. let s:SHADA_DEFAULT_MARK_NAME = '"'
  144. ""
  145. " Mapping that maps timestamps represented using msgpack#string to strftime
  146. " output. Used by s:shada_strftime.
  147. let s:shada_strftime_cache = {}
  148. ""
  149. " Mapping that maps strftime output from s:shada_strftime to timestamps.
  150. let s:shada_strptime_cache = {}
  151. ""
  152. " Time format used for displaying ShaDa files.
  153. let s:SHADA_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
  154. ""
  155. " Wrapper around msgpack#strftime that caches its output.
  156. "
  157. " Format is hardcoded to s:SHADA_TIME_FORMAT.
  158. function s:shada_strftime(timestamp) abort
  159. let key = msgpack#string(a:timestamp)
  160. if has_key(s:shada_strftime_cache, key)
  161. return s:shada_strftime_cache[key]
  162. endif
  163. let val = msgpack#strftime(s:SHADA_TIME_FORMAT, a:timestamp)
  164. let s:shada_strftime_cache[key] = val
  165. let s:shada_strptime_cache[val] = a:timestamp
  166. return val
  167. endfunction
  168. ""
  169. " Wrapper around msgpack#strftime that uses cache created by s:shada_strftime().
  170. "
  171. " Also caches its own results. Format is hardcoded to s:SHADA_TIME_FORMAT.
  172. function s:shada_strptime(string) abort
  173. if has_key(s:shada_strptime_cache, a:string)
  174. return s:shada_strptime_cache[a:string]
  175. endif
  176. let ts = msgpack#strptime(s:SHADA_TIME_FORMAT, a:string)
  177. let s:shada_strptime_cache[a:string] = ts
  178. return ts
  179. endfunction
  180. ""
  181. " Check whether given value matches given type.
  182. "
  183. " @return Zero if value matches, error message string if it does not.
  184. function s:shada_check_type(type, val) abort
  185. let type = msgpack#type(a:val)
  186. if type is# a:type
  187. return 0
  188. endif
  189. if has_key(s:SHADA_ENUMS, a:type)
  190. let msg = s:shada_check_type('uint', a:val)
  191. if msg isnot 0
  192. return msg
  193. endif
  194. if !has_key(s:SHADA_REV_ENUMS[a:type], a:val)
  195. let evals_msg = join(map(sort(items(s:SHADA_REV_ENUMS[a:type])),
  196. \'v:val[0] . " (" . v:val[1] . ")"'), ', ')
  197. return 'Unexpected enum value: expected one of ' . evals_msg
  198. endif
  199. return 0
  200. elseif a:type is# 'uint'
  201. if type isnot# 'integer'
  202. return 'Expected integer'
  203. endif
  204. if !(type(a:val) == type({}) ? a:val._VAL[0] == 1 : a:val >= 0)
  205. return 'Value is negative'
  206. endif
  207. return 0
  208. elseif a:type is# 'bin'
  209. " Binary string without zero bytes
  210. if type isnot# 'binary'
  211. return 'Expected binary string'
  212. elseif (type(a:val) == type({})
  213. \&& !empty(filter(copy(a:val._VAL), 'stridx(v:val, "\n") != -1')))
  214. return 'Expected no NUL bytes'
  215. endif
  216. return 0
  217. elseif a:type is# 'intchar'
  218. let msg = s:shada_check_type('uint', a:val)
  219. if msg isnot# 0
  220. return msg
  221. endif
  222. return 0
  223. elseif a:type is# 'binarray'
  224. if type isnot# 'array'
  225. return 'Expected array value'
  226. elseif !empty(filter(copy(type(a:val) == type({}) ? a:val._VAL : a:val),
  227. \'msgpack#type(v:val) isnot# "binary"'))
  228. return 'Expected array of binary strings'
  229. else
  230. for element in (type(a:val) == type({}) ? a:val._VAL : a:val)
  231. if (type(element) == type({})
  232. \&& !empty(filter(copy(element._VAL), 'stridx(v:val, "\n") != -1')))
  233. return 'Expected no NUL bytes'
  234. endif
  235. unlet element
  236. endfor
  237. endif
  238. return 0
  239. elseif a:type is# 'boolean'
  240. return 'Expected boolean'
  241. elseif a:type is# 'integer'
  242. return 'Expected integer'
  243. elseif a:type is# 'any'
  244. return 0
  245. endif
  246. return 'Internal error: unknown type ' . a:type
  247. endfunction
  248. ""
  249. " Convert msgpack mapping object to a list of strings for
  250. " s:shada_convert_entry().
  251. "
  252. " @param[in] map Mapping to convert.
  253. " @param[in] default_keys List of keys which have default value in this
  254. " mapping.
  255. " @param[in] name Name of the converted entry.
  256. function s:shada_convert_map(map, default_keys, name) abort
  257. let ret = []
  258. let keys = copy(a:default_keys)
  259. call map(sort(keys(a:map)), 'index(keys, v:val) == -1 ? add(keys, v:val) : 0')
  260. let descriptions = map(copy(keys),
  261. \'get(s:SHADA_STANDARD_KEYS, v:val, ["", 0, 0])')
  262. let max_key_len = max(map(copy(keys), 'len(v:val)'))
  263. let max_desc_len = max(map(copy(descriptions),
  264. \'v:val[0] is 0 ? 0 : len(v:val[0])'))
  265. if max_key_len < len('Key')
  266. let max_key_len = len('Key')
  267. endif
  268. let key_header = 'Key' . repeat('_', max_key_len - len('Key'))
  269. if max_desc_len == 0
  270. call add(ret, printf(' %% %s %s', key_header, 'Value'))
  271. else
  272. if max_desc_len < len('Description')
  273. let max_desc_len = len('Description')
  274. endif
  275. let desc_header = ('Description'
  276. \. repeat('_', max_desc_len - len('Description')))
  277. call add(ret, printf(' %% %s %s %s', key_header, desc_header, 'Value'))
  278. endif
  279. let i = 0
  280. for key in keys
  281. let [description, type, default] = descriptions[i]
  282. if a:name isnot# 'local_mark' && key is# 'n'
  283. unlet default
  284. let default = s:SHADA_REQUIRED
  285. endif
  286. let value = get(a:map, key, default)
  287. if (key is# 'n' && !has_key(s:SHADA_REQUIRES_NAME, a:name)
  288. \&& value is# s:SHADA_REQUIRED)
  289. " Do nothing
  290. elseif value is s:SHADA_REQUIRED
  291. call add(ret, ' # Required key missing: ' . key)
  292. elseif max_desc_len == 0
  293. call add(ret, printf(' + %-*s %s',
  294. \max_key_len, key,
  295. \msgpack#string(value)))
  296. else
  297. if type isnot 0 && value isnot# default
  298. let msg = s:shada_check_type(type, value)
  299. if msg isnot 0
  300. call add(ret, ' # ' . msg)
  301. endif
  302. endif
  303. let strval = s:shada_string(type, value)
  304. if msgpack#type(value) is# 'array' && msg is 0
  305. let shift = 2 + 2 + max_key_len + 2 + max_desc_len + 2
  306. " Value: 1 2 3 4 5 6:
  307. " " + Key Description Value"
  308. " 1122333445555555555566
  309. if shift + strdisplaywidth(strval, shift) > 80
  310. let strval = '@'
  311. endif
  312. endif
  313. call add(ret, printf(' + %-*s %-*s %s',
  314. \max_key_len, key,
  315. \max_desc_len, description,
  316. \strval))
  317. if strval is '@'
  318. for v in value
  319. call add(ret, printf(' | - %s', msgpack#string(v)))
  320. unlet v
  321. endfor
  322. endif
  323. endif
  324. let i += 1
  325. unlet value
  326. unlet default
  327. endfor
  328. return ret
  329. endfunction
  330. ""
  331. " Wrapper around msgpack#string() which may return string from s:SHADA_REV_ENUMS
  332. function s:shada_string(type, v) abort
  333. if (has_key(s:SHADA_ENUMS, a:type) && type(a:v) == type(0)
  334. \&& has_key(s:SHADA_REV_ENUMS[a:type], a:v))
  335. return s:SHADA_REV_ENUMS[a:type][a:v]
  336. " Restricting a:v to be <= 127 is not necessary, but intchar constants are
  337. " normally expected to be either ASCII printable characters or NUL.
  338. elseif a:type is# 'intchar' && type(a:v) == type(0) && a:v >= 0 && a:v <= 127
  339. if a:v > 0 && strtrans(nr2char(a:v)) is# nr2char(a:v)
  340. return "'" . nr2char(a:v) . "'"
  341. else
  342. return "'\\" . a:v . "'"
  343. endif
  344. else
  345. return msgpack#string(a:v)
  346. endif
  347. endfunction
  348. ""
  349. " Evaluate string obtained by s:shada_string().
  350. function s:shada_eval(s) abort
  351. return msgpack#eval(a:s, s:SHADA_SPECIAL_OBJS)
  352. endfunction
  353. ""
  354. " Convert one ShaDa entry to a list of strings suitable for setline().
  355. "
  356. " Returned format looks like this:
  357. "
  358. " TODO
  359. function s:shada_convert_entry(entry) abort
  360. if type(a:entry.type) == type({})
  361. " |msgpack-special-dict| may only be used if value does not fit into the
  362. " default integer type. All known entry types do fit, so it is definitely
  363. " unknown entry.
  364. let name = 'unknown_(' . msgpack#int_dict_to_str(a:entry.type) . ')'
  365. else
  366. let name = get(s:SHADA_ENTRY_NAMES, a:entry.type, 0)
  367. if name is 0
  368. let name = printf('unknown_(0x%x)', a:entry.type)
  369. endif
  370. endif
  371. let title = toupper(name[0]) . tr(name[1:], '_', ' ')
  372. let header = printf('%s with timestamp %s:', title,
  373. \s:shada_strftime(a:entry.timestamp))
  374. let ret = [header]
  375. if name[:8] is# 'unknown_(' && name[-1:] is# ')'
  376. call add(ret, ' = ' . msgpack#string(a:entry.data))
  377. elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name)
  378. if type(a:entry.data) != type([])
  379. call add(ret, printf(' # Unexpected type: %s instead of array',
  380. \msgpack#type(a:entry.data)))
  381. call add(ret, ' = ' . msgpack#string(a:entry.data))
  382. return ret
  383. endif
  384. let i = 0
  385. let max_desc_len = max(map(copy(s:SHADA_FIXED_ARRAY_ENTRIES[name]),
  386. \'len(v:val[0])'))
  387. if max_desc_len < len('Description')
  388. let max_desc_len = len('Description')
  389. endif
  390. let desc_header = ('Description'
  391. \. repeat('_', max_desc_len - len('Description')))
  392. call add(ret, printf(' @ %s %s', desc_header, 'Value'))
  393. for value in a:entry.data
  394. let [desc, type] = get(s:SHADA_FIXED_ARRAY_ENTRIES[name], i, ['', 0])
  395. if (i == 2 && name is# 'history_entry'
  396. \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH)
  397. let [desc, type] = ['', 0]
  398. endif
  399. if type isnot 0
  400. let msg = s:shada_check_type(type, value)
  401. if msg isnot 0
  402. call add(ret, ' # ' . msg)
  403. endif
  404. endif
  405. call add(ret, printf(' - %-*s %s', max_desc_len, desc,
  406. \s:shada_string(type, value)))
  407. let i += 1
  408. unlet value
  409. endfor
  410. if (len(a:entry.data) < len(s:SHADA_FIXED_ARRAY_ENTRIES[name])
  411. \&& !(name is# 'history_entry'
  412. \&& len(a:entry.data) == 2
  413. \&& a:entry.data[0] isnot# s:SHADA_ENUMS.histtype.SEARCH))
  414. call add(ret, ' # Expected more elements in list')
  415. endif
  416. elseif has_key(s:SHADA_MAP_ENTRIES, name)
  417. if type(a:entry.data) != type({})
  418. call add(ret, printf(' # Unexpected type: %s instead of map',
  419. \msgpack#type(a:entry.data)))
  420. call add(ret, ' = ' . msgpack#string(a:entry.data))
  421. return ret
  422. endif
  423. if msgpack#special_type(a:entry.data) isnot 0
  424. call add(ret, ' # Entry is a special dict which is unexpected')
  425. call add(ret, ' = ' . msgpack#string(a:entry.data))
  426. return ret
  427. endif
  428. let ret += s:shada_convert_map(a:entry.data, s:SHADA_MAP_ENTRIES[name],
  429. \name)
  430. elseif name is# 'buffer_list'
  431. if type(a:entry.data) != type([])
  432. call add(ret, printf(' # Unexpected type: %s instead of array',
  433. \msgpack#type(a:entry.data)))
  434. call add(ret, ' = ' . msgpack#string(a:entry.data))
  435. return ret
  436. elseif !empty(filter(copy(a:entry.data),
  437. \'type(v:val) != type({}) '
  438. \. '|| msgpack#special_type(v:val) isnot 0'))
  439. call add(ret, ' # Expected array of maps')
  440. call add(ret, ' = ' . msgpack#string(a:entry.data))
  441. return ret
  442. endif
  443. for bufdef in a:entry.data
  444. if bufdef isnot a:entry.data[0]
  445. call add(ret, '')
  446. endif
  447. let ret += s:shada_convert_map(bufdef, s:SHADA_BUFFER_LIST_KEYS, name)
  448. endfor
  449. else
  450. throw 'internal-unknown-type:Internal error: unknown type name: ' . name
  451. endif
  452. return ret
  453. endfunction
  454. ""
  455. " Order of msgpack objects in one ShaDa entry. Each item in the list is name of
  456. " the key in dictionaries returned by shada#read().
  457. let s:SHADA_ENTRY_OBJECT_SEQUENCE = ['type', 'timestamp', 'length', 'data']
  458. ""
  459. " Convert list returned by msgpackparse() to a list of ShaDa objects
  460. "
  461. " @param[in] mpack List of VimL objects returned by msgpackparse().
  462. "
  463. " @return List of dictionaries with keys type, timestamp, length and data. Each
  464. " dictionary describes one ShaDa entry.
  465. function shada#mpack_to_sd(mpack) abort
  466. let ret = []
  467. let i = 0
  468. for element in a:mpack
  469. let key = s:SHADA_ENTRY_OBJECT_SEQUENCE[
  470. \i % len(s:SHADA_ENTRY_OBJECT_SEQUENCE)]
  471. if key is# 'type'
  472. call add(ret, {})
  473. endif
  474. let ret[-1][key] = element
  475. if key isnot# 'data'
  476. if !msgpack#is_uint(element)
  477. throw printf('not-uint:Entry %i has %s element '.
  478. \'which is not an unsigned integer',
  479. \len(ret), key)
  480. endif
  481. if key is# 'type' && msgpack#equal(element, 0)
  482. throw printf('zero-uint:Entry %i has %s element '.
  483. \'which is zero',
  484. \len(ret), key)
  485. endif
  486. endif
  487. let i += 1
  488. unlet element
  489. endfor
  490. return ret
  491. endfunction
  492. ""
  493. " Convert read ShaDa file to a list of lines suitable for setline()
  494. "
  495. " @param[in] shada List of ShaDa entries like returned by shada#mpack_to_sd().
  496. "
  497. " @return List of strings suitable for setline()-like functions.
  498. function shada#sd_to_strings(shada) abort
  499. let ret = []
  500. for entry in a:shada
  501. let ret += s:shada_convert_entry(entry)
  502. endfor
  503. return ret
  504. endfunction
  505. ""
  506. " Convert a readfile()-like list of strings to a list of lines suitable for
  507. " setline().
  508. "
  509. " @param[in] binstrings List of strings to convert.
  510. "
  511. " @return List of lines.
  512. function shada#get_strings(binstrings) abort
  513. return shada#sd_to_strings(shada#mpack_to_sd(msgpackparse(a:binstrings)))
  514. endfunction
  515. ""
  516. " Convert s:shada_convert_entry() output to original entry.
  517. function s:shada_convert_strings(strings) abort
  518. let strings = copy(a:strings)
  519. let match = matchlist(
  520. \strings[0],
  521. \'\v\C^(.{-})\m with timestamp \(\d\{4}-\d\d-\d\dT\d\d:\d\d:\d\d\):$')
  522. if empty(match)
  523. throw 'invalid-header:Header has invalid format: ' . strings[0]
  524. endif
  525. call remove(strings, 0)
  526. let title = match[1]
  527. let name = tolower(title[0]) . tr(title[1:], ' ', '_')
  528. let ret = {}
  529. let empty_default = g:msgpack#nil
  530. if name[:8] is# 'unknown_(' && name[-1:] is# ')'
  531. let ret.type = +name[9:-2]
  532. elseif has_key(s:SHADA_ENTRY_TYPES, name)
  533. let ret.type = s:SHADA_ENTRY_TYPES[name]
  534. if has_key(s:SHADA_MAP_ENTRIES, name)
  535. unlet empty_default
  536. let empty_default = {}
  537. elseif has_key(s:SHADA_FIXED_ARRAY_ENTRIES, name) || name is# 'buffer_list'
  538. unlet empty_default
  539. let empty_default = []
  540. endif
  541. else
  542. throw 'invalid-type:Unknown type ' . name
  543. endif
  544. let ret.timestamp = s:shada_strptime(match[2])
  545. if empty(strings)
  546. let ret.data = empty_default
  547. else
  548. while !empty(strings)
  549. if strings[0][2] is# '='
  550. let data = s:shada_eval(strings[0][4:])
  551. call remove(strings, 0)
  552. elseif strings[0][2] is# '%'
  553. if name is# 'buffer_list' && !has_key(ret, 'data')
  554. let ret.data = []
  555. endif
  556. let match = matchlist(
  557. \strings[0],
  558. \'\m\C^ % \(Key_*\)\( Description_*\)\? Value')
  559. if empty(match)
  560. throw 'invalid-map-header:Invalid mapping header: ' . strings[0]
  561. endif
  562. call remove(strings, 0)
  563. let key_len = len(match[1])
  564. let desc_skip_len = len(match[2])
  565. let data = {'_TYPE': v:msgpack_types.map, '_VAL': []}
  566. while !empty(strings) && strings[0][2] is# '+'
  567. let line = remove(strings, 0)[4:]
  568. let key = substitute(line[:key_len - 1], '\v\C\ *$', '', '')
  569. let strval = line[key_len + desc_skip_len + 2:]
  570. if strval is# '@'
  571. let val = []
  572. while !empty(strings) && strings[0][2] is# '|'
  573. if strings[0][4] isnot# '-'
  574. throw ('invalid-array:Expected hyphen-minus at column 5: '
  575. \. strings)
  576. endif
  577. call add(val, s:shada_eval(remove(strings, 0)[5:]))
  578. endwhile
  579. else
  580. let val = s:shada_eval(strval)
  581. endif
  582. if (has_key(s:SHADA_STANDARD_KEYS, key)
  583. \&& s:SHADA_STANDARD_KEYS[key][2] isnot# s:SHADA_REQUIRED
  584. \&& msgpack#equal(s:SHADA_STANDARD_KEYS[key][2], val))
  585. unlet val
  586. continue
  587. endif
  588. call add(data._VAL, [{'_TYPE': v:msgpack_types.string, '_VAL': [key]},
  589. \val])
  590. unlet val
  591. endwhile
  592. elseif strings[0][2] is# '@'
  593. let match = matchlist(
  594. \strings[0],
  595. \'\m\C^ @ \(Description_* \)\?Value')
  596. if empty(match)
  597. throw 'invalid-array-header:Invalid array header: ' . strings[0]
  598. endif
  599. call remove(strings, 0)
  600. let desc_skip_len = len(match[1])
  601. let data = []
  602. while !empty(strings) && strings[0][2] is# '-'
  603. let val = remove(strings, 0)[4 + desc_skip_len :]
  604. call add(data, s:shada_eval(val))
  605. endwhile
  606. else
  607. throw 'invalid-line:Unrecognized line: ' . strings[0]
  608. endif
  609. if !has_key(ret, 'data')
  610. let ret.data = data
  611. elseif type(ret.data) == type([])
  612. call add(ret.data, data)
  613. else
  614. let ret.data = [ret.data, data]
  615. endif
  616. unlet data
  617. endwhile
  618. endif
  619. let ret._data = msgpackdump([ret.data])
  620. let ret.length = len(ret._data) - 1
  621. for s in ret._data
  622. let ret.length += len(s)
  623. endfor
  624. return ret
  625. endfunction
  626. ""
  627. " Convert s:shada_sd_to_strings() output to a list of original entries.
  628. function shada#strings_to_sd(strings) abort
  629. let strings = filter(copy(a:strings), 'v:val !~# ''\v^\s*%(\#|$)''')
  630. let stringss = []
  631. for string in strings
  632. if string[0] isnot# ' '
  633. call add(stringss, [])
  634. endif
  635. call add(stringss[-1], string)
  636. endfor
  637. return map(copy(stringss), 's:shada_convert_strings(v:val)')
  638. endfunction
  639. ""
  640. " Convert a list of strings to list of strings suitable for writefile().
  641. function shada#get_binstrings(strings) abort
  642. let entries = shada#strings_to_sd(a:strings)
  643. if !g:shada#keep_old_header
  644. call filter(entries, 'v:val.type != ' . s:SHADA_ENTRY_TYPES.header)
  645. endif
  646. if g:shada#add_own_header
  647. let data = {'version': v:version, 'generator': 'shada.vim'}
  648. let dumped_data = msgpackdump([data])
  649. let length = len(dumped_data) - 1
  650. for s in dumped_data
  651. let length += len(s)
  652. endfor
  653. call insert(entries, {
  654. \'type': s:SHADA_ENTRY_TYPES.header,
  655. \'timestamp': localtime(),
  656. \'length': length,
  657. \'data': data,
  658. \'_data': dumped_data,
  659. \})
  660. endif
  661. let mpack = []
  662. for entry in entries
  663. let mpack += map(copy(s:SHADA_ENTRY_OBJECT_SEQUENCE), 'entry[v:val]')
  664. endfor
  665. return msgpackdump(mpack)
  666. endfunction