Use regular expressions instead of Lua patterns for find and replace editor commands. Syntax files can now use regex or Lua patterns as before keeping backward compatibility for plugins.
314 lines
8.9 KiB
Lua
314 lines
8.9 KiB
Lua
-- mod-version:1 -- lite-xl 1.16
|
|
local core = require "core"
|
|
local common = require "core.common"
|
|
local keymap = require "core.keymap"
|
|
local command = require "core.command"
|
|
local style = require "core.style"
|
|
local View = require "core.view"
|
|
|
|
|
|
local ResultsView = View:extend()
|
|
|
|
|
|
function ResultsView:new(text, fn)
|
|
ResultsView.super.new(self)
|
|
self.scrollable = true
|
|
self.brightness = 0
|
|
self:begin_search(text, fn)
|
|
end
|
|
|
|
|
|
function ResultsView:get_name()
|
|
return "Search Results"
|
|
end
|
|
|
|
|
|
local function find_all_matches_in_file(t, filename, fn)
|
|
local fp = io.open(filename)
|
|
if not fp then return t end
|
|
local n = 1
|
|
for line in fp:lines() do
|
|
local s = fn(line)
|
|
if s then
|
|
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
|
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
|
local start_index = math.max(s - 80, 1)
|
|
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
|
core.redraw = true
|
|
end
|
|
if n % 100 == 0 then coroutine.yield() end
|
|
n = n + 1
|
|
core.redraw = true
|
|
end
|
|
fp:close()
|
|
end
|
|
|
|
|
|
function ResultsView:begin_search(text, fn)
|
|
self.search_args = { text, fn }
|
|
self.results = {}
|
|
self.last_file_idx = 1
|
|
self.query = text
|
|
self.searching = true
|
|
self.selected_idx = 0
|
|
|
|
core.add_thread(function()
|
|
local i = 1
|
|
for dir_name, file in core.get_project_files() do
|
|
if file.type == "file" then
|
|
local path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
|
find_all_matches_in_file(self.results, path .. file.filename, fn)
|
|
end
|
|
self.last_file_idx = i
|
|
i = i + 1
|
|
end
|
|
self.searching = false
|
|
self.brightness = 100
|
|
core.redraw = true
|
|
end, self.results)
|
|
|
|
self.scroll.to.y = 0
|
|
end
|
|
|
|
|
|
function ResultsView:refresh()
|
|
self:begin_search(table.unpack(self.search_args))
|
|
end
|
|
|
|
|
|
function ResultsView:on_mouse_moved(mx, my, ...)
|
|
ResultsView.super.on_mouse_moved(self, mx, my, ...)
|
|
self.selected_idx = 0
|
|
for i, item, x,y,w,h in self:each_visible_result() do
|
|
if mx >= x and my >= y and mx < x + w and my < y + h then
|
|
self.selected_idx = i
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function ResultsView:on_mouse_pressed(...)
|
|
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
|
if not caught then
|
|
self:open_selected_result()
|
|
end
|
|
end
|
|
|
|
|
|
function ResultsView:open_selected_result()
|
|
local res = self.results[self.selected_idx]
|
|
if not res then
|
|
return
|
|
end
|
|
core.try(function()
|
|
local dv = core.root_view:open_doc(core.open_doc(res.file))
|
|
core.root_view.root_node:update_layout()
|
|
dv.doc:set_selection(res.line, res.col)
|
|
dv:scroll_to_line(res.line, false, true)
|
|
end)
|
|
end
|
|
|
|
|
|
function ResultsView:update()
|
|
self:move_towards("brightness", 0, 0.1)
|
|
ResultsView.super.update(self)
|
|
end
|
|
|
|
|
|
function ResultsView:get_results_yoffset()
|
|
return style.font:get_height() + style.padding.y * 3
|
|
end
|
|
|
|
|
|
function ResultsView:get_line_height()
|
|
return style.padding.y + style.font:get_height()
|
|
end
|
|
|
|
|
|
function ResultsView:get_scrollable_size()
|
|
return self:get_results_yoffset() + #self.results * self:get_line_height()
|
|
end
|
|
|
|
|
|
function ResultsView:get_visible_results_range()
|
|
local lh = self:get_line_height()
|
|
local oy = self:get_results_yoffset()
|
|
local min = math.max(1, math.floor((self.scroll.y - oy) / lh))
|
|
return min, min + math.floor(self.size.y / lh) + 1
|
|
end
|
|
|
|
|
|
function ResultsView:each_visible_result()
|
|
return coroutine.wrap(function()
|
|
local lh = self:get_line_height()
|
|
local x, y = self:get_content_offset()
|
|
local min, max = self:get_visible_results_range()
|
|
y = y + self:get_results_yoffset() + lh * (min - 1)
|
|
for i = min, max do
|
|
local item = self.results[i]
|
|
if not item then break end
|
|
coroutine.yield(i, item, x, y, self.size.x, lh)
|
|
y = y + lh
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
function ResultsView:scroll_to_make_selected_visible()
|
|
local h = self:get_line_height()
|
|
local y = self:get_results_yoffset() + h * (self.selected_idx - 1)
|
|
self.scroll.to.y = math.min(self.scroll.to.y, y)
|
|
self.scroll.to.y = math.max(self.scroll.to.y, y + h - self.size.y)
|
|
end
|
|
|
|
|
|
function ResultsView:draw()
|
|
self:draw_background(style.background)
|
|
|
|
-- status
|
|
local ox, oy = self:get_content_offset()
|
|
local x, y = ox + style.padding.x, oy + style.padding.y
|
|
local files_number = core.project_files_number()
|
|
local per = files_number and self.last_file_idx / files_number or 1
|
|
local text
|
|
if self.searching then
|
|
if files_number then
|
|
text = string.format("Searching %d%% (%d of %d files, %d matches) for %q...",
|
|
per * 100, self.last_file_idx, files_number,
|
|
#self.results, self.query)
|
|
else
|
|
text = string.format("Searching (%d files, %d matches) for %q...",
|
|
self.last_file_idx, #self.results, self.query)
|
|
end
|
|
else
|
|
text = string.format("Found %d matches for %q",
|
|
#self.results, self.query)
|
|
end
|
|
local color = common.lerp(style.text, style.accent, self.brightness / 100)
|
|
renderer.draw_text(style.font, text, x, y, color)
|
|
|
|
-- horizontal line
|
|
local yoffset = self:get_results_yoffset()
|
|
local x = ox + style.padding.x
|
|
local w = self.size.x - style.padding.x * 2
|
|
local h = style.divider_size
|
|
local color = common.lerp(style.dim, style.text, self.brightness / 100)
|
|
renderer.draw_rect(x, oy + yoffset - style.padding.y, w, h, color)
|
|
if self.searching then
|
|
renderer.draw_rect(x, oy + yoffset - style.padding.y, w * per, h, style.text)
|
|
end
|
|
|
|
-- results
|
|
local y1, y2 = self.position.y, self.position.y + self.size.y
|
|
for i, item, x,y,w,h in self:each_visible_result() do
|
|
local color = style.text
|
|
if i == self.selected_idx then
|
|
color = style.accent
|
|
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
|
end
|
|
x = x + style.padding.x
|
|
local text = string.format("%s at line %d (col %d): ", item.file, item.line, item.col)
|
|
x = common.draw_text(style.font, style.dim, text, "left", x, y, w, h)
|
|
x = common.draw_text(style.code_font, color, item.text, "left", x, y, w, h)
|
|
end
|
|
|
|
self:draw_scrollbar()
|
|
end
|
|
|
|
|
|
local function begin_search(text, fn)
|
|
if text == "" then
|
|
core.error("Expected non-empty string")
|
|
return
|
|
end
|
|
local rv = ResultsView(text, fn)
|
|
core.root_view:get_active_node_default():add_view(rv)
|
|
end
|
|
|
|
|
|
command.add(nil, {
|
|
["project-search:find"] = function()
|
|
core.command_view:enter("Find Text In Project", function(text)
|
|
text = text:lower()
|
|
begin_search(text, function(line_text)
|
|
return line_text:lower():find(text, nil, true)
|
|
end)
|
|
end)
|
|
end,
|
|
|
|
["project-search:find-regex"] = function()
|
|
core.command_view:enter("Find Regex In Project", function(text)
|
|
local re = regex.compile(text, "i")
|
|
begin_search(text, function(line_text)
|
|
return regex.cmatch(re, line_text)
|
|
end)
|
|
end)
|
|
end,
|
|
|
|
["project-search:fuzzy-find"] = function()
|
|
core.command_view:enter("Fuzzy Find Text In Project", function(text)
|
|
begin_search(text, function(line_text)
|
|
return common.fuzzy_match(line_text, text) and 1
|
|
end)
|
|
end)
|
|
end,
|
|
})
|
|
|
|
|
|
command.add(ResultsView, {
|
|
["project-search:select-previous"] = function()
|
|
local view = core.active_view
|
|
view.selected_idx = math.max(view.selected_idx - 1, 1)
|
|
view:scroll_to_make_selected_visible()
|
|
end,
|
|
|
|
["project-search:select-next"] = function()
|
|
local view = core.active_view
|
|
view.selected_idx = math.min(view.selected_idx + 1, #view.results)
|
|
view:scroll_to_make_selected_visible()
|
|
end,
|
|
|
|
["project-search:open-selected"] = function()
|
|
core.active_view:open_selected_result()
|
|
end,
|
|
|
|
["project-search:refresh"] = function()
|
|
core.active_view:refresh()
|
|
end,
|
|
|
|
["project-search:move-to-previous-page"] = function()
|
|
local view = core.active_view
|
|
view.scroll.to.y = view.scroll.to.y - view.size.y
|
|
end,
|
|
|
|
["project-search:move-to-next-page"] = function()
|
|
local view = core.active_view
|
|
view.scroll.to.y = view.scroll.to.y + view.size.y
|
|
end,
|
|
|
|
["project-search:move-to-start-of-doc"] = function()
|
|
local view = core.active_view
|
|
view.scroll.to.y = 0
|
|
end,
|
|
|
|
["project-search:move-to-end-of-doc"] = function()
|
|
local view = core.active_view
|
|
view.scroll.to.y = view:get_scrollable_size()
|
|
end
|
|
})
|
|
|
|
keymap.add {
|
|
["f5"] = "project-search:refresh",
|
|
["ctrl+shift+f"] = "project-search:find",
|
|
["up"] = "project-search:select-previous",
|
|
["down"] = "project-search:select-next",
|
|
["return"] = "project-search:open-selected",
|
|
["pageup"] = "project-search:move-to-previous-page",
|
|
["pagedown"] = "project-search:move-to-next-page",
|
|
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
|
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
|
["home"] = "project-search:move-to-start-of-doc",
|
|
["end"] = "project-search:move-to-end-of-doc"
|
|
}
|