Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion lua/spectre/actions.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---@module 'spectre.actions'
local api = vim.api
local config = require('spectre.config')
local state = require('spectre.state')
Expand All @@ -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)
Expand All @@ -17,13 +23,19 @@ 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
end
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
Expand All @@ -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
Expand All @@ -50,6 +63,13 @@ M.select_entry = function()
end
end

---@class SpectreSearchState
---@field query SpectreQuery
---@field cwd string|nil
---@field options table<string, boolean>

---Get a copy of the current search state (query, cwd, options).
---@return SpectreSearchState
M.get_state = function()
local result = {
query = state.query,
Expand All @@ -59,13 +79,17 @@ 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
item.is_replace_finish = true
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
Expand All @@ -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
Expand All @@ -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')
Expand All @@ -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 = ''
Expand Down Expand Up @@ -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
Expand All @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.')
Expand All @@ -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))
Expand Down
42 changes: 42 additions & 0 deletions lua/spectre/config.lua
Original file line number Diff line number Diff line change
@@ -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<string, SpectreEngineOption>
---@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<string, string>
---@field mapping table<string, SpectreMapping>
---@field find_engine table<string, SpectreEngineConfig>
---@field replace_engine table<string, SpectreEngineConfig>
---@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'),
Expand Down
3 changes: 3 additions & 0 deletions lua/spectre/highlight.lua
Original file line number Diff line number Diff line change
@@ -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 })
Expand Down
6 changes: 6 additions & 0 deletions lua/spectre/replace/init.lua
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
6 changes: 6 additions & 0 deletions lua/spectre/search/init.lua
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
18 changes: 16 additions & 2 deletions lua/spectre/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, boolean>
---@field is_running boolean
---@field is_open boolean
---@field total_item table
---@field total_item table<number, SpectreEntry>
---@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<string, any>
---@field view table<string, any>
local state = {
-- current config
status_line = '',
Expand Down
27 changes: 26 additions & 1 deletion lua/spectre/state_utils.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,31 +29,48 @@ 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)
cfg.options_value = get_options(cfg)
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)
cfg.options_value = get_options(cfg)
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' },
Expand Down
Loading