-- C/C++ Major Mode for Lumacs -- Define helper function for escaping Lua patterns local function escape_pattern(text) return text:gsub("([%%^%$%.%[%]%*%+%-%?%(%)])", "%%%1") end -- C/C++ Keywords local cpp_keywords = { "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq" } -- C/C++ Types (some overlap with keywords, but good to have separate for flexibility) local cpp_types = { "void", "char", "short", "int", "long", "float", "double", "bool", "signed", "unsigned", "class", "struct", "union", "enum", "typename", -- Standard library types commonly used "size_t", "ptrdiff_t", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "string", "vector", "map", "set", "unique_ptr", "shared_ptr", "cout", "cin", "cerr", "endl" -- Common std:: things } -- C/C++ Preprocessor directives local preprocessor_directives = { "include", "define", "undef", "if", "ifdef", "ifndef", "else", "elif", "endif", "error", "pragma", "line" } -- C/C++ Operators (for potential future highlighting, regex is hard here) -- This list is mostly for reference, direct highlighting of all operators is complex local cpp_operators = { "+", "-", "*", "/", "%", "==", "!=", "<", ">", "<= ", ">=", "&&", "||", "!", "&", "|", "^", "~", "<<", ">>", "=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", "?", ":", ".", "->", "::", "++", "--" } -- Build keyword patterns local keyword_patterns = {} for _, kw in ipairs(cpp_keywords) do table.insert(keyword_patterns, "%f[%w]" .. escape_pattern(kw) .. "%f[%W]") end local full_keyword_pattern = table.concat(keyword_patterns, "|") -- Build type patterns local type_patterns = {} for _, ty in ipairs(cpp_types) do table.insert(type_patterns, "%f[%w]" .. escape_pattern(ty) .. "%f[%W]") end local full_type_pattern = table.concat(type_patterns, "|") -- Function to perform syntax highlighting local function highlight_cpp_buffer() local buf = editor.buffer buf:clear_styles() local in_multiline_comment = false for line_num = 0, buf:line_count() - 1 do local line_text = buf:line(line_num) local line_len = #line_text -- Handle multiline comments if in_multiline_comment then local comment_end_pos = string.find(line_text, "*/", 1, true) if comment_end_pos then local range = lumacs.Range( lumacs.Position(line_num, 0), lumacs.Position(line_num, comment_end_pos + 1) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0)) in_multiline_comment = false else local range = lumacs.Range( lumacs.Position(line_num, 0), lumacs.Position(line_num, line_len) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0)) end end -- Single-line comments (//) local single_comment_pos = string.find(line_text, "//", 1, true) if single_comment_pos and not in_multiline_comment then -- only if not already in multi-line local range = lumacs.Range( lumacs.Position(line_num, single_comment_pos - 1), lumacs.Position(line_num, line_len) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0)) end -- Multiline comment start (/*) local multi_comment_start_pos = string.find(line_text, "/*", 1, true) if multi_comment_start_pos and not in_multiline_comment then -- only if not already in multi-line local multi_comment_end_pos = string.find(line_text, "*/", multi_comment_start_pos + 2, true) if multi_comment_end_pos then -- Single line multiline comment local range = lumacs.Range( lumacs.Position(line_num, multi_comment_start_pos - 1), lumacs.Position(line_num, multi_comment_end_pos + 1) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0)) else -- Multiline comment continues to next line local range = lumacs.Range( lumacs.Position(line_num, multi_comment_start_pos - 1), lumacs.Position(line_num, line_len) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0)) in_multiline_comment = true end end -- Strings (double and single quotes) local search_from = 1 while search_from <= line_len do -- Pattern for double-quoted strings (simplified - handles basic cases) local dq_start_idx, dq_end_idx = string.find(line_text, '"[^"]*"', search_from) -- Pattern for single-quoted characters/strings (simplified) local sq_start_idx, sq_end_idx = string.find(line_text, "'[^']*'", search_from) local current_match_start, current_match_end if dq_start_idx and (not sq_start_idx or dq_start_idx < sq_start_idx) then current_match_start, current_match_end = dq_start_idx, dq_end_idx elseif sq_start_idx then current_match_start, current_match_end = sq_start_idx, sq_end_idx else break -- No more strings found on this line end local range = lumacs.Range( lumacs.Position(line_num, current_match_start - 1), lumacs.Position(line_num, current_match_end - 1) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.String, 0)) search_from = current_match_end + 1 end -- Numbers -- Match integers and decimals (simplified pattern that works in Lua) for num_start_idx, num_end_idx in string.gmatch(line_text, "()%d+%.?%d*()") do local range = lumacs.Range( lumacs.Position(line_num, num_start_idx - 1), lumacs.Position(line_num, num_end_idx - 1) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Number, 0)) end -- Preprocessor directives local preprocessor_match_start, preprocessor_match_end = string.find(line_text, "^%s*#(%a+)", 1) -- find and capture directive name if preprocessor_match_start then local directive_name = string.match(line_text, "^%s*#(%a+)") for _, pp_dir in ipairs(preprocessor_directives) do if directive_name == pp_dir then local hash_start_idx = string.find(line_text, "#", 1, true) local range = lumacs.Range( lumacs.Position(line_num, hash_start_idx - 1), lumacs.Position(line_num, line_len) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Variable, 0)) -- Using Variable for directives break -- Found and styled, move to next end end end -- Keywords - iterate through each keyword individually for _, kw in ipairs(cpp_keywords) do local search_pos = 1 while search_pos <= line_len do local pattern = "%f[%w_]" .. escape_pattern(kw) .. "%f[%W_]" local kw_start, kw_end = string.find(line_text, pattern, search_pos) if not kw_start then break end local range = lumacs.Range( lumacs.Position(line_num, kw_start - 1), lumacs.Position(line_num, kw_end) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0)) search_pos = kw_end + 1 end end -- Types - iterate through each type individually for _, ty in ipairs(cpp_types) do local search_pos = 1 while search_pos <= line_len do local pattern = "%f[%w_]" .. escape_pattern(ty) .. "%f[%W_]" local type_start, type_end = string.find(line_text, pattern, search_pos) if not type_start then break end local range = lumacs.Range( lumacs.Position(line_num, type_start - 1), lumacs.Position(line_num, type_end) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Type, 0)) search_pos = type_end + 1 end end -- Operators (simple approach: highlight some common multi-char operators) local ops_to_highlight = {"==", "!=", "<= ", ">=", "&&", "||", "++", "--", "->", "::", "<<", ">>", ".*", "->*"} for _, op in ipairs(ops_to_highlight) do local op_start_pos = 1 while op_start_pos <= line_len do local pos = string.find(line_text, escape_pattern(op), op_start_pos, true) if not pos then break end local range = lumacs.Range( lumacs.Position(line_num, pos - 1), lumacs.Position(line_num, pos + #op - 1) ) buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Operator, 0)) op_start_pos = pos + #op end end end end -- Function to format the current buffer using clang-format local function format_buffer() local buf = editor.buffer local filepath = buf:filepath() if not filepath then editor:message("Cannot format unsaved buffer. Save the file first.") return end editor:message("Formatting buffer with clang-format...") -- Create a temporary file local temp_file = os.tmpname() .. ".cpp" -- Use .cpp extension for clang-format detection -- Write buffer content to temp file local file = io.open(temp_file, "w") if file then file:write(buf:get_all_text()) file:close() else editor:message("Error: Could not create temporary file for formatting.") os.remove(temp_file) -- Try to clean up if file creation failed mid-way return end -- Execute clang-format local clang_format_cmd = string.format("clang-format -style=file -i %s", temp_file) local success, status_string, code = os.execute(clang_format_cmd) -- os.execute returns true/false, exit status string, and exit code if success then -- Read formatted content back local formatted_file = io.open(temp_file, "r") if formatted_file then local formatted_text = formatted_file:read("*all") formatted_file:close() -- Preserve cursor position local original_cursor = editor.cursor -- Replace buffer content local full_buffer_range = lumacs.Range(lumacs.Position(0,0), lumacs.Position(buf:line_count() - 1, #buf:line(buf:line_count() - 1))) buf:replace(full_buffer_range, formatted_text) -- Restore cursor position (clamp if necessary) local new_line_count = buf:line_count() local new_line = original_cursor.line local new_col = original_cursor.column if new_line >= new_line_count then new_line = new_line_count - 1 if new_line < 0 then new_line = 0 end -- Handle empty buffer after format end if new_line >= 0 then local line_len = #buf:line(new_line) if new_col > line_len then new_col = line_len end else new_col = 0 -- Should be (0,0) for empty buffer end editor.cursor = lumacs.Position(new_line, new_col) editor:message("Buffer formatted successfully.") else editor:message("Error: Could not read formatted content from temporary file.") end else editor:message(string.format("Error: clang-format failed with status '%s', code '%s'", tostring(status_string), tostring(code))) editor:message("Please ensure clang-format is installed and available in your PATH.") end -- Clean up temporary file os.remove(temp_file) end -- Register the C/C++ Major Mode define_major_mode("c-cpp-mode", { file_patterns = { "%.c$", "%.cpp$", "%.h$", "%.hpp$", "%.cc$", "%.cxx$", "%.hxx$", "%.C$", "%.CPP$", "%.H$", "%.HPP$", "%.CC$", "%.CXX$", "%.HXX$" -- Case-insensitive extensions }, comment_syntax = "//", -- Default for single-line comments setup = function() editor:message("[c-cpp-mode] C/C++ mode activated") end, cleanup = function() editor:message("[c-cpp-mode] C/C++ mode deactivated") end, highlight = highlight_cpp_buffer, keybindings = { [" "] = function() editor:execute_command("self-insert-command", {" "}) end, -- Explicitly bind space ["M-q"] = function() format_buffer() end, -- Bind M-q to format the buffer ["C-c C-f"] = function() format_buffer() end, -- Alternative binding } }) -- Register format-buffer command so it can be called via M-x editor:register_command( "format-buffer", "Formats the current C/C++ buffer using clang-format.", format_buffer, true -- Interactive, can be called via M-x )