2019-12-28 12:16:32 +01:00
|
|
|
local search = {}
|
|
|
|
|
|
|
|
local default_opt = {}
|
|
|
|
|
|
|
|
|
|
|
|
local function pattern_lower(str)
|
|
|
|
if str:sub(1, 1) == "%" then
|
|
|
|
return str
|
|
|
|
end
|
|
|
|
return str:lower()
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function init_args(doc, line, col, text, opt)
|
|
|
|
opt = opt or default_opt
|
|
|
|
line, col = doc:sanitize_position(line, col)
|
|
|
|
|
2021-06-02 21:27:00 +02:00
|
|
|
if opt.no_case and not opt.regex then
|
|
|
|
text = text:lower()
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
return doc, line, col, text, opt
|
|
|
|
end
|
|
|
|
|
2021-10-10 00:42:30 +02:00
|
|
|
-- 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
|
2021-09-10 21:32:34 +02:00
|
|
|
end
|
|
|
|
|
2021-10-10 00:42:30 +02:00
|
|
|
local function rfind(func, text, pattern, index, plain)
|
|
|
|
local s, e = func(text, pattern, 1, plain)
|
2021-09-10 21:32:34 +02:00
|
|
|
local last_s, last_e
|
|
|
|
if index < 0 then index = #text - index + 1 end
|
2021-10-10 00:42:30 +02:00
|
|
|
while e and e <= index do
|
2021-09-10 21:32:34 +02:00
|
|
|
last_s, last_e = s, e
|
2021-10-10 00:42:30 +02:00
|
|
|
s, e = func(text, pattern, s + 1, plain)
|
2021-09-10 21:32:34 +02:00
|
|
|
end
|
|
|
|
return last_s, last_e
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2019-12-28 12:16:32 +01:00
|
|
|
function search.find(doc, line, col, text, opt)
|
|
|
|
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
2021-10-25 00:18:20 +02:00
|
|
|
local plain = not opt.pattern
|
2021-10-10 00:42:30 +02:00
|
|
|
local pattern = text
|
|
|
|
local search_func = string.find
|
2021-06-02 21:27:00 +02:00
|
|
|
if opt.regex then
|
2021-10-10 00:42:30 +02:00
|
|
|
pattern = regex.compile(text, opt.no_case and "i" or "")
|
|
|
|
search_func = regex_func
|
2021-06-02 21:27:00 +02:00
|
|
|
end
|
2021-09-10 21:32:34 +02:00
|
|
|
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
|
2019-12-28 12:16:32 +01:00
|
|
|
local line_text = doc.lines[line]
|
2021-10-10 00:42:30 +02:00
|
|
|
if opt.no_case and not opt.regex then
|
|
|
|
line_text = line_text:lower()
|
|
|
|
end
|
|
|
|
local s, e
|
|
|
|
if opt.reverse then
|
2021-10-25 00:18:20 +02:00
|
|
|
s, e = rfind(search_func, line_text, pattern, col - 1, plain)
|
2021-06-02 21:27:00 +02:00
|
|
|
else
|
2021-10-25 00:18:20 +02:00
|
|
|
s, e = search_func(line_text, pattern, col, plain)
|
2021-10-10 00:42:30 +02:00
|
|
|
end
|
|
|
|
if s then
|
2023-11-29 16:40:47 +01:00
|
|
|
local line2 = line
|
|
|
|
-- If we've matched the newline too,
|
|
|
|
-- return until the initial character of the next line.
|
|
|
|
if e >= #doc.lines[line] then
|
|
|
|
line2 = line + 1
|
|
|
|
e = 0
|
|
|
|
end
|
|
|
|
-- Avoid returning matches that go beyond the last line.
|
|
|
|
-- This is needed to avoid selecting the "last" newline.
|
|
|
|
if line2 <= #doc.lines then
|
|
|
|
return line, s, line2, e + 1
|
|
|
|
end
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
2021-10-10 00:42:30 +02:00
|
|
|
col = opt.reverse and -1 or 1
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if opt.wrap then
|
2021-09-10 21:32:34 +02:00
|
|
|
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
|
2019-12-28 12:16:32 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
return search
|