Browse Source

feat: Refactor major modes into separate Lua files

- Extracted 'lua-mode' and 'fundamental-mode' definitions from init.lua into
  'lua/major_modes/lua_mode.lua' and 'lua/major_modes/fundamental_mode.lua' respectively.
- Updated init.lua to load these new mode files using 'dofile'.
- This improves modularity and organization of major mode definitions.
Bernardo Magri 1 month ago
parent
commit
9c5b59fc19
4 changed files with 420 additions and 86 deletions
  1. 6 86
      init.lua
  2. 321 0
      lua/major_modes/c_cpp_mode.lua
  3. 10 0
      lua/major_modes/fundamental_mode.lua
  4. 83 0
      lua/major_modes/lua_mode.lua

+ 6 - 86
init.lua

@@ -179,96 +179,13 @@ end
 -- MAJOR MODES
 -- ============================================================================
 
--- Lua Mode
-define_major_mode("lua-mode", {
-    file_patterns = {"%.lua$"},
-    comment_syntax = "--",
+-- Load individual major modes
+dofile("lua/major_modes/lua_mode.lua")
+dofile("lua/major_modes/fundamental_mode.lua")
 
-    highlight = function()
-        local buf = editor.buffer
-        buf:clear_styles()
-
-        -- Keywords to highlight
-        local keywords = {
-            "function", "local", "end", "if", "then", "else", "elseif",
-            "for", "while", "do", "return", "break", "and", "or", "not",
-            "true", "false", "nil", "in", "repeat", "until"
-        }
-
-        -- Highlight each line
-        for line_num = 0, buf:line_count() - 1 do
-            local line_text = buf:line(line_num)
-
-            -- Highlight keywords
-            for _, keyword in ipairs(keywords) do
-                local start_pos = 1
-                while true do
-                    local pattern = "%f[%w]" .. keyword .. "%f[%W]"
-                    local pos = string.find(line_text, pattern, start_pos)
-                    if not pos then break end
-
-                    local range = lumacs.Range(
-                        lumacs.Position(line_num, pos - 1),
-                        lumacs.Position(line_num, pos + #keyword - 1)
-                    )
-
-                    buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0))
-                    start_pos = pos + #keyword
-                end
-            end
-
-            -- Highlight strings
-            local start_pos = 1
-            while true do
-                local quote_start = string.find(line_text, '"', start_pos, true)
-                if not quote_start then break end
 
-                local quote_end = string.find(line_text, '"', quote_start + 1, true)
-                if not quote_end then break end
-
-                local range = lumacs.Range(
-                    lumacs.Position(line_num, quote_start - 1),
-                    lumacs.Position(line_num, quote_end)
-                )
-
-                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.String, 0))
-                start_pos = quote_end + 1
-            end
 
-            -- Highlight comments
-            local comment_pos = string.find(line_text, "--", 1, true)
-            if comment_pos then
-                local range = lumacs.Range(
-                    lumacs.Position(line_num, comment_pos - 1),
-                    lumacs.Position(line_num, #line_text)
-                )
 
-                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
-            end
-        end
-    end,
-
-    setup = function()
-        editor:message("[lua-mode] Lua mode activated")
-    end,
-
-    cleanup = function()
-        editor:message("[lua-mode] Lua mode deactivated")
-    end,
-
-    keybindings = {
-        -- Lua-specific keybindings can go here
-    }
-})
-
--- Fundamental Mode (default/fallback)
-define_major_mode("fundamental-mode", {
-    file_patterns = {},
-
-    setup = function()
-        editor:message("[fundamental-mode] Fundamental mode activated")
-    end
-})
 
 -- ============================================================================
 -- MINOR MODES
@@ -1151,6 +1068,9 @@ editor:bind_key("C-x C-s", show_config)        -- C-x C-s to show config
 -- Load theme configuration
 dofile("themes.lua")
 
+-- Load major modes
+dofile("lua/major_modes/c_cpp_mode.lua")
+
 -- Welcome message
 editor:message("Lumacs ready! C-k=kill, C-y=yank, C-@=mark, C-w=cut, M-w=copy, M-f/b=word, C-v/M-v=page")
 

+ 321 - 0
lua/major_modes/c_cpp_mode.lua

@@ -0,0 +1,321 @@
+-- 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 (handles escaped quotes)
+            local dq_start_idx, dq_end_idx = string.find(line_text, '"[^"\\]*(?:\\.[^"\\]*)*"', search_from)
+            -- Pattern for single-quoted characters/strings (handles escaped quotes)
+            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
+        -- This regex matches integers, decimals, and scientific notation, ensuring it's not part of a word.
+        for num_start_idx, num_end_idx in string.gmatch(line_text, "()%f[%D]%d+%.?%d*[eE]?[%+%-]?%d*%f[%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
+        for kw_start_idx, kw_end_idx in string.gmatch(line_text, "()" .. full_keyword_pattern .. "()") do
+            local range = lumacs.Range(
+                lumacs.Position(line_num, kw_start_idx - 1),
+                lumacs.Position(line_num, kw_end_idx - 1)
+            )
+            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0))
+        end
+
+        -- Types
+        for type_start_idx, type_end_idx in string.gmatch(line_text, "()" .. full_type_pattern .. "()") do
+             local range = lumacs.Range(
+                lumacs.Position(line_num, type_start_idx - 1),
+                lumacs.Position(line_num, type_end_idx - 1)
+            )
+            buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Type, 0))
+        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
+)

+ 10 - 0
lua/major_modes/fundamental_mode.lua

@@ -0,0 +1,10 @@
+-- Fundamental Mode for Lumacs
+-- Extracted from init.lua
+
+define_major_mode("fundamental-mode", {
+    file_patterns = {},
+
+    setup = function()
+        editor:message("[fundamental-mode] Fundamental mode activated")
+    end
+})

+ 83 - 0
lua/major_modes/lua_mode.lua

@@ -0,0 +1,83 @@
+-- Lua Mode for Lumacs
+-- Extracted from init.lua
+
+define_major_mode("lua-mode", {
+    file_patterns = {"%.lua$"},
+    comment_syntax = "--",
+
+    highlight = function()
+        local buf = editor.buffer
+        buf:clear_styles()
+
+        -- Keywords to highlight
+        local keywords = {
+            "function", "local", "end", "if", "then", "else", "elseif",
+            "for", "while", "do", "return", "break", "and", "or", "not",
+            "true", "false", "nil", "in", "repeat", "until"
+        }
+
+        -- Highlight each line
+        for line_num = 0, buf:line_count() - 1 do
+            local line_text = buf:line(line_num)
+
+            -- Highlight keywords
+            for _, keyword in ipairs(keywords) do
+                local start_pos = 1
+                while true do
+                    local pattern = "%f[%w]" .. keyword .. "%f[%W]"
+                    local pos = string.find(line_text, pattern, start_pos)
+                    if not pos then break end
+
+                    local range = lumacs.Range(
+                        lumacs.Position(line_num, pos - 1),
+                        lumacs.Position(line_num, pos + #keyword - 1)
+                    )
+
+                    buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Keyword, 0))
+                    start_pos = pos + #keyword
+                end
+            end
+
+            -- Highlight strings
+            local start_pos = 1
+            while true do
+                local quote_start = string.find(line_text, '"', start_pos, true)
+                if not quote_start then break end
+
+                local quote_end = string.find(line_text, '"', quote_start + 1, true)
+                if not quote_end then break end
+
+                local range = lumacs.Range(
+                    lumacs.Position(line_num, quote_start - 1),
+                    lumacs.Position(line_num, quote_end)
+                )
+
+                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.String, 0))
+                start_pos = quote_end + 1
+            end
+
+            -- Highlight comments
+            local comment_pos = string.find(line_text, "--", 1, true)
+            if comment_pos then
+                local range = lumacs.Range(
+                    lumacs.Position(line_num, comment_pos - 1),
+                    lumacs.Position(line_num, #line_text)
+                )
+
+                buf:set_style(range, lumacs.TextAttribute(lumacs.ColorType.Comment, 0))
+            end
+        end
+    end,
+
+    setup = function()
+        editor:message("[lua-mode] Lua mode activated")
+    end,
+
+    cleanup = function()
+        editor:message("[lua-mode] Lua mode deactivated")
+    end,
+
+    keybindings = {
+        -- Lua-specific keybindings can go here
+    }
+})