mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-12 17:48:19 +01:00
Fix inline reference at block start rendering on its own line (#298497)
Copilot-authored message: Fix inline reference at block start rendering on its own line When an inlineReference is the first item in a chat response (no preceding markdown), it creates a standalone markdownContent with default MarkdownString properties. The subsequent markdown from the model has different properties (e.g. isTrusted: true), causing canMergeMarkdownStrings to return false. This leaves them as separate content parts, rendering the inline reference link on its own line. Fix by detecting when the previous item consists solely of synthesized content-ref links (via isContentRefOnly check) and merging them by prepending the reference text while adopting the incoming markdown's properties. Fixes #278191
This commit is contained in:
@@ -57,9 +57,25 @@ export function annotateSpecialMarkdownContent(response: Iterable<IChatProgressR
|
||||
result.push({ content: new MarkdownString(markdownText), inlineReferences: annotationMetadata, kind: 'markdownContent' });
|
||||
}
|
||||
}
|
||||
} else if (item.kind === 'markdownContent' && previousItem?.kind === 'markdownContent' && canMergeMarkdownStrings(previousItem.content, item.content)) {
|
||||
const merged = appendMarkdownString(previousItem.content, item.content);
|
||||
result[previousItemIndex] = { ...previousItem, content: merged };
|
||||
} else if (item.kind === 'markdownContent' && previousItem?.kind === 'markdownContent') {
|
||||
if (canMergeMarkdownStrings(previousItem.content, item.content)) {
|
||||
const merged = appendMarkdownString(previousItem.content, item.content);
|
||||
result[previousItemIndex] = { ...previousItem, content: merged };
|
||||
} else if (previousItem.inlineReferences && isContentRefOnly(previousItem.content.value)) {
|
||||
// The previous item is a standalone inline reference whose MarkdownString
|
||||
// was synthesized with default properties that don't match the incoming
|
||||
// markdown (e.g., different isTrusted). Prepend the reference text and
|
||||
// adopt the incoming item's properties so they render together in one block.
|
||||
result[previousItemIndex] = {
|
||||
...previousItem,
|
||||
content: {
|
||||
...item.content,
|
||||
value: previousItem.content.value + item.content.value,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
} else if (item.kind === 'markdownVuln') {
|
||||
const vulnText = encodeURIComponent(JSON.stringify(item.vulnerabilities));
|
||||
const markdownText = `<vscode_annotation details='${vulnText}'>${item.content.value}</vscode_annotation>`;
|
||||
@@ -88,6 +104,18 @@ export function annotateSpecialMarkdownContent(response: Iterable<IChatProgressR
|
||||
return result;
|
||||
}
|
||||
|
||||
const contentRefPattern = new RegExp(`^(\\[.*?\\]\\(${contentRefUrl}/\\d+\\))+$`);
|
||||
|
||||
/**
|
||||
* Returns true when the text consists entirely of synthesized content-ref
|
||||
* links (e.g. `[file.ts](http://_vscodecontentref_/0)`), with no other
|
||||
* markdown text mixed in. Used to decide whether the MarkdownString
|
||||
* properties are "synthetic defaults" that can safely be replaced.
|
||||
*/
|
||||
function isContentRefOnly(text: string): boolean {
|
||||
return contentRefPattern.test(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the end of a markdown string is inside a code context
|
||||
* (fenced code block or inline code span) where markdown link syntax
|
||||
|
||||
@@ -239,5 +239,39 @@ suite('Annotations', function () {
|
||||
assert.ok(!md.content.value.includes('_vscodecontentref_'));
|
||||
assert.ok(md.content.value.endsWith('index.ts'));
|
||||
});
|
||||
|
||||
test('inline reference at start of block merges with following markdown', () => {
|
||||
const result = annotateSpecialMarkdownContent([
|
||||
{ kind: 'inlineReference', inlineReference: URI.parse('file:///index.ts'), name: 'index.ts' },
|
||||
{ kind: 'markdownContent', content: new MarkdownString(' is the entry point', { isTrusted: true, supportThemeIcons: true }) },
|
||||
]);
|
||||
|
||||
assert.strictEqual(result.length, 1);
|
||||
const md = result[0] as IChatMarkdownContent;
|
||||
assert.ok(md.content.value.includes('[index.ts]'));
|
||||
assert.ok(md.content.value.includes('_vscodecontentref_'));
|
||||
assert.ok(md.content.value.endsWith(' is the entry point'));
|
||||
assert.ok(md.inlineReferences);
|
||||
assert.strictEqual(md.content.isTrusted, true);
|
||||
assert.strictEqual(md.content.supportThemeIcons, true);
|
||||
});
|
||||
|
||||
test('inline reference after regular text does not force-merge incompatible markdown', () => {
|
||||
const result = annotateSpecialMarkdownContent([
|
||||
content('See '),
|
||||
{ kind: 'inlineReference', inlineReference: URI.parse('file:///index.ts'), name: 'index.ts' },
|
||||
{ kind: 'markdownContent', content: new MarkdownString(' more info', { isTrusted: true, supportThemeIcons: true }) },
|
||||
]);
|
||||
|
||||
// The first item has "See [index.ts](...)" with default markdown properties,
|
||||
// the second item has different properties - they must stay separate.
|
||||
assert.strictEqual(result.length, 2);
|
||||
const first = result[0] as IChatMarkdownContent;
|
||||
assert.ok(first.content.value.startsWith('See '));
|
||||
assert.ok(first.inlineReferences);
|
||||
const second = result[1] as IChatMarkdownContent;
|
||||
assert.strictEqual(second.content.value, ' more info');
|
||||
assert.strictEqual(second.content.isTrusted, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user