2022-05-31 22:34:14 +02:00
|
|
|
-- mod-version:3
|
2021-09-11 04:22:30 +02:00
|
|
|
|
2023-01-30 17:43:26 +01:00
|
|
|
local core = require "core"
|
2021-09-11 04:22:30 +02:00
|
|
|
local style = require "core.style"
|
|
|
|
local DocView = require "core.docview"
|
|
|
|
local common = require "core.common"
|
2022-04-01 18:10:22 +02:00
|
|
|
local config = require "core.config"
|
2022-06-11 06:30:13 +02:00
|
|
|
local Highlighter = require "core.doc.highlighter"
|
2021-09-11 04:22:30 +02:00
|
|
|
|
2022-04-01 18:10:22 +02:00
|
|
|
config.plugins.drawwhitespace = common.merge({
|
2022-06-02 05:07:31 +02:00
|
|
|
enabled = true,
|
2022-04-01 18:10:22 +02:00
|
|
|
show_leading = true,
|
|
|
|
show_trailing = true,
|
|
|
|
show_middle = true,
|
2023-01-30 17:43:26 +01:00
|
|
|
show_selected_only = false,
|
2022-04-01 18:10:22 +02:00
|
|
|
|
|
|
|
show_middle_min = 1,
|
|
|
|
|
|
|
|
color = style.syntax.whitespace or style.syntax.comment,
|
|
|
|
leading_color = nil,
|
|
|
|
middle_color = nil,
|
|
|
|
trailing_color = nil,
|
|
|
|
|
|
|
|
substitutions = {
|
|
|
|
{
|
|
|
|
char = " ",
|
|
|
|
sub = "·",
|
|
|
|
-- You can put any of the previous options here too.
|
|
|
|
-- For example:
|
|
|
|
-- show_middle_min = 2,
|
|
|
|
-- show_leading = false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
char = "\t",
|
|
|
|
sub = "»",
|
|
|
|
},
|
|
|
|
},
|
2022-06-02 05:07:31 +02:00
|
|
|
|
|
|
|
config_spec = {
|
|
|
|
name = "Draw Whitespace",
|
|
|
|
{
|
|
|
|
label = "Enabled",
|
|
|
|
description = "Disable or enable the drawing of white spaces.",
|
|
|
|
path = "enabled",
|
|
|
|
type = "toggle",
|
|
|
|
default = true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label = "Show Leading",
|
|
|
|
description = "Draw whitespaces starting at the beginning of a line.",
|
|
|
|
path = "show_leading",
|
|
|
|
type = "toggle",
|
|
|
|
default = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label = "Show Middle",
|
|
|
|
description = "Draw whitespaces on the middle of a line.",
|
|
|
|
path = "show_middle",
|
|
|
|
type = "toggle",
|
|
|
|
default = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label = "Show Trailing",
|
|
|
|
description = "Draw whitespaces on the end of a line.",
|
|
|
|
path = "show_trailing",
|
|
|
|
type = "toggle",
|
|
|
|
default = true,
|
|
|
|
},
|
2023-01-30 17:43:26 +01:00
|
|
|
{
|
|
|
|
label = "Show Selected Only",
|
|
|
|
description = "Only draw whitespaces if it is within a selection.",
|
|
|
|
path = "show_selected_only",
|
|
|
|
type = "toggle",
|
|
|
|
default = false,
|
|
|
|
},
|
2022-06-02 05:07:31 +02:00
|
|
|
{
|
|
|
|
label = "Show Trailing as Error",
|
|
|
|
description = "Uses an error square to spot them easily, requires 'Show Trailing' enabled.",
|
|
|
|
path = "show_trailing_error",
|
|
|
|
type = "toggle",
|
|
|
|
default = false,
|
|
|
|
on_apply = function(enabled)
|
|
|
|
local found = nil
|
|
|
|
local substitutions = config.plugins.drawwhitespace.substitutions
|
|
|
|
for i, sub in ipairs(substitutions) do
|
|
|
|
if sub.trailing_error then
|
|
|
|
found = i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if found == nil and enabled then
|
|
|
|
table.insert(substitutions, {
|
|
|
|
char = " ",
|
|
|
|
sub = "█",
|
|
|
|
show_leading = false,
|
|
|
|
show_middle = false,
|
|
|
|
show_trailing = true,
|
|
|
|
trailing_color = style.error,
|
|
|
|
trailing_error = true
|
|
|
|
})
|
|
|
|
elseif found ~= nil and not enabled then
|
|
|
|
table.remove(substitutions, found)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
}
|
2022-04-01 18:10:22 +02:00
|
|
|
}, config.plugins.drawwhitespace)
|
|
|
|
|
2022-06-11 06:30:13 +02:00
|
|
|
|
2022-06-16 00:03:25 +02:00
|
|
|
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
|
2022-06-11 06:30:13 +02:00
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
|
|
|
2022-04-01 18:10:22 +02:00
|
|
|
local function get_option(substitution, option)
|
|
|
|
if substitution[option] == nil then
|
|
|
|
return config.plugins.drawwhitespace[option]
|
|
|
|
end
|
|
|
|
return substitution[option]
|
|
|
|
end
|
2021-09-11 04:22:30 +02:00
|
|
|
|
2022-04-01 18:10:22 +02:00
|
|
|
local draw_line_text = DocView.draw_line_text
|
2021-09-11 04:22:30 +02:00
|
|
|
function DocView:draw_line_text(idx, x, y)
|
2022-06-02 05:07:31 +02:00
|
|
|
if
|
|
|
|
not config.plugins.drawwhitespace.enabled
|
|
|
|
or
|
|
|
|
getmetatable(self) ~= DocView
|
|
|
|
then
|
|
|
|
return draw_line_text(self, idx, x, y)
|
|
|
|
end
|
|
|
|
|
2021-11-24 00:42:01 +01:00
|
|
|
local font = (self:get_font() or style.syntax_fonts["whitespace"] or style.syntax_fonts["comment"])
|
2022-06-25 00:09:14 +02:00
|
|
|
local font_size = font:get_size()
|
2022-07-15 06:52:50 +02:00
|
|
|
local _, indent_size = self.doc:get_indent_info()
|
2022-04-01 18:10:22 +02:00
|
|
|
|
2022-06-16 00:03:25 +02:00
|
|
|
reset_cache_if_needed()
|
2022-06-11 06:30:13 +02:00
|
|
|
if
|
|
|
|
not ws_cache[self.doc.highlighter]
|
|
|
|
or ws_cache[self.doc.highlighter].font ~= font
|
2022-06-25 00:09:14 +02:00
|
|
|
or ws_cache[self.doc.highlighter].font_size ~= font_size
|
2022-07-15 06:52:50 +02:00
|
|
|
or ws_cache[self.doc.highlighter].indent_size ~= indent_size
|
2022-06-11 06:30:13 +02:00
|
|
|
then
|
2022-06-25 00:09:14 +02:00
|
|
|
ws_cache[self.doc.highlighter] =
|
|
|
|
setmetatable(
|
2022-07-15 06:52:50 +02:00
|
|
|
{ font = font, font_size = font_size, indent_size = indent_size },
|
2022-06-25 00:09:14 +02:00
|
|
|
{ __mode = "k" }
|
|
|
|
)
|
2022-06-11 06:30:13 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
if not ws_cache[self.doc.highlighter][idx] then -- need to cache line
|
|
|
|
local cache = {}
|
|
|
|
|
|
|
|
local tx
|
|
|
|
local text = self.doc.lines[idx]
|
|
|
|
|
|
|
|
for _, substitution in pairs(config.plugins.drawwhitespace.substitutions) do
|
|
|
|
local char = substitution.char
|
|
|
|
local sub = substitution.sub
|
|
|
|
local offset = 1
|
|
|
|
|
|
|
|
local show_leading = get_option(substitution, "show_leading")
|
|
|
|
local show_middle = get_option(substitution, "show_middle")
|
|
|
|
local show_trailing = get_option(substitution, "show_trailing")
|
|
|
|
|
|
|
|
local show_middle_min = get_option(substitution, "show_middle_min")
|
|
|
|
|
|
|
|
local base_color = get_option(substitution, "color")
|
|
|
|
local leading_color = get_option(substitution, "leading_color") or base_color
|
|
|
|
local middle_color = get_option(substitution, "middle_color") or base_color
|
|
|
|
local trailing_color = get_option(substitution, "trailing_color") or base_color
|
|
|
|
|
|
|
|
local pattern = char.."+"
|
|
|
|
while true do
|
|
|
|
local s, e = text:find(pattern, offset)
|
|
|
|
if not s then break end
|
|
|
|
|
|
|
|
tx = self:get_col_x_offset(idx, s)
|
|
|
|
|
|
|
|
local color = base_color
|
|
|
|
local draw = false
|
|
|
|
|
2022-10-21 19:53:42 +02:00
|
|
|
if e >= #text - 1 then
|
2022-06-11 06:30:13 +02:00
|
|
|
draw = show_trailing
|
|
|
|
color = trailing_color
|
|
|
|
elseif s == 1 then
|
|
|
|
draw = show_leading
|
|
|
|
color = leading_color
|
2022-04-01 18:10:22 +02:00
|
|
|
else
|
2022-06-11 06:30:13 +02:00
|
|
|
draw = show_middle and (e - s + 1 >= show_middle_min)
|
|
|
|
color = middle_color
|
2022-04-01 18:10:22 +02:00
|
|
|
end
|
2022-06-11 06:30:13 +02:00
|
|
|
|
|
|
|
if draw then
|
|
|
|
local last_cache_idx = #cache
|
|
|
|
-- We need to draw tabs one at a time because they might have a
|
|
|
|
-- different size than the substituting character.
|
|
|
|
-- This also applies to any other char if we use non-monospace fonts
|
|
|
|
-- but we ignore this case for now.
|
|
|
|
if char == "\t" then
|
|
|
|
for i = s,e do
|
|
|
|
tx = self:get_col_x_offset(idx, i)
|
|
|
|
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
|
|
|
|
else
|
|
|
|
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
|
|
|
|
offset = e + 1
|
2022-04-01 18:10:22 +02:00
|
|
|
end
|
2022-06-11 06:30:13 +02:00
|
|
|
end
|
|
|
|
ws_cache[self.doc.highlighter][idx] = cache
|
|
|
|
end
|
2022-04-01 18:10:22 +02:00
|
|
|
|
2022-06-11 06:30:13 +02:00
|
|
|
-- draw from cache
|
|
|
|
local x1, _, x2, _ = self:get_content_bounds()
|
|
|
|
x1 = x1 + x
|
|
|
|
x2 = x2 + x
|
2022-06-20 19:56:53 +02:00
|
|
|
local ty = y + self:get_line_text_y_offset()
|
2022-06-11 06:30:13 +02:00
|
|
|
local cache = ws_cache[self.doc.highlighter][idx]
|
|
|
|
for i=1,#cache,4 do
|
|
|
|
local tx = cache[i + 1] + x
|
|
|
|
local tw = cache[i + 2]
|
2023-01-30 17:43:26 +01:00
|
|
|
local sub = cache[i]
|
|
|
|
local color = cache[i + 3]
|
|
|
|
local partials = {}
|
|
|
|
if config.plugins.drawwhitespace.show_selected_only and self.doc:has_any_selection() then
|
|
|
|
for _, l1, c1, l2, c2 in self.doc:get_selections(true) do
|
|
|
|
if idx > l1 and idx < l2 then
|
|
|
|
-- Between selection lines, so everything is selected
|
|
|
|
table.insert(partials, false)
|
|
|
|
elseif idx == l1 and idx == l2 then
|
|
|
|
-- Both ends of the selection are on the same line
|
|
|
|
local _x1 = math.max(cache[i + 1], self:get_col_x_offset(idx, c1))
|
|
|
|
local _x2 = math.min((cache[i + 1] + tw), self:get_col_x_offset(idx, c2))
|
|
|
|
if _x1 < _x2 then
|
|
|
|
table.insert(partials, {_x1 + x, 0, _x2 - _x1, math.huge})
|
|
|
|
end
|
|
|
|
elseif idx >= l1 and idx <= l2 then
|
|
|
|
-- On one of the selection ends
|
|
|
|
if idx == l1 then -- Start of the selection
|
|
|
|
local _x = math.max(cache[i + 1], self:get_col_x_offset(idx, c1))
|
|
|
|
table.insert(partials, {_x + x, 0, math.huge, math.huge})
|
|
|
|
else -- End of the selection
|
|
|
|
local _x = math.min((cache[i + 1] + tw), self:get_col_x_offset(idx, c2))
|
|
|
|
table.insert(partials, {0, 0, _x + x, math.huge})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if #partials == 0 and not config.plugins.drawwhitespace.show_selected_only then
|
|
|
|
renderer.draw_text(font, sub, tx, ty, color)
|
|
|
|
else
|
|
|
|
for _, p in pairs(partials) do
|
|
|
|
if p then core.push_clip_rect(table.unpack(p)) end
|
|
|
|
renderer.draw_text(font, sub, tx, ty, color)
|
|
|
|
if p then core.pop_clip_rect() end
|
2022-10-30 02:54:30 +01:00
|
|
|
end
|
2022-04-01 18:10:22 +02:00
|
|
|
end
|
2021-09-11 04:22:30 +02:00
|
|
|
end
|
2022-04-01 18:10:22 +02:00
|
|
|
|
2022-06-03 09:16:18 +02:00
|
|
|
return draw_line_text(self, idx, x, y)
|
2021-09-11 04:22:30 +02:00
|
|
|
end
|