update
This commit is contained in:
@@ -1,14 +1,20 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { relative, sep } from 'node:path'
|
||||
import { getConfig } from './config'
|
||||
|
||||
/** Content search via ripgrep; file-name list via `rg --files`. Substring
|
||||
* (fixed-string), smart-case — matching the prototype's search semantics. */
|
||||
/** ripgrep is the single source of "what files are in the project": it powers
|
||||
* content search, the file-name list, AND the Explorer tree / content index
|
||||
* (via fs-service) — so gitignore + files.exclude are honored everywhere the
|
||||
* same way. Substring (fixed-string), smart-case search. */
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
let rgPath: string | null = null
|
||||
try {
|
||||
rgPath = (require('@vscode/ripgrep') as { rgPath: string }).rgPath
|
||||
// When packaged the binary is unpacked from the asar; rgPath still points
|
||||
// inside app.asar, so redirect it. No-op in dev (path has no app.asar).
|
||||
if (rgPath) rgPath = rgPath.replace(/\bapp\.asar\b/, 'app.asar.unpacked')
|
||||
} catch (e) {
|
||||
console.error('[helder] @vscode/ripgrep unavailable:', (e as Error).message)
|
||||
}
|
||||
@@ -16,12 +22,29 @@ try {
|
||||
export interface ContentHit { no: number; ln: string; ix: number }
|
||||
export interface ContentGroup { path: string; hits: ContentHit[] }
|
||||
|
||||
const IGNORE_GLOBS = ['node_modules', '.git', 'out', 'dist', 'build', '.cache', 'vendor', 'coverage', '.helder']
|
||||
.flatMap((d) => ['--glob', `!${d}`])
|
||||
/** Always-excluded heavy/noise dirs, on top of gitignore + user excludes. */
|
||||
const BASE_IGNORE = [
|
||||
'node_modules', '.git', 'out', 'dist', 'build', '.cache',
|
||||
'vendor', 'coverage', '.helder', '.next', '.nuxt', '.turbo', '.idea', '.vscode',
|
||||
]
|
||||
|
||||
const MAX_FILES = 400
|
||||
const MAX_LINE = 1000
|
||||
|
||||
export function rgAvailable(): boolean {
|
||||
return !!rgPath
|
||||
}
|
||||
|
||||
/** Glob/ignore args derived from config (files.exclude, files.followGitignore). */
|
||||
function ignoreArgs(): string[] {
|
||||
const cfg = getConfig()
|
||||
const args: string[] = []
|
||||
for (const d of BASE_IGNORE) args.push('--glob', `!${d}`)
|
||||
for (const g of cfg.files.exclude) if (g) args.push('--glob', `!${g}`)
|
||||
if (!cfg.files.followGitignore) args.push('--no-ignore')
|
||||
return args
|
||||
}
|
||||
|
||||
function toRel(root: string, p: string): string {
|
||||
return relative(root, p).split(sep).join('/')
|
||||
}
|
||||
@@ -30,9 +53,9 @@ export function searchContent(root: string, query: string): Promise<ContentGroup
|
||||
return new Promise((resolve) => {
|
||||
if (!rgPath || query.trim().length < 2) return resolve([])
|
||||
const child = spawn(rgPath, [
|
||||
'--json', '--fixed-strings', '--smart-case',
|
||||
'--json', '--fixed-strings', '--smart-case', '--hidden',
|
||||
'--max-count', '50', '--max-columns', '2000',
|
||||
...IGNORE_GLOBS, '-e', query, '--', root,
|
||||
...ignoreArgs(), '-e', query, '--', root,
|
||||
])
|
||||
const order: string[] = []
|
||||
const groups = new Map<string, ContentGroup>()
|
||||
@@ -65,10 +88,12 @@ export function searchContent(root: string, query: string): Promise<ContentGroup
|
||||
})
|
||||
}
|
||||
|
||||
/** All project files (relative paths), honoring gitignore + excludes. Includes
|
||||
* dotfiles (--hidden) so .env etc. show up unless ignored. */
|
||||
export function listFiles(root: string): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
if (!rgPath) return resolve([])
|
||||
const child = spawn(rgPath, ['--files', ...IGNORE_GLOBS, '--', root])
|
||||
const child = spawn(rgPath, ['--files', '--hidden', ...ignoreArgs(), '--', root])
|
||||
let buf = ''
|
||||
const out: string[] = []
|
||||
let done = false
|
||||
|
||||
Reference in New Issue
Block a user