|
|
@@ -11,6 +11,7 @@ import { Filesystem } from "../util/filesystem"
|
|
|
import { Instance } from "../project/instance"
|
|
|
import { Ripgrep } from "./ripgrep"
|
|
|
import fuzzysort from "fuzzysort"
|
|
|
+import { Global } from "../global"
|
|
|
|
|
|
export namespace File {
|
|
|
const log = Log.create({ service: "file" })
|
|
|
@@ -122,10 +123,49 @@ export namespace File {
|
|
|
type Entry = { files: string[]; dirs: string[] }
|
|
|
let cache: Entry = { files: [], dirs: [] }
|
|
|
let fetching = false
|
|
|
+
|
|
|
+ const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
|
|
|
+
|
|
|
const fn = async (result: Entry) => {
|
|
|
// Disable scanning if in root of file system
|
|
|
if (Instance.directory === path.parse(Instance.directory).root) return
|
|
|
fetching = true
|
|
|
+
|
|
|
+ if (isGlobalHome) {
|
|
|
+ const dirs = new Set<string>()
|
|
|
+ const ignore = new Set<string>()
|
|
|
+
|
|
|
+ if (process.platform === "darwin") ignore.add("Library")
|
|
|
+ if (process.platform === "win32") ignore.add("AppData")
|
|
|
+
|
|
|
+ const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
|
|
|
+ const shouldIgnore = (name: string) => name.startsWith(".") || ignore.has(name)
|
|
|
+ const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
|
|
|
+
|
|
|
+ const top = await fs.promises
|
|
|
+ .readdir(Instance.directory, { withFileTypes: true })
|
|
|
+ .catch(() => [] as fs.Dirent[])
|
|
|
+
|
|
|
+ for (const entry of top) {
|
|
|
+ if (!entry.isDirectory()) continue
|
|
|
+ if (shouldIgnore(entry.name)) continue
|
|
|
+ dirs.add(entry.name + "/")
|
|
|
+
|
|
|
+ const base = path.join(Instance.directory, entry.name)
|
|
|
+ const children = await fs.promises.readdir(base, { withFileTypes: true }).catch(() => [] as fs.Dirent[])
|
|
|
+ for (const child of children) {
|
|
|
+ if (!child.isDirectory()) continue
|
|
|
+ if (shouldIgnoreNested(child.name)) continue
|
|
|
+ dirs.add(entry.name + "/" + child.name + "/")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ result.dirs = Array.from(dirs).toSorted()
|
|
|
+ cache = result
|
|
|
+ fetching = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
const set = new Set<string>()
|
|
|
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
|
|
result.files.push(file)
|
|
|
@@ -329,15 +369,43 @@ export namespace File {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- export async function search(input: { query: string; limit?: number; dirs?: boolean }) {
|
|
|
- log.info("search", { query: input.query })
|
|
|
+ export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
|
|
|
+ const query = input.query.trim()
|
|
|
const limit = input.limit ?? 100
|
|
|
+ const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
|
|
+ log.info("search", { query, kind })
|
|
|
+
|
|
|
const result = await state().then((x) => x.files())
|
|
|
- if (!input.query)
|
|
|
- return input.dirs !== false ? result.dirs.toSorted().slice(0, limit) : result.files.slice(0, limit)
|
|
|
- const items = input.dirs !== false ? [...result.files, ...result.dirs] : result.files
|
|
|
- const sorted = fuzzysort.go(input.query, items, { limit: limit }).map((r) => r.target)
|
|
|
- log.info("search", { query: input.query, results: sorted.length })
|
|
|
- return sorted
|
|
|
+
|
|
|
+ const hidden = (item: string) => {
|
|
|
+ const normalized = item.replaceAll("\\", "/").replace(/\/+$/, "")
|
|
|
+ return normalized.split("/").some((p) => p.startsWith(".") && p.length > 1)
|
|
|
+ }
|
|
|
+ const preferHidden = query.startsWith(".") || query.includes("/.")
|
|
|
+ const sortHiddenLast = (items: string[]) => {
|
|
|
+ if (preferHidden) return items
|
|
|
+ const visible: string[] = []
|
|
|
+ const hiddenItems: string[] = []
|
|
|
+ for (const item of items) {
|
|
|
+ const isHidden = hidden(item)
|
|
|
+ if (isHidden) hiddenItems.push(item)
|
|
|
+ if (!isHidden) visible.push(item)
|
|
|
+ }
|
|
|
+ return [...visible, ...hiddenItems]
|
|
|
+ }
|
|
|
+ if (!query) {
|
|
|
+ if (kind === "file") return result.files.slice(0, limit)
|
|
|
+ return sortHiddenLast(result.dirs.toSorted()).slice(0, limit)
|
|
|
+ }
|
|
|
+
|
|
|
+ const items =
|
|
|
+ kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
|
|
|
+
|
|
|
+ const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
|
|
|
+ const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((r) => r.target)
|
|
|
+ const output = kind === "directory" ? sortHiddenLast(sorted).slice(0, limit) : sorted
|
|
|
+
|
|
|
+ log.info("search", { query, kind, results: output.length })
|
|
|
+ return output
|
|
|
}
|
|
|
}
|