shada: Add support for dumping and restoring buffer list

This commit is contained in:
ZyX 2015-07-04 22:36:06 +03:00
parent 4eeafe7f72
commit 8c93877e1c
4 changed files with 268 additions and 32 deletions

View File

@ -30,6 +30,7 @@
#include <inttypes.h>
#include "nvim/api/private/handle.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
@ -556,6 +557,10 @@ static void free_buffer(buf_T *buf)
free_buffer_stuff(buf, TRUE);
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
if (buf->additional_data != NULL) {
api_free_dictionary(*buf->additional_data);
xfree(buf->additional_data);
}
free_fmark(buf->b_last_cursor);
free_fmark(buf->b_last_insert);
free_fmark(buf->b_last_change);
@ -1988,12 +1993,18 @@ buflist_nr2name (
fullname ? buf->b_ffname : buf->b_fname);
}
/*
* Set the "lnum" and "col" for the buffer "buf" and the current window.
* When "copy_options" is TRUE save the local window option values.
* When "lnum" is 0 only do the options.
*/
static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, int copy_options)
/// Set the line and column numbers for the given buffer and window
///
/// @param[in,out] buf Buffer for which line and column are set.
/// @param[in,out] win Window for which line and column are set.
/// @param[in] lnum Line number to be set. If it is zero then only
/// options are touched.
/// @param[in] col Column number to be set.
/// @param[in] copy_options If true save the local window option values.
void buflist_setfpos(buf_T *const buf, win_T *const win,
linenr_T lnum, colnr_T col,
bool copy_options)
FUNC_ATTR_NONNULL_ALL
{
wininfo_T *wip;

View File

@ -26,7 +26,7 @@ typedef struct file_buffer buf_T; // Forward declaration
#include "nvim/eval_defs.h"
// for proftime_T
#include "nvim/profile.h"
// for String
// for String and Dictionary
#include "nvim/api/private/defs.h"
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
@ -748,6 +748,8 @@ struct file_buffer {
signlist_T *b_signlist; /* list of signs to draw */
Terminal *terminal; // Terminal instance associated with the buffer
Dictionary *additional_data; // Additional data from shada file if any.
};
/*

View File

@ -35,6 +35,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/globals.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/misc2.h"
#include "nvim/ex_getln.h"
#include "nvim/search.h"
@ -66,6 +67,10 @@ KHASH_MAP_INIT_STR(fnamebufs, buf_T *)
#define emsgn(a, ...) emsgn((char_u *) a, __VA_ARGS__)
#define home_replace_save(a, b) \
((char *)home_replace_save(a, (char_u *)b))
#define path_shorten_fname_if_possible(b) \
((char *)path_shorten_fname_if_possible((char_u *)b))
#define buflist_new(ffname, sfname, ...) \
(buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__))
// From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some
// additional checks done after examining `{compiler} -dM -E - < /dev/null`
@ -180,7 +185,14 @@ typedef struct {
char *sub;
Array *additional_elements;
} sub_string;
Array buffer_list;
struct buffer_list {
size_t size;
struct buffer_list_buffer {
pos_T pos;
char *fname;
Dictionary *additional_data;
} *buffers;
} buffer_list;
} data;
} ShadaEntry;
@ -617,7 +629,24 @@ static void shada_read(FILE *const fp, const int flags)
break;
}
case kSDItemBufferList: {
// FIXME
if (!(flags & kShaDaWantInfo) || find_viminfo_parameter('%') == NULL
|| ARGCOUNT != 0) {
shada_free_shada_entry(&cur_entry);
break;
}
for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) {
char *const sfname = path_shorten_fname_if_possible(
cur_entry.data.buffer_list.buffers[i].fname);
buf_T *const buf = buflist_new(
cur_entry.data.buffer_list.buffers[i].fname, sfname, 0,
BLN_LISTED);
if (buf != NULL) {
RESET_FMARK(&buf->b_last_cursor,
cur_entry.data.buffer_list.buffers[i].pos, 0);
buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
buf->b_last_cursor.mark.col, false);
}
}
shada_free_shada_entry(&cur_entry);
break;
}
@ -1019,7 +1048,46 @@ static void shada_pack_entry(msgpack_packer *const packer,
break;
}
case kSDItemBufferList: {
msgpack_rpc_from_array(entry.data.buffer_list, spacker);
msgpack_pack_array(spacker, entry.data.buffer_list.size);
for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
const size_t map_size = (size_t) (
1 // Buffer name
// Line number: defaults to 1
+ (entry.data.buffer_list.buffers[i].pos.lnum != 1)
// Column number: defaults to 0
+ (entry.data.buffer_list.buffers[i].pos.col != 0)
// Additional entries, if any:
+ (entry.data.buffer_list.buffers[i].additional_data == NULL
? 0
: entry.data.buffer_list.buffers[i].additional_data->size)
);
msgpack_pack_map(spacker, map_size);
PACK_STATIC_STR("file");
msgpack_rpc_from_string(
cstr_as_string(entry.data.buffer_list.buffers[i].fname), spacker);
if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
PACK_STATIC_STR("line");
msgpack_pack_uint64(
spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.lnum);
}
if (entry.data.buffer_list.buffers[i].pos.col != 0) {
PACK_STATIC_STR("col");
msgpack_pack_uint64(
spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col);
}
if (entry.data.buffer_list.buffers[i].additional_data != NULL) {
for (size_t j = 0;
j < entry.data.buffer_list.buffers[i].additional_data->size;
j++) {
msgpack_rpc_from_string(
entry.data.buffer_list.buffers[i].additional_data->items[j].key,
spacker);
msgpack_rpc_from_object(
entry.data.buffer_list.buffers[i].additional_data->items[j].value,
spacker);
}
}
}
break;
}
case kSDItemHeader: {
@ -1095,35 +1163,38 @@ static void shada_write(FILE *const newfp, FILE *const oldfp)
// 2. Buffer list
if (find_viminfo_parameter('%') != NULL) {
msgpack_pack_uint64(packer, (uint64_t) kSDItemBufferList);
msgpack_pack_uint64(packer, (uint64_t) os_time());
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
size_t buf_count = 0;
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) {
buf_count++;
}
}
msgpack_pack_array(packer, buf_count);
ShadaEntry buflist_entry = (ShadaEntry) {
.type = kSDItemBufferList,
.timestamp = os_time(),
.data = {
.buffer_list = {
.size = buf_count,
.buffers = xmalloc(buf_count
* sizeof(*buflist_entry.data.buffer_list.buffers)),
},
},
};
size_t i = 0;
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) {
continue;
}
msgpack_pack_map(packer, 3);
PACK_STATIC_STR("fname");
msgpack_rpc_from_string(cstr_as_string((char *) buf->b_ffname), packer);
PACK_STATIC_STR("lnum");
msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.lnum);
PACK_STATIC_STR("col");
msgpack_pack_uint64(packer, (uint64_t) buf->b_last_cursor.mark.col);
buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) {
.pos = buf->b_last_cursor.mark,
.fname = (char *) buf->b_ffname,
.additional_data = buf->additional_data,
};
i++;
}
msgpack_pack_uint64(packer, (uint64_t) sbuf.size);
packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size);
msgpack_packer_free(spacker);
msgpack_sbuffer_destroy(&sbuf);
shada_pack_entry(packer, buflist_entry, 0);
xfree(buflist_entry.data.buffer_list.buffers);
}
// 3. Jump list
@ -1460,7 +1531,15 @@ static void shada_free_shada_entry(ShadaEntry *const entry)
break;
}
case kSDItemBufferList: {
api_free_array(entry->data.buffer_list);
for (size_t i = 0; i < entry->data.buffer_list.size; i++) {
xfree(entry->data.buffer_list.buffers[i].fname);
if (entry->data.buffer_list.buffers[i].additional_data != NULL) {
api_free_dictionary(
*entry->data.buffer_list.buffers[i].additional_data);
xfree(entry->data.buffer_list.buffers[i].additional_data);
}
}
xfree(entry->data.buffer_list.buffers);
break;
}
}
@ -1609,7 +1688,7 @@ shada_read_next_item_start:
// First: manually unpack type, timestamp and length.
// This is needed to avoid both seeking and having to maintain a buffer.
uint64_t type_u64;
uint64_t type_u64 = (uint64_t) kSDItemMissing;
uint64_t timestamp_u64;
uint64_t length_u64;
@ -2258,12 +2337,88 @@ shada_read_next_item_start:
break;
}
case kSDItemBufferList: {
if (!msgpack_rpc_to_array(&(unpacked.data), &(entry->data.buffer_list))) {
if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
emsgn("Error while reading ShaDa file: "
"buffer list entry at position %" PRId64 " is not an array",
"buffer list entry at position %" PRId64 " "
"is not an array",
(int64_t) initial_fpos);
goto shada_read_next_item_error;
}
entry->data.buffer_list = (struct buffer_list) {
.size = 0,
.buffers = NULL,
};
if (unpacked.data.via.array.size == 0) {
break;
}
entry->data.buffer_list.buffers =
xcalloc(unpacked.data.via.array.size,
sizeof(*entry->data.buffer_list.buffers));
for (size_t i = 0; i < unpacked.data.via.array.size; i++) {
entry->data.buffer_list.size++;
msgpack_unpacked unpacked_2 = (msgpack_unpacked) {
.data = unpacked.data.via.array.ptr[i],
};
{
msgpack_unpacked unpacked = unpacked_2;
if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
emsgn("Error while reading ShaDa file: "
"buffer list at position %" PRId64 " "
"contains entry that is not a dictionary",
(int64_t) initial_fpos);
goto shada_read_next_item_error;
}
entry->data.buffer_list.buffers[i].pos.lnum = 1;
garray_T ad_ga;
ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
{
const size_t j = i;
{
for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
CHECK_KEY_IS_STR("buffer list entry");
LONG_KEY("buffer list entry", "line",
entry->data.buffer_list.buffers[j].pos.lnum)
else INTEGER_KEY("buffer list entry", "col",
entry->data.buffer_list.buffers[j].pos.col)
else STRING_KEY("buffer list entry", "file",
entry->data.buffer_list.buffers[j].fname)
else ADDITIONAL_KEY
}
}
}
if (entry->data.buffer_list.buffers[i].fname == NULL) {
emsgn("Error while reading ShaDa file: "
"buffer list at position %" PRId64 " "
"contains entry that does not have a file name",
(int64_t) initial_fpos);
ga_clear(&ad_ga);
goto shada_read_next_item_error;
}
if (ad_ga.ga_len) {
msgpack_object obj = {
.type = MSGPACK_OBJECT_MAP,
.via = {
.map = {
.size = (uint32_t) ad_ga.ga_len,
.ptr = ad_ga.ga_data,
}
}
};
entry->data.buffer_list.buffers[i].additional_data =
xmalloc(sizeof(Dictionary));
if (!msgpack_rpc_to_dictionary(
&obj, entry->data.buffer_list.buffers[i].additional_data)) {
emsgu("Error while reading ShaDa file: "
"buffer list at position %" PRIu64 " "
"contains entry that cannot be converted to a Dictionary",
(uint64_t) initial_fpos);
ga_clear(&ad_ga);
goto shada_read_next_item_error;
}
}
ga_clear(&ad_ga);
}
}
break;
}
case kSDItemMissing: {
@ -2297,6 +2452,9 @@ shada_read_next_item_start:
shada_read_next_item_error:
msgpack_unpacked_destroy(&unpacked);
msgpack_unpacker_free(unpacker);
entry->type = (ShadaEntryType) type_u64;
shada_free_shada_entry(entry);
entry->type = kSDItemMissing;
return FAIL;
shada_read_next_item_end:
msgpack_unpacked_destroy(&unpacked);

View File

@ -0,0 +1,65 @@
-- ShaDa buffer list saving/reading support
local helpers = require('test.functional.helpers')
local nvim, nvim_window, nvim_curwin, nvim_command, nvim_feed, nvim_eval, eq =
helpers.nvim, helpers.window, helpers.curwin, helpers.command, helpers.feed,
helpers.eval, helpers.eq
local shada_helpers = require('test.functional.shada.helpers')
local reset, set_additional_cmd, clear =
shada_helpers.reset, shada_helpers.set_additional_cmd,
shada_helpers.clear
local nvim_current_line = function()
return nvim_window('get_cursor', nvim_curwin())[1]
end
describe('ShaDa support code', function()
testfilename = 'Xtestfile-functional-shada-buffers'
testfilename_2 = 'Xtestfile-functional-shada-buffers-2'
before_each(reset)
after_each(clear)
it('is able to dump and restore buffer list', function()
set_additional_cmd('set viminfo+=%')
reset()
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
-- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END')
-- nvim_command('wviminfo /tmp/foo')
nvim_command('qall')
reset()
-- nvim_command('call writefile([&viminfo], "/tmp/vistr")')
eq(3, nvim_eval('bufnr("$")'))
eq('', nvim_eval('bufname(1)'))
eq(testfilename, nvim_eval('bufname(2)'))
eq(testfilename_2, nvim_eval('bufname(3)'))
end)
it('does not restore buffer list without % in &viminfo', function()
set_additional_cmd('set viminfo+=%')
reset()
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
-- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END')
-- nvim_command('wviminfo /tmp/foo')
set_additional_cmd('')
nvim_command('qall')
reset()
-- nvim_command('call writefile([&viminfo], "/tmp/vistr")')
eq(1, nvim_eval('bufnr("$")'))
eq('', nvim_eval('bufname(1)'))
end)
it('does not dump buffer list without % in &viminfo', function()
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
-- nvim_command('redir! > /tmp/vistr | verbose set viminfo? | redir END')
-- nvim_command('wviminfo /tmp/foo')
set_additional_cmd('set viminfo+=%')
nvim_command('qall')
reset()
-- nvim_command('call writefile([&viminfo], "/tmp/vistr")')
eq(1, nvim_eval('bufnr("$")'))
eq('', nvim_eval('bufname(1)'))
end)
end)