mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix: enhance path handling in handleAtCommand to support relative paths (#9065)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -38,6 +38,10 @@ describe('handleAtCommand', () => {
|
||||
return path.resolve(testRootDir, fullPath);
|
||||
}
|
||||
|
||||
function getRelativePath(absolutePath: string): string {
|
||||
return path.relative(testRootDir, absolutePath);
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetAllMocks();
|
||||
|
||||
@@ -138,6 +142,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'path', 'to', 'file.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const relativePath = getRelativePath(filePath);
|
||||
const query = `@${filePath}`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
@@ -151,9 +156,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${filePath}` },
|
||||
{ text: `@${relativePath}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${relativePath}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -175,8 +180,10 @@ describe('handleAtCommand', () => {
|
||||
fileContent,
|
||||
);
|
||||
const dirPath = path.dirname(filePath);
|
||||
const relativeDirPath = getRelativePath(dirPath);
|
||||
const relativeFilePath = getRelativePath(filePath);
|
||||
const query = `@${dirPath}`;
|
||||
const resolvedGlob = `${dirPath}/**`;
|
||||
const resolvedGlob = `${relativeDirPath}/**`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -191,7 +198,7 @@ describe('handleAtCommand', () => {
|
||||
processedQuery: [
|
||||
{ text: `@${resolvedGlob}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${relativeFilePath}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -208,6 +215,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'doc.md'),
|
||||
fileContent,
|
||||
);
|
||||
const relativePath = getRelativePath(filePath);
|
||||
const textBefore = 'Explain this: ';
|
||||
const textAfter = ' in detail.';
|
||||
const query = `${textBefore}@${filePath}${textAfter}`;
|
||||
@@ -223,9 +231,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `${textBefore}@${filePath}${textAfter}` },
|
||||
{ text: `${textBefore}@${relativePath}${textAfter}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${relativePath}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -253,9 +261,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${filePath}` },
|
||||
{ text: `@${getRelativePath(filePath)}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -294,11 +302,13 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: query },
|
||||
{
|
||||
text: `@${getRelativePath(file1Path)} @${getRelativePath(file2Path)}`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${file1Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file1Path)}:\n` },
|
||||
{ text: content1 },
|
||||
{ text: `\nContent from @${file2Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file2Path)}:\n` },
|
||||
{ text: content2 },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -333,11 +343,13 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: query },
|
||||
{
|
||||
text: `${text1}@${getRelativePath(file1Path)}${text2}@${getRelativePath(file2Path)}${text3}`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${file1Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file1Path)}:\n` },
|
||||
{ text: content1 },
|
||||
{ text: `\nContent from @${file2Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file2Path)}:\n` },
|
||||
{ text: content2 },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -371,12 +383,12 @@ describe('handleAtCommand', () => {
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{
|
||||
text: `Look at @${file1Path} then @${invalidFile} and also just @ symbol, then @${file2Path}`,
|
||||
text: `Look at @${getRelativePath(file1Path)} then @${invalidFile} and also just @ symbol, then @${getRelativePath(file2Path)}`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${file2Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file2Path)}:\n` },
|
||||
{ text: content2 },
|
||||
{ text: `\nContent from @${file1Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file1Path)}:\n` },
|
||||
{ text: content1 },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -474,9 +486,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${validFile}` },
|
||||
{ text: `@${getRelativePath(validFile)}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${validFile}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(validFile)}:\n` },
|
||||
{ text: 'console.log("Hello world");' },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -507,9 +519,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${validFile} @${gitIgnoredFile}` },
|
||||
{ text: `@${getRelativePath(validFile)} @${gitIgnoredFile}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${validFile}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(validFile)}:\n` },
|
||||
{ text: '# Project README' },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -633,9 +645,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${validFile}` },
|
||||
{ text: `@${getRelativePath(validFile)}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${validFile}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(validFile)}:\n` },
|
||||
{ text: 'console.log("Hello world");' },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -669,9 +681,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `@${validFile} @${geminiIgnoredFile}` },
|
||||
{ text: `@${getRelativePath(validFile)} @${geminiIgnoredFile}` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${validFile}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(validFile)}:\n` },
|
||||
{ text: '// Main application entry' },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -692,7 +704,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'test.txt',
|
||||
fileContent: 'File content here',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Look at @${filePath}, then explain it.`,
|
||||
`Look at @${getRelativePath(filePath)}, then explain it.`,
|
||||
messageId: 400,
|
||||
},
|
||||
{
|
||||
@@ -700,7 +712,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'readme.md',
|
||||
fileContent: 'File content here',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Check @${filePath}. What does it say?`,
|
||||
`Check @${getRelativePath(filePath)}. What does it say?`,
|
||||
messageId: 401,
|
||||
},
|
||||
{
|
||||
@@ -708,7 +720,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'example.js',
|
||||
fileContent: 'Code example',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Review @${filePath}; check for bugs.`,
|
||||
`Review @${getRelativePath(filePath)}; check for bugs.`,
|
||||
messageId: 402,
|
||||
},
|
||||
{
|
||||
@@ -716,7 +728,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'important.txt',
|
||||
fileContent: 'Important content',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Look at @${filePath}! This is critical.`,
|
||||
`Look at @${getRelativePath(filePath)}! This is critical.`,
|
||||
messageId: 403,
|
||||
},
|
||||
{
|
||||
@@ -724,7 +736,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'config.json',
|
||||
fileContent: 'Config settings',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`What is in @${filePath}? Please explain.`,
|
||||
`What is in @${getRelativePath(filePath)}? Please explain.`,
|
||||
messageId: 404,
|
||||
},
|
||||
{
|
||||
@@ -732,7 +744,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'func.ts',
|
||||
fileContent: 'Function definition',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Analyze @${filePath}(the main function).`,
|
||||
`Analyze @${getRelativePath(filePath)}(the main function).`,
|
||||
messageId: 405,
|
||||
},
|
||||
{
|
||||
@@ -740,7 +752,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'data.json',
|
||||
fileContent: 'Test data',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Use data from @${filePath}) for testing.`,
|
||||
`Use data from @${getRelativePath(filePath)}) for testing.`,
|
||||
messageId: 406,
|
||||
},
|
||||
{
|
||||
@@ -748,7 +760,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'array.js',
|
||||
fileContent: 'Array data',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Check @${filePath}[0] for the first element.`,
|
||||
`Check @${getRelativePath(filePath)}[0] for the first element.`,
|
||||
messageId: 407,
|
||||
},
|
||||
{
|
||||
@@ -756,7 +768,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'list.md',
|
||||
fileContent: 'List content',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Review item @${filePath}] from the list.`,
|
||||
`Review item @${getRelativePath(filePath)}] from the list.`,
|
||||
messageId: 408,
|
||||
},
|
||||
{
|
||||
@@ -764,7 +776,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'object.ts',
|
||||
fileContent: 'Object definition',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Parse @${filePath}{prop1: value1}.`,
|
||||
`Parse @${getRelativePath(filePath)}{prop1: value1}.`,
|
||||
messageId: 409,
|
||||
},
|
||||
{
|
||||
@@ -772,7 +784,7 @@ describe('handleAtCommand', () => {
|
||||
fileName: 'config.yaml',
|
||||
fileContent: 'Configuration',
|
||||
queryTemplate: (filePath: string) =>
|
||||
`Use settings from @${filePath}} for deployment.`,
|
||||
`Use settings from @${getRelativePath(filePath)}} for deployment.`,
|
||||
messageId: 410,
|
||||
},
|
||||
];
|
||||
@@ -799,7 +811,7 @@ describe('handleAtCommand', () => {
|
||||
processedQuery: [
|
||||
{ text: query },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -832,11 +844,13 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Compare @${file1Path}, @${file2Path}; what's different?` },
|
||||
{
|
||||
text: `Compare @${getRelativePath(file1Path)}, @${getRelativePath(file2Path)}; what's different?`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${file1Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file1Path)}:\n` },
|
||||
{ text: content1 },
|
||||
{ text: `\nContent from @${file2Path}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(file2Path)}:\n` },
|
||||
{ text: content2 },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -864,9 +878,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${filePath}, it has spaces.` },
|
||||
{ text: `Check @${getRelativePath(filePath)}, it has spaces.` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -880,7 +894,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'example.d.ts'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Analyze @${filePath} for type definitions.`;
|
||||
const query = `Analyze @${getRelativePath(filePath)} for type definitions.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -893,9 +907,11 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Analyze @${filePath} for type definitions.` },
|
||||
{
|
||||
text: `Analyze @${getRelativePath(filePath)} for type definitions.`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -909,7 +925,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'config.json'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Check @${filePath}. This file contains settings.`;
|
||||
const query = `Check @${getRelativePath(filePath)}. This file contains settings.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -922,9 +938,11 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${filePath}. This file contains settings.` },
|
||||
{
|
||||
text: `Check @${getRelativePath(filePath)}. This file contains settings.`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -938,7 +956,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'package.json'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Review @${filePath}, then check dependencies.`;
|
||||
const query = `Review @${getRelativePath(filePath)}, then check dependencies.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -951,9 +969,11 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Review @${filePath}, then check dependencies.` },
|
||||
{
|
||||
text: `Review @${getRelativePath(filePath)}, then check dependencies.`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -967,7 +987,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'version.1.2.3.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Check @${filePath} contains version information.`;
|
||||
const query = `Check @${getRelativePath(filePath)} contains version information.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -980,9 +1000,11 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${filePath} contains version information.` },
|
||||
{
|
||||
text: `Check @${getRelativePath(filePath)} contains version information.`,
|
||||
},
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -996,7 +1018,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'end.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Show me @${filePath}.`;
|
||||
const query = `Show me @${getRelativePath(filePath)}.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -1009,9 +1031,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Show me @${filePath}.` },
|
||||
{ text: `Show me @${getRelativePath(filePath)}.` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -1025,7 +1047,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'file$with&special#chars.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Check @${filePath} for content.`;
|
||||
const query = `Check @${getRelativePath(filePath)} for content.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -1038,9 +1060,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${filePath} for content.` },
|
||||
{ text: `Check @${getRelativePath(filePath)} for content.` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -1054,7 +1076,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'basicfile.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Check @${filePath} please.`;
|
||||
const query = `Check @${getRelativePath(filePath)} please.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
@@ -1067,9 +1089,9 @@ describe('handleAtCommand', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${filePath} please.` },
|
||||
{ text: `Check @${getRelativePath(filePath)} please.` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${filePath}:\n` },
|
||||
{ text: `\nContent from @${getRelativePath(filePath)}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
@@ -1078,6 +1100,113 @@ describe('handleAtCommand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('absolute path handling', () => {
|
||||
it('should handle absolute file paths correctly', async () => {
|
||||
const fileContent = 'console.log("This is an absolute path test");';
|
||||
const relativePath = path.join('src', 'absolute-test.ts');
|
||||
const absolutePath = await createTestFile(
|
||||
path.join(testRootDir, relativePath),
|
||||
fileContent,
|
||||
);
|
||||
const query = `Check @${absolutePath} please.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
config: mockConfig,
|
||||
addItem: mockAddItem,
|
||||
onDebugMessage: mockOnDebugMessage,
|
||||
messageId: 500,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [
|
||||
{ text: `Check @${relativePath} please.` },
|
||||
{ text: '\n--- Content from referenced files ---' },
|
||||
{ text: `\nContent from @${relativePath}:\n` },
|
||||
{ text: fileContent },
|
||||
{ text: '\n--- End of content ---' },
|
||||
],
|
||||
shouldProceed: true,
|
||||
});
|
||||
|
||||
expect(mockOnDebugMessage).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`using relative path: ${relativePath}`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle absolute directory paths correctly', async () => {
|
||||
const fileContent =
|
||||
'export default function test() { return "absolute dir test"; }';
|
||||
const subDirPath = 'src/utils';
|
||||
const fileName = 'helper.ts';
|
||||
await createTestFile(
|
||||
path.join(testRootDir, subDirPath, fileName),
|
||||
fileContent,
|
||||
);
|
||||
const absoluteDirPath = path.join(testRootDir, subDirPath);
|
||||
const query = `Check @${absoluteDirPath} please.`;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
config: mockConfig,
|
||||
addItem: mockAddItem,
|
||||
onDebugMessage: mockOnDebugMessage,
|
||||
messageId: 501,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
expect(result.shouldProceed).toBe(true);
|
||||
expect(result.processedQuery).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ text: `Check @${subDirPath}/** please.` },
|
||||
expect.objectContaining({
|
||||
text: '\n--- Content from referenced files ---',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(mockOnDebugMessage).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`using glob: ${subDirPath}/**`),
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip absolute paths outside workspace', async () => {
|
||||
const outsidePath = '/tmp/outside-workspace.txt';
|
||||
const query = `Check @${outsidePath} please.`;
|
||||
|
||||
const mockWorkspaceContext = {
|
||||
isPathWithinWorkspace: vi.fn((path: string) =>
|
||||
path.startsWith(testRootDir),
|
||||
),
|
||||
getDirectories: () => [testRootDir],
|
||||
addDirectory: vi.fn(),
|
||||
getInitialDirectories: () => [testRootDir],
|
||||
setDirectories: vi.fn(),
|
||||
onDirectoriesChanged: vi.fn(() => () => {}),
|
||||
} as unknown as ReturnType<typeof mockConfig.getWorkspaceContext>;
|
||||
mockConfig.getWorkspaceContext = () => mockWorkspaceContext;
|
||||
|
||||
const result = await handleAtCommand({
|
||||
query,
|
||||
config: mockConfig,
|
||||
addItem: mockAddItem,
|
||||
onDebugMessage: mockOnDebugMessage,
|
||||
messageId: 502,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
processedQuery: [{ text: `Check @${outsidePath} please.` }],
|
||||
shouldProceed: true,
|
||||
});
|
||||
|
||||
expect(mockOnDebugMessage).toHaveBeenCalledWith(
|
||||
`Path ${outsidePath} is not in the workspace and will be skipped.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not add the user's turn to history, as that is the caller's responsibility", async () => {
|
||||
// Arrange
|
||||
const fileContent = 'This is the file content.';
|
||||
@@ -1085,7 +1214,7 @@ describe('handleAtCommand', () => {
|
||||
path.join(testRootDir, 'path', 'to', 'another-file.txt'),
|
||||
fileContent,
|
||||
);
|
||||
const query = `A query with @${filePath}`;
|
||||
const query = `A query with @${getRelativePath(filePath)}`;
|
||||
|
||||
// Act
|
||||
await handleAtCommand({
|
||||
|
||||
@@ -145,6 +145,7 @@ export async function handleAtCommand({
|
||||
const pathSpecsToRead: string[] = [];
|
||||
const atPathToResolvedSpecMap = new Map<string, string>();
|
||||
const contentLabelsForDisplay: string[] = [];
|
||||
const absoluteToRelativePathMap = new Map<string, string>();
|
||||
const ignoredByReason: Record<string, string[]> = {
|
||||
git: [],
|
||||
gemini: [],
|
||||
@@ -229,17 +230,30 @@ export async function handleAtCommand({
|
||||
for (const dir of config.getWorkspaceContext().getDirectories()) {
|
||||
let currentPathSpec = pathName;
|
||||
let resolvedSuccessfully = false;
|
||||
let relativePath = pathName;
|
||||
try {
|
||||
const absolutePath = path.resolve(dir, pathName);
|
||||
const absolutePath = path.isAbsolute(pathName)
|
||||
? pathName
|
||||
: path.resolve(dir, pathName);
|
||||
const stats = await fs.stat(absolutePath);
|
||||
|
||||
// Convert absolute path to relative path
|
||||
relativePath = path.isAbsolute(pathName)
|
||||
? path.relative(dir, absolutePath)
|
||||
: pathName;
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
currentPathSpec =
|
||||
pathName + (pathName.endsWith(path.sep) ? `**` : `/**`);
|
||||
relativePath + (relativePath.endsWith(path.sep) ? `**` : `/**`);
|
||||
onDebugMessage(
|
||||
`Path ${pathName} resolved to directory, using glob: ${currentPathSpec}`,
|
||||
);
|
||||
} else {
|
||||
onDebugMessage(`Path ${pathName} resolved to file: ${absolutePath}`);
|
||||
currentPathSpec = relativePath;
|
||||
absoluteToRelativePathMap.set(absolutePath, relativePath);
|
||||
onDebugMessage(
|
||||
`Path ${pathName} resolved to file: ${absolutePath}, using relative path: ${relativePath}`,
|
||||
);
|
||||
}
|
||||
resolvedSuccessfully = true;
|
||||
} catch (error) {
|
||||
@@ -266,6 +280,10 @@ export async function handleAtCommand({
|
||||
if (lines.length > 1 && lines[1]) {
|
||||
const firstMatchAbsolute = lines[1].trim();
|
||||
currentPathSpec = path.relative(dir, firstMatchAbsolute);
|
||||
absoluteToRelativePathMap.set(
|
||||
firstMatchAbsolute,
|
||||
currentPathSpec,
|
||||
);
|
||||
onDebugMessage(
|
||||
`Glob search for ${pathName} found ${firstMatchAbsolute}, using relative path: ${currentPathSpec}`,
|
||||
);
|
||||
@@ -305,7 +323,8 @@ export async function handleAtCommand({
|
||||
if (resolvedSuccessfully) {
|
||||
pathSpecsToRead.push(currentPathSpec);
|
||||
atPathToResolvedSpecMap.set(originalAtPath, currentPathSpec);
|
||||
contentLabelsForDisplay.push(pathName);
|
||||
const displayPath = path.isAbsolute(pathName) ? relativePath : pathName;
|
||||
contentLabelsForDisplay.push(displayPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -430,10 +449,27 @@ export async function handleAtCommand({
|
||||
if (typeof part === 'string') {
|
||||
const match = fileContentRegex.exec(part);
|
||||
if (match) {
|
||||
const filePathSpecInContent = match[1]; // This is a resolved pathSpec
|
||||
const filePathSpecInContent = match[1];
|
||||
const fileActualContent = match[2].trim();
|
||||
|
||||
let displayPath = absoluteToRelativePathMap.get(
|
||||
filePathSpecInContent,
|
||||
);
|
||||
|
||||
// Fallback: if no mapping found, try to convert absolute path to relative
|
||||
if (!displayPath) {
|
||||
for (const dir of config.getWorkspaceContext().getDirectories()) {
|
||||
if (filePathSpecInContent.startsWith(dir)) {
|
||||
displayPath = path.relative(dir, filePathSpecInContent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayPath = displayPath || filePathSpecInContent;
|
||||
|
||||
processedQueryParts.push({
|
||||
text: `\nContent from @${filePathSpecInContent}:\n`,
|
||||
text: `\nContent from @${displayPath}:\n`,
|
||||
});
|
||||
processedQueryParts.push({ text: fileActualContent });
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user