Skip to content

Commit 9b529ff

Browse files
committed
Show replace word when replaced (WIP)
1 parent ce45d47 commit 9b529ff

5 files changed

Lines changed: 248 additions & 52 deletions

File tree

lua/spectre/actions.lua

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@ M.get_state = function()
5959
end
6060

6161
M.set_entry_finish = function(display_lnum)
62-
local item = state.total_item[display_lnum + 1]
62+
-- Safety check: ensure display_lnum is valid and state.total_item exists
63+
if not display_lnum or not state.total_item then return end
64+
65+
-- In Lua, arrays are 1-indexed but display_lnum might be 0-indexed
66+
local index = display_lnum + 1
67+
68+
-- Check if the item exists in total_item
69+
local item = state.total_item[index]
6370
if item then
6471
item.is_replace_finish = true
6572
end
@@ -171,8 +178,15 @@ function M.run_replace(entries)
171178
local replacer_creator = state_utils.get_replace_creator()
172179
local replacer = replacer_creator:new(state_utils.get_replace_engine_config(), {
173180
on_done = function(result)
174-
if result.ref then
181+
if result.ref and result.ref.display_lnum ~= nil then
182+
-- Set the entry as finished and mark it as replaced
175183
M.set_entry_finish(result.ref.display_lnum)
184+
185+
-- Add a safety check before accessing state.total_item
186+
if state.total_item and state.total_item[result.ref.display_lnum] then
187+
state.total_item[result.ref.display_lnum].is_replace = true
188+
end
189+
176190
-- Update UI by adding a checkmark to the line
177191
local bufnr = api.nvim_get_current_buf()
178192
local line = result.ref.display_lnum
@@ -183,16 +197,41 @@ function M.run_replace(entries)
183197
0,
184198
{ virt_text = { { '', 'String' } }, virt_text_pos = 'eol' }
185199
)
186-
-- Trigger renderer redraw
200+
201+
-- If we have a renderer, trigger a full redraw
187202
if state.renderer then
188-
print("redrawing")
189-
state.renderer:redraw()
203+
-- Update the node in the UI
204+
local tree = state.renderer:get_component_by_id("results-tree")
205+
-- Check if tree exists and has the get_nodes method
206+
if tree and type(tree) == "table" and type(tree.get_nodes) == "function" then
207+
local success, nodes = pcall(function()
208+
return tree:get_nodes()
209+
end)
210+
211+
if success and nodes then
212+
for _, node in ipairs(nodes) do
213+
-- Add safety check for node.display_lnum
214+
if node.display_lnum and node.display_lnum == result.ref.display_lnum then
215+
node.is_done = true
216+
-- This triggers the prepare_node function
217+
pcall(function() state.renderer:redraw() end)
218+
break
219+
end
220+
end
221+
else
222+
-- If we can't get nodes, just redraw
223+
pcall(function() state.renderer:redraw() end)
224+
end
225+
else
226+
-- If tree doesn't exist or doesn't have get_nodes, just redraw
227+
pcall(function() state.renderer:redraw() end)
228+
end
190229
end
191230
end
192231
end,
193232
on_error = function(result)
194-
if result.ref then
195-
vim.notify("Error replacing: " .. result.value, vim.log.levels.ERROR)
233+
if result.ref and result.ref.display_lnum ~= nil then
234+
vim.notify("Error replacing: " .. (result.value or "unknown error"), vim.log.levels.ERROR)
196235
-- Add error mark to the line
197236
local bufnr = api.nvim_get_current_buf()
198237
local line = result.ref.display_lnum
@@ -205,8 +244,10 @@ function M.run_replace(entries)
205244
)
206245
-- Trigger renderer redraw
207246
if state.renderer then
208-
print("redrawing")
209-
state.renderer:redraw()
247+
-- Make sure renderer has redraw method
248+
if type(state.renderer) == "table" and type(state.renderer.redraw) == "function" then
249+
pcall(function() state.renderer:redraw() end)
250+
end
210251
end
211252
end
212253
end,
@@ -245,44 +286,54 @@ M.run_delete_line = function(entries)
245286
local replacer_creator = state_utils.get_replace_creator()
246287
local replacer = replacer_creator:new(state_utils.get_replace_engine_config(), {
247288
on_done = function(result)
248-
if result.ref then
289+
if result.ref and result.ref.display_lnums then
249290
done_item = done_item + 1
250291
local value = result.ref
251292
state.status_line = 'Delete line: ' .. done_item .. ' Error:' .. error_item
252293
for _, display_lnum in ipairs(value.display_lnums) do
253-
M.set_entry_finish(display_lnum)
254-
api.nvim_buf_set_extmark(
255-
state.bufnr,
256-
config.namespace,
257-
display_lnum,
258-
0,
259-
{ virt_text = { { '󰄲 DONE', 'String' } }, virt_text_pos = 'eol' }
260-
)
294+
if display_lnum ~= nil then
295+
M.set_entry_finish(display_lnum)
296+
api.nvim_buf_set_extmark(
297+
state.bufnr,
298+
config.namespace,
299+
display_lnum,
300+
0,
301+
{ virt_text = { { '󰄲 DONE', 'String' } }, virt_text_pos = 'eol' }
302+
)
303+
end
261304
end
262305
-- Trigger renderer redraw
263306
if state.renderer then
264-
state.renderer:redraw()
307+
-- Make sure renderer has redraw method
308+
if type(state.renderer) == "table" and type(state.renderer.redraw) == "function" then
309+
pcall(function() state.renderer:redraw() end)
310+
end
265311
end
266312
end
267313
end,
268314
on_error = function(result)
269-
if result.ref then
315+
if result.ref and result.ref.display_lnums then
270316
error_item = error_item + 1
271317
local value = result.ref
272318
state.status_line = 'Delete line: ' .. done_item .. ' Error:' .. error_item
273319
for _, display_lnum in ipairs(value.display_lnums) do
274-
M.set_entry_finish(display_lnum)
275-
api.nvim_buf_set_extmark(
276-
state.bufnr,
277-
config.namespace,
278-
display_lnum,
279-
0,
280-
{ virt_text = { { '󰄱 ERROR', 'Error' } }, virt_text_pos = 'eol' }
281-
)
320+
if display_lnum ~= nil then
321+
M.set_entry_finish(display_lnum)
322+
api.nvim_buf_set_extmark(
323+
state.bufnr,
324+
config.namespace,
325+
display_lnum,
326+
0,
327+
{ virt_text = { { '󰄱 ERROR', 'Error' } }, virt_text_pos = 'eol' }
328+
)
329+
end
282330
end
283331
-- Trigger renderer redraw
284332
if state.renderer then
285-
state.renderer:redraw()
333+
-- Make sure renderer has redraw method
334+
if type(state.renderer) == "table" and type(state.renderer.redraw) == "function" then
335+
pcall(function() state.renderer:redraw() end)
336+
end
286337
end
287338
end
288339
end,

lua/spectre/state_utils.lua

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,46 @@ M.get_replace_creator = function()
1111
return replace_engine[state.user_config.default.replace.cmd]
1212
end
1313

14+
-- Get a regex engine instance, initializing it if needed
15+
M.get_regex = function()
16+
-- If regex is not initialized yet, initialize it
17+
if not state.regex then
18+
-- Default to vim regex as a fallback
19+
local regex_engine_name = 'vim'
20+
21+
-- Try to use the current replace engine's regex
22+
if state.user_config and state.user_config.default and state.user_config.default.replace and state.user_config.default.replace.cmd then
23+
local replace_cmd = state.user_config.default.replace.cmd
24+
25+
-- Map replace engines to regex engines
26+
if replace_cmd == 'oxi' then
27+
regex_engine_name = 'rust'
28+
elseif replace_cmd == 'sed' then
29+
regex_engine_name = 'vim'
30+
elseif replace_cmd == 'sd' then
31+
regex_engine_name = 'rust'
32+
end
33+
end
34+
35+
-- Require the regex engine
36+
local success, regex = pcall(require, 'spectre.regex.' .. regex_engine_name)
37+
if success then
38+
state.regex = regex
39+
40+
-- Initialize options if available
41+
local cfg = M.get_replace_engine_config()
42+
if cfg and cfg.options_value then
43+
state.regex.change_options(cfg.options_value)
44+
end
45+
else
46+
-- Fallback to vim regex if the preferred engine couldn't be loaded
47+
state.regex = require('spectre.regex.vim')
48+
end
49+
end
50+
51+
return state.regex
52+
end
53+
1454
local get_options = function(cfg)
1555
local options_value = {}
1656
for key, value in pairs(state.options) do

lua/spectre/ui.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ M.render_line = function(bufnr, namespace, text_opts, view_opts, regex)
2121
search_text = text_opts.search_text,
2222
show_search = view_opts.show_search,
2323
show_replace = view_opts.show_replace,
24+
is_replace = text_opts.is_replace,
2425
}, regex)
2526
local end_lnum = text_opts.is_replace == true and text_opts.lnum + 1 or text_opts.lnum
2627

lua/spectre/ui/nui_components/init.lua

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ local function create_search_ui()
8282
on_select = function(node, component)
8383
if node.is_done ~= nil then
8484
node.is_done = not node.is_done
85-
-- component:redraw()
8685
end
8786
end,
8887
on_focus = function()
@@ -104,16 +103,28 @@ local function create_search_ui()
104103
-- Add search highlighting if there's a search query
105104
if state.query and state.query.search_query and #state.query.search_query > 0 then
106105
for i, line in ipairs(lines) do
107-
local matches = utils.match_text_line(state.query.search_query, line, 0)
106+
-- Safely get matches with error handling
107+
local matches = {}
108+
local success, result = pcall(function()
109+
return utils.match_text_line(state.query.search_query, line, 0)
110+
end)
111+
112+
if success and type(result) == "table" then
113+
matches = result
114+
end
115+
108116
for _, match in ipairs(matches) do
109-
api.nvim_buf_add_highlight(
110-
preview_bufnr,
111-
preview_namespace,
112-
state.user_config.highlight.search,
113-
i - 1,
114-
match[1],
115-
match[2]
116-
)
117+
-- Safely add highlight
118+
pcall(function()
119+
api.nvim_buf_add_highlight(
120+
preview_bufnr,
121+
preview_namespace,
122+
state.user_config.highlight.search,
123+
i - 1,
124+
match[1],
125+
match[2]
126+
)
127+
end)
117128
end
118129
end
119130
end
@@ -141,14 +152,14 @@ local function create_search_ui()
141152
if has_devicons then
142153
icon = '󰱒'
143154
end
144-
line:append(' ' .. icon .. ' ', hl)
155+
line:append(' ' .. icon .. ' ', hl)
145156
else
146157
local icon = ""
147158
local hl = "Comment"
148159
if has_devicons then
149-
icon = ''
160+
icon = ''
150161
end
151-
line:append(' ' .. icon .. ' ', hl)
162+
line:append(' ' .. icon .. ' ', hl)
152163
end
153164
end
154165

@@ -158,19 +169,87 @@ local function create_search_ui()
158169

159170
-- Add search highlighting if there's a search query
160171
if state.query and state.query.search_query and #state.query.search_query > 0 and node.text then
161-
local matches = utils.match_text_line(state.query.search_query, node.text, 0)
172+
-- Safely get matches with error handling
173+
local matches = {}
174+
local success, result = pcall(function()
175+
return utils.match_text_line(state.query.search_query, node.text, 0)
176+
end)
177+
178+
if success and type(result) == "table" then
179+
matches = result
180+
end
181+
162182
local last_pos = 0
163183
local max_width = vim.api.nvim_win_get_width(0) -
164184
15 -- Leave some space for icons and padding
165185
local truncated_text = utils.truncate(node.text, max_width)
166186

187+
-- Find if this node has been replaced
188+
local is_replaced = false
189+
if state.total_item and node.display_lnum ~= nil then
190+
for _, item in ipairs(state.total_item) do
191+
if item and item.display_lnum and item.display_lnum == node.display_lnum and item.is_replace then
192+
is_replaced = true
193+
break
194+
end
195+
end
196+
end
197+
167198
for _, match in ipairs(matches) do
168199
-- Add text before the match
169200
if match[1] > last_pos then
170201
line:append(truncated_text:sub(last_pos + 1, match[1]))
171202
end
203+
172204
-- Add highlighted match
173205
line:append(truncated_text:sub(match[1] + 1, match[2]), state.user_config.highlight.search)
206+
207+
-- Add replacement preview if exists and not replaced yet
208+
if state.query.replace_query and #state.query.replace_query > 0 and not is_replaced then
209+
-- Get the regex engine with safety check
210+
local regex = nil
211+
local success, result = pcall(state_utils.get_regex)
212+
if success then
213+
regex = result
214+
else
215+
-- Fallback to vim regex
216+
regex = require('spectre.regex.vim')
217+
end
218+
219+
-- Calculate replace_match with error handling
220+
local replace_match = {}
221+
success, result = pcall(function()
222+
return utils.get_hl_line_text({
223+
search_query = state.query.search_query,
224+
replace_query = state.query.replace_query,
225+
search_text = truncated_text:sub(match[1] + 1, match[2]),
226+
show_search = true,
227+
show_replace = true
228+
}, regex).replace
229+
end)
230+
231+
if success then
232+
replace_match = result
233+
end
234+
235+
if type(replace_match) == "table" and #replace_match > 0 then
236+
-- Calculate replace_text with error handling
237+
local replace_text = ""
238+
success, result = pcall(function()
239+
return " → (" .. utils.get_hl_line_text({
240+
search_query = state.query.search_query,
241+
replace_query = state.query.replace_query,
242+
search_text = truncated_text:sub(match[1] + 1, match[2]),
243+
}, regex).text .. ")"
244+
end)
245+
246+
if success then
247+
replace_text = result
248+
line:append(replace_text, state.user_config.highlight.replace)
249+
end
250+
end
251+
end
252+
174253
last_pos = match[2]
175254
end
176255
-- Add remaining text after last match
@@ -225,7 +304,6 @@ local function create_search_ui()
225304
on_press = function()
226305
vim.schedule(function()
227306
require('spectre.actions').run_replace()
228-
M.on_search_change()
229307
end)
230308
end,
231309
})
@@ -312,7 +390,8 @@ function M.search(query)
312390
col = result.col,
313391
lnum = result.lnum,
314392
text = string.format("%d:%d: %s", result.lnum, result.col, result.text),
315-
is_done = false
393+
is_done = false,
394+
display_lnum = #state.total_item
316395
})
317396
table.insert(results, entry)
318397
-- Store the entry in state.total_item with all required fields

0 commit comments

Comments
 (0)