| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- -- 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
- )
|