| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- -- smartparens.lua
- -- ============================================================================
- -- Automatic pairing of brackets, quotes, and other delimiters.
- -- Similar to Emacs smartparens or electric-pair-mode.
- -- ============================================================================
- local smartparens = {}
- -- Configuration
- smartparens.config = {
- enabled = true,
- pairs = {
- ["("] = ")",
- ["["] = "]",
- ["{"] = "}",
- ['"'] = '"',
- ["'"] = "'",
- ["`"] = "`",
- },
- -- Skip closing when next char is the same closer
- skip_when_next = true,
- -- Delete pair together when backspacing empty pair
- delete_pair = true,
- -- Wrap region with pair when mark is active
- wrap_region = true,
- }
- -- State
- smartparens.active = true
- -- Check if char is an opener
- function smartparens.is_opener(char)
- return smartparens.config.pairs[char] ~= nil
- end
- -- Check if char is a closer
- function smartparens.is_closer(char)
- for opener, closer in pairs(smartparens.config.pairs) do
- if closer == char and opener ~= closer then
- return true, opener
- end
- end
- return false, nil
- end
- -- Get closer for an opener
- function smartparens.get_closer(opener)
- return smartparens.config.pairs[opener]
- end
- -- Check if we're inside a string (simple heuristic)
- function smartparens.in_string()
- local buf = editor.buffer
- local cursor = editor.cursor
- local line = buf:line(cursor.line)
- local quote_count = 0
- local dquote_count = 0
- for i = 1, cursor.column do
- local c = line:sub(i, i)
- if c == "'" then quote_count = quote_count + 1 end
- if c == '"' then dquote_count = dquote_count + 1 end
- end
- return (quote_count % 2 == 1) or (dquote_count % 2 == 1)
- end
- -- Get char at cursor
- function smartparens.char_at_cursor()
- local buf = editor.buffer
- local cursor = editor.cursor
- local line = buf:line(cursor.line)
- if cursor.column < #line then
- return line:sub(cursor.column + 1, cursor.column + 1)
- end
- return nil
- end
- -- Get char before cursor
- function smartparens.char_before_cursor()
- local buf = editor.buffer
- local cursor = editor.cursor
- local line = buf:line(cursor.line)
- if cursor.column > 0 then
- return line:sub(cursor.column, cursor.column)
- end
- return nil
- end
- -- Insert pair and position cursor between them
- function smartparens.insert_pair(opener)
- if not smartparens.active or not smartparens.config.enabled then
- return false
- end
- local closer = smartparens.get_closer(opener)
- if not closer then
- return false
- end
- local buf = editor.buffer
- local cursor = editor.cursor
- -- Check if we have a region selected (for wrapping)
- if smartparens.config.wrap_region and buf:has_mark() then
- local region = buf:get_region(cursor)
- if region then
- local text = buf:get_text_in_range(region)
- buf:erase(region)
- buf:insert(region.start, opener .. text .. closer)
- -- Position cursor after the wrapped text
- editor.cursor = lumacs.Position(region.start.line, region.start.column + #text + 1)
- buf:deactivate_mark()
- return true
- end
- end
- -- For symmetric pairs (quotes), check if we should skip
- if opener == closer then
- local next_char = smartparens.char_at_cursor()
- if next_char == opener then
- -- Skip instead of inserting
- editor.cursor = lumacs.Position(cursor.line, cursor.column + 1)
- return true
- end
- end
- -- Insert the pair
- buf:insert(cursor, opener .. closer)
- -- Position cursor between the pair
- editor.cursor = lumacs.Position(cursor.line, cursor.column + 1)
- return true
- end
- -- Handle closing delimiter - skip if already there
- function smartparens.handle_closer(closer)
- if not smartparens.active or not smartparens.config.skip_when_next then
- return false
- end
- local next_char = smartparens.char_at_cursor()
- if next_char == closer then
- local cursor = editor.cursor
- editor.cursor = lumacs.Position(cursor.line, cursor.column + 1)
- return true
- end
- return false
- end
- -- Handle backspace - delete pair together if empty
- function smartparens.handle_backspace()
- if not smartparens.active or not smartparens.config.delete_pair then
- return false
- end
- local prev = smartparens.char_before_cursor()
- local next = smartparens.char_at_cursor()
- if prev and next then
- local expected_closer = smartparens.get_closer(prev)
- if expected_closer == next then
- -- Delete both chars
- local buf = editor.buffer
- local cursor = editor.cursor
- local range = lumacs.Range(
- lumacs.Position(cursor.line, cursor.column - 1),
- lumacs.Position(cursor.line, cursor.column + 1)
- )
- buf:erase(range)
- editor.cursor = lumacs.Position(cursor.line, cursor.column - 1)
- return true
- end
- end
- return false
- end
- -- Toggle smartparens
- function smartparens.toggle()
- smartparens.active = not smartparens.active
- if smartparens.active then
- editor:message("Smartparens enabled")
- else
- editor:message("Smartparens disabled")
- end
- end
- -- Register commands
- editor:register_command("smartparens-mode", "Toggle smartparens auto-pairing", function(args)
- smartparens.toggle()
- return {success = true}
- end, {"sp-mode"})
- editor:register_command("smartparens-strict-mode", "Toggle strict pairing rules", function(args)
- smartparens.config.skip_when_next = not smartparens.config.skip_when_next
- smartparens.config.delete_pair = not smartparens.config.delete_pair
- local state = smartparens.config.skip_when_next and "enabled" or "disabled"
- editor:message("Strict mode " .. state)
- return {success = true}
- end)
- -- Define minor mode
- lumacs.define_minor_mode("smartparens-mode", {
- lighter = "()",
- global = false,
- setup = function()
- smartparens.active = true
- editor:message("Smartparens enabled")
- end,
- cleanup = function()
- smartparens.active = false
- editor:message("Smartparens disabled")
- end
- })
- -- Store in lumacs namespace
- lumacs.smartparens = smartparens
- print("[smartparens] Package loaded")
- return smartparens
|