diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 100ac6b7396..447d5e02c3f 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -180,13 +180,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende if (options.actionHandler) { const _activateLink = function (event: StandardMouseEvent | StandardKeyboardEvent): void { - let target: HTMLElement | null = event.target; - if (target.tagName !== 'A') { - target = target.parentElement; - if (!target || target.tagName !== 'A') { - return; - } + const target = event.target.closest('a[data-href]'); + if (!DOM.isHTMLElement(target)) { + return; } + try { let href = target.dataset['href']; if (href) { diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 37d85b0762d..d3e17a2ade9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -7,19 +7,23 @@ import { applyDragImage } from '../../../../base/browser/dnd.js'; import * as dom from '../../../../base/browser/dom.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { Lazy } from '../../../../base/common/lazy.js'; -import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { revive } from '../../../../base/common/marshalling.js'; import { URI } from '../../../../base/common/uri.js'; import { Location } from '../../../../editor/common/languages.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; +import { getIconClasses } from '../../../../editor/common/services/getIconClasses.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ILabelService } from '../../../../platform/label/common/label.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { listActiveSelectionBackground, listActiveSelectionForeground } from '../../../../platform/theme/common/colorRegistry.js'; +import { listActiveSelectionBackground, listActiveSelectionForeground } from '../../../../platform/theme/common/colors/listColors.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { fillEditorsDragData } from '../../../browser/dnd.js'; @@ -32,6 +36,7 @@ import { IChatVariablesService } from '../common/chatVariables.js'; import { ILanguageModelToolsService } from '../common/languageModelToolsService.js'; import { IChatWidgetService } from './chat.js'; import { ChatAgentHover, getChatAgentHoverOptions } from './chatAgentHover.js'; +import './media/chatInlineFileLinkWidget.css'; /** For rendering slash commands, variables */ const decorationRefUrl = `http://_vscodedecoration_`; @@ -78,10 +83,10 @@ interface IDecorationWidgetArgs { title?: string; } -export class ChatMarkdownDecorationsRenderer { +export class ChatMarkdownDecorationsRenderer extends Disposable { + constructor( @IKeybindingService private readonly keybindingService: IKeybindingService, - @ILabelService private readonly labelService: ILabelService, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -90,9 +95,11 @@ export class ChatMarkdownDecorationsRenderer { @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @ICommandService private readonly commandService: ICommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, + @ILabelService private readonly labelService: ILabelService, @ILanguageModelToolsService private readonly toolsService: ILanguageModelToolsService, - @IThemeService private readonly themeService: IThemeService, - ) { } + ) { + super(); + } convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { let result = ''; @@ -247,28 +254,9 @@ export class ChatMarkdownDecorationsRenderer { return; } - const fragment = location.range ? `${location.range.startLineNumber}-${location.range.endLineNumber}` : ''; - a.setAttribute('data-href', location.uri.with({ fragment }).toString()); - - const label = this.labelService.getUriLabel(location.uri, { relative: true }); - a.replaceChildren(dom.$('code', undefined, label)); - - const title = location.range ? - `${label}#${location.range.startLineNumber}-${location.range.endLineNumber}` : - label; - store.add(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), a, title)); - - // Drag and drop - a.draggable = true; - store.add(dom.addDisposableListener(a, 'dragstart', e => { - this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, [location.uri], e)); - - const theme = this.themeService.getColorTheme(); - applyDragImage(e, label, 'monaco-drag-image', theme.getColor(listActiveSelectionBackground)?.toString(), theme.getColor(listActiveSelectionForeground)?.toString()); - })); + store.add(this.instantiationService.createInstance(InlineFileLinkWidget, a, location)); } - private renderResourceWidget(name: string, args: IDecorationWidgetArgs | undefined, store: DisposableStore): HTMLElement { const container = dom.$('span.chat-resource-widget'); const alias = dom.$('span', undefined, name); @@ -294,3 +282,49 @@ export class ChatMarkdownDecorationsRenderer { } } } + + +class InlineFileLinkWidget extends Disposable { + + constructor( + element: HTMLAnchorElement, + location: Location | { uri: URI; range: undefined }, + @IHoverService hoverService: IHoverService, + @IInstantiationService instantiationService: IInstantiationService, + @ILabelService labelService: ILabelService, + @ILanguageService languageService: ILanguageService, + @IModelService modelService: IModelService, + @IThemeService themeService: IThemeService, + ) { + super(); + + element.classList.add('chat-inline-file-link-widget'); + + const fragment = location.range ? `${location.range.startLineNumber}-${location.range.endLineNumber}` : ''; + element.setAttribute('data-href', location.uri.with({ fragment }).toString()); + + const label = labelService.getUriLabel(location.uri, { relative: true }); + const title = location.range ? + `${label}#${location.range.startLineNumber}-${location.range.endLineNumber}` : + label; + + element.replaceChildren(); + + const resourceLabel = this._register(new IconLabel(element, { supportHighlights: false, supportIcons: true })); + resourceLabel.setLabel(label, undefined, { + extraClasses: getIconClasses(modelService, languageService, location.uri) + }); + + // Hover + this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('element'), element, title)); + + // Drag and drop + element.draggable = true; + this._register(dom.addDisposableListener(element, 'dragstart', e => { + instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, [location.uri], e)); + + const theme = themeService.getColorTheme(); + applyDragImage(e, label, 'monaco-drag-image', theme.getColor(listActiveSelectionBackground)?.toString(), theme.getColor(listActiveSelectionForeground)?.toString()); + })); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chatInlineFileLinkWidget.css b/src/vs/workbench/contrib/chat/browser/media/chatInlineFileLinkWidget.css new file mode 100644 index 00000000000..d4746e3afe3 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/media/chatInlineFileLinkWidget.css @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.chat-inline-file-link-widget .monaco-icon-label { + display: inline-flex; +} + +.chat-inline-file-link-widget .monaco-icon-label .monaco-highlighted-label { + white-space: normal; +}