mirror of
https://github.com/neovim/neovim.git
synced 2025-11-05 18:24:22 +00:00
Merge pull request #12851 from bfredl/luahl
luahl: still WIP but better
This commit is contained in:
@@ -577,7 +577,7 @@ updates.
|
|||||||
Parser files *treesitter-parsers*
|
Parser files *treesitter-parsers*
|
||||||
|
|
||||||
Parsers are the heart of tree-sitter. They are libraries that tree-sitter will
|
Parsers are the heart of tree-sitter. They are libraries that tree-sitter will
|
||||||
search for in the `parsers` runtime directory.
|
search for in the `parser` runtime directory.
|
||||||
|
|
||||||
For a parser to be available for a given language, there must be a file named
|
For a parser to be available for a given language, there must be a file named
|
||||||
`{lang}.so` within the parser directory.
|
`{lang}.so` within the parser directory.
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ local a = vim.api
|
|||||||
-- support reload for quick experimentation
|
-- support reload for quick experimentation
|
||||||
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
|
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
|
||||||
TSHighlighter.__index = TSHighlighter
|
TSHighlighter.__index = TSHighlighter
|
||||||
local ts_hs_ns = a.nvim_create_namespace("treesitter_hl")
|
|
||||||
|
TSHighlighter.active = TSHighlighter.active or {}
|
||||||
|
|
||||||
-- These are conventions defined by tree-sitter, though it
|
-- These are conventions defined by tree-sitter, though it
|
||||||
-- needs to be user extensible also.
|
-- needs to be user extensible also.
|
||||||
@@ -54,13 +55,16 @@ TSHighlighter.hl_map = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TSHighlighter.new(query, bufnr, ft)
|
function TSHighlighter.new(query, bufnr, ft)
|
||||||
|
if bufnr == nil or bufnr == 0 then
|
||||||
|
bufnr = a.nvim_get_current_buf()
|
||||||
|
end
|
||||||
|
|
||||||
local self = setmetatable({}, TSHighlighter)
|
local self = setmetatable({}, TSHighlighter)
|
||||||
self.parser = vim.treesitter.get_parser(
|
self.parser = vim.treesitter.get_parser(
|
||||||
bufnr,
|
bufnr,
|
||||||
ft,
|
ft,
|
||||||
{
|
{
|
||||||
on_changedtree = function(...) self:on_changedtree(...) end,
|
on_changedtree = function(...) self:on_changedtree(...) end,
|
||||||
on_bytes = function() self.parser:parse() end
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,8 +73,12 @@ function TSHighlighter.new(query, bufnr, ft)
|
|||||||
self.edit_count = 0
|
self.edit_count = 0
|
||||||
self.redraw_count = 0
|
self.redraw_count = 0
|
||||||
self.line_count = {}
|
self.line_count = {}
|
||||||
|
self.root = self.parser:parse():root()
|
||||||
a.nvim_buf_set_option(self.buf, "syntax", "")
|
a.nvim_buf_set_option(self.buf, "syntax", "")
|
||||||
|
|
||||||
|
-- TODO(bfredl): can has multiple highlighters per buffer????
|
||||||
|
TSHighlighter.active[bufnr] = self
|
||||||
|
|
||||||
-- Tricky: if syntax hasn't been enabled, we need to reload color scheme
|
-- Tricky: if syntax hasn't been enabled, we need to reload color scheme
|
||||||
-- but use synload.vim rather than syntax.vim to not enable
|
-- but use synload.vim rather than syntax.vim to not enable
|
||||||
-- syntax FileType autocmds. Later on we should integrate with the
|
-- syntax FileType autocmds. Later on we should integrate with the
|
||||||
@@ -100,6 +108,12 @@ function TSHighlighter:get_hl_from_capture(capture)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TSHighlighter:on_changedtree(changes)
|
||||||
|
for _, ch in ipairs(changes or {}) do
|
||||||
|
a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function TSHighlighter:set_query(query)
|
function TSHighlighter:set_query(query)
|
||||||
if type(query) == "string" then
|
if type(query) == "string" then
|
||||||
query = vim.treesitter.parse_query(self.parser.lang, query)
|
query = vim.treesitter.parse_query(self.parser.lang, query)
|
||||||
@@ -123,28 +137,60 @@ function TSHighlighter:set_query(query)
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
self:on_changedtree({{self.parser:parse():root():range()}})
|
a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf))
|
||||||
end
|
end
|
||||||
|
|
||||||
function TSHighlighter:on_changedtree(changes)
|
function TSHighlighter._on_line(_, _win, buf, line)
|
||||||
-- Get a fresh root
|
-- on_line is only called when this is non-nil
|
||||||
local root = self.parser:parse():root()
|
local self = TSHighlighter.active[buf]
|
||||||
|
if self.root == nil then
|
||||||
|
return -- parser bought the farm already
|
||||||
|
end
|
||||||
|
|
||||||
for _, ch in ipairs(changes or {}) do
|
if self.iter == nil then
|
||||||
a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3]+1)
|
self.iter = self.query:iter_captures(self.root,buf,line,self.botline)
|
||||||
|
end
|
||||||
for capture, node in self.query:iter_captures(root, self.buf, ch[1], ch[3] + 1) do
|
while line >= self.nextrow do
|
||||||
|
local capture, node = self.iter()
|
||||||
|
if capture == nil then
|
||||||
|
break
|
||||||
|
end
|
||||||
local start_row, start_col, end_row, end_col = node:range()
|
local start_row, start_col, end_row, end_col = node:range()
|
||||||
local hl = self.hl_cache[capture]
|
local hl = self.hl_cache[capture]
|
||||||
if hl then
|
if hl and end_row >= line then
|
||||||
a.nvim_buf_set_extmark(self.buf, ts_hs_ns, start_row, start_col, {
|
a.nvim__put_attr(start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl })
|
||||||
end_col = end_col,
|
|
||||||
end_line = end_row,
|
|
||||||
hl_group = hl
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
if start_row > line then
|
||||||
|
self.nextrow = start_row
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function TSHighlighter._on_start(_, buf, _tick)
|
||||||
|
local self = TSHighlighter.active[buf]
|
||||||
|
if self then
|
||||||
|
local tree = self.parser:parse()
|
||||||
|
self.root = (tree and tree:root()) or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TSHighlighter._on_win(_, _win, buf, _topline, botline)
|
||||||
|
local self = TSHighlighter.active[buf]
|
||||||
|
if not self then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
self.iter = nil
|
||||||
|
self.nextrow = 0
|
||||||
|
self.botline = botline
|
||||||
|
self.redraw_count = self.redraw_count + 1
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
a.nvim__set_luahl {
|
||||||
|
on_start = TSHighlighter._on_start;
|
||||||
|
on_win = TSHighlighter._on_win;
|
||||||
|
on_line = TSHighlighter._on_line;
|
||||||
|
}
|
||||||
|
|
||||||
return TSHighlighter
|
return TSHighlighter
|
||||||
|
|||||||
@@ -244,78 +244,6 @@ Boolean nvim_buf_detach(uint64_t channel_id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void buf_clear_luahl(buf_T *buf, bool force)
|
|
||||||
{
|
|
||||||
if (buf->b_luahl || force) {
|
|
||||||
api_free_luaref(buf->b_luahl_start);
|
|
||||||
api_free_luaref(buf->b_luahl_window);
|
|
||||||
api_free_luaref(buf->b_luahl_line);
|
|
||||||
api_free_luaref(buf->b_luahl_end);
|
|
||||||
}
|
|
||||||
buf->b_luahl_start = LUA_NOREF;
|
|
||||||
buf->b_luahl_window = LUA_NOREF;
|
|
||||||
buf->b_luahl_line = LUA_NOREF;
|
|
||||||
buf->b_luahl_end = LUA_NOREF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unstabilized interface for defining syntax hl in lua.
|
|
||||||
///
|
|
||||||
/// This is not yet safe for general use, lua callbacks will need to
|
|
||||||
/// be restricted, like textlock and probably other stuff.
|
|
||||||
///
|
|
||||||
/// The API on_line/nvim__put_attr is quite raw and not intended to be the
|
|
||||||
/// final shape. Ideally this should operate on chunks larger than a single
|
|
||||||
/// line to reduce interpreter overhead, and generate annotation objects
|
|
||||||
/// (bufhl/virttext) on the fly but using the same representation.
|
|
||||||
void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer,
|
|
||||||
DictionaryOf(LuaRef) opts, Error *err)
|
|
||||||
FUNC_API_LUA_ONLY
|
|
||||||
{
|
|
||||||
buf_T *buf = find_buffer_by_handle(buffer, err);
|
|
||||||
|
|
||||||
if (!buf) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
redraw_buf_later(buf, NOT_VALID);
|
|
||||||
buf_clear_luahl(buf, false);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < opts.size; i++) {
|
|
||||||
String k = opts.items[i].key;
|
|
||||||
Object *v = &opts.items[i].value;
|
|
||||||
if (strequal("on_start", k.data)) {
|
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
buf->b_luahl_start = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
|
||||||
} else if (strequal("on_window", k.data)) {
|
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
buf->b_luahl_window = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
|
||||||
} else if (strequal("on_line", k.data)) {
|
|
||||||
if (v->type != kObjectTypeLuaRef) {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
buf->b_luahl_line = v->data.luaref;
|
|
||||||
v->data.luaref = LUA_NOREF;
|
|
||||||
} else {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf->b_luahl = true;
|
|
||||||
return;
|
|
||||||
error:
|
|
||||||
buf_clear_luahl(buf, true);
|
|
||||||
buf->b_luahl = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
|
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
|
||||||
Error *err)
|
Error *err)
|
||||||
FUNC_API_LUA_ONLY
|
FUNC_API_LUA_ONLY
|
||||||
@@ -1664,43 +1592,6 @@ void nvim_buf_clear_highlight(Buffer buffer,
|
|||||||
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
|
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static VirtText parse_virt_text(Array chunks, Error *err)
|
|
||||||
{
|
|
||||||
VirtText virt_text = KV_INITIAL_VALUE;
|
|
||||||
for (size_t i = 0; i < chunks.size; i++) {
|
|
||||||
if (chunks.items[i].type != kObjectTypeArray) {
|
|
||||||
api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
|
|
||||||
goto free_exit;
|
|
||||||
}
|
|
||||||
Array chunk = chunks.items[i].data.array;
|
|
||||||
if (chunk.size == 0 || chunk.size > 2
|
|
||||||
|| chunk.items[0].type != kObjectTypeString
|
|
||||||
|| (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
|
|
||||||
api_set_error(err, kErrorTypeValidation,
|
|
||||||
"Chunk is not an array with one or two strings");
|
|
||||||
goto free_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
String str = chunk.items[0].data.string;
|
|
||||||
char *text = transstr(str.size > 0 ? str.data : ""); // allocates
|
|
||||||
|
|
||||||
int hl_id = 0;
|
|
||||||
if (chunk.size == 2) {
|
|
||||||
String hl = chunk.items[1].data.string;
|
|
||||||
if (hl.size > 0) {
|
|
||||||
hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return virt_text;
|
|
||||||
|
|
||||||
free_exit:
|
|
||||||
clear_virttext(&virt_text);
|
|
||||||
return virt_text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Set the virtual text (annotation) for a buffer line.
|
/// Set the virtual text (annotation) for a buffer line.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "nvim/lua/executor.h"
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/assert.h"
|
#include "nvim/assert.h"
|
||||||
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/syntax.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
@@ -1579,3 +1581,52 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VirtText parse_virt_text(Array chunks, Error *err)
|
||||||
|
{
|
||||||
|
VirtText virt_text = KV_INITIAL_VALUE;
|
||||||
|
for (size_t i = 0; i < chunks.size; i++) {
|
||||||
|
if (chunks.items[i].type != kObjectTypeArray) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
|
||||||
|
goto free_exit;
|
||||||
|
}
|
||||||
|
Array chunk = chunks.items[i].data.array;
|
||||||
|
if (chunk.size == 0 || chunk.size > 2
|
||||||
|
|| chunk.items[0].type != kObjectTypeString
|
||||||
|
|| (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"Chunk is not an array with one or two strings");
|
||||||
|
goto free_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
String str = chunk.items[0].data.string;
|
||||||
|
char *text = transstr(str.size > 0 ? str.data : ""); // allocates
|
||||||
|
|
||||||
|
int hl_id = 0;
|
||||||
|
if (chunk.size == 2) {
|
||||||
|
String hl = chunk.items[1].data.string;
|
||||||
|
if (hl.size > 0) {
|
||||||
|
hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return virt_text;
|
||||||
|
|
||||||
|
free_exit:
|
||||||
|
clear_virttext(&virt_text);
|
||||||
|
return virt_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool api_is_truthy(Object obj, const char *what, Error *err)
|
||||||
|
{
|
||||||
|
if (obj.type == kObjectTypeBoolean) {
|
||||||
|
return obj.data.boolean;
|
||||||
|
} else if (obj.type == kObjectTypeInteger) {
|
||||||
|
return obj.data.integer; // C semantics: non-zery int is true
|
||||||
|
} else {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2610,22 +2610,91 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
|
|||||||
/// interface should probably be derived from a reformed
|
/// interface should probably be derived from a reformed
|
||||||
/// bufhl/virttext interface with full support for multi-line
|
/// bufhl/virttext interface with full support for multi-line
|
||||||
/// ranges etc
|
/// ranges etc
|
||||||
void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
|
void nvim__put_attr(Integer line, Integer col, Dictionary opts, Error *err)
|
||||||
Integer end_row, Integer end_col)
|
|
||||||
FUNC_API_LUA_ONLY
|
FUNC_API_LUA_ONLY
|
||||||
{
|
{
|
||||||
if (!lua_attr_active) {
|
if (!lua_attr_active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (id == 0 || syn_get_final_id((int)id) == 0) {
|
int line2 = -1, hl_id = 0;
|
||||||
|
colnr_T col2 = 0;
|
||||||
|
VirtText virt_text = KV_INITIAL_VALUE;
|
||||||
|
for (size_t i = 0; i < opts.size; i++) {
|
||||||
|
String k = opts.items[i].key;
|
||||||
|
Object *v = &opts.items[i].value;
|
||||||
|
if (strequal("end_line", k.data)) {
|
||||||
|
if (v->type != kObjectTypeInteger) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"end_line is not an integer");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (v->data.integer < 0) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"end_line value outside range");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
line2 = (int)v->data.integer;
|
||||||
|
} else if (strequal("end_col", k.data)) {
|
||||||
|
if (v->type != kObjectTypeInteger) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"end_col is not an integer");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (v->data.integer < 0 || v->data.integer > MAXCOL) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"end_col value outside range");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
col2 = (colnr_T)v->data.integer;
|
||||||
|
} else if (strequal("hl_group", k.data)) {
|
||||||
|
String hl_group;
|
||||||
|
switch (v->type) {
|
||||||
|
case kObjectTypeString:
|
||||||
|
hl_group = v->data.string;
|
||||||
|
hl_id = syn_check_group(
|
||||||
|
(char_u *)(hl_group.data),
|
||||||
|
(int)hl_group.size);
|
||||||
|
break;
|
||||||
|
case kObjectTypeInteger:
|
||||||
|
hl_id = (int)v->data.integer;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"hl_group is not valid.");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else if (strequal("virt_text", k.data)) {
|
||||||
|
if (v->type != kObjectTypeArray) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"virt_text is not an Array");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
virt_text = parse_virt_text(v->data.array, err);
|
||||||
|
if (ERROR_SET(err)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (col2 && line2 < 0) {
|
||||||
|
line2 = (int)line;
|
||||||
|
}
|
||||||
|
|
||||||
|
int attr = hl_id ? syn_id2attr((int)hl_id) : 0;
|
||||||
|
if (attr == 0 && !kv_size(virt_text)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int attr = syn_id2attr((int)id);
|
|
||||||
if (attr == 0) {
|
VirtText *v = xmalloc(sizeof(*v));
|
||||||
|
*v = virt_text; // LeakSanitizer be sad
|
||||||
|
decorations_add_luahl_attr(attr, (int)line, (colnr_T)col,
|
||||||
|
(int)line2, (colnr_T)col2, v);
|
||||||
|
error:
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
|
|
||||||
(int)end_row, (colnr_T)end_col);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nvim__screenshot(String path)
|
void nvim__screenshot(String path)
|
||||||
@@ -2633,3 +2702,68 @@ void nvim__screenshot(String path)
|
|||||||
{
|
{
|
||||||
ui_call_screenshot(path);
|
ui_call_screenshot(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clear_luahl(bool force)
|
||||||
|
{
|
||||||
|
if (luahl_active || force) {
|
||||||
|
api_free_luaref(luahl_start);
|
||||||
|
api_free_luaref(luahl_win);
|
||||||
|
api_free_luaref(luahl_line);
|
||||||
|
api_free_luaref(luahl_end);
|
||||||
|
}
|
||||||
|
luahl_start = LUA_NOREF;
|
||||||
|
luahl_win = LUA_NOREF;
|
||||||
|
luahl_line = LUA_NOREF;
|
||||||
|
luahl_end = LUA_NOREF;
|
||||||
|
luahl_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unstabilized interface for defining syntax hl in lua.
|
||||||
|
///
|
||||||
|
/// This is not yet safe for general use, lua callbacks will need to
|
||||||
|
/// be restricted, like textlock and probably other stuff.
|
||||||
|
///
|
||||||
|
/// The API on_line/nvim__put_attr is quite raw and not intended to be the
|
||||||
|
/// final shape. Ideally this should operate on chunks larger than a single
|
||||||
|
/// line to reduce interpreter overhead, and generate annotation objects
|
||||||
|
/// (bufhl/virttext) on the fly but using the same representation.
|
||||||
|
void nvim__set_luahl(DictionaryOf(LuaRef) opts, Error *err)
|
||||||
|
FUNC_API_LUA_ONLY
|
||||||
|
{
|
||||||
|
redraw_later(NOT_VALID);
|
||||||
|
clear_luahl(false);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < opts.size; i++) {
|
||||||
|
String k = opts.items[i].key;
|
||||||
|
Object *v = &opts.items[i].value;
|
||||||
|
if (strequal("on_start", k.data)) {
|
||||||
|
if (v->type != kObjectTypeLuaRef) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
luahl_start = v->data.luaref;
|
||||||
|
v->data.luaref = LUA_NOREF;
|
||||||
|
} else if (strequal("on_win", k.data)) {
|
||||||
|
if (v->type != kObjectTypeLuaRef) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
luahl_win = v->data.luaref;
|
||||||
|
v->data.luaref = LUA_NOREF;
|
||||||
|
} else if (strequal("on_line", k.data)) {
|
||||||
|
if (v->type != kObjectTypeLuaRef) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
luahl_line = v->data.luaref;
|
||||||
|
v->data.luaref = LUA_NOREF;
|
||||||
|
} else {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
luahl_active = true;
|
||||||
|
return;
|
||||||
|
error:
|
||||||
|
clear_luahl(true);
|
||||||
|
}
|
||||||
|
|||||||
@@ -842,12 +842,6 @@ struct file_buffer {
|
|||||||
// The number for times the current line has been flushed in the memline.
|
// The number for times the current line has been flushed in the memline.
|
||||||
int flush_count;
|
int flush_count;
|
||||||
|
|
||||||
bool b_luahl;
|
|
||||||
LuaRef b_luahl_start;
|
|
||||||
LuaRef b_luahl_window;
|
|
||||||
LuaRef b_luahl_line;
|
|
||||||
LuaRef b_luahl_end;
|
|
||||||
|
|
||||||
int b_diff_failed; // internal diff failed for this buffer
|
int b_diff_failed; // internal diff failed for this buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -844,8 +844,15 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
|
|||||||
bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
|
bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
|
||||||
{
|
{
|
||||||
state->row = -1;
|
state->row = -1;
|
||||||
|
for (size_t i = 0; i < kv_size(state->active); i++) {
|
||||||
|
HlRange item = kv_A(state->active, i);
|
||||||
|
if (item.virt_text_owned) {
|
||||||
|
clear_virttext(item.virt_text);
|
||||||
|
xfree(item.virt_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
kv_size(state->active) = 0;
|
kv_size(state->active) = 0;
|
||||||
return buf->b_extmark_index || buf->b_luahl;
|
return buf->b_extmark_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -889,10 +896,10 @@ bool decorations_redraw_start(buf_T *buf, int top_row,
|
|||||||
HlRange range;
|
HlRange range;
|
||||||
if (mark.id&MARKTREE_END_FLAG) {
|
if (mark.id&MARKTREE_END_FLAG) {
|
||||||
range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
|
range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
|
||||||
attr_id, vt };
|
attr_id, vt, false };
|
||||||
} else {
|
} else {
|
||||||
range = (HlRange){ mark.row, mark.col, altpos.row,
|
range = (HlRange){ mark.row, mark.col, altpos.row,
|
||||||
altpos.col, attr_id, vt };
|
altpos.col, attr_id, vt, false };
|
||||||
}
|
}
|
||||||
kv_push(state->active, range);
|
kv_push(state->active, range);
|
||||||
|
|
||||||
@@ -957,7 +964,7 @@ int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
|
|||||||
VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
|
VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
|
||||||
kv_push(state->active, ((HlRange){ mark.row, mark.col,
|
kv_push(state->active, ((HlRange){ mark.row, mark.col,
|
||||||
endpos.row, endpos.col,
|
endpos.row, endpos.col,
|
||||||
attr_id, vt }));
|
attr_id, vt, false }));
|
||||||
|
|
||||||
next_mark:
|
next_mark:
|
||||||
marktree_itr_next(buf->b_marktree, state->itr);
|
marktree_itr_next(buf->b_marktree, state->itr);
|
||||||
@@ -991,6 +998,9 @@ next_mark:
|
|||||||
}
|
}
|
||||||
if (keep) {
|
if (keep) {
|
||||||
kv_A(state->active, j++) = kv_A(state->active, i);
|
kv_A(state->active, j++) = kv_A(state->active, i);
|
||||||
|
} else if (item.virt_text_owned) {
|
||||||
|
clear_virttext(item.virt_text);
|
||||||
|
xfree(item.virt_text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kv_size(state->active) = j;
|
kv_size(state->active) = j;
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ typedef struct {
|
|||||||
int end_col;
|
int end_col;
|
||||||
int attr_id;
|
int attr_id;
|
||||||
VirtText *virt_text;
|
VirtText *virt_text;
|
||||||
|
bool virt_text_owned;
|
||||||
} HlRange;
|
} HlRange;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -405,6 +405,12 @@ EXTERN int sys_menu INIT(= false);
|
|||||||
// ('lines' and 'rows') must not be changed.
|
// ('lines' and 'rows') must not be changed.
|
||||||
EXTERN int updating_screen INIT(= 0);
|
EXTERN int updating_screen INIT(= 0);
|
||||||
|
|
||||||
|
EXTERN bool luahl_active INIT(= false);
|
||||||
|
EXTERN LuaRef luahl_start INIT(= LUA_NOREF);
|
||||||
|
EXTERN LuaRef luahl_win INIT(= LUA_NOREF);
|
||||||
|
EXTERN LuaRef luahl_line INIT(= LUA_NOREF);
|
||||||
|
EXTERN LuaRef luahl_end INIT(= LUA_NOREF);
|
||||||
|
|
||||||
// All windows are linked in a list. firstwin points to the first entry,
|
// All windows are linked in a list. firstwin points to the first entry,
|
||||||
// lastwin to the last entry (can be the same as firstwin) and curwin to the
|
// lastwin to the last entry (can be the same as firstwin) and curwin to the
|
||||||
// currently active window.
|
// currently active window.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
#include <msgpack.h>
|
#include <msgpack.h>
|
||||||
|
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ static bool msg_grid_invalid = false;
|
|||||||
|
|
||||||
static bool resizing = false;
|
static bool resizing = false;
|
||||||
|
|
||||||
|
static bool do_luahl_line = false;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "screen.c.generated.h"
|
# include "screen.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -508,12 +510,12 @@ int update_screen(int type)
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf_T *buf = wp->w_buffer;
|
buf_T *buf = wp->w_buffer;
|
||||||
if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
|
if (luahl_active && luahl_start != LUA_NOREF) {
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
FIXED_TEMP_ARRAY(args, 2);
|
FIXED_TEMP_ARRAY(args, 2);
|
||||||
args.items[0] = BUFFER_OBJ(buf->handle);
|
args.items[0] = BUFFER_OBJ(buf->handle);
|
||||||
args.items[1] = INTEGER_OBJ(display_tick);
|
args.items[1] = INTEGER_OBJ(display_tick);
|
||||||
nlua_call_ref(buf->b_luahl_start, "start", args, false, &err);
|
nlua_call_ref(luahl_start, "start", args, false, &err);
|
||||||
if (ERROR_SET(&err)) {
|
if (ERROR_SET(&err)) {
|
||||||
ELOG("error in luahl start: %s", err.msg);
|
ELOG("error in luahl start: %s", err.msg);
|
||||||
api_clear_error(&err);
|
api_clear_error(&err);
|
||||||
@@ -639,10 +641,11 @@ bool decorations_active = false;
|
|||||||
|
|
||||||
void decorations_add_luahl_attr(int attr_id,
|
void decorations_add_luahl_attr(int attr_id,
|
||||||
int start_row, int start_col,
|
int start_row, int start_col,
|
||||||
int end_row, int end_col)
|
int end_row, int end_col, VirtText *virt_text)
|
||||||
{
|
{
|
||||||
kv_push(decorations.active,
|
kv_push(decorations.active,
|
||||||
((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL }));
|
((HlRange){ start_row, start_col,
|
||||||
|
end_row, end_col, attr_id, virt_text, true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1238,7 +1241,9 @@ static void win_update(win_T *wp)
|
|||||||
|
|
||||||
decorations_active = decorations_redraw_reset(buf, &decorations);
|
decorations_active = decorations_redraw_reset(buf, &decorations);
|
||||||
|
|
||||||
if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
|
do_luahl_line = false;
|
||||||
|
|
||||||
|
if (luahl_win != LUA_NOREF) {
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
FIXED_TEMP_ARRAY(args, 4);
|
FIXED_TEMP_ARRAY(args, 4);
|
||||||
linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
|
linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
|
||||||
@@ -1251,7 +1256,13 @@ static void win_update(win_T *wp)
|
|||||||
args.items[3] = INTEGER_OBJ(knownmax);
|
args.items[3] = INTEGER_OBJ(knownmax);
|
||||||
// TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
|
// TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
|
||||||
// For now the "start" callback is expected to use nvim__buf_redraw_range.
|
// For now the "start" callback is expected to use nvim__buf_redraw_range.
|
||||||
nlua_call_ref(buf->b_luahl_window, "window", args, false, &err);
|
Object ret = nlua_call_ref(luahl_win, "win", args, true, &err);
|
||||||
|
|
||||||
|
if (!ERROR_SET(&err) && api_is_truthy(ret, "luahl_window retval", &err)) {
|
||||||
|
do_luahl_line = true;
|
||||||
|
decorations_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ERROR_SET(&err)) {
|
if (ERROR_SET(&err)) {
|
||||||
ELOG("error in luahl window: %s", err.msg);
|
ELOG("error in luahl window: %s", err.msg);
|
||||||
api_clear_error(&err);
|
api_clear_error(&err);
|
||||||
@@ -2348,7 +2359,7 @@ win_line (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (decorations_active) {
|
if (decorations_active) {
|
||||||
if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
|
if (do_luahl_line && luahl_line != LUA_NOREF) {
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
FIXED_TEMP_ARRAY(args, 3);
|
FIXED_TEMP_ARRAY(args, 3);
|
||||||
args.items[0] = WINDOW_OBJ(wp->handle);
|
args.items[0] = WINDOW_OBJ(wp->handle);
|
||||||
@@ -2356,14 +2367,10 @@ win_line (
|
|||||||
args.items[2] = INTEGER_OBJ(lnum-1);
|
args.items[2] = INTEGER_OBJ(lnum-1);
|
||||||
lua_attr_active = true;
|
lua_attr_active = true;
|
||||||
extra_check = true;
|
extra_check = true;
|
||||||
Object o = nlua_call_ref(buf->b_luahl_line, "line", args, true, &err);
|
nlua_call_ref(luahl_line, "line", args, false, &err);
|
||||||
lua_attr_active = false;
|
lua_attr_active = false;
|
||||||
if (o.type == kObjectTypeString) {
|
|
||||||
// TODO(bfredl): this is a bit of a hack. A final API should use an
|
if (ERROR_SET(&err)) {
|
||||||
// "unified" interface where luahl can add both bufhl and virttext
|
|
||||||
luatext = o.data.string.data;
|
|
||||||
do_virttext = true;
|
|
||||||
} else if (ERROR_SET(&err)) {
|
|
||||||
ELOG("error in luahl line: %s", err.msg);
|
ELOG("error in luahl line: %s", err.msg);
|
||||||
luatext = err.msg;
|
luatext = err.msg;
|
||||||
do_virttext = true;
|
do_virttext = true;
|
||||||
|
|||||||
@@ -446,10 +446,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
]]}
|
]]}
|
||||||
|
|
||||||
feed("5Goc<esc>dd")
|
feed("5Goc<esc>dd")
|
||||||
if true == true then
|
|
||||||
pending('reenable this check in luahl PR')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
{2:/// Schedule Lua callback on main loop's event queue} |
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
||||||
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
||||||
@@ -480,7 +477,7 @@ static int nlua_schedule(lua_State *const lstate)
|
|||||||
|| {6:lstate} != {6:lstate}) { |
|
|| {6:lstate} != {6:lstate}) { |
|
||||||
{11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
|
{11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
|
||||||
{4:return} {11:lua_error}(lstate); |
|
{4:return} {11:lua_error}(lstate); |
|
||||||
*^/ |
|
{8:*^/} |
|
||||||
} |
|
} |
|
||||||
|
|
|
|
||||||
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
||||||
|
|||||||
Reference in New Issue
Block a user