2019-12-28 12:16:32 +01:00
|
|
|
local core = require "core"
|
|
|
|
local command = require "core.command"
|
2020-05-02 12:14:07 +02:00
|
|
|
local config = require "core.config"
|
2019-12-28 12:16:32 +01:00
|
|
|
local search = require "core.doc.search"
|
2021-06-21 03:22:37 +02:00
|
|
|
local keymap = require "core.keymap"
|
2019-12-28 12:16:32 +01:00
|
|
|
local DocView = require "core.docview"
|
2021-06-21 03:22:37 +02:00
|
|
|
local CommandView = require "core.commandview"
|
|
|
|
local StatusView = require "core.statusview"
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2021-09-10 21:58:11 +02:00
|
|
|
local last_view, last_fn, last_text, last_sel
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2021-07-16 00:15:05 +02:00
|
|
|
local case_sensitive = config.find_case_sensitive or false
|
|
|
|
local find_regex = config.find_regex or false
|
2021-09-09 23:30:18 +02:00
|
|
|
local found_expression
|
2019-12-28 12:16:32 +01:00
|
|
|
|
|
|
|
local function doc()
|
2021-09-11 03:37:12 +02:00
|
|
|
local is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
|
2021-10-16 03:02:42 +02:00
|
|
|
return is_DocView and core.active_view.doc or (last_view and last_view.doc)
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
local function get_find_tooltip()
|
|
|
|
local rf = keymap.get_binding("find-replace:repeat-find")
|
2021-07-16 00:15:05 +02:00
|
|
|
local ti = keymap.get_binding("find-replace:toggle-sensitivity")
|
|
|
|
local tr = keymap.get_binding("find-replace:toggle-regex")
|
|
|
|
return (find_regex and "[Regex] " or "") ..
|
|
|
|
(case_sensitive and "[Sensitive] " or "") ..
|
2021-06-21 03:22:37 +02:00
|
|
|
(rf and ("Press " .. rf .. " to select the next match.") or "") ..
|
|
|
|
(ti and (" " .. ti .. " toggles case sensitivity.") or "") ..
|
2021-07-16 00:15:05 +02:00
|
|
|
(tr and (" " .. tr .. " toggles regex find.") or "")
|
2021-06-21 03:22:37 +02:00
|
|
|
end
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
local function update_preview(sel, search_fn, text)
|
2021-08-11 04:01:28 +02:00
|
|
|
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
|
|
|
sel[1], sel[2], text, case_sensitive, find_regex)
|
2021-06-21 03:22:37 +02:00
|
|
|
if ok and line1 and text ~= "" then
|
|
|
|
last_view.doc:set_selection(line2, col2, line1, col1)
|
|
|
|
last_view:scroll_to_line(line2, true)
|
2021-09-09 23:30:18 +02:00
|
|
|
found_expression = true
|
2021-06-21 03:22:37 +02:00
|
|
|
else
|
2021-08-29 03:14:12 +02:00
|
|
|
last_view.doc:set_selection(table.unpack(sel))
|
2021-09-09 23:30:18 +02:00
|
|
|
found_expression = false
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-07 18:33:58 +02:00
|
|
|
|
|
|
|
local function insert_unique(t, v)
|
|
|
|
local n = #t
|
|
|
|
for i = 1, n do
|
2022-06-23 05:14:37 +02:00
|
|
|
if t[i] == v then
|
|
|
|
table.remove(t, i)
|
|
|
|
break
|
|
|
|
end
|
2021-09-07 18:33:58 +02:00
|
|
|
end
|
2022-06-23 05:14:37 +02:00
|
|
|
table.insert(t, 1, v)
|
2021-09-07 18:33:58 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2019-12-28 12:16:32 +01:00
|
|
|
local function find(label, search_fn)
|
2021-09-10 21:58:11 +02:00
|
|
|
last_view, last_sel = core.active_view,
|
|
|
|
{ core.active_view.doc:get_selection() }
|
2021-12-31 13:53:01 +01:00
|
|
|
local text = last_view.doc:get_text(table.unpack(last_sel))
|
2021-09-09 23:30:18 +02:00
|
|
|
found_expression = false
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
core.status_view:show_tooltip(get_find_tooltip())
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2022-05-30 22:06:47 +02:00
|
|
|
core.command_view:enter(label, {
|
2022-06-01 19:34:46 +02:00
|
|
|
text = text,
|
|
|
|
select_text = true,
|
2022-06-01 18:34:11 +02:00
|
|
|
show_suggestions = false,
|
2022-05-30 22:06:47 +02:00
|
|
|
submit = function(text, item)
|
|
|
|
insert_unique(core.previous_find, text)
|
|
|
|
core.status_view:remove_tooltip()
|
|
|
|
if found_expression then
|
|
|
|
last_fn, last_text = search_fn, text
|
|
|
|
else
|
|
|
|
core.error("Couldn't find %q", text)
|
|
|
|
last_view.doc:set_selection(table.unpack(last_sel))
|
|
|
|
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
suggest = function(text)
|
|
|
|
update_preview(last_sel, search_fn, text)
|
2019-12-28 12:16:32 +01:00
|
|
|
last_fn, last_text = search_fn, text
|
2022-05-30 22:06:47 +02:00
|
|
|
return core.previous_find
|
|
|
|
end,
|
|
|
|
cancel = function(explicit)
|
|
|
|
core.status_view:remove_tooltip()
|
|
|
|
if explicit then
|
|
|
|
last_view.doc:set_selection(table.unpack(last_sel))
|
|
|
|
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
|
|
|
end
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
2022-05-30 22:06:47 +02:00
|
|
|
})
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2020-05-07 11:55:11 +02:00
|
|
|
local function replace(kind, default, fn)
|
2021-06-21 03:22:37 +02:00
|
|
|
core.status_view:show_tooltip(get_find_tooltip())
|
2022-05-30 22:06:47 +02:00
|
|
|
core.command_view:enter("Find To Replace " .. kind, {
|
2022-06-01 19:34:46 +02:00
|
|
|
text = default,
|
|
|
|
select_text = true,
|
2022-06-01 18:34:11 +02:00
|
|
|
show_suggestions = false,
|
2022-05-30 22:06:47 +02:00
|
|
|
submit = function(old)
|
|
|
|
insert_unique(core.previous_find, old)
|
|
|
|
|
|
|
|
local s = string.format("Replace %s %q With", kind, old)
|
|
|
|
core.command_view:enter(s, {
|
2022-06-01 19:34:46 +02:00
|
|
|
text = old,
|
|
|
|
select_text = true,
|
2022-06-01 18:34:11 +02:00
|
|
|
show_suggestions = false,
|
2022-05-30 22:06:47 +02:00
|
|
|
submit = function(new)
|
|
|
|
core.status_view:remove_tooltip()
|
|
|
|
insert_unique(core.previous_replace, new)
|
2022-06-23 00:15:20 +02:00
|
|
|
local results = doc():replace(function(text)
|
2022-05-30 22:06:47 +02:00
|
|
|
return fn(text, old, new)
|
|
|
|
end)
|
2022-06-23 00:15:20 +02:00
|
|
|
local n = 0
|
|
|
|
for _,v in pairs(results) do
|
|
|
|
n = n + v
|
|
|
|
end
|
2022-05-30 22:06:47 +02:00
|
|
|
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
|
|
|
end,
|
2022-06-01 18:19:33 +02:00
|
|
|
suggest = function() return core.previous_replace end,
|
|
|
|
cancel = function()
|
2022-05-30 22:06:47 +02:00
|
|
|
core.status_view:remove_tooltip()
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end,
|
2022-06-01 18:19:33 +02:00
|
|
|
suggest = function() return core.previous_find end,
|
|
|
|
cancel = function()
|
2021-06-21 03:22:37 +02:00
|
|
|
core.status_view:remove_tooltip()
|
2022-05-30 22:06:47 +02:00
|
|
|
end
|
|
|
|
})
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
|
2020-05-01 11:21:57 +02:00
|
|
|
local function has_selection()
|
2021-06-21 03:22:37 +02:00
|
|
|
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
2020-05-01 11:21:57 +02:00
|
|
|
end
|
|
|
|
|
2021-09-01 16:29:47 +02:00
|
|
|
local function has_unique_selection()
|
2021-10-16 03:02:42 +02:00
|
|
|
if not doc() then return false end
|
2021-08-28 19:42:06 +02:00
|
|
|
local text = nil
|
|
|
|
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
|
|
|
if line1 == line2 and col1 == col2 then return false end
|
|
|
|
local selection = doc():get_text(line1, col1, line2, col2)
|
|
|
|
if text ~= nil and text ~= selection then return false end
|
|
|
|
text = selection
|
|
|
|
end
|
|
|
|
return text ~= nil
|
|
|
|
end
|
|
|
|
|
2021-09-08 22:47:37 +02:00
|
|
|
local function is_in_selection(line, col, l1, c1, l2, c2)
|
|
|
|
if line < l1 or line > l2 then return false end
|
|
|
|
if line == l1 and col <= c1 then return false end
|
|
|
|
if line == l2 and col > c2 then return false end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local function is_in_any_selection(line, col)
|
|
|
|
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
|
|
|
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2021-09-10 21:54:50 +02:00
|
|
|
local function select_add_next(all)
|
2023-11-29 16:40:47 +01:00
|
|
|
local il1, ic1
|
|
|
|
for _, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
|
|
|
if not il1 then
|
|
|
|
il1, ic1 = l1, c1
|
|
|
|
end
|
2020-05-01 11:21:57 +02:00
|
|
|
local text = doc():get_text(l1, c1, l2, c2)
|
2021-08-28 19:42:06 +02:00
|
|
|
repeat
|
|
|
|
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
|
|
|
if l1 == il1 and c1 == ic1 then break end
|
2023-11-29 16:40:47 +01:00
|
|
|
if l2 and not is_in_any_selection(l2, c2) then
|
2021-09-08 22:47:37 +02:00
|
|
|
doc():add_selection(l2, c2, l1, c1)
|
|
|
|
if not all then
|
|
|
|
core.active_view:scroll_to_make_visible(l2, c2)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
2021-08-28 19:42:06 +02:00
|
|
|
until not all or not l2
|
2021-09-08 22:47:37 +02:00
|
|
|
if all then break end
|
2020-05-01 11:21:57 +02:00
|
|
|
end
|
2021-08-28 19:42:06 +02:00
|
|
|
end
|
|
|
|
|
2021-09-10 21:54:50 +02:00
|
|
|
local function select_next(reverse)
|
|
|
|
local l1, c1, l2, c2 = doc():get_selection(true)
|
|
|
|
local text = doc():get_text(l1, c1, l2, c2)
|
|
|
|
if reverse then
|
|
|
|
l1, c1, l2, c2 = search.find(doc(), l1, c1, text, { wrap = true, reverse = true })
|
|
|
|
else
|
2021-06-21 03:22:37 +02:00
|
|
|
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
2021-09-10 21:54:50 +02:00
|
|
|
end
|
|
|
|
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
|
|
|
end
|
|
|
|
|
|
|
|
command.add(has_unique_selection, {
|
|
|
|
["find-replace:select-next"] = select_next,
|
|
|
|
["find-replace:select-previous"] = function() select_next(true) end,
|
|
|
|
["find-replace:select-add-next"] = select_add_next,
|
|
|
|
["find-replace:select-add-all"] = function() select_add_next(true) end
|
2020-05-01 11:21:57 +02:00
|
|
|
})
|
|
|
|
|
2022-09-14 04:29:52 +02:00
|
|
|
command.add("core.docview!", {
|
2019-12-28 12:16:32 +01:00
|
|
|
["find-replace:find"] = function()
|
2021-09-10 21:58:11 +02:00
|
|
|
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex, find_reverse)
|
|
|
|
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex, reverse = find_reverse }
|
2019-12-28 12:16:32 +01:00
|
|
|
return search.find(doc, line, col, text, opt)
|
|
|
|
end)
|
|
|
|
end,
|
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
["find-replace:replace"] = function()
|
2024-02-11 18:51:12 +01:00
|
|
|
local l1, c1, l2, c2 = doc():get_selection()
|
|
|
|
local selected_text = doc():get_text(l1, c1, l2, c2)
|
|
|
|
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
|
|
|
if not find_regex then
|
|
|
|
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
|
|
|
end
|
|
|
|
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
|
|
|
return result, matches
|
|
|
|
end)
|
2019-12-28 12:16:32 +01:00
|
|
|
end,
|
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
["find-replace:replace-symbol"] = function()
|
|
|
|
local first = ""
|
|
|
|
if doc():has_selection() then
|
|
|
|
local text = doc():get_text(doc():get_selection())
|
|
|
|
first = text:match(config.symbol_pattern) or ""
|
|
|
|
end
|
|
|
|
replace("Symbol", first, function(text, old, new)
|
|
|
|
local n = 0
|
|
|
|
local res = text:gsub(config.symbol_pattern, function(sym)
|
|
|
|
if old == sym then
|
|
|
|
n = n + 1
|
|
|
|
return new
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
return res, n
|
|
|
|
end)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
|
|
|
local function valid_for_finding()
|
2023-03-03 11:42:31 +01:00
|
|
|
-- Allow using this while in the CommandView
|
|
|
|
if core.active_view:is(CommandView) and last_view then
|
|
|
|
return true, last_view
|
|
|
|
end
|
|
|
|
return core.active_view:is(DocView), core.active_view
|
2021-06-21 03:22:37 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
command.add(valid_for_finding, {
|
2023-03-03 11:42:31 +01:00
|
|
|
["find-replace:repeat-find"] = function(dv)
|
2019-12-28 12:16:32 +01:00
|
|
|
if not last_fn then
|
|
|
|
core.error("No find to continue from")
|
|
|
|
else
|
2023-03-03 11:42:31 +01:00
|
|
|
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true)
|
2023-11-29 16:40:47 +01:00
|
|
|
local line1, col1, line2, col2 = last_fn(dv.doc, sl2, sc2, last_text, case_sensitive, find_regex, false)
|
2019-12-28 12:16:32 +01:00
|
|
|
if line1 then
|
2023-03-03 11:42:31 +01:00
|
|
|
dv.doc:set_selection(line2, col2, line1, col1)
|
|
|
|
dv:scroll_to_line(line2, true)
|
2021-09-10 21:58:11 +02:00
|
|
|
else
|
|
|
|
core.error("Couldn't find %q", last_text)
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
|
2023-03-03 11:42:31 +01:00
|
|
|
["find-replace:previous-find"] = function(dv)
|
2021-09-10 21:58:11 +02:00
|
|
|
if not last_fn then
|
|
|
|
core.error("No find to continue from")
|
|
|
|
else
|
2023-03-03 11:42:31 +01:00
|
|
|
local sl1, sc1, sl2, sc2 = dv.doc:get_selection(true)
|
|
|
|
local line1, col1, line2, col2 = last_fn(dv.doc, sl1, sc1, last_text, case_sensitive, find_regex, true)
|
2021-09-10 21:58:11 +02:00
|
|
|
if line1 then
|
2023-03-03 11:42:31 +01:00
|
|
|
dv.doc:set_selection(line2, col2, line1, col1)
|
|
|
|
dv:scroll_to_line(line2, true)
|
2021-09-10 21:58:11 +02:00
|
|
|
else
|
|
|
|
core.error("Couldn't find %q", last_text)
|
|
|
|
end
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
end,
|
2021-06-21 03:22:37 +02:00
|
|
|
})
|
2019-12-28 12:16:32 +01:00
|
|
|
|
2021-06-21 03:22:37 +02:00
|
|
|
command.add("core.commandview", {
|
2021-07-16 00:15:05 +02:00
|
|
|
["find-replace:toggle-sensitivity"] = function()
|
|
|
|
case_sensitive = not case_sensitive
|
2021-06-21 03:22:37 +02:00
|
|
|
core.status_view:show_tooltip(get_find_tooltip())
|
2021-08-11 04:01:28 +02:00
|
|
|
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
2020-05-02 12:14:07 +02:00
|
|
|
end,
|
|
|
|
|
2021-07-16 00:15:05 +02:00
|
|
|
["find-replace:toggle-regex"] = function()
|
|
|
|
find_regex = not find_regex
|
2021-06-21 03:22:37 +02:00
|
|
|
core.status_view:show_tooltip(get_find_tooltip())
|
2021-08-11 04:01:28 +02:00
|
|
|
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
2021-06-21 03:22:37 +02:00
|
|
|
end
|
2019-12-28 12:16:32 +01:00
|
|
|
})
|