Merge pull request #520 from Guldoman/select_previous

Add reverse search and some related commands
This commit is contained in:
Francesco 2021-10-10 10:01:55 +02:00 committed by GitHub
commit c179f909e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 51 deletions

View File

@ -7,15 +7,15 @@ local DocView = require "core.docview"
local CommandView = require "core.commandview"
local StatusView = require "core.statusview"
local max_last_finds = 50
local last_finds, last_view, last_fn, last_text, last_sel
local last_view, last_fn, last_text, last_sel
local case_sensitive = config.find_case_sensitive or false
local find_regex = config.find_regex or false
local found_expression
local function doc()
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
local is_DocView = core.active_view:is(DocView) and not core.active_view:is(CommandView)
return is_DocView and core.active_view.doc or last_view.doc
end
local function get_find_tooltip()
@ -53,8 +53,8 @@ end
local function find(label, search_fn)
last_view, last_sel, last_finds = core.active_view,
{ core.active_view.doc:get_selection() }, {}
last_view, last_sel = core.active_view,
{ core.active_view.doc:get_selection() }
local text = last_view.doc:get_text(unpack(last_sel))
found_expression = false
@ -142,7 +142,7 @@ local function is_in_any_selection(line, col)
return false
end
local function select_next(all)
local function select_add_next(all)
local il1, ic1 = doc():get_selection(true)
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
local text = doc():get_text(l1, c1, l2, c2)
@ -161,21 +161,28 @@ local function select_next(all)
end
end
command.add(has_unique_selection, {
["find-replace:select-next"] = function()
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
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
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l2 then doc():set_selection(l2, c2, l1, c1) end
end,
["find-replace:select-add-next"] = function() select_next(false) end,
["find-replace:select-add-all"] = function() select_next(true) end
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
})
command.add("core.docview", {
["find-replace:find"] = function()
find("Find Text", function(doc, line, col, text, case_sensitive, find_regex)
local opt = { wrap = true, no_case = not case_sensitive, regex = find_regex }
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 }
return search.find(doc, line, col, text, opt)
end)
end,
@ -221,29 +228,29 @@ command.add(valid_for_finding, {
core.error("No find to continue from")
else
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc2, last_text, case_sensitive, find_regex, false)
if line1 then
if last_view.doc ~= doc() then
last_finds = {}
end
if #last_finds >= max_last_finds then
table.remove(last_finds, 1)
end
table.insert(last_finds, { sl1, sc1, sl2, sc2 })
doc():set_selection(line2, col2, line1, col1)
last_view:scroll_to_line(line2, true)
else
core.error("Couldn't find %q", last_text)
end
end
end,
["find-replace:previous-find"] = function()
local sel = table.remove(last_finds)
if not sel or doc() ~= last_view.doc then
core.error("No previous finds")
return
if not last_fn then
core.error("No find to continue from")
else
local sl1, sc1, sl2, sc2 = doc():get_selection(true)
local line1, col1, line2, col2 = last_fn(doc(), sl1, sc1, last_text, case_sensitive, find_regex, true)
if line1 then
doc():set_selection(line2, col2, line1, col1)
last_view:scroll_to_line(line2, true)
else
core.error("Couldn't find %q", last_text)
end
end
doc():set_selection(table.unpack(sel))
last_view:scroll_to_line(sel[3], true)
end,
})

View File

@ -22,37 +22,61 @@ local function init_args(doc, line, col, text, opt)
return doc, line, col, text, opt
end
-- This function is needed to uniform the behavior of
-- `regex:cmatch` and `string.find`.
local function regex_func(text, re, index, _)
local s, e = re:cmatch(text, index)
return s, e and e - 1
end
local function rfind(func, text, pattern, index, plain)
local s, e = func(text, pattern, 1, plain)
local last_s, last_e
if index < 0 then index = #text - index + 1 end
while e and e <= index do
last_s, last_e = s, e
s, e = func(text, pattern, s + 1, plain)
end
return last_s, last_e
end
function search.find(doc, line, col, text, opt)
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
local re
local pattern = text
local search_func = string.find
if opt.regex then
re = regex.compile(text, opt.no_case and "i" or "")
pattern = regex.compile(text, opt.no_case and "i" or "")
search_func = regex_func
end
for line = line, #doc.lines do
local start, finish, step = line, #doc.lines, 1
if opt.reverse then
start, finish, step = line, 1, -1
end
for line = start, finish, step do
local line_text = doc.lines[line]
if opt.regex then
local s, e = re:cmatch(line_text, col)
if s then
return line, s, line, e
end
col = 1
else
if opt.no_case then
line_text = line_text:lower()
end
local s, e = line_text:find(text, col, true)
if s then
return line, s, line, e + 1
end
col = 1
if opt.no_case and not opt.regex then
line_text = line_text:lower()
end
local s, e
if opt.reverse then
s, e = rfind(search_func, line_text, pattern, col - 1)
else
s, e = search_func(line_text, pattern, col)
end
if s then
return line, s, line, e + 1
end
col = opt.reverse and -1 or 1
end
if opt.wrap then
opt = { no_case = opt.no_case, regex = opt.regex }
return search.find(doc, 1, 1, text, opt)
opt = { no_case = opt.no_case, regex = opt.regex, reverse = opt.reverse }
if opt.reverse then
return search.find(doc, #doc.lines, #doc.lines[#doc.lines], text, opt)
else
return search.find(doc, 1, 1, text, opt)
end
end
end

View File

@ -170,6 +170,7 @@ keymap.add_direct {
["ctrl+a"] = "doc:select-all",
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
["ctrl+f3"] = "find-replace:select-next",
["ctrl+shift+f3"] = "find-replace:select-previous",
["ctrl+l"] = "doc:select-lines",
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
["ctrl+/"] = "doc:toggle-line-comments",