|
|
@@ -853,6 +853,308 @@ editor:register_command("eval-expression", "Evaluate Lua expression", function(a
|
|
|
end
|
|
|
end, true, "s")
|
|
|
|
|
|
+-- === Lua Evaluation Commands (Emacs-like) ===
|
|
|
+
|
|
|
+-- Helper function to safely evaluate Lua code and return result
|
|
|
+function lumacs.eval_lua(code)
|
|
|
+ if not code or code == "" then
|
|
|
+ return nil, "No code to evaluate"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Try to evaluate as expression first (prepend "return")
|
|
|
+ local func, err = load("return " .. code)
|
|
|
+ if not func then
|
|
|
+ -- If that fails, try as statement
|
|
|
+ func, err = load(code)
|
|
|
+ end
|
|
|
+
|
|
|
+ if not func then
|
|
|
+ return nil, "Parse error: " .. (err or "unknown")
|
|
|
+ end
|
|
|
+
|
|
|
+ local success, result = pcall(func)
|
|
|
+ if success then
|
|
|
+ return result, nil
|
|
|
+ else
|
|
|
+ return nil, "Error: " .. tostring(result)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- Helper function to find the start of a Lua expression before cursor
|
|
|
+-- Handles: parentheses (), braces {}, brackets [], strings, and simple expressions
|
|
|
+function lumacs.find_sexp_start(line, col)
|
|
|
+ if col == 0 then return 0 end
|
|
|
+
|
|
|
+ local pos = col
|
|
|
+ local depth_paren = 0
|
|
|
+ local depth_brace = 0
|
|
|
+ local depth_bracket = 0
|
|
|
+ local in_string = false
|
|
|
+ local string_char = nil
|
|
|
+
|
|
|
+ -- Scan backward from cursor position
|
|
|
+ while pos > 0 do
|
|
|
+ local char = line:sub(pos, pos)
|
|
|
+ local prev_char = pos > 1 and line:sub(pos - 1, pos - 1) or ""
|
|
|
+
|
|
|
+ -- Handle string literals
|
|
|
+ if (char == '"' or char == "'") and prev_char ~= "\\" then
|
|
|
+ if in_string and char == string_char then
|
|
|
+ in_string = false
|
|
|
+ string_char = nil
|
|
|
+ elseif not in_string then
|
|
|
+ in_string = true
|
|
|
+ string_char = char
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ if not in_string then
|
|
|
+ if char == ")" then depth_paren = depth_paren + 1
|
|
|
+ elseif char == "(" then
|
|
|
+ depth_paren = depth_paren - 1
|
|
|
+ if depth_paren < 0 then return pos end
|
|
|
+ elseif char == "}" then depth_brace = depth_brace + 1
|
|
|
+ elseif char == "{" then
|
|
|
+ depth_brace = depth_brace - 1
|
|
|
+ if depth_brace < 0 then return pos end
|
|
|
+ elseif char == "]" then depth_bracket = depth_bracket + 1
|
|
|
+ elseif char == "[" then
|
|
|
+ depth_bracket = depth_bracket - 1
|
|
|
+ if depth_bracket < 0 then return pos end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- If all depths are 0 and we hit whitespace or line start, we found the expression start
|
|
|
+ if depth_paren == 0 and depth_brace == 0 and depth_bracket == 0 then
|
|
|
+ -- Check for expression boundaries: whitespace, operators that end expressions
|
|
|
+ if char:match("[%s,;]") then
|
|
|
+ return pos + 1
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ pos = pos - 1
|
|
|
+ end
|
|
|
+
|
|
|
+ return 1 -- Start of line if nothing else found
|
|
|
+end
|
|
|
+
|
|
|
+-- Get the Lua expression at or before the cursor
|
|
|
+function lumacs.get_sexp_at_point()
|
|
|
+ local cursor = editor.cursor
|
|
|
+ local buf = editor.buffer
|
|
|
+ local line = buf:line(cursor.line)
|
|
|
+
|
|
|
+ if not line or #line == 0 then
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Find expression boundaries
|
|
|
+ local col = cursor.column
|
|
|
+ if col > #line then col = #line end
|
|
|
+
|
|
|
+ -- Simple heuristic: find expression from line start to cursor
|
|
|
+ -- For multi-line expressions, we'd need more sophisticated parsing
|
|
|
+ local start_col = lumacs.find_sexp_start(line, col)
|
|
|
+ local expr = line:sub(start_col, col)
|
|
|
+
|
|
|
+ -- Trim whitespace
|
|
|
+ expr = expr:match("^%s*(.-)%s*$")
|
|
|
+
|
|
|
+ return expr
|
|
|
+end
|
|
|
+
|
|
|
+-- Get multi-line expression ending at cursor (scans backward for complete expression)
|
|
|
+function lumacs.get_multiline_sexp()
|
|
|
+ local cursor = editor.cursor
|
|
|
+ local buf = editor.buffer
|
|
|
+
|
|
|
+ -- Start from cursor and work backward to find complete expression
|
|
|
+ local lines = {}
|
|
|
+ local current_line = cursor.line
|
|
|
+ local depth_paren = 0
|
|
|
+ local depth_brace = 0
|
|
|
+ local depth_bracket = 0
|
|
|
+
|
|
|
+ -- First, get text from line start to cursor on current line
|
|
|
+ local line = buf:line(current_line)
|
|
|
+ local end_col = cursor.column
|
|
|
+ if end_col > #line then end_col = #line end
|
|
|
+ local text_on_line = line:sub(1, end_col)
|
|
|
+
|
|
|
+ -- Count closing delimiters that need matching
|
|
|
+ for char in text_on_line:gmatch(".") do
|
|
|
+ if char == ")" then depth_paren = depth_paren + 1
|
|
|
+ elseif char == "(" then depth_paren = depth_paren - 1
|
|
|
+ elseif char == "}" then depth_brace = depth_brace + 1
|
|
|
+ elseif char == "{" then depth_brace = depth_brace - 1
|
|
|
+ elseif char == "]" then depth_bracket = depth_bracket + 1
|
|
|
+ elseif char == "[" then depth_bracket = depth_bracket - 1
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ table.insert(lines, 1, text_on_line)
|
|
|
+
|
|
|
+ -- Scan backward through previous lines if we need more opening delimiters
|
|
|
+ while (depth_paren > 0 or depth_brace > 0 or depth_bracket > 0) and current_line > 0 do
|
|
|
+ current_line = current_line - 1
|
|
|
+ line = buf:line(current_line)
|
|
|
+ if line then
|
|
|
+ for char in line:gmatch(".") do
|
|
|
+ if char == ")" then depth_paren = depth_paren + 1
|
|
|
+ elseif char == "(" then depth_paren = depth_paren - 1
|
|
|
+ elseif char == "}" then depth_brace = depth_brace + 1
|
|
|
+ elseif char == "{" then depth_brace = depth_brace - 1
|
|
|
+ elseif char == "]" then depth_bracket = depth_bracket + 1
|
|
|
+ elseif char == "[" then depth_bracket = depth_bracket - 1
|
|
|
+ end
|
|
|
+ end
|
|
|
+ table.insert(lines, 1, line)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ return table.concat(lines, "\n")
|
|
|
+end
|
|
|
+
|
|
|
+editor:register_command("eval-last-sexp", "Evaluate Lua expression before point", function()
|
|
|
+ -- Try single-line first, then multi-line
|
|
|
+ local expr = lumacs.get_sexp_at_point()
|
|
|
+
|
|
|
+ if not expr or expr == "" then
|
|
|
+ -- Try multi-line expression
|
|
|
+ expr = lumacs.get_multiline_sexp()
|
|
|
+ end
|
|
|
+
|
|
|
+ if not expr or expr == "" then
|
|
|
+ editor:message("No expression at point", "warning")
|
|
|
+ return {success = false, message = "No expression at point"}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result, err = lumacs.eval_lua(expr)
|
|
|
+
|
|
|
+ if err then
|
|
|
+ editor:message(err, "error")
|
|
|
+ return {success = false, message = err}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result_str = tostring(result)
|
|
|
+ editor:message("=> " .. result_str)
|
|
|
+ return {success = true, message = result_str}
|
|
|
+end)
|
|
|
+
|
|
|
+editor:register_command("eval-region", "Evaluate Lua code in region", function()
|
|
|
+ local buf = editor.buffer
|
|
|
+ local range = buf:get_region(editor.cursor)
|
|
|
+
|
|
|
+ if not range then
|
|
|
+ editor:message("No region active", "warning")
|
|
|
+ return {success = false, message = "No region active"}
|
|
|
+ end
|
|
|
+
|
|
|
+ local code = buf:get_text_in_range(range)
|
|
|
+ if not code or code == "" then
|
|
|
+ editor:message("Region is empty", "warning")
|
|
|
+ return {success = false, message = "Region is empty"}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result, err = lumacs.eval_lua(code)
|
|
|
+
|
|
|
+ if err then
|
|
|
+ editor:message(err, "error")
|
|
|
+ return {success = false, message = err}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result_str = tostring(result)
|
|
|
+ editor:message("=> " .. result_str)
|
|
|
+ return {success = true, message = result_str}
|
|
|
+end)
|
|
|
+
|
|
|
+editor:register_command("eval-buffer", "Evaluate entire buffer as Lua code", function()
|
|
|
+ local buf = editor.buffer
|
|
|
+ local code = buf:content()
|
|
|
+
|
|
|
+ if not code or code == "" then
|
|
|
+ editor:message("Buffer is empty", "warning")
|
|
|
+ return {success = false, message = "Buffer is empty"}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result, err = lumacs.eval_lua(code)
|
|
|
+
|
|
|
+ if err then
|
|
|
+ editor:message(err, "error")
|
|
|
+ return {success = false, message = err}
|
|
|
+ end
|
|
|
+
|
|
|
+ local result_str = result ~= nil and tostring(result) or "nil"
|
|
|
+ editor:message("Buffer evaluated: " .. result_str)
|
|
|
+ return {success = true, message = result_str}
|
|
|
+end)
|
|
|
+
|
|
|
+-- === Face and Theme Helpers for Interactive Use ===
|
|
|
+
|
|
|
+-- Helper to create a FaceAttributes table for use with define_face
|
|
|
+function lumacs.face(opts)
|
|
|
+ local attrs = lumacs.FaceAttributes()
|
|
|
+ if opts.foreground then
|
|
|
+ if type(opts.foreground) == "string" then
|
|
|
+ attrs.foreground = lumacs.Color.from_hex(opts.foreground)
|
|
|
+ else
|
|
|
+ attrs.foreground = opts.foreground
|
|
|
+ end
|
|
|
+ end
|
|
|
+ if opts.background then
|
|
|
+ if type(opts.background) == "string" then
|
|
|
+ attrs.background = lumacs.Color.from_hex(opts.background)
|
|
|
+ else
|
|
|
+ attrs.background = opts.background
|
|
|
+ end
|
|
|
+ end
|
|
|
+ if opts.weight then
|
|
|
+ if opts.weight == "bold" then
|
|
|
+ attrs.weight = lumacs.FontWeight.Bold
|
|
|
+ elseif opts.weight == "light" then
|
|
|
+ attrs.weight = lumacs.FontWeight.Light
|
|
|
+ end
|
|
|
+ end
|
|
|
+ if opts.slant then
|
|
|
+ if opts.slant == "italic" then
|
|
|
+ attrs.slant = lumacs.FontSlant.Italic
|
|
|
+ elseif opts.slant == "oblique" then
|
|
|
+ attrs.slant = lumacs.FontSlant.Oblique
|
|
|
+ end
|
|
|
+ end
|
|
|
+ if opts.underline ~= nil then
|
|
|
+ attrs.underline = opts.underline
|
|
|
+ end
|
|
|
+ if opts.family then
|
|
|
+ attrs.family = opts.family
|
|
|
+ end
|
|
|
+ if opts.height then
|
|
|
+ attrs.height = opts.height
|
|
|
+ end
|
|
|
+ return attrs
|
|
|
+end
|
|
|
+
|
|
|
+-- Convenience function to set a face on the active theme
|
|
|
+function lumacs.set_face(name, opts)
|
|
|
+ local theme = editor.theme_manager:active_theme()
|
|
|
+ if theme then
|
|
|
+ theme:set_face(name, lumacs.face(opts))
|
|
|
+ editor:message("Face '" .. name .. "' updated")
|
|
|
+ else
|
|
|
+ editor:message("No active theme", "error")
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- Convenience function to get RGB color
|
|
|
+function lumacs.rgb(r, g, b)
|
|
|
+ return lumacs.Color(r, g, b)
|
|
|
+end
|
|
|
+
|
|
|
+-- Convenience function to get color from hex string
|
|
|
+function lumacs.hex(hex_str)
|
|
|
+ return lumacs.Color.from_hex(hex_str)
|
|
|
+end
|
|
|
+
|
|
|
-- =====================================================
|
|
|
-- 5. DEFAULT KEYBINDINGS (Emacs-compatible)
|
|
|
-- =====================================================
|
|
|
@@ -942,6 +1244,9 @@ editor:bind_key("C-x =", "what-cursor-position")
|
|
|
editor:bind_key("C-x C-u", "upcase-region")
|
|
|
editor:bind_key("C-x C-l", "downcase-region")
|
|
|
|
|
|
+-- === C-x C-e (Lua evaluation) ===
|
|
|
+editor:bind_key("C-x C-e", "eval-last-sexp")
|
|
|
+
|
|
|
-- === C-x r (Register/Rectangle prefix) ===
|
|
|
editor:bind_key("C-x r s", "copy-to-register")
|
|
|
editor:bind_key("C-x r i", "insert-register")
|