diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/buffer.c | 370 | ||||
-rw-r--r-- | src/nvim/api/dispatch_deprecated.lua | 69 | ||||
-rw-r--r-- | src/nvim/api/private/defs.h | 20 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 45 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.h | 23 | ||||
-rw-r--r-- | src/nvim/api/private/handle.c | 38 | ||||
-rw-r--r-- | src/nvim/api/private/handle.h | 7 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 443 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 105 | ||||
-rw-r--r-- | src/nvim/api/tabpage.c | 118 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 424 | ||||
-rw-r--r-- | src/nvim/api/ui.h | 11 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 432 | ||||
-rw-r--r-- | src/nvim/api/window.c | 189 |
14 files changed, 1642 insertions, 652 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 55b535c78c..eaaae943d2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -14,7 +14,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/ex_cmds.h" #include "nvim/mark.h" #include "nvim/fileio.h" @@ -29,10 +28,10 @@ /// Gets the buffer line count /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The line count -Integer buffer_line_count(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Line count +Integer nvim_buf_line_count(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -45,22 +44,22 @@ Integer buffer_line_count(Buffer buffer, Error *err) /// Gets a buffer line /// -/// @deprecated use buffer_get_lines instead. +/// @deprecated use nvim_buf_get_lines instead. /// for positive indices (including 0) use -/// "buffer_get_lines(buffer, index, index+1, true)" +/// "nvim_buf_get_lines(buffer, index, index+1, true)" /// for negative indices use -/// "buffer_get_lines(buffer, index-1, index, true)" +/// "nvim_buf_get_lines(buffer, index-1, index, true)" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred -/// @return The line string +/// @param buffer Buffer handle +/// @param index Line index +/// @param[out] err Error details, if any +/// @return Line string String buffer_get_line(Buffer buffer, Integer index, Error *err) { String rv = { .size = 0 }; index = convert_index(index); - Array slice = buffer_get_lines(buffer, index, index+1, true, err); + Array slice = nvim_buf_get_lines(0, buffer, index, index+1, true, err); if (!err->set && slice.size) { rv = slice.items[0].data.string; @@ -73,64 +72,64 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// Sets a buffer line /// -/// @deprecated use buffer_set_lines instead. +/// @deprecated use nvim_buf_set_lines instead. /// for positive indices use -/// "buffer_set_lines(buffer, index, index+1, true, [line])" +/// "nvim_buf_set_lines(buffer, index, index+1, true, [line])" /// for negative indices use -/// "buffer_set_lines(buffer, index-1, index, true, [line])" +/// "nvim_buf_set_lines(buffer, index-1, index, true, [line])" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param line The new line. -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param index Line index +/// @param line Contents of the new line +/// @param[out] err Error details, if any void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) { Object l = STRING_OBJ(line); Array array = { .items = &l, .size = 1 }; index = convert_index(index); - buffer_set_lines(buffer, index, index+1, true, array, err); + nvim_buf_set_lines(0, buffer, index, index+1, true, array, err); } /// Deletes a buffer line /// -/// @deprecated use buffer_set_lines instead. +/// @deprecated use nvim_buf_set_lines instead. /// for positive indices use -/// "buffer_set_lines(buffer, index, index+1, true, [])" +/// "nvim_buf_set_lines(buffer, index, index+1, true, [])" /// for negative indices use -/// "buffer_set_lines(buffer, index-1, index, true, [])" -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred +/// "nvim_buf_set_lines(buffer, index-1, index, true, [])" +/// @param buffer buffer handle +/// @param index line index +/// @param[out] err Error details, if any void buffer_del_line(Buffer buffer, Integer index, Error *err) { Array array = ARRAY_DICT_INIT; index = convert_index(index); - buffer_set_lines(buffer, index, index+1, true, array, err); + nvim_buf_set_lines(0, buffer, index, index+1, true, array, err); } /// Retrieves a line range from the buffer /// -/// @deprecated use buffer_get_lines(buffer, newstart, newend, false) +/// @deprecated use nvim_buf_get_lines(buffer, newstart, newend, false) /// where newstart = start + int(not include_start) - int(start < 0) /// newend = end + int(include_end) - int(end < 0) /// int(bool) = 1 if bool is true else 0 -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param[out] err Error details, if any +/// @return Array of lines ArrayOf(String) buffer_get_line_slice(Buffer buffer, - Integer start, - Integer end, - Boolean include_start, - Boolean include_end, - Error *err) + Integer start, + Integer end, + Boolean include_start, + Boolean include_end, + Error *err) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - return buffer_get_lines(buffer, start , end, false, err); + return nvim_buf_get_lines(0, buffer, start , end, false, err); } @@ -143,17 +142,18 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines -ArrayOf(String) buffer_get_lines(Buffer buffer, - Integer start, - Integer end, - Boolean strict_indexing, - Error *err) +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param[out] err Error details, if any +/// @return Array of lines +ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, + Buffer buffer, + Integer start, + Integer end, + Boolean strict_indexing, + Error *err) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -191,7 +191,9 @@ ArrayOf(String) buffer_get_lines(Buffer buffer, 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'); + if (channel_id != INTERNAL_CALL) { + strchrsub(str.data.string.data, '\n', '\0'); + } rv.items[i] = str; } @@ -212,30 +214,30 @@ end: /// Replaces a line range on the buffer /// -/// @deprecated use buffer_set_lines(buffer, newstart, newend, false, lines) +/// @deprecated use nvim_buf_set_lines(buffer, newstart, newend, false, lines) /// where newstart = start + int(not include_start) + int(start < 0) /// newend = end + int(include_end) + int(end < 0) /// int(bool) = 1 if bool is true else 0 /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param replacement An array of lines to use as replacement(A 0-length -// array will simply delete the line range) -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param replacement Array of lines to use as replacement (0-length +// array will delete the line range) +/// @param[out] err Error details, if any void buffer_set_line_slice(Buffer buffer, - Integer start, - Integer end, - Boolean include_start, - Boolean include_end, - ArrayOf(String) replacement, - Error *err) + Integer start, + Integer end, + Boolean include_start, + Boolean include_end, + ArrayOf(String) replacement, // NOLINT + Error *err) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - buffer_set_lines(buffer, start, end, false, replacement, err); + nvim_buf_set_lines(0, buffer, start, end, false, replacement, err); } @@ -251,18 +253,19 @@ void buffer_set_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param replacement An array of lines to use as replacement -/// @param[out] err Details of an error that may have occurred -void buffer_set_lines(Buffer buffer, - Integer start, - Integer end, - Boolean strict_indexing, - ArrayOf(String) replacement, - Error *err) +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param replacement Array of lines to use as replacement +/// @param[out] err Error details, if any +void nvim_buf_set_lines(uint64_t channel_id, + Buffer buffer, + Integer start, + Integer end, + Boolean strict_indexing, + ArrayOf(String) replacement, // NOLINT + Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -309,7 +312,7 @@ void buffer_set_lines(Buffer buffer, // line and convert NULs to newlines to avoid truncation. lines[i] = xmallocz(l.size); for (size_t j = 0; j < l.size; j++) { - if (l.data[j] == '\n') { + if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) { api_set_error(err, Exception, _("string cannot contain newlines")); new_len = i + 1; goto end; @@ -408,11 +411,11 @@ end: /// Gets a buffer-scoped (b:) variable. /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object buffer_get_var(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_buf_get_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -425,11 +428,46 @@ Object buffer_get_var(Buffer buffer, String name, Error *err) /// Sets a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param buffer Buffer handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return; + } + + dict_set_value(buf->b_vars, name, value, false, false, err); +} + +/// Removes a buffer-scoped (b:) variable +/// +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +void nvim_buf_del_var(Buffer buffer, String name, Error *err) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return; + } + + dict_set_value(buf->b_vars, name, NIL, true, false, err); +} + +/// Sets a buffer-scoped (b:) variable +/// +/// @deprecated +/// +/// @param buffer Buffer handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -441,18 +479,17 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(buf->b_vars, name, value, false, err); + return dict_set_value(buf->b_vars, name, value, false, true, err); } /// Removes a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object buffer_del_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -461,16 +498,17 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(buf->b_vars, name, NIL, true, err); + return dict_set_value(buf->b_vars, name, NIL, true, true, err); } + /// Gets a buffer option value /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object buffer_get_option(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_buf_get_option(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -481,14 +519,14 @@ Object buffer_get_option(Buffer buffer, String name, Error *err) return get_option_from(buf, SREQ_BUF, name, err); } -/// Sets a buffer option value. Passing 'nil' as value deletes the option(only +/// Sets a buffer option value. Passing 'nil' as value deletes the option (only /// works if there's a global fallback) /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred -void buffer_set_option(Buffer buffer, String name, Object value, Error *err) +/// @param buffer Buffer handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any +void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -501,10 +539,10 @@ void buffer_set_option(Buffer buffer, String name, Object value, Error *err) /// Gets the buffer number /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer number -Integer buffer_get_number(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Buffer number +Integer nvim_buf_get_number(Buffer buffer, Error *err) { Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -518,10 +556,10 @@ Integer buffer_get_number(Buffer buffer, Error *err) /// Gets the full file name for the buffer /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer name -String buffer_get_name(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Buffer name +String nvim_buf_get_name(Buffer buffer, Error *err) { String rv = STRING_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -535,10 +573,10 @@ String buffer_get_name(Buffer buffer, Error *err) /// Sets the full file name for a buffer /// -/// @param buffer The buffer handle -/// @param name The buffer name -/// @param[out] err Details of an error that may have occurred -void buffer_set_name(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Buffer name +/// @param[out] err Error details, if any +void nvim_buf_set_name(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -565,9 +603,9 @@ void buffer_set_name(Buffer buffer, String name, Error *err) /// Checks if a buffer is valid /// -/// @param buffer The buffer handle +/// @param buffer Buffer handle /// @return true if the buffer is valid, false otherwise -Boolean buffer_is_valid(Buffer buffer) +Boolean nvim_buf_is_valid(Buffer buffer) { Error stub = ERROR_INIT; return find_buffer_by_handle(buffer, &stub) != NULL; @@ -575,13 +613,13 @@ Boolean buffer_is_valid(Buffer buffer) /// Inserts a sequence of lines to a buffer at a certain index /// -/// @deprecated use buffer_set_lines(buffer, lnum, lnum, true, lines) +/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines) /// -/// @param buffer The buffer handle -/// @param lnum Insert the lines after `lnum`. If negative, it will append -/// to the end of the buffer. -/// @param lines An array of lines -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param lnum Insert the lines after `lnum`. If negative, appends to +/// the end of the buffer. +/// @param lines Array of lines +/// @param[out] err Error details, if any void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, @@ -589,16 +627,16 @@ void buffer_insert(Buffer buffer, { // "lnum" will be the index of the line after inserting, // no matter if it is negative or not - buffer_set_lines(buffer, lnum, lnum, true, lines, err); + nvim_buf_set_lines(0, buffer, lnum, lnum, true, lines, err); } /// Return a tuple (row,col) representing the position of the named mark /// -/// @param buffer The buffer handle -/// @param name The mark's name -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple -ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Mark name +/// @param[out] err Error details, if any +/// @return (row, col) tuple +ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -648,7 +686,7 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) /// called with src_id = 0, an unique source id is generated and returned. /// Succesive calls can pass in it as "src_id" to add new highlights to the same /// source group. All highlights in the same group can then be cleared with -/// buffer_clear_highlight. If the highlight never will be manually deleted +/// nvim_buf_clear_highlight. If the highlight never will be manually deleted /// pass in -1 for "src_id". /// /// If "hl_group" is the empty string no highlight is added, but a new src_id @@ -656,23 +694,23 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) /// request an unique src_id at initialization, and later asynchronously add and /// clear highlights in response to buffer changes. /// -/// @param buffer The buffer handle -/// @param src_id Source group to use or 0 to use a new group, -/// or -1 for ungrouped highlight -/// @param hl_group Name of the highlight group to use -/// @param line The line to highlight -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param src_id Source group to use or 0 to use a new group, +/// or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight +/// @param col_start Start of range of columns to highlight +/// @param col_end End of range of columns to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any /// @return The src_id that was used -Integer buffer_add_highlight(Buffer buffer, - Integer src_id, - String hl_group, - Integer line, - Integer col_start, - Integer col_end, - Error *err) +Integer nvim_buf_add_highlight(Buffer buffer, + Integer src_id, + String hl_group, + Integer line, + Integer col_start, + Integer col_end, + Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -691,7 +729,7 @@ Integer buffer_add_highlight(Buffer buffer, col_end = MAXCOL; } - int hlg_id = syn_name2id((char_u*)hl_group.data); + int hlg_id = syn_name2id((char_u *)(hl_group.data ? hl_group.data : "")); src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1, (colnr_T)col_start+1, (colnr_T)col_end); return src_id; @@ -702,17 +740,17 @@ Integer buffer_add_highlight(Buffer buffer, /// To clear a source group in the entire buffer, pass in 1 and -1 to /// line_start and line_end respectively. /// -/// @param buffer The buffer handle -/// @param src_id Highlight source group to clear, or -1 to clear all groups. +/// @param buffer Buffer handle +/// @param src_id Highlight source group to clear, or -1 to clear all. /// @param line_start Start of range of lines to clear -/// @param line_end End of range of lines to clear (exclusive) -/// or -1 to clear to end of file. -/// @param[out] err Details of an error that may have occurred -void buffer_clear_highlight(Buffer buffer, - Integer src_id, - Integer line_start, - Integer line_end, - Error *err) +/// @param line_end End of range of lines to clear (exclusive) or -1 to clear +/// to end of file. +/// @param[out] err Error details, if any +void nvim_buf_clear_highlight(Buffer buffer, + Integer src_id, + Integer line_start, + Integer line_end, + Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { diff --git a/src/nvim/api/dispatch_deprecated.lua b/src/nvim/api/dispatch_deprecated.lua new file mode 100644 index 0000000000..5650a77ac0 --- /dev/null +++ b/src/nvim/api/dispatch_deprecated.lua @@ -0,0 +1,69 @@ +local deprecated_aliases = { + nvim_buf_add_highlight="buffer_add_highlight", + nvim_buf_clear_highlight="buffer_clear_highlight", + nvim_buf_get_lines="buffer_get_lines", + nvim_buf_get_mark="buffer_get_mark", + nvim_buf_get_name="buffer_get_name", + nvim_buf_get_number="buffer_get_number", + nvim_buf_get_option="buffer_get_option", + nvim_buf_get_var="buffer_get_var", + nvim_buf_is_valid="buffer_is_valid", + nvim_buf_line_count="buffer_line_count", + nvim_buf_set_lines="buffer_set_lines", + nvim_buf_set_name="buffer_set_name", + nvim_buf_set_option="buffer_set_option", + nvim_call_function="vim_call_function", + nvim_command="vim_command", + nvim_command_output="vim_command_output", + nvim_del_current_line="vim_del_current_line", + nvim_err_write="vim_err_write", + nvim_err_writeln="vim_report_error", + nvim_eval="vim_eval", + nvim_feedkeys="vim_feedkeys", + nvim_get_api_info="vim_get_api_info", + nvim_get_color_by_name="vim_name_to_color", + nvim_get_color_map="vim_get_color_map", + nvim_get_current_buf="vim_get_current_buffer", + nvim_get_current_line="vim_get_current_line", + nvim_get_current_tabpage="vim_get_current_tabpage", + nvim_get_current_win="vim_get_current_window", + nvim_get_option="vim_get_option", + nvim_get_var="vim_get_var", + nvim_get_vvar="vim_get_vvar", + nvim_input="vim_input", + nvim_list_bufs="vim_get_buffers", + nvim_list_runtime_paths="vim_list_runtime_paths", + nvim_list_tabpages="vim_get_tabpages", + nvim_list_wins="vim_get_windows", + nvim_out_write="vim_out_write", + nvim_replace_termcodes="vim_replace_termcodes", + nvim_set_current_buf="vim_set_current_buffer", + nvim_set_current_dir="vim_change_directory", + nvim_set_current_line="vim_set_current_line", + nvim_set_current_tabpage="vim_set_current_tabpage", + nvim_set_current_win="vim_set_current_window", + nvim_set_option="vim_set_option", + nvim_strwidth="vim_strwidth", + nvim_subscribe="vim_subscribe", + nvim_tabpage_get_var="tabpage_get_var", + nvim_tabpage_get_win="tabpage_get_window", + nvim_tabpage_is_valid="tabpage_is_valid", + nvim_tabpage_list_wins="tabpage_get_windows", + nvim_ui_detach="ui_detach", + nvim_ui_try_resize="ui_try_resize", + nvim_unsubscribe="vim_unsubscribe", + nvim_win_get_buf="window_get_buffer", + nvim_win_get_cursor="window_get_cursor", + nvim_win_get_height="window_get_height", + nvim_win_get_option="window_get_option", + nvim_win_get_position="window_get_position", + nvim_win_get_tabpage="window_get_tabpage", + nvim_win_get_var="window_get_var", + nvim_win_get_width="window_get_width", + nvim_win_is_valid="window_is_valid", + nvim_win_set_cursor="window_set_cursor", + nvim_win_set_height="window_set_height", + nvim_win_set_option="window_set_option", + nvim_win_set_width="window_set_width", +} +return deprecated_aliases diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index fbfa87d5ae..223aab09dc 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -9,13 +9,15 @@ #define STRING_INIT {.data = NULL, .size = 0} #define OBJECT_INIT { .type = kObjectTypeNil } #define ERROR_INIT { .set = false } -#define REMOTE_TYPE(type) typedef uint64_t type +#define REMOTE_TYPE(type) typedef handle_T type #ifdef INCLUDE_GENERATED_DECLARATIONS - #define ArrayOf(...) Array - #define DictionaryOf(...) Dictionary +# define ArrayOf(...) Array +# define DictionaryOf(...) Dictionary #endif +typedef int handle_T; + // Basic types typedef enum { kErrorTypeException, @@ -31,6 +33,9 @@ typedef enum { /// Used as the message ID of notifications. #define NO_RESPONSE UINT64_MAX +/// Used as channel_id when the call is local. +#define INTERNAL_CALL UINT64_MAX + typedef struct { ErrorType type; char msg[1024]; @@ -41,6 +46,12 @@ typedef bool Boolean; typedef int64_t Integer; typedef double Float; +/// Maximum value of an Integer +#define API_INTEGER_MAX INT64_MAX + +/// Minimum value of an Integer +#define API_INTEGER_MIN INT64_MIN + typedef struct { char *data; size_t size; @@ -80,9 +91,6 @@ typedef enum { struct object { ObjectType type; union { - Buffer buffer; - Window window; - Tabpage tabpage; Boolean boolean; Integer integer; Float floating; diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c new file mode 100644 index 0000000000..9b3bcc380a --- /dev/null +++ b/src/nvim/api/private/dispatch.c @@ -0,0 +1,45 @@ +#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> +#include <assert.h> +#include <msgpack.h> + +#include "nvim/map.h" +#include "nvim/log.h" +#include "nvim/vim.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/api/private/dispatch.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/defs.h" + +#include "nvim/api/buffer.h" +#include "nvim/api/tabpage.h" +#include "nvim/api/ui.h" +#include "nvim/api/vim.h" +#include "nvim/api/window.h" + +static Map(String, MsgpackRpcRequestHandler) *methods = NULL; + +static void msgpack_rpc_add_method_handler(String method, + MsgpackRpcRequestHandler handler) +{ + map_put(String, MsgpackRpcRequestHandler)(methods, method, handler); +} + +MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, + size_t name_len) +{ + String m = { .data = (char *)name, .size = name_len }; + MsgpackRpcRequestHandler rv = + map_get(String, MsgpackRpcRequestHandler)(methods, m); + + if (!rv.fn) { + rv.fn = msgpack_rpc_handle_missing_method; + } + + return rv; +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "api/private/dispatch_wrappers.generated.h" +#endif diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h new file mode 100644 index 0000000000..39aabd708a --- /dev/null +++ b/src/nvim/api/private/dispatch.h @@ -0,0 +1,23 @@ +#ifndef NVIM_API_PRIVATE_DISPATCH_H +#define NVIM_API_PRIVATE_DISPATCH_H + +#include "nvim/api/private/defs.h" + +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, + Array args, + Error *error); + +/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores +/// functions of this type. +typedef struct { + ApiDispatchWrapper fn; + bool async; // function is always safe to run immediately instead of being + // put in a request queue for handling when nvim waits for input. +} MsgpackRpcRequestHandler; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/private/dispatch.h.generated.h" +# include "api/private/dispatch_wrappers.h.generated.h" +#endif + +#endif // NVIM_API_PRIVATE_DISPATCH_H diff --git a/src/nvim/api/private/handle.c b/src/nvim/api/private/handle.c index abbda95073..acb0fb332a 100644 --- a/src/nvim/api/private/handle.c +++ b/src/nvim/api/private/handle.c @@ -5,30 +5,26 @@ #include "nvim/map.h" #include "nvim/api/private/handle.h" -#define HANDLE_INIT(name) name##_handles = pmap_new(uint64_t)() +#define HANDLE_INIT(name) name##_handles = pmap_new(handle_T)() -#define HANDLE_IMPL(type, name) \ - static PMap(uint64_t) *name##_handles = NULL; \ - \ - type *handle_get_##name(uint64_t handle) \ - { \ - return pmap_get(uint64_t)(name##_handles, handle); \ - } \ - \ - void handle_register_##name(type *name) \ - { \ - assert(!name->handle); \ - name->handle = next_handle++; \ - pmap_put(uint64_t)(name##_handles, name->handle, name); \ - } \ - \ - void handle_unregister_##name(type *name) \ - { \ - pmap_del(uint64_t)(name##_handles, name->handle); \ +#define HANDLE_IMPL(type, name) \ + static PMap(handle_T) *name##_handles = NULL; /* NOLINT */ \ + \ + type *handle_get_##name(handle_T handle) \ + { \ + return pmap_get(handle_T)(name##_handles, handle); \ + } \ + \ + void handle_register_##name(type *name) \ + { \ + pmap_put(handle_T)(name##_handles, name->handle, name); \ + } \ + \ + void handle_unregister_##name(type *name) \ + { \ + pmap_del(handle_T)(name##_handles, name->handle); \ } -static uint64_t next_handle = 1; - HANDLE_IMPL(buf_T, buffer) HANDLE_IMPL(win_T, window) HANDLE_IMPL(tabpage_T, tabpage) diff --git a/src/nvim/api/private/handle.h b/src/nvim/api/private/handle.h index 1a196f6797..30bbfbee1b 100644 --- a/src/nvim/api/private/handle.h +++ b/src/nvim/api/private/handle.h @@ -3,10 +3,11 @@ #include "nvim/vim.h" #include "nvim/buffer_defs.h" +#include "nvim/api/private/defs.h" -#define HANDLE_DECLS(type, name) \ - type *handle_get_##name(uint64_t handle); \ - void handle_register_##name(type *name); \ +#define HANDLE_DECLS(type, name) \ + type *handle_get_##name(handle_T handle); \ + void handle_register_##name(type *name); \ void handle_unregister_##name(type *name); HANDLE_DECLS(buf_T, buffer) diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index db3e499427..7daa4d7207 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -7,6 +7,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -17,9 +18,17 @@ #include "nvim/map.h" #include "nvim/option.h" #include "nvim/option_defs.h" +#include "nvim/version.h" +#include "nvim/lib/kvec.h" + +/// Helper structure for vim_to_object +typedef struct { + kvec_t(Object) stack; ///< Object stack. +} EncodedData; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.c.generated.h" +# include "api/private/funcs_metadata.generated.h" #endif /// Start block that may cause vimscript exceptions @@ -97,10 +106,11 @@ Object dict_get_value(dict_T *dict, String key, Error *err) /// @param value The new value /// @param del Delete key in place of setting it. Argument `value` is ignored in /// this case. +/// @param retval If true the old value will be converted and returned. /// @param[out] err Details of an error that may have occurred -/// @return the old value, if any +/// @return The old value if `retval` is true and the key was present, else NIL Object dict_set_value(dict_T *dict, String key, Object value, bool del, - Error *err) + bool retval, Error *err) { Object rv = OBJECT_INIT; @@ -128,7 +138,9 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, api_set_error(err, Validation, _("Key \"%s\" doesn't exist"), key.data); } else { // Return the old value - rv = vim_to_object(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } // Delete the entry hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key); hash_remove(&dict->dv_hashtab, hi); @@ -149,7 +161,9 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, dict_add(dict, di); } else { // Return the old value - rv = vim_to_object(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } clear_tv(&di->di_tv); } @@ -310,6 +324,201 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } } +#define TYPVAL_ENCODE_ALLOW_SPECIALS false + +#define TYPVAL_ENCODE_CONV_NIL(tv) \ + kv_push(edata->stack, NIL) + +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ + kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num))) + +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ + kv_push(edata->stack, INTEGER_OBJ((Integer)(num))) + +#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER + +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) + +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ + do { \ + const size_t len_ = (size_t)(len); \ + const char *const str_ = (const char *)(str); \ + assert(len_ == 0 || str_ != NULL); \ + kv_push(edata->stack, STRING_OBJ(((String) { \ + .data = xmemdupz((len_?str_:""), len_), \ + .size = len_ \ + }))); \ + } while (0) + +#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING + +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + TYPVAL_ENCODE_CONV_NIL(tv) + +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ + do { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + goto typval_encode_stop_converting_one_item; \ + } while (0) + +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) + +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ + kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) + +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ + kv_push(edata->stack, \ + DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) + +static inline void typval_encode_list_start(EncodedData *const edata, + const size_t len) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + kv_push(edata->stack, ARRAY_OBJ(((Array) { + .capacity = len, + .size = 0, + .items = xmalloc(len * sizeof(*((Object *)NULL)->data.array.items)), + }))); +} + +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ + typval_encode_list_start(edata, (size_t)(len)) + +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + +static inline void typval_encode_between_list_items(EncodedData *const edata) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + Object item = kv_pop(edata->stack); + Object *const list = &kv_last(edata->stack); + assert(list->type == kObjectTypeArray); + assert(list->data.array.size < list->data.array.capacity); + list->data.array.items[list->data.array.size++] = item; +} + +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ + typval_encode_between_list_items(edata) + +static inline void typval_encode_list_end(EncodedData *const edata) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + typval_encode_between_list_items(edata); +#ifndef NDEBUG + const Object *const list = &kv_last(edata->stack); + assert(list->data.array.size == list->data.array.capacity); +#endif +} + +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ + typval_encode_list_end(edata) + +static inline void typval_encode_dict_start(EncodedData *const edata, + const size_t len) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + kv_push(edata->stack, DICTIONARY_OBJ(((Dictionary) { + .capacity = len, + .size = 0, + .items = xmalloc(len * sizeof(*((Object *)NULL)->data.dictionary.items)), + }))); +} + +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ + typval_encode_dict_start(edata, (size_t)(len)) + +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) + +static inline void typval_encode_after_key(EncodedData *const edata) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + Object key = kv_pop(edata->stack); + Object *const dict = &kv_last(edata->stack); + assert(dict->type == kObjectTypeDictionary); + assert(dict->data.dictionary.size < dict->data.dictionary.capacity); + if (key.type == kObjectTypeString) { + dict->data.dictionary.items[dict->data.dictionary.size].key + = key.data.string; + } else { + api_free_object(key); + dict->data.dictionary.items[dict->data.dictionary.size].key + = STATIC_CSTR_TO_STRING("__INVALID_KEY__"); + } +} + +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ + typval_encode_after_key(edata) + +static inline void typval_encode_between_dict_items(EncodedData *const edata) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + Object val = kv_pop(edata->stack); + Object *const dict = &kv_last(edata->stack); + assert(dict->type == kObjectTypeDictionary); + assert(dict->data.dictionary.size < dict->data.dictionary.capacity); + dict->data.dictionary.items[dict->data.dictionary.size++].value = val; +} + +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ + typval_encode_between_dict_items(edata) + +static inline void typval_encode_dict_end(EncodedData *const edata) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL +{ + typval_encode_between_dict_items(edata); +#ifndef NDEBUG + const Object *const dict = &kv_last(edata->stack); + assert(dict->data.dictionary.size == dict->data.dictionary.capacity); +#endif +} + +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ + typval_encode_dict_end(edata) + +#define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ + TYPVAL_ENCODE_CONV_NIL() + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME object +#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME edata +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME + +#undef TYPVAL_ENCODE_CONV_STRING +#undef TYPVAL_ENCODE_CONV_STR_STRING +#undef TYPVAL_ENCODE_CONV_EXT_STRING +#undef TYPVAL_ENCODE_CONV_NUMBER +#undef TYPVAL_ENCODE_CONV_FLOAT +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END +#undef TYPVAL_ENCODE_CONV_EMPTY_LIST +#undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START +#undef TYPVAL_ENCODE_CONV_EMPTY_DICT +#undef TYPVAL_ENCODE_CONV_NIL +#undef TYPVAL_ENCODE_CONV_BOOL +#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER +#undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START +#undef TYPVAL_ENCODE_CONV_DICT_END +#undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY +#undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_CONV_LIST_END +#undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS +#undef TYPVAL_ENCODE_CONV_RECURSE +#undef TYPVAL_ENCODE_ALLOW_SPECIALS + /// Convert a vim object to an `Object` instance, recursively expanding /// Arrays/Dictionaries. /// @@ -317,17 +526,23 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) /// @return The converted value Object vim_to_object(typval_T *obj) { - Object rv; - // We use a lookup table to break out of cyclic references - PMap(ptr_t) *lookup = pmap_new(ptr_t)(); - rv = vim_to_object_rec(obj, lookup); - // Free the table - pmap_free(ptr_t)(lookup); - return rv; + EncodedData edata = { .stack = KV_INITIAL_VALUE }; + const int evo_ret = encode_vim_to_object(&edata, obj, + "vim_to_object argument"); + (void)evo_ret; + assert(evo_ret == OK); + Object ret = kv_A(edata.stack, 0); + assert(kv_size(edata.stack) == 1); + kv_destroy(edata.stack); + return ret; } buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { + if (buffer == 0) { + return curbuf; + } + buf_T *rv = handle_get_buffer(buffer); if (!rv) { @@ -339,6 +554,10 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err) win_T * find_window_by_handle(Window window, Error *err) { + if (window == 0) { + return curwin; + } + win_T *rv = handle_get_window(window); if (!rv) { @@ -350,6 +569,10 @@ win_T * find_window_by_handle(Window window, Error *err) tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) { + if (tabpage == 0) { + return curtab; + } + tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { @@ -393,10 +616,16 @@ String cstr_as_string(char *str) FUNC_ATTR_PURE return (String) {.data = str, .size = strlen(str)}; } +/// Converts from type Object to a VimL value. +/// +/// @param obj Object to convert from. +/// @param tv Conversion result is placed here. On failure member v_type is +/// set to VAR_UNKNOWN (no allocation was made for this variable). +/// returns true if conversion is successful, otherwise false. bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; - tv->v_lock = 0; + tv->v_lock = VAR_UNLOCKED; switch (obj.type) { case kObjectTypeNil: @@ -413,13 +642,14 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeWindow: case kObjectTypeTabpage: case kObjectTypeInteger: - if (obj.data.integer > INT_MAX || obj.data.integer < INT_MIN) { + if (obj.data.integer > VARNUMBER_MAX + || obj.data.integer < VARNUMBER_MIN) { api_set_error(err, Validation, _("Integer value outside range")); return false; } tv->v_type = VAR_NUMBER; - tv->vval.v_number = (int)obj.data.integer; + tv->vval.v_number = (varnumber_T)obj.data.integer; break; case kObjectTypeFloat: @@ -437,9 +667,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } break; - case kObjectTypeArray: - tv->v_type = VAR_LIST; - tv->vval.v_list = list_alloc(); + case kObjectTypeArray: { + list_T *list = list_alloc(); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; @@ -448,45 +677,51 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) if (!object_to_vim(item, &li->li_tv, err)) { // cleanup listitem_free(li); - list_free(tv->vval.v_list, true); + list_free(list); return false; } - list_append(tv->vval.v_list, li); + list_append(list, li); } - tv->vval.v_list->lv_refcount++; + list->lv_refcount++; + + tv->v_type = VAR_LIST; + tv->vval.v_list = list; break; + } - case kObjectTypeDictionary: - tv->v_type = VAR_DICT; - tv->vval.v_dict = dict_alloc(); + case kObjectTypeDictionary: { + dict_T *dict = dict_alloc(); for (uint32_t i = 0; i < obj.data.dictionary.size; i++) { KeyValuePair item = obj.data.dictionary.items[i]; String key = item.key; if (key.size == 0) { - api_set_error(err, - Validation, + api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); // cleanup - dict_free(tv->vval.v_dict, true); + dict_free(dict); return false; } - dictitem_T *di = dictitem_alloc((uint8_t *) key.data); + dictitem_T *di = dictitem_alloc((uint8_t *)key.data); if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup dictitem_free(di); - dict_free(tv->vval.v_dict, true); + dict_free(dict); return false; } - dict_add(tv->vval.v_dict, di); + dict_add(dict, di); } - tv->vval.v_dict->dv_refcount++; + dict->dv_refcount++; + + tv->v_type = VAR_DICT; + tv->vval.v_dict = dict; break; + } default: abort(); } @@ -556,7 +791,8 @@ Dictionary api_metadata(void) static Dictionary metadata = ARRAY_DICT_INIT; if (!metadata.size) { - msgpack_rpc_init_function_metadata(&metadata); + PUT(metadata, "version", DICTIONARY_OBJ(version_dict())); + init_function_metadata(&metadata); init_error_type_metadata(&metadata); init_type_metadata(&metadata); } @@ -564,6 +800,22 @@ Dictionary api_metadata(void) return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; } +static void init_function_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)funcs_metadata, + sizeof(funcs_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object functions; + msgpack_rpc_to_object(&unpacked.data, &functions); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "functions", functions); +} + static void init_error_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; @@ -579,18 +831,22 @@ static void init_error_type_metadata(Dictionary *metadata) PUT(*metadata, "error_types", DICTIONARY_OBJ(types)); } + static void init_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; Dictionary buffer_metadata = ARRAY_DICT_INIT; PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer)); + PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_"))); Dictionary window_metadata = ARRAY_DICT_INIT; PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow)); + PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_"))); Dictionary tabpage_metadata = ARRAY_DICT_INIT; PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage)); + PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_"))); PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); PUT(types, "Window", DICTIONARY_OBJ(window_metadata)); @@ -633,131 +889,6 @@ Object copy_object(Object obj) } } -/// Recursion helper for the `vim_to_object`. This uses a pointer table -/// to avoid infinite recursion due to cyclic references -/// -/// @param obj The source object -/// @param lookup Lookup table containing pointers to all processed objects -/// @return The converted value -static Object vim_to_object_rec(typval_T *obj, PMap(ptr_t) *lookup) -{ - Object rv = OBJECT_INIT; - - if (obj->v_type == VAR_LIST || obj->v_type == VAR_DICT) { - // Container object, add it to the lookup table - if (pmap_has(ptr_t)(lookup, obj)) { - // It's already present, meaning we alredy processed it so just return - // nil instead. - return rv; - } - pmap_put(ptr_t)(lookup, obj, NULL); - } - - switch (obj->v_type) { - case VAR_SPECIAL: - switch (obj->vval.v_special) { - case kSpecialVarTrue: - case kSpecialVarFalse: { - rv.type = kObjectTypeBoolean; - rv.data.boolean = (obj->vval.v_special == kSpecialVarTrue); - break; - } - case kSpecialVarNull: { - rv.type = kObjectTypeNil; - break; - } - } - break; - - case VAR_STRING: - rv.type = kObjectTypeString; - rv.data.string = cstr_to_string((char *) obj->vval.v_string); - break; - - case VAR_NUMBER: - rv.type = kObjectTypeInteger; - rv.data.integer = obj->vval.v_number; - break; - - case VAR_FLOAT: - rv.type = kObjectTypeFloat; - rv.data.floating = obj->vval.v_float; - break; - - case VAR_LIST: - { - list_T *list = obj->vval.v_list; - listitem_T *item; - - if (list != NULL) { - rv.type = kObjectTypeArray; - assert(list->lv_len >= 0); - rv.data.array.size = (size_t)list->lv_len; - rv.data.array.items = xmalloc(rv.data.array.size * sizeof(Object)); - - uint32_t i = 0; - for (item = list->lv_first; item != NULL; item = item->li_next) { - rv.data.array.items[i] = vim_to_object_rec(&item->li_tv, lookup); - i++; - } - } - } - break; - - case VAR_DICT: - { - dict_T *dict = obj->vval.v_dict; - hashtab_T *ht; - uint64_t todo; - hashitem_T *hi; - dictitem_T *di; - - if (dict != NULL) { - ht = &obj->vval.v_dict->dv_hashtab; - todo = ht->ht_used; - rv.type = kObjectTypeDictionary; - - // Count items - rv.data.dictionary.size = 0; - for (hi = ht->ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - rv.data.dictionary.size++; - } - } - - rv.data.dictionary.items = - xmalloc(rv.data.dictionary.size * sizeof(KeyValuePair)); - todo = ht->ht_used; - uint32_t i = 0; - - // Convert all - for (hi = ht->ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - di = dict_lookup(hi); - // Convert key - rv.data.dictionary.items[i].key = - cstr_to_string((char *) hi->hi_key); - // Convert value - rv.data.dictionary.items[i].value = - vim_to_object_rec(&di->di_tv, lookup); - todo--; - i++; - } - } - } - } - break; - - case VAR_UNKNOWN: - case VAR_FUNC: - break; - } - - return rv; -} - - static void set_option_value_for(char *key, int numval, char *stringval, diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index a0f14ac7a4..9fe8c351cf 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -8,69 +8,70 @@ #include "nvim/memory.h" #include "nvim/lib/kvec.h" -#define api_set_error(err, errtype, ...) \ - do { \ - snprintf((err)->msg, \ - sizeof((err)->msg), \ - __VA_ARGS__); \ - (err)->set = true; \ - (err)->type = kErrorType##errtype; \ +#define api_set_error(err, errtype, ...) \ + do { \ + snprintf((err)->msg, \ + sizeof((err)->msg), \ + __VA_ARGS__); \ + (err)->set = true; \ + (err)->type = kErrorType##errtype; \ } while (0) #define OBJECT_OBJ(o) o -#define BOOLEAN_OBJ(b) ((Object) { \ - .type = kObjectTypeBoolean, \ - .data.boolean = b \ - }) - -#define INTEGER_OBJ(i) ((Object) { \ - .type = kObjectTypeInteger, \ - .data.integer = i \ - }) - -#define STRING_OBJ(s) ((Object) { \ - .type = kObjectTypeString, \ - .data.string = s \ - }) - -#define BUFFER_OBJ(s) ((Object) { \ - .type = kObjectTypeBuffer, \ - .data.buffer = s \ - }) - -#define WINDOW_OBJ(s) ((Object) { \ - .type = kObjectTypeWindow, \ - .data.window = s \ - }) - -#define TABPAGE_OBJ(s) ((Object) { \ - .type = kObjectTypeTabpage, \ - .data.tabpage = s \ - }) - -#define ARRAY_OBJ(a) ((Object) { \ - .type = kObjectTypeArray, \ - .data.array = a \ - }) - -#define DICTIONARY_OBJ(d) ((Object) { \ - .type = kObjectTypeDictionary, \ - .data.dictionary = d \ - }) +#define BOOLEAN_OBJ(b) ((Object) { \ + .type = kObjectTypeBoolean, \ + .data.boolean = b }) + +#define INTEGER_OBJ(i) ((Object) { \ + .type = kObjectTypeInteger, \ + .data.integer = i }) + +#define FLOATING_OBJ(f) ((Object) { \ + .type = kObjectTypeFloat, \ + .data.floating = f }) + +#define STRING_OBJ(s) ((Object) { \ + .type = kObjectTypeString, \ + .data.string = s }) + +#define BUFFER_OBJ(s) ((Object) { \ + .type = kObjectTypeBuffer, \ + .data.integer = s }) + +#define WINDOW_OBJ(s) ((Object) { \ + .type = kObjectTypeWindow, \ + .data.integer = s }) + +#define TABPAGE_OBJ(s) ((Object) { \ + .type = kObjectTypeTabpage, \ + .data.integer = s }) + +#define ARRAY_OBJ(a) ((Object) { \ + .type = kObjectTypeArray, \ + .data.array = a }) + +#define DICTIONARY_OBJ(d) ((Object) { \ + .type = kObjectTypeDictionary, \ + .data.dictionary = d }) #define NIL ((Object) {.type = kObjectTypeNil}) -#define PUT(dict, k, v) \ - kv_push(KeyValuePair, \ - dict, \ - ((KeyValuePair) {.key = cstr_to_string(k), .value = v})) +#define PUT(dict, k, v) \ + kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) -#define ADD(array, item) \ - kv_push(Object, array, item) +#define ADD(array, item) \ + kv_push(array, item) #define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) +/// Create a new String instance, putting data in allocated memory +/// +/// @param[in] s String to work with. Must be a string literal. +#define STATIC_CSTR_TO_STRING(s) ((String){ \ + .data = xmemdupz(s, sizeof(s) - 1), \ + .size = sizeof(s) - 1 }) + // Helpers used by the generated msgpack-rpc api wrappers #define api_init_boolean #define api_init_integer diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index c8311b0aa0..9e61ec1871 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -11,10 +11,10 @@ /// Gets the windows in a tabpage /// -/// @param tabpage The tabpage -/// @param[out] err Details of an error that may have occurred -/// @return The windows in `tabpage` -ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err) +/// @param tabpage Tabpage +/// @param[out] err Error details, if any +/// @return List of windows in `tabpage` +ArrayOf(Window) nvim_tabpage_list_wins(Tabpage tabpage, Error *err) { Array rv = ARRAY_DICT_INIT; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -39,11 +39,11 @@ ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err) /// Gets a tab-scoped (t:) variable /// -/// @param tabpage The tab page handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object tabpage_get_var(Tabpage tabpage, String name, Error *err) +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_tabpage_get_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -56,11 +56,49 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err) /// Sets a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_tabpage_set_var(Tabpage tabpage, + String name, + Object value, + Error *err) +{ + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return; + } + + dict_set_value(tab->tp_vars, name, value, false, false, err); +} + +/// Removes a tab-scoped (t:) variable +/// +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) +{ + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return; + } + + dict_set_value(tab->tp_vars, name, NIL, true, false, err); +} + +/// Sets a tab-scoped (t:) variable +/// +/// @deprecated +/// +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -72,18 +110,17 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(tab->tp_vars, name, value, false, err); + return dict_set_value(tab->tp_vars, name, value, false, true, err); } /// Removes a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object tabpage_del_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -92,15 +129,15 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(tab->tp_vars, name, NIL, true, err); + return dict_set_value(tab->tp_vars, name, NIL, true, true, err); } -/// Gets the current window in a tab page +/// Gets the current window in a tabpage /// -/// @param tabpage The tab page handle -/// @param[out] err Details of an error that may have occurred -/// @return The Window handle -Window tabpage_get_window(Tabpage tabpage, Error *err) +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Window handle +Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) { Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -110,7 +147,7 @@ Window tabpage_get_window(Tabpage tabpage, Error *err) } if (tab == curtab) { - return vim_get_current_window(); + return nvim_get_current_win(); } else { FOR_ALL_WINDOWS_IN_TAB(wp, tab) { if (wp == tab->tp_curwin) { @@ -122,11 +159,28 @@ Window tabpage_get_window(Tabpage tabpage, Error *err) } } -/// Checks if a tab page is valid +/// Gets the tabpage number +/// +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Tabpage number +Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) +{ + Integer rv = 0; + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return rv; + } + + return tabpage_index(tab); +} + +/// Checks if a tabpage is valid /// -/// @param tabpage The tab page handle -/// @return true if the tab page is valid, false otherwise -Boolean tabpage_is_valid(Tabpage tabpage) +/// @param tabpage Tabpage handle +/// @return true if the tabpage is valid, false otherwise +Boolean nvim_tabpage_is_valid(Tabpage tabpage) { Error stub = ERROR_INIT; return find_tab_by_handle(tabpage, &stub) != NULL; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c new file mode 100644 index 0000000000..9178538110 --- /dev/null +++ b/src/nvim/api/ui.c @@ -0,0 +1,424 @@ +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "nvim/vim.h" +#include "nvim/ui.h" +#include "nvim/memory.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/api/ui.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/popupmnu.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/ui.c.generated.h" +#endif + +typedef struct { + uint64_t channel_id; + Array buffer; +} UIData; + +static PMap(uint64_t) *connected_uis = NULL; + +void remote_ui_init(void) + FUNC_API_NOEXPORT +{ + connected_uis = pmap_new(uint64_t)(); +} + +void remote_ui_disconnect(uint64_t channel_id) + FUNC_API_NOEXPORT +{ + UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); + if (!ui) { + return; + } + UIData *data = ui->data; + // destroy pending screen updates + api_free_array(data->buffer); + pmap_del(uint64_t)(connected_uis, channel_id); + xfree(ui->data); + ui_detach_impl(ui); + xfree(ui); +} + +void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, + Dictionary options, Error *err) + FUNC_API_NOEVAL +{ + if (pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(err, Exception, _("UI already attached for channel")); + return; + } + + if (width <= 0 || height <= 0) { + api_set_error(err, Validation, + _("Expected width > 0 and height > 0")); + return; + } + UI *ui = xcalloc(1, sizeof(UI)); + ui->width = (int)width; + ui->height = (int)height; + ui->rgb = true; + ui->pum_external = false; + ui->resize = remote_ui_resize; + ui->clear = remote_ui_clear; + ui->eol_clear = remote_ui_eol_clear; + ui->cursor_goto = remote_ui_cursor_goto; + ui->update_menu = remote_ui_update_menu; + ui->busy_start = remote_ui_busy_start; + ui->busy_stop = remote_ui_busy_stop; + ui->mouse_on = remote_ui_mouse_on; + ui->mouse_off = remote_ui_mouse_off; + ui->mode_change = remote_ui_mode_change; + ui->set_scroll_region = remote_ui_set_scroll_region; + ui->scroll = remote_ui_scroll; + ui->highlight_set = remote_ui_highlight_set; + ui->put = remote_ui_put; + ui->bell = remote_ui_bell; + ui->visual_bell = remote_ui_visual_bell; + ui->update_fg = remote_ui_update_fg; + ui->update_bg = remote_ui_update_bg; + ui->update_sp = remote_ui_update_sp; + ui->flush = remote_ui_flush; + ui->suspend = remote_ui_suspend; + ui->set_title = remote_ui_set_title; + ui->set_icon = remote_ui_set_icon; + ui->event = remote_ui_event; + + for (size_t i = 0; i < options.size; i++) { + ui_set_option(ui, options.items[i].key, options.items[i].value, err); + if (err->set) { + xfree(ui); + return; + } + } + + UIData *data = xmalloc(sizeof(UIData)); + data->channel_id = channel_id; + data->buffer = (Array)ARRAY_DICT_INIT; + ui->data = data; + + pmap_put(uint64_t)(connected_uis, channel_id, ui); + ui_attach_impl(ui); +} + +/// @deprecated +void ui_attach(uint64_t channel_id, Integer width, Integer height, + Boolean enable_rgb, Error *err) +{ + Dictionary opts = ARRAY_DICT_INIT; + PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb)); + nvim_ui_attach(channel_id, width, height, opts, err); + api_free_dictionary(opts); +} + +void nvim_ui_detach(uint64_t channel_id, Error *err) + FUNC_API_NOEVAL +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(err, Exception, _("UI is not attached for channel")); + return; + } + remote_ui_disconnect(channel_id); +} + + +void nvim_ui_try_resize(uint64_t channel_id, Integer width, + Integer height, Error *err) + FUNC_API_NOEVAL +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(err, Exception, _("UI is not attached for channel")); + return; + } + + if (width <= 0 || height <= 0) { + api_set_error(err, Validation, + _("Expected width > 0 and height > 0")); + return; + } + + UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); + ui->width = (int)width; + ui->height = (int)height; + ui_refresh(); +} + +void nvim_ui_set_option(uint64_t channel_id, String name, + Object value, Error *error) + FUNC_API_NOEVAL +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(error, Exception, _("UI is not attached for channel")); + return; + } + UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); + + ui_set_option(ui, name, value, error); + if (!error->set) { + ui_refresh(); + } +} + +static void ui_set_option(UI *ui, String name, Object value, Error *error) { + if (strcmp(name.data, "rgb") == 0) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, Validation, _("rgb must be a Boolean")); + return; + } + ui->rgb = value.data.boolean; + } else if (strcmp(name.data, "popupmenu_external") == 0) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, Validation, + _("popupmenu_external must be a Boolean")); + return; + } + ui->pum_external = value.data.boolean; + } else { + api_set_error(error, Validation, _("No such ui option")); + } +} + +static void push_call(UI *ui, char *name, Array args) +{ + Array call = ARRAY_DICT_INIT; + UIData *data = ui->data; + + // To optimize data transfer(especially for "put"), we bundle adjacent + // calls to same method together, so only add a new call entry if the last + // method call is different from "name" + if (kv_size(data->buffer)) { + call = kv_A(data->buffer, kv_size(data->buffer) - 1).data.array; + } + + if (!kv_size(call) || strcmp(kv_A(call, 0).data.string.data, name)) { + call = (Array)ARRAY_DICT_INIT; + ADD(data->buffer, ARRAY_OBJ(call)); + ADD(call, STRING_OBJ(cstr_to_string(name))); + } + + ADD(call, ARRAY_OBJ(args)); + kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; +} + +static void remote_ui_resize(UI *ui, int width, int height) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(width)); + ADD(args, INTEGER_OBJ(height)); + push_call(ui, "resize", args); +} + +static void remote_ui_clear(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "clear", args); +} + +static void remote_ui_eol_clear(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "eol_clear", args); +} + +static void remote_ui_cursor_goto(UI *ui, int row, int col) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "cursor_goto", args); +} + +static void remote_ui_update_menu(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "update_menu", args); +} + +static void remote_ui_busy_start(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "busy_start", args); +} + +static void remote_ui_busy_stop(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "busy_stop", args); +} + +static void remote_ui_mouse_on(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "mouse_on", args); +} + +static void remote_ui_mouse_off(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "mouse_off", args); +} + +static void remote_ui_mode_change(UI *ui, int mode) +{ + Array args = ARRAY_DICT_INIT; + if (mode == INSERT) { + ADD(args, STRING_OBJ(cstr_to_string("insert"))); + } else if (mode == REPLACE) { + ADD(args, STRING_OBJ(cstr_to_string("replace"))); + } else if (mode == CMDLINE) { + ADD(args, STRING_OBJ(cstr_to_string("cmdline"))); + } else { + assert(mode == NORMAL); + ADD(args, STRING_OBJ(cstr_to_string("normal"))); + } + push_call(ui, "mode_change", args); +} + +static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left, + int right) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right)); + push_call(ui, "set_scroll_region", args); +} + +static void remote_ui_scroll(UI *ui, int count) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(count)); + push_call(ui, "scroll", args); +} + +static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) +{ + Array args = ARRAY_DICT_INIT; + Dictionary hl = ARRAY_DICT_INIT; + + if (attrs.bold) { + PUT(hl, "bold", BOOLEAN_OBJ(true)); + } + + if (attrs.underline) { + PUT(hl, "underline", BOOLEAN_OBJ(true)); + } + + if (attrs.undercurl) { + PUT(hl, "undercurl", BOOLEAN_OBJ(true)); + } + + if (attrs.italic) { + PUT(hl, "italic", BOOLEAN_OBJ(true)); + } + + if (attrs.reverse) { + PUT(hl, "reverse", BOOLEAN_OBJ(true)); + } + + if (attrs.foreground != -1) { + PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); + } + + if (attrs.background != -1) { + PUT(hl, "background", INTEGER_OBJ(attrs.background)); + } + + if (attrs.special != -1) { + PUT(hl, "special", INTEGER_OBJ(attrs.special)); + } + + ADD(args, DICTIONARY_OBJ(hl)); + push_call(ui, "highlight_set", args); +} + +static void remote_ui_put(UI *ui, uint8_t *data, size_t size) +{ + Array args = ARRAY_DICT_INIT; + String str = { .data = xmemdupz(data, size), .size = size }; + ADD(args, STRING_OBJ(str)); + push_call(ui, "put", args); +} + +static void remote_ui_bell(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "bell", args); +} + +static void remote_ui_visual_bell(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "visual_bell", args); +} + +static void remote_ui_update_fg(UI *ui, int fg) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(fg)); + push_call(ui, "update_fg", args); +} + +static void remote_ui_update_bg(UI *ui, int bg) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(bg)); + push_call(ui, "update_bg", args); +} + +static void remote_ui_update_sp(UI *ui, int sp) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(sp)); + push_call(ui, "update_sp", args); +} + +static void remote_ui_flush(UI *ui) +{ + UIData *data = ui->data; + channel_send_event(data->channel_id, "redraw", data->buffer); + data->buffer = (Array)ARRAY_DICT_INIT; +} + +static void remote_ui_suspend(UI *ui) +{ + Array args = ARRAY_DICT_INIT; + push_call(ui, "suspend", args); +} + +static void remote_ui_set_title(UI *ui, char *title) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(cstr_to_string(title))); + push_call(ui, "set_title", args); +} + +static void remote_ui_set_icon(UI *ui, char *icon) +{ + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(cstr_to_string(icon))); + push_call(ui, "set_icon", args); +} + +static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) +{ + Array my_args = ARRAY_DICT_INIT; + // Objects are currently single-reference + // make a copy, but only if necessary + if (*args_consumed) { + for (size_t i = 0; i < args.size; i++) { + ADD(my_args, copy_object(args.items[i])); + } + } else { + my_args = args; + *args_consumed = true; + } + push_call(ui, name, my_args); +} diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h new file mode 100644 index 0000000000..b3af14f8a8 --- /dev/null +++ b/src/nvim/api/ui.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_UI_H +#define NVIM_API_UI_H + +#include <stdint.h> + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/ui.h.generated.h" +#endif +#endif // NVIM_API_UI_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 46ac3c9022..1732ee0bae 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -14,6 +14,7 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/file_search.h" #include "nvim/window.h" #include "nvim/types.h" #include "nvim/ex_docmd.h" @@ -21,7 +22,7 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/eval.h" -#include "nvim/misc2.h" +#include "nvim/option.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" @@ -32,35 +33,35 @@ # include "api/vim.c.generated.h" #endif -/// Executes an ex-mode command str +/// Executes an ex-command. +/// On VimL error: Returns the VimL error; v:errmsg is not updated. /// -/// @param str The command str -/// @param[out] err Details of an error that may have occurred -void vim_command(String str, Error *err) +/// @param command Ex-command string +/// @param[out] err Error details (including actual VimL error), if any +void nvim_command(String command, Error *err) { // Run the command try_start(); - do_cmdline_cmd(str.data); + do_cmdline_cmd(command.data); update_screen(VALID); try_end(err); } -/// Passes input keys to Neovim +/// Passes input keys to Nvim. +/// On VimL error: Does not fail, but updates v:errmsg. /// -/// @param keys to be typed -/// @param mode specifies the mapping options -/// @param escape_csi the string needs escaping for K_SPECIAL/CSI bytes +/// @param keys to be typed +/// @param mode mapping options +/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` /// @see feedkeys() /// @see vim_strsave_escape_csi -void vim_feedkeys(String keys, String mode, Boolean escape_csi) +void nvim_feedkeys(String keys, String mode, Boolean escape_csi) { bool remap = true; bool insert = false; bool typed = false; - - if (keys.size == 0) { - return; - } + bool execute = false; + bool dangerous = false; for (size_t i = 0; i < mode.size; ++i) { switch (mode.data[i]) { @@ -68,9 +69,15 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi) case 'm': remap = true; break; case 't': typed = true; break; case 'i': insert = true; break; + case 'x': execute = true; break; + case '!': dangerous = true; break; } } + if (keys.size == 0 && !execute) { + return; + } + char *keys_esc; if (escape_csi) { // Need to escape K_SPECIAL and CSI before putting the string in the @@ -86,19 +93,36 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi) xfree(keys_esc); } - if (vgetc_busy) + if (vgetc_busy) { typebuf_was_filled = true; + } + if (execute) { + int save_msg_scroll = msg_scroll; + + /* Avoid a 1 second delay when the keys start Insert mode. */ + msg_scroll = false; + if (!dangerous) { + ex_normal_busy++; + } + exec_normal(true); + if (!dangerous) { + ex_normal_busy--; + } + msg_scroll |= save_msg_scroll; + } } -/// Passes input keys to Neovim. Unlike `vim_feedkeys`, this will use a -/// lower-level input buffer and the call is not deferred. -/// This is the most reliable way to emulate real user input. +/// Passes keys to Nvim as raw user-input. +/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call +/// is not deferred. This is the most reliable way to emulate real user input. /// /// @param keys to be typed -/// @return The number of bytes actually written, which can be lower than -/// requested if the buffer becomes full. -Integer vim_input(String keys) - FUNC_ATTR_ASYNC +/// @return Number of bytes actually written (can be fewer than +/// requested if the buffer becomes full). +Integer nvim_input(String keys) + FUNC_API_ASYNC { return (Integer)input_enqueue(keys); } @@ -107,7 +131,7 @@ Integer vim_input(String keys) /// /// @see replace_termcodes /// @see cpoptions -String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, +String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special) { if (str.size == 0) { @@ -127,10 +151,10 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String vim_command_output(String str, Error *err) +String nvim_command_output(String str, Error *err) { do_cmdline_cmd("redir => v:command_output"); - vim_command(str, err); + nvim_command(str, err); do_cmdline_cmd("redir END"); if (err->set) { @@ -140,19 +164,19 @@ String vim_command_output(String str, Error *err) return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); } -/// Evaluates the expression str using the Vim internal expression -/// evaluator (see |expression|). -/// Dictionaries and lists are recursively expanded. +/// Evaluates a VimL expression (:help expression). +/// Dictionaries and Lists are recursively expanded. +/// On VimL error: Returns a generic error; v:errmsg is not updated. /// -/// @param str The expression str -/// @param[out] err Details of an error that may have occurred -/// @return The expanded object -Object vim_eval(String str, Error *err) +/// @param expr VimL expression string +/// @param[out] err Error details, if any +/// @return Evaluation result or expanded object +Object nvim_eval(String expr, Error *err) { Object rv = OBJECT_INIT; // Evaluate the expression try_start(); - typval_T *expr_result = eval_expr((char_u *) str.data, NULL); + typval_T *expr_result = eval_expr((char_u *)expr.data, NULL); if (!expr_result) { api_set_error(err, Exception, _("Failed to evaluate expression")); @@ -168,13 +192,14 @@ Object vim_eval(String str, Error *err) return rv; } -/// Call the given function with the given arguments stored in an array. +/// Calls a VimL function with the given arguments. +/// On VimL error: Returns a generic error; v:errmsg is not updated. /// -/// @param fname Function to call -/// @param args Functions arguments packed in an Array -/// @param[out] err Details of an error that may have occurred +/// @param fname Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any /// @return Result of the function call -Object vim_call_function(String fname, Array args, Error *err) +Object nvim_call_function(String fname, Array args, Error *err) { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { @@ -200,7 +225,7 @@ Object vim_call_function(String fname, Array args, Error *err) &rettv, (int) args.size, vim_args, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, - NULL); + NULL, NULL); if (r == FAIL) { api_set_error(err, Exception, _("Error calling function.")); } @@ -217,13 +242,13 @@ free_vim_args: return rv; } -/// Calculates the number of display cells `str` occupies, tab is counted as -/// one cell. +/// Calculates the number of display cells occupied by `text`. +/// <Tab> counts as one cell. /// -/// @param str Some text -/// @param[out] err Details of an error that may have occurred -/// @return The number of cells -Integer vim_strwidth(String str, Error *err) +/// @param text Some text +/// @param[out] err Error details, if any +/// @return Number of cells +Integer nvim_strwidth(String str, Error *err) { if (str.size > INT_MAX) { api_set_error(err, Validation, _("String length is too high")); @@ -233,10 +258,10 @@ Integer vim_strwidth(String str, Error *err) return (Integer) mb_string2cells((char_u *) str.data); } -/// Gets a list of paths contained in 'runtimepath' +/// Gets the paths contained in 'runtimepath'. /// -/// @return The list of paths -ArrayOf(String) vim_list_runtime_paths(void) +/// @return List of paths +ArrayOf(String) nvim_list_runtime_paths(void) { Array rv = ARRAY_DICT_INIT; uint8_t *rtp = p_rtp; @@ -273,11 +298,11 @@ ArrayOf(String) vim_list_runtime_paths(void) return rv; } -/// Changes Vim working directory +/// Changes the global working directory. /// -/// @param dir The new working directory -/// @param[out] err Details of an error that may have occurred -void vim_change_directory(String dir, Error *err) +/// @param dir Directory path +/// @param[out] err Error details, if any +void nvim_set_current_dir(String dir, Error *err) { if (dir.size >= MAXPATHL) { api_set_error(err, Validation, _("Directory string is too long")); @@ -285,12 +310,12 @@ void vim_change_directory(String dir, Error *err) } char string[MAXPATHL]; - strncpy(string, dir.data, dir.size); + memcpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); - if (vim_chdir((char_u *)string)) { + if (vim_chdir((char_u *)string, kCdScopeGlobal)) { if (!try_end(err)) { api_set_error(err, Exception, _("Failed to change directory")); } @@ -303,127 +328,148 @@ void vim_change_directory(String dir, Error *err) /// Gets the current line /// -/// @param[out] err Details of an error that may have occurred -/// @return The current line string -String vim_get_current_line(Error *err) +/// @param[out] err Error details, if any +/// @return Current line string +String nvim_get_current_line(Error *err) { return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } /// Sets the current line /// -/// @param line The line contents -/// @param[out] err Details of an error that may have occurred -void vim_set_current_line(String line, Error *err) +/// @param line Line contents +/// @param[out] err Error details, if any +void nvim_set_current_line(String line, Error *err) { buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); } /// Deletes the current line /// -/// @param[out] err Details of an error that may have occurred -void vim_del_current_line(Error *err) +/// @param[out] err Error details, if any +void nvim_del_current_line(Error *err) { buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } -/// Gets a global variable +/// Gets a global (g:) variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object vim_get_var(String name, Error *err) +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_get_var(String name, Error *err) { return dict_get_value(&globvardict, name, err); } +/// Sets a global (g:) variable +/// +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_set_var(String name, Object value, Error *err) +{ + dict_set_value(&globvardict, name, value, false, false, err); +} + +/// Removes a global (g:) variable +/// +/// @param name Variable name +/// @param[out] err Error details, if any +void nvim_del_var(String name, Error *err) +{ + dict_set_value(&globvardict, name, NIL, true, false, err); +} + /// Sets a global variable /// -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated +/// +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. Object vim_set_var(String name, Object value, Error *err) { - return dict_set_value(&globvardict, name, value, false, err); + return dict_set_value(&globvardict, name, value, false, true, err); } /// Removes a global variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object vim_del_var(String name, Error *err) { - return dict_set_value(&globvardict, name, NIL, true, err); + return dict_set_value(&globvardict, name, NIL, true, true, err); } -/// Gets a vim variable +/// Gets a v: variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object vim_get_vvar(String name, Error *err) +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_get_vvar(String name, Error *err) { return dict_get_value(&vimvardict, name, err); } /// Gets an option value string /// -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object vim_get_option(String name, Error *err) +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_get_option(String name, Error *err) { return get_option_from(NULL, SREQ_GLOBAL, name, err); } /// Sets an option value /// -/// @param name The option name -/// @param value The new option value -/// @param[out] err Details of an error that may have occurred -void vim_set_option(String name, Object value, Error *err) +/// @param name Option name +/// @param value New option value +/// @param[out] err Error details, if any +void nvim_set_option(String name, Object value, Error *err) { set_option_to(NULL, SREQ_GLOBAL, name, value, err); } /// Writes a message to vim output buffer /// -/// @param str The message -void vim_out_write(String str) +/// @param str Message +void nvim_out_write(String str) { write_msg(str, false); } /// Writes a message to vim error buffer /// -/// @param str The message -void vim_err_write(String str) +/// @param str Message +void nvim_err_write(String str) { write_msg(str, true); } -/// Higher level error reporting function that ensures all str contents -/// are written by sending a trailing linefeed to `vim_err_write` +/// Writes a message to vim error buffer. Appends a linefeed to ensure all +/// contents are written. /// -/// @param str The message -void vim_report_error(String str) +/// @param str Message +/// @see nvim_err_write() +void nvim_err_writeln(String str) { - vim_err_write(str); - vim_err_write((String) {.data = "\n", .size = 1}); + nvim_err_write(str); + nvim_err_write((String) { .data = "\n", .size = 1 }); } /// Gets the current list of buffer handles /// -/// @return The number of buffers -ArrayOf(Buffer) vim_get_buffers(void) +/// @return List of buffer handles +ArrayOf(Buffer) nvim_list_bufs(void) { Array rv = ARRAY_DICT_INIT; @@ -443,17 +489,17 @@ ArrayOf(Buffer) vim_get_buffers(void) /// Gets the current buffer /// -/// @reqturn The buffer handle -Buffer vim_get_current_buffer(void) +/// @return Buffer handle +Buffer nvim_get_current_buf(void) { return curbuf->handle; } /// Sets the current buffer /// -/// @param id The buffer handle -/// @param[out] err Details of an error that may have occurred -void vim_set_current_buffer(Buffer buffer, Error *err) +/// @param id Buffer handle +/// @param[out] err Error details, if any +void nvim_set_current_buf(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -466,15 +512,15 @@ void vim_set_current_buffer(Buffer buffer, Error *err) if (!try_end(err) && result == FAIL) { api_set_error(err, Exception, - _("Failed to switch to buffer %" PRIu64), + _("Failed to switch to buffer %d"), buffer); } } /// Gets the current list of window handles /// -/// @return The number of windows -ArrayOf(Window) vim_get_windows(void) +/// @return List of window handles +ArrayOf(Window) nvim_list_wins(void) { Array rv = ARRAY_DICT_INIT; @@ -494,16 +540,16 @@ ArrayOf(Window) vim_get_windows(void) /// Gets the current window /// -/// @return The window handle -Window vim_get_current_window(void) +/// @return Window handle +Window nvim_get_current_win(void) { return curwin->handle; } /// Sets the current window /// -/// @param handle The window handle -void vim_set_current_window(Window window, Error *err) +/// @param handle Window handle +void nvim_set_current_win(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -516,15 +562,15 @@ void vim_set_current_window(Window window, Error *err) if (!try_end(err) && win != curwin) { api_set_error(err, Exception, - _("Failed to switch to window %" PRIu64), + _("Failed to switch to window %d"), window); } } /// Gets the current list of tabpage handles /// -/// @return The number of tab pages -ArrayOf(Tabpage) vim_get_tabpages(void) +/// @return List of tabpage handles +ArrayOf(Tabpage) nvim_list_tabpages(void) { Array rv = ARRAY_DICT_INIT; @@ -542,19 +588,19 @@ ArrayOf(Tabpage) vim_get_tabpages(void) return rv; } -/// Gets the current tab page +/// Gets the current tabpage /// -/// @return The tab page handle -Tabpage vim_get_current_tabpage(void) +/// @return Tabpage handle +Tabpage nvim_get_current_tabpage(void) { return curtab->handle; } -/// Sets the current tab page +/// Sets the current tabpage /// -/// @param handle The tab page handle -/// @param[out] err Details of an error that may have occurred -void vim_set_current_tabpage(Tabpage tabpage, Error *err) +/// @param handle Tabpage handle +/// @param[out] err Error details, if any +void nvim_set_current_tabpage(Tabpage tabpage, Error *err) { tabpage_T *tp = find_tab_by_handle(tabpage, err); @@ -567,16 +613,17 @@ void vim_set_current_tabpage(Tabpage tabpage, Error *err) if (!try_end(err) && tp != curtab) { api_set_error(err, Exception, - _("Failed to switch to tabpage %" PRIu64), + _("Failed to switch to tabpage %d"), tabpage); } } /// Subscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string -void vim_subscribe(uint64_t channel_id, String event) +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string +void nvim_subscribe(uint64_t channel_id, String event) + FUNC_API_NOEVAL { size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN); char e[METHOD_MAXLEN + 1]; @@ -587,9 +634,10 @@ void vim_subscribe(uint64_t channel_id, String event) /// Unsubscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string -void vim_unsubscribe(uint64_t channel_id, String event) +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string +void nvim_unsubscribe(uint64_t channel_id, String event) + FUNC_API_NOEVAL { size_t length = (event.size < METHOD_MAXLEN ? event.size : @@ -600,12 +648,12 @@ void vim_unsubscribe(uint64_t channel_id, String event) channel_unsubscribe(channel_id, e); } -Integer vim_name_to_color(String name) +Integer nvim_get_color_by_name(String name) { return name_to_color((uint8_t *)name.data); } -Dictionary vim_get_color_map(void) +Dictionary nvim_get_color_map(void) { Dictionary colors = ARRAY_DICT_INIT; @@ -617,8 +665,8 @@ Dictionary vim_get_color_map(void) } -Array vim_get_api_info(uint64_t channel_id) - FUNC_ATTR_ASYNC +Array nvim_get_api_info(uint64_t channel_id) + FUNC_API_ASYNC FUNC_API_NOEVAL { Array rv = ARRAY_DICT_INIT; @@ -629,26 +677,114 @@ Array vim_get_api_info(uint64_t channel_id) return rv; } +/// Call many api methods atomically +/// +/// This has two main usages: Firstly, to perform several requests from an +/// async context atomically, i.e. without processing requests from other rpc +/// clients or redrawing or allowing user interaction in between. Note that api +/// methods that could fire autocommands or do event processing still might do +/// so. For instance invoking the :sleep command might call timer callbacks. +/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing +/// many requests in sequence. +/// +/// @param calls an array of calls, where each call is described by an array +/// with two elements: the request name, and an array of arguments. +/// @param[out] err Details of a validation error of the nvim_multi_request call +/// itself, i e malformatted `calls` parameter. Errors from called methods will +/// be indicated in the return value, see below. +/// +/// @return an array with two elements. The first is an array of return +/// values. The second is NIL if all calls succeeded. If a call resulted in +/// an error, it is a three-element array with the zero-based index of the call +/// which resulted in an error, the error type and the error message. If an +/// error ocurred, the values from all preceding calls will still be returned. +Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) + FUNC_API_NOEVAL +{ + Array rv = ARRAY_DICT_INIT; + Array results = ARRAY_DICT_INIT; + Error nested_error = ERROR_INIT; + + size_t i; // also used for freeing the variables + for (i = 0; i < calls.size; i++) { + if (calls.items[i].type != kObjectTypeArray) { + api_set_error(err, + Validation, + _("All items in calls array must be arrays")); + goto validation_error; + } + Array call = calls.items[i].data.array; + if (call.size != 2) { + api_set_error(err, + Validation, + _("All items in calls array must be arrays of size 2")); + goto validation_error; + } + + if (call.items[0].type != kObjectTypeString) { + api_set_error(err, + Validation, + _("name must be String")); + goto validation_error; + } + String name = call.items[0].data.string; + + if (call.items[1].type != kObjectTypeArray) { + api_set_error(err, + Validation, + _("args must be Array")); + goto validation_error; + } + Array args = call.items[1].data.array; + + MsgpackRpcRequestHandler handler = msgpack_rpc_get_handler_for(name.data, + name.size); + Object result = handler.fn(channel_id, args, &nested_error); + if (nested_error.set) { + // error handled after loop + break; + } + + ADD(results, result); + } + + ADD(rv, ARRAY_OBJ(results)); + if (nested_error.set) { + Array errval = ARRAY_DICT_INIT; + ADD(errval, INTEGER_OBJ((Integer)i)); + ADD(errval, INTEGER_OBJ(nested_error.type)); + ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); + ADD(rv, ARRAY_OBJ(errval)); + } else { + ADD(rv, NIL); + } + return rv; + +validation_error: + api_free_array(results); + return rv; +} + + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing /// later. /// -/// @param message The message to write -/// @param to_err true if it should be treated as an error message (use -/// `emsg` instead of `msg` to print each line) +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) static void write_msg(String message, bool to_err) { static size_t out_pos = 0, err_pos = 0; static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE]; -#define PUSH_CHAR(i, pos, line_buf, msg) \ - if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ - line_buf[pos] = NUL; \ - msg((uint8_t *)line_buf); \ - pos = 0; \ - continue; \ - } \ - \ +#define PUSH_CHAR(i, pos, line_buf, msg) \ + if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ + line_buf[pos] = NUL; \ + msg((uint8_t *)line_buf); \ + pos = 0; \ + continue; \ + } \ + \ line_buf[pos++] = message.data[i]; ++no_wait_return; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f644453358..1f555a6a05 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -11,15 +11,14 @@ #include "nvim/window.h" #include "nvim/screen.h" #include "nvim/move.h" -#include "nvim/misc2.h" /// Gets the current buffer in a window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer handle -Buffer window_get_buffer(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Buffer handle +Buffer nvim_win_get_buf(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -32,10 +31,10 @@ Buffer window_get_buffer(Window window, Error *err) /// Gets the cursor position in the window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the (row, col) tuple -ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return (row, col) tuple +ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -50,10 +49,10 @@ ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) /// Sets the cursor position in the window /// -/// @param window The window handle -/// @param pos the (row, col) tuple representing the new position -/// @param[out] err Details of an error that may have occurred -void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) +/// @param window Window handle +/// @param pos (row, col) tuple representing the new position +/// @param[out] err Error details, if any +void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -96,10 +95,10 @@ void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) /// Gets the window height /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the height in rows -Integer window_get_height(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Height as a count of rows +Integer nvim_win_get_height(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -113,10 +112,10 @@ Integer window_get_height(Window window, Error *err) /// Sets the window height. This will only succeed if the screen is split /// horizontally. /// -/// @param window The window handle -/// @param height the new height in rows -/// @param[out] err Details of an error that may have occurred -void window_set_height(Window window, Integer height, Error *err) +/// @param window Window handle +/// @param height Height as a count of rows +/// @param[out] err Error details, if any +void nvim_win_set_height(Window window, Integer height, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -139,10 +138,10 @@ void window_set_height(Window window, Integer height, Error *err) /// Gets the window width /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the width in columns -Integer window_get_width(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Width as a count of columns +Integer nvim_win_get_width(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -156,10 +155,10 @@ Integer window_get_width(Window window, Error *err) /// Sets the window width. This will only succeed if the screen is split /// vertically. /// -/// @param window The window handle -/// @param width the new width in columns -/// @param[out] err Details of an error that may have occurred -void window_set_width(Window window, Integer width, Error *err) +/// @param window Window handle +/// @param width Width as a count of columns +/// @param[out] err Error details, if any +void nvim_win_set_width(Window window, Integer width, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -182,11 +181,11 @@ void window_set_width(Window window, Integer width, Error *err) /// Gets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object window_get_var(Window window, String name, Error *err) +/// @param window Window handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_win_get_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -199,11 +198,46 @@ Object window_get_var(Window window, String name, Error *err) /// Sets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param window Window handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_win_set_var(Window window, String name, Object value, Error *err) +{ + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return; + } + + dict_set_value(win->w_vars, name, value, false, false, err); +} + +/// Removes a window-scoped (w:) variable +/// +/// @param window Window handle +/// @param name Variable name +/// @param[out] err Error details, if any +void nvim_win_del_var(Window window, String name, Error *err) +{ + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return; + } + + dict_set_value(win->w_vars, name, NIL, true, false, err); +} + +/// Sets a window-scoped (w:) variable +/// +/// @deprecated +/// +/// @param window Window handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -215,18 +249,17 @@ Object window_set_var(Window window, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(win->w_vars, name, value, false, err); + return dict_set_value(win->w_vars, name, value, false, true, err); } /// Removes a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param window Window handle +/// @param name variable name +/// @param[out] err Error details, if any +/// @return Old value Object window_del_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -235,16 +268,16 @@ Object window_del_var(Window window, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(win->w_vars, name, NIL, true, err); + return dict_set_value(win->w_vars, name, NIL, true, true, err); } /// Gets a window option value /// -/// @param window The window handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object window_get_option(Window window, String name, Error *err) +/// @param window Window handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_win_get_option(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -258,11 +291,11 @@ Object window_get_option(Window window, String name, Error *err) /// Sets a window option value. Passing 'nil' as value deletes the option(only /// works if there's a global fallback) /// -/// @param window The window handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred -void window_set_option(Window window, String name, Object value, Error *err) +/// @param window Window handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any +void nvim_win_set_option(Window window, String name, Object value, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -275,10 +308,10 @@ void window_set_option(Window window, String name, Object value, Error *err) /// Gets the window position in display cells. First position is zero. /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple with the window position -ArrayOf(Integer, 2) window_get_position(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return (row, col) tuple with the window position +ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -291,12 +324,12 @@ ArrayOf(Integer, 2) window_get_position(Window window, Error *err) return rv; } -/// Gets the window tab page +/// Gets the window tabpage /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The tab page that contains the window -Tabpage window_get_tabpage(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Tabpage that contains the window +Tabpage nvim_win_get_tabpage(Window window, Error *err) { Tabpage rv = 0; win_T *win = find_window_by_handle(window, err); @@ -308,11 +341,31 @@ Tabpage window_get_tabpage(Window window, Error *err) return rv; } +/// Gets the window number +/// +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Window number +Integer nvim_win_get_number(Window window, Error *err) +{ + int rv = 0; + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return rv; + } + + int tabnr; + win_get_tabwin(window, &tabnr, &rv); + + return rv; +} + /// Checks if a window is valid /// -/// @param window The window handle +/// @param window Window handle /// @return true if the window is valid, false otherwise -Boolean window_is_valid(Window window) +Boolean nvim_win_is_valid(Window window) { Error stub = ERROR_INIT; return find_window_by_handle(window, &stub) != NULL; |