Skip to content

Commit 4eb72c9

Browse files
committed
Extract to ChatAgentHover
1 parent 8b8d3e6 commit 4eb72c9

File tree

4 files changed

+126
-63
lines changed

4 files changed

+126
-63
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as dom from 'vs/base/browser/dom';
7+
import { h } from 'vs/base/browser/dom';
8+
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
9+
import { CancellationToken } from 'vs/base/common/cancellation';
10+
import { FileAccess } from 'vs/base/common/network';
11+
import { ThemeIcon } from 'vs/base/common/themables';
12+
import { URI } from 'vs/base/common/uri';
13+
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
14+
import { verifiedPublisherIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
15+
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
16+
17+
export class ChatAgentHover {
18+
public readonly domNode: HTMLElement;
19+
20+
constructor(
21+
id: string,
22+
@IChatAgentService private readonly chatAgentService: IChatAgentService,
23+
@IExtensionsWorkbenchService private readonly extensionService: IExtensionsWorkbenchService,
24+
) {
25+
const agent = this.chatAgentService.getAgent(id)!;
26+
27+
const hoverElement = h(
28+
'.chat-agent-hover@root',
29+
[
30+
h('.chat-agent-hover-header', [
31+
h('.chat-agent-hover-icon@icon'),
32+
h('.chat-agent-hover-details', [
33+
h('.chat-agent-hover-name@name'),
34+
h('.chat-agent-hover-extension', [
35+
h('.chat-agent-hover-extension-name@extensionName'),
36+
h('.chat-agent-hover-separator@separator'),
37+
h('.chat-agent-hover-publisher@publisher'),
38+
]),
39+
]),
40+
]),
41+
h('.chat-agent-hover-description@description'),
42+
]);
43+
this.domNode = hoverElement.root;
44+
45+
if (agent.metadata.icon instanceof URI) {
46+
const avatarIcon = dom.$<HTMLImageElement>('img.icon');
47+
avatarIcon.src = FileAccess.uriToBrowserUri(agent.metadata.icon).toString(true);
48+
hoverElement.icon.replaceChildren(dom.$('.avatar', undefined, avatarIcon));
49+
} else if (agent.metadata.themeIcon) {
50+
const avatarIcon = dom.$(ThemeIcon.asCSSSelector(agent.metadata.themeIcon));
51+
hoverElement.icon.replaceChildren(dom.$('.avatar.codicon-avatar', undefined, avatarIcon));
52+
}
53+
54+
hoverElement.name.textContent = `@${agent.name}`;
55+
hoverElement.extensionName.textContent = agent.extensionDisplayName;
56+
hoverElement.separator.textContent = '|';
57+
58+
const verifiedBadge = dom.$('span.extension-verified-publisher', undefined, renderIcon(verifiedPublisherIcon));
59+
verifiedBadge.style.display = 'none';
60+
dom.append(
61+
hoverElement.publisher,
62+
verifiedBadge,
63+
agent.extensionPublisher);
64+
65+
66+
const description = agent.description && !agent.description.endsWith('.') ?
67+
`${agent.description}. ` :
68+
(agent.description || '');
69+
hoverElement.description.textContent = description;
70+
71+
// const marketplaceLink = document.createElement('a');
72+
// marketplaceLink.setAttribute('href', `command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([agent.extensionId.value]))}`);
73+
// marketplaceLink.textContent = localize('marketplaceLabel', "View in Marketplace") + '.';
74+
// hoverElement.description.appendChild(marketplaceLink);
75+
76+
this.extensionService.getExtensions([{ id: agent.extensionId.value }], CancellationToken.None).then(extensions => {
77+
const extension = extensions[0];
78+
if (extension?.publisherDomain?.verified) {
79+
verifiedBadge.style.display = '';
80+
}
81+
});
82+
}
83+
}

src/vs/workbench/contrib/chat/browser/chatListRenderer.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,14 @@ import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/text
7171
import { TextEdit } from 'vs/editor/common/languages';
7272
import { IChatListItemRendererOptions } from './chat';
7373
import { CancellationTokenSource } from 'vs/base/common/cancellation';
74+
import { IHoverService } from 'vs/platform/hover/browser/hover';
75+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
76+
import { ChatAgentHover } from 'vs/workbench/contrib/chat/browser/chatAgentHover';
7477

7578
const $ = dom.$;
7679

7780
interface IChatListItemTemplate {
81+
currentElement?: ChatTreeItem;
7882
readonly rowContainer: HTMLElement;
7983
readonly titleToolbar?: MenuWorkbenchToolBar;
8084
readonly avatarContainer: HTMLElement;
@@ -146,6 +150,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
146150
@ICommandService private readonly commandService: ICommandService,
147151
@ITextModelService private readonly textModelService: ITextModelService,
148152
@IModelService private readonly modelService: IModelService,
153+
@IHoverService private readonly hoverService: IHoverService,
149154
) {
150155
super();
151156

@@ -283,6 +288,16 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
283288
}
284289
}));
285290
}
291+
292+
templateDisposables.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), header, () => {
293+
if (isResponseVM(template.currentElement) && template.currentElement.agent) {
294+
const hover = this.instantiationService.createInstance(ChatAgentHover, template.currentElement.agent.id);
295+
return hover.domNode;
296+
}
297+
298+
return undefined;
299+
}));
300+
286301
const template: IChatListItemTemplate = { avatarContainer, agentAvatarContainer, username, detail, referencesListContainer, value, rowContainer, elementDisposables, titleToolbar, templateDisposables, contextKeyService };
287302
return template;
288303
}
@@ -292,6 +307,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
292307
}
293308

294309
renderChatTreeItem(element: ChatTreeItem, index: number, templateData: IChatListItemTemplate): void {
310+
templateData.currentElement = element;
295311
const kind = isRequestVM(element) ? 'request' :
296312
isResponseVM(element) ? 'response' :
297313
'welcome';
@@ -399,11 +415,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
399415
}
400416

401417
templateData.detail.textContent = progressMsg;
402-
if (element.agent) {
403-
templateData.detail.title = progressMsg + (element.slashCommand?.description ? `\n${element.slashCommand.description}` : '');
404-
} else {
405-
templateData.detail.title = '';
406-
}
407418
}
408419

409420
private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void {
@@ -1021,7 +1032,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
10211032
disposables.add(toDisposable(() => this.codeBlocksByResponseId.delete(element.id)));
10221033
}
10231034

1024-
this.markdownDecorationsRenderer.walkTreeAndAnnotateReferenceLinks(result.element);
1035+
disposables.add(this.markdownDecorationsRenderer.walkTreeAndAnnotateReferenceLinks(result.element));
10251036

10261037
orderedDisposablesList.reverse().forEach(d => disposables.add(d));
10271038
return {

src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@
55

66
import * as dom from 'vs/base/browser/dom';
77
import { toErrorMessage } from 'vs/base/common/errorMessage';
8+
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
89
import { revive } from 'vs/base/common/marshalling';
910
import { URI } from 'vs/base/common/uri';
1011
import { Location } from 'vs/editor/common/languages';
12+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1113
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1214
import { ILabelService } from 'vs/platform/label/common/label';
1315
import { ILogService } from 'vs/platform/log/common/log';
16+
import { ChatAgentHover } from 'vs/workbench/contrib/chat/browser/chatAgentHover';
1417
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
1518
import { ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
1619
import { contentRefUrl } from '../common/annotations';
1720
import { IHoverService } from 'vs/platform/hover/browser/hover';
1821
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
19-
import { h } from 'vs/base/browser/dom';
20-
import { FileAccess } from 'vs/base/common/network';
21-
import { ThemeIcon } from 'vs/base/common/themables';
22-
import { localize } from 'vs/nls';
23-
import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
2422

2523
const variableRefUrl = 'http://_vscodedecoration_';
2624
const agentRefUrl = 'http://_chatagent_';
@@ -31,6 +29,7 @@ export class ChatMarkdownDecorationsRenderer {
3129
@ILabelService private readonly labelService: ILabelService,
3230
@ILogService private readonly logService: ILogService,
3331
@IChatAgentService private readonly chatAgentService: IChatAgentService,
32+
@IInstantiationService private readonly instantiationService: IInstantiationService,
3433
@IHoverService private readonly hoverService: IHoverService,
3534
) { }
3635

@@ -62,14 +61,15 @@ export class ChatMarkdownDecorationsRenderer {
6261
return result;
6362
}
6463

65-
walkTreeAndAnnotateReferenceLinks(element: HTMLElement): void {
64+
walkTreeAndAnnotateReferenceLinks(element: HTMLElement): IDisposable {
65+
const store = new DisposableStore();
6666
element.querySelectorAll('a').forEach(a => {
6767
const href = a.getAttribute('data-href');
6868
if (href) {
6969
if (href.startsWith(agentRefUrl)) {
7070
const title = decodeURIComponent(href.slice(agentRefUrl.length + 1));
7171
a.parentElement!.replaceChild(
72-
this.renderAgentWidget(a.textContent!, title),
72+
this.renderAgentWidget(a.textContent!, title, store),
7373
a);
7474
} else if (href.startsWith(variableRefUrl)) {
7575
const title = decodeURIComponent(href.slice(variableRefUrl.length + 1));
@@ -83,57 +83,17 @@ export class ChatMarkdownDecorationsRenderer {
8383
}
8484
}
8585
});
86-
}
87-
88-
private renderAgentWidget(name: string, id: string): HTMLElement {
89-
const agent = this.chatAgentService.getAgent(id)!;
90-
91-
const container = dom.$('span.chat-resource-widget');
92-
const alias = dom.$('span', undefined, name);
9386

94-
const hoverElement = h(
95-
'.chat-agent-hover@root',
96-
[
97-
h('.chat-agent-hover-header', [
98-
h('.chat-agent-hover-icon@icon'),
99-
h('.chat-agent-hover-details', [
100-
h('.chat-agent-hover-name@name'),
101-
h('.chat-agent-hover-extension', [
102-
h('.chat-agent-hover-extension-name@extensionName'),
103-
h('.chat-agent-hover-separator@separator'),
104-
h('.chat-agent-hover-publisher@publisher'),
105-
]),
106-
]),
107-
]),
108-
h('.chat-agent-hover-description@description'),
109-
]);
110-
111-
if (agent.metadata.icon instanceof URI) {
112-
const avatarIcon = dom.$<HTMLImageElement>('img.icon');
113-
avatarIcon.src = FileAccess.uriToBrowserUri(agent.metadata.icon).toString(true);
114-
hoverElement.icon.replaceChildren(dom.$('.avatar', undefined, avatarIcon));
115-
} else if (agent.metadata.themeIcon) {
116-
const avatarIcon = dom.$(ThemeIcon.asCSSSelector(agent.metadata.themeIcon));
117-
hoverElement.icon.replaceChildren(dom.$('.avatar.codicon-avatar', undefined, avatarIcon));
118-
}
119-
120-
hoverElement.name.textContent = `@${agent.name}`;
121-
hoverElement.extensionName.textContent = agent.extensionDisplayName;
122-
hoverElement.separator.textContent = ' | ';
123-
hoverElement.publisher.textContent = agent.extensionPublisher;
124-
125-
const description = agent.description && !agent.description.endsWith('.') ?
126-
`${agent.description}. ` :
127-
(agent.description || '');
128-
hoverElement.description.textContent = description;
87+
return store;
88+
}
12989

130-
const marketplaceLink = document.createElement('a');
131-
marketplaceLink.setAttribute('href', `command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([agent.extensionId.value]))}`);
132-
marketplaceLink.textContent = localize('marketplaceLabel', "View in Marketplace") + '.';
133-
hoverElement.description.appendChild(marketplaceLink);
90+
private renderAgentWidget(name: string, id: string, store: DisposableStore): HTMLElement {
91+
const container = dom.$('span.chat-resource-widget', undefined, dom.$('span', undefined, name));
13492

135-
this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), container, hoverElement.root);
136-
container.appendChild(alias);
93+
store.add(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('element'), container, () => {
94+
const hover = this.instantiationService.createInstance(ChatAgentHover, id);
95+
return hover.domNode;
96+
}));
13797
return container;
13898
}
13999

src/vs/workbench/contrib/chat/browser/media/chatHover.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,34 @@
2121
outline: 1px solid var(--vscode-chat-requestBorder);
2222
}
2323

24-
.chat-agent-hover-icon .codicon {
24+
.monaco-hover .markdown-hover .hover-contents .chat-agent-hover-icon .codicon {
2525
font-size: 23px;
2626
display: flex;
2727
justify-content: center;
2828
align-items: center;
2929
}
3030

31+
.chat-agent-hover-publisher {
32+
display: flex;
33+
gap: 4px;
34+
}
35+
36+
.monaco-hover .chat-agent-hover .chat-agent-hover-publisher .codicon.codicon-extensions-verified-publisher {
37+
color: var(--vscode-extensionIcon-verifiedForeground);
38+
}
39+
3140
.chat-agent-hover-header .chat-agent-hover-name {
3241
font-size: 15px;
3342
font-weight: 600;
3443
}
3544

3645
.chat-agent-hover-extension {
3746
display: flex;
47+
gap: 6px;
3848
}
3949

4050
.chat-agent-hover-separator {
4151
opacity: 0.7;
42-
margin: 0px 6px;
4352
}
4453

4554
.chat-agent-hover-description {

0 commit comments

Comments
 (0)