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 <stdbool.h> |
5 | #include <stdint.h> |
6 | #include <stdlib.h> |
7 | #include <limits.h> |
8 | |
9 | #include "nvim/ascii.h" |
10 | #include "nvim/globals.h" |
11 | #include "nvim/api/window.h" |
12 | #include "nvim/api/private/defs.h" |
13 | #include "nvim/api/private/helpers.h" |
14 | #include "nvim/ex_docmd.h" |
15 | #include "nvim/vim.h" |
16 | #include "nvim/buffer.h" |
17 | #include "nvim/cursor.h" |
18 | #include "nvim/option.h" |
19 | #include "nvim/window.h" |
20 | #include "nvim/screen.h" |
21 | #include "nvim/move.h" |
22 | |
23 | /// Gets the current buffer in a window |
24 | /// |
25 | /// @param window Window handle, or 0 for current window |
26 | /// @param[out] err Error details, if any |
27 | /// @return Buffer handle |
28 | Buffer nvim_win_get_buf(Window window, Error *err) |
29 | FUNC_API_SINCE(1) |
30 | { |
31 | win_T *win = find_window_by_handle(window, err); |
32 | |
33 | if (!win) { |
34 | return 0; |
35 | } |
36 | |
37 | return win->w_buffer->handle; |
38 | } |
39 | |
40 | /// Sets the current buffer in a window, without side-effects |
41 | /// |
42 | /// @param window Window handle, or 0 for current window |
43 | /// @param buffer Buffer handle |
44 | /// @param[out] err Error details, if any |
45 | void nvim_win_set_buf(Window window, Buffer buffer, Error *err) |
46 | FUNC_API_SINCE(5) |
47 | { |
48 | win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; |
49 | buf_T *buf = find_buffer_by_handle(buffer, err); |
50 | tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; |
51 | |
52 | if (!win || !buf) { |
53 | return; |
54 | } |
55 | |
56 | if (switch_win(&save_curwin, &save_curtab, win, tab, false) == FAIL) { |
57 | api_set_error(err, |
58 | kErrorTypeException, |
59 | "Failed to switch to window %d" , |
60 | window); |
61 | } |
62 | |
63 | try_start(); |
64 | int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); |
65 | if (!try_end(err) && result == FAIL) { |
66 | api_set_error(err, |
67 | kErrorTypeException, |
68 | "Failed to set buffer %d" , |
69 | buffer); |
70 | } |
71 | |
72 | // If window is not current, state logic will not validate its cursor. |
73 | // So do it now. |
74 | validate_cursor(); |
75 | |
76 | restore_win(save_curwin, save_curtab, false); |
77 | } |
78 | |
79 | /// Gets the (1,0)-indexed cursor position in the window. |api-indexing| |
80 | /// |
81 | /// @param window Window handle, or 0 for current window |
82 | /// @param[out] err Error details, if any |
83 | /// @return (row, col) tuple |
84 | ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) |
85 | FUNC_API_SINCE(1) |
86 | { |
87 | Array rv = ARRAY_DICT_INIT; |
88 | win_T *win = find_window_by_handle(window, err); |
89 | |
90 | if (win) { |
91 | ADD(rv, INTEGER_OBJ(win->w_cursor.lnum)); |
92 | ADD(rv, INTEGER_OBJ(win->w_cursor.col)); |
93 | } |
94 | |
95 | return rv; |
96 | } |
97 | |
98 | /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| |
99 | /// |
100 | /// @param window Window handle, or 0 for current window |
101 | /// @param pos (row, col) tuple representing the new position |
102 | /// @param[out] err Error details, if any |
103 | void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) |
104 | FUNC_API_SINCE(1) |
105 | { |
106 | win_T *win = find_window_by_handle(window, err); |
107 | |
108 | if (!win) { |
109 | return; |
110 | } |
111 | |
112 | if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger |
113 | || pos.items[1].type != kObjectTypeInteger) { |
114 | api_set_error(err, |
115 | kErrorTypeValidation, |
116 | "Argument \"pos\" must be a [row, col] array" ); |
117 | return; |
118 | } |
119 | |
120 | int64_t row = pos.items[0].data.integer; |
121 | int64_t col = pos.items[1].data.integer; |
122 | |
123 | if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) { |
124 | api_set_error(err, kErrorTypeValidation, "Cursor position outside buffer" ); |
125 | return; |
126 | } |
127 | |
128 | if (col > MAXCOL || col < 0) { |
129 | api_set_error(err, kErrorTypeValidation, "Column value outside range" ); |
130 | return; |
131 | } |
132 | |
133 | win->w_cursor.lnum = (linenr_T)row; |
134 | win->w_cursor.col = (colnr_T)col; |
135 | win->w_cursor.coladd = 0; |
136 | // When column is out of range silently correct it. |
137 | check_cursor_col_win(win); |
138 | |
139 | // Make sure we stick in this column. |
140 | win->w_set_curswant = true; |
141 | |
142 | // make sure cursor is in visible range even if win != curwin |
143 | update_topline_win(win); |
144 | |
145 | redraw_win_later(win, VALID); |
146 | } |
147 | |
148 | /// Gets the window height |
149 | /// |
150 | /// @param window Window handle, or 0 for current window |
151 | /// @param[out] err Error details, if any |
152 | /// @return Height as a count of rows |
153 | Integer nvim_win_get_height(Window window, Error *err) |
154 | FUNC_API_SINCE(1) |
155 | { |
156 | win_T *win = find_window_by_handle(window, err); |
157 | |
158 | if (!win) { |
159 | return 0; |
160 | } |
161 | |
162 | return win->w_height; |
163 | } |
164 | |
165 | /// Sets the window height. This will only succeed if the screen is split |
166 | /// horizontally. |
167 | /// |
168 | /// @param window Window handle, or 0 for current window |
169 | /// @param height Height as a count of rows |
170 | /// @param[out] err Error details, if any |
171 | void nvim_win_set_height(Window window, Integer height, Error *err) |
172 | FUNC_API_SINCE(1) |
173 | { |
174 | win_T *win = find_window_by_handle(window, err); |
175 | |
176 | if (!win) { |
177 | return; |
178 | } |
179 | |
180 | if (height > INT_MAX || height < INT_MIN) { |
181 | api_set_error(err, kErrorTypeValidation, "Height value outside range" ); |
182 | return; |
183 | } |
184 | |
185 | win_T *savewin = curwin; |
186 | curwin = win; |
187 | try_start(); |
188 | win_setheight((int)height); |
189 | curwin = savewin; |
190 | try_end(err); |
191 | } |
192 | |
193 | /// Gets the window width |
194 | /// |
195 | /// @param window Window handle, or 0 for current window |
196 | /// @param[out] err Error details, if any |
197 | /// @return Width as a count of columns |
198 | Integer nvim_win_get_width(Window window, Error *err) |
199 | FUNC_API_SINCE(1) |
200 | { |
201 | win_T *win = find_window_by_handle(window, err); |
202 | |
203 | if (!win) { |
204 | return 0; |
205 | } |
206 | |
207 | return win->w_width; |
208 | } |
209 | |
210 | /// Sets the window width. This will only succeed if the screen is split |
211 | /// vertically. |
212 | /// |
213 | /// @param window Window handle, or 0 for current window |
214 | /// @param width Width as a count of columns |
215 | /// @param[out] err Error details, if any |
216 | void nvim_win_set_width(Window window, Integer width, Error *err) |
217 | FUNC_API_SINCE(1) |
218 | { |
219 | win_T *win = find_window_by_handle(window, err); |
220 | |
221 | if (!win) { |
222 | return; |
223 | } |
224 | |
225 | if (width > INT_MAX || width < INT_MIN) { |
226 | api_set_error(err, kErrorTypeValidation, "Width value outside range" ); |
227 | return; |
228 | } |
229 | |
230 | win_T *savewin = curwin; |
231 | curwin = win; |
232 | try_start(); |
233 | win_setwidth((int)width); |
234 | curwin = savewin; |
235 | try_end(err); |
236 | } |
237 | |
238 | /// Gets a window-scoped (w:) variable |
239 | /// |
240 | /// @param window Window handle, or 0 for current window |
241 | /// @param name Variable name |
242 | /// @param[out] err Error details, if any |
243 | /// @return Variable value |
244 | Object nvim_win_get_var(Window window, String name, Error *err) |
245 | FUNC_API_SINCE(1) |
246 | { |
247 | win_T *win = find_window_by_handle(window, err); |
248 | |
249 | if (!win) { |
250 | return (Object) OBJECT_INIT; |
251 | } |
252 | |
253 | return dict_get_value(win->w_vars, name, err); |
254 | } |
255 | |
256 | /// Sets a window-scoped (w:) variable |
257 | /// |
258 | /// @param window Window handle, or 0 for current window |
259 | /// @param name Variable name |
260 | /// @param value Variable value |
261 | /// @param[out] err Error details, if any |
262 | void nvim_win_set_var(Window window, String name, Object value, Error *err) |
263 | FUNC_API_SINCE(1) |
264 | { |
265 | win_T *win = find_window_by_handle(window, err); |
266 | |
267 | if (!win) { |
268 | return; |
269 | } |
270 | |
271 | dict_set_var(win->w_vars, name, value, false, false, err); |
272 | } |
273 | |
274 | /// Removes a window-scoped (w:) variable |
275 | /// |
276 | /// @param window Window handle, or 0 for current window |
277 | /// @param name Variable name |
278 | /// @param[out] err Error details, if any |
279 | void nvim_win_del_var(Window window, String name, Error *err) |
280 | FUNC_API_SINCE(1) |
281 | { |
282 | win_T *win = find_window_by_handle(window, err); |
283 | |
284 | if (!win) { |
285 | return; |
286 | } |
287 | |
288 | dict_set_var(win->w_vars, name, NIL, true, false, err); |
289 | } |
290 | |
291 | /// Sets a window-scoped (w:) variable |
292 | /// |
293 | /// @deprecated |
294 | /// |
295 | /// @param window Window handle, or 0 for current window |
296 | /// @param name Variable name |
297 | /// @param value Variable value |
298 | /// @param[out] err Error details, if any |
299 | /// @return Old value or nil if there was no previous value. |
300 | /// |
301 | /// @warning It may return nil if there was no previous value |
302 | /// or if previous value was `v:null`. |
303 | Object window_set_var(Window window, String name, Object value, Error *err) |
304 | { |
305 | win_T *win = find_window_by_handle(window, err); |
306 | |
307 | if (!win) { |
308 | return (Object) OBJECT_INIT; |
309 | } |
310 | |
311 | return dict_set_var(win->w_vars, name, value, false, true, err); |
312 | } |
313 | |
314 | /// Removes a window-scoped (w:) variable |
315 | /// |
316 | /// @deprecated |
317 | /// |
318 | /// @param window Window handle, or 0 for current window |
319 | /// @param name variable name |
320 | /// @param[out] err Error details, if any |
321 | /// @return Old value |
322 | Object window_del_var(Window window, String name, Error *err) |
323 | { |
324 | win_T *win = find_window_by_handle(window, err); |
325 | |
326 | if (!win) { |
327 | return (Object) OBJECT_INIT; |
328 | } |
329 | |
330 | return dict_set_var(win->w_vars, name, NIL, true, true, err); |
331 | } |
332 | |
333 | /// Gets a window option value |
334 | /// |
335 | /// @param window Window handle, or 0 for current window |
336 | /// @param name Option name |
337 | /// @param[out] err Error details, if any |
338 | /// @return Option value |
339 | Object nvim_win_get_option(Window window, String name, Error *err) |
340 | FUNC_API_SINCE(1) |
341 | { |
342 | win_T *win = find_window_by_handle(window, err); |
343 | |
344 | if (!win) { |
345 | return (Object) OBJECT_INIT; |
346 | } |
347 | |
348 | return get_option_from(win, SREQ_WIN, name, err); |
349 | } |
350 | |
351 | /// Sets a window option value. Passing 'nil' as value deletes the option(only |
352 | /// works if there's a global fallback) |
353 | /// |
354 | /// @param channel_id |
355 | /// @param window Window handle, or 0 for current window |
356 | /// @param name Option name |
357 | /// @param value Option value |
358 | /// @param[out] err Error details, if any |
359 | void nvim_win_set_option(uint64_t channel_id, Window window, |
360 | String name, Object value, Error *err) |
361 | FUNC_API_SINCE(1) |
362 | { |
363 | win_T *win = find_window_by_handle(window, err); |
364 | |
365 | if (!win) { |
366 | return; |
367 | } |
368 | |
369 | set_option_to(channel_id, win, SREQ_WIN, name, value, err); |
370 | } |
371 | |
372 | /// Gets the window position in display cells. First position is zero. |
373 | /// |
374 | /// @param window Window handle, or 0 for current window |
375 | /// @param[out] err Error details, if any |
376 | /// @return (row, col) tuple with the window position |
377 | ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) |
378 | FUNC_API_SINCE(1) |
379 | { |
380 | Array rv = ARRAY_DICT_INIT; |
381 | win_T *win = find_window_by_handle(window, err); |
382 | |
383 | if (win) { |
384 | ADD(rv, INTEGER_OBJ(win->w_winrow)); |
385 | ADD(rv, INTEGER_OBJ(win->w_wincol)); |
386 | } |
387 | |
388 | return rv; |
389 | } |
390 | |
391 | /// Gets the window tabpage |
392 | /// |
393 | /// @param window Window handle, or 0 for current window |
394 | /// @param[out] err Error details, if any |
395 | /// @return Tabpage that contains the window |
396 | Tabpage nvim_win_get_tabpage(Window window, Error *err) |
397 | FUNC_API_SINCE(1) |
398 | { |
399 | Tabpage rv = 0; |
400 | win_T *win = find_window_by_handle(window, err); |
401 | |
402 | if (win) { |
403 | rv = win_find_tabpage(win)->handle; |
404 | } |
405 | |
406 | return rv; |
407 | } |
408 | |
409 | /// Gets the window number |
410 | /// |
411 | /// @param window Window handle, or 0 for current window |
412 | /// @param[out] err Error details, if any |
413 | /// @return Window number |
414 | Integer nvim_win_get_number(Window window, Error *err) |
415 | FUNC_API_SINCE(1) |
416 | { |
417 | int rv = 0; |
418 | win_T *win = find_window_by_handle(window, err); |
419 | |
420 | if (!win) { |
421 | return rv; |
422 | } |
423 | |
424 | int tabnr; |
425 | win_get_tabwin(window, &tabnr, &rv); |
426 | |
427 | return rv; |
428 | } |
429 | |
430 | /// Checks if a window is valid |
431 | /// |
432 | /// @param window Window handle, or 0 for current window |
433 | /// @return true if the window is valid, false otherwise |
434 | Boolean nvim_win_is_valid(Window window) |
435 | FUNC_API_SINCE(1) |
436 | { |
437 | Error stub = ERROR_INIT; |
438 | Boolean ret = find_window_by_handle(window, &stub) != NULL; |
439 | api_clear_error(&stub); |
440 | return ret; |
441 | } |
442 | |
443 | |
444 | /// Configures window layout. Currently only for floating and external windows |
445 | /// (including changing a split window to those layouts). |
446 | /// |
447 | /// When reconfiguring a floating window, absent option keys will not be |
448 | /// changed. `row`/`col` and `relative` must be reconfigured together. |
449 | /// |
450 | /// @see |nvim_open_win()| |
451 | /// |
452 | /// @param window Window handle, or 0 for current window |
453 | /// @param config Map defining the window configuration, |
454 | /// see |nvim_open_win()| |
455 | /// @param[out] err Error details, if any |
456 | void nvim_win_set_config(Window window, Dictionary config, Error *err) |
457 | FUNC_API_SINCE(6) |
458 | { |
459 | win_T *win = find_window_by_handle(window, err); |
460 | if (!win) { |
461 | return; |
462 | } |
463 | bool new_float = !win->w_floating; |
464 | // reuse old values, if not overriden |
465 | FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; |
466 | |
467 | if (!parse_float_config(config, &fconfig, !new_float, err)) { |
468 | return; |
469 | } |
470 | if (new_float) { |
471 | if (!win_new_float(win, fconfig, err)) { |
472 | return; |
473 | } |
474 | redraw_later(NOT_VALID); |
475 | } else { |
476 | win_config_float(win, fconfig); |
477 | win->w_pos_changed = true; |
478 | } |
479 | if (fconfig.style == kWinStyleMinimal) { |
480 | win_set_minimal_style(win); |
481 | didset_window_options(win); |
482 | } |
483 | } |
484 | |
485 | /// Gets window configuration. |
486 | /// |
487 | /// The returned value may be given to |nvim_open_win()|. |
488 | /// |
489 | /// `relative` is empty for normal windows. |
490 | /// |
491 | /// @param window Window handle, or 0 for current window |
492 | /// @param[out] err Error details, if any |
493 | /// @return Map defining the window configuration, see |nvim_open_win()| |
494 | Dictionary nvim_win_get_config(Window window, Error *err) |
495 | FUNC_API_SINCE(6) |
496 | { |
497 | Dictionary rv = ARRAY_DICT_INIT; |
498 | |
499 | win_T *wp = find_window_by_handle(window, err); |
500 | if (!wp) { |
501 | return rv; |
502 | } |
503 | |
504 | FloatConfig *config = &wp->w_float_config; |
505 | |
506 | PUT(rv, "focusable" , BOOLEAN_OBJ(config->focusable)); |
507 | PUT(rv, "external" , BOOLEAN_OBJ(config->external)); |
508 | |
509 | if (wp->w_floating) { |
510 | PUT(rv, "width" , INTEGER_OBJ(config->width)); |
511 | PUT(rv, "height" , INTEGER_OBJ(config->height)); |
512 | if (!config->external) { |
513 | if (config->relative == kFloatRelativeWindow) { |
514 | PUT(rv, "win" , INTEGER_OBJ(config->window)); |
515 | if (config->bufpos.lnum >= 0) { |
516 | Array pos = ARRAY_DICT_INIT; |
517 | ADD(pos, INTEGER_OBJ(config->bufpos.lnum)); |
518 | ADD(pos, INTEGER_OBJ(config->bufpos.col)); |
519 | PUT(rv, "bufpos" , ARRAY_OBJ(pos)); |
520 | } |
521 | } |
522 | PUT(rv, "anchor" , STRING_OBJ(cstr_to_string( |
523 | float_anchor_str[config->anchor]))); |
524 | PUT(rv, "row" , FLOAT_OBJ(config->row)); |
525 | PUT(rv, "col" , FLOAT_OBJ(config->col)); |
526 | } |
527 | } |
528 | |
529 | const char *rel = (wp->w_floating && !config->external |
530 | ? float_relative_str[config->relative] : "" ); |
531 | PUT(rv, "relative" , STRING_OBJ(cstr_to_string(rel))); |
532 | |
533 | return rv; |
534 | } |
535 | |
536 | /// Closes the window (like |:close| with a |window-ID|). |
537 | /// |
538 | /// @param window Window handle, or 0 for current window |
539 | /// @param force Behave like `:close!` The last window of a buffer with |
540 | /// unwritten changes can be closed. The buffer will become |
541 | /// hidden, even if 'hidden' is not set. |
542 | /// @param[out] err Error details, if any |
543 | void nvim_win_close(Window window, Boolean force, Error *err) |
544 | FUNC_API_SINCE(6) |
545 | { |
546 | win_T *win = find_window_by_handle(window, err); |
547 | if (!win) { |
548 | return; |
549 | } |
550 | |
551 | if (cmdwin_type != 0) { |
552 | if (win == curwin) { |
553 | cmdwin_result = Ctrl_C; |
554 | } else { |
555 | api_set_error(err, kErrorTypeException, "%s" , _(e_cmdwin)); |
556 | } |
557 | return; |
558 | } |
559 | |
560 | tabpage_T *tabpage = win_find_tabpage(win); |
561 | TryState tstate; |
562 | try_enter(&tstate); |
563 | ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); |
564 | vim_ignored = try_leave(&tstate, err); |
565 | } |
566 | |