aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/vim.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api/vim.c')
-rw-r--r--src/nvim/api/vim.c515
1 files changed, 338 insertions, 177 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3be45d0cf7..b5cc02e761 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -17,6 +17,7 @@
#include "nvim/api/window.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/context.h"
#include "nvim/decoration.h"
#include "nvim/edit.h"
@@ -59,7 +60,6 @@
#endif
void api_vim_free_all_mem(void)
- FUNC_API_NOEXPORT
{
String name;
handle_T id;
@@ -196,7 +196,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
Integer nvim_get_hl_id_by_name(String name)
FUNC_API_SINCE(7)
{
- return syn_check_group((const char_u *)name.data, (int)name.size);
+ return syn_check_group(name.data, (int)name.size);
}
Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
@@ -228,7 +228,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
FUNC_API_SINCE(7)
{
- int hl_id = syn_check_group( (char_u *)(name.data), (int)name.size);
+ int hl_id = syn_check_group(name.data, (int)name.size);
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
@@ -264,7 +264,6 @@ void nvim__set_hl_ns(Integer ns_id, Error *err)
}
static void on_redraw_event(void **argv)
- FUNC_API_NOEXPORT
{
redraw_all_later(NOT_VALID);
}
@@ -714,7 +713,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
}
fn = (String) {
.data = (char *)di->di_tv.vval.v_string,
- .size = strlen((char *)di->di_tv.vval.v_string),
+ .size = STRLEN(di->di_tv.vval.v_string),
};
}
@@ -758,6 +757,11 @@ ArrayOf(String) nvim_list_runtime_paths(Error *err)
return nvim_get_runtime_file(NULL_STRING, true, err);
}
+Array nvim__runtime_inspect(void)
+{
+ return runtime_inspect();
+}
+
/// Find files in runtime directories
///
/// 'name' can contain wildcards. For example
@@ -796,6 +800,25 @@ String nvim__get_lib_dir(void)
return cstr_as_string(get_lib_dir());
}
+/// Find files in runtime directories
+///
+/// @param pat pattern of files to search for
+/// @param all whether to return all matches or only the first
+/// @param options
+/// 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);
+ if (ERROR_SET(err)) {
+ return (Array)ARRAY_DICT_INIT;
+ }
+ return runtime_get_named(is_lua, pat, all);
+}
+
+
/// Changes the global working directory.
///
/// @param dir Directory path
@@ -1221,14 +1244,20 @@ fail:
/// buffer in a configured window before calling this. For instance, for a
/// floating display, first create an empty buffer using |nvim_create_buf()|,
/// then display it using |nvim_open_win()|, and then call this function.
-/// Then |nvim_chan_send()| cal be called immediately to process sequences
+/// Then |nvim_chan_send()| can be called immediately to process sequences
/// in a virtual terminal having the intended size.
///
/// @param buffer the buffer to use (expected to be empty)
-/// @param opts Optional parameters. Reserved for future use.
+/// @param opts Optional parameters.
+/// - 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
+/// to call |nvim_chan_send| directly in the callback however.
+/// ["input", term, bufnr, data]
/// @param[out] err Error details, if any
/// @return Channel id, or 0 on error
-Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
+Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1236,13 +1265,27 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
return 0;
}
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- 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");
+ return 0;
+ }
+ cb = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ break;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ }
}
TerminalOptions topts;
Channel *chan = channel_alloc(kChannelStreamInternal);
+ chan->stream.internal.cb = cb;
topts.data = chan;
// NB: overridden in terminal_check_size if a window is already
// displaying the buffer
@@ -1260,7 +1303,18 @@ Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
static void term_write(char *buf, size_t size, void *data)
{
- // TODO(bfredl): lua callback
+ Channel *chan = data;
+ LuaRef cb = chan->stream.internal.cb;
+ if (cb == LUA_NOREF) {
+ return;
+ }
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = INTEGER_OBJ((Integer)chan->id);
+ args.items[1] = BUFFER_OBJ(terminal_buf(chan->term));
+ args.items[2] = STRING_OBJ(((String){ .data = buf, .size = size }));
+ textlock++;
+ nlua_call_ref(cb, "input", args, false, NULL);
+ textlock--;
}
static void term_resize(uint16_t width, uint16_t height, void *data)
@@ -1305,153 +1359,6 @@ void nvim_chan_send(Integer chan, String data, Error *err)
}
}
-/// Open a new window.
-///
-/// Currently this is used to open floating and external windows.
-/// Floats are windows that are drawn above the split layout, at some anchor
-/// position in some other window. Floats can be drawn internally or by external
-/// GUI with the |ui-multigrid| extension. External windows are only supported
-/// with multigrid GUIs, and are displayed as separate top-level windows.
-///
-/// For a general overview of floats, see |api-floatwin|.
-///
-/// Exactly one of `external` and `relative` must be specified. The `width` and
-/// `height` of the new window must be specified.
-///
-/// With relative=editor (row=0,col=0) refers to the top-left corner of the
-/// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
-/// corner. Fractional values are allowed, but the builtin implementation
-/// (used by non-multigrid UIs) will always round down to nearest integer.
-///
-/// Out-of-bounds values, and configurations that make the float not fit inside
-/// the main editor, are allowed. The builtin implementation truncates values
-/// so floats are fully within the main screen grid. External GUIs
-/// could let floats hover outside of the main window like a tooltip, but
-/// this should not be used to specify arbitrary WM screen positions.
-///
-/// Example (Lua): window-relative float
-/// <pre>
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', row=3, col=3, width=12, height=3})
-/// </pre>
-///
-/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
-/// <pre>
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', width=12, height=3, bufpos={100,10}})
-/// </pre>
-///
-/// @param buffer Buffer to display, or 0 for current buffer
-/// @param enter Enter the window (make it the current window)
-/// @param config Map defining the window configuration. Keys:
-/// - `relative`: Sets the window layout to "floating", placed at (row,col)
-/// coordinates relative to:
-/// - "editor" The global editor grid
-/// - "win" Window given by the `win` field, or current window.
-/// - "cursor" Cursor position in current window.
-/// - `win`: |window-ID| for relative="win".
-/// - `anchor`: Decides which corner of the float to place at (row,col):
-/// - "NW" northwest (default)
-/// - "NE" northeast
-/// - "SW" southwest
-/// - "SE" southeast
-/// - `width`: Window width (in character cells). Minimum of 1.
-/// - `height`: Window height (in character cells). Minimum of 1.
-/// - `bufpos`: Places float relative to buffer text (only when
-/// relative="win"). Takes a tuple of zero-indexed [line, column].
-/// `row` and `col` if given are applied relative to this
-/// position, else they default to `row=1` and `col=0`
-/// (thus like a tooltip near the buffer text).
-/// - `row`: Row position in units of "screen cell height", may be fractional.
-/// - `col`: Column position in units of "screen cell width", may be
-/// fractional.
-/// - `focusable`: Enable focus by user actions (wincmds, mouse events).
-/// Defaults to true. Non-focusable windows can be entered by
-/// |nvim_set_current_win()|.
-/// - `external`: GUI should display the window as an external
-/// top-level window. Currently accepts no other positioning
-/// configuration together with this.
-/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
-/// floats with lower indices. Must be larger than zero. The
-/// following screen elements have hard-coded z-indices:
-/// - 100: insert completion popupmenu
-/// - 200: message scrollback
-/// - 250: cmdline completion popupmenu (when wildoptions+=pum)
-/// The default value for floats are 50. In general, values below 100 are
-/// recommended, unless there is a good reason to overshadow builtin
-/// elements.
-/// - `style`: Configure the appearance of the window. Currently only takes
-/// one non-empty value:
-/// - "minimal" Nvim will display the window with many UI options
-/// disabled. This is useful when displaying a temporary
-/// float where the text should not be edited. Disables
-/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
-/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto` and 'colorcolumn' is cleared. The
-/// end-of-buffer region is hidden by setting `eob` flag of
-/// 'fillchars' to a space char, and clearing the
-/// |EndOfBuffer| region in 'winhighlight'.
-/// - `border`: Style of (optional) window border. This can either be a string
-/// or an array. The string values are
-/// - "none": No border (default).
-/// - "single": A single line box.
-/// - "double": A double line box.
-/// - "rounded": Like "single", but with rounded corners ("╭" etc.).
-/// - "solid": Adds padding by a single whitespace cell.
-/// - "shadow": A drop shadow effect by blending with the background.
-/// - If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specifify the eight chars building up the border
-/// in a clockwise fashion starting with the top-left corner. As an
-/// example, the double box style could be specified as
-/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
-/// If the number of chars are less than eight, they will be repeated. Thus
-/// an ASCII border could be specified as
-/// [ "/", "-", "\\", "|" ],
-/// or all chars the same as
-/// [ "x" ].
-/// An empty string can be used to turn off a specific border, for instance,
-/// [ "", "", "", ">", "", "", "", "<" ]
-/// will only make vertical borders but not horizontal ones.
-/// By default, `FloatBorder` highlight is used, which links to `VertSplit`
-/// when not defined. It could also be specified by character:
-/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ].
-/// - `noautocmd`: If true then no buffer-related autocommand events such as
-/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
-/// calling this function.
-///
-/// @param[out] err Error details, if any
-///
-/// @return Window handle, or 0 on error
-Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, Error *err)
- FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
-{
- FloatConfig fconfig = FLOAT_CONFIG_INIT;
- if (!parse_float_config(config, &fconfig, false, true, err)) {
- return 0;
- }
- win_T *wp = win_new_float(NULL, fconfig, err);
- if (!wp) {
- return 0;
- }
- if (enter) {
- win_enter(wp, false);
- }
- if (!win_valid(wp)) {
- api_set_error(err, kErrorTypeException, "Window was closed immediately");
- return 0;
- }
- if (buffer > 0) {
- win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
- }
-
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(wp);
- didset_window_options(wp);
- }
- return wp->handle;
-}
-
/// Gets the current list of tabpage handles.
///
/// @return List of tabpage handles
@@ -1507,7 +1414,7 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
}
}
-/// Creates a new *namespace*, or gets an existing one.
+/// Creates a new \*namespace\* or gets an existing one.
///
/// Namespaces are used for buffer highlights and virtual text, see
/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|.
@@ -1758,24 +1665,15 @@ Dictionary nvim_get_color_map(void)
/// @param[out] err Error details, if any
///
/// @return map of global |context|.
-Dictionary nvim_get_context(Dictionary opts, Error *err)
+Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object v = opts.items[i].value;
- if (strequal("types", k.data)) {
- if (v.type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: %s",
- k.data);
- return (Dictionary)ARRAY_DICT_INIT;
- }
- types = v.data.array;
- } else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return (Dictionary)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;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1885,7 +1783,7 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// as keys excluding |<buffer>| but including |noremap|.
/// Values are Booleans. Unknown key is an error.
/// @param[out] err Error details, if any.
-void nvim_set_keymap(String mode, String lhs, String rhs, Dictionary opts, Error *err)
+void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err)
FUNC_API_SINCE(6)
{
modify_keymap(-1, false, mode, lhs, rhs, opts, err);
@@ -1911,7 +1809,7 @@ void nvim_del_keymap(String mode, String lhs, Error *err)
/// @param[out] err Error details, if any.
///
/// @returns Map of maps describing commands.
-Dictionary nvim_get_commands(Dictionary opts, Error *err)
+Dictionary nvim_get_commands(Dict(get_commands) *opts, Error *err)
FUNC_API_SINCE(4)
{
return nvim_buf_get_commands(-1, opts, err);
@@ -2937,3 +2835,266 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro
error:
decor_provider_clear(p);
}
+
+/// Deletes a uppercase/file named mark. See |mark-motions|.
+///
+/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @param name Mark name
+/// @return true if the mark was deleted, else false.
+/// @see |nvim_buf_del_mark()|
+/// @see |nvim_get_mark()|
+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");
+ 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);
+ }
+ return res;
+}
+
+/// Return a tuple (row, col, buffer, buffername) representing the position of
+/// the uppercase/file named mark. See |mark-motions|.
+///
+/// Marks are (1,0)-indexed. |api-indexing|
+///
+/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @param name Mark name
+/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
+/// not set.
+/// @see |nvim_buf_set_mark()|
+/// @see |nvim_del_mark()|
+Array nvim_get_mark(String name, Error *err)
+ FUNC_API_SINCE(8)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ if (name.size != 1) {
+ api_set_error(err, kErrorTypeValidation,
+ "Mark name must be a single character");
+ 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);
+ return rv;
+ }
+
+ xfmark_T mark = get_global_mark(*name.data);
+ pos_T pos = mark.fmark.mark;
+ bool allocated = false;
+ int bufnr;
+ char *filename;
+
+ // Marks are from an open buffer it fnum is non zero
+ if (mark.fmark.fnum != 0) {
+ bufnr = mark.fmark.fnum;
+ filename = (char *)buflist_nr2name(bufnr, true, true);
+ allocated = true;
+ // Marks comes from shada
+ } else {
+ filename = (char *)mark.fname;
+ bufnr = 0;
+ }
+
+ bool exists = filename != NULL;
+ Integer row;
+ Integer col;
+
+ if (!exists || pos.lnum <= 0) {
+ if (allocated) {
+ xfree(filename);
+ allocated = false;
+ }
+ filename = "";
+ bufnr = 0;
+ row = 0;
+ col = 0;
+ } else {
+ row = pos.lnum;
+ col = pos.col;
+ }
+
+ ADD(rv, INTEGER_OBJ(row));
+ ADD(rv, INTEGER_OBJ(col));
+ ADD(rv, INTEGER_OBJ(bufnr));
+ ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+
+ if (allocated) {
+ xfree(filename);
+ }
+
+ return rv;
+}
+
+/// Evaluates statusline string.
+///
+/// @param str Statusline string (see 'statusline').
+/// @param opts Optional parameters.
+/// - winid: (number) |window-ID| of the window to use as context for statusline.
+/// - maxwidth: (number) Maximum width of statusline.
+/// - fillchar: (string) Character to fill blank spaces in the statusline (see
+/// 'fillchars').
+/// - highlights: (boolean) Return highlight information.
+/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid}
+/// is ignored.
+///
+/// @param[out] err Error details, if any.
+/// @return Dictionary containing statusline information, with these keys:
+/// - str: (string) Characters that will be displayed on the statusline.
+/// - width: (number) Display width of the statusline.
+/// - highlights: Array containing highlight information of the statusline. Only included when
+/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a
+/// |Dictionary| with these keys:
+/// - start: (number) Byte index (0-based) of first character that uses the highlight.
+/// - group: (string) Name of highlight group.
+Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *err)
+ FUNC_API_SINCE(8) FUNC_API_FAST
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ int maxwidth;
+ char fillchar = 0;
+ Window window = 0;
+ bool use_tabline = false;
+ bool highlights = false;
+
+ 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->fillchar)) {
+ if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) {
+ api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character");
+ return result;
+ }
+
+ fillchar = opts->fillchar.data.string.data[0];
+ }
+
+ 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_tabline)) {
+ use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
+
+ if (ERROR_SET(err)) {
+ return result;
+ }
+ }
+
+ win_T *wp, *ewp;
+
+ if (use_tabline) {
+ wp = NULL;
+ ewp = curwin;
+ fillchar = ' ';
+ } else {
+ wp = find_window_by_handle(window, err);
+ ewp = wp;
+
+ if (fillchar == 0) {
+ int attr;
+ fillchar = (char)fillchar_status(&attr, wp);
+ }
+ }
+
+ if (HAS_KEY(opts->maxwidth)) {
+ if (opts->maxwidth.type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
+ return result;
+ }
+
+ maxwidth = (int)opts->maxwidth.data.integer;
+ } else {
+ maxwidth = use_tabline ? 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 width = build_stl_str_hl(
+ ewp,
+ (char_u *)buf,
+ sizeof(buf),
+ (char_u *)str.data,
+ false,
+ (char_u)fillchar,
+ maxwidth,
+ hltab_ptr,
+ NULL);
+
+ PUT(result, "width", INTEGER_OBJ(width));
+
+ // Restore original value of 'cursorbind'
+ ewp->w_p_crb = p_crb_save;
+
+ if (highlights) {
+ Array hl_values = ARRAY_DICT_INIT;
+ const char *grpname;
+ char user_group[6];
+
+ // If first character doesn't have a defined highlight,
+ // add the default highlight at the beginning of the highlight list
+ if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) {
+ Dictionary hl_info = ARRAY_DICT_INIT;
+ grpname = get_default_stl_hl(wp);
+
+ PUT(hl_info, "start", INTEGER_OBJ(0));
+ PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
+
+ ADD(hl_values, DICTIONARY_OBJ(hl_info));
+ }
+
+ 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));
+
+ if (sp->userhl == 0) {
+ grpname = get_default_stl_hl(wp);
+ } else if (sp->userhl < 0) {
+ grpname = (char *)syn_id2name(-sp->userhl);
+ } else {
+ snprintf(user_group, sizeof(user_group), "User%d", sp->userhl);
+ grpname = user_group;
+ }
+
+ PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
+
+ ADD(hl_values, DICTIONARY_OBJ(hl_info));
+ }
+
+ PUT(result, "highlights", ARRAY_OBJ(hl_values));
+ }
+
+ PUT(result, "str", CSTR_TO_OBJ((char *)buf));
+
+ return result;
+}