|
|
@@ -0,0 +1,247 @@
|
|
|
+# Extending Lumacs with Lua: A Comprehensive Tutorial
|
|
|
+
|
|
|
+Lumacs is designed to be highly extensible, similar to Emacs. Almost every aspect of the editor—from keybindings to syntax highlighting and core behavior—can be customized using Lua.
|
|
|
+
|
|
|
+This tutorial will guide you through the process of extending Lumacs, turning it into your own personalized development environment.
|
|
|
+
|
|
|
+## 1. Getting Started
|
|
|
+
|
|
|
+Lumacs loads your configuration from `init.lua`. It searches for this file in the following locations (in order):
|
|
|
+1. `./init.lua` (current working directory)
|
|
|
+2. `~/.config/lumacs/init.lua`
|
|
|
+3. `~/.lumacs/init.lua`
|
|
|
+
|
|
|
+Create your first customization by creating this file and adding a welcome message:
|
|
|
+
|
|
|
+```lua
|
|
|
+-- init.lua
|
|
|
+message("Welcome to my custom Lumacs!")
|
|
|
+```
|
|
|
+
|
|
|
+Restart Lumacs, and you should see the message in the status line.
|
|
|
+
|
|
|
+## 2. Keybindings
|
|
|
+
|
|
|
+The most common customization is defining keybindings. Use the global `bind_key` function.
|
|
|
+
|
|
|
+### Basic Bindings
|
|
|
+
|
|
|
+```lua
|
|
|
+-- Bind F12 to insert a timestamp
|
|
|
+bind_key("F12", function()
|
|
|
+ local timestamp = os.date("%Y-%m-%d %H:%M:%S")
|
|
|
+ editor.buffer:insert(editor.cursor, timestamp)
|
|
|
+ message("Inserted timestamp")
|
|
|
+end)
|
|
|
+```
|
|
|
+
|
|
|
+### Modifier Keys
|
|
|
+
|
|
|
+Lumacs supports Control (`C-`) and Meta/Alt (`M-`) modifiers.
|
|
|
+
|
|
|
+```lua
|
|
|
+bind_key("C-s", function() editor.buffer:save() end) -- Ctrl+s
|
|
|
+bind_key("M-x", function() editor:command_mode() end) -- Alt+x
|
|
|
+```
|
|
|
+
|
|
|
+### Key Sequences
|
|
|
+
|
|
|
+You can define multi-key sequences (chords), similar to Emacs.
|
|
|
+
|
|
|
+```lua
|
|
|
+-- Define 'C-c t' to insert a TODO comment
|
|
|
+bind_key("C-c t", function()
|
|
|
+ editor.buffer:insert(editor.cursor, "-- TODO: ")
|
|
|
+end)
|
|
|
+```
|
|
|
+
|
|
|
+## 3. Defining Commands (M-x)
|
|
|
+
|
|
|
+While keybindings are great for frequent actions, you don't want to bind a key for everything. **Commands** allow you to register functions that can be executed by name via the Minibuffer (`M-x`).
|
|
|
+
|
|
|
+Use `define_command(name, function, documentation)`:
|
|
|
+
|
|
|
+```lua
|
|
|
+define_command("hello-world", function()
|
|
|
+ message("Hello from the Minibuffer!")
|
|
|
+end, "Prints a greeting message")
|
|
|
+
|
|
|
+define_command("delete-current-line", function()
|
|
|
+ editor:move_to_line_start()
|
|
|
+ editor:kill_line()
|
|
|
+ editor:kill_line() -- Kill the newline too
|
|
|
+end, "Deletes the current line")
|
|
|
+```
|
|
|
+
|
|
|
+Now, press `M-x` (or `Alt+x`), type `hello-world`, and press Enter. You can also use TAB for auto-completion.
|
|
|
+
|
|
|
+## 4. Creating Major Modes
|
|
|
+
|
|
|
+Major modes specialize the editor for specific file types (e.g., Lua, C++, Python). A buffer can only have one major mode at a time.
|
|
|
+
|
|
|
+Use `define_major_mode(name, configuration)`:
|
|
|
+
|
|
|
+```lua
|
|
|
+define_major_mode("markdown-mode", {
|
|
|
+ -- Files matching these patterns will auto-activate this mode
|
|
|
+ file_patterns = {"%.md$", "%.markdown$"},
|
|
|
+
|
|
|
+ -- String used for comments (used by M-;)
|
|
|
+ comment_syntax = "<!--",
|
|
|
+
|
|
|
+ -- Setup function: Run when mode is activated
|
|
|
+ setup = function()
|
|
|
+ print("Markdown mode activated")
|
|
|
+ -- You can set mode-specific keybindings here
|
|
|
+ end,
|
|
|
+
|
|
|
+ -- Cleanup function: Run when switching away from this mode
|
|
|
+ cleanup = function()
|
|
|
+ print("Markdown mode deactivated")
|
|
|
+ end,
|
|
|
+
|
|
|
+ -- Syntax Highlighting Logic
|
|
|
+ highlight = function()
|
|
|
+ local buf = editor.buffer
|
|
|
+ buf:clear_styles()
|
|
|
+
|
|
|
+ -- Simple example: Highlight headings (# Heading)
|
|
|
+ for line_num = 0, buf:line_count() - 1 do
|
|
|
+ local line_text = buf:line(line_num)
|
|
|
+
|
|
|
+ -- If line starts with #, color it as a Keyword (often used for headings)
|
|
|
+ if string.match(line_text, "^#") then
|
|
|
+ local range = lumacs.Range(
|
|
|
+ lumacs.Position(line_num, 0),
|
|
|
+ lumacs.Position(line_num, #line_text)
|
|
|
+ )
|
|
|
+ -- Apply the 'font-lock-keyword-face'
|
|
|
+ buf:set_style(range, lumacs.TextAttribute("font-lock-keyword-face"))
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+The editor will now automatically detect `.md` files and apply your highlighting!
|
|
|
+
|
|
|
+## 5. Creating Minor Modes
|
|
|
+
|
|
|
+Minor modes provide optional functionality that can be toggled on/off independently of the major mode (e.g., Auto-Save, Spell Check).
|
|
|
+
|
|
|
+```lua
|
|
|
+define_minor_mode("auto-save-mode", {
|
|
|
+ global = false, -- Can be buffer-local or global
|
|
|
+
|
|
|
+ setup = function()
|
|
|
+ message("Auto-save enabled")
|
|
|
+ -- Start a timer or hook into events (see below)
|
|
|
+ end,
|
|
|
+
|
|
|
+ cleanup = function()
|
|
|
+ message("Auto-save disabled")
|
|
|
+ end
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+Toggle it via `M-x auto-save-mode`.
|
|
|
+
|
|
|
+## 6. Hooks and Events
|
|
|
+
|
|
|
+Your scripts can react to editor events using `editor.buffer:on_buffer_event`.
|
|
|
+
|
|
|
+**Available Events:**
|
|
|
+* `lumacs.BufferEvent.Loaded`: A file was loaded.
|
|
|
+* `lumacs.BufferEvent.BeforeSave` / `AfterSave`.
|
|
|
+* `lumacs.BufferEvent.BeforeChange` / `AfterChange`: Text was modified.
|
|
|
+* `lumacs.BufferEvent.LanguageChanged`: Major mode changed.
|
|
|
+
|
|
|
+**Example: Auto-Save on Change**
|
|
|
+
|
|
|
+```lua
|
|
|
+local change_count = 0
|
|
|
+
|
|
|
+editor.buffer:on_buffer_event(function(data)
|
|
|
+ if data.event == lumacs.BufferEvent.AfterChange then
|
|
|
+ change_count = change_count + 1
|
|
|
+ if change_count > 20 then
|
|
|
+ editor.buffer:save()
|
|
|
+ message("Auto-saved!")
|
|
|
+ change_count = 0
|
|
|
+ end
|
|
|
+ end
|
|
|
+end)
|
|
|
+```
|
|
|
+
|
|
|
+## 7. The Face System (Theming)
|
|
|
+
|
|
|
+Lumacs uses a "Face" system to separate logical styles (e.g., "This is a comment") from visual appearance (e.g., "Comments are grey and italic").
|
|
|
+
|
|
|
+### Defining Styles
|
|
|
+
|
|
|
+You can modify the active theme or create new faces.
|
|
|
+
|
|
|
+```lua
|
|
|
+-- Get the theme manager
|
|
|
+local tm = editor.theme_manager
|
|
|
+local theme = tm:active_theme()
|
|
|
+
|
|
|
+-- Customize the 'comment' face
|
|
|
+-- Note: In standard terminal (Ncurses), 'family' and 'height' are ignored,
|
|
|
+-- but 'weight', 'slant', 'underline', and 'inverse' are respected.
|
|
|
+theme:set_face("font-lock-comment-face", {
|
|
|
+ foreground = lumacs.Color(100, 100, 100), -- Grey
|
|
|
+ slant = lumacs.FontSlant.Italic
|
|
|
+})
|
|
|
+
|
|
|
+-- Create a custom face
|
|
|
+theme:set_face("my-custom-face", {
|
|
|
+ foreground = lumacs.Color(255, 0, 0), -- Red
|
|
|
+ background = lumacs.Color(255, 255, 0), -- Yellow
|
|
|
+ weight = lumacs.FontWeight.Bold,
|
|
|
+ underline = true
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### Applying Faces
|
|
|
+
|
|
|
+In your major mode's `highlight` function, apply these faces:
|
|
|
+
|
|
|
+```lua
|
|
|
+local range = lumacs.Range(start_pos, end_pos)
|
|
|
+-- Apply standard face
|
|
|
+buf:set_style(range, lumacs.TextAttribute("font-lock-comment-face"))
|
|
|
+-- Or your custom face
|
|
|
+buf:set_style(range, lumacs.TextAttribute("my-custom-face"))
|
|
|
+```
|
|
|
+
|
|
|
+## 8. Buffer Manipulation API Reference
|
|
|
+
|
|
|
+Common methods available on `editor.buffer` (accessed via `editor:buffer()`):
|
|
|
+
|
|
|
+* **Text Access:**
|
|
|
+ * `line(n)`: Get content of line `n` (0-indexed).
|
|
|
+ * `line_count()`: Total lines.
|
|
|
+ * `content()`: Full buffer content.
|
|
|
+
|
|
|
+* **Editing:**
|
|
|
+ * `insert(pos, text)`: Insert string at `lumacs.Position(row, col)`.
|
|
|
+ * `replace(range, text)`: Replace text in `lumacs.Range(start, end)`.
|
|
|
+ * `erase(range)`: Delete text.
|
|
|
+
|
|
|
+* **Search:**
|
|
|
+ * `find(query, pos)`: Returns a `Range` if found, or `nil`.
|
|
|
+
|
|
|
+* **Cursor:**
|
|
|
+ * `editor.cursor`: Get/Set current `Position`.
|
|
|
+ * `editor:set_cursor(pos)`: Move cursor.
|
|
|
+
|
|
|
+## Summary
|
|
|
+
|
|
|
+1. **Define Commands** for user actions (`define_command`).
|
|
|
+2. **Bind Keys** to those commands or Lua functions (`bind_key`).
|
|
|
+3. **Create Modes** to organize functionality (`define_major_mode`).
|
|
|
+4. **Use Hooks** to automate behavior (`on_buffer_event`).
|
|
|
+5. **Style Text** using the Face system (`set_face`, `set_style`).
|
|
|
+
|
|
|
+Happy hacking!
|