-- rainbow-delimiters.lua -- ============================================================================ -- Colorize nested delimiters (parentheses, brackets, braces) with different -- colors based on nesting depth. Similar to Emacs rainbow-delimiters. -- ============================================================================ local rainbow = {} -- Configuration rainbow.config = { enabled = true, max_depth = 9, -- Number of colors to cycle through delimiters = { ["("] = ")", ["["] = "]", ["{"] = "}", }, } -- Default rainbow colors (can be customized per theme) rainbow.colors = { "#ff6b6b", -- Red "#ffa94d", -- Orange "#ffd43b", -- Yellow "#69db7c", -- Green "#38d9a9", -- Teal "#4dabf7", -- Blue "#748ffc", -- Indigo "#da77f2", -- Violet "#f783ac", -- Pink } -- Alternative color schemes rainbow.presets = { default = { "#ff6b6b", "#ffa94d", "#ffd43b", "#69db7c", "#38d9a9", "#4dabf7", "#748ffc", "#da77f2", "#f783ac", }, pastel = { "#ffb3ba", "#ffdfba", "#ffffba", "#baffc9", "#bae1ff", "#d4baff", "#ffbae1", "#baffff", "#e1ffba", }, neon = { "#ff0080", "#ff8000", "#ffff00", "#00ff00", "#00ffff", "#0080ff", "#8000ff", "#ff00ff", "#ff0040", }, monochrome = { "#ffffff", "#e0e0e0", "#c0c0c0", "#a0a0a0", "#808080", "#606060", "#404040", "#303030", "#202020", }, nord = { "#bf616a", "#d08770", "#ebcb8b", "#a3be8c", "#88c0d0", "#81a1c1", "#5e81ac", "#b48ead", "#bf616a", }, dracula = { "#ff5555", "#ffb86c", "#f1fa8c", "#50fa7b", "#8be9fd", "#bd93f9", "#ff79c6", "#6272a4", "#ff5555", }, } -- State rainbow.active = true -- Setup faces for each depth level function rainbow.setup_faces() for i, color in ipairs(rainbow.colors) do local face_name = "rainbow-delimiters-depth-" .. i -- Use lumacs.face helper which handles hex conversion local attrs = lumacs.face({ foreground = color, weight = "bold", }) if editor.theme_manager then local theme = editor.theme_manager:active_theme() if theme then theme:set_face(face_name, attrs) end end end end -- Get face name for a given depth function rainbow.get_face(depth) local idx = ((depth - 1) % #rainbow.colors) + 1 return "rainbow-delimiters-depth-" .. idx end -- Check if char is an opener function rainbow.is_opener(char) return rainbow.config.delimiters[char] ~= nil end -- Check if char is a closer function rainbow.is_closer(char) for opener, closer in pairs(rainbow.config.delimiters) do if closer == char then return true, opener end end return false, nil end -- Highlight delimiters in the current buffer function rainbow.highlight_buffer() if not rainbow.active or not rainbow.config.enabled then return end local buf = editor.buffer local line_count = buf:line_count() local depth = 0 local delimiter_positions = {} -- First pass: find all delimiters and calculate their depths for line_num = 0, line_count - 1 do local line = buf:line(line_num) local in_string = false local string_char = nil local escaped = false for col = 0, #line - 1 do local char = line:sub(col + 1, col + 1) -- Handle escape sequences if escaped then escaped = false elseif char == "\\" then escaped = true -- Handle strings elseif not in_string and (char == '"' or char == "'") then in_string = true string_char = char elseif in_string and char == string_char then in_string = false string_char = nil -- Handle delimiters (outside strings) elseif not in_string then if rainbow.is_opener(char) then depth = depth + 1 table.insert(delimiter_positions, { line = line_num, col = col, depth = depth, char = char, }) elseif rainbow.is_closer(char) then table.insert(delimiter_positions, { line = line_num, col = col, depth = depth, char = char, }) depth = math.max(0, depth - 1) end end end end -- Second pass: apply styles for _, pos in ipairs(delimiter_positions) do local face = rainbow.get_face(pos.depth) local range = lumacs.Range( lumacs.Position(pos.line, pos.col), lumacs.Position(pos.line, pos.col + 1) ) local attr = lumacs.TextAttribute(face) buf:set_style(range, attr) end end -- Toggle rainbow delimiters function rainbow.toggle() rainbow.active = not rainbow.active if rainbow.active then rainbow.highlight_buffer() editor:message("Rainbow delimiters enabled") else -- Clear rainbow styles editor.buffer:clear_styles() editor:message("Rainbow delimiters disabled") end end -- Set color preset function rainbow.set_preset(name) local preset = rainbow.presets[name] if not preset then editor:message("Unknown preset: " .. name, "warning") return end rainbow.colors = preset rainbow.setup_faces() if rainbow.active then rainbow.highlight_buffer() end editor:message("Rainbow preset: " .. name) end -- Register commands editor:register_command("rainbow-delimiters-mode", "Toggle rainbow delimiter coloring", function(args) rainbow.toggle() return {success = true} end, {"rainbow"}) editor:register_command("rainbow-preset", "Set rainbow delimiter color preset", function(args) if #args == 0 then local presets = {} for name, _ in pairs(rainbow.presets) do table.insert(presets, name) end table.sort(presets) return {success = false, message = "Available presets: " .. table.concat(presets, ", ")} end rainbow.set_preset(args[1]) return {success = true} end, {}, true, "s") editor:register_command("rainbow-highlight", "Re-apply rainbow highlighting to current buffer", function(args) rainbow.highlight_buffer() return {success = true, message = "Rainbow highlighting applied"} end) -- Define minor mode lumacs.define_minor_mode("rainbow-delimiters-mode", { lighter = "Rainbow", global = false, setup = function() rainbow.active = true rainbow.setup_faces() rainbow.highlight_buffer() editor:message("Rainbow delimiters enabled") end, cleanup = function() rainbow.active = false editor.buffer:clear_styles() editor:message("Rainbow delimiters disabled") end }) -- Initialize faces rainbow.setup_faces() -- Store in lumacs namespace lumacs.rainbow_delimiters = rainbow print("[rainbow-delimiters] Package loaded") return rainbow