Skip to content

Combine related md stripping functions #256563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions src/vs/base/browser/markdownRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { onUnexpectedError } from '../common/errors.js';
import { Event } from '../common/event.js';
import { escapeDoubleQuotes, IMarkdownString, isMarkdownString, MarkdownStringTrustedOptions, parseHrefAndDimensions, removeMarkdownEscapes } from '../common/htmlContent.js';
import { escapeDoubleQuotes, IMarkdownString, MarkdownStringTrustedOptions, parseHrefAndDimensions, removeMarkdownEscapes } from '../common/htmlContent.js';
import { markdownEscapeEscapedIcons } from '../common/iconLabels.js';
import { defaultGenerator } from '../common/idGenerator.js';
import { KeyCode } from '../common/keyCodes.js';
Expand Down Expand Up @@ -585,28 +585,25 @@ function getSanitizerOptions(options: IInternalSanitizerOptions): domSanitize.Sa
}

/**
* Strips all markdown from `string`, if it's an IMarkdownString. For example
* `# Header` would be output as `Header`. If it's not, the string is returned.
*/
export function renderStringAsPlaintext(string: IMarkdownString | string) {
return isMarkdownString(string) ? renderMarkdownAsPlaintext(string) : string;
}

/**
* Strips all markdown from `markdown`
* Renders `str` as plaintext, stripping out Markdown syntax if it's a {@link IMarkdownString}.
*
* For example `# Header` would be output as `Header`.
*
* @param withCodeBlocks Include the ``` of code blocks as well
*/
export function renderMarkdownAsPlaintext(markdown: IMarkdownString, withCodeBlocks?: boolean) {
export function renderAsPlaintext(str: IMarkdownString | string, options?: {
/** Controls if the ``` of code blocks should be preserved in the output or not */
readonly includeCodeBlocksFences?: boolean;
}) {
if (typeof str === 'string') {
return str;
}

// values that are too long will freeze the UI
let value = markdown.value ?? '';
let value = str.value ?? '';
if (value.length > 100_000) {
value = `${value.substr(0, 100_000)}…`;
}

const html = marked.parse(value, { async: false, renderer: withCodeBlocks ? plainTextWithCodeBlocksRenderer.value : plainTextRenderer.value });
const html = marked.parse(value, { async: false, renderer: options?.includeCodeBlocksFences ? plainTextWithCodeBlocksRenderer.value : plainTextRenderer.value });
return sanitizeRenderedMarkdown({ isTrusted: false }, html)
.toString()
.replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m)
Expand Down
8 changes: 4 additions & 4 deletions src/vs/base/browser/ui/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { IContextMenuProvider } from '../../contextmenu.js';
import { addDisposableListener, EventHelper, EventType, IFocusTracker, isActiveElement, reset, trackFocus, $ } from '../../dom.js';
import { StandardKeyboardEvent } from '../../keyboardEvent.js';
import { renderMarkdown, renderStringAsPlaintext } from '../../markdownRenderer.js';
import { renderMarkdown, renderAsPlaintext } from '../../markdownRenderer.js';
import { Gesture, EventType as TouchEventType } from '../../touch.js';
import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js';
import { IHoverDelegate } from '../hover/hoverDelegate.js';
Expand Down Expand Up @@ -269,7 +269,7 @@ export class Button extends Disposable implements IButton {
if (typeof this.options.title === 'string') {
title = this.options.title;
} else if (this.options.title) {
title = renderStringAsPlaintext(value);
title = renderAsPlaintext(value);
}

this.setTitle(title);
Expand Down Expand Up @@ -393,7 +393,7 @@ export class ButtonWithDropdown extends Disposable implements IButton {

this.primaryButton = this._register(new Button(this.element, options));
this._register(this.primaryButton.onDidClick(e => this._onDidClick.fire(e)));
this.action = this._register(new Action('primaryAction', renderStringAsPlaintext(this.primaryButton.label), undefined, true, async () => this._onDidClick.fire(undefined)));
this.action = this._register(new Action('primaryAction', renderAsPlaintext(this.primaryButton.label), undefined, true, async () => this._onDidClick.fire(undefined)));

this.separatorContainer = document.createElement('div');
this.separatorContainer.classList.add('monaco-button-dropdown-separator');
Expand Down Expand Up @@ -670,7 +670,7 @@ export class ButtonWithIcon extends Button {
if (typeof this.options.title === 'string') {
title = this.options.title;
} else if (this.options.title) {
title = renderStringAsPlaintext(value);
title = renderAsPlaintext(value);
}

this.setTitle(title);
Expand Down
8 changes: 4 additions & 4 deletions src/vs/base/test/browser/markdownRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import assert from 'assert';
import { fillInIncompleteTokens, renderMarkdown, renderMarkdownAsPlaintext } from '../../browser/markdownRenderer.js';
import { fillInIncompleteTokens, renderMarkdown, renderAsPlaintext } from '../../browser/markdownRenderer.js';
import { IMarkdownString, MarkdownString } from '../../common/htmlContent.js';
import * as marked from '../../common/marked/marked.js';
import { parse } from '../../common/marshalling.js';
Expand Down Expand Up @@ -261,14 +261,14 @@ suite('MarkdownRenderer', () => {
test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => {
const markdown = { value: '`code`\n>quote\n# heading\n- list\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' };
const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text';
const result: string = renderMarkdownAsPlaintext(markdown);
const result: string = renderAsPlaintext(markdown);
assert.strictEqual(result, expected);
});

test('test html, hr, image, link are rendered plaintext', () => {
const markdown = { value: '<div>html</div>\n\n---\n![image](imageLink)\n[text](textLink)' };
const expected = 'text';
const result: string = renderMarkdownAsPlaintext(markdown);
const result: string = renderAsPlaintext(markdown);
assert.strictEqual(result, expected);
});

Expand All @@ -285,7 +285,7 @@ suite('MarkdownRenderer', () => {
'<form>html</form>',
'```',
].join('\n');
const result: string = renderMarkdownAsPlaintext(markdown, true);
const result: string = renderAsPlaintext(markdown, { includeCodeBlocksFences: true });
assert.strictEqual(result, expected);
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/browser/parts/views/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { DataTransfers, IDragAndDropData } from '../../../../base/browser/dnd.js';
import * as DOM from '../../../../base/browser/dom.js';
import * as cssJs from '../../../../base/browser/cssValue.js';
import { renderMarkdownAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js';
import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js';
Expand Down Expand Up @@ -1306,7 +1306,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
} else if (node.tooltip === undefined) {
return label;
} else if (!isString(node.tooltip)) {
return { markdown: node.tooltip, markdownNotSupportedFallback: resource ? undefined : renderMarkdownAsPlaintext(node.tooltip) }; // Passing undefined as the fallback for a resource falls back to the old native hover
return { markdown: node.tooltip, markdownNotSupportedFallback: resource ? undefined : renderAsPlaintext(node.tooltip) }; // Passing undefined as the fallback for a resource falls back to the old native hover
} else if (node.tooltip !== '') {
return node.tooltip;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common
import { AccessibilityProgressSignalScheduler } from '../../../../platform/accessibilitySignal/browser/progressAccessibilitySignalScheduler.js';
import { IChatAccessibilityService } from './chat.js';
import { IChatResponseViewModel } from '../common/chatViewModel.js';
import { renderStringAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { AccessibilityVoiceSettingId } from '../../accessibility/browser/accessibilityConfiguration.js';
Expand Down Expand Up @@ -46,7 +46,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi
return;
}
const errorDetails = isPanelChat && response.errorDetails ? ` ${response.errorDetails.message}` : '';
const plainTextResponse = renderStringAsPlaintext(new MarkdownString(responseContent));
const plainTextResponse = renderAsPlaintext(new MarkdownString(responseContent));
if (!isVoiceInput || this._configurationService.getValue(AccessibilityVoiceSettingId.AutoSynthesize) !== 'on') {
status(plainTextResponse + errorDetails);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as dom from '../../../../../base/browser/dom.js';
import { renderStringAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { Button, ButtonWithDropdown, IButton, IButtonOptions } from '../../../../../base/browser/ui/button/button.js';
import { Action } from '../../../../../base/common/actions.js';
import { Emitter, Event } from '../../../../../base/common/event.js';
Expand Down Expand Up @@ -201,7 +201,7 @@ abstract class BaseChatConfirmationWidget extends Disposable {
this._hostService.focus(targetWindow, { mode: FocusMode.Notify });

// Notify
const title = renderStringAsPlaintext(this.title);
const title = renderAsPlaintext(this.title);
const notification = await dom.triggerNotification(title ? localize('notificationTitle', "Chat: {0}", title) : localize('defaultTitle', "Chat: Confirmation Required"),
{
detail: localize('notificationDetail', "The current chat session requires your confirmation to proceed.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Codicon } from '../../../../../base/common/codicons.js';
import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import * as arrays from '../../../../../base/common/arrays.js';
import { renderStringAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';

class ChatEditorOverlayWidget extends Disposable {
Expand Down Expand Up @@ -112,7 +112,7 @@ class ChatEditorOverlayWidget extends Disposable {
if (!busy || !value || this._session.read(r)?.isGlobalEditingSession) {
textProgress.innerText = '';
} else if (value) {
textProgress.innerText = renderStringAsPlaintext(value.message);
textProgress.innerText = renderAsPlaintext(value.message);
}
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { renderMarkdownAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { stripIcons } from '../../../../base/common/iconLabels.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
Expand Down Expand Up @@ -71,7 +71,7 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi
for (const toolInvocation of toolInvocations) {
if (toolInvocation.confirmationMessages) {
const title = typeof toolInvocation.confirmationMessages.title === 'string' ? toolInvocation.confirmationMessages.title : toolInvocation.confirmationMessages.title.value;
const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderMarkdownAsPlaintext(toolInvocation.confirmationMessages.message));
const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderAsPlaintext(toolInvocation.confirmationMessages.message));
let input = '';
if (toolInvocation.toolSpecificData) {
input = toolInvocation.toolSpecificData?.kind === 'terminal'
Expand All @@ -89,20 +89,20 @@ class ChatResponseAccessibleProvider extends Disposable implements IAccessibleVi
responseContent += `\n${message}\n`;
} else if (toolInvocation.isComplete && toolInvocation.resultDetails && 'input' in toolInvocation.resultDetails) {
responseContent += '\n' + toolInvocation.resultDetails.isError ? 'Errored ' : 'Completed ';
responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderMarkdownAsPlaintext(toolInvocation.invocationMessage))} with input: ${toolInvocation.resultDetails.input}`}\n`;
responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderAsPlaintext(toolInvocation.invocationMessage))} with input: ${toolInvocation.resultDetails.input}`}\n`;
}
}

const pastConfirmations = item.response.value.filter(item => item.kind === 'toolInvocationSerialized');
for (const pastConfirmation of pastConfirmations) {
if (pastConfirmation.isComplete && pastConfirmation.resultDetails && 'input' in pastConfirmation.resultDetails) {
if (pastConfirmation.pastTenseMessage) {
responseContent += `\n${`${typeof pastConfirmation.pastTenseMessage === 'string' ? pastConfirmation.pastTenseMessage : stripIcons(renderMarkdownAsPlaintext(pastConfirmation.pastTenseMessage))} with input: ${pastConfirmation.resultDetails.input}`}\n`;
responseContent += `\n${`${typeof pastConfirmation.pastTenseMessage === 'string' ? pastConfirmation.pastTenseMessage : stripIcons(renderAsPlaintext(pastConfirmation.pastTenseMessage))} with input: ${pastConfirmation.resultDetails.input}`}\n`;
}
}
}
}
return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true);
return renderAsPlaintext(new MarkdownString(responseContent), { includeCodeBlocksFences: true });
}

onClose(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { renderStringAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { assertNever } from '../../../../base/common/assert.js';
import { RunOnceScheduler } from '../../../../base/common/async.js';
import { encodeBase64 } from '../../../../base/common/buffer.js';
Expand Down Expand Up @@ -295,7 +295,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
} else {
const prepared = await this.prepareToolInvocation(tool, dto, token);
if (prepared?.confirmationMessages && !this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace)) {
const result = await this._dialogService.confirm({ message: renderStringAsPlaintext(prepared.confirmationMessages.title), detail: renderStringAsPlaintext(prepared.confirmationMessages.message) });
const result = await this._dialogService.confirm({ message: renderAsPlaintext(prepared.confirmationMessages.title), detail: renderAsPlaintext(prepared.confirmationMessages.message) });
if (!result.confirmed) {
throw new CancellationError();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { renderStringAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { RunOnceScheduler, disposableTimeout, raceCancellation } from '../../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../base/common/codicons.js';
Expand Down Expand Up @@ -818,7 +818,7 @@ class ChatSynthesizerSessions {
}

return {
chunk: chunk ? renderStringAsPlaintext({ value: chunk }) : chunk, // convert markdown to plain text
chunk: chunk ? renderAsPlaintext({ value: chunk }) : chunk, // convert markdown to plain text
offset
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { MarkdownString } from '../../../../base/common/htmlContent.js';
import { renderMarkdownAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';
import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';

export class InlineChatAccessibleView implements IAccessibleViewImplementation {
Expand All @@ -37,7 +37,7 @@ export class InlineChatAccessibleView implements IAccessibleViewImplementation {
return new AccessibleContentProvider(
AccessibleViewProviderId.InlineChat,
{ type: AccessibleViewType.View },
() => renderMarkdownAsPlaintext(new MarkdownString(responseContent), true),
() => renderAsPlaintext(new MarkdownString(responseContent), { includeCodeBlocksFences: true }),
() => controller.focus(),
AccessibilityVerbositySettingId.InlineChat
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { renderMarkdownAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { Emitter, Event } from '../../../../../base/common/event.js';
import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
import { marked } from '../../../../../base/common/marked/marked.js';
Expand Down Expand Up @@ -317,7 +317,7 @@ export function* getMarkdownHeadersInCell(cellContent: string): Iterable<{ reado
if (token.type === 'heading') {
yield {
depth: token.depth,
text: renderMarkdownAsPlaintext({ value: token.raw }).trim()
text: renderAsPlaintext({ value: token.raw }).trim()
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { renderMarkdownAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { IOutlineModelService, OutlineModelService } from '../../../../../editor/contrib/documentSymbols/browser/outlineModel.js';
import { localize } from '../../../../../nls.js';
Expand Down Expand Up @@ -87,7 +87,7 @@ export class NotebookOutlineEntryFactory implements INotebookOutlineEntryFactory
}

if (!hasHeader) {
content = renderMarkdownAsPlaintext({ value: content });
content = renderAsPlaintext({ value: content });
}
}

Expand Down
Loading
Loading