@ 261105.1

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

Changes to plugins/rainbow_brackets.lua. All files

plugins/rainbow_brackets.lua

new file mode 100644@@ -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 },+}