aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuldoman <giulio.lettieri@gmail.com>2022-06-14 18:17:31 +0200
committerGitHub <noreply@github.com>2022-06-14 18:17:31 +0200
commitd269ab6e69b270d5450e0e8316b745e04f5e3bd7 (patch)
tree8795c12c3b80385d74fa201a76493b7397691c13
parent5fc20466d1620b4115c5ed609df007385dc1cf0d (diff)
parent074d270ecf5960f67f33da98977ff3385de0b534 (diff)
downloadlite-xl-plugins-d269ab6e69b270d5450e0e8316b745e04f5e3bd7.tar.gz
lite-xl-plugins-d269ab6e69b270d5450e0e8316b745e04f5e3bd7.zip
Merge pull request #97 from Guldoman/PR_improve_minimap2
`minimap`: Performance improvements
-rw-r--r--plugins/minimap.lua304
1 files changed, 232 insertions, 72 deletions
diff --git a/plugins/minimap.lua b/plugins/minimap.lua
index f2638c8..4751ce6 100644
--- a/plugins/minimap.lua
+++ b/plugins/minimap.lua
@@ -5,6 +5,7 @@ local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
+local Highlighter = require "core.doc.highlighter"
local Object = require "core.object"
-- Sample configurations:
@@ -27,6 +28,8 @@ config.plugins.minimap = common.merge({
instant_scroll = false,
syntax_highlight = true,
scale = 1,
+ -- number of spaces needed to split a token
+ spaces_to_split = 2,
-- hide on small docs (can be true, false or min number of lines)
avoid_small_docs = false,
-- how many spaces one tab is equivalent to
@@ -84,6 +87,14 @@ config.plugins.minimap = common.merge({
step = 0.1
},
{
+ label = "Spaces to split",
+ description = "Number of spaces needed to split a token.",
+ path = "spaces_to_split",
+ type = "number",
+ default = 2,
+ min = 1
+ },
+ {
label = "Hide for small Docs",
description = "Hide the minimap when a Doc is small enough.",
path = "avoid_small_docs",
@@ -177,10 +188,123 @@ config.plugins.minimap = common.merge({
}
}, config.plugins.minimap)
+
+-- contains the settings values that require a cache reset if changed
+local cached_settings = {
+ color_scheme_canary = nil,
+ syntax_highlight = nil,
+ spaces_to_split = nil,
+ scale = nil,
+ width = nil,
+}
+
-- Configure size for rendering each char in the minimap
-local char_height = 1 * SCALE * config.plugins.minimap.scale
-local char_spacing = 0.8 * SCALE * config.plugins.minimap.scale
-local line_spacing = 2 * SCALE * config.plugins.minimap.scale
+local char_height
+local char_spacing
+local line_spacing
+
+-- cache for the location of the rects for each Doc
+local highlighter_cache
+local function reset_cache()
+ highlighter_cache = setmetatable({}, { __mode = "k" })
+ cached_settings = {
+ color_scheme_canary = style.syntax["normal"],
+ syntax_highlight = config.plugins.minimap.syntax_highlight,
+ spaces_to_split = config.plugins.minimap.spaces_to_split,
+ scale = config.plugins.minimap.scale,
+ width = config.plugins.minimap.width,
+ }
+ char_height = 1 * SCALE * config.plugins.minimap.scale
+ char_spacing = 0.8 * SCALE * config.plugins.minimap.scale
+ line_spacing = 2 * SCALE * config.plugins.minimap.scale
+end
+reset_cache()
+
+
+local function reset_cache_if_needed()
+ if
+ cached_settings.color_scheme_canary ~= style.syntax["normal"]
+ or cached_settings.syntax_highlight ~= config.plugins.minimap.syntax_highlight
+ or cached_settings.spaces_to_split ~= config.plugins.minimap.spaces_to_split
+ or cached_settings.scale ~= config.plugins.minimap.scale
+ or cached_settings.width ~= config.plugins.minimap.width
+ then
+ reset_cache()
+ end
+end
+
+
+-- minimap status per DocView
+local per_docview
+local function reset_per_docview()
+ per_docview = setmetatable({}, { __mode = "k" })
+end
+reset_per_docview()
+
+
+-- 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, ...)
+ local blanks = { }
+ if not highlighter_cache[self] then
+ highlighter_cache[self] = {}
+ else
+ local to = math.min(line + n, #self.doc.lines)
+ for i=#self.doc.lines+n,to,-1 do
+ highlighter_cache[self][i] = highlighter_cache[self][i - n]
+ end
+ for i=line,to do
+ highlighter_cache[self][i] = nil
+ end
+ 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 highlighter_cache[self] then
+ highlighter_cache[self] = {}
+ else
+ local to = math.max(line + n, #self.doc.lines)
+ for i=line,to do
+ highlighter_cache[self][i] = highlighter_cache[self][i + n]
+ end
+ end
+end
+
+
+-- Remove changed lines from the cache
+local prev_tokenize_line = Highlighter.tokenize_line
+function Highlighter:tokenize_line(idx, state, ...)
+ local res = prev_tokenize_line(self, idx, state, ...)
+ if not highlighter_cache[self] then
+ highlighter_cache[self] = {}
+ end
+ highlighter_cache[self][idx] = nil
+ return res
+end
+
+-- Ask the Highlighter to retokenize the lines we have in cache
+local prev_invalidate = Highlighter.invalidate
+function Highlighter:invalidate(idx, ...)
+ local cache = highlighter_cache[self]
+ if cache then
+ self.max_wanted_line = math.max(self.max_wanted_line, #cache)
+ end
+ return prev_invalidate(self, idx, ...)
+end
+
+
+-- Remove cache on Highlighter reset (for example on syntax change)
+local prev_soft_reset = Highlighter.soft_reset
+function Highlighter:soft_reset(...)
+ prev_soft_reset(self, ...)
+ highlighter_cache[self] = {}
+end
+
local MiniMap = Object:extend()
@@ -192,14 +316,17 @@ function MiniMap:line_highlight_color(line_index)
end
local minimap = MiniMap()
-local per_docview = setmetatable({}, { __mode = "k" })
local function show_minimap(docview)
if not docview:is(DocView) then return false end
if
not config.plugins.minimap.enabled
- or not docview:is(DocView)
- or per_docview[docview] == false
+ and per_docview[docview] ~= true
+ then
+ return false
+ elseif
+ config.plugins.minimap.enabled
+ and per_docview[docview] == false
then
return false
end
@@ -422,22 +549,46 @@ DocView.draw_scrollbar = function(self)
-- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
local batch_width = 0
local batch_start = x
- local minimap_cutoff_x = x + config.plugins.minimap.width * SCALE
+ local last_batch_end = -1
+ local minimap_cutoff_x = config.plugins.minimap.width * SCALE
local batch_syntax_type = nil
- local function flush_batch(type)
- local old_color = color
- color = style.syntax[batch_syntax_type]
- if config.plugins.minimap.syntax_highlight and color ~= nil then
- -- fetch and dim colors
- color = {color[1], color[2], color[3], color[4] * 0.5}
- else
- color = old_color
- end
+ local function flush_batch(type, cache)
if batch_width > 0 then
- renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
+ local lastidx = #cache
+ local old_color = color
+ color = style.syntax[type]
+ if config.plugins.minimap.syntax_highlight and color ~= nil then
+ -- fetch and dim colors
+ color = {color[1], color[2], color[3], (color[4] or 255) * 0.5}
+ else
+ color = old_color
+ end
+ if #cache >= 3 then
+ local last_color = cache[lastidx]
+ if
+ last_batch_end == batch_start -- no space skipped
+ and (
+ batch_syntax_type == type -- and same syntax
+ or ( -- or same color
+ last_color[1] == color[1]
+ and last_color[2] == color[2]
+ and last_color[3] == color[3]
+ and last_color[4] == color[4]
+ )
+ )
+ then
+ batch_start = cache[lastidx - 2]
+ batch_width = cache[lastidx - 1] + batch_width
+ lastidx = lastidx - 3
+ end
+ end
+ cache[lastidx + 1] = batch_start
+ cache[lastidx + 2] = batch_width
+ cache[lastidx + 3] = color
end
batch_syntax_type = type
batch_start = batch_start + batch_width
+ last_batch_end = batch_start
batch_width = 0
end
@@ -456,73 +607,77 @@ DocView.draw_scrollbar = function(self)
local endidx = minimap_start_line + max_minmap_lines
endidx = math.min(endidx, line_count)
- -- render lines with syntax highlighting
- if config.plugins.minimap.syntax_highlight then
- -- keep track of the highlight type, since this needs to break batches as well
- batch_syntax_type = nil
+ reset_cache_if_needed()
- -- per line
- for idx = minimap_start_line, endidx do
- batch_syntax_type = nil
- batch_start = x + gutter_width
- batch_width = 0
+ if not highlighter_cache[self.doc.highlighter] then
+ highlighter_cache[self.doc.highlighter] = {}
+ end
- render_highlight(idx, line_y)
+ -- per line
+ for idx = minimap_start_line, endidx do
+ batch_syntax_type = nil
+ batch_start = 0
+ batch_width = 0
+ last_batch_end = -1
+ render_highlight(idx, line_y)
+ local cache = highlighter_cache[self.doc.highlighter][idx]
+ if not highlighter_cache[self.doc.highlighter][idx] then -- need to cache
+ highlighter_cache[self.doc.highlighter][idx] = {}
+ cache = highlighter_cache[self.doc.highlighter][idx]
-- per token
for _, type, text in self.doc.highlighter:each_token(idx) do
- -- flush prev batch
- if not batch_syntax_type then batch_syntax_type = type end
- if batch_syntax_type ~= type then flush_batch(type) end
-
- -- per character
- for char in common.utf8_chars(text) do
- if char == " " or char == "\n" then
- flush_batch(type)
- batch_start = batch_start + char_spacing
- elseif char == " " then
- flush_batch(type)
- batch_start = batch_start + (char_spacing * config.plugins.minimap.tab_width)
- elseif batch_start + batch_width > minimap_cutoff_x then
- flush_batch(type)
+ if not config.plugins.minimap.syntax_highlight then
+ type = nil
+ end
+ local start = 1
+ while true do
+ -- find text followed spaces followed by newline
+ local s, e, w, eol = string.ufind(text, "[^%s]*()[ \t]*()\n?", start)
+ if not s then break end
+ local nchars = w - s
+ start = e + 1
+ batch_width = batch_width + char_spacing * nchars
+
+ local nspaces = 0
+ for i=w,e do
+ local whitespace = string.sub(text, i, i)
+ if whitespace == "\t" then
+ nspaces = nspaces + config.plugins.minimap.tab_width
+ elseif whitespace == " " then
+ nspaces = nspaces + 1
+ end
+ end
+ -- not enough spaces; consider them part of the batch
+ if nspaces < config.plugins.minimap.spaces_to_split then
+ batch_width = batch_width + nspaces * char_spacing
+ end
+ -- line has ended or no more space in the minimap;
+ -- we can go to the next line
+ if eol <= w or batch_start + batch_width > minimap_cutoff_x then
+ if batch_width > 0 then
+ flush_batch(type, cache)
+ end
break
- else
- batch_width = batch_width + char_spacing
end
-
+ -- enough spaces to split the batch
+ if nspaces >= config.plugins.minimap.spaces_to_split then
+ flush_batch(type, cache)
+ batch_start = batch_start + nspaces * char_spacing
+ end
end
end
- flush_batch(nil)
- line_y = line_y + line_spacing
end
-
- else -- render lines without syntax highlighting
- for idx = minimap_start_line, endidx do
- batch_start = x + gutter_width
- batch_width = 0
-
- render_highlight(idx, line_y)
-
- for char in common.utf8_chars(self.doc.lines[idx]) do
- if char == " " or char == "\n" then
- flush_batch()
- batch_start = batch_start + char_spacing
- elseif char == " " then
- flush_batch()
- batch_start = batch_start + (char_spacing * config.plugins.minimap.tab_width)
- elseif batch_start + batch_width > minimap_cutoff_x then
- flush_batch()
- else
- batch_width = batch_width + char_spacing
- end
- end
- flush_batch()
- line_y = line_y + line_spacing
+ -- draw from cache
+ for i=1,#cache,3 do
+ local batch_start = cache[i ] + x + gutter_width
+ local batch_width = cache[i + 1]
+ local color = cache[i + 2]
+ renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
end
-
+ line_y = line_y + line_spacing
end
-
end
local prev_update = DocView.update
@@ -535,6 +690,7 @@ end
command.add(nil, {
["minimap:toggle-visibility"] = function()
config.plugins.minimap.enabled = not config.plugins.minimap.enabled
+ reset_per_docview()
end,
["minimap:toggle-syntax-highlighting"] = function()
config.plugins.minimap.syntax_highlight = not config.plugins.minimap.syntax_highlight
@@ -543,7 +699,11 @@ command.add(nil, {
command.add("core.docview!", {
["minimap:toggle-visibility-for-current-view"] = function()
- per_docview[core.active_view] = per_docview[core.active_view] == false
+ if config.plugins.minimap.enabled then
+ per_docview[core.active_view] = per_docview[core.active_view] == false
+ else
+ per_docview[core.active_view] = not per_docview[core.active_view]
+ end
end
})