aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/vim.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
commit1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch)
treecd08258054db80bb9a11b1061bb091c70b76926a /src/nvim/api/vim.c
parenteaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.gz
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.bz2
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.zip
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'src/nvim/api/vim.c')
-rw-r--r--src/nvim/api/vim.c688
1 files changed, 354 insertions, 334 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a53b30dd8a..d631b10af9 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1,8 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,25 +9,29 @@
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/vim.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -38,7 +40,7 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -47,24 +49,24 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
+#include "nvim/sign.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#define LINE_BUFFER_MIN_SIZE 4096
@@ -73,44 +75,6 @@
# include "api/vim.c.generated.h"
#endif
-/// Gets a highlight definition by name.
-///
-/// @param name Highlight group name
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary result = ARRAY_DICT_INIT;
- int id = syn_name2id(name.data);
-
- if (id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
- return result;
- }
- return nvim_get_hl_by_id(id, rgb, arena, err);
-}
-
-/// Gets a highlight definition by id. |hlID()|
-/// @param hl_id Highlight id as returned by |hlID()|
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary dic = ARRAY_DICT_INIT;
- if (syn_get_final_id((int)hl_id) == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id);
- return dic;
- }
- int attrcode = syn_id2attr((int)hl_id);
- return hl_get_attr_by_id(attrcode, rgb, arena, err);
-}
-
/// Gets a highlight group by name
///
/// similar to |hlID()|, but allocates a new ID if not present.
@@ -120,12 +84,26 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group(name.data, name.size);
}
-Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
+/// Gets all or specific highlight groups in a namespace.
+///
+/// @note When the `link` attribute is defined in the highlight definition
+/// map, other attributes will not be taking effect (see |:hi-link|).
+///
+/// @param ns_id Get highlight groups for namespace ns_id |nvim_get_namespaces()|.
+/// Use 0 to get global highlight groups |:highlight|.
+/// @param opts Options dict:
+/// - name: (string) Get a highlight definition by name.
+/// - id: (integer) Get a highlight definition by id.
+/// - link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|.
+/// - create: (boolean, default true) When highlight group doesn't exist create it.
+///
+/// @param[out] err Error details, if any.
+/// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|,
+/// or only a single highlight definition map if requested by name or id.
+Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+ FUNC_API_SINCE(11)
{
- if (ns_id == 0) {
- return get_global_hl_defs(arena);
- }
- abort();
+ return ns_get_hl_defs((NS)ns_id, opts, arena, err);
}
/// Sets a highlight group.
@@ -140,8 +118,14 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// values of the Normal group. If the Normal group has not been defined,
/// using these values results in an error.
///
+///
+/// @note If `link` is used in combination with other attributes; only the
+/// `link` will take effect (see |:hi-link|).
+///
/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
/// Use 0 to set a highlight group globally |:highlight|.
+/// Highlights from non-global namespaces are not active by default, use
+/// |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to activate them.
/// @param name Highlight group name, e.g. "ErrorMsg"
/// @param val Highlight definition map, accepts the following keys:
/// - fg (or foreground): color name or "#RRGGBB", see note.
@@ -166,6 +150,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// - cterm: cterm attribute map, like |highlight-args|. If not set,
/// cterm attributes will match those from the attribute map
/// documented above.
+/// - force: if true force update the highlight group when it exists.
/// @param[out] err Error details, if any
///
// TODO(bfredl): val should take update vs reset flag
@@ -173,10 +158,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, name.size);
- if (hl_id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
+ VALIDATE_S((hl_id != 0), "highlight name", name.data, {
return;
- }
+ });
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
@@ -185,25 +169,47 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
}
}
-/// Set active namespace for highlights. This can be set for a single window,
-/// see |nvim_win_set_hl_ns()|.
+/// Gets the active highlight namespace.
+///
+/// @param opts Optional parameters
+/// - winid: (number) |window-ID| for retrieving a window's highlight
+/// namespace. A value of -1 is returned when |nvim_win_set_hl_ns()|
+/// has not been called for the window (or was called with a namespace
+/// of -1).
+/// @param[out] err Error details, if any
+/// @return Namespace id, or -1
+Integer nvim_get_hl_ns(Dict(get_ns) *opts, Error *err)
+ FUNC_API_SINCE(12)
+{
+ if (HAS_KEY(opts, get_ns, winid)) {
+ win_T *win = find_window_by_handle(opts->winid, err);
+ if (!win) {
+ return 0;
+ }
+ return win->w_ns_hl;
+ } else {
+ return ns_hl_global;
+ }
+}
+
+/// Set active namespace for highlights defined with |nvim_set_hl()|. This can be set for
+/// a single window, see |nvim_win_set_hl_ns()|.
///
/// @param ns_id the namespace to use
/// @param[out] err Error details, if any
void nvim_set_hl_ns(Integer ns_id, Error *err)
FUNC_API_SINCE(10)
{
- if (ns_id < 0) {
- api_set_error(err, kErrorTypeValidation, "no such namespace");
+ VALIDATE_INT((ns_id >= 0), "namespace", ns_id, {
return;
- }
+ });
ns_hl_global = (NS)ns_id;
hl_check_ns();
redraw_all_later(UPD_NOT_VALID);
}
-/// Set active namespace for highlights while redrawing.
+/// Set active namespace for highlights defined with |nvim_set_hl()| while redrawing.
///
/// This function meant to be called while redrawing, primarily from
/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which
@@ -229,10 +235,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err)
/// nvim_feedkeys().
///
/// Example:
-/// <pre>vim
-/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:false)
-/// </pre>
+///
+/// ```vim
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
+/// ```
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -323,6 +330,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
Integer nvim_input(String keys)
FUNC_API_SINCE(1) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
return (Integer)input_enqueue(keys);
}
@@ -352,6 +360,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
Integer col, Error *err)
FUNC_API_SINCE(6) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
+
if (button.data == NULL || action.data == NULL) {
goto error;
}
@@ -403,11 +413,9 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
continue;
}
int mod = name_to_mod_mask(byte);
- if (mod == 0) {
- api_set_error(err, kErrorTypeValidation,
- "invalid modifier %c", byte);
+ VALIDATE((mod != 0), "Invalid modifier: %c", byte, {
return;
- }
+ });
modmask |= mod;
}
@@ -448,7 +456,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
}
char *ptr = NULL;
- replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(str.data, str.size, &ptr, 0, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
@@ -500,15 +508,14 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
- if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String is too long");
+ VALIDATE_S((text.size <= INT_MAX), "text length", "(too long)", {
return 0;
- }
+ });
return (Integer)mb_string2cells(text.data);
}
-/// Gets the paths contained in 'runtimepath'.
+/// Gets the paths contained in |runtime-search-path|.
///
/// @return List of paths
ArrayOf(String) nvim_list_runtime_paths(Error *err)
@@ -542,20 +549,23 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
- try_end(err);
});
return rv;
}
-static void find_runtime_cb(char *fname, void *cookie)
+static bool find_runtime_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
Array *rv = (Array *)cookie;
- if (fname != NULL) {
- ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ for (int i = 0; i < num_fnames; i++) {
+ ADD(*rv, CSTR_TO_OBJ(fnames[i]));
+ if (!all) {
+ return true;
+ }
}
+
+ return num_fnames > 0;
}
String nvim__get_lib_dir(void)
@@ -567,28 +577,24 @@ String nvim__get_lib_dir(void)
///
/// @param pat pattern of files to search for
/// @param all whether to return all matches or only the first
-/// @param opts is_lua: only search lua subdirs
+/// @param opts is_lua: only search Lua subdirs
/// @return list of absolute paths to the found files
ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
FUNC_API_SINCE(8)
FUNC_API_FAST
{
- bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
- bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
- if (source && !nlua_is_deferred_safe()) {
- api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
- }
-
+ VALIDATE(!opts->do_source || nlua_is_deferred_safe(), "%s", "'do_source' used in fast callback",
+ {});
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
- ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+ ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all);
- if (source) {
+ if (opts->do_source) {
for (size_t i = 0; i < res.size; i++) {
String name = res.items[i].data.string;
- (void)do_source(name.data, false, DOSO_NONE);
+ (void)do_source(name.data, false, DOSO_NONE, NULL);
}
}
@@ -602,10 +608,9 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
- if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory name is too long");
+ VALIDATE_S((dir.size < MAXPATHL), "directory name", "(too long)", {
return;
- }
+ });
char string[MAXPATHL];
memcpy(string, dir.data, dir.size);
@@ -639,7 +644,7 @@ String nvim_get_current_line(Error *err)
/// @param[out] err Error details, if any
void nvim_set_current_line(String line, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
}
@@ -649,7 +654,7 @@ void nvim_set_current_line(String line, Error *err)
/// @param[out] err Error details, if any
void nvim_del_current_line(Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
}
@@ -664,16 +669,15 @@ Object nvim_get_var(String name, Error *err)
{
dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script
- if (!script_autoload(name.data, name.size, false) || aborting()) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ bool found = script_autoload(name.data, name.size, false) && !aborting();
+ VALIDATE(found, "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
}
- if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ VALIDATE((di != NULL), "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
return vim_to_object(&di->di_tv);
}
@@ -738,15 +742,13 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err)
goto error;
}
- bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err);
-
- if (verbose) {
+ if (opts->verbose) {
verbose_enter();
}
msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
- if (verbose) {
+ if (opts->verbose) {
verbose_leave();
verbose_stop(); // flush now
}
@@ -767,7 +769,7 @@ error:
void nvim_out_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, false);
+ write_msg(str, false, false);
}
/// Writes a message to the Vim error buffer. Does not append "\n", the
@@ -777,7 +779,7 @@ void nvim_out_write(String str)
void nvim_err_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, true);
+ write_msg(str, true, false);
}
/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
@@ -788,8 +790,7 @@ void nvim_err_write(String str)
void nvim_err_writeln(String str)
FUNC_API_SINCE(1)
{
- nvim_err_write(str);
- nvim_err_write((String) { .data = "\n", .size = 1 });
+ write_msg(str, true, true);
}
/// Gets the current list of buffer handles
@@ -832,7 +833,7 @@ Buffer nvim_get_current_buf(void)
/// @param[out] err Error details, if any
void nvim_set_current_buf(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -887,7 +888,7 @@ Window nvim_get_current_win(void)
/// @param[out] err Error details, if any
void nvim_set_current_win(Window window, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
win_T *win = find_window_by_handle(window, err);
@@ -918,7 +919,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
FUNC_API_SINCE(6)
{
try_start();
- buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0,
+ buf_T *buf = buflist_new(NULL, NULL, 0,
BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
try_end(err);
if (buf == NULL) {
@@ -936,14 +937,23 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
goto fail;
}
+ // Set last_changedtick to avoid triggering a TextChanged autocommand right
+ // after it was added.
+ buf->b_last_changedtick = buf_get_changedtick(buf);
+ buf->b_last_changedtick_i = buf_get_changedtick(buf);
+ buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+
+ // Only strictly needed for scratch, but could just as well be consistent
+ // and do this now. buffer is created NOW, not when it latter first happen
+ // to reach a window or aucmd_prepbuf() ..
+ buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+
if (scratch) {
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
- aucmd_restbuf(&aco);
+ set_string_option_direct_in_buf(buf, "bufhidden", -1, "hide", OPT_LOCAL, 0);
+ set_string_option_direct_in_buf(buf, "buftype", -1, "nofile", OPT_LOCAL, 0);
+ assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
+ buf->b_p_swf = false;
+ buf->b_p_ml = false;
}
return buf->b_fnum;
@@ -970,7 +980,7 @@ fail:
///
/// @param buffer the buffer to use (expected to be empty)
/// @param opts Optional parameters.
-/// - on_input: lua callback for input sent, i e keypresses in terminal
+/// - on_input: Lua callback for input sent, i e keypresses in terminal
/// mode. Note: keypresses are sent raw as they would be to the pty
/// master end. For instance, a carriage return is sent
/// as a "\r", not as a "\n". |textlock| applies. It is possible
@@ -980,27 +990,31 @@ fail:
/// @return Channel id, or 0 on error
Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
+ if (cmdwin_type != 0 && buf == curbuf) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
LuaRef cb = LUA_NOREF;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("on_input", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", "on_input");
+ VALIDATE_T("on_input", kObjectTypeLuaRef, v->type, {
return 0;
- }
+ });
cb = v->data.luaref;
v->data.luaref = LUA_NOREF;
break;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(false, "'opts' key", k.data, {});
}
}
@@ -1016,9 +1030,12 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- Terminal *term = terminal_open(buf, topts);
- terminal_check_size(term);
- chan->term = term;
+ channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
+ if (chan->term != NULL) {
+ terminal_check_size(chan->term);
+ }
+ channel_decref(chan);
return (Integer)chan->id;
}
@@ -1040,7 +1057,7 @@ static void term_write(char *buf, size_t size, void *data) // NOLINT(readabilit
static void term_resize(uint16_t width, uint16_t height, void *data)
{
- // TODO(bfredl): lua callback
+ // TODO(bfredl): Lua callback
}
static void term_close(void *data)
@@ -1075,9 +1092,7 @@ void nvim_chan_send(Integer chan, String data, Error *err)
channel_send((uint64_t)chan, data.data, data.size,
false, &error);
- if (error) {
- api_set_error(err, kErrorTypeValidation, "%s", error);
- }
+ VALIDATE(!error, "%s", error, {});
}
/// Gets the current list of tabpage handles.
@@ -1117,7 +1132,7 @@ Tabpage nvim_get_current_tabpage(void)
/// @param[out] err Error details, if any
void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
tabpage_T *tp = find_tab_by_handle(tabpage, err);
@@ -1159,15 +1174,14 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// - false: Client must cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
static bool draining = false;
bool cancel = false;
- if (phase < -1 || phase > 3) {
- api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
+ VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
- }
+ });
Array args = ARRAY_DICT_INIT;
Object rv = OBJECT_INIT;
if (phase == -1 || phase == 1) { // Start of paste-stream.
@@ -1231,23 +1245,20 @@ theend:
/// @param[out] err Error details, if any
void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
- if (!prepare_yankreg_from_object(reg, type, lines.size)) {
- api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
+ VALIDATE_S((prepare_yankreg_from_object(reg, type, lines.size)), "type", type.data, {
goto cleanup;
- }
+ });
if (lines.size == 0) {
goto cleanup; // Nothing to do.
}
for (size_t i = 0; i < lines.size; i++) {
- if (lines.items[i].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid lines (expected array of strings)");
+ VALIDATE_T("line", kObjectTypeString, lines.items[i].type, {
goto cleanup;
- }
+ });
String line = lines.items[i].data.string;
reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size);
@@ -1255,14 +1266,12 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
finish_yankreg_from_object(reg, false);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
bool VIsual_was_active = VIsual_active;
msg_silent++; // Avoid "N more lines" message.
do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
msg_silent--;
VIsual_active = VIsual_was_active;
- try_end(err);
});
cleanup:
@@ -1291,9 +1300,9 @@ void nvim_subscribe(uint64_t channel_id, String event)
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- size_t length = (event.size < METHOD_MAXLEN ?
- event.size :
- METHOD_MAXLEN);
+ size_t length = (event.size < METHOD_MAXLEN
+ ? event.size
+ : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
@@ -1304,10 +1313,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
/// "#rrggbb" hexadecimal string.
///
/// Example:
-/// <pre>vim
-/// :echo nvim_get_color_by_name("Pink")
-/// :echo nvim_get_color_by_name("#cbcbcb")
-/// </pre>
+///
+/// ```vim
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// ```
///
/// @param name Color name or "#rrggbb" string
/// @return 24-bit RGB value, or -1 for invalid argument.
@@ -1348,11 +1358,8 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- if (opts->types.type == kObjectTypeArray) {
- types = opts->types.data.array;
- } else if (opts->types.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: types");
- return (Dictionary)ARRAY_DICT_INIT;
+ if (HAS_KEY(opts, context, types)) {
+ types = opts->types;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1373,8 +1380,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
} else if (strequal(s, "funcs")) {
int_types |= kCtxFuncs;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s);
- return (Dictionary)ARRAY_DICT_INIT;
+ VALIDATE_S(false, "type", s, {
+ return (Dictionary)ARRAY_DICT_INIT;
+ });
}
}
}
@@ -1390,7 +1398,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
/// Sets the current editor state from the given |context| map.
///
/// @param dict |Context| map.
-Object nvim_load_context(Dictionary dict)
+Object nvim_load_context(Dictionary dict, Error *err)
FUNC_API_SINCE(6)
{
Context ctx = CONTEXT_INIT;
@@ -1398,8 +1406,8 @@ Object nvim_load_context(Dictionary dict)
int save_did_emsg = did_emsg;
did_emsg = false;
- ctx_from_dict(dict, &ctx);
- if (!did_emsg) {
+ ctx_from_dict(dict, &ctx, err);
+ if (!ERROR_SET(err)) {
ctx_restore(&ctx, kCtxAll);
}
@@ -1421,7 +1429,7 @@ Dictionary nvim_get_mode(void)
get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr)));
+ PUT(rv, "mode", CSTR_TO_OBJ(modestr));
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
@@ -1446,29 +1454,31 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
///
/// Example:
-/// <pre>vim
-/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
-/// </pre>
+///
+/// ```vim
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// ```
///
/// is equivalent to:
-/// <pre>vim
-/// nmap <nowait> <Space><NL> <Nop>
-/// </pre>
+///
+/// ```vim
+/// nmap <nowait> <Space><NL> <Nop>
+/// ```
///
/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
+/// "ia", "ca" or "!a" for abbreviation in Insert mode, Cmdline mode, or both, respectively
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
-/// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default
-/// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including
-/// |:noremap| and "desc". Unknown key is an error.
-/// "desc" can be used to give a description to the mapping.
-/// When called from Lua, also accepts a "callback" key that takes a Lua function to
-/// call when the mapping is executed.
-/// When "expr" is true, "replace_keycodes" (boolean) can be used to replace keycodes
-/// in the resulting string (see |nvim_replace_termcodes()|), and a Lua callback
-/// returning `nil` is equivalent to returning an empty string.
+/// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|,
+/// values are booleans (default false). Also:
+/// - "noremap" disables |recursive_mapping|, like |:noremap|
+/// - "desc" human-readable description.
+/// - "callback" Lua function called in place of {rhs}.
+/// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the
+/// resulting string (see |nvim_replace_termcodes()|). Returning nil from the Lua
+/// "callback" is equivalent to returning an empty string.
/// @param[out] err Error details, if any.
void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
Error *err)
@@ -1527,7 +1537,10 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
/// - "commit" hash or similar identifier of commit
/// @param type Must be one of the following values. Client libraries should
/// default to "remote" unless overridden by the user.
-/// - "remote" remote client connected to Nvim.
+/// - "remote" remote client connected "Nvim flavored" MessagePack-RPC (responses
+/// must be in reverse order of requests). |msgpack-rpc|
+/// - "msgpack-rpc" remote client connected to Nvim via fully MessagePack-RPC
+/// compliant protocol.
/// - "ui" gui frontend
/// - "embedder" application using Nvim as a component (for example,
/// IDE/editor implementing a vim mode).
@@ -1651,34 +1664,20 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
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,
- kErrorTypeValidation,
- "Items in calls array must be arrays");
+ VALIDATE_T("'calls' item", kObjectTypeArray, calls.items[i].type, {
goto theend;
- }
+ });
Array call = calls.items[i].data.array;
- if (call.size != 2) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays of size 2");
+ VALIDATE_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
goto theend;
- }
-
- if (call.items[0].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Name must be String");
+ });
+ VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
goto theend;
- }
+ });
String name = call.items[0].data.string;
-
- if (call.items[1].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Args must be Array");
+ VALIDATE_T("call args", kObjectTypeArray, call.items[1].type, {
goto theend;
- }
+ });
Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler =
@@ -1726,34 +1725,44 @@ theend:
///
/// @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)
+/// @param writeln Append a trailing newline
+static void write_msg(String message, bool to_err, bool writeln)
{
static StringBuilder out_line_buf = KV_INITIAL_VALUE;
static StringBuilder err_line_buf = KV_INITIAL_VALUE;
+ StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf;
-#define PUSH_CHAR(i, line_buf, msg) \
- if (kv_max(line_buf) == 0) { \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
+#define PUSH_CHAR(c) \
+ if (kv_max(*line_buf) == 0) { \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
} \
- if (message.data[i] == NL) { \
- kv_push(line_buf, NUL); \
- msg(line_buf.items); \
- kv_drop(line_buf, kv_size(line_buf)); \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
- continue; \
- } \
- kv_push(line_buf, message.data[i]);
+ if (c == NL) { \
+ kv_push(*line_buf, NUL); \
+ if (to_err) { \
+ emsg(line_buf->items); \
+ } else { \
+ msg(line_buf->items, 0); \
+ } \
+ if (msg_silent == 0) { \
+ msg_didout = true; \
+ } \
+ kv_drop(*line_buf, kv_size(*line_buf)); \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
+ } else if (c == NUL) { \
+ kv_push(*line_buf, NL); \
+ } else { \
+ kv_push(*line_buf, c); \
+ }
no_wait_return++;
for (uint32_t i = 0; i < message.size; i++) {
if (got_int) {
break;
}
- if (to_err) {
- PUSH_CHAR(i, err_line_buf, emsg);
- } else {
- PUSH_CHAR(i, out_line_buf, msg);
- }
+ PUSH_CHAR(message.data[i]);
+ }
+ if (writeln) {
+ PUSH_CHAR(NL);
}
no_wait_return--;
msg_end();
@@ -1850,10 +1859,9 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array rvobj = ARRAY_DICT_INIT;
int *proc_list = NULL;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
goto end;
- }
+ });
size_t proc_count;
int rv = os_proc_children((int)pid, &proc_list, &proc_count);
@@ -1892,10 +1900,10 @@ Object nvim_get_proc(Integer pid, Error *err)
rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT;
rvobj.type = kObjectTypeDictionary;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
return NIL;
- }
+ });
+
#ifdef MSWIN
rvobj.data.dictionary = os_proc_info((int)pid);
if (rvobj.data.dictionary.size == 0) { // Process not found.
@@ -1937,10 +1945,9 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
Error *err)
FUNC_API_SINCE(6)
{
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return;
- }
+ });
if (finish) {
insert = true;
@@ -1961,13 +1968,10 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
g = &pum_grid;
} else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid);
- if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
- g = &wp->w_grid_alloc;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "No grid with the given handle");
+ VALIDATE_INT((wp != NULL && wp->w_grid_alloc.chars != NULL), "grid handle", grid, {
return ret;
- }
+ });
+ g = &wp->w_grid_alloc;
}
if (row < 0 || row >= g->rows
@@ -1976,7 +1980,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off])));
+ char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
+ schar_get(sc_buf, g->chars[off]);
+ ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@@ -1992,6 +1998,14 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+/// For testing. The condition in schar_cache_clear_if_full is hard to
+/// reach, so this function can be used to force a cache clear in a test.
+void nvim__invalidate_glyph_cache(void)
+{
+ schar_cache_clear();
+ must_redraw = UPD_CLEAR;
+}
+
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{
@@ -2000,7 +2014,7 @@ Object nvim__unpack(String str, Error *err)
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @return true if the mark was deleted, else false.
/// @see |nvim_buf_del_mark()|
@@ -2009,29 +2023,26 @@ Boolean nvim_del_mark(String name, Error *err)
FUNC_API_SINCE(8)
{
bool res = false;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
// Only allow file/uppercase marks
// TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
- if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
- res = set_mark(NULL, name, 0, 0, err);
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
- }
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
+ return res;
+ });
+ res = set_mark(NULL, name, 0, 0, err);
return res;
}
-/// Return a tuple (row, col, buffer, buffername) representing the position of
-/// the uppercase/file named mark. See |mark-motions|.
+/// Returns a `(row, col, buffer, buffername)` tuple representing the position
+/// of the uppercase/file named mark. "End of line" column position is returned
+/// as |v:maxcol| (big number). See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @param opts Optional parameters. Reserved for future use.
/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
@@ -2043,16 +2054,13 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
{
Array rv = ARRAY_DICT_INIT;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
+ });
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
return rv;
- }
+ });
xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
pos_T pos = mark->fmark.mark;
@@ -2092,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
ADD(rv, INTEGER_OBJ(row));
ADD(rv, INTEGER_OBJ(col));
ADD(rv, INTEGER_OBJ(bufnr));
- ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+ ADD(rv, CSTR_TO_OBJ(filename));
if (allocated) {
xfree(filename);
@@ -2113,6 +2121,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
/// - use_winbar: (boolean) Evaluate winbar instead of statusline.
/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid}
/// is ignored. Mutually exclusive with {use_winbar}.
+/// - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline.
///
/// @param[out] err Error details, if any.
/// @return Dictionary containing statusline information, with these keys:
@@ -2130,106 +2139,117 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int maxwidth;
int fillchar = 0;
+ int statuscol_lnum = 0;
Window window = 0;
- bool use_winbar = false;
- bool use_tabline = false;
- bool highlights = false;
if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) {
const char *const errmsg = check_stl_option(str.data);
- if (errmsg) {
- api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+ VALIDATE(!errmsg, "%s", errmsg, {
return result;
- }
+ });
}
- if (HAS_KEY(opts->winid)) {
- if (opts->winid.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "winid must be an integer");
- return result;
- }
-
- window = (Window)opts->winid.data.integer;
+ if (HAS_KEY(opts, eval_statusline, winid)) {
+ window = opts->winid;
}
- if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
- || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
- != opts->fillchar.data.string.size)) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
+ if (HAS_KEY(opts, eval_statusline, fillchar)) {
+ VALIDATE_EXP((*opts->fillchar.data != 0
+ && ((size_t)utf_ptr2len(opts->fillchar.data) == opts->fillchar.size)),
+ "fillchar", "single character", NULL, {
return result;
- }
- fillchar = utf_ptr2char(opts->fillchar.data.string.data);
+ });
+ fillchar = utf_ptr2char(opts->fillchar.data);
}
- if (HAS_KEY(opts->highlights)) {
- highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
- if (ERROR_SET(err)) {
- return result;
- }
- }
- if (HAS_KEY(opts->use_winbar)) {
- use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
+ int use_bools = (int)opts->use_winbar + (int)opts->use_tabline;
- if (ERROR_SET(err)) {
- return result;
- }
+ win_T *wp = opts->use_tabline ? curwin : find_window_by_handle(window, err);
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
}
- if (HAS_KEY(opts->use_tabline)) {
- use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
- if (ERROR_SET(err)) {
+ if (HAS_KEY(opts, eval_statusline, use_statuscol_lnum)) {
+ statuscol_lnum = (int)opts->use_statuscol_lnum;
+ VALIDATE_RANGE(statuscol_lnum > 0 && statuscol_lnum <= wp->w_buffer->b_ml.ml_line_count,
+ "use_statuscol_lnum", {
return result;
- }
+ });
+ use_bools++;
}
- if (use_winbar && use_tabline) {
- api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ VALIDATE(use_bools <= 1, "%s",
+ "Can only use one of 'use_winbar', 'use_tabline' and 'use_statuscol_lnum'", {
return result;
- }
+ });
- win_T *wp, *ewp;
+ int stc_hl_id = 0;
+ statuscol_T statuscol = { 0 };
+ SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 };
- if (use_tabline) {
- wp = NULL;
- ewp = curwin;
+ if (opts->use_tabline) {
fillchar = ' ';
} else {
- wp = find_window_by_handle(window, err);
- if (wp == NULL) {
- api_set_error(err, kErrorTypeException, "unknown winid %d", window);
- return result;
- }
- ewp = wp;
-
if (fillchar == 0) {
- if (use_winbar) {
+ if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr;
} else {
int attr;
fillchar = fillchar_status(&attr, wp);
}
}
- }
+ if (statuscol_lnum) {
+ int line_id = 0;
+ int cul_id = 0;
+ int num_id = 0;
+ linenr_T lnum = statuscol_lnum;
+ wp->w_scwidth = win_signcol_count(wp);
+ decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);
+
+ statuscol.sattrs = sattrs;
+ statuscol.foldinfo = fold_info(wp, lnum);
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+
+ if (wp->w_p_cul) {
+ if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) {
+ wp->w_cursorline = statuscol.foldinfo.fi_lnum;
+ }
+ statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR);
+ }
- if (HAS_KEY(opts->maxwidth)) {
- if (opts->maxwidth.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
- return result;
+ statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0;
+ if (num_id) {
+ stc_hl_id = num_id;
+ } else if (statuscol.use_cul) {
+ stc_hl_id = HLF_CLN + 1;
+ } else if (wp->w_p_rnu) {
+ stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB) + 1;
+ } else {
+ stc_hl_id = HLF_N + 1;
+ }
+
+ set_vim_var_nr(VV_LNUM, lnum);
+ set_vim_var_nr(VV_RELNUM, labs(get_cursor_rel_lnum(wp, lnum)));
+ set_vim_var_nr(VV_VIRTNUM, 0);
}
+ }
- maxwidth = (int)opts->maxwidth.data.integer;
+ if (HAS_KEY(opts, eval_statusline, maxwidth)) {
+ maxwidth = (int)opts->maxwidth;
} else {
- maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
+ maxwidth = statuscol_lnum ? win_col_off(wp)
+ : (opts->use_tabline
+ || (!opts->use_winbar
+ && global_stl_height() > 0)) ? Columns : wp->w_width;
}
char buf[MAXPATHL];
stl_hlrec_t *hltab;
- stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL;
// Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
- int p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = false;
+ int p_crb_save = wp->w_p_crb;
+ wp->w_p_crb = false;
- int width = build_stl_str_hl(ewp,
+ int width = build_stl_str_hl(wp,
buf,
sizeof(buf),
str.data,
@@ -2237,25 +2257,25 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
0,
fillchar,
maxwidth,
- hltab_ptr,
+ opts->highlights ? &hltab : NULL,
NULL,
- NULL);
+ statuscol_lnum ? &statuscol : NULL);
PUT(result, "width", INTEGER_OBJ(width));
// Restore original value of 'cursorbind'
- ewp->w_p_crb = p_crb_save;
+ wp->w_p_crb = p_crb_save;
- if (highlights) {
+ if (opts->highlights) {
Array hl_values = ARRAY_DICT_INIT;
const char *grpname;
- char user_group[6];
+ char user_group[15]; // strlen("User") + strlen("2147483647") + NUL
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
PUT(hl_info, "start", INTEGER_OBJ(0));
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2266,10 +2286,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
Dictionary hl_info = ARRAY_DICT_INIT;
- PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
+ PUT(hl_info, "start", INTEGER_OBJ(sp->start - buf));
if (sp->userhl == 0) {
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
} else if (sp->userhl < 0) {
grpname = syn_id2name(-sp->userhl);
} else {
@@ -2281,7 +2301,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
PUT(result, "highlights", ARRAY_OBJ(hl_values));
}
- PUT(result, "str", CSTR_TO_OBJ((char *)buf));
+ PUT(result, "str", CSTR_TO_OBJ(buf));
return result;
}