Skip to content

Commit 4f0bd1f

Browse files
roblourenspierceboggan
authored andcommitted
Fix backtick in search query breaking tool result codespan (#515)
* Fix backtick in search query breaking tool result codespan * Fix
1 parent 0fd4b27 commit 4f0bd1f

File tree

2 files changed

+29
-4
lines changed

2 files changed

+29
-4
lines changed

src/extension/tools/node/findTextInFilesTool.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,27 @@ export class FindTextInFilesTool implements ICopilotTool<IFindTextInFilesToolPar
102102
};
103103
}
104104

105-
private formatQueryString(input: IFindTextInFilesToolParams): string {
106-
return input.includePattern && input.includePattern !== '**/*' ?
107-
`\`${input.query}\` (\`${input.includePattern}\`)` :
108-
`\`${input.query}\``;
105+
/**
106+
* Formats text as a Markdown inline code span that is resilient to backticks within the text.
107+
* It chooses a backtick fence one longer than the longest run of backticks in the content,
108+
* and pads with a space when the content begins or ends with a backtick as per CommonMark.
109+
*/
110+
private formatCodeSpan(text: string): string {
111+
const matches = text.match(/`+/g);
112+
const maxRun = matches ? matches.reduce((m, s) => Math.max(m, s.length), 0) : 0;
113+
const fence = '`'.repeat(maxRun + 1);
114+
const needsPadding = text.startsWith('`') || text.endsWith('`');
115+
const inner = needsPadding ? ` ${text} ` : text;
116+
return `${fence}${inner}${fence}`;
117+
}
109118

119+
private formatQueryString(input: IFindTextInFilesToolParams): string {
120+
const querySpan = this.formatCodeSpan(input.query);
121+
if (input.includePattern && input.includePattern !== '**/*') {
122+
const patternSpan = this.formatCodeSpan(input.includePattern);
123+
return `${querySpan} (${patternSpan})`;
124+
}
125+
return querySpan;
110126
}
111127

112128
async resolveInput(input: IFindTextInFilesToolParams, _promptContext: IBuildPromptContext, mode: CopilotToolMode): Promise<IFindTextInFilesToolParams> {

src/extension/tools/node/test/findTextInFiles.spec.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SyncDescriptor } from '../../../../util/vs/platform/instantiation/commo
1717
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
1818
import { createExtensionUnitTestingServices } from '../../../test/node/services';
1919
import { FindTextInFilesTool } from '../findTextInFilesTool';
20+
import { MarkdownString } from '../../../../util/vs/base/common/htmlContent';
2021

2122
suite('FindTextInFiles', () => {
2223
let accessor: ITestingServicesAccessor;
@@ -72,6 +73,14 @@ suite('FindTextInFiles', () => {
7273
const tool = accessor.get(IInstantiationService).createInstance(FindTextInFilesTool);
7374
await tool.invoke({ input: { query: 'hello', includePattern: workspaceFolder }, toolInvocationToken: null!, }, CancellationToken.None);
7475
});
76+
77+
test('escapes backtick', async () => {
78+
setup(new RelativePattern(URI.file(workspaceFolder), ''));
79+
80+
const tool = accessor.get(IInstantiationService).createInstance(FindTextInFilesTool);
81+
const prepared = await tool.prepareInvocation({ input: { query: 'hello `world`' }, }, CancellationToken.None);
82+
expect((prepared?.invocationMessage as any as MarkdownString).value).toMatchInlineSnapshot(`"Searching text for \`\` hello \`world\` \`\`"`);
83+
});
7584
});
7685

7786
class TestSearchService extends AbstractSearchService {

0 commit comments

Comments
 (0)