aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/minimap.lua224
1 files changed, 156 insertions, 68 deletions
diff --git a/plugins/minimap.lua b/plugins/minimap.lua
index 7c4ddab..6a89b4d 100644
--- a/plugins/minimap.lua
+++ b/plugins/minimap.lua
@@ -5,8 +5,23 @@ 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"
+-- cache for the location of the rects for each Doc
+local highlighter_cache
+local function reset_cache()
+ highlighter_cache = setmetatable({}, { __mode = "k" })
+end
+reset_cache()
+
+-- minimap status per DocView
+local per_docview
+local function reset_per_docview()
+ per_docview = setmetatable({}, { __mode = "k" })
+end
+reset_per_docview()
+
-- Sample configurations:
-- full width:
-- config.plugins.minimap.highlight_width = 100
@@ -27,6 +42,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
@@ -71,7 +88,11 @@ config.plugins.minimap = common.merge({
description = "Disable to improve performance.",
path = "syntax_highlight",
type = "toggle",
- default = true
+ default = true,
+ on_apply = function(value)
+ config.plugins.minimap.syntax_highlight = value
+ reset_cache()
+ end
},
{
label = "Scale",
@@ -84,6 +105,18 @@ 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,
+ on_apply = function(value)
+ config.plugins.minimap.spaces_to_split = value
+ reset_cache()
+ end
+ },
+ {
label = "Hide for small Docs",
description = "Hide the minimap when a Doc is small enough.",
path = "avoid_small_docs",
@@ -182,6 +215,35 @@ 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
+-- 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()
function MiniMap:new()
@@ -192,7 +254,6 @@ 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
@@ -426,22 +487,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 last_batch_end = -1
local minimap_cutoff_x = 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] * 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
@@ -460,73 +545,75 @@ 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
-
- -- 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
@@ -539,10 +626,11 @@ end
command.add(nil, {
["minimap:toggle-visibility"] = function()
config.plugins.minimap.enabled = not config.plugins.minimap.enabled
- setmetatable({}, { __mode = "k" })
+ reset_per_docview()
end,
["minimap:toggle-syntax-highlighting"] = function()
config.plugins.minimap.syntax_highlight = not config.plugins.minimap.syntax_highlight
+ reset_cache()
end
})