init.lua 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. -- rainbow-delimiters.lua
  2. -- ============================================================================
  3. -- Colorize nested delimiters (parentheses, brackets, braces) with different
  4. -- colors based on nesting depth. Similar to Emacs rainbow-delimiters.
  5. -- ============================================================================
  6. local rainbow = {}
  7. -- Configuration
  8. rainbow.config = {
  9. enabled = true,
  10. max_depth = 9, -- Number of colors to cycle through
  11. delimiters = {
  12. ["("] = ")",
  13. ["["] = "]",
  14. ["{"] = "}",
  15. },
  16. }
  17. -- Default rainbow colors (can be customized per theme)
  18. rainbow.colors = {
  19. "#ff6b6b", -- Red
  20. "#ffa94d", -- Orange
  21. "#ffd43b", -- Yellow
  22. "#69db7c", -- Green
  23. "#38d9a9", -- Teal
  24. "#4dabf7", -- Blue
  25. "#748ffc", -- Indigo
  26. "#da77f2", -- Violet
  27. "#f783ac", -- Pink
  28. }
  29. -- Alternative color schemes
  30. rainbow.presets = {
  31. default = {
  32. "#ff6b6b", "#ffa94d", "#ffd43b", "#69db7c",
  33. "#38d9a9", "#4dabf7", "#748ffc", "#da77f2", "#f783ac",
  34. },
  35. pastel = {
  36. "#ffb3ba", "#ffdfba", "#ffffba", "#baffc9",
  37. "#bae1ff", "#d4baff", "#ffbae1", "#baffff", "#e1ffba",
  38. },
  39. neon = {
  40. "#ff0080", "#ff8000", "#ffff00", "#00ff00",
  41. "#00ffff", "#0080ff", "#8000ff", "#ff00ff", "#ff0040",
  42. },
  43. monochrome = {
  44. "#ffffff", "#e0e0e0", "#c0c0c0", "#a0a0a0",
  45. "#808080", "#606060", "#404040", "#303030", "#202020",
  46. },
  47. nord = {
  48. "#bf616a", "#d08770", "#ebcb8b", "#a3be8c",
  49. "#88c0d0", "#81a1c1", "#5e81ac", "#b48ead", "#bf616a",
  50. },
  51. dracula = {
  52. "#ff5555", "#ffb86c", "#f1fa8c", "#50fa7b",
  53. "#8be9fd", "#bd93f9", "#ff79c6", "#6272a4", "#ff5555",
  54. },
  55. }
  56. -- State
  57. rainbow.active = true
  58. -- Setup faces for each depth level
  59. function rainbow.setup_faces()
  60. for i, color in ipairs(rainbow.colors) do
  61. local face_name = "rainbow-delimiters-depth-" .. i
  62. -- Use lumacs.face helper which handles hex conversion
  63. local attrs = lumacs.face({
  64. foreground = color,
  65. weight = "bold",
  66. })
  67. if editor.theme_manager then
  68. local theme = editor.theme_manager:active_theme()
  69. if theme then
  70. theme:set_face(face_name, attrs)
  71. end
  72. end
  73. end
  74. end
  75. -- Get face name for a given depth
  76. function rainbow.get_face(depth)
  77. local idx = ((depth - 1) % #rainbow.colors) + 1
  78. return "rainbow-delimiters-depth-" .. idx
  79. end
  80. -- Check if char is an opener
  81. function rainbow.is_opener(char)
  82. return rainbow.config.delimiters[char] ~= nil
  83. end
  84. -- Check if char is a closer
  85. function rainbow.is_closer(char)
  86. for opener, closer in pairs(rainbow.config.delimiters) do
  87. if closer == char then
  88. return true, opener
  89. end
  90. end
  91. return false, nil
  92. end
  93. -- Highlight delimiters in the current buffer
  94. function rainbow.highlight_buffer()
  95. if not rainbow.active or not rainbow.config.enabled then
  96. return
  97. end
  98. local buf = editor.buffer
  99. local line_count = buf:line_count()
  100. local depth = 0
  101. local delimiter_positions = {}
  102. -- First pass: find all delimiters and calculate their depths
  103. for line_num = 0, line_count - 1 do
  104. local line = buf:line(line_num)
  105. local in_string = false
  106. local string_char = nil
  107. local escaped = false
  108. for col = 0, #line - 1 do
  109. local char = line:sub(col + 1, col + 1)
  110. -- Handle escape sequences
  111. if escaped then
  112. escaped = false
  113. elseif char == "\\" then
  114. escaped = true
  115. -- Handle strings
  116. elseif not in_string and (char == '"' or char == "'") then
  117. in_string = true
  118. string_char = char
  119. elseif in_string and char == string_char then
  120. in_string = false
  121. string_char = nil
  122. -- Handle delimiters (outside strings)
  123. elseif not in_string then
  124. if rainbow.is_opener(char) then
  125. depth = depth + 1
  126. table.insert(delimiter_positions, {
  127. line = line_num,
  128. col = col,
  129. depth = depth,
  130. char = char,
  131. })
  132. elseif rainbow.is_closer(char) then
  133. table.insert(delimiter_positions, {
  134. line = line_num,
  135. col = col,
  136. depth = depth,
  137. char = char,
  138. })
  139. depth = math.max(0, depth - 1)
  140. end
  141. end
  142. end
  143. end
  144. -- Second pass: apply styles
  145. for _, pos in ipairs(delimiter_positions) do
  146. local face = rainbow.get_face(pos.depth)
  147. local range = lumacs.Range(
  148. lumacs.Position(pos.line, pos.col),
  149. lumacs.Position(pos.line, pos.col + 1)
  150. )
  151. local attr = lumacs.TextAttribute(face)
  152. buf:set_style(range, attr)
  153. end
  154. end
  155. -- Toggle rainbow delimiters
  156. function rainbow.toggle()
  157. rainbow.active = not rainbow.active
  158. if rainbow.active then
  159. rainbow.highlight_buffer()
  160. editor:message("Rainbow delimiters enabled")
  161. else
  162. -- Clear rainbow styles
  163. editor.buffer:clear_styles()
  164. editor:message("Rainbow delimiters disabled")
  165. end
  166. end
  167. -- Set color preset
  168. function rainbow.set_preset(name)
  169. local preset = rainbow.presets[name]
  170. if not preset then
  171. editor:message("Unknown preset: " .. name, "warning")
  172. return
  173. end
  174. rainbow.colors = preset
  175. rainbow.setup_faces()
  176. if rainbow.active then
  177. rainbow.highlight_buffer()
  178. end
  179. editor:message("Rainbow preset: " .. name)
  180. end
  181. -- Register commands
  182. editor:register_command("rainbow-delimiters-mode", "Toggle rainbow delimiter coloring", function(args)
  183. rainbow.toggle()
  184. return {success = true}
  185. end, {"rainbow"})
  186. editor:register_command("rainbow-preset", "Set rainbow delimiter color preset", function(args)
  187. if #args == 0 then
  188. local presets = {}
  189. for name, _ in pairs(rainbow.presets) do
  190. table.insert(presets, name)
  191. end
  192. table.sort(presets)
  193. return {success = false, message = "Available presets: " .. table.concat(presets, ", ")}
  194. end
  195. rainbow.set_preset(args[1])
  196. return {success = true}
  197. end, {}, true, "s")
  198. editor:register_command("rainbow-highlight", "Re-apply rainbow highlighting to current buffer", function(args)
  199. rainbow.highlight_buffer()
  200. return {success = true, message = "Rainbow highlighting applied"}
  201. end)
  202. -- Define minor mode
  203. lumacs.define_minor_mode("rainbow-delimiters-mode", {
  204. lighter = "Rainbow",
  205. global = false,
  206. setup = function()
  207. rainbow.active = true
  208. rainbow.setup_faces()
  209. rainbow.highlight_buffer()
  210. editor:message("Rainbow delimiters enabled")
  211. end,
  212. cleanup = function()
  213. rainbow.active = false
  214. editor.buffer:clear_styles()
  215. editor:message("Rainbow delimiters disabled")
  216. end
  217. })
  218. -- Initialize faces
  219. rainbow.setup_faces()
  220. -- Store in lumacs namespace
  221. lumacs.rainbow_delimiters = rainbow
  222. print("[rainbow-delimiters] Package loaded")
  223. return rainbow