Skip to content

Commit

Permalink
Merge #7917 'API: buffer updates'
Browse files Browse the repository at this point in the history
  • Loading branch information
justinmk committed Jun 8, 2018
2 parents c500f22 + 2ca6223 commit f85cbea
Show file tree
Hide file tree
Showing 18 changed files with 1,337 additions and 74 deletions.
126 changes: 126 additions & 0 deletions runtime/doc/msgpack_rpc.txt
Expand Up @@ -241,4 +241,130 @@ Even for statically compiled clients it is good practice to avoid hardcoding
the type codes, because a client may be built against one Nvim version but
connect to another with different type codes.

==============================================================================
6. Buffer Updates *buffer-updates* *rpc-buffer-updates*

A dedicated API has been created to allow co-processes to be notified when a
buffer is changed in any way. It is difficult and error-prone to try and do
this with autocommands such as |TextChanged|.

*buffer-updates-events*
BufferUpdates Events~

The co-process will start receiving the following notification events:

*nvim_buf_lines_event*
nvim_buf_lines_event[{buf}, {changedtick}, {firstline}, {lastline}, {linedata}, {more}]

Indicates that the lines between {firstline} and {lastline} (end-exclusive,
zero-indexed) have been replaced with the new line data contained in the
{linedata} list. All buffer changes (even adding single characters) will be
transmitted as whole-line changes.

{buf} is an API handle for the buffer.

{changedtick} is the value of |b:changedtick| for the buffer. If you send an
API command back to nvim you can check the value of |b:changedtick| as
part of your request to ensure that no other changes have been made.

{firstline} is the integer line number of the first line that was replaced.
Note that {firstline} is zero-indexed, so if line `1` was replaced then
{firstline} will be `0` instead of `1`. {firstline} is guaranteed to always
be less than or equal to the number of lines that were in the buffer before
the lines were replaced.

{lastline} is the integer line number of the first line that was not replaced
(i.e. the range {firstline}, {lastline} is end-exclusive). Note that
{lastline} is zero-indexed, so if line numbers 2 to 5 were replaced, this
will be `5` instead of `6`. {lastline} is guaranteed to always be less than
or equal to the number of lines that were in the buffer before the lines were
replaced. {lastline} will be `-1` if the event is part of the initial
sending of the buffer.

{linedata} is a list of strings containing the contents of the new buffer
lines. Newline characters are not included in the strings, so empty lines
will be given as empty strings.

{more} is a boolean which tells you whether or not to expect more
|nvim_buf_lines_event| notifications for a single buffer change (i.e. Nvim has
chunked up one event into several). Not yet used.

Note: sometimes {changedtick} will be |v:null|, which means that the buffer
text *looks* like it has changed, but actually hasn't. In this case the lines
in {linedata} contain the modified text that is shown to the user, but
doesn't reflect the actual buffer contents. Currently this behaviour is
only used for the |inccommand| option.

nvim_buf_changedtick_event[{buf}, {changedtick}] *nvim_buf_changedtick_event*

Indicates that |b:changedtick| was incremented for the buffer {buf}, but no
text was changed. This is currently only used by undo/redo.

{buf} is an API handle for the buffer.

{changedtick} is the new value of |b:changedtick| for that buffer.

nvim_buf_detach_event[{buf}] *nvim_buf_detach_event*

Indicates that buffer updates for the nominated buffer have been disabled,
either by calling |nvim_buf_detach| or because the buffer was unloaded
(see |buffer-updates-limitations| for more information).

{buf} is an API handle for the buffer.


*buffer-updates-limitations*
Limitations~

Note that any of the following actions will also turn off buffer updates because
the buffer contents are unloaded from memory:

- Closing all a buffer's windows (unless 'hidden' is enabled).
- Using |:edit| to reload the buffer
- reloading the buffer after it is changed from outside nvim.

*buffer-updates-examples*
Examples~

If buffer updates are activated on an empty buffer (and sending the buffer's
content on the initial notification has been requested), the following
|nvim_buf_lines_event| event will be sent: >
nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, [""], v:false]
If the user adds 2 new lines to the start of a buffer, the following event
would be generated: >
nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, ["line1", "line2"], v:false]
If the puts the cursor on a line containing the text `"Hello world"` and adds
a `!` character to the end using insert mode, the following event would be
generated: >
nvim_buf_lines_event[
{buf}, {changedtick}, {linenr}, {linenr} + 1,
["Hello world!"], v:false
]
If the user moves their cursor to line 3 of a buffer and deletes 20 lines
using `20dd`, the following event will be generated: >
nvim_buf_lines_event[{buf}, {changedtick}, 2, 22, [], v:false]
If the user selects lines 3-5 of a buffer using |linewise-visual| mode and
then presses `p` to paste in a new block of 6 lines, then the following event
would be sent to the co-process: >
nvim_buf_lines_event[
{buf}, {changedtick}, 2, 5,
['pasted line 1', 'pasted line 2', 'pasted line 3', 'pasted line 4',
'pasted line 5', 'pasted line 6'],
v:false
]
If the user uses :edit to reload a buffer then the following event would be
generated: >
nvim_buf_detach_event[{buf}]
vim:tw=78:ts=8:ft=help:norl:
56 changes: 55 additions & 1 deletion src/nvim/api/buffer.c
Expand Up @@ -25,6 +25,7 @@
#include "nvim/window.h"
#include "nvim/undo.h"
#include "nvim/ex_docmd.h"
#include "nvim/buffer_updates.h"

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.c.generated.h"
Expand Down Expand Up @@ -75,6 +76,59 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}

/// Activate updates from this buffer to the current channel.
///
/// @param buffer The buffer handle
/// @param send_buffer Set to true if the initial notification should contain
/// the whole buffer. If so, the first notification will be a
/// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event`
/// @param opts Optional parameters. Currently not used.
/// @param[out] err Details of an error that may have occurred
/// @return False when updates couldn't be enabled because the buffer isn't
/// loaded or `opts` contained an invalid key; otherwise True.
Boolean nvim_buf_attach(uint64_t channel_id,
Buffer buffer,
Boolean send_buffer,
Dictionary opts,
Error *err)
FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
{
if (opts.size > 0) {
api_set_error(err, kErrorTypeValidation, "dict isn't empty");
return false;
}

buf_T *buf = find_buffer_by_handle(buffer, err);

if (!buf) {
return false;
}

return buf_updates_register(buf, channel_id, send_buffer);
}
//
/// Deactivate updates from this buffer to the current channel.
///
/// @param buffer The buffer handle
/// @param[out] err Details of an error that may have occurred
/// @return False when updates couldn't be disabled because the buffer
/// isn't loaded; otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
Buffer buffer,
Error *err)
FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
{
buf_T *buf = find_buffer_by_handle(buffer, err);

if (!buf) {
return false;
}

buf_updates_unregister(buf, channel_id);
return true;
}

/// Sets a buffer line
///
/// @deprecated use nvim_buf_set_lines instead.
Expand Down Expand Up @@ -407,7 +461,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
false);
}

changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);

if (save_curbuf.br_buf == NULL) {
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
Expand Down
14 changes: 11 additions & 3 deletions src/nvim/buffer.c
Expand Up @@ -73,6 +73,7 @@
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/buffer_updates.h"

typedef enum {
kBLSUnchanged = 0,
Expand Down Expand Up @@ -574,6 +575,9 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
/* Change directories when the 'acd' option is set. */
do_autochdir();

// disable buffer updates for the current buffer
buf_updates_unregister_all(buf);

/*
* Remove the buffer from the list.
*/
Expand Down Expand Up @@ -784,6 +788,8 @@ free_buffer_stuff (
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
xfree(buf->b_start_fenc);
buf->b_start_fenc = NULL;

buf_updates_unregister_all(buf);
}

/*
Expand Down Expand Up @@ -1732,9 +1738,11 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
if (flags & BLN_DUMMY)
buf->b_flags |= BF_DUMMY;
buf_clear_file(buf);
clrallmarks(buf); /* clear marks */
fmarks_check_names(buf); /* check file marks for this file */
buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; /* init 'buflisted' */
clrallmarks(buf); // clear marks
fmarks_check_names(buf); // check file marks for this file
buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted'
kv_destroy(buf->update_channels);
kv_init(buf->update_channels);
if (!(flags & BLN_DUMMY)) {
// Tricky: these autocommands may change the buffer list. They could also
// split the window with re-using the one empty buffer. This may result in
Expand Down
6 changes: 6 additions & 0 deletions src/nvim/buffer_defs.h
Expand Up @@ -38,6 +38,8 @@ typedef struct {
#include "nvim/api/private/defs.h"
// for Map(K, V)
#include "nvim/map.h"
// for kvec
#include "nvim/lib/kvec.h"

#define MODIFIABLE(buf) (buf->b_p_ma)

Expand Down Expand Up @@ -771,6 +773,10 @@ struct file_buffer {
BufhlInfo b_bufhl_info; // buffer stored highlights

kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights

// array of channelids which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
};

/*
Expand Down

0 comments on commit f85cbea

Please sign in to comment.