diff options
-rw-r--r-- | plugins/minimap.lua | 304 |
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 }) |