Add chat file icon widget

Moving logic into class and adopting `IconLabel`
This commit is contained in:
Matt Bierner
2024-09-05 11:50:55 -07:00
parent ec4019f7b6
commit 4e2d09d5a9
3 changed files with 76 additions and 32 deletions
+4 -6
View File
@@ -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) {
@@ -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());
}));
}
}
@@ -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;
}