bufhl: new mechanism for plugins to add highlights to a buffer

This commit is contained in:
Björn Linse 2015-01-18 15:05:04 +01:00
parent 18605d6785
commit 44b2cef83a
12 changed files with 421 additions and 25 deletions

View File

@ -19,6 +19,7 @@
#include "nvim/mark.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
#include "nvim/window.h"
#include "nvim/undo.h"
@ -514,6 +515,99 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err)
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.
// 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) {
clear_wininfo(buf); /* including window-local options */
free_buf_options(buf, TRUE);
clear_wininfo(buf); // including window-local options
free_buf_options(buf, true);
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);
uc_clear(&buf->b_ucmds); /* clear local user commands */
buf_delete_signs(buf); /* delete any signs */
map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */
map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf); // delete any signs
bufhl_clear_all(buf); // delete any highligts
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);
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.
*/

View File

@ -28,6 +28,8 @@ typedef struct file_buffer buf_T; // Forward declaration
#include "nvim/profile.h"
// for String
#include "nvim/api/private/defs.h"
// for Map(K, V)
#include "nvim/map.h"
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
@ -101,6 +103,11 @@ typedef int scid_T; /* script ID */
// for signlist_T
#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
#include "nvim/os/fs_defs.h"
@ -754,6 +761,8 @@ struct file_buffer {
dict_T *additional_data; // Additional data from shada file if any.
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)
{
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)
{
map_clear(eap->cmd, eap->arg, TRUE, TRUE);
map_clear_mode(eap->cmd, eap->arg, true, true);
}
static void ex_autocmd(exarg_T *eap)

View File

@ -2915,9 +2915,9 @@ do_map (
did_it = TRUE;
}
}
if (mp->m_mode == 0) { /* entry can be deleted */
map_free(mpp);
continue; /* continue with *mpp */
if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
continue; // continue with *mpp
}
/*
@ -3012,7 +3012,7 @@ theend:
* Delete one entry from the abbrlist or maphash[].
* "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;
@ -3080,7 +3080,7 @@ int get_map_mode(char_u **cmdp, int forceit)
* Clear all mappings or 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 local;
@ -3132,8 +3132,8 @@ map_clear_int (
mp = *mpp;
if (mp->m_mode & mode) {
mp->m_mode &= ~mode;
if (mp->m_mode == 0) { /* entry can be deleted */
map_free(mpp);
if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
continue;
}
/*

View File

@ -77,10 +77,10 @@ int main() {
(v).items[(v).size++] = (x); \
} 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).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)? \
((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 int_hash kh_int_hash_func
#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)
#define ptr_t_hash(key) uint64_t_hash((uint64_t)key)
@ -78,6 +81,25 @@
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 rv = INITIALIZER(T, U); \
@ -118,3 +140,5 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false}
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/api/private/defs.h"
#include "nvim/msgpack_rpc/defs.h"
#include "nvim/bufhl_defs.h"
#define MAP_DECLS(T, U) \
KHASH_DECLARE(T##_##U##_map, T, U) \
@ -19,6 +20,7 @@
U map_##T##_##U##_get(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##_ref(Map(T, U) *map, T key, bool put); \
U map_##T##_##U##_del(Map(T, U) *map, T key); \
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(uint64_t, ptr_t)
MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(linenr_T, bufhl_vec_T)
#define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free
#define map_get(T, U) map_##T##_##U##_get
#define map_has(T, U) map_##T##_##U##_has
#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_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);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after);
}
/* previous context mark */

View File

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

View File

@ -2184,6 +2184,10 @@ win_line (
int prev_c1 = 0; /* first composing char for prev_c */
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: */
#define WL_START 0 /* nothing done yet */
# 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'. */
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
if (color_cols != NULL)
@ -3335,6 +3344,17 @@ win_line (
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) {
char_attr = hl_combine_attr(char_attr, term_attrs[vcol]);
}