266 lines
7.6 KiB
Lua
266 lines
7.6 KiB
Lua
--- mod-version:3
|
|
local core = require "core"
|
|
local style = require "core.style"
|
|
local command = require "core.command"
|
|
local keymap = require "core.keymap"
|
|
local DocView = require "core.docview"
|
|
local config = require "core.config"
|
|
local common = require "core.common"
|
|
|
|
-- Colors can be configured as follows:
|
|
-- underline color = `style.bracketmatch_color`
|
|
-- bracket color = `style.bracketmatch_char_color`
|
|
-- background color = `style.bracketmatch_block_color`
|
|
-- frame color = `style.bracketmatch_frame_color`
|
|
|
|
config.plugins.bracketmatch = common.merge({
|
|
-- highlight the current bracket too
|
|
highlight_both = true,
|
|
-- can be "underline", "block", "frame", "none"
|
|
style = "underline",
|
|
-- color the bracket
|
|
color_char = false,
|
|
-- the size of the lines used in "underline" and "frame"
|
|
line_size = math.ceil(1 * SCALE),
|
|
-- The config specification used by the settings gui
|
|
config_spec = {
|
|
name = "Bracket Match",
|
|
{
|
|
label = "Highlight Both",
|
|
description = "Highlight the current bracket too.",
|
|
path = "highlight_both",
|
|
type = "toggle",
|
|
default = true
|
|
},
|
|
{
|
|
label = "Style",
|
|
description = "The visual indicator for pair brackets.",
|
|
path = "style",
|
|
type = "selection",
|
|
default = "underline",
|
|
values = {
|
|
{"Underline", "underline"},
|
|
{"Block", "block"},
|
|
{"Frame", "frame"},
|
|
{"None", "none"}
|
|
}
|
|
},
|
|
{
|
|
label = "Colorize Bracket",
|
|
description = "Change the color of the matching brackets.",
|
|
path = "color_char",
|
|
type = "toggle",
|
|
default = false
|
|
},
|
|
{
|
|
label = "Line Size",
|
|
description = "Height of the underline on matching brackets.",
|
|
path = "line_size",
|
|
type = "number",
|
|
default = 1,
|
|
min = 1,
|
|
step = 1,
|
|
get_value = function(value)
|
|
return math.floor(value / SCALE)
|
|
end,
|
|
set_value = function(value)
|
|
return math.ceil(value * SCALE)
|
|
end
|
|
}
|
|
}
|
|
}, config.plugins.bracketmatch)
|
|
|
|
|
|
local bracket_maps = {
|
|
-- [ ] ( ) { }
|
|
{ [91] = 93, [40] = 41, [123] = 125, direction = 1 },
|
|
-- ] [ ) ( } {
|
|
{ [93] = 91, [41] = 40, [125] = 123, direction = -1 },
|
|
}
|
|
|
|
|
|
local function get_token_at(doc, line, col)
|
|
local column = 0
|
|
for _,type,text in doc.highlighter:each_token(line) do
|
|
column = column + #text
|
|
if column >= col then return type, text end
|
|
end
|
|
end
|
|
|
|
|
|
local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, direction)
|
|
local end_line = line + line_limit * direction
|
|
local depth = 0
|
|
|
|
while line ~= end_line do
|
|
local byte = doc.lines[line]:byte(col)
|
|
if byte == open_byte and get_token_at(doc, line, col) ~= "comment" then
|
|
depth = depth + 1
|
|
elseif byte == close_byte and get_token_at(doc, line, col) ~= "comment" then
|
|
depth = depth - 1
|
|
if depth == 0 then return line, col end
|
|
end
|
|
|
|
local prev_line, prev_col = line, col
|
|
line, col = doc:position_offset(line, col, direction)
|
|
if line == prev_line and col == prev_col then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local state = {}
|
|
local select_adj = 0
|
|
|
|
local function update_state(line_limit)
|
|
line_limit = line_limit or math.huge
|
|
|
|
-- reset if we don't have a document (eg. DocView isn't focused)
|
|
local doc = core.active_view.doc
|
|
if not doc then
|
|
state = {}
|
|
return
|
|
end
|
|
|
|
-- early exit if nothing has changed since the last call
|
|
local line, col = doc:get_selection()
|
|
local change_id = doc:get_change_id()
|
|
if state.doc == doc and state.line == line and state.col == col
|
|
and state.change_id == change_id and state.limit == line_limit then
|
|
return
|
|
end
|
|
|
|
-- find matching bracket if we're on a bracket
|
|
local line2, col2
|
|
for _, map in ipairs(bracket_maps) do
|
|
for i = 0, -1, -1 do
|
|
local line, col = doc:position_offset(line, col, i)
|
|
local open = doc.lines[line]:byte(col)
|
|
local close = map[open]
|
|
if close and get_token_at(doc, line, col) ~= "comment" then
|
|
-- i == 0 if the cursor is on the left side of a bracket (or -1 when on right)
|
|
select_adj = i + 1 -- if i == 0 then select_adj = 1 else select_adj = 0 end
|
|
line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.direction)
|
|
goto found
|
|
end
|
|
end
|
|
end
|
|
::found::
|
|
|
|
-- update
|
|
state = {
|
|
change_id = change_id,
|
|
doc = doc,
|
|
line = line,
|
|
col = col,
|
|
line2 = line2,
|
|
col2 = col2,
|
|
limit = line_limit,
|
|
}
|
|
end
|
|
|
|
|
|
local update = DocView.update
|
|
|
|
function DocView:update(...)
|
|
update(self, ...)
|
|
update_state(100)
|
|
end
|
|
|
|
|
|
local function redraw_char(dv, x, y, line, col, bg_color, char_color)
|
|
local x1 = x + dv:get_col_x_offset(line, col)
|
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
|
local lh = dv:get_line_height()
|
|
local token = get_token_at(dv.doc, line, col)
|
|
if not char_color then
|
|
char_color = style.syntax[token]
|
|
end
|
|
local font = style.syntax_fonts[token] or dv:get_font()
|
|
local char = string.sub(dv.doc.lines[line], col, col)
|
|
|
|
if not bg_color then
|
|
-- redraw background
|
|
core.push_clip_rect(x1, y, x2 - x1, lh)
|
|
local dlt = DocView.draw_line_text
|
|
DocView.draw_line_text = function() end
|
|
dv:draw_line_body(line, x, y)
|
|
DocView.draw_line_text = dlt
|
|
core.pop_clip_rect()
|
|
else
|
|
renderer.draw_rect(x1, y, x2 - x1, lh, bg_color)
|
|
end
|
|
renderer.draw_text(font, char, x1, y + dv:get_line_text_y_offset(), char_color)
|
|
end
|
|
|
|
|
|
local function draw_decoration(dv, x, y, line, col)
|
|
local conf = config.plugins.bracketmatch
|
|
local color = style.bracketmatch_color or style.syntax["function"]
|
|
local char_color = style.bracketmatch_char_color
|
|
or (conf.style == "block" and style.background or style.syntax["keyword"])
|
|
local block_color = style.bracketmatch_block_color or style.line_number2
|
|
local frame_color = style.bracketmatch_frame_color or style.line_number2
|
|
|
|
local h = conf.line_size
|
|
|
|
if conf.color_char or conf.style == "block" then
|
|
redraw_char(dv, x, y, line, col,
|
|
conf.style == "block" and block_color, conf.color_char and char_color)
|
|
end
|
|
if conf.style == "underline" then
|
|
local x1 = x + dv:get_col_x_offset(line, col)
|
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
|
local lh = dv:get_line_height()
|
|
|
|
renderer.draw_rect(x1, y + lh - h, x2 - x1, h, color)
|
|
elseif conf.style == "frame" then
|
|
local x1 = x + dv:get_col_x_offset(line, col)
|
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
|
local lh = dv:get_line_height()
|
|
|
|
renderer.draw_rect(x1, y + lh - h, x2 - x1, h, frame_color)
|
|
renderer.draw_rect(x1, y, x2 - x1, h, frame_color)
|
|
renderer.draw_rect(x1, y, h, lh, frame_color)
|
|
renderer.draw_rect(x2, y, h, lh, frame_color)
|
|
end
|
|
end
|
|
|
|
|
|
local draw_line_text = DocView.draw_line_text
|
|
|
|
function DocView:draw_line_text(line, x, y)
|
|
local lh = draw_line_text(self, line, x, y)
|
|
if self.doc == state.doc and state.line2 then
|
|
if line == state.line2 then
|
|
draw_decoration(self, x, y, line, state.col2)
|
|
end
|
|
if line == state.line and config.plugins.bracketmatch.highlight_both then
|
|
draw_decoration(self, x, y, line, state.col + select_adj - 1)
|
|
end
|
|
end
|
|
return lh
|
|
end
|
|
|
|
|
|
command.add("core.docview", {
|
|
["bracket-match:move-to-matching"] = function(dv)
|
|
update_state()
|
|
if state.line2 then
|
|
dv.doc:set_selection(state.line2, state.col2)
|
|
end
|
|
end,
|
|
["bracket-match:select-to-matching"] = function(dv)
|
|
update_state()
|
|
if state.line2 then
|
|
dv.doc:set_selection(state.line, state.col, state.line2, state.col2 + select_adj)
|
|
end
|
|
end,
|
|
})
|
|
|
|
keymap.add {
|
|
["ctrl+m"] = "bracket-match:move-to-matching",
|
|
["ctrl+shift+m"] = "bracket-match:select-to-matching",
|
|
}
|