Compare commits

...

3 commits

Author SHA1 Message Date
9622219679
feat: Auto jump forewards for the next quotes
All checks were successful
/ Release (push) Successful in 57s
BREAKING CHANGE: Automatically jump towards next pair if not in a pair
2025-07-28 20:59:49 +02:00
828c62a0a6
chore: Add semantic releases
All checks were successful
/ Release (push) Successful in 1m47s
2025-07-28 20:55:06 +02:00
6e09ffe4ff
chore: Add author 2025-07-28 20:50:01 +02:00
3 changed files with 54 additions and 30 deletions

View file

@ -0,0 +1,15 @@
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance
steps:
- name: Create Release
uses: https://git.kjan.de/actions/semantic-release@main
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

View file

@ -1,7 +1,6 @@
--- JQuote: A Neovim plugin for cycling through quote characters --- JQuote: A Neovim plugin for cycling through quote characters
--- @module jquote --- @module jquote
--- @author Author Name --- @author Jan
--- @version 1.0.0
--- @license MIT --- @license MIT
local M = {} local M = {}
@ -24,7 +23,6 @@ M.options = vim.deepcopy(DEFAULT_OPTIONS)
--- Plugin metadata --- Plugin metadata
--- @type table --- @type table
M._version = "1.0.0"
M._name = "jquote" M._name = "jquote"
--- Validates if a character is one of the configured quote characters --- Validates if a character is one of the configured quote characters
@ -35,7 +33,7 @@ local function is_defined_quote_char(char)
if type(char) ~= "string" or #char ~= 1 then if type(char) ~= "string" or #char ~= 1 then
return false return false
end end
for _, quote_char in ipairs(M.options.quote_chars) do for _, quote_char in ipairs(M.options.quote_chars) do
if char == quote_char then if char == quote_char then
return true return true
@ -58,14 +56,14 @@ local function find_quoted_string_at_cursor(line, cursor_col)
--- @type table[] Array of quote position records --- @type table[] Array of quote position records
local quote_positions = {} local quote_positions = {}
-- Scan line for quote characters and record their positions -- Scan line for quote characters and record their positions
for i = 1, #line do for i = 1, #line do
local char = line:sub(i, i) local char = line:sub(i, i)
if is_defined_quote_char(char) then if is_defined_quote_char(char) then
table.insert(quote_positions, { table.insert(quote_positions, {
char = char, char = char,
index = i - 1 -- Convert to 0-based indexing index = i - 1 -- Convert to 0-based indexing
}) })
end end
end end
@ -75,13 +73,13 @@ local function find_quoted_string_at_cursor(line, cursor_col)
local start_quote = quote_positions[i] local start_quote = quote_positions[i]
for j = i + 1, #quote_positions do for j = i + 1, #quote_positions do
local end_quote = quote_positions[j] local end_quote = quote_positions[j]
-- Match opening and closing quotes of same type -- Match opening and closing quotes of same type
if start_quote.char == end_quote.char then if start_quote.char == end_quote.char then
if cursor_col >= start_quote.index and cursor_col <= end_quote.index then 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, start_quote.char return start_quote.index, end_quote.index, start_quote.char
end end
-- Skip to next pair to avoid nested matches -- Skip to next pair to avoid nested matches
i = j i = j
goto continue_outer_loop goto continue_outer_loop
@ -101,9 +99,9 @@ local function get_next_quote_char(current_char)
if type(current_char) ~= "string" then if type(current_char) ~= "string" then
return M.options.quote_chars[1] return M.options.quote_chars[1]
end end
local quote_count = #M.options.quote_chars local quote_count = #M.options.quote_chars
for i = 1, quote_count do for i = 1, quote_count do
if M.options.quote_chars[i] == current_char then if M.options.quote_chars[i] == current_char then
-- Cycle to next character (wrapping around to first if at end) -- Cycle to next character (wrapping around to first if at end)
@ -111,7 +109,7 @@ local function get_next_quote_char(current_char)
return M.options.quote_chars[next_index] return M.options.quote_chars[next_index]
end end
end end
-- Fallback to first quote character if current not found -- Fallback to first quote character if current not found
return M.options.quote_chars[1] return M.options.quote_chars[1]
end end
@ -125,13 +123,13 @@ function M.toggle_quotes()
vim.notify("JQuote: Failed to get current line", vim.log.levels.ERROR) vim.notify("JQuote: Failed to get current line", vim.log.levels.ERROR)
return false return false
end end
local success_cursor, cursor_pos = pcall(vim.api.nvim_win_get_cursor, 0) local success_cursor, cursor_pos = pcall(vim.api.nvim_win_get_cursor, 0)
if not success_cursor or type(cursor_pos) ~= "table" or #cursor_pos < 2 then if not success_cursor or type(cursor_pos) ~= "table" or #cursor_pos < 2 then
vim.notify("JQuote: Failed to get cursor position", vim.log.levels.ERROR) vim.notify("JQuote: Failed to get cursor position", vim.log.levels.ERROR)
return false return false
end end
local cursor_row, cursor_col = cursor_pos[1], cursor_pos[2] local cursor_row, cursor_col = cursor_pos[1], cursor_pos[2]
local start_idx, end_idx, current_char = find_quoted_string_at_cursor(current_line, cursor_col) local start_idx, end_idx, current_char = find_quoted_string_at_cursor(current_line, cursor_col)
@ -160,7 +158,7 @@ function M.toggle_quotes()
vim.notify("JQuote: Failed to update line", vim.log.levels.ERROR) vim.notify("JQuote: Failed to update line", vim.log.levels.ERROR)
return false return false
end end
-- Restore cursor position -- Restore cursor position
pcall(vim.api.nvim_win_set_cursor, 0, { cursor_row, cursor_col }) pcall(vim.api.nvim_win_set_cursor, 0, { cursor_row, cursor_col })
return true return true
@ -175,27 +173,27 @@ local function validate_options(options)
if type(options) ~= "table" then if type(options) ~= "table" then
return false, "Options must be a table" return false, "Options must be a table"
end end
if options.hotkey and type(options.hotkey) ~= "string" then if options.hotkey and type(options.hotkey) ~= "string" then
return false, "hotkey must be a string" return false, "hotkey must be a string"
end end
if options.quote_chars then if options.quote_chars then
if type(options.quote_chars) ~= "table" then if type(options.quote_chars) ~= "table" then
return false, "quote_chars must be a table" return false, "quote_chars must be a table"
end end
if #options.quote_chars == 0 then if #options.quote_chars == 0 then
return false, "quote_chars cannot be empty" return false, "quote_chars cannot be empty"
end end
for i, char in ipairs(options.quote_chars) do for i, char in ipairs(options.quote_chars) do
if type(char) ~= "string" or #char ~= 1 then if type(char) ~= "string" or #char ~= 1 then
return false, string.format("quote_chars[%d] must be a single character string", i) return false, string.format("quote_chars[%d] must be a single character string", i)
end end
end end
end end
return true, nil return true, nil
end end
@ -204,23 +202,23 @@ end
--- @return boolean success Whether setup completed successfully --- @return boolean success Whether setup completed successfully
function M.setup(user_options) function M.setup(user_options)
user_options = user_options or {} user_options = user_options or {}
-- Validate user options -- Validate user options
local is_valid, error_msg = validate_options(user_options) local is_valid, error_msg = validate_options(user_options)
if not is_valid then if not is_valid then
vim.notify(string.format("JQuote setup error: %s", error_msg), vim.log.levels.ERROR) vim.notify(string.format("JQuote setup error: %s", error_msg), vim.log.levels.ERROR)
return false return false
end end
-- Merge with defaults using safe deep extend -- Merge with defaults using safe deep extend
local success, merged_options = pcall(vim.tbl_deep_extend, "force", vim.deepcopy(DEFAULT_OPTIONS), user_options) local success, merged_options = pcall(vim.tbl_deep_extend, "force", vim.deepcopy(DEFAULT_OPTIONS), user_options)
if not success then if not success then
vim.notify("JQuote: Failed to merge configuration options", vim.log.levels.ERROR) vim.notify("JQuote: Failed to merge configuration options", vim.log.levels.ERROR)
return false return false
end end
M.options = merged_options M.options = merged_options
-- Defensive check for quote_chars integrity -- Defensive check for quote_chars integrity
if not M.options.quote_chars or #M.options.quote_chars == 0 then if not M.options.quote_chars or #M.options.quote_chars == 0 then
vim.notify("JQuote: quote_chars corrupted, restoring defaults", vim.log.levels.WARN) vim.notify("JQuote: quote_chars corrupted, restoring defaults", vim.log.levels.WARN)
@ -239,7 +237,7 @@ function M.setup(user_options)
vim.notify(string.format("JQuote: Failed to set keymap for '%s'", M.options.hotkey), vim.log.levels.ERROR) vim.notify(string.format("JQuote: Failed to set keymap for '%s'", M.options.hotkey), vim.log.levels.ERROR)
return false return false
end end
vim.notify(string.format("JQuote: Initialized with hotkey '%s'", M.options.hotkey), vim.log.levels.INFO) vim.notify(string.format("JQuote: Initialized with hotkey '%s'", M.options.hotkey), vim.log.levels.INFO)
return true return true
end end
@ -248,13 +246,12 @@ end
--- @return table health_info Plugin health information --- @return table health_info Plugin health information
function M.health() function M.health()
local health_info = { local health_info = {
version = M._version,
options = M.options, options = M.options,
quote_chars_count = M.options.quote_chars and #M.options.quote_chars or 0, quote_chars_count = M.options.quote_chars and #M.options.quote_chars or 0,
hotkey_configured = M.options.hotkey ~= nil, hotkey_configured = M.options.hotkey ~= nil,
} }
return health_info return health_info
end end
return M return M

12
release.config.cjs Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
branches: ['main'],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/changelog',
["@saithodev/semantic-release-gitea", {
"giteaUrl": "https://git.kjan.de"
}],
],
};