1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include "nvim/buffer_updates.h" |
5 | #include "nvim/memline.h" |
6 | #include "nvim/api/private/helpers.h" |
7 | #include "nvim/msgpack_rpc/channel.h" |
8 | #include "nvim/lua/executor.h" |
9 | #include "nvim/assert.h" |
10 | #include "nvim/buffer.h" |
11 | |
12 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
13 | # include "buffer_updates.c.generated.h" |
14 | #endif |
15 | |
16 | // Register a channel. Return True if the channel was added, or already added. |
17 | // Return False if the channel couldn't be added because the buffer is |
18 | // unloaded. |
19 | bool buf_updates_register(buf_T *buf, uint64_t channel_id, |
20 | BufUpdateCallbacks cb, bool send_buffer) |
21 | { |
22 | // must fail if the buffer isn't loaded |
23 | if (buf->b_ml.ml_mfp == NULL) { |
24 | return false; |
25 | } |
26 | |
27 | if (channel_id == LUA_INTERNAL_CALL) { |
28 | kv_push(buf->update_callbacks, cb); |
29 | if (cb.utf_sizes) { |
30 | buf->update_need_codepoints = true; |
31 | } |
32 | return true; |
33 | } |
34 | |
35 | // count how many channels are currently watching the buffer |
36 | size_t size = kv_size(buf->update_channels); |
37 | if (size) { |
38 | for (size_t i = 0; i < size; i++) { |
39 | if (kv_A(buf->update_channels, i) == channel_id) { |
40 | // buffer is already registered ... nothing to do |
41 | return true; |
42 | } |
43 | } |
44 | } |
45 | |
46 | // append the channelid to the list |
47 | kv_push(buf->update_channels, channel_id); |
48 | |
49 | if (send_buffer) { |
50 | Array args = ARRAY_DICT_INIT; |
51 | args.size = 6; |
52 | args.items = xcalloc(sizeof(Object), args.size); |
53 | |
54 | // the first argument is always the buffer handle |
55 | args.items[0] = BUFFER_OBJ(buf->handle); |
56 | args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); |
57 | // the first line that changed (zero-indexed) |
58 | args.items[2] = INTEGER_OBJ(0); |
59 | // the last line that was changed |
60 | args.items[3] = INTEGER_OBJ(-1); |
61 | Array linedata = ARRAY_DICT_INIT; |
62 | |
63 | // collect buffer contents |
64 | |
65 | STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM" ); |
66 | size_t line_count = (size_t)buf->b_ml.ml_line_count; |
67 | |
68 | if (line_count >= 1) { |
69 | linedata.size = line_count; |
70 | linedata.items = xcalloc(sizeof(Object), line_count); |
71 | |
72 | buf_collect_lines(buf, line_count, 1, true, &linedata, NULL); |
73 | } |
74 | |
75 | args.items[4] = ARRAY_OBJ(linedata); |
76 | args.items[5] = BOOLEAN_OBJ(false); |
77 | |
78 | rpc_send_event(channel_id, "nvim_buf_lines_event" , args); |
79 | } else { |
80 | buf_updates_changedtick_single(buf, channel_id); |
81 | } |
82 | |
83 | return true; |
84 | } |
85 | |
86 | bool buf_updates_active(buf_T *buf) |
87 | { |
88 | return kv_size(buf->update_channels) || kv_size(buf->update_callbacks); |
89 | } |
90 | |
91 | void buf_updates_send_end(buf_T *buf, uint64_t channelid) |
92 | { |
93 | Array args = ARRAY_DICT_INIT; |
94 | args.size = 1; |
95 | args.items = xcalloc(sizeof(Object), args.size); |
96 | args.items[0] = BUFFER_OBJ(buf->handle); |
97 | rpc_send_event(channelid, "nvim_buf_detach_event" , args); |
98 | } |
99 | |
100 | void buf_updates_unregister(buf_T *buf, uint64_t channelid) |
101 | { |
102 | size_t size = kv_size(buf->update_channels); |
103 | if (!size) { |
104 | return; |
105 | } |
106 | |
107 | // go through list backwards and remove the channel id each time it appears |
108 | // (it should never appear more than once) |
109 | size_t j = 0; |
110 | size_t found = 0; |
111 | for (size_t i = 0; i < size; i++) { |
112 | if (kv_A(buf->update_channels, i) == channelid) { |
113 | found++; |
114 | } else { |
115 | // copy item backwards into prior slot if needed |
116 | if (i != j) { |
117 | kv_A(buf->update_channels, j) = kv_A(buf->update_channels, i); |
118 | } |
119 | j++; |
120 | } |
121 | } |
122 | |
123 | if (found) { |
124 | // remove X items from the end of the array |
125 | buf->update_channels.size -= found; |
126 | |
127 | // make a new copy of the active array without the channelid in it |
128 | buf_updates_send_end(buf, channelid); |
129 | |
130 | if (found == size) { |
131 | kv_destroy(buf->update_channels); |
132 | kv_init(buf->update_channels); |
133 | } |
134 | } |
135 | } |
136 | |
137 | void buf_updates_unregister_all(buf_T *buf) |
138 | { |
139 | size_t size = kv_size(buf->update_channels); |
140 | if (size) { |
141 | for (size_t i = 0; i < size; i++) { |
142 | buf_updates_send_end(buf, kv_A(buf->update_channels, i)); |
143 | } |
144 | kv_destroy(buf->update_channels); |
145 | kv_init(buf->update_channels); |
146 | } |
147 | |
148 | for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { |
149 | BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); |
150 | if (cb.on_detach != LUA_NOREF) { |
151 | Array args = ARRAY_DICT_INIT; |
152 | Object items[1]; |
153 | args.size = 1; |
154 | args.items = items; |
155 | |
156 | // the first argument is always the buffer handle |
157 | args.items[0] = BUFFER_OBJ(buf->handle); |
158 | |
159 | textlock++; |
160 | executor_exec_lua_cb(cb.on_detach, "detach" , args, false); |
161 | textlock--; |
162 | } |
163 | free_update_callbacks(cb); |
164 | } |
165 | kv_destroy(buf->update_callbacks); |
166 | kv_init(buf->update_callbacks); |
167 | } |
168 | |
169 | void buf_updates_send_changes(buf_T *buf, |
170 | linenr_T firstline, |
171 | int64_t num_added, |
172 | int64_t num_removed, |
173 | bool send_tick) |
174 | { |
175 | size_t deleted_codepoints, deleted_codeunits; |
176 | size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints, |
177 | &deleted_codeunits); |
178 | |
179 | if (!buf_updates_active(buf)) { |
180 | return; |
181 | } |
182 | |
183 | // if one the channels doesn't work, put its ID here so we can remove it later |
184 | uint64_t badchannelid = 0; |
185 | |
186 | // notify each of the active channels |
187 | for (size_t i = 0; i < kv_size(buf->update_channels); i++) { |
188 | uint64_t channelid = kv_A(buf->update_channels, i); |
189 | |
190 | // send through the changes now channel contents now |
191 | Array args = ARRAY_DICT_INIT; |
192 | args.size = 6; |
193 | args.items = xcalloc(sizeof(Object), args.size); |
194 | |
195 | // the first argument is always the buffer handle |
196 | args.items[0] = BUFFER_OBJ(buf->handle); |
197 | |
198 | // next argument is b:changedtick |
199 | args.items[1] = send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL; |
200 | |
201 | // the first line that changed (zero-indexed) |
202 | args.items[2] = INTEGER_OBJ(firstline - 1); |
203 | |
204 | // the last line that was changed |
205 | args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed); |
206 | |
207 | // linedata of lines being swapped in |
208 | Array linedata = ARRAY_DICT_INIT; |
209 | if (num_added > 0) { |
210 | STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM" ); |
211 | linedata.size = (size_t)num_added; |
212 | linedata.items = xcalloc(sizeof(Object), (size_t)num_added); |
213 | buf_collect_lines(buf, (size_t)num_added, firstline, true, &linedata, |
214 | NULL); |
215 | } |
216 | args.items[4] = ARRAY_OBJ(linedata); |
217 | args.items[5] = BOOLEAN_OBJ(false); |
218 | if (!rpc_send_event(channelid, "nvim_buf_lines_event" , args)) { |
219 | // We can't unregister the channel while we're iterating over the |
220 | // update_channels array, so we remember its ID to unregister it at |
221 | // the end. |
222 | badchannelid = channelid; |
223 | } |
224 | } |
225 | |
226 | // We can only ever remove one dead channel at a time. This is OK because the |
227 | // change notifications are so frequent that many dead channels will be |
228 | // cleared up quickly. |
229 | if (badchannelid != 0) { |
230 | ELOG("Disabling buffer updates for dead channel %" PRIu64, badchannelid); |
231 | buf_updates_unregister(buf, badchannelid); |
232 | } |
233 | |
234 | // notify each of the active channels |
235 | size_t j = 0; |
236 | for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { |
237 | BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); |
238 | bool keep = true; |
239 | if (cb.on_lines != LUA_NOREF) { |
240 | Array args = ARRAY_DICT_INIT; |
241 | Object items[8]; |
242 | args.size = 6; // may be increased to 8 below |
243 | args.items = items; |
244 | |
245 | // the first argument is always the buffer handle |
246 | args.items[0] = BUFFER_OBJ(buf->handle); |
247 | |
248 | // next argument is b:changedtick |
249 | args.items[1] = send_tick ? INTEGER_OBJ(buf_get_changedtick(buf)) : NIL; |
250 | |
251 | // the first line that changed (zero-indexed) |
252 | args.items[2] = INTEGER_OBJ(firstline - 1); |
253 | |
254 | // the last line that was changed |
255 | args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed); |
256 | |
257 | // the last line in the updated range |
258 | args.items[4] = INTEGER_OBJ(firstline - 1 + num_added); |
259 | |
260 | // byte count of previous contents |
261 | args.items[5] = INTEGER_OBJ((Integer)deleted_bytes); |
262 | if (cb.utf_sizes) { |
263 | args.size = 8; |
264 | args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints); |
265 | args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); |
266 | } |
267 | textlock++; |
268 | Object res = executor_exec_lua_cb(cb.on_lines, "lines" , args, true); |
269 | textlock--; |
270 | |
271 | if (res.type == kObjectTypeBoolean && res.data.boolean == true) { |
272 | free_update_callbacks(cb); |
273 | keep = false; |
274 | } |
275 | api_free_object(res); |
276 | } |
277 | if (keep) { |
278 | kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); |
279 | } |
280 | } |
281 | kv_size(buf->update_callbacks) = j; |
282 | } |
283 | |
284 | void buf_updates_changedtick(buf_T *buf) |
285 | { |
286 | // notify each of the active channels |
287 | for (size_t i = 0; i < kv_size(buf->update_channels); i++) { |
288 | uint64_t channel_id = kv_A(buf->update_channels, i); |
289 | buf_updates_changedtick_single(buf, channel_id); |
290 | } |
291 | size_t j = 0; |
292 | for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { |
293 | BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); |
294 | bool keep = true; |
295 | if (cb.on_changedtick != LUA_NOREF) { |
296 | Array args = ARRAY_DICT_INIT; |
297 | Object items[2]; |
298 | args.size = 2; |
299 | args.items = items; |
300 | |
301 | // the first argument is always the buffer handle |
302 | args.items[0] = BUFFER_OBJ(buf->handle); |
303 | |
304 | // next argument is b:changedtick |
305 | args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); |
306 | |
307 | textlock++; |
308 | Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick" , |
309 | args, true); |
310 | textlock--; |
311 | |
312 | if (res.type == kObjectTypeBoolean && res.data.boolean == true) { |
313 | free_update_callbacks(cb); |
314 | keep = false; |
315 | } |
316 | api_free_object(res); |
317 | } |
318 | if (keep) { |
319 | kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); |
320 | } |
321 | } |
322 | kv_size(buf->update_callbacks) = j; |
323 | } |
324 | |
325 | void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) |
326 | { |
327 | Array args = ARRAY_DICT_INIT; |
328 | args.size = 2; |
329 | args.items = xcalloc(sizeof(Object), args.size); |
330 | |
331 | // the first argument is always the buffer handle |
332 | args.items[0] = BUFFER_OBJ(buf->handle); |
333 | |
334 | // next argument is b:changedtick |
335 | args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); |
336 | |
337 | // don't try and clean up dead channels here |
338 | rpc_send_event(channel_id, "nvim_buf_changedtick_event" , args); |
339 | } |
340 | |
341 | static void free_update_callbacks(BufUpdateCallbacks cb) |
342 | { |
343 | executor_free_luaref(cb.on_lines); |
344 | executor_free_luaref(cb.on_changedtick); |
345 | } |
346 | |