From c71ec003543d431f5a61b30c47f64e9713ecd653 Mon Sep 17 00:00:00 2001 From: nightwatch75 <30896720+nightwatch75@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:49:12 +0200 Subject: [PATCH] =?UTF-8?q?Add=20nightwatch75/file-search=200.0.9=20?= =?UTF-8?q?=E2=80=94=20fuzzy=20file=20search=20(bar=20widget=20+=20panel?= =?UTF-8?q?=20+=20launcher=20provider)=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add nightwatch75/file-search 0.0.7 Co-Authored-By: Claude Fable 5 * file-search: backtick dependency names in Requirements (CI check) Co-Authored-By: Claude Fable 5 * file-search 0.0.8: address review — safe index records, atomic cache writes, command dependencies, index fingerprint Co-Authored-By: Claude Fable 5 * file-search 0.0.8: min_noctalia → plugin_api = 3 (manifest format change) Co-Authored-By: Claude Fable 5 * file-search 0.0.9: move index to noctalia.pluginDataDir() Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Massimiliano Co-authored-by: Claude Fable 5 Co-authored-by: nightwatch75 --- file-search/README.md | 130 +++++++++++ file-search/file-search.luau | 70 ++++++ file-search/launcher.luau | 278 ++++++++++++++++++++++ file-search/panel.luau | 387 +++++++++++++++++++++++++++++++ file-search/plugin.toml | 78 +++++++ file-search/thumbnail.webp | Bin 0 -> 57884 bytes file-search/translations/en.json | 30 +++ 7 files changed, 973 insertions(+) create mode 100644 file-search/README.md create mode 100644 file-search/file-search.luau create mode 100644 file-search/launcher.luau create mode 100644 file-search/panel.luau create mode 100644 file-search/plugin.toml create mode 100644 file-search/thumbnail.webp create mode 100644 file-search/translations/en.json diff --git a/file-search/README.md b/file-search/README.md new file mode 100644 index 0000000..2117ea3 --- /dev/null +++ b/file-search/README.md @@ -0,0 +1,130 @@ +# File Search + +A [noctalia](https://github.com/noctalia-dev/noctalia) v5 bar plugin: fuzzy +search files and folders as you type, with [fzf](https://github.com/junegunn/fzf) +as the matching subsystem. Click the bar glyph to open a search panel; picking +a result opens it with the system MIME association (`xdg-open`) — directories +open in your file manager. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `nightwatch75/file-search` | +| Entries | Bar widget: `file-search`; panel: `panel`; launcher provider: `launcher` | +| Launcher Prefix | `/fs` | + +## Usage + +Add the `file-search` widget from Noctalia's widget picker and click it to +open the search panel. You can also open the panel directly or bind it in +your compositor: + +```sh +noctalia msg panel-toggle nightwatch75/file-search:panel +``` + +| Action | Effect | +|--------------|-------------------------------------------------| +| Left click | Open/close the search panel | +| Right click | Open the search folder in the file manager | +| Middle click | Copy the search folder path to the clipboard | + +In the panel: + +| Key | Action | +|---------|-------------------------------------| +| `Enter` | Open the top match | +| `Esc` | Close the panel (noctalia default) | + +In the noctalia launcher (keyboard-first flow, native navigation): + +| Key | Action | +|-------------|-------------------------------------------| +| `/fs `| Fuzzy search files and folders | +| `↑` / `↓` | Move through the results | +| `Enter` | Open the selected result (MIME/xdg-open) | + +With an empty `/fs` query the list also offers *Rebuild search index*; the +index is shared with the panel and built on demand when missing. + +## Features + +- Live results while you type: the search folder is walked once with `find` + into a cache, then every keystroke is fuzzy-matched through + `fzf --filter`, so typing stays responsive even on large trees +- Configurable bar glyph, search folder (defaults to `~`), excluded folder + names (`.git, node_modules, .cache, .venv` by default, matched anywhere in + the tree), hidden entries on/off, max results +- `Enter` opens the top match; every result row opens on click via the + system MIME association — files in their default app, folders in the file + manager +- Launcher provider for a keyboard-first flow: type `/fs ` in the + noctalia launcher and navigate the results with the native arrow keys + + `Enter` (plugin panels cannot receive arrow keys in the current Luau API, + so the launcher is the keyboard way to browse results) +- Folder results are marked with a trailing `/` and a folder glyph +- Index rebuilds automatically when the relevant settings change, and on + demand via the panel's refresh button (external file changes are picked up + on rebuild) +- Panel placement (attached/floating), position and open-near-click are the + standard per-panel settings noctalia exposes in Settings → Plugins + +## Settings + +| Setting | Type | Default | Description | +| --- | --- | --- | --- | +| `search_folder` | `folder` | *(empty)* | Root folder the search indexes. Empty = your home folder. | +| `exclude_dirs` | `string` | `.git, node_modules, .cache, .venv` | Folder names skipped while indexing, separated by `,` or `;`, matched anywhere in the tree. | +| `show_hidden` | `bool` | `false` | Index files and folders whose name starts with a dot. | +| `max_results` | `int` | `50` | How many matches the panel lists at most (10–200). | +| `glyph` (widget) | `glyph` | `search` | Icon shown on the bar. | + +## Requirements + +- noctalia ≥ 5.0.0 +- [`fzf`](https://github.com/junegunn/fzf) — the fuzzy matcher +- `find` (GNU findutils) — walks the search folder into the index +- `xdg-open` (xdg-utils) — opens results with the MIME association +- `mktemp`, `mv`, `wc`, `head`, `rm` — GNU coreutils, standard on any Linux + desktop + +## Install + +Install **File Search** from Noctalia's plugin store (*Settings → Plugins*), +then add the widget to a bar from *Settings → Bar*. Plugin options live in +*Settings → Plugins*. + +For local development, add your working copy as a path source instead +(`.luau` edits hot-reload): + +```sh +noctalia msg plugins source add dev path /path/to/plugins +noctalia msg plugins enable nightwatch75/file-search +``` + +## Notes + +- The index lives in the plugin's private data directory + (`noctalia.pluginDataDir()`, by default + `~/.local/state/noctalia/plugins/data/nightwatch75/file-search/` — honors + `NOCTALIA_STATE_HOME`/`XDG_STATE_HOME`): `index.list` is a plain list of + paths relative to the search folder, and `index.meta` records which folder + and exclusions built it, so both the panel and the launcher rebuild + automatically after a settings change. +- Both files are written to `mktemp`-created private files and renamed into + place, so a rebuild never writes through a symlink planted at the cache + path. +- Names containing a newline are excluded from the index (they would break + the one-record-per-line format), and every record is validated against + the search root before being opened. +- Excluded entries match by folder/file *name* (`find -name`), not by path; + entries containing `/` are skipped and logged. +- With hidden entries off, anything starting with a dot is pruned — both + hidden folders (not descended into) and hidden files. +- Unreadable subtrees are silently skipped (permission errors don't fail the + index). + +## License + +MIT. diff --git a/file-search/file-search.luau b/file-search/file-search.luau new file mode 100644 index 0000000..86d1127 --- /dev/null +++ b/file-search/file-search.luau @@ -0,0 +1,70 @@ +--!nonstrict +-- file-search — bar widget that toggles the fuzzy search panel. +-- +-- The panel (panel.luau) publishes its open state on the shared +-- "file_search_open" state key; the glyph lights up while it is open. +-- +-- Click mapping: +-- Left click — open/close the search panel +-- Right click — open the search folder in the file manager +-- Middle click — copy the search folder path to the clipboard + +local PANEL_ID = "nightwatch75/file-search:panel" + +local open = false + +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +local function searchRoot() + local dir = noctalia.getConfig("search_folder") + if dir == nil or dir == "" then + dir = noctalia.getenv("HOME") or "/tmp" + else + dir = noctalia.expandPath(dir) + end + dir = dir:gsub("/+$", "") + if dir == "" then + dir = "/" + end + return dir +end + +local function render() + barWidget.setGlyph(noctalia.getConfig("glyph")) + local root = searchRoot() + if open then + barWidget.setGlyphColor("primary") + barWidget.setTooltip(noctalia.tr("tooltip_open", { path = root })) + else + barWidget.setGlyphColor("on_surface") + barWidget.setTooltip(noctalia.tr("tooltip_closed", { path = root })) + end +end + +noctalia.state.watch("file_search_open", function(value) + open = value == true + render() +end) + +-- Periodic re-render keeps the glyph and tooltip in sync with settings changes. +function update() + render() +end + +function onClick() + noctalia.togglePanel(PANEL_ID) +end + +function onRightClick() + noctalia.runAsync("xdg-open " .. shellQuote(searchRoot()) .. " >/dev/null 2>&1") +end + +function onMiddleClick() + noctalia.copyToClipboard(searchRoot(), "text/plain") + noctalia.notify(noctalia.tr("title"), noctalia.tr("copied_path")) +end + +noctalia.setUpdateInterval(1000) +render() diff --git a/file-search/launcher.luau b/file-search/launcher.luau new file mode 100644 index 0000000..cb591dd --- /dev/null +++ b/file-search/launcher.luau @@ -0,0 +1,278 @@ +--!nonstrict +-- file-search — launcher provider: the same fuzzy search with the native +-- keyboard flow of the noctalia launcher (type, arrows, Enter). +-- +-- `/fs ` fuzzy-matches over the index file the panel builds; when the +-- index is missing it is built here on demand. Activating a result opens it +-- with the system MIME association (xdg-open) — directories open in the +-- file manager. Late async results map back through the query echo of +-- launcher.setResults, so fzf can answer out of band. + +local MAX_RESULTS = 9 + +local searching = false +local indexing = false +local pendingQuery = nil -- latest query typed while a search/index ran + +local runQuery + +local function tr(key, args) + return noctalia.tr(key, args) +end + +local function trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +-- Private per-plugin storage (XDG state); the host creates the directory on +-- every call. nil (with a log line) when no state directory resolves. +local function dataDir() + local dir, err = noctalia.pluginDataDir() + if dir == nil then + noctalia.log("file-search: pluginDataDir failed: " .. tostring(err)) + return nil + end + return dir +end + +-- Shell header shared by every cache command. The .meta sidecar holds the +-- settings fingerprint the cache was built with, so the panel and the +-- launcher can both tell a stale index apart. +local function cacheSh(dir) + return "CACHE_DIR=" .. shellQuote(dir) + .. '\nCACHE="$CACHE_DIR/index.list"\nMETA="$CACHE_DIR/index.meta"' +end + +local function searchRoot() + local dir = noctalia.getConfig("search_folder") + if dir == nil or dir == "" then + dir = noctalia.getenv("HOME") or "/tmp" + else + dir = noctalia.expandPath(dir) + end + dir = dir:gsub("/+$", "") + if dir == "" then + dir = "/" + end + return dir +end + + +-- Same exclusion set as the panel: names from the setting plus hidden +-- entries unless enabled. +local function excludeNames() + local raw = noctalia.getConfig("exclude_dirs") + if type(raw) ~= "string" then + raw = "" + end + local names = {} + for entry in raw:gmatch("[^,;]+") do + local name = trim(entry) + if name ~= "" and not name:find("/") then + table.insert(names, name) + end + end + if noctalia.getConfig("show_hidden") ~= true then + table.insert(names, ".*") + end + return names +end + +-- Must build the same string as the panel's indexKey(): the fingerprint in +-- the .meta sidecar is how the two entries recognize each other's index. +local function indexKey() + return searchRoot() .. "\n" .. table.concat(excludeNames(), "\n") +end + +-- The cache on disk is current when its fingerprint matches the settings, +-- whether the panel or the launcher built it. +local function cacheFresh(dir) + return noctalia.readFile(dir .. "/index.meta") == indexKey() +end + +-- One cache record, about to be joined to the search root. The cache is a +-- plain user-editable file, so records are untrusted: reject anything that +-- could resolve outside the root. +local function safeRel(rel) + if rel == "" or rel:sub(1, 1) == "/" or rel:find("\n", 1, true) then + return false + end + for part in rel:gmatch("[^/]+") do + if part == ".." then + return false + end + end + return true +end + +local function noopRow(titleKey) + return { id = "noop", title = tr(titleKey), glyph = "info-circle" } +end + +local function buildIndex(query) + if indexing then + pendingQuery = query + return + end + local dir = dataDir() + if dir == nil then + launcher.setResults(query, { noopRow("err_index") }) + return + end + indexing = true + pendingQuery = query + + local key = indexKey() + -- Names containing a newline would each forge extra one-per-fragment + -- records (a crafted name can smuggle '..' lines into the index), so + -- they are pruned unconditionally, before the user's exclusions. + local names = { "-name " .. shellQuote("*\n*") } + for _, name in ipairs(excludeNames()) do + table.insert(names, "-name " .. shellQuote(name)) + end + local prune = "\\( " .. table.concat(names, " -o ") .. " \\) -prune -o " + -- find's own exit status is ignored, so permission errors inside the + -- tree don't fail the build. Cache and fingerprint are written to + -- mktemp-created private files and renamed into place: rename replaces + -- a planted symlink at the destination instead of following it. The + -- host guarantees $CACHE_DIR exists (created by pluginDataDir above). + local cmd = cacheSh(dir) + .. '\nTMP=$(mktemp "$CACHE_DIR/index.list.XXXXXX") || exit 1\n' + .. "find " .. shellQuote(searchRoot()) .. " -mindepth 1 " .. prune + .. "-type d -printf '%P/\\n' -o -printf '%P\\n' > \"$TMP\" 2>/dev/null\n" + .. 'mv -f "$TMP" "$CACHE" || exit 1\n' + .. 'TMPM=$(mktemp "$CACHE_DIR/index.meta.XXXXXX") || exit 1\n' + .. "printf '%s' " .. shellQuote(key) .. ' > "$TMPM"\n' + .. 'mv -f "$TMPM" "$META" || exit 1' + + launcher.setResults(query, { noopRow("launcher.indexing") }) + local ok = noctalia.runAsync(cmd, function(result) + indexing = false + local queued = pendingQuery + pendingQuery = nil + if result.exitCode == 0 and not result.timedOut then + runQuery(queued or query) + else + launcher.setResults(queued or query, { noopRow("err_index") }) + end + end, 180000) + if not ok then + indexing = false + launcher.setResults(query, { noopRow("err_spawn") }) + end +end + +runQuery = function(query) + if indexing then + pendingQuery = query + return + end + if searching then + pendingQuery = query + return + end + local dir = dataDir() + if dir == nil then + launcher.setResults(query, { noopRow("err_index") }) + return + end + searching = true + local text = trim(query) + local cmd + if text == "" then + cmd = cacheSh(dir) .. '\nhead -n ' .. MAX_RESULTS .. ' "$CACHE" 2>/dev/null' + else + cmd = cacheSh(dir) .. "\nfzf --filter=" .. shellQuote(text) + .. ' < "$CACHE" 2>/dev/null | head -n ' .. MAX_RESULTS + end + local ok = noctalia.runAsync(cmd, function(result) + searching = false + local rows = {} + if not result.timedOut then + for line in (result.stdout or ""):gmatch("[^\n]+") do + local isDir = line:sub(-1) == "/" + local rel = line:gsub("/+$", "") + table.insert(rows, { + id = "open:" .. line, + title = rel:match("[^/]+$") or rel, + subtitle = line, + glyph = isDir and "folder" or "file", + }) + end + end + if #rows == 0 and text ~= "" then + table.insert(rows, noopRow("launcher.no_results")) + end + if text == "" then + table.insert(rows, { + id = "reindex", + title = tr("launcher.reindex_title"), + subtitle = searchRoot(), + glyph = "refresh", + }) + end + launcher.setResults(query, rows) + if pendingQuery ~= nil and pendingQuery ~= query then + local nextQuery = pendingQuery + pendingQuery = nil + runQuery(nextQuery) + end + end, 15000) + if not ok then + searching = false + end +end + +function onQuery(query) + if not noctalia.commandExists("fzf") then + launcher.setResults(query, { noopRow("err_no_fzf") }) + return + end + local dir = dataDir() + if dir == nil then + launcher.setResults(query, { noopRow("err_index") }) + return + end + -- A missing or stale index (no meta, or built with a different root or + -- exclusion set) is rebuilt before searching: stale relative paths + -- joined to a new root could resolve to unrelated files. + if not cacheFresh(dir) then + buildIndex(query) + return + end + runQuery(query) +end + +function onActivate(id) + if id == "reindex" then + local dir = dataDir() + if dir == nil then + return + end + -- Drop cache and fingerprint so the next keystroke rebuilds against + -- fresh disk state (a surviving .meta would read as a fresh index). + noctalia.runAsync(cacheSh(dir) .. '\nrm -f "$CACHE" "$META"', function(_result) + noctalia.notify(tr("title"), tr("launcher.reindex_done")) + end) + return + end + local rel = id:match("^open:(.+)$") + if rel == nil then + return + end + if not safeRel(rel) then + noctalia.log("file-search: refusing unsafe index record: " .. rel) + noctalia.notify(tr("title"), tr("err_bad_record")) + return + end + local path = searchRoot() + if path ~= "/" then + path = path .. "/" + end + path = path .. rel:gsub("/+$", "") + noctalia.runAsync("xdg-open " .. shellQuote(path) .. " >/dev/null 2>&1") +end diff --git a/file-search/panel.luau b/file-search/panel.luau new file mode 100644 index 0000000..55a97bf --- /dev/null +++ b/file-search/panel.luau @@ -0,0 +1,387 @@ +--!nonstrict +-- file-search — fuzzy search panel, fzf as the matching subsystem. +-- +-- On open the search folder is walked once with `find` into a cache file +-- (excluded directory names are pruned, hidden entries too unless enabled); +-- after that every keystroke runs `fzf --filter=` over the cache, so +-- typing stays responsive even on large trees. Results update live; picking +-- one opens it with the system MIME association (xdg-open) — directories +-- open in the file manager. Enter opens the top match. +-- +-- The index is rebuilt when the panel opens with changed settings, or on +-- demand via the refresh button. The bar widget mirrors the panel's open +-- state through the shared "file_search_open" state key. + +local query = "" +local results = {} -- relative paths; directories keep a trailing "/" +local total = nil -- entries in the index, shown in the footer +local indexing = false +local searching = false +local errMsg = nil +local fzfMissing = false +local inputRev = 0 -- bumped to reseed the query input on open +local haveIndex = false -- the cache on disk matches the current settings + +local render +local runSearch +local buildIndex + +-- Per-row callbacks need distinct global names; the reconciler dispatches +-- callbacks by name only. getfenv() is the script environment lua_getglobal +-- reads from, so assigning into it defines the callback the host will find. +local env = getfenv() +local function rowCallback(prefix, index, fn) + local name = prefix .. "_" .. index + env[name] = fn + return name +end + +local function tr(key, args) + return noctalia.tr(key, args) +end + +local function trim(value) + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function shellQuote(value) + return "'" .. value:gsub("'", "'\\''") .. "'" +end + +-- Private per-plugin storage (XDG state); the host creates the directory on +-- every call. nil (with a log line) when no state directory resolves. +local function dataDir() + local dir, err = noctalia.pluginDataDir() + if dir == nil then + noctalia.log("file-search: pluginDataDir failed: " .. tostring(err)) + return nil + end + return dir +end + +-- Shell header shared by every cache command. The .meta sidecar holds the +-- settings fingerprint the cache was built with, so the panel and the +-- launcher can both tell a stale index apart. +local function cacheSh(dir) + return "CACHE_DIR=" .. shellQuote(dir) + .. '\nCACHE="$CACHE_DIR/index.list"\nMETA="$CACHE_DIR/index.meta"' +end + +local function searchRoot() + local dir = noctalia.getConfig("search_folder") + if dir == nil or dir == "" then + dir = noctalia.getenv("HOME") or "/tmp" + else + dir = noctalia.expandPath(dir) + end + dir = dir:gsub("/+$", "") + if dir == "" then + dir = "/" + end + return dir +end + +local function maxResults() + return math.max(10, math.min(200, tonumber(noctalia.getConfig("max_results")) or 50)) +end + +-- Excluded directory names from the setting, split on ',' or ';'. Matching +-- is by basename (find -name), so entries containing '/' are skipped and +-- logged. Hidden entries are folded in as an extra '.*' pattern. +local function excludeNames() + local raw = noctalia.getConfig("exclude_dirs") + if type(raw) ~= "string" then + raw = "" + end + local names = {} + for entry in raw:gmatch("[^,;]+") do + local name = trim(entry) + if name:find("/") then + noctalia.log("file-search: ignoring exclude entry with '/': '" .. name .. "'") + elseif name ~= "" then + table.insert(names, name) + end + end + if noctalia.getConfig("show_hidden") ~= true then + table.insert(names, ".*") + end + return names +end + +local function indexKey() + return searchRoot() .. "\n" .. table.concat(excludeNames(), "\n") +end + +-- The cache on disk is current when its fingerprint matches the settings, +-- whether the panel or the launcher built it. +local function cacheFresh(dir) + return noctalia.readFile(dir .. "/index.meta") == indexKey() +end + +-- One cache record, about to be joined to the search root. The cache is a +-- plain user-editable file, so records are untrusted: reject anything that +-- could resolve outside the root. +local function safeRel(rel) + if rel == "" or rel:sub(1, 1) == "/" or rel:find("\n", 1, true) then + return false + end + for part in rel:gmatch("[^/]+") do + if part == ".." then + return false + end + end + return true +end + +buildIndex = function() + if fzfMissing or indexing then + return + end + local dir = dataDir() + if dir == nil then + errMsg = tr("err_index") + render() + return + end + indexing = true + errMsg = nil + local key = indexKey() + + -- Names containing a newline would each forge extra one-per-fragment + -- records (a crafted name can smuggle '..' lines into the index), so + -- they are pruned unconditionally, before the user's exclusions. + local names = { "-name " .. shellQuote("*\n*") } + for _, name in ipairs(excludeNames()) do + table.insert(names, "-name " .. shellQuote(name)) + end + local prune = "\\( " .. table.concat(names, " -o ") .. " \\) -prune -o " + -- %P prints paths relative to the root; the trailing '/' marks + -- directories so rows get the right glyph. find's own exit status is + -- ignored, so permission errors inside the tree don't fail the build. + -- Cache and fingerprint are written to mktemp-created private files and + -- renamed into place: rename replaces a planted symlink at the + -- destination instead of following it. The host guarantees $CACHE_DIR + -- exists (created by pluginDataDir above). + local cmd = cacheSh(dir) + .. '\nTMP=$(mktemp "$CACHE_DIR/index.list.XXXXXX") || exit 1\n' + .. "find " .. shellQuote(searchRoot()) .. " -mindepth 1 " .. prune + .. "-type d -printf '%P/\\n' -o -printf '%P\\n' > \"$TMP\" 2>/dev/null\n" + .. 'mv -f "$TMP" "$CACHE" || exit 1\n' + .. 'TMPM=$(mktemp "$CACHE_DIR/index.meta.XXXXXX") || exit 1\n' + .. "printf '%s' " .. shellQuote(key) .. ' > "$TMPM"\n' + .. 'mv -f "$TMPM" "$META" || exit 1\n' + .. 'wc -l < "$CACHE"' + + render() + local ok = noctalia.runAsync(cmd, function(result) + indexing = false + if result.exitCode == 0 and not result.timedOut then + haveIndex = true + total = tonumber(trim(result.stdout or "")) or 0 + if indexKey() ~= key then + buildIndex() -- settings changed while the walk was running + else + runSearch() + end + else + haveIndex = false + errMsg = tr("err_index") + end + render() + end, 180000) + if not ok then + indexing = false + errMsg = tr("err_spawn") + render() + end +end + +runSearch = function() + if fzfMissing or indexing or searching or not haveIndex then + return + end + local dir = dataDir() + if dir == nil then + return + end + searching = true + local q = query + local limit = maxResults() + local cmd + if trim(q) == "" then + cmd = cacheSh(dir) .. '\nhead -n ' .. limit .. ' "$CACHE" 2>/dev/null' + else + -- head closes the pipe early; the pipeline status stays head's (0). + cmd = cacheSh(dir) .. "\nfzf --filter=" .. shellQuote(q) + .. ' < "$CACHE" 2>/dev/null | head -n ' .. limit + end + local ok = noctalia.runAsync(cmd, function(result) + searching = false + if not result.timedOut then + results = {} + for line in (result.stdout or ""):gmatch("[^\n]+") do + table.insert(results, line) + end + end + -- The query moved on while this search ran: chase it. The stale + -- rows rendered below are overwritten as soon as it completes. + if query ~= q then + runSearch() + end + render() + end, 15000) + if not ok then + searching = false + end +end + +local function openEntry(rel) + if not safeRel(rel) then + noctalia.log("file-search: refusing unsafe index record: " .. rel) + noctalia.notify(tr("title"), tr("err_bad_record")) + return + end + local path = searchRoot() + if path ~= "/" then + path = path .. "/" + end + path = path .. rel:gsub("/+$", "") + noctalia.runAsync("xdg-open " .. shellQuote(path) .. " >/dev/null 2>&1") + panel.close() +end + +local function resultRow(rel, index) + local isDir = rel:sub(-1) == "/" + return ui.button({ + key = "hit-" .. index, + glyph = isDir and "folder" or "file", + text = rel, + variant = "ghost", + contentAlign = "start", + onClick = rowCallback("openHit", index, function() + openEntry(rel) + end), + }) +end + +local function statusFooter() + if indexing then + return ui.label({ text = tr("status_indexing", { path = searchRoot() }), fontSize = 11, color = "secondary" }) + end + local shown = #results + return ui.label({ + text = tr("counts", { shown = shown, total = total or 0 }), + fontSize = 11, + color = "on_surface_variant", + }) +end + +render = function() + local children = { + ui.row({ gap = 6, align = "center" }, { + ui.label({ + text = tr("title"), + fontSize = 16, + fontWeight = "bold", + color = "on_surface", + flexGrow = 1, + }), + ui.button({ glyph = "refresh", variant = "ghost", tooltip = tr("tip_refresh"), onClick = "onRefreshIndex" }), + ui.button({ glyph = "close", variant = "ghost", tooltip = tr("tip_close"), onClick = "onClosePanel" }), + }), + ui.input({ + key = "query-" .. inputRev, + value = query, + focus = true, + placeholder = tr("search_placeholder"), + onChange = "onQueryChanged", + onSubmit = "onOpenFirst", + }), + } + + if fzfMissing then + table.insert(children, ui.label({ text = tr("err_no_fzf"), color = "error" })) + elseif errMsg ~= nil then + table.insert(children, ui.label({ text = errMsg, color = "error" })) + else + if #results == 0 and not indexing and trim(query) ~= "" then + table.insert(children, ui.label({ + text = tr("no_results", { query = query }), + color = "on_surface_variant", + flexGrow = 1, + })) + else + local rows = {} + for index, rel in ipairs(results) do + table.insert(rows, resultRow(rel, index)) + end + table.insert(children, ui.scroll({ flexGrow = 1, gap = 2 }, rows)) + end + table.insert(children, statusFooter()) + end + + panel.render(ui.column({ flexGrow = 1, gap = 12, align = "stretch" }, children)) +end + +function onOpen(_context) + fzfMissing = not noctalia.commandExists("fzf") + query = "" + results = {} + inputRev += 1 + noctalia.state.set("file_search_open", true) + if not fzfMissing then + local dir = dataDir() + if dir == nil then + errMsg = tr("err_index") + else + haveIndex = cacheFresh(dir) + if not haveIndex then + buildIndex() + else + runSearch() + end + end + end + render() +end + +function onClose() + noctalia.state.set("file_search_open", false) +end + +function onConfigChanged() + if fzfMissing or indexing then + return + end + local dir = dataDir() + if dir == nil then + return + end + haveIndex = cacheFresh(dir) + if not haveIndex then + buildIndex() + else + runSearch() + end +end + +function onQueryChanged(value) + query = value + runSearch() +end + +function onOpenFirst(value) + query = value + if results[1] ~= nil then + openEntry(results[1]) + end +end + +function onRefreshIndex() + haveIndex = false + buildIndex() +end + +function onClosePanel() + panel.close() +end diff --git a/file-search/plugin.toml b/file-search/plugin.toml new file mode 100644 index 0000000..1d690c6 --- /dev/null +++ b/file-search/plugin.toml @@ -0,0 +1,78 @@ +# File Search — fuzzy file & folder search from the bar, powered by fzf. +# The bar glyph toggles a search panel: the search folder is indexed with +# `find` (honoring the excluded directories), every keystroke is matched +# through `fzf --filter`, and picking a result opens it with the system +# MIME association (xdg-open). + +id = "nightwatch75/file-search" +name = "File Search" +version = "0.0.9" +plugin_api = 3 +author = "nightwatch75" +license = "MIT" +# The exact commands the plugin spawns (fzf aside, findutils + coreutils + +# xdg-utils on any Linux desktop). +dependencies = ["fzf", "find", "mktemp", "mv", "wc", "head", "rm", "xdg-open"] +tags = ["bar", "launcher", "panel", "utility", "productivity"] +icon = "search" +description = "Search files and folders as you type, fuzzy-matched with fzf; open results with the system MIME association." + +# Plugin-level settings: shared by the bar widget (tooltip, folder actions) +# and the panel (index + search). + +[[setting]] +key = "search_folder" +type = "folder" +label_key = "settings.search_folder.label" +description_key = "settings.search_folder.description" + +[[setting]] +key = "exclude_dirs" +type = "string" +label_key = "settings.exclude_dirs.label" +description_key = "settings.exclude_dirs.description" +default = ".git, node_modules, .cache, .venv" + +[[setting]] +key = "show_hidden" +type = "bool" +label_key = "settings.show_hidden.label" +description_key = "settings.show_hidden.description" +default = false + +[[setting]] +key = "max_results" +type = "int" +label_key = "settings.max_results.label" +description_key = "settings.max_results.description" +default = 50 +min = 10 +max = 200 + +# Keyboard-first flow: the noctalia launcher provides native arrows + Enter +# navigation, which plugin panels cannot receive from the current Luau API. +[[launcher_provider]] +id = "launcher" +entry = "launcher.luau" +prefix = "fs" +glyph = "search" +include_in_global_search = false + +[[panel]] +id = "panel" +entry = "panel.luau" +width = 520 +height = 480 +placement = "attached" +open_near_click = true + +[[widget]] +id = "file-search" +entry = "file-search.luau" + + [[widget.setting]] + key = "glyph" + type = "glyph" + label_key = "settings.glyph.label" + description_key = "settings.glyph.description" + default = "search" diff --git a/file-search/thumbnail.webp b/file-search/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..5cd5a93e1f5f5b095048e1056db24860ee64730d GIT binary patch literal 57884 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