117 lines
No EOL
3.8 KiB
Lua
117 lines
No EOL
3.8 KiB
Lua
local M = {}
|
|
|
|
-- Default options
|
|
local defaults = {
|
|
hotkey = "<leader>tq",
|
|
quote_chars = { "'", '"', "`" },
|
|
}
|
|
|
|
-- Initialize options with a deep copy of the defaults
|
|
M.options = vim.deepcopy(defaults)
|
|
|
|
--- Checks if a given character is one of the predefined quote characters.
|
|
--- @param char string The character to check.
|
|
--- @return boolean true if the character is a quote character, false otherwise.
|
|
local function is_defined_quote_char(char)
|
|
for _, qc in ipairs(M.options.quote_chars) do
|
|
if char == qc then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Finds the starting and ending indices of a quoted string on the current line
|
|
--- that contains the cursor.
|
|
--- @param line string The current line of text.
|
|
--- @param cursor_col number The column index of the cursor (0-based).
|
|
--- @return number|nil start_index The 0-based starting index of the quote, or nil if not found.
|
|
--- @return number|nil end_index The 0-based ending index of the quote, or nil if not found.
|
|
--- @return string|nil current_quote_char The character of the identified quote, or nil.
|
|
local function find_quoted_string_at_cursor(line, cursor_col)
|
|
local quote_positions = {}
|
|
for i = 1, #line do
|
|
local char = line:sub(i, i)
|
|
if is_defined_quote_char(char) then
|
|
table.insert(quote_positions, { char = char, index = i - 1 })
|
|
end
|
|
end
|
|
|
|
for i = 1, #quote_positions do
|
|
local start_quote = quote_positions[i]
|
|
for j = i + 1, #quote_positions do
|
|
local end_quote = quote_positions[j]
|
|
if start_quote.char == end_quote.char then
|
|
if cursor_col >= start_quote.index and cursor_col <= end_quote.index then
|
|
return start_quote.index, end_quote.index, start_quote.char
|
|
end
|
|
-- Found a pair, so we skip the inner quote for the next outer loop iteration
|
|
i = j
|
|
goto next_outer_loop
|
|
end
|
|
end
|
|
::next_outer_loop::
|
|
end
|
|
|
|
return nil, nil, nil
|
|
end
|
|
|
|
|
|
--- Cycles to the next quote character in the predefined list.
|
|
--- @param current_char string The current quote character.
|
|
--- @return string The next quote character.
|
|
local function get_next_quote_char(current_char)
|
|
local num_chars = #M.options.quote_chars
|
|
for i = 1, num_chars do
|
|
if M.options.quote_chars[i] == current_char then
|
|
return M.options.quote_chars[(i % num_chars) + 1]
|
|
end
|
|
end
|
|
return M.options.quote_chars[1]
|
|
end
|
|
|
|
--- Toggles the quotes around the string at the cursor position.
|
|
function M.toggle_quotes()
|
|
local current_line = vim.api.nvim_get_current_line()
|
|
local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0))
|
|
|
|
local start_idx, end_idx, current_char = find_quoted_string_at_cursor(current_line, cursor_col)
|
|
|
|
if not start_idx then
|
|
return
|
|
end
|
|
|
|
local next_char = get_next_quote_char(current_char)
|
|
|
|
local new_line =
|
|
current_line:sub(1, start_idx) ..
|
|
next_char ..
|
|
current_line:sub(start_idx + 2, end_idx) ..
|
|
next_char ..
|
|
current_line:sub(end_idx + 2)
|
|
|
|
vim.api.nvim_set_current_line(new_line)
|
|
vim.api.nvim_win_set_cursor(0, { cursor_row, cursor_col })
|
|
end
|
|
|
|
--- Merges user-provided options with default options.
|
|
--- @param user_options table|nil User-provided options
|
|
function M.setup(user_options)
|
|
user_options = user_options or {}
|
|
M.options = vim.tbl_deep_extend("force", vim.deepcopy(defaults), user_options)
|
|
|
|
-- Ensure quote_chars is never nil
|
|
if not M.options.quote_chars or #M.options.quote_chars == 0 then
|
|
M.options.quote_chars = vim.deepcopy(defaults.quote_chars)
|
|
end
|
|
|
|
local map_opts = {
|
|
desc = "JQuote: Swap quote characters",
|
|
silent = true,
|
|
noremap = true,
|
|
}
|
|
|
|
vim.keymap.set("n", M.options.hotkey, M.toggle_quotes, map_opts)
|
|
end
|
|
|
|
return M |