215 lines
5.1 KiB
Lua
215 lines
5.1 KiB
Lua
local wezterm = require("wezterm")
|
|
local state = require("plugins.sessionizer.src.state")
|
|
local command_builder = require("plugins.sessionizer.src.command_builder")
|
|
local utils = require("plugins.sessionizer.src.utils")
|
|
local notify = require("plugins.sessionizer.src.notify")
|
|
|
|
local M = {}
|
|
|
|
local _scan_cache = {}
|
|
local _meta_lookup = {}
|
|
local CONTROL_PATTERN = "[%z\r\n]"
|
|
|
|
local function deep_copy(list)
|
|
local copy = {}
|
|
for i, v in ipairs(list or {}) do
|
|
local project_copy = {}
|
|
for k, val in pairs(v) do
|
|
project_copy[k] = val
|
|
end
|
|
copy[i] = project_copy
|
|
end
|
|
return copy
|
|
end
|
|
|
|
local function clear_scan_cache()
|
|
for k in pairs(_scan_cache) do
|
|
_scan_cache[k] = nil
|
|
end
|
|
for k in pairs(_meta_lookup) do
|
|
_meta_lookup[k] = nil
|
|
end
|
|
end
|
|
|
|
local function cache_list(list)
|
|
state.cached_directories = list
|
|
state.cached_checksum = utils.checksum(list)
|
|
end
|
|
|
|
local function scan_base(base)
|
|
local key = tostring(base.path) .. ":" .. tostring(base.max_depth or "default")
|
|
if _scan_cache[key] then
|
|
if state.profiler_enabled then
|
|
wezterm.log_info(("[sessionizer] scan_base %s: cache_hit=true"):format(tostring(base.path)))
|
|
end
|
|
return _scan_cache[key]
|
|
end
|
|
|
|
return utils.profile("scan_base " .. tostring(base.path), function()
|
|
local paths = utils.resolve_glob_all(base.path)
|
|
local cmd = command_builder.build_cmd(base)
|
|
local backend = cmd[1] or ""
|
|
|
|
local ok, out = utils.retry_command(cmd, 3, 200)
|
|
if not ok then
|
|
local message = "Failed to scan: " .. tostring(base.path)
|
|
wezterm.log_error(message)
|
|
state.last_scan_error = message
|
|
return {}
|
|
end
|
|
|
|
local choices = {}
|
|
local out_lines = 0
|
|
local skipped_control = 0
|
|
|
|
for _, line in ipairs(wezterm.split_by_newlines(out)) do
|
|
out_lines = out_lines + 1
|
|
if line ~= "" then
|
|
if line:find(CONTROL_PATTERN) then
|
|
skipped_control = skipped_control + 1
|
|
wezterm.log_error("[sessionizer] Skipping path with control characters: " .. line)
|
|
else
|
|
local id = line
|
|
local label = line:gsub(wezterm.home_dir, "~")
|
|
label = label:gsub(CONTROL_PATTERN, "")
|
|
|
|
table.insert(choices, {
|
|
id = id,
|
|
label = label,
|
|
})
|
|
|
|
_meta_lookup[id] = {
|
|
workspace = utils.basename(line),
|
|
title = utils.basename(line),
|
|
path = line,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
_scan_cache[key] = choices
|
|
state.last_scan_error = nil
|
|
if state.profiler_enabled then
|
|
wezterm.log_info((
|
|
"[sessionizer] scan_base %s: cache_hit=false backend=%s glob_paths=%d lines=%d dirs=%d skipped_control=%d"
|
|
):format(
|
|
tostring(base.path),
|
|
tostring(backend),
|
|
#paths,
|
|
out_lines,
|
|
#choices,
|
|
skipped_control
|
|
))
|
|
end
|
|
return choices
|
|
end)
|
|
end
|
|
|
|
local function perform_scan()
|
|
return utils.profile("workspace_scan", function()
|
|
local list = {}
|
|
local seen = {}
|
|
local last_error = nil
|
|
local bases = #state.project_base
|
|
local total_dirs = 0
|
|
|
|
for _, base in ipairs(state.project_base) do
|
|
local ok, scanned = pcall(scan_base, base)
|
|
if ok and scanned then
|
|
total_dirs = total_dirs + #scanned
|
|
for _, folder in ipairs(scanned) do
|
|
if folder.id and not seen[folder.id] then
|
|
table.insert(list, folder)
|
|
seen[folder.id] = true
|
|
end
|
|
end
|
|
else
|
|
last_error = "Failed to scan project base: " .. tostring(base.path)
|
|
wezterm.log_error(last_error)
|
|
notify.show(last_error)
|
|
end
|
|
end
|
|
|
|
cache_list(list)
|
|
state.last_scan_error = last_error
|
|
|
|
if state.profiler_enabled then
|
|
wezterm.log_info((
|
|
"[sessionizer] workspace_scan: bases=%d total_dirs=%d unique_dirs=%d"
|
|
):format(bases, total_dirs, #list))
|
|
end
|
|
|
|
return list
|
|
end)
|
|
end
|
|
|
|
local function finish_scan(ok, result)
|
|
if not ok then
|
|
local message = "[sessionizer] Workspace scan failed: " .. tostring(result)
|
|
wezterm.log_error(message)
|
|
state.last_scan_error = message
|
|
notify.show(message)
|
|
end
|
|
|
|
state.scan_in_progress = false
|
|
if state.scan_pending then
|
|
state.scan_pending = false
|
|
M.refresh_async()
|
|
end
|
|
end
|
|
|
|
--- Kick off a background refresh of project directories without blocking the UI.
|
|
function M.refresh_async()
|
|
if state.scan_in_progress then
|
|
state.scan_pending = true
|
|
return
|
|
end
|
|
|
|
if state.config_changed then
|
|
clear_scan_cache()
|
|
state.config_changed = false
|
|
end
|
|
|
|
if #state.project_base == 0 then
|
|
cache_list({})
|
|
state.last_scan_error = "No project bases configured"
|
|
return
|
|
end
|
|
|
|
state.scan_in_progress = true
|
|
wezterm.time.call_after(0, function()
|
|
local ok, result = pcall(perform_scan)
|
|
finish_scan(ok, result)
|
|
end)
|
|
end
|
|
|
|
--- Get a list of cached project directories. Triggers a refresh if cache is empty/stale.
|
|
function M.all_dirs()
|
|
if state.cached_directories and utils.checksum(state.cached_directories) == state.cached_checksum then
|
|
return deep_copy(state.cached_directories)
|
|
end
|
|
|
|
if not state.scan_in_progress then
|
|
M.refresh_async()
|
|
end
|
|
|
|
return {}
|
|
end
|
|
|
|
--- Get cached project directories without triggering a refresh.
|
|
function M.cached_dirs()
|
|
if state.cached_directories and utils.checksum(state.cached_directories) == state.cached_checksum then
|
|
return deep_copy(state.cached_directories)
|
|
end
|
|
return {}
|
|
end
|
|
|
|
function M.get_metadata(id)
|
|
return _meta_lookup[id]
|
|
end
|
|
|
|
function M.clear_scan_cache()
|
|
clear_scan_cache()
|
|
end
|
|
|
|
return M
|