diff --git a/lua/spectre/actions.lua b/lua/spectre/actions.lua index ee87ca9..7bbaa7b 100644 --- a/lua/spectre/actions.lua +++ b/lua/spectre/actions.lua @@ -1,3 +1,4 @@ +---@module 'spectre.actions' local api = vim.api local config = require('spectre.config') local state = require('spectre.state') @@ -7,6 +8,11 @@ local utils = require('spectre.utils') local M = {} +---Open a file at the given position, optionally in a specific window. +---@param filename string +---@param lnum number +---@param col number +---@param winid number? local open_file = function(filename, lnum, col, winid) if winid ~= nil then vim.fn.win_gotoid(winid) @@ -17,6 +23,9 @@ local open_file = function(filename, lnum, col, winid) pcall(api.nvim_win_set_cursor, 0, { lnum, col }) end +---Check if a filename is an absolute path. +---@param filename string +---@return boolean local is_absolute = function(filename) if vim.loop.os_uname().sysname == 'Windows_NT' then return string.find(filename, '%a:\\') == 1 @@ -24,6 +33,9 @@ local is_absolute = function(filename) return string.sub(filename, 1, 1) == '/' end +---Resolve a filename to an absolute path using the current working directory. +---@param filename string +---@return string local get_file_path = function(filename) -- if the path is absolute, return as is if is_absolute(filename) then @@ -38,6 +50,7 @@ local get_file_path = function(filename) return vim.fn.expand(state.cwd) .. Path.path.sep .. filename end +---Open the file for the search result under the cursor. M.select_entry = function() local t = M.get_current_entry() if t == nil then @@ -50,6 +63,13 @@ M.select_entry = function() end end +---@class SpectreSearchState +---@field query SpectreQuery +---@field cwd string|nil +---@field options table + +---Get a copy of the current search state (query, cwd, options). +---@return SpectreSearchState M.get_state = function() local result = { query = state.query, @@ -59,6 +79,8 @@ M.get_state = function() return vim.deepcopy(result) end +---Mark an entry as finished (already replaced). +---@param display_lnum number M.set_entry_finish = function(display_lnum) local item = state.total_item[display_lnum + 1] if item then @@ -66,6 +88,8 @@ M.set_entry_finish = function(display_lnum) end end +---Get the search result entry at the current cursor position. +---@return SpectreEntry|nil M.get_current_entry = function() if not state.total_item then return @@ -79,6 +103,8 @@ M.get_current_entry = function() end end +---Get all active (non-disabled) search result entries. +---@return SpectreEntry[] M.get_all_entries = function() local entries = {} for _, item in pairs(state.total_item) do @@ -91,6 +117,8 @@ M.get_all_entries = function() return entries end +---Send all search results to the quickfix list. +---@return SpectreEntry[] M.send_to_qf = function() local entries = M.get_all_entries() vim.fn.setqflist(entries, 'r') @@ -107,7 +135,7 @@ M.send_to_qf = function() return entries end --- input that comand to run on vim +---Build and feed a vim substitute command for the current search/replace. M.replace_cmd = function() M.send_to_qf() local replace_cmd = '' @@ -135,6 +163,7 @@ M.replace_cmd = function() end end +---Run replace for the entry at the current cursor position. M.run_current_replace = function() local entry = M.get_current_entry() if entry then @@ -146,6 +175,8 @@ end local is_running = false +---Run replace on the given entries (or all entries if nil). +---@param entries SpectreEntry[]|nil M.run_replace = function(entries) if is_running == true then print('it is already running') @@ -223,6 +254,8 @@ M.delete_line_file_current = function() end end +---Delete lines from files for the given entries (or all entries if nil). +---@param entries SpectreEntry[]|nil M.run_delete_line = function(entries) entries = entries or M.get_all_entries() local done_item = 0 @@ -289,6 +322,7 @@ M.run_delete_line = function(entries) end end +---Show a picker to select from configured search templates. M.select_template = function() if not state.user_config.open_template or #state.user_config.open_template == 0 then vim.notify('You need to set open_template on setup function.') @@ -311,6 +345,7 @@ M.select_template = function() end) end +---Copy the current line's text content to a register. M.copy_current_line = function() local line_text = vim.api.nvim_get_current_line() local row = unpack(vim.api.nvim_win_get_cursor(0)) diff --git a/lua/spectre/config.lua b/lua/spectre/config.lua index a7d9e43..2568193 100644 --- a/lua/spectre/config.lua +++ b/lua/spectre/config.lua @@ -1,6 +1,48 @@ local api = vim.api +---@class SpectreMapping +---@field map string +---@field cmd string +---@field desc string + +---@class SpectreEngineOption +---@field value string +---@field icon string +---@field desc string + +---@class SpectreEngineConfig +---@field cmd string +---@field args string[]|nil +---@field options table +---@field warn boolean? + ---@class SpectreConfig +---@field filetype string +---@field namespace number +---@field namespace_ui number +---@field namespace_header number +---@field namespace_status number +---@field namespace_result number +---@field lnum_UI number +---@field line_result number +---@field line_sep_start string +---@field result_padding string +---@field line_sep string +---@field color_devicons boolean +---@field open_cmd string|function +---@field live_update boolean +---@field lnum_for_results boolean +---@field highlight table +---@field mapping table +---@field find_engine table +---@field replace_engine table +---@field default table +---@field replace_vim_cmd string +---@field use_trouble_qf boolean +---@field is_open_target_win boolean +---@field is_insert_mode boolean +---@field is_block_ui_break boolean +---@field open_template table[] local config = { filetype = 'spectre_panel', namespace = api.nvim_create_namespace('SEARCH_PANEL'), diff --git a/lua/spectre/highlight.lua b/lua/spectre/highlight.lua index 02d68a3..23516ed 100644 --- a/lua/spectre/highlight.lua +++ b/lua/spectre/highlight.lua @@ -1,5 +1,8 @@ +---@module 'spectre.highlight' local M = {} +---Set the default highlight groups for the Spectre UI. +---Uses `default = true` so user-defined highlights take priority. M.set_hl = function() vim.api.nvim_set_hl(0, 'SpectreHeader', { link = 'Comment', default = true }) vim.api.nvim_set_hl(0, 'SpectreBody', { link = 'String', default = true }) diff --git a/lua/spectre/replace/init.lua b/lua/spectre/replace/init.lua index 5afba68..c42c439 100644 --- a/lua/spectre/replace/init.lua +++ b/lua/spectre/replace/init.lua @@ -1,5 +1,11 @@ +---@module 'spectre.replace' +---Replace engine factory. Lazily loads replace engine modules by name. local base = require('spectre.replace.base') local r = {} + +---Get a replace engine by name. +---@param key string Engine name (e.g., "sed", "sd", "oxi") +---@return table engine Replace engine creator r.get = function(key) assert(key ~= nil, 'key no nil') local ok, engine = pcall(require, 'spectre.replace.' .. key) diff --git a/lua/spectre/search/init.lua b/lua/spectre/search/init.lua index 7874a49..24f312c 100644 --- a/lua/spectre/search/init.lua +++ b/lua/spectre/search/init.lua @@ -1,5 +1,11 @@ +---@module 'spectre.search' +---Search engine factory. Lazily loads search engine modules by name. local base = require('spectre.search.base') local s = {} + +---Get a search engine by name. +---@param key string Engine name (e.g., "rg", "ag") +---@return table engine Search engine creator s.get = function(key) assert(key ~= nil, 'key no nil') local ok, engine = pcall(require, 'spectre.search.' .. key) diff --git a/lua/spectre/state.lua b/lua/spectre/state.lua index 1c9f7b6..e9dc664 100644 --- a/lua/spectre/state.lua +++ b/lua/spectre/state.lua @@ -4,21 +4,35 @@ ---@field path string ---@field is_file boolean +---@class SpectreEntry +---@field filename string +---@field lnum number +---@field col number +---@field text string +---@field search_text string? +---@field replace_text string? +---@field display_lnum number? +---@field disable boolean? +---@field is_replace_finish boolean? + ---@class SpectreState ---@field user_config SpectreConfig ---@field status_line string ---@field cwd string|nil ---@field query SpectreQuery ---@field query_backup SpectreQuery|nil ----@field options table +---@field options table ---@field is_running boolean ---@field is_open boolean ----@field total_item table +---@field total_item table ---@field regex any ---@field finder_instance any|nil ---@field async_id number ---@field target_winid number ---@field target_bufnr number +---@field bufnr number|nil +---@field vt table +---@field view table local state = { -- current config status_line = '', diff --git a/lua/spectre/state_utils.lua b/lua/spectre/state_utils.lua index 0279f0b..51d6227 100644 --- a/lua/spectre/state_utils.lua +++ b/lua/spectre/state_utils.lua @@ -1,16 +1,24 @@ +---@module 'spectre.state_utils' local state = require('spectre.state') local search_engine = require('spectre.search') local replace_engine = require('spectre.replace') local M = {} +---Get the search engine creator for the configured find command. +---@return table M.get_finder_creator = function() return search_engine[state.user_config.default.find.cmd] end +---Get the replace engine creator for the configured replace command. +---@return table M.get_replace_creator = function() return replace_engine[state.user_config.default.replace.cmd] end +---Get enabled option values for a given engine configuration. +---@param cfg SpectreEngineConfig Engine configuration with options +---@return string[] options_value List of active option values local get_options = function(cfg) local options_value = {} for key, value in pairs(state.options) do @@ -21,6 +29,8 @@ local get_options = function(cfg) return options_value end +---Get the replace engine configuration with active options applied. +---@return SpectreEngineConfig M.get_replace_engine_config = function() local cfg = state.user_config.replace_engine[state.user_config.default.replace.cmd] or {} cfg = vim.deepcopy(cfg) @@ -28,6 +38,8 @@ M.get_replace_engine_config = function() return cfg end +---Get the search engine configuration with active options applied. +---@return SpectreEngineConfig M.get_search_engine_config = function() local cfg = state.user_config.find_engine[state.user_config.default.find.cmd] or {} cfg = vim.deepcopy(cfg) @@ -35,17 +47,30 @@ M.get_search_engine_config = function() return cfg end +---Get the current user configuration. +---@return SpectreConfig M.config = function() return state.user_config end +---Check if a search option is enabled. +---@param key string +---@return boolean M.has_options = function(key) return state.options[key] == true end +---@class SpectreStatusLineOptions +---@field separator string? +---@field seprator string? Typo-compatible alias for separator +---@field main_color string? + +---Generate a status line configuration for integration with status line plugins. +---@param opt SpectreStatusLineOptions|nil Options with separator, main_color fields +---@return table spectre Status line configuration table M.status_line = function(opt) opt = opt or {} - local slant_right = opt.seprator or '' + local slant_right = opt.separator or opt.seprator or '' local main_color = opt.main_color or 'black' local spectre = { filetypes = { 'spectre_panel' }, diff --git a/lua/spectre/utils.lua b/lua/spectre/utils.lua index 8c7dde1..077db74 100644 --- a/lua/spectre/utils.lua +++ b/lua/spectre/utils.lua @@ -1,3 +1,4 @@ +---@module 'spectre.utils' local api = vim.api local M = {} @@ -7,6 +8,16 @@ local config = require('spectre.config') local state = require('spectre.state') local _regex_file_line = [[([^:]+):(%d+):(%d+):(.*)]] + +---@class GrepParsedLine +---@field filename string +---@field lnum number +---@field col number +---@field text string + +---Parse a grep-style output line into its components. +---@param query string A line of output in format "file:lnum:col:text" +---@return GrepParsedLine|nil parsed Parsed result with filename, lnum, col, text fields M.parse_line_grep = function(query) local t = { text = query } local _, _, filename, lnum, col, text = string.find(t.text, _regex_file_line) @@ -31,24 +42,33 @@ M.parse_line_grep = function(query) return t end --- help /ordinary-atom --- help non-greedy --- escape >=< to \> \= \< but if it dont have \> +---Escape vim magic mode special characters in a query string. +---@param query string +---@return string M.escape_vim_magic = function(query) query = string.gsub(query, '@', '\\@') local regex = [=[(\\)@<=](\\)@!]=] return vim.fn.substitute(query, '\\v' .. regex, [[\\\0]], 'g') end --- escape_chars but don't escape it if have slash before or after ! +---Escape special regex characters in a query string. +---@param query string +---@return string M.escape_chars = function(query) local regex = [=[(\\)@') @@ -190,7 +227,9 @@ M.get_hl_line_text = function(opts, regex) end return result end ---- remove item duplicate on table +---Remove duplicate values from a list. +---@param tbl table +---@return table M.tbl_remove_dup = function(tbl) local hash = {} local res = {} @@ -203,6 +242,10 @@ M.tbl_remove_dup = function(tbl) return res end +---Flatten a nested table into a single-level list. +---Uses vim.iter on Neovim 0.11+, falls back to vim.tbl_flatten. +---@param t table +---@return table M.tbl_flatten = function(t) return vim.fn.has('nvim-0.11') == 1 and vim.iter(t):flatten(math.huge):totable() or vim.tbl_flatten(t) end