mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-03 07:33:45 +00:00
overhaul file search and support @ mentioning directories
This commit is contained in:
@@ -7,6 +7,8 @@ import fs from "fs"
|
||||
import ignore from "ignore"
|
||||
import { Log } from "../util/log"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Ripgrep } from "./ripgrep"
|
||||
import fuzzysort from "fuzzysort"
|
||||
|
||||
export namespace File {
|
||||
const log = Log.create({ service: "file" })
|
||||
@@ -74,6 +76,43 @@ export namespace File {
|
||||
),
|
||||
}
|
||||
|
||||
const state = Instance.state(async () => {
|
||||
type Entry = { files: string[]; dirs: string[] }
|
||||
let cache: Entry = { files: [], dirs: [] }
|
||||
let fetching = false
|
||||
const fn = async (result: Entry) => {
|
||||
fetching = true
|
||||
const set = new Set<string>()
|
||||
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
||||
result.files.push(file)
|
||||
let current = file
|
||||
while (true) {
|
||||
const dir = path.dirname(current)
|
||||
if (dir === current) break
|
||||
current = dir
|
||||
if (set.has(dir)) continue
|
||||
set.add(dir)
|
||||
result.dirs.push(dir + "/")
|
||||
}
|
||||
}
|
||||
cache = result
|
||||
fetching = false
|
||||
}
|
||||
fn(cache)
|
||||
|
||||
return {
|
||||
async files() {
|
||||
if (!fetching) {
|
||||
fn({
|
||||
files: [],
|
||||
dirs: [],
|
||||
})
|
||||
}
|
||||
return cache
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export async function status() {
|
||||
const project = Instance.project
|
||||
if (project.vcs !== "git") return []
|
||||
@@ -201,4 +240,12 @@ export namespace File {
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
|
||||
export async function search(input: { query: string; limit?: number }) {
|
||||
const limit = input.limit ?? 100
|
||||
const result = await state().then((x) => x.files())
|
||||
const items = input.query ? [...result.files, ...result.dirs] : [...result.dirs]
|
||||
const sorted = fuzzysort.go(input.query, items, { limit: limit }).map((r) => r.target)
|
||||
return sorted
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import z from "zod/v4"
|
||||
import { NamedError } from "../util/error"
|
||||
import { lazy } from "../util/lazy"
|
||||
import { $ } from "bun"
|
||||
import { Fzf } from "./fzf"
|
||||
|
||||
import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"
|
||||
|
||||
export namespace Ripgrep {
|
||||
@@ -203,24 +203,48 @@ export namespace Ripgrep {
|
||||
return filepath
|
||||
}
|
||||
|
||||
export async function files(input: { cwd: string; query?: string; glob?: string[]; limit?: number }) {
|
||||
const commands = [`${$.escape(await filepath())} --files --follow --hidden --glob='!.git/*'`]
|
||||
|
||||
export async function* files(input: { cwd: string; glob?: string[] }) {
|
||||
const args = [await filepath(), "--files", "--follow", "--hidden", "--glob=!.git/*"]
|
||||
if (input.glob) {
|
||||
for (const g of input.glob) {
|
||||
commands[0] += ` --glob='${g}'`
|
||||
args.push(`--glob=${g}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (input.query) commands.push(`${await Fzf.filepath()} --filter=${input.query}`)
|
||||
if (input.limit) commands.push(`head -n ${input.limit}`)
|
||||
const joined = commands.join(" | ")
|
||||
const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text()
|
||||
return result.split("\n").filter(Boolean)
|
||||
const proc = Bun.spawn(args, {
|
||||
cwd: input.cwd,
|
||||
stdout: "pipe",
|
||||
stderr: "ignore",
|
||||
maxBuffer: 1024 * 1024 * 20,
|
||||
})
|
||||
|
||||
const reader = proc.stdout.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ""
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split("\n")
|
||||
buffer = lines.pop() || ""
|
||||
|
||||
for (const line of lines) {
|
||||
if (line) yield line
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer) yield buffer
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
await proc.exited
|
||||
}
|
||||
}
|
||||
|
||||
export async function tree(input: { cwd: string; limit?: number }) {
|
||||
const files = await Ripgrep.files({ cwd: input.cwd })
|
||||
const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd }))
|
||||
interface Node {
|
||||
path: string[]
|
||||
children: Node[]
|
||||
|
||||
Reference in New Issue
Block a user