Skip to content

Commit 456567e

Browse files
authored
Rg missing features (#49)
* Add ripgrep compatibility features: --files, -d, --stats, -P, markdown type This PR adds several missing ripgrep flags and improves compatibility with the real rg command. New Features --files - List files that would be searched without requiring a pattern rg --files # List all searchable files rg --files -t js # List only JavaScript files rg --files -g '*.txt' # List files matching glob rg --files -d 2 # List files up to depth 2 -d NUM - Short alias for --max-depth rg -d 2 pattern # Same as rg --max-depth 2 pattern --stats - Print search statistics after results rg --stats pattern # Output includes: matches, matched lines, files with matches, files searched, bytes searched -P/--pcre2 - Returns helpful error message (PCRE2 not supported) rg -P pattern # Error: rg: PCRE2 is not supported. Use standard regex syntax instead. -t markdown - Added markdown as alias for md file type rg -t markdown pattern # Search .md, .markdown, .mdown, .mkd files Changes - rg-search.ts: Added listFiles() function for --files mode, stats output - rg-parser.ts: Added -d alias, --files/--stats flags, -P error handling - rg-options.ts: Added files and stats boolean options - file-types.ts: Added markdown type alias - rg.ts: Updated help text with new flags Tests Added 22 new tests covering: - --files with various filters (type, glob, depth, hidden) - -d at various depths and combined with filters - --stats output format and edge cases - markdown type matching all extensions - -P/--pcre2 error messages Test count: 417 rg tests (up from 395) * tests-galore * file-types
1 parent 9a03f7d commit 456567e

18 files changed

+3270
-261
lines changed

src/commands/rg/file-types.ts

Lines changed: 145 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export interface FileType {
1414
* Built-in file type definitions
1515
* Use `rg --type-list` to see all types in real ripgrep
1616
*/
17-
export const FILE_TYPES: Record<string, FileType> = {
17+
const FILE_TYPES: Record<string, FileType> = {
1818
// Web languages
1919
js: { extensions: [".js", ".mjs", ".cjs", ".jsx"], globs: [] },
2020
ts: { extensions: [".ts", ".tsx", ".mts", ".cts"], globs: [] },
@@ -64,7 +64,11 @@ export const FILE_TYPES: Record<string, FileType> = {
6464
csv: { extensions: [".csv", ".tsv"], globs: [] },
6565

6666
// Documentation
67-
md: { extensions: [".md", ".markdown", ".mdown", ".mkd"], globs: [] },
67+
md: { extensions: [".md", ".mdx", ".markdown", ".mdown", ".mkd"], globs: [] },
68+
markdown: {
69+
extensions: [".md", ".mdx", ".markdown", ".mdown", ".mkd"],
70+
globs: [],
71+
},
6872
rst: { extensions: [".rst"], globs: [] },
6973
txt: { extensions: [".txt", ".text"], globs: [] },
7074
tex: { extensions: [".tex", ".ltx", ".sty", ".cls"], globs: [] },
@@ -85,37 +89,157 @@ export const FILE_TYPES: Record<string, FileType> = {
8589
};
8690

8791
/**
88-
* Check if a filename matches any of the specified types
92+
* Mutable file type registry for runtime type modifications
93+
* Supports --type-add and --type-clear flags
8994
*/
90-
export function matchesType(filename: string, types: string[]): boolean {
91-
const lowerFilename = filename.toLowerCase();
95+
export class FileTypeRegistry {
96+
private types: Map<string, FileType>;
97+
98+
constructor() {
99+
// Clone default types
100+
this.types = new Map(
101+
Object.entries(FILE_TYPES).map(([name, type]) => [
102+
name,
103+
{ extensions: [...type.extensions], globs: [...type.globs] },
104+
]),
105+
);
106+
}
107+
108+
/**
109+
* Add a type definition
110+
* Format: "name:pattern" where pattern can be:
111+
* - "*.ext" - glob pattern
112+
* - "include:other" - include patterns from another type
113+
*/
114+
addType(spec: string): void {
115+
const colonIdx = spec.indexOf(":");
116+
if (colonIdx === -1) return;
92117

93-
for (const typeName of types) {
94-
const fileType = FILE_TYPES[typeName];
95-
if (!fileType) continue;
118+
const name = spec.slice(0, colonIdx);
119+
const pattern = spec.slice(colonIdx + 1);
96120

97-
// Check extensions
98-
for (const ext of fileType.extensions) {
99-
if (lowerFilename.endsWith(ext)) {
100-
return true;
121+
if (pattern.startsWith("include:")) {
122+
// Include patterns from another type
123+
const otherName = pattern.slice(8);
124+
const other = this.types.get(otherName);
125+
if (other) {
126+
const existing = this.types.get(name) || { extensions: [], globs: [] };
127+
existing.extensions.push(...other.extensions);
128+
existing.globs.push(...other.globs);
129+
this.types.set(name, existing);
101130
}
131+
} else {
132+
// Add glob pattern
133+
const existing = this.types.get(name) || { extensions: [], globs: [] };
134+
// If pattern is like "*.ext", add to extensions
135+
if (pattern.startsWith("*.") && !pattern.slice(2).includes("*")) {
136+
const ext = pattern.slice(1); // Keep the dot
137+
if (!existing.extensions.includes(ext)) {
138+
existing.extensions.push(ext);
139+
}
140+
} else {
141+
// Add as glob pattern
142+
if (!existing.globs.includes(pattern)) {
143+
existing.globs.push(pattern);
144+
}
145+
}
146+
this.types.set(name, existing);
102147
}
148+
}
149+
150+
/**
151+
* Clear all patterns from a type
152+
*/
153+
clearType(name: string): void {
154+
const existing = this.types.get(name);
155+
if (existing) {
156+
existing.extensions = [];
157+
existing.globs = [];
158+
}
159+
}
160+
161+
/**
162+
* Get a type by name
163+
*/
164+
getType(name: string): FileType | undefined {
165+
return this.types.get(name);
166+
}
167+
168+
/**
169+
* Get all type names
170+
*/
171+
getAllTypes(): Map<string, FileType> {
172+
return this.types;
173+
}
174+
175+
/**
176+
* Check if a filename matches any of the specified types
177+
*/
178+
matchesType(filename: string, typeNames: string[]): boolean {
179+
const lowerFilename = filename.toLowerCase();
180+
181+
for (const typeName of typeNames) {
182+
// Special case: 'all' matches any file with a recognized type
183+
if (typeName === "all") {
184+
if (this.matchesAnyType(filename)) {
185+
return true;
186+
}
187+
continue;
188+
}
189+
190+
const fileType = this.types.get(typeName);
191+
if (!fileType) continue;
192+
193+
// Check extensions
194+
for (const ext of fileType.extensions) {
195+
if (lowerFilename.endsWith(ext)) {
196+
return true;
197+
}
198+
}
103199

104-
// Check globs (simple exact match for now)
105-
for (const glob of fileType.globs) {
106-
if (glob.includes("*")) {
107-
// Simple glob matching
108-
const pattern = glob.replace(/\./g, "\\.").replace(/\*/g, ".*");
109-
if (new RegExp(`^${pattern}$`, "i").test(filename)) {
200+
// Check globs
201+
for (const glob of fileType.globs) {
202+
if (glob.includes("*")) {
203+
const pattern = glob.replace(/\./g, "\\.").replace(/\*/g, ".*");
204+
if (new RegExp(`^${pattern}$`, "i").test(filename)) {
205+
return true;
206+
}
207+
} else if (lowerFilename === glob.toLowerCase()) {
110208
return true;
111209
}
112-
} else if (lowerFilename === glob.toLowerCase()) {
113-
return true;
114210
}
115211
}
212+
213+
return false;
116214
}
117215

118-
return false;
216+
/**
217+
* Check if a filename matches any recognized type
218+
*/
219+
private matchesAnyType(filename: string): boolean {
220+
const lowerFilename = filename.toLowerCase();
221+
222+
for (const fileType of this.types.values()) {
223+
for (const ext of fileType.extensions) {
224+
if (lowerFilename.endsWith(ext)) {
225+
return true;
226+
}
227+
}
228+
229+
for (const glob of fileType.globs) {
230+
if (glob.includes("*")) {
231+
const pattern = glob.replace(/\./g, "\\.").replace(/\*/g, ".*");
232+
if (new RegExp(`^${pattern}$`, "i").test(filename)) {
233+
return true;
234+
}
235+
} else if (lowerFilename === glob.toLowerCase()) {
236+
return true;
237+
}
238+
}
239+
}
240+
241+
return false;
242+
}
119243
}
120244

121245
/**

0 commit comments

Comments
 (0)