-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nvim): Add fancy search/replace UI powered by nvim-spectre
- Loading branch information
1 parent
ec7e02f
commit d698cf7
Showing
7 changed files
with
502 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
nvim/lua/my/configure/spectre/components/search-tree.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
local n = require('nui-components') | ||
|
||
local function replace_handler(tree, node) | ||
return { | ||
on_done = function(result) | ||
if result.ref then | ||
node.ref = result.ref | ||
tree:render() | ||
end | ||
end, | ||
on_error = function(_) end, | ||
} | ||
end | ||
|
||
local function mappings(search_query, replace_query) | ||
local spectre_state_utils = require('spectre.state_utils') | ||
|
||
return function(component) | ||
return { | ||
{ | ||
mode = { 'n' }, | ||
key = 'r', | ||
handler = function() | ||
local tree = component:get_tree() | ||
local focused_node = component:get_focused_node() | ||
|
||
if not focused_node then | ||
return | ||
end | ||
|
||
local has_children = focused_node:has_children() | ||
|
||
if not has_children then | ||
local replacer_creator = spectre_state_utils.get_replace_creator() | ||
local replacer = | ||
replacer_creator:new(spectre_state_utils.get_replace_engine_config(), replace_handler(tree, focused_node)) | ||
|
||
local entry = focused_node.entry | ||
|
||
replacer:replace({ | ||
lnum = entry.lnum, | ||
col = entry.col, | ||
cwd = vim.fn.getcwd(), | ||
display_lnum = 0, | ||
filename = entry.filename, | ||
search_text = search_query:get_value(), | ||
replace_text = replace_query:get_value(), | ||
}) | ||
end | ||
end, | ||
}, | ||
} | ||
end | ||
end | ||
|
||
local function prepare_node(node, line, component) | ||
local _, devicons = pcall(require, 'nvim-web-devicons') | ||
local has_children = node:has_children() | ||
|
||
line:append(string.rep(' ', node:get_depth() - 1)) | ||
|
||
if has_children then | ||
local icon, icon_highlight = devicons.get_icon(node.text, string.match(node.text, '%a+$'), { default = true }) | ||
|
||
line:append(node:is_expanded() and ' ' or ' ', component:hl_group('SpectreIcon')) | ||
line:append(icon .. ' ', icon_highlight) | ||
line:append(node.text, component:hl_group('SpectreFileName')) | ||
|
||
return line | ||
end | ||
|
||
local is_replacing = #node.diff.replace > 0 | ||
local search_highlight_group = component:hl_group(is_replacing and 'SpectreSearchOldValue' or 'SpectreSearchValue') | ||
local default_text_highlight = component:hl_group('SpectreCodeLine') | ||
|
||
local _, empty_spaces = string.find(node.diff.text, '^%s*') | ||
local ref = node.ref | ||
|
||
if ref then | ||
line:append(' ', component:hl_group('SpectreReplaceSuccess')) | ||
end | ||
|
||
if #node.diff.search > 0 then | ||
local code_text = vim.trim(node.diff.text) | ||
|
||
vim.iter(ipairs(node.diff.search)):each(function(index, value) | ||
local start = value[1] - empty_spaces | ||
local end_ = value[2] - empty_spaces | ||
|
||
if index == 1 then | ||
line:append(string.sub(code_text, 1, start), default_text_highlight) | ||
end | ||
|
||
local search_text = string.sub(code_text, start + 1, end_) | ||
line:append(search_text, search_highlight_group) | ||
|
||
local replace_diff_value = node.diff.replace[index] | ||
|
||
if replace_diff_value then | ||
local replace_text = | ||
string.sub(code_text, replace_diff_value[1] + 1 - empty_spaces, replace_diff_value[2] - empty_spaces) | ||
line:append(replace_text, component:hl_group('SpectreSearchNewValue')) | ||
end_ = replace_diff_value[2] - empty_spaces | ||
end | ||
|
||
if index == #node.diff.search then | ||
line:append(string.sub(code_text, end_ + 1), default_text_highlight) | ||
end | ||
end) | ||
end | ||
|
||
return line | ||
end | ||
|
||
local function on_select(origin_winid) | ||
return function(node, component) | ||
local tree = component:get_tree() | ||
|
||
if node:has_children() then | ||
if node:is_expanded() then | ||
node:collapse() | ||
else | ||
node:expand() | ||
end | ||
|
||
return tree:render() | ||
end | ||
|
||
local entry = node.entry | ||
|
||
if vim.api.nvim_win_is_valid(origin_winid) then | ||
local escaped_filename = vim.fn.fnameescape(entry.filename) | ||
|
||
vim.api.nvim_set_current_win(origin_winid) | ||
vim.api.nvim_command([[execute "normal! m` "]]) | ||
vim.cmd('e ' .. escaped_filename) | ||
vim.api.nvim_win_set_cursor(0, { entry.lnum, entry.col - 1 }) | ||
end | ||
end | ||
end | ||
|
||
local function search_tree(props) | ||
return n.tree({ | ||
border_style = 'none', | ||
flex = 1, | ||
padding = { | ||
left = 1, | ||
right = 1, | ||
}, | ||
hidden = props.hidden, | ||
data = props.data, | ||
mappings = mappings(props.search_query, props.replace_query), | ||
prepare_node = prepare_node, | ||
on_select = on_select(props.origin_winid), | ||
}) | ||
end | ||
|
||
return search_tree |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
local spectre_search = require('spectre.search') | ||
local spectre_state = require('spectre.state') | ||
local spectre_state_utils = require('spectre.state_utils') | ||
local spectre_utils = require('spectre.utils') | ||
|
||
local Tree = require('nui.tree') | ||
|
||
local M = {} | ||
|
||
function M.process(options) | ||
options = options or {} | ||
|
||
return vim | ||
.iter(spectre_state.groups) | ||
:map(function(filename, group) | ||
local children = vim | ||
.iter(ipairs(group)) | ||
:map(function(_, entry) | ||
local id = tostring(math.random()) | ||
|
||
local diff = spectre_utils.get_hl_line_text({ | ||
search_query = options.search_query, | ||
replace_query = options.replace_query, | ||
search_text = entry.text, | ||
padding = 0, | ||
}, spectre_state.regex) | ||
|
||
return Tree.Node({ text = diff.text, _id = id, diff = diff, entry = entry }) | ||
end) | ||
:totable() | ||
|
||
local id = tostring(math.random()) | ||
local node = Tree.Node({ text = filename:gsub('^./', ''), _id = id }, children) | ||
|
||
node:expand() | ||
|
||
return node | ||
end) | ||
:totable() | ||
end | ||
|
||
local function search_handler(options, signal) | ||
local start_time = 0 | ||
local total = 0 | ||
|
||
spectre_state.groups = {} | ||
|
||
return { | ||
on_start = function() | ||
spectre_state.is_running = true | ||
start_time = vim.loop.hrtime() | ||
end, | ||
on_result = function(item) | ||
if not spectre_state.is_running then | ||
return | ||
end | ||
|
||
if not spectre_state.groups[item.filename] then | ||
spectre_state.groups[item.filename] = {} | ||
end | ||
|
||
table.insert(spectre_state.groups[item.filename], item) | ||
total = total + 1 | ||
end, | ||
on_error = function(_) end, | ||
on_finish = function() | ||
if not spectre_state.is_running then | ||
return | ||
end | ||
|
||
local end_time = (vim.loop.hrtime() - start_time) / 1E9 | ||
|
||
signal.search_results = M.process(options) | ||
signal.search_info = string.format('Total: %s match, time: %ss', total, end_time) | ||
|
||
spectre_state.finder_instance = nil | ||
spectre_state.is_running = false | ||
end, | ||
} | ||
end | ||
|
||
function M.stop() | ||
if not spectre_state.finder_instance then | ||
return | ||
end | ||
|
||
spectre_state.finder_instance:stop() | ||
spectre_state.finder_instance = nil | ||
end | ||
|
||
function M.search(options, signal) | ||
options = options or {} | ||
|
||
M.stop() | ||
|
||
local search_engine = spectre_search['rg'] | ||
spectre_state.options['ignore-case'] = not options.is_case_insensitive_checked | ||
spectre_state.finder_instance = | ||
search_engine:new(spectre_state_utils.get_search_engine_config(), search_handler(options, signal)) | ||
spectre_state.regex = require('spectre.regex.vim') | ||
|
||
pcall(function() | ||
spectre_state.finder_instance:search({ | ||
cwd = vim.fn.getcwd(), | ||
search_text = options.search_query, | ||
replace_query = options.replace_query, | ||
-- path = spectre_state.query.path, | ||
search_paths = #options.search_paths > 0 and options.search_paths or nil, | ||
}) | ||
end) | ||
end | ||
|
||
return M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
return { | ||
'nvim-pack/nvim-spectre', | ||
dependencies = { | ||
{ 'grapp-dev/nui-components.nvim', dependencies = { 'MunifTanjim/nui.nvim' } }, | ||
}, | ||
keys = { | ||
{ | ||
'<C-f>', | ||
function() | ||
require('my.configure.spectre.ui').toggle() | ||
end, | ||
desc = 'Global Search & Replace', | ||
}, | ||
}, | ||
opts = {}, | ||
} |
Oops, something went wrong.