commit 056d3e901e089d6f7fbfc6da6f1824162d3c535e Author: jank Date: Mon Jul 28 19:32:36 2025 +0200 Initial commit diff --git a/lua/jquote/init.lua b/lua/jquote/init.lua new file mode 100644 index 0000000..8f71388 --- /dev/null +++ b/lua/jquote/init.lua @@ -0,0 +1,129 @@ +local M = {} + +M.options = { + hotkey = "tq", + quote_chars = { "'", '"', "`" }, +} + +---Merges user-provided options with default options. +---@param user_options table|nil User-provided options +function M.setup(user_options) + if user_options and type(user_options) == "table" then + for k, v in pairs(user_options) do + M.options[k] = v + end + end +end + +--- 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.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, using a predefined set of quote characters. +--- @param line string The current line of text. +--- @param cursor_col number The column index of the cursor (0-based). +--- @return number start_index The 0-based starting index of the quote, or -1 if not found. +--- @return number end_index The 0-based ending index of the quote, or -1 if not found. +--- @return string current_quote_char The character of the identified quote, or nil. +local function find_quoted_string_at_cursor(line, cursor_col) + local start_quote_index = -1 + local end_quote_index = -1 + local current_quote_char = nil + + -- Iterate through the line to find a matching pair of quotes that contain the cursor. + for i = 0, #line - 1 do + local char = line:sub(i + 1, i + 1) + + if not is_defined_quote_char(char) then + goto continue_loop -- Not a quote character, skip. + end + + -- If we haven't found an opening quote yet, this is a potential candidate. + if start_quote_index == -1 then + start_quote_index = i + current_quote_char = char + elseif char == current_quote_char then + -- Found a closing quote that matches the current opening quote. + end_quote_index = i + + -- Check if the cursor is within this identified quoted string (inclusive of quotes). + if (cursor_col >= start_quote_index and cursor_col <= end_quote_index) or (cursor_col <= start_quote_index and cursor_col <= end_quote_index) then + return start_quote_index, end_quote_index, current_quote_char + else + -- Cursor is not in this string, reset to look for the next potential string. + start_quote_index = -1 + end_quote_index = -1 + current_quote_char = nil + end + end + ::continue_loop:: + end + + -- No relevant quoted string found that contains the cursor. + return -1, -1, 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, or nil if current_char is not found. +local function get_next_quote_char(current_char) + local current_char_list_index = -1 + for i, qc in ipairs(M.quote_chars) do + if qc == current_char then + current_char_list_index = i + break + end + end + + if current_char_list_index == -1 then + return nil -- Current character not in our defined list. + end + + local next_char_list_index = (current_char_list_index % #M.quote_chars) + 1 + return M.quote_chars[next_char_list_index] +end + +--- Toggles the quotes around the string at the cursor position +--- by cycling through a predefined list of quote characters. +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 no quoted string containing the cursor was found, do nothing. + if start_idx == -1 or end_idx == -1 or not current_char then + return + end + + local next_char = get_next_quote_char(current_char) + if not next_char then + -- This should ideally not happen if current_char came from find_quoted_string_at_cursor + -- and QUOTE_CHARS is consistent, but acts as a safeguard. + return + end + + -- Reconstruct the line with the new quote characters. + 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) + + -- Update Neovim's buffer and cursor position. + vim.api.nvim_set_current_line(new_line) + vim.api.nvim_win_set_cursor(0, { cursor_row, cursor_col }) +end + +return M diff --git a/plugin/jquote.lua b/plugin/jquote.lua new file mode 100644 index 0000000..2d8bed0 --- /dev/null +++ b/plugin/jquote.lua @@ -0,0 +1,14 @@ +if vim.g.jquote_loaded then + return +end +vim.g.jquote_loaded = true + +local jquote = require("jquote") + +local map_opts = { + desc = "Swaps the quotes around", + silent = true, + noremap = true, +} + +vim.keymap.set("n", jquote.options.hotkey, jquote.toggle_quotes, map_opts)