261030.7 · b24aecce0ec5d7f9d21afe58d06f6f85481181b8
add rainbow brackets plug-in
- colours (), [] and {} by nesting depth, unmatched closers in red
- palette chosen from theme canvas brightness, overridable via --plug-in-param
- supports HTML, XHTML, SVG, Pango, BBCode, Truecolor and xterm256 output
- chains to previously defined Decorate functions
reading declarations
@@ -0,0 +1,177 @@+--[[+Rainbow brackets: colours (), [] and {} by their nesting depth, like+VSCode's bracket pair colorization or Vim's rainbow plug-ins.++Closing brackets without a matching opening bracket are marked in red.++Optional plug-in parameter: comma separated list of hex colours used for the+nesting levels, e.g. --plug-in-param="#ffd700,#da70d6,#179fff"+Without parameter, a palette is chosen based on the theme's canvas colour.++Supported output formats: HTML, XHTML, SVG, Pango, BBCode, Truecolor and+xterm256 escape sequences.++Can be combined with other plug-ins which define a Decorate function+(e.g. color_swatches.lua).++Note: highlight copies the Decorate function without its upvalues, so all+state is kept in the global table RainbowBrackets instead of local variables.+]]++Description="Colours brackets by their nesting depth (HTML, SVG, Pango, BBCode, Truecolor, xterm256)."++Categories = {"format", "html", "svg", "pango", "bbcode", "truecolor", "xterm256", "usability" }++function syntaxUpdate(desc)++ if HL_OUTPUT ~= HL_FORMAT_HTML and HL_OUTPUT ~= HL_FORMAT_XHTML+ and HL_OUTPUT ~= HL_FORMAT_SVG and HL_OUTPUT ~= HL_FORMAT_PANGO+ and HL_OUTPUT ~= HL_FORMAT_BBCODE+ and HL_OUTPUT ~= HL_FORMAT_TRUECOLOR and HL_OUTPUT ~= HL_FORMAT_XTERM256 then+ return+ end++ -- do not chain twice if the chunk is evaluated again in the same state+ if RainbowBrackets then+ return+ end++ RainbowBrackets = {+ -- VSCode default bracket pair colours+ darkPalette = { "#ffd700", "#da70d6", "#179fff" },+ lightPalette = { "#0431fa", "#319331", "#7b3814" },+ unmatchedColour = "#ff1212",++ openers = { ["("]=")", ["["]="]", ["{"]="}" },+ closers = { [")"]=true, ["]"]=true, ["}"]=true },+ stack = {}, -- currently open brackets+ palette = nil, -- resolved lazily, after the theme chunk stored its value++ prevDecorate = Decorate,+ }++ function RainbowBrackets.parsePalette(param)+ local p = {}+ for c in string.gmatch(param, "#?%x%x%x%x%x%x") do+ if string.sub(c, 1, 1) ~= "#" then c = "#"..c end+ p[#p+1] = string.lower(c)+ end+ return p+ end++ function RainbowBrackets.getPalette()+ local rb = RainbowBrackets+ if rb.palette then return rb.palette end+ if HL_PLUGIN_PARAM and #HL_PLUGIN_PARAM > 0 then+ rb.palette = rb.parsePalette(HL_PLUGIN_PARAM)+ end+ if not rb.palette or #rb.palette == 0 then+ rb.palette = (StoreValue("rbLightCanvas") == "1") and rb.lightPalette or rb.darkPalette+ end+ return rb.palette+ end++ function RainbowBrackets.colourize(token, colour)+ local r = tonumber(string.sub(colour, 2, 3), 16)+ local g = tonumber(string.sub(colour, 4, 5), 16)+ local b = tonumber(string.sub(colour, 6, 7), 16)++ if HL_OUTPUT == HL_FORMAT_HTML or HL_OUTPUT == HL_FORMAT_XHTML then+ return '<span style="color:'..colour..'">'..token..'</span>'+ elseif HL_OUTPUT == HL_FORMAT_SVG then+ return '<tspan fill="'..colour..'">'..token..'</tspan>'+ elseif HL_OUTPUT == HL_FORMAT_PANGO then+ return '<span foreground="'..colour..'">'..token..'</span>'+ elseif HL_OUTPUT == HL_FORMAT_BBCODE then+ return '[color='..colour..']'..token..'[/color]'+ elseif HL_OUTPUT == HL_FORMAT_TRUECOLOR then+ return string.format("\x1B[38;2;%d;%d;%dm", r, g, b)..token+ elseif HL_OUTPUT == HL_FORMAT_XTERM256 then+ local idx = 16 + 36 * math.floor(r * 5 / 255 + 0.5)+ + 6 * math.floor(g * 5 / 255 + 0.5)+ + math.floor(b * 5 / 255 + 0.5)+ return "\x1B[38;5;"..idx.."m"..token+ end+ return token+ end++ function RainbowBrackets.colourForDepth(depth)+ local p = RainbowBrackets.getPalette()+ return p[(depth - 1) % #p + 1]+ end++ -- returns the coloured brackets or nil if the token is not a bracket operator+ function RainbowBrackets.colourBrackets(token, state)+ local rb = RainbowBrackets++ if state ~= HL_OPERATOR then+ return nil+ end++ -- only handle tokens which consist of brackets exclusively, so that no+ -- other operator characters need to be escaped or recoloured+ if string.find(token, "^[%(%)%[%]{}]+$") == nil then+ return nil+ end++ local out = {}+ for ch in string.gmatch(token, ".") do+ if rb.openers[ch] then+ rb.stack[#rb.stack+1] = ch+ out[#out+1] = rb.colourize(ch, rb.colourForDepth(#rb.stack))+ else+ local top = rb.stack[#rb.stack]+ if top and rb.openers[top] == ch then+ out[#out+1] = rb.colourize(ch, rb.colourForDepth(#rb.stack))+ rb.stack[#rb.stack] = nil+ else+ out[#out+1] = rb.colourize(ch, rb.unmatchedColour)+ end+ end+ end+ return table.concat(out)+ end++ function Decorate(token, state, kwGroupID, lineHasStmt, lineno, column)+ local rb = RainbowBrackets+ local prevRes+ if rb.prevDecorate then+ prevRes = rb.prevDecorate(token, state, kwGroupID, lineHasStmt, lineno, column)+ end++ local coloured = rb.colourBrackets(token, state)+ if coloured then+ if prevRes == nil then+ return coloured+ end+ -- keep additions of other plug-ins which were appended to the token+ if string.sub(prevRes, 1, #token) == token then+ return coloured..string.sub(prevRes, #token + 1)+ end+ end++ if prevRes then+ return prevRes+ end+ end+end++function themeUpdate(desc)+ local canvas = Canvas and Canvas.Colour or ""+ local light = "0"+ if string.match(canvas, "^#%x%x%x%x%x%x") then+ local r = tonumber(string.sub(canvas, 2, 3), 16)+ local g = tonumber(string.sub(canvas, 4, 5), 16)+ local b = tonumber(string.sub(canvas, 6, 7), 16)+ -- perceived luminance (ITU-R BT.601)+ if (0.299 * r + 0.587 * g + 0.114 * b) > 128 then+ light = "1"+ end+ end+ StoreValue("rbLightCanvas", light)+end++Plugins={+ { Type="lang", Chunk=syntaxUpdate },+ { Type="theme", Chunk=themeUpdate },+}