c_cpp_mode.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. -- C/C++ Major Mode for Lumacs
  2. -- Define helper function for escaping Lua patterns
  3. local function escape_pattern(text)
  4. return text:gsub("([%%^%$%.%[%]%*%+%-%?%(%)])", "%%%1")
  5. end
  6. -- C/C++ Keywords
  7. local cpp_keywords = {
  8. "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
  9. "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case",
  10. "catch", "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept",
  11. "const", "consteval", "constexpr", "constinit", "const_cast", "continue",
  12. "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do",
  13. "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern",
  14. "false", "float", "for", "friend", "goto", "if", "inline", "int", "long",
  15. "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator",
  16. "or", "or_eq", "private", "protected", "public", "reflexpr", "register",
  17. "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static",
  18. "static_assert", "static_cast", "struct", "switch", "synchronized", "template",
  19. "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename",
  20. "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while",
  21. "xor", "xor_eq"
  22. }
  23. -- C/C++ Types (some overlap with keywords, but good to have separate for flexibility)
  24. local cpp_types = {
  25. "void", "char", "short", "int", "long", "float", "double", "bool",
  26. "signed", "unsigned", "class", "struct", "union", "enum", "typename",
  27. -- Standard library types commonly used
  28. "size_t", "ptrdiff_t", "int8_t", "int16_t", "int32_t", "int64_t",
  29. "uint8_t", "uint16_t", "uint32_t", "uint64_t", "string", "vector",
  30. "map", "set", "unique_ptr", "shared_ptr", "cout", "cin", "cerr", "endl" -- Common std:: things
  31. }
  32. -- C/C++ Preprocessor directives
  33. local preprocessor_directives = {
  34. "include", "define", "undef", "if", "ifdef", "ifndef", "else", "elif",
  35. "endif", "error", "pragma", "line"
  36. }
  37. -- C/C++ Operators (for potential future highlighting, regex is hard here)
  38. -- This list is mostly for reference, direct highlighting of all operators is complex
  39. local cpp_operators = {
  40. "+", "-", "*", "/", "%", "==", "!=", "<", ">", "<= ", ">=", "&&", "||", "!",
  41. "&", "|", "^", "~", "<<", ">>", "=", "+=", "-=", "*=", "/=", "%=", "&=",
  42. "|=", "^=", "<<=", ">>=", "?", ":", ".", "->", "::", "++", "--"
  43. }
  44. -- Build keyword patterns
  45. local keyword_patterns = {}
  46. for _, kw in ipairs(cpp_keywords) do
  47. table.insert(keyword_patterns, "%f[%w]" .. escape_pattern(kw) .. "%f[%W]")
  48. end
  49. local full_keyword_pattern = table.concat(keyword_patterns, "|")
  50. -- Build type patterns
  51. local type_patterns = {}
  52. for _, ty in ipairs(cpp_types) do
  53. table.insert(type_patterns, "%f[%w]" .. escape_pattern(ty) .. "%f[%W]")
  54. end
  55. local full_type_pattern = table.concat(type_patterns, "|")
  56. -- Function to perform syntax highlighting
  57. local function highlight_cpp_buffer()
  58. local buf = editor.buffer
  59. buf:clear_styles()
  60. local in_multiline_comment = false
  61. for line_num = 0, buf:line_count() - 1 do
  62. local line_text = buf:line(line_num)
  63. local line_len = #line_text
  64. -- Handle multiline comments
  65. if in_multiline_comment then
  66. local comment_end_pos = string.find(line_text, "*/", 1, true)
  67. if comment_end_pos then
  68. local range = lumacs.Range(
  69. lumacs.Position(line_num, 0),
  70. lumacs.Position(line_num, comment_end_pos + 1)
  71. )
  72. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
  73. in_multiline_comment = false
  74. else
  75. local range = lumacs.Range(
  76. lumacs.Position(line_num, 0),
  77. lumacs.Position(line_num, line_len)
  78. )
  79. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
  80. end
  81. end
  82. -- Single-line comments (//)
  83. local single_comment_pos = string.find(line_text, "//", 1, true)
  84. if single_comment_pos and not in_multiline_comment then -- only if not already in multi-line
  85. local range = lumacs.Range(
  86. lumacs.Position(line_num, single_comment_pos - 1),
  87. lumacs.Position(line_num, line_len)
  88. )
  89. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
  90. end
  91. -- Multiline comment start (/*)
  92. local multi_comment_start_pos = string.find(line_text, "/*", 1, true)
  93. if multi_comment_start_pos and not in_multiline_comment then -- only if not already in multi-line
  94. local multi_comment_end_pos = string.find(line_text, "*/", multi_comment_start_pos + 2, true)
  95. if multi_comment_end_pos then
  96. -- Single line multiline comment
  97. local range = lumacs.Range(
  98. lumacs.Position(line_num, multi_comment_start_pos - 1),
  99. lumacs.Position(line_num, multi_comment_end_pos + 1)
  100. )
  101. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
  102. else
  103. -- Multiline comment continues to next line
  104. local range = lumacs.Range(
  105. lumacs.Position(line_num, multi_comment_start_pos - 1),
  106. lumacs.Position(line_num, line_len)
  107. )
  108. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
  109. in_multiline_comment = true
  110. end
  111. end
  112. -- Strings (double and single quotes)
  113. local search_from = 1
  114. while search_from <= line_len do
  115. -- Pattern for double-quoted strings (simplified - handles basic cases)
  116. local dq_start_idx, dq_end_idx = string.find(line_text, '"[^"]*"', search_from)
  117. -- Pattern for single-quoted characters/strings (simplified)
  118. local sq_start_idx, sq_end_idx = string.find(line_text, "'[^']*'", search_from)
  119. local current_match_start, current_match_end
  120. if dq_start_idx and (not sq_start_idx or dq_start_idx < sq_start_idx) then
  121. current_match_start, current_match_end = dq_start_idx, dq_end_idx
  122. elseif sq_start_idx then
  123. current_match_start, current_match_end = sq_start_idx, sq_end_idx
  124. else
  125. break -- No more strings found on this line
  126. end
  127. local range = lumacs.Range(
  128. lumacs.Position(line_num, current_match_start - 1),
  129. lumacs.Position(line_num, current_match_end - 1)
  130. )
  131. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.String, 0))
  132. search_from = current_match_end + 1
  133. end
  134. -- Numbers
  135. -- Match integers and decimals (simplified pattern that works in Lua)
  136. for num_start_idx, num_end_idx in string.gmatch(line_text, "()%d+%.?%d*()") do
  137. local range = lumacs.Range(
  138. lumacs.Position(line_num, num_start_idx - 1),
  139. lumacs.Position(line_num, num_end_idx - 1)
  140. )
  141. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Number, 0))
  142. end
  143. -- Preprocessor directives
  144. local preprocessor_match_start, preprocessor_match_end = string.find(line_text, "^%s*#(%a+)", 1) -- find and capture directive name
  145. if preprocessor_match_start then
  146. local directive_name = string.match(line_text, "^%s*#(%a+)")
  147. for _, pp_dir in ipairs(preprocessor_directives) do
  148. if directive_name == pp_dir then
  149. local hash_start_idx = string.find(line_text, "#", 1, true)
  150. local range = lumacs.Range(
  151. lumacs.Position(line_num, hash_start_idx - 1),
  152. lumacs.Position(line_num, line_len)
  153. )
  154. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Variable, 0)) -- Using Variable for directives
  155. break -- Found and styled, move to next
  156. end
  157. end
  158. end
  159. -- Keywords - iterate through each keyword individually
  160. for _, kw in ipairs(cpp_keywords) do
  161. local search_pos = 1
  162. while search_pos <= line_len do
  163. local pattern = "%f[%w_]" .. escape_pattern(kw) .. "%f[%W_]"
  164. local kw_start, kw_end = string.find(line_text, pattern, search_pos)
  165. if not kw_start then break end
  166. local range = lumacs.Range(
  167. lumacs.Position(line_num, kw_start - 1),
  168. lumacs.Position(line_num, kw_end)
  169. )
  170. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0))
  171. search_pos = kw_end + 1
  172. end
  173. end
  174. -- Types - iterate through each type individually
  175. for _, ty in ipairs(cpp_types) do
  176. local search_pos = 1
  177. while search_pos <= line_len do
  178. local pattern = "%f[%w_]" .. escape_pattern(ty) .. "%f[%W_]"
  179. local type_start, type_end = string.find(line_text, pattern, search_pos)
  180. if not type_start then break end
  181. local range = lumacs.Range(
  182. lumacs.Position(line_num, type_start - 1),
  183. lumacs.Position(line_num, type_end)
  184. )
  185. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Type, 0))
  186. search_pos = type_end + 1
  187. end
  188. end
  189. -- Operators (simple approach: highlight some common multi-char operators)
  190. local ops_to_highlight = {"==", "!=", "<= ", ">=", "&&", "||", "++", "--", "->", "::", "<<", ">>", ".*", "->*"}
  191. for _, op in ipairs(ops_to_highlight) do
  192. local op_start_pos = 1
  193. while op_start_pos <= line_len do
  194. local pos = string.find(line_text, escape_pattern(op), op_start_pos, true)
  195. if not pos then break end
  196. local range = lumacs.Range(
  197. lumacs.Position(line_num, pos - 1),
  198. lumacs.Position(line_num, pos + #op - 1)
  199. )
  200. buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Operator, 0))
  201. op_start_pos = pos + #op
  202. end
  203. end
  204. end
  205. end
  206. -- Function to format the current buffer using clang-format
  207. local function format_buffer()
  208. local buf = editor.buffer
  209. local filepath = buf:filepath()
  210. if not filepath then
  211. editor:message("Cannot format unsaved buffer. Save the file first.")
  212. return
  213. end
  214. editor:message("Formatting buffer with clang-format...")
  215. -- Create a temporary file
  216. local temp_file = os.tmpname() .. ".cpp" -- Use .cpp extension for clang-format detection
  217. -- Write buffer content to temp file
  218. local file = io.open(temp_file, "w")
  219. if file then
  220. file:write(buf:get_all_text())
  221. file:close()
  222. else
  223. editor:message("Error: Could not create temporary file for formatting.")
  224. os.remove(temp_file) -- Try to clean up if file creation failed mid-way
  225. return
  226. end
  227. -- Execute clang-format
  228. local clang_format_cmd = string.format("clang-format -style=file -i %s", temp_file)
  229. local success, status_string, code = os.execute(clang_format_cmd) -- os.execute returns true/false, exit status string, and exit code
  230. if success then
  231. -- Read formatted content back
  232. local formatted_file = io.open(temp_file, "r")
  233. if formatted_file then
  234. local formatted_text = formatted_file:read("*all")
  235. formatted_file:close()
  236. -- Preserve cursor position
  237. local original_cursor = editor.cursor
  238. -- Replace buffer content
  239. local full_buffer_range = lumacs.Range(lumacs.Position(0,0), lumacs.Position(buf:line_count() - 1, #buf:line(buf:line_count() - 1)))
  240. buf:replace(full_buffer_range, formatted_text)
  241. -- Restore cursor position (clamp if necessary)
  242. local new_line_count = buf:line_count()
  243. local new_line = original_cursor.line
  244. local new_col = original_cursor.column
  245. if new_line >= new_line_count then
  246. new_line = new_line_count - 1
  247. if new_line < 0 then new_line = 0 end -- Handle empty buffer after format
  248. end
  249. if new_line >= 0 then
  250. local line_len = #buf:line(new_line)
  251. if new_col > line_len then
  252. new_col = line_len
  253. end
  254. else
  255. new_col = 0 -- Should be (0,0) for empty buffer
  256. end
  257. editor.cursor = lumacs.Position(new_line, new_col)
  258. editor:message("Buffer formatted successfully.")
  259. else
  260. editor:message("Error: Could not read formatted content from temporary file.")
  261. end
  262. else
  263. editor:message(string.format("Error: clang-format failed with status '%s', code '%s'", tostring(status_string), tostring(code)))
  264. editor:message("Please ensure clang-format is installed and available in your PATH.")
  265. end
  266. -- Clean up temporary file
  267. os.remove(temp_file)
  268. end
  269. -- Register the C/C++ Major Mode
  270. define_major_mode("c-cpp-mode", {
  271. file_patterns = {
  272. "%.c$", "%.cpp$", "%.h$", "%.hpp$", "%.cc$", "%.cxx$", "%.hxx$",
  273. "%.C$", "%.CPP$", "%.H$", "%.HPP$", "%.CC$", "%.CXX$", "%.HXX$" -- Case-insensitive extensions
  274. },
  275. comment_syntax = "//", -- Default for single-line comments
  276. setup = function()
  277. editor:message("[c-cpp-mode] C/C++ mode activated")
  278. end,
  279. cleanup = function()
  280. editor:message("[c-cpp-mode] C/C++ mode deactivated")
  281. end,
  282. highlight = highlight_cpp_buffer,
  283. keybindings = {
  284. [" "] = function() editor:execute_command("self-insert-command", {" "}) end, -- Explicitly bind space
  285. ["M-q"] = function() format_buffer() end, -- Bind M-q to format the buffer
  286. ["C-c C-f"] = function() format_buffer() end, -- Alternative binding
  287. }
  288. })
  289. -- Register format-buffer command so it can be called via M-x
  290. editor:register_command(
  291. "format-buffer",
  292. "Formats the current C/C++ buffer using clang-format.",
  293. format_buffer,
  294. true -- Interactive, can be called via M-x
  295. )