aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/buffer_updates.c
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2018-06-08 10:13:04 +0200
committerJustin M. Keyes <justinkz@gmail.com>2018-06-08 10:13:04 +0200
commitf85cbea725b4d21412dc0ddfd07239307b3b63a4 (patch)
treecb88d8d6a010490160b9f7d0b1a236d61d29b323 /src/nvim/buffer_updates.c
parentc500f22f3ced8bbc271c4ec50d2217626ba5e97e (diff)
parent2ca62239675b0c1e68b01aae1a0d45567b15e319 (diff)
downloadrneovim-f85cbea725b4d21412dc0ddfd07239307b3b63a4.tar.gz
rneovim-f85cbea725b4d21412dc0ddfd07239307b3b63a4.tar.bz2
rneovim-f85cbea725b4d21412dc0ddfd07239307b3b63a4.zip
Merge #7917 'API: buffer updates'
Diffstat (limited to 'src/nvim/buffer_updates.c')
-rw-r--r--src/nvim/buffer_updates.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
new file mode 100644
index 0000000000..157f80e55a
--- /dev/null
+++ b/src/nvim/buffer_updates.c
@@ -0,0 +1,233 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include "nvim/buffer_updates.h"
+#include "nvim/memline.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/assert.h"
+
+// Register a channel. Return True if the channel was added, or already added.
+// Return False if the channel couldn't be added because the buffer is
+// unloaded.
+bool buf_updates_register(buf_T *buf, uint64_t channel_id, bool send_buffer)
+{
+ // must fail if the buffer isn't loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ return false;
+ }
+
+ // count how many channels are currently watching the buffer
+ size_t size = kv_size(buf->update_channels);
+ if (size) {
+ for (size_t i = 0; i < size; i++) {
+ if (kv_A(buf->update_channels, i) == channel_id) {
+ // buffer is already registered ... nothing to do
+ return true;
+ }
+ }
+ }
+
+ // append the channelid to the list
+ kv_push(buf->update_channels, channel_id);
+
+ if (send_buffer) {
+ Array args = ARRAY_DICT_INIT;
+ args.size = 6;
+ args.items = xcalloc(sizeof(Object), args.size);
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+ args.items[1] = INTEGER_OBJ(buf->b_changedtick);
+ // the first line that changed (zero-indexed)
+ args.items[2] = INTEGER_OBJ(0);
+ // the last line that was changed
+ args.items[3] = INTEGER_OBJ(-1);
+ Array linedata = ARRAY_DICT_INIT;
+
+ // collect buffer contents
+
+ // True now, but a compile time reminder for future systems we support
+ STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t to small to hold the number of"
+ " lines in a buffer");
+ size_t line_count = (size_t)buf->b_ml.ml_line_count;
+
+ if (line_count >= 1) {
+ linedata.size = line_count;
+ linedata.items = xcalloc(sizeof(Object), line_count);
+ for (size_t i = 0; i < line_count; i++) {
+ linenr_T lnum = 1 + (linenr_T)i;
+
+ const char *bufstr = (char *)ml_get_buf(buf, lnum, false);
+ Object str = STRING_OBJ(cstr_to_string(bufstr));
+
+ // Vim represents NULs as NLs, but this may confuse clients.
+ strchrsub(str.data.string.data, '\n', '\0');
+
+ linedata.items[i] = str;
+ }
+ }
+
+ args.items[4] = ARRAY_OBJ(linedata);
+ args.items[5] = BOOLEAN_OBJ(false);
+
+ rpc_send_event(channel_id, "nvim_buf_lines_event", args);
+ } else {
+ buf_updates_changedtick_single(buf, channel_id);
+ }
+
+ return true;
+}
+
+void buf_updates_send_end(buf_T *buf, uint64_t channelid)
+{
+ Array args = ARRAY_DICT_INIT;
+ args.size = 1;
+ args.items = xcalloc(sizeof(Object), args.size);
+ args.items[0] = BUFFER_OBJ(buf->handle);
+ rpc_send_event(channelid, "nvim_buf_detach_event", args);
+}
+
+void buf_updates_unregister(buf_T *buf, uint64_t channelid)
+{
+ size_t size = kv_size(buf->update_channels);
+ if (!size) {
+ return;
+ }
+
+ // go through list backwards and remove the channel id each time it appears
+ // (it should never appear more than once)
+ size_t j = 0;
+ size_t found = 0;
+ for (size_t i = 0; i < size; i++) {
+ if (kv_A(buf->update_channels, i) == channelid) {
+ found++;
+ } else {
+ // copy item backwards into prior slot if needed
+ if (i != j) {
+ kv_A(buf->update_channels, j) = kv_A(buf->update_channels, i);
+ }
+ j++;
+ }
+ }
+
+ if (found) {
+ // remove X items from the end of the array
+ buf->update_channels.size -= found;
+
+ // make a new copy of the active array without the channelid in it
+ buf_updates_send_end(buf, channelid);
+
+ if (found == size) {
+ kv_destroy(buf->update_channels);
+ kv_init(buf->update_channels);
+ }
+ }
+}
+
+void buf_updates_unregister_all(buf_T *buf)
+{
+ size_t size = kv_size(buf->update_channels);
+ if (size) {
+ for (size_t i = 0; i < size; i++) {
+ buf_updates_send_end(buf, kv_A(buf->update_channels, i));
+ }
+ kv_destroy(buf->update_channels);
+ kv_init(buf->update_channels);
+ }
+}
+
+void buf_updates_send_changes(buf_T *buf,
+ linenr_T firstline,
+ int64_t num_added,
+ int64_t num_removed,
+ bool send_tick)
+{
+ // if one the channels doesn't work, put its ID here so we can remove it later
+ uint64_t badchannelid = 0;
+
+ // notify each of the active channels
+ for (size_t i = 0; i < kv_size(buf->update_channels); i++) {
+ uint64_t channelid = kv_A(buf->update_channels, i);
+
+ // send through the changes now channel contents now
+ Array args = ARRAY_DICT_INIT;
+ args.size = 6;
+ args.items = xcalloc(sizeof(Object), args.size);
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+
+ // next argument is b:changedtick
+ args.items[1] = send_tick ? INTEGER_OBJ(buf->b_changedtick) : NIL;
+
+ // the first line that changed (zero-indexed)
+ args.items[2] = INTEGER_OBJ(firstline - 1);
+
+ // the last line that was changed
+ args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed);
+
+ // linedata of lines being swapped in
+ Array linedata = ARRAY_DICT_INIT;
+ if (num_added > 0) {
+ // True now, but a compile time reminder for future systems we support
+ // Note that `num_added` is a `int64_t`, but still must be lower than
+ // `MAX_LNUM`
+ STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t to small to hold the number "
+ "of lines in a buffer");
+ linedata.size = (size_t)num_added;
+ linedata.items = xcalloc(sizeof(Object), (size_t)num_added);
+ for (int64_t i = 0; i < num_added; i++) {
+ int64_t lnum = firstline + i;
+ const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false);
+ Object str = STRING_OBJ(cstr_to_string(bufstr));
+
+ // Vim represents NULs as NLs, but this may confuse clients.
+ strchrsub(str.data.string.data, '\n', '\0');
+
+ linedata.items[i] = str;
+ }
+ }
+ args.items[4] = ARRAY_OBJ(linedata);
+ args.items[5] = BOOLEAN_OBJ(false);
+ if (!rpc_send_event(channelid, "nvim_buf_lines_event", args)) {
+ // We can't unregister the channel while we're iterating over the
+ // update_channels array, so we remember its ID to unregister it at
+ // the end.
+ badchannelid = channelid;
+ }
+ }
+
+ // We can only ever remove one dead channel at a time. This is OK because the
+ // change notifications are so frequent that many dead channels will be
+ // cleared up quickly.
+ if (badchannelid != 0) {
+ ELOG("Disabling buffer updates for dead channel %llu", badchannelid);
+ buf_updates_unregister(buf, badchannelid);
+ }
+}
+
+void buf_updates_changedtick(buf_T *buf)
+{
+ // notify each of the active channels
+ for (size_t i = 0; i < kv_size(buf->update_channels); i++) {
+ uint64_t channel_id = kv_A(buf->update_channels, i);
+ buf_updates_changedtick_single(buf, channel_id);
+ }
+}
+
+void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
+{
+ Array args = ARRAY_DICT_INIT;
+ args.size = 2;
+ args.items = xcalloc(sizeof(Object), args.size);
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+
+ // next argument is b:changedtick
+ args.items[1] = INTEGER_OBJ(buf->b_changedtick);
+
+ // don't try and clean up dead channels here
+ rpc_send_event(channel_id, "nvim_buf_changedtick_event", args);
+}