Merge pull request #1817 from bfredl/bufmatch

support buffer-associated highlighting by external plugins
This commit is contained in:
Björn Linse 2016-02-23 23:32:21 +01:00
commit b10c9b4f5b
17 changed files with 807 additions and 69 deletions

99
runtime/doc/api.txt Normal file
View File

@ -0,0 +1,99 @@
*api.txt* For Nvim. {Nvim}
NVIM REFERENCE MANUAL by Thiago de Arruda
The C API of Nvim *nvim-api*
1. Introduction |nvim-api-intro|
2. API Types |nvim-api-types|
3. API metadata |nvim-api-metadata|
4. Buffer highlighting |nvim-api-highlights|
==============================================================================
1. Introduction *nvim-api-intro*
Nvim defines a C API as the primary way for external code to interact with
the NVim core. In the present version of Nvim the API is primarily used by
external processes to interact with Nvim using the msgpack-rpc protocol, see
|msgpack-rpc|. The API will also be used from vimscript to access new Nvim core
features, but this is not implemented yet. Later on, Nvim might be embeddable
in C applications as libnvim, and the application will then control the
embedded instance by calling the C API directly.
==============================================================================
2. API Types *nvim-api-types*
Nvim's C API uses custom types for all functions. Some are just typedefs
around C99 standard types, and some are Nvim defined data structures.
Boolean -> bool
Integer (signed 64-bit integer) -> int64_t
Float (IEEE 754 double precision) -> double
String -> {char* data, size_t size} struct
Additionally, the following data structures are defined:
Array
Dictionary
Object
The following handle types are defined as integer typedefs, but are
discriminated as separate types in an Object:
Buffer -> enum value kObjectTypeBuffer
Window -> enum value kObjectTypeWindow
Tabpage -> enum value kObjectTypeTabpage
==============================================================================
3. API metadata *nvim-api-metadata*
Nvim exposes metadata about the API as a Dictionary with the following keys:
functions calling signature of the API functions
types The custom handle types defined by Nvim
error_types The possible kinds of errors an API function can exit with.
This metadata is mostly useful for external programs accessing the api over
msgpack-api, see |msgpack-rpc-api|.
==============================================================================
4. Buffer highlighting *nvim-api-highlights*
Nvim allows plugins to add position-based highlights to buffers. This is
similar to |matchaddpos()| but with some key differences. The added highlights
are associated with a buffer and adapts to line insertions and deletions,
similar to signs. It is also possible to manage a set of highlights as a group
and delete or replace all at once.
The intended use case are linter or semantic highlighter plugins that monitor
a buffer for changes, and in the background compute highlights to the buffer.
Another use case are plugins that show output in an append-only buffer, and
want to add highlights to the outputs. Highlight data cannot be preserved
on writing and loading a buffer to file, nor in undo/redo cycles.
Highlights are registered using the |buffer_add_highlight| function, see the
generated API documentation for details. If an external highlighter plugin is
adding a large number of highlights in a batch, performance can be improved by
calling |buffer_add_highlight| as an asynchronous notification, after first
(synchronously) reqesting a source id. Here is an example using wrapper
functions in the python client:
>
src = vim.new_highlight_source()
buf = vim.current.buffer
for i in range(5):
buf.add_highlight("String",i,0,-1,src_id=src)
# some time later
buf.clear_highlight(src)
<
If the highlights don't need to be deleted or updated, just pass -1 as
src_id (this is the default in python). |buffer_clear_highlight| can be used
to clear highligts from a specific source, in a specific line range or the
entire buffer by passing in the line range 0, -1 (the later is the default
in python as used above).
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -7,7 +7,7 @@
The Msgpack-RPC Interface to Nvim *msgpack-rpc* The Msgpack-RPC Interface to Nvim *msgpack-rpc*
1. Introduction |msgpack-rpc-intro| 1. Introduction |msgpack-rpc-intro|
2. API |msgpack-rpc-api| 2. API mapping |msgpack-rpc-api|
3. Connecting |msgpack-rpc-connecting| 3. Connecting |msgpack-rpc-connecting|
4. Clients |msgpack-rpc-clients| 4. Clients |msgpack-rpc-clients|
5. Types |msgpack-rpc-types| 5. Types |msgpack-rpc-types|
@ -36,13 +36,13 @@ Nvim's msgpack-rpc interface is like a more powerful version of Vim's
`clientserver` feature. `clientserver` feature.
============================================================================== ==============================================================================
2. API *msgpack-rpc-api* 2. API mapping *msgpack-rpc-api*
The Nvim C API is automatically exposed to the msgpack-rpc interface by the The Nvim C API, see |nvim-api|, is automatically exposed to the msgpack-rpc
build system, which parses headers at src/nvim/api from the project root. A interface by the build system, which parses headers at src/nvim/api from the
dispatch function is generated, which matches msgpack-rpc method names with project root. A dispatch function is generated, which matches msgpack-rpc method
non-static API functions, converting/validating arguments and return values names with non-static API functions, converting/validating arguments and return
back to msgpack. values back to msgpack.
Client libraries will normally provide wrappers that hide msgpack-rpc details Client libraries will normally provide wrappers that hide msgpack-rpc details
from programmers. The wrappers can be automatically generated by reading from programmers. The wrappers can be automatically generated by reading
@ -63,7 +63,7 @@ Here's a simple way to get human-readable description of the API (requires
Python and the `pyyaml`/`msgpack-python` pip packages): Python and the `pyyaml`/`msgpack-python` pip packages):
> >
nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))' > api.yaml nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))' > api.yaml
<
============================================================================== ==============================================================================
3. Connecting *msgpack-rpc-connecting* 3. Connecting *msgpack-rpc-connecting*
@ -162,8 +162,8 @@ https://github.com/msgpack-rpc/msgpack-rpc-ruby/blob/master/lib/msgpack/rpc/tran
============================================================================== ==============================================================================
5. Types *msgpack-rpc-types* 5. Types *msgpack-rpc-types*
Nvim's C API uses custom types for all functions (some are just typedefs Nvim's C API uses custom types for all functions, se |nvim-api-types|.
around C99 standard types). The types can be split into two groups: For the purpose of mapping to msgpack, he types can be split into two groups:
- Basic types that map natively to msgpack (and probably have a default - Basic types that map natively to msgpack (and probably have a default
representation in msgpack-supported programming languages) representation in msgpack-supported programming languages)

View File

@ -19,6 +19,7 @@
#include "nvim/mark.h" #include "nvim/mark.h"
#include "nvim/fileio.h" #include "nvim/fileio.h"
#include "nvim/move.h" #include "nvim/move.h"
#include "nvim/syntax.h"
#include "nvim/window.h" #include "nvim/window.h"
#include "nvim/undo.h" #include "nvim/undo.h"
@ -514,6 +515,99 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err)
return rv; return rv;
} }
/// Adds a highlight to buffer.
///
/// This can be used for plugins which dynamically generate highlights to a
/// buffer (like a semantic highlighter or linter). The function adds a single
/// highlight to a buffer. Unlike matchaddpos() highlights follow changes to
/// line numbering (as lines are inserted/removed above the highlighted line),
/// like signs and marks do.
///
/// "src_id" is useful for batch deletion/updating of a set of highlights. When
/// 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
/// pass in -1 for "src_id".
///
/// If "hl_group" is the empty string no highlight is added, but a new src_id
/// is still returned. This is useful for an external plugin to synchrounously
/// 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
/// @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)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
if (line < 0 || line >= MAXLNUM) {
api_set_error(err, Validation, _("Line number outside range"));
return 0;
}
if (col_start < 0 || col_start > MAXCOL) {
api_set_error(err, Validation, _("Column value outside range"));
return 0;
}
if (col_end < 0 || col_end > MAXCOL) {
col_end = MAXCOL;
}
int hlg_id = syn_name2id((char_u*)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;
}
/// Clears highlights from a given source group and a range of lines
///
/// 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 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)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return;
}
if (line_start < 0 || line_start >= MAXLNUM) {
api_set_error(err, Validation, _("Line number outside range"));
return;
}
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end);
}
// Check if deleting lines made the cursor position invalid. // Check if deleting lines made the cursor position invalid.
// Changed the lines from "lo" to "hi" and added "extra" lines (negative if // Changed the lines from "lo" to "hi" and added "extra" lines (negative if

View File

@ -580,16 +580,17 @@ free_buffer_stuff (
) )
{ {
if (free_options) { if (free_options) {
clear_wininfo(buf); /* including window-local options */ clear_wininfo(buf); // including window-local options
free_buf_options(buf, TRUE); free_buf_options(buf, true);
ga_clear(&buf->b_s.b_langp); ga_clear(&buf->b_s.b_langp);
} }
vars_clear(&buf->b_vars->dv_hashtab); /* free all internal variables */ vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables
hash_init(&buf->b_vars->dv_hashtab); hash_init(&buf->b_vars->dv_hashtab);
uc_clear(&buf->b_ucmds); /* clear local user commands */ uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf); /* delete any signs */ buf_delete_signs(buf); // delete any signs
map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */ bufhl_clear_all(buf); // delete any highligts
map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */ map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
xfree(buf->b_start_fenc); xfree(buf->b_start_fenc);
buf->b_start_fenc = NULL; buf->b_start_fenc = NULL;
} }
@ -4870,6 +4871,224 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a
} }
} }
// bufhl: plugin highlights associated with a buffer
/// Adds a highlight to buffer.
///
/// Unlike matchaddpos() highlights follow changes to line numbering (as lines
/// are inserted/removed above the highlighted line), like signs and marks do.
///
/// When called with "src_id" set to 0, a unique source id is generated and
/// returned. Succesive calls can pass it in as "src_id" to add new highlights
/// to the same source group. All highlights in the same group can be cleared
/// at once. If the highlight never will be manually deleted pass in -1 for
/// "src_id"
///
/// if "hl_id" or "lnum" is invalid no highlight is added, but a new src_id
/// is still returned.
///
/// @param buf The buffer to add highlights to
/// @param src_id src_id to use or 0 to use a new src_id group,
/// or -1 for ungrouped highlight.
/// @param hl_id Id of the highlight group to use
/// @param lnum The line to highlight
/// @param col_start First column to highlight
/// @param col_end The last column to highlight,
/// or -1 to highlight to end of line
/// @return The src_id that was used
int bufhl_add_hl(buf_T *buf,
int src_id,
int hl_id,
linenr_T lnum,
colnr_T col_start,
colnr_T col_end) {
static int next_src_id = 1;
if (src_id == 0) {
src_id = next_src_id++;
}
if (hl_id <= 0) {
// no highlight group or invalid line, just return src_id
return src_id;
}
if (!buf->b_bufhl_info) {
buf->b_bufhl_info = map_new(linenr_T, bufhl_vec_T)();
}
bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(buf->b_bufhl_info,
lnum, true);
bufhl_hl_item_T *hlentry = kv_pushp(bufhl_hl_item_T, *lineinfo);
hlentry->src_id = src_id;
hlentry->hl_id = hl_id;
hlentry->start = col_start;
hlentry->stop = col_end;
if (0 < lnum && lnum <= buf->b_ml.ml_line_count) {
changed_lines_buf(buf, lnum, lnum+1, 0);
redraw_buf_later(buf, VALID);
}
return src_id;
}
/// Clear bufhl highlights from a given source group and range of lines.
///
/// @param buf The buffer to remove highlights from
/// @param src_id highlight source group to clear, or -1 to clear all groups.
/// @param line_start first line to clear
/// @param line_end last line to clear or MAXLNUM to clear to end of file.
void bufhl_clear_line_range(buf_T *buf,
int src_id,
linenr_T line_start,
linenr_T line_end) {
if (!buf->b_bufhl_info) {
return;
}
linenr_T line;
linenr_T first_changed = MAXLNUM, last_changed = -1;
// In the case line_start - line_end << bufhl_info->size
// it might be better to reverse this, i e loop over the lines
// to clear on.
bufhl_vec_T unused;
map_foreach(buf->b_bufhl_info, line, unused, {
(void)unused;
if (line_start <= line && line <= line_end) {
if (bufhl_clear_line(buf->b_bufhl_info, src_id, line)) {
if (line > last_changed) {
last_changed = line;
}
if (line < first_changed) {
first_changed = line;
}
}
}
})
if (last_changed != -1) {
changed_lines_buf(buf, first_changed, last_changed+1, 0);
redraw_buf_later(buf, VALID);
}
}
/// Clear bufhl highlights from a given source group and given line
///
/// @param bufhl_info The highlight info for the buffer
/// @param src_id Highlight source group to clear, or -1 to clear all groups.
/// @param lnum Linenr where the highlight should be cleared
static bool bufhl_clear_line(bufhl_info_T *bufhl_info, int src_id, int lnum) {
bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(bufhl_info,
lnum, false);
size_t oldsize = kv_size(*lineinfo);
if (src_id < 0) {
kv_size(*lineinfo) = 0;
} else {
size_t newind = 0;
for (size_t i = 0; i < kv_size(*lineinfo); i++) {
if (kv_A(*lineinfo, i).src_id != src_id) {
if (i != newind) {
kv_A(*lineinfo, newind) = kv_A(*lineinfo, i);
}
newind++;
}
}
kv_size(*lineinfo) = newind;
}
if (kv_size(*lineinfo) == 0) {
kv_destroy(*lineinfo);
map_del(linenr_T, bufhl_vec_T)(bufhl_info, lnum);
}
return kv_size(*lineinfo) != oldsize;
}
/// Remove all highlights and free the highlight data
void bufhl_clear_all(buf_T* buf) {
if (!buf->b_bufhl_info) {
return;
}
bufhl_clear_line_range(buf, -1, 1, MAXLNUM);
map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info);
buf->b_bufhl_info = NULL;
}
/// Adjust a placed highlight for inserted/deleted lines.
void bufhl_mark_adjust(buf_T* buf,
linenr_T line1,
linenr_T line2,
long amount,
long amount_after) {
if (!buf->b_bufhl_info) {
return;
}
bufhl_info_T *newmap = map_new(linenr_T, bufhl_vec_T)();
linenr_T line;
bufhl_vec_T lineinfo;
map_foreach(buf->b_bufhl_info, line, lineinfo, {
if (line >= line1 && line <= line2) {
if (amount == MAXLNUM) {
bufhl_clear_line(buf->b_bufhl_info, -1, line);
continue;
} else {
line += amount;
}
} else if (line > line2) {
line += amount_after;
}
map_put(linenr_T, bufhl_vec_T)(newmap, line, lineinfo);
});
map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info);
buf->b_bufhl_info = newmap;
}
/// Get highlights to display at a specific line
///
/// @param buf The buffer handle
/// @param lnum The line number
/// @param[out] info The highligts for the line
/// @return true if there was highlights to display
bool bufhl_start_line(buf_T *buf, linenr_T lnum, bufhl_lineinfo_T *info) {
if (!buf->b_bufhl_info) {
return false;
}
info->valid_to = -1;
info->entries = map_get(linenr_T, bufhl_vec_T)(buf->b_bufhl_info, lnum);
return kv_size(info->entries) > 0;
}
/// get highlighting at column col
///
/// It is is assumed this will be called with
/// non-decreasing column nrs, so that it is
/// possible to only recalculate highlights
/// at endpoints.
///
/// @param info The info returned by bufhl_start_line
/// @param col The column to get the attr for
/// @return The highilight attr to display at the column
int bufhl_get_attr(bufhl_lineinfo_T *info, colnr_T col) {
if (col <= info->valid_to) {
return info->current;
}
int attr = 0;
info->valid_to = MAXCOL;
for (size_t i = 0; i < kv_size(info->entries); i++) {
bufhl_hl_item_T entry = kv_A(info->entries, i);
if (entry.start <= col && col <= entry.stop) {
int entry_attr = syn_id2attr(entry.hl_id);
attr = hl_combine_attr(attr, entry_attr);
if (entry.stop < info->valid_to) {
info->valid_to = entry.stop;
}
} else if (col < entry.start && entry.start-1 < info->valid_to) {
info->valid_to = entry.start-1;
}
}
info->current = attr;
return attr;
}
/* /*
* Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
*/ */

View File

@ -28,6 +28,8 @@ typedef struct file_buffer buf_T; // Forward declaration
#include "nvim/profile.h" #include "nvim/profile.h"
// for String // for String
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
// for Map(K, V)
#include "nvim/map.h"
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma) #define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
@ -59,21 +61,21 @@ typedef struct file_buffer buf_T; // Forward declaration
#define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */ #define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */
#define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */ #define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */
/* flags for b_flags */ // flags for b_flags
#define BF_RECOVERED 0x01 /* buffer has been recovered */ #define BF_RECOVERED 0x01 // buffer has been recovered
#define BF_CHECK_RO 0x02 /* need to check readonly when loading file #define BF_CHECK_RO 0x02 // need to check readonly when loading file
into buffer (set by ":e", may be reset by // into buffer (set by ":e", may be reset by
":buf" */ // ":buf")
#define BF_NEVERLOADED 0x04 /* file has never been loaded into buffer, #define BF_NEVERLOADED 0x04 // file has never been loaded into buffer,
many variables still need to be set */ // many variables still need to be set
#define BF_NOTEDITED 0x08 /* Set when file name is changed after #define BF_NOTEDITED 0x08 // Set when file name is changed after
starting to edit, reset when file is // starting to edit, reset when file is
written out. */ // written out.
#define BF_NEW 0x10 /* file didn't exist when editing started */ #define BF_NEW 0x10 // file didn't exist when editing started
#define BF_NEW_W 0x20 /* Warned for BF_NEW and file created */ #define BF_NEW_W 0x20 // Warned for BF_NEW and file created
#define BF_READERR 0x40 /* got errors while reading the file */ #define BF_READERR 0x40 // got errors while reading the file
#define BF_DUMMY 0x80 /* dummy buffer, only used internally */ #define BF_DUMMY 0x80 // dummy buffer, only used internally
#define BF_PRESERVED 0x100 /* ":preserve" was used */ #define BF_PRESERVED 0x100 // ":preserve" was used
/* Mask to check for flags that prevent normal writing */ /* Mask to check for flags that prevent normal writing */
#define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR) #define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR)
@ -101,6 +103,11 @@ typedef int scid_T; /* script ID */
// for signlist_T // for signlist_T
#include "nvim/sign_defs.h" #include "nvim/sign_defs.h"
// for bufhl_*_T
#include "nvim/bufhl_defs.h"
typedef Map(linenr_T, bufhl_vec_T) bufhl_info_T;
// for FileID // for FileID
#include "nvim/os/fs_defs.h" #include "nvim/os/fs_defs.h"
@ -754,6 +761,8 @@ struct file_buffer {
dict_T *additional_data; // Additional data from shada file if any. dict_T *additional_data; // Additional data from shada file if any.
int b_mapped_ctrl_c; // modes where CTRL-C is mapped int b_mapped_ctrl_c; // modes where CTRL-C is mapped
bufhl_info_T *b_bufhl_info; // buffer stored highlights
}; };
/* /*

25
src/nvim/bufhl_defs.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef NVIM_BUFHL_DEFS_H
#define NVIM_BUFHL_DEFS_H
#include "nvim/pos.h"
#include "nvim/lib/kvec.h"
// bufhl: buffer specific highlighting
struct bufhl_hl_item
{
int src_id;
int hl_id; // highlight group
colnr_T start; // first column to highlight
colnr_T stop; // last column to highlight
};
typedef struct bufhl_hl_item bufhl_hl_item_T;
typedef kvec_t(struct bufhl_hl_item) bufhl_vec_T;
typedef struct {
bufhl_vec_T entries;
int current;
colnr_T valid_to;
} bufhl_lineinfo_T;
#endif // NVIM_BUFHL_DEFS_H

View File

@ -4320,7 +4320,7 @@ static void ex_unmap(exarg_T *eap)
*/ */
static void ex_mapclear(exarg_T *eap) static void ex_mapclear(exarg_T *eap)
{ {
map_clear(eap->cmd, eap->arg, eap->forceit, FALSE); map_clear_mode(eap->cmd, eap->arg, eap->forceit, false);
} }
/* /*
@ -4328,7 +4328,7 @@ static void ex_mapclear(exarg_T *eap)
*/ */
static void ex_abclear(exarg_T *eap) static void ex_abclear(exarg_T *eap)
{ {
map_clear(eap->cmd, eap->arg, TRUE, TRUE); map_clear_mode(eap->cmd, eap->arg, true, true);
} }
static void ex_autocmd(exarg_T *eap) static void ex_autocmd(exarg_T *eap)

View File

@ -2915,9 +2915,9 @@ do_map (
did_it = TRUE; did_it = TRUE;
} }
} }
if (mp->m_mode == 0) { /* entry can be deleted */ if (mp->m_mode == 0) { // entry can be deleted
map_free(mpp); mapblock_free(mpp);
continue; /* continue with *mpp */ continue; // continue with *mpp
} }
/* /*
@ -3012,7 +3012,7 @@ theend:
* Delete one entry from the abbrlist or maphash[]. * Delete one entry from the abbrlist or maphash[].
* "mpp" is a pointer to the m_next field of the PREVIOUS entry! * "mpp" is a pointer to the m_next field of the PREVIOUS entry!
*/ */
static void map_free(mapblock_T **mpp) static void mapblock_free(mapblock_T **mpp)
{ {
mapblock_T *mp; mapblock_T *mp;
@ -3080,7 +3080,7 @@ int get_map_mode(char_u **cmdp, int forceit)
* Clear all mappings or abbreviations. * Clear all mappings or abbreviations.
* 'abbr' should be FALSE for mappings, TRUE for abbreviations. * 'abbr' should be FALSE for mappings, TRUE for abbreviations.
*/ */
void map_clear(char_u *cmdp, char_u *arg, int forceit, int abbr) void map_clear_mode(char_u *cmdp, char_u *arg, int forceit, int abbr)
{ {
int mode; int mode;
int local; int local;
@ -3132,8 +3132,8 @@ map_clear_int (
mp = *mpp; mp = *mpp;
if (mp->m_mode & mode) { if (mp->m_mode & mode) {
mp->m_mode &= ~mode; mp->m_mode &= ~mode;
if (mp->m_mode == 0) { /* entry can be deleted */ if (mp->m_mode == 0) { // entry can be deleted
map_free(mpp); mapblock_free(mpp);
continue; continue;
} }
/* /*

View File

@ -184,7 +184,7 @@ typedef khint_t khiter_t;
#define kfree(P) xfree(P) #define kfree(P) xfree(P)
#endif #endif
static const double __ac_HASH_UPPER = 0.77; #define __ac_HASH_UPPER 0.77
#define __KHASH_TYPE(name, khkey_t, khval_t) \ #define __KHASH_TYPE(name, khkey_t, khval_t) \
typedef struct { \ typedef struct { \

View File

@ -77,10 +77,10 @@ int main() {
(v).items[(v).size++] = (x); \ (v).items[(v).size++] = (x); \
} while (0) } while (0)
#define kv_pushp(type, v) (((v).size == (v).capacity)? \ #define kv_pushp(type, v) ((((v).size == (v).capacity)? \
((v).capacity = ((v).capacity? (v).capacity<<1 : 8), \ ((v).capacity = ((v).capacity? (v).capacity<<1 : 8), \
(v).items = (type*)xrealloc((v).items, sizeof(type) * (v).capacity), 0) \ (v).items = (type*)xrealloc((v).items, sizeof(type) * (v).capacity), 0) \
: 0), ((v).items + ((v).size++)) : 0), ((v).items + ((v).size++)))
#define kv_a(type, v, i) (((v).capacity <= (size_t)(i)? \ #define kv_a(type, v, i) (((v).capacity <= (size_t)(i)? \
((v).capacity = (v).size = (i) + 1, kv_roundup32((v).capacity), \ ((v).capacity = (v).size = (i) + 1, kv_roundup32((v).capacity), \

View File

@ -18,6 +18,9 @@
#define uint32_t_eq kh_int_hash_equal #define uint32_t_eq kh_int_hash_equal
#define int_hash kh_int_hash_func #define int_hash kh_int_hash_func
#define int_eq kh_int_hash_equal #define int_eq kh_int_hash_equal
#define linenr_T_hash kh_int_hash_func
#define linenr_T_eq kh_int_hash_equal
#if defined(ARCH_64) #if defined(ARCH_64)
#define ptr_t_hash(key) uint64_t_hash((uint64_t)key) #define ptr_t_hash(key) uint64_t_hash((uint64_t)key)
@ -78,6 +81,25 @@
return rv; \ return rv; \
} \ } \
\ \
U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put) \
{ \
int ret; \
khiter_t k; \
if (put) { \
k = kh_put(T##_##U##_map, map->table, key, &ret); \
if (ret) { \
kh_val(map->table, k) = INITIALIZER(T, U); \
} \
} else { \
k = kh_get(T##_##U##_map, map->table, key); \
if (k == kh_end(map->table)) { \
return NULL; \
} \
} \
\
return &kh_val(map->table, k); \
} \
\
U map_##T##_##U##_del(Map(T, U) *map, T key) \ U map_##T##_##U##_del(Map(T, U) *map, T key) \
{ \ { \
U rv = INITIALIZER(T, U); \ U rv = INITIALIZER(T, U); \
@ -118,3 +140,5 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false} #define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false}
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(linenr_T, bufhl_vec_T, KVEC_INITIALIZER)

View File

@ -6,6 +6,7 @@
#include "nvim/map_defs.h" #include "nvim/map_defs.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/msgpack_rpc/defs.h" #include "nvim/msgpack_rpc/defs.h"
#include "nvim/bufhl_defs.h"
#define MAP_DECLS(T, U) \ #define MAP_DECLS(T, U) \
KHASH_DECLARE(T##_##U##_map, T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \
@ -19,6 +20,7 @@
U map_##T##_##U##_get(Map(T, U) *map, T key); \ U map_##T##_##U##_get(Map(T, U) *map, T key); \
bool map_##T##_##U##_has(Map(T, U) *map, T key); \ bool map_##T##_##U##_has(Map(T, U) *map, T key); \
U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \
U map_##T##_##U##_del(Map(T, U) *map, T key); \ U map_##T##_##U##_del(Map(T, U) *map, T key); \
void map_##T##_##U##_clear(Map(T, U) *map); void map_##T##_##U##_clear(Map(T, U) *map);
@ -28,12 +30,14 @@ MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(linenr_T, bufhl_vec_T)
#define map_new(T, U) map_##T##_##U##_new #define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free #define map_free(T, U) map_##T##_##U##_free
#define map_get(T, U) map_##T##_##U##_get #define map_get(T, U) map_##T##_##U##_get
#define map_has(T, U) map_##T##_##U##_has #define map_has(T, U) map_##T##_##U##_has
#define map_put(T, U) map_##T##_##U##_put #define map_put(T, U) map_##T##_##U##_put
#define map_ref(T, U) map_##T##_##U##_ref
#define map_del(T, U) map_##T##_##U##_del #define map_del(T, U) map_##T##_##U##_del
#define map_clear(T, U) map_##T##_##U##_clear #define map_clear(T, U) map_##T##_##U##_clear

View File

@ -922,6 +922,7 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)
} }
sign_mark_adjust(line1, line2, amount, amount_after); sign_mark_adjust(line1, line2, amount, amount_after);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after);
} }
/* previous context mark */ /* previous context mark */

View File

@ -1985,13 +1985,13 @@ changed_lines (
changed_common(lnum, col, lnume, xtra); changed_common(lnum, col, lnume, xtra);
} }
static void /// Mark line range in buffer as changed.
changed_lines_buf ( ///
buf_T *buf, /// @param buf the buffer where lines were changed
linenr_T lnum, /* first line with change */ /// @param lnum first line with change
linenr_T lnume, /* line below last changed line */ /// @param lnume line below last changed line
long xtra /* number of extra lines (negative when deleting) */ /// @param xtra number of extra lines (negative when deleting)
) void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra)
{ {
if (buf->b_mod_set) { if (buf->b_mod_set) {
/* find the maximum area that must be redisplayed */ /* find the maximum area that must be redisplayed */

View File

@ -1,8 +1,6 @@
#ifndef NVIM_MSGPACK_RPC_DEFS_H #ifndef NVIM_MSGPACK_RPC_DEFS_H
#define NVIM_MSGPACK_RPC_DEFS_H #define NVIM_MSGPACK_RPC_DEFS_H
#include <msgpack.h>
/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
/// functions of this type. /// functions of this type.
@ -24,22 +22,6 @@ void msgpack_rpc_add_method_handler(String method,
void msgpack_rpc_init_function_metadata(Dictionary *metadata); void msgpack_rpc_init_function_metadata(Dictionary *metadata);
/// Dispatches to the actual API function after basic payload validation by
/// `msgpack_rpc_call`. It is responsible for validating/converting arguments
/// to C types, and converting the return value back to msgpack types.
/// The implementation is generated at compile time with metadata extracted
/// from the api/*.h headers,
///
/// @param channel_id The channel id
/// @param method_id The method id
/// @param req The parsed request object
/// @param error Pointer to error structure
/// @return Some object
Object msgpack_rpc_dispatch(uint64_t channel_id,
msgpack_object *req,
Error *error)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3);
MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name,
size_t name_len) size_t name_len)
FUNC_ATTR_NONNULL_ARG(1); FUNC_ATTR_NONNULL_ARG(1);

View File

@ -2184,6 +2184,10 @@ win_line (
int prev_c1 = 0; /* first composing char for prev_c */ int prev_c1 = 0; /* first composing char for prev_c */
int did_line_attr = 0; int did_line_attr = 0;
bool has_bufhl = false; // this buffer has highlight matches
int bufhl_attr = 0; // attributes desired by bufhl
bufhl_lineinfo_T bufhl_info; // bufhl data for this line
/* draw_state: items that are drawn in sequence: */ /* draw_state: items that are drawn in sequence: */
#define WL_START 0 /* nothing done yet */ #define WL_START 0 /* nothing done yet */
# define WL_CMDLINE WL_START + 1 /* cmdline window column */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */
@ -2244,6 +2248,11 @@ win_line (
} }
} }
if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) {
has_bufhl = true;
extra_check = true;
}
/* Check for columns to display for 'colorcolumn'. */ /* Check for columns to display for 'colorcolumn'. */
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
if (color_cols != NULL) if (color_cols != NULL)
@ -3335,6 +3344,17 @@ win_line (
char_attr = hl_combine_attr(spell_attr, char_attr); char_attr = hl_combine_attr(spell_attr, char_attr);
} }
if (has_bufhl && v > 0) {
bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v);
if (bufhl_attr != 0) {
if (!attr_pri) {
char_attr = hl_combine_attr(char_attr, bufhl_attr);
} else {
char_attr = hl_combine_attr(bufhl_attr, char_attr);
}
}
}
if (wp->w_buffer->terminal) { if (wp->w_buffer->terminal) {
char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); char_attr = hl_combine_attr(char_attr, term_attrs[vcol]);
} }

View File

@ -0,0 +1,261 @@
local helpers = require('test.functional.helpers')
local Screen = require('test.functional.ui.screen')
local clear, feed, nvim, insert = helpers.clear, helpers.feed, helpers.nvim, helpers.insert
local execute, request, eq, neq = helpers.execute, helpers.request, helpers.eq, helpers.neq
describe('Buffer highlighting', function()
local screen
local curbuf
local hl_colors = {
NonText = Screen.colors.Blue,
Question = Screen.colors.SeaGreen,
String = Screen.colors.Fuchsia,
Statement = Screen.colors.Brown,
Special = Screen.colors.SlateBlue,
Identifier = Screen.colors.DarkCyan
}
before_each(function()
clear()
execute("syntax on")
screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ignore( {{bold=true, foreground=hl_colors.NonText}} )
screen:set_default_attr_ids({
[1] = {foreground = hl_colors.String},
[2] = {foreground = hl_colors.Statement, bold = true},
[3] = {foreground = hl_colors.Special},
[4] = {bold = true, foreground = hl_colors.Special},
[5] = {foreground = hl_colors.Identifier},
[6] = {bold = true},
[7] = {underline = true, bold = true, foreground = hl_colors.Special},
[8] = {foreground = hl_colors.Special, underline = true}
})
curbuf = request('vim_get_current_buffer')
end)
after_each(function()
screen:detach()
end)
local function add_hl(...)
return request('buffer_add_highlight', curbuf, ...)
end
local function clear_hl(...)
return request('buffer_clear_highlight', curbuf, ...)
end
it('works', function()
insert([[
these are some lines
with colorful text]])
feed('+')
screen:expect([[
these are some lines |
with colorful tex^t |
~ |
~ |
~ |
~ |
~ |
|
]])
add_hl(-1, "String", 0 , 10, 14)
add_hl(-1, "Statement", 1 , 5, -1)
screen:expect([[
these are {1:some} lines |
with {2:colorful tex^t} |
~ |
~ |
~ |
~ |
~ |
|
]])
feed("ggo<esc>")
screen:expect([[
these are {1:some} lines |
^ |
with {2:colorful text} |
~ |
~ |
~ |
~ |
|
]])
clear_hl(-1, 0 , -1)
screen:expect([[
these are some lines |
^ |
with colorful text |
~ |
~ |
~ |
~ |
|
]])
end)
describe('support adding multiple sources', function()
local id1, id2
before_each(function()
insert([[
a longer example
in order to demonstrate
combining highlights
from different sources]])
execute("hi ImportantWord gui=bold cterm=bold")
id1 = add_hl(0, "ImportantWord", 0, 2, 8)
add_hl(id1, "ImportantWord", 1, 12, -1)
add_hl(id1, "ImportantWord", 2, 0, 9)
add_hl(id1, "ImportantWord", 3, 5, 14)
id2 = add_hl(0, "Special", 0, 2, 8)
add_hl(id2, "Identifier", 1, 3, 8)
add_hl(id2, "Special", 1, 14, 20)
add_hl(id2, "Underlined", 2, 6, 12)
add_hl(id2, "Underlined", 3, 0, 9)
neq(id1, id2)
screen:expect([[
a {4:longer} example |
in {5:order} to {6:de}{4:monstr}{6:ate} |
{6:combin}{7:ing}{8: hi}ghlights |
{8:from }{7:diff}{6:erent} source^s |
~ |
~ |
~ |
:hi ImportantWord gui=bold cterm=bold |
]])
end)
it('and clearing the first added', function()
clear_hl(id1, 0, -1)
screen:expect([[
a {3:longer} example |
in {5:order} to de{3:monstr}ate |
combin{8:ing hi}ghlights |
{8:from diff}erent source^s |
~ |
~ |
~ |
:hi ImportantWord gui=bold cterm=bold |
]])
end)
it('and clearing the second added', function()
clear_hl(id2, 0, -1)
screen:expect([[
a {6:longer} example |
in order to {6:demonstrate} |
{6:combining} highlights |
from {6:different} source^s |
~ |
~ |
~ |
:hi ImportantWord gui=bold cterm=bold |
]])
end)
it('and clearing line ranges', function()
clear_hl(-1, 0, 1)
clear_hl(id1, 1, 2)
clear_hl(id2, 2, -1)
screen:expect([[
a longer example |
in {5:order} to de{3:monstr}ate |
{6:combining} highlights |
from {6:different} source^s |
~ |
~ |
~ |
:hi ImportantWord gui=bold cterm=bold |
]])
end)
it('and renumbering lines', function()
feed('3Gddggo<esc>')
screen:expect([[
a {4:longer} example |
^ |
in {5:order} to {6:de}{4:monstr}{6:ate} |
{8:from }{7:diff}{6:erent} sources |
~ |
~ |
~ |
|
]])
execute(':3move 4')
screen:expect([[
a {4:longer} example |
|
{8:from }{7:diff}{6:erent} sources |
^in {5:order} to {6:de}{4:monstr}{6:ate} |
~ |
~ |
~ |
::3move 4 |
]])
end)
end)
it('prioritizes latest added highlight', function()
insert([[
three overlapping colors]])
id1 = add_hl(0, "Identifier", 0, 6, 17)
id2 = add_hl(0, "Special", 0, 0, 9)
id3 = add_hl(0, "String", 0, 14, 23)
screen:expect([[
{3:three ove}{5:rlapp}{1:ing color}^s |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
clear_hl(id2, 0, 1)
screen:expect([[
three {5:overlapp}{1:ing color}^s |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
end)
it('works with multibyte text', function()
insert([[
Ta båten över sjön!]])
add_hl(-1, "Identifier", 0, 3, 9)
add_hl(-1, "String", 0, 16, 21)
screen:expect([[
Ta {5:båten} över {1:sjön}^! |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
end)
end)