Merge pull request #1030 from Guldoman/PR_cache_draw_whitespace

`drawwhitespace`: Cache whitespace location
This commit is contained in:
Jefferson González 2022-06-15 21:08:09 -04:00 committed by GitHub
commit 380cfb9a24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 48 deletions

View File

@ -22,13 +22,21 @@ function Highlighter:new(doc)
else else
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line) local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
local retokenized_from
for i = self.first_invalid_line, max do for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i] local line = self.lines[i]
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
retokenized_from = retokenized_from or i
self.lines[i] = self:tokenize_line(i, state) self.lines[i] = self:tokenize_line(i, state)
elseif retokenized_from then
self:update_notify(retokenized_from, i - retokenized_from - 1)
retokenized_from = nil
end end
end end
if retokenized_from then
self:update_notify(retokenized_from, max - retokenized_from)
end
self.first_invalid_line = max + 1 self.first_invalid_line = max + 1
core.redraw = true core.redraw = true
@ -71,6 +79,10 @@ function Highlighter:remove_notify(line, n)
common.splice(self.lines, line, n) common.splice(self.lines, line, n)
end end
function Highlighter:update_notify(line, n)
-- plugins can hook here to be notified that lines have been retokenized
end
function Highlighter:tokenize_line(idx, state) function Highlighter:tokenize_line(idx, state)
local res = {} local res = {}
@ -87,6 +99,7 @@ function Highlighter:get_line(idx)
local prev = self.lines[idx - 1] local prev = self.lines[idx - 1]
line = self:tokenize_line(idx, prev and prev.state) line = self:tokenize_line(idx, prev and prev.state)
self.lines[idx] = line self.lines[idx] = line
self:update_notify(idx, 0)
end end
self.max_wanted_line = math.max(self.max_wanted_line, idx) self.max_wanted_line = math.max(self.max_wanted_line, idx)
return line return line

View File

@ -4,6 +4,7 @@ local style = require "core.style"
local DocView = require "core.docview" local DocView = require "core.docview"
local common = require "core.common" local common = require "core.common"
local config = require "core.config" local config = require "core.config"
local Highlighter = require "core.doc.highlighter"
config.plugins.drawwhitespace = common.merge({ config.plugins.drawwhitespace = common.merge({
enabled = true, enabled = true,
@ -95,6 +96,87 @@ config.plugins.drawwhitespace = common.merge({
} }
}, config.plugins.drawwhitespace) }, config.plugins.drawwhitespace)
local ws_cache
local cached_settings
local function reset_cache()
ws_cache = setmetatable({}, { __mode = "k" })
local settings = config.plugins.drawwhitespace
cached_settings = {
show_leading = settings.show_leading,
show_trailing = settings.show_trailing,
show_middle = settings.show_middle,
show_middle_min = settings.show_middle_min,
color = settings.color,
leading_color = settings.leading_color,
middle_color = settings.middle_color,
trailing_color = settings.trailing_color,
substitutions = settings.substitutions,
}
end
reset_cache()
local function reset_cache_if_needed()
local settings = config.plugins.drawwhitespace
if
not ws_cache or
cached_settings.show_leading ~= settings.show_leading
or cached_settings.show_trailing ~= settings.show_trailing
or cached_settings.show_middle ~= settings.show_middle
or cached_settings.show_middle_min ~= settings.show_middle_min
or cached_settings.color ~= settings.color
or cached_settings.leading_color ~= settings.leading_color
or cached_settings.middle_color ~= settings.middle_color
or cached_settings.trailing_color ~= settings.trailing_color
-- we assume that the entire table changes
or cached_settings.substitutions ~= settings.substitutions
then
reset_cache()
end
end
-- Move cache to make space for new lines
local prev_insert_notify = Highlighter.insert_notify
function Highlighter:insert_notify(line, n, ...)
prev_insert_notify(self, line, n, ...)
if not ws_cache[self] then
ws_cache[self] = {}
end
local to = math.min(line + n, #self.doc.lines)
for i=#self.doc.lines+n,to,-1 do
ws_cache[self][i] = ws_cache[self][i - n]
end
for i=line,to do
ws_cache[self][i] = nil
end
end
-- Close the cache gap created by removed lines
local prev_remove_notify = Highlighter.remove_notify
function Highlighter:remove_notify(line, n, ...)
prev_remove_notify(self, line, n, ...)
if not ws_cache[self] then
ws_cache[self] = {}
end
local to = math.max(line + n, #self.doc.lines)
for i=line,to do
ws_cache[self][i] = ws_cache[self][i + n]
end
end
-- Remove changed lines from the cache
local prev_update_notify = Highlighter.update_notify
function Highlighter:update_notify(line, n, ...)
prev_update_notify(self, line, n, ...)
if not ws_cache[self] then
ws_cache[self] = {}
end
for i=line,line+n do
ws_cache[self][i] = nil
end
end
local function get_option(substitution, option) local function get_option(substitution, option)
if substitution[option] == nil then if substitution[option] == nil then
return config.plugins.drawwhitespace[option] return config.plugins.drawwhitespace[option]
@ -113,16 +195,25 @@ function DocView:draw_line_text(idx, x, y)
end end
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"]) local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
local ty = y + self:get_line_text_y_offset()
reset_cache_if_needed()
if
not ws_cache[self.doc.highlighter]
or ws_cache[self.doc.highlighter].font ~= font
then
ws_cache[self.doc.highlighter] = {font = font}
end
if not ws_cache[self.doc.highlighter][idx] then -- need to cache line
local cache = {}
local tx local tx
local text, offset, s, e = self.doc.lines[idx], 1 local text = self.doc.lines[idx]
local x1, _, x2, _ = self:get_content_bounds()
local _offset = self:get_x_offset_col(idx, x1)
for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do
local char = substitution.char local char = substitution.char
local sub = substitution.sub local sub = substitution.sub
offset = _offset local offset = 1
local show_leading = get_option(substitution, "show_leading") local show_leading = get_option(substitution, "show_leading")
local show_middle = get_option(substitution, "show_middle") local show_middle = get_option(substitution, "show_middle")
@ -137,10 +228,10 @@ function DocView:draw_line_text(idx, x, y)
local pattern = char.."+" local pattern = char.."+"
while true do while true do
s, e = text:find(pattern, offset) local s, e = text:find(pattern, offset)
if not s then break end if not s then break end
tx = self:get_col_x_offset(idx, s) + x tx = self:get_col_x_offset(idx, s)
local color = base_color local color = base_color
local draw = false local draw = false
@ -157,24 +248,48 @@ function DocView:draw_line_text(idx, x, y)
end end
if draw then if draw then
local last_cache_idx = #cache
-- We need to draw tabs one at a time because they might have a -- We need to draw tabs one at a time because they might have a
-- different size than the substituting character. -- different size than the substituting character.
-- This also applies to any other char if we use non-monospace fonts -- This also applies to any other char if we use non-monospace fonts
-- but we ignore this case for now. -- but we ignore this case for now.
if char == "\t" then if char == "\t" then
for i = s,e do for i = s,e do
tx = self:get_col_x_offset(idx, i) + x tx = self:get_col_x_offset(idx, i)
tx = renderer.draw_text(font, sub, tx, ty, color) cache[last_cache_idx + 1] = sub
cache[last_cache_idx + 2] = tx
cache[last_cache_idx + 3] = font:get_width(sub)
cache[last_cache_idx + 4] = color
last_cache_idx = last_cache_idx + 4
end end
else else
tx = renderer.draw_text(font, string.rep(sub, e - s + 1), tx, ty, color) cache[last_cache_idx + 1] = string.rep(sub, e - s + 1)
cache[last_cache_idx + 2] = tx
cache[last_cache_idx + 3] = font:get_width(cache[last_cache_idx + 1])
cache[last_cache_idx + 4] = color
end end
end end
if tx > x + x2 then break end
offset = e + 1 offset = e + 1
end end
end end
ws_cache[self.doc.highlighter][idx] = cache
end
-- draw from cache
local x1, _, x2, _ = self:get_content_bounds()
x1 = x1 + x
x2 = x2 + x
local cache = ws_cache[self.doc.highlighter][idx]
for i=1,#cache,4 do
local sub = cache[i]
local tx = cache[i + 1] + x
local tw = cache[i + 2]
local color = cache[i + 3]
if tx + tw >= x1 then
tx = renderer.draw_text(font, sub, tx, y, color)
end
if tx > x2 then break end
end
return draw_line_text(self, idx, x, y) return draw_line_text(self, idx, x, y)
end end