Fix: Make file path case-insensitive in @-command (#659)
This commit is contained in:
parent
b1d693786c
commit
c414512f19
|
@ -484,6 +484,77 @@ ${content2}`,
|
||||||
expect(result.processedQuery).toEqual([
|
expect(result.processedQuery).toEqual([
|
||||||
{ text: 'Check @nonexistent.txt and @ also' },
|
{ text: 'Check @nonexistent.txt and @ also' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(result.shouldProceed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process a file path case-insensitively', async () => {
|
||||||
|
// const actualFilePath = 'path/to/MyFile.txt'; // Unused, path in llmContent should match queryPath
|
||||||
|
const queryPath = 'path/to/myfile.txt'; // Different case
|
||||||
|
const query = `@${queryPath}`;
|
||||||
|
const fileContent = 'This is the case-insensitive file content.';
|
||||||
|
|
||||||
|
// Mock fs.stat to "find" MyFile.txt when looking for myfile.txt
|
||||||
|
// This simulates a case-insensitive file system or resolution
|
||||||
|
vi.mocked(fsPromises.stat).mockImplementation(async (p) => {
|
||||||
|
if (p.toString().toLowerCase().endsWith('myfile.txt')) {
|
||||||
|
return {
|
||||||
|
isDirectory: () => false,
|
||||||
|
// You might need to add other Stats properties if your code uses them
|
||||||
|
} as Stats;
|
||||||
|
}
|
||||||
|
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
||||||
|
});
|
||||||
|
|
||||||
|
mockReadManyFilesExecute.mockResolvedValue({
|
||||||
|
llmContent: `
|
||||||
|
--- ${queryPath} ---
|
||||||
|
${fileContent}`,
|
||||||
|
returnDisplay: 'Read 1 file.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await handleAtCommand({
|
||||||
|
query,
|
||||||
|
config: mockConfig,
|
||||||
|
addItem: mockAddItem,
|
||||||
|
onDebugMessage: mockOnDebugMessage,
|
||||||
|
messageId: 134, // New messageId
|
||||||
|
signal: abortController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
|
{ type: 'user', text: query },
|
||||||
|
134,
|
||||||
|
);
|
||||||
|
// The atCommandProcessor resolves the path before calling read_many_files.
|
||||||
|
// We expect it to be called with the path that fs.stat "found".
|
||||||
|
// In a real case-insensitive FS, stat(myfile.txt) might return info for MyFile.txt.
|
||||||
|
// The key is that *a* valid path that points to the content is used.
|
||||||
|
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
|
||||||
|
// Depending on how path resolution and fs.stat mock interact,
|
||||||
|
// this could be queryPath or actualFilePath.
|
||||||
|
// For this test, we'll assume the processor uses the path that stat "succeeded" with.
|
||||||
|
// If the underlying fs/stat is truly case-insensitive, it might resolve to actualFilePath.
|
||||||
|
// If the mock is simpler, it might use queryPath if stat(queryPath) succeeds.
|
||||||
|
// The most important part is that *some* version of the path that leads to the content is used.
|
||||||
|
// Let's assume it uses the path from the query if stat confirms it exists (even if different case on disk)
|
||||||
|
{ paths: [queryPath] },
|
||||||
|
abortController.signal,
|
||||||
|
);
|
||||||
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: [expect.objectContaining({ status: ToolCallStatus.Success })],
|
||||||
|
}),
|
||||||
|
134,
|
||||||
|
);
|
||||||
|
expect(result.processedQuery).toEqual([
|
||||||
|
{ text: `@${queryPath}` }, // Query uses the input path
|
||||||
|
{ text: '\n--- Content from referenced files ---' },
|
||||||
|
{ text: `\nContent from @${queryPath}:\n` }, // Content display also uses input path
|
||||||
|
{ text: fileContent },
|
||||||
|
{ text: '\n--- End of content ---' },
|
||||||
|
]);
|
||||||
expect(result.shouldProceed).toBe(true);
|
expect(result.shouldProceed).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -193,6 +193,7 @@ export function useCompletion(
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lowerSearchPrefix = searchPrefix.toLowerCase();
|
||||||
let foundSuggestions: Suggestion[] = [];
|
let foundSuggestions: Suggestion[] = [];
|
||||||
try {
|
try {
|
||||||
const entries = await fs.readdir(startDir, { withFileTypes: true });
|
const entries = await fs.readdir(startDir, { withFileTypes: true });
|
||||||
|
@ -200,7 +201,7 @@ export function useCompletion(
|
||||||
if (foundSuggestions.length >= maxResults) break;
|
if (foundSuggestions.length >= maxResults) break;
|
||||||
|
|
||||||
const entryPathRelative = path.join(currentRelativePath, entry.name);
|
const entryPathRelative = path.join(currentRelativePath, entry.name);
|
||||||
if (entry.name.startsWith(searchPrefix)) {
|
if (entry.name.toLowerCase().startsWith(lowerSearchPrefix)) {
|
||||||
foundSuggestions.push({
|
foundSuggestions.push({
|
||||||
label: entryPathRelative + (entry.isDirectory() ? '/' : ''),
|
label: entryPathRelative + (entry.isDirectory() ? '/' : ''),
|
||||||
value: escapePath(
|
value: escapePath(
|
||||||
|
@ -217,7 +218,7 @@ export function useCompletion(
|
||||||
foundSuggestions = foundSuggestions.concat(
|
foundSuggestions = foundSuggestions.concat(
|
||||||
await findFilesRecursively(
|
await findFilesRecursively(
|
||||||
path.join(startDir, entry.name),
|
path.join(startDir, entry.name),
|
||||||
searchPrefix,
|
searchPrefix, // Pass original searchPrefix for recursive calls
|
||||||
entryPathRelative,
|
entryPathRelative,
|
||||||
depth + 1,
|
depth + 1,
|
||||||
maxDepth,
|
maxDepth,
|
||||||
|
@ -242,11 +243,12 @@ export function useCompletion(
|
||||||
fetchedSuggestions = await findFilesRecursively(cwd, prefix);
|
fetchedSuggestions = await findFilesRecursively(cwd, prefix);
|
||||||
} else {
|
} else {
|
||||||
// Original behavior: list files in the specific directory
|
// Original behavior: list files in the specific directory
|
||||||
|
const lowerPrefix = prefix.toLowerCase();
|
||||||
const entries = await fs.readdir(baseDirAbsolute, {
|
const entries = await fs.readdir(baseDirAbsolute, {
|
||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
});
|
});
|
||||||
fetchedSuggestions = entries
|
fetchedSuggestions = entries
|
||||||
.filter((entry) => entry.name.startsWith(prefix))
|
.filter((entry) => entry.name.toLowerCase().startsWith(lowerPrefix))
|
||||||
.map((entry) => {
|
.map((entry) => {
|
||||||
const label = entry.isDirectory() ? entry.name + '/' : entry.name;
|
const label = entry.isDirectory() ? entry.name + '/' : entry.name;
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue