Skip to content

Commit ae8192a

Browse files
committed
Add markdown renderer for plugin tree tooltips
Signed-off-by: robmor01 <[email protected]>
1 parent 10d5275 commit ae8192a

File tree

10 files changed

+173
-9
lines changed

10 files changed

+173
-9
lines changed

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"perfect-scrollbar": "^1.3.0",
5656
"react": "^16.8.0",
5757
"react-dom": "^16.8.0",
58+
"react-tooltip": "^4.2.21",
5859
"react-virtualized": "^9.20.0",
5960
"reconnecting-websocket": "^4.2.0",
6061
"reflect-metadata": "^0.1.10",

packages/core/src/browser/frontend-application-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ import {
118118
DefaultBreadcrumbRenderer,
119119
} from './breadcrumbs';
120120
import { RendererHost } from './widgets';
121+
import { TooltipService, TooltipServiceImpl } from './tooltip-service';
121122

122123
export { bindResourceProvider, bindMessageService, bindPreferenceService };
123124

@@ -186,6 +187,9 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
186187
bind(CommandOpenHandler).toSelf().inSingletonScope();
187188
bind(OpenHandler).toService(CommandOpenHandler);
188189

190+
bind(TooltipServiceImpl).toSelf().inSingletonScope();
191+
bind(TooltipService).toService(TooltipServiceImpl);
192+
189193
bindContributionProvider(bind, ApplicationShellLayoutMigration);
190194
bind<ApplicationShellLayoutMigration>(ApplicationShellLayoutMigration).toConstantValue({
191195
layoutVersion: 2.0,

packages/core/src/browser/frontend-application.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { FrontendApplicationStateService } from './frontend-application-state';
2525
import { preventNavigation, parseCssTime, animationFrame } from './browser';
2626
import { CorePreferences } from './core-preferences';
2727
import { WindowService } from './window/window-service';
28+
import { TooltipService } from './tooltip-service';
2829

2930
/**
3031
* Clients can implement to get a callback for contributing widgets to a shell on start.
@@ -100,6 +101,9 @@ export class FrontendApplication {
100101
@inject(WindowService)
101102
protected readonly windowsService: WindowService;
102103

104+
@inject(TooltipService)
105+
protected readonly tooltipService: TooltipService;
106+
103107
constructor(
104108
@inject(CommandRegistry) protected readonly commands: CommandRegistry,
105109
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry,
@@ -130,6 +134,7 @@ export class FrontendApplication {
130134

131135
const host = await this.getHost();
132136
this.attachShell(host);
137+
this.attachTooltip(host);
133138
await animationFrame();
134139
this.stateService.state = 'attached_shell';
135140

@@ -221,6 +226,13 @@ export class FrontendApplication {
221226
Widget.attach(this.shell, host, ref);
222227
}
223228

229+
/**
230+
* Attach the tooltip container to the host element.
231+
*/
232+
protected attachTooltip(host: HTMLElement): void {
233+
this.tooltipService.attachTo(host);
234+
}
235+
224236
/**
225237
* If a startup indicator is present, it is first hidden with the `theia-hidden` CSS class and then
226238
* removed after a while. The delay until removal is taken from the CSS transition duration.

packages/core/src/browser/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ export * from './diff-uris';
4040
export * from './core-preferences';
4141
export * from './view-container';
4242
export * from './breadcrumbs';
43+
export * from './tooltip-service';

packages/core/src/browser/style/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,4 @@ button.secondary[disabled], .theia-button.secondary[disabled] {
259259
@import './quick-title-bar.css';
260260
@import './progress-bar.css';
261261
@import './breadcrumbs.css';
262+
@import './tooltip.css';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/********************************************************************************
2+
* Copyright (C) 2021 Arm and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
.theia-tooltip {
18+
color: var(--theia-list-hoverForeground) !important;
19+
background: var(--theia-list-hoverBackground) !important;
20+
border: 1px solid !important;
21+
border-color: var(--theia-list-hoverForeground) !important;
22+
}
23+
24+
/* Hide tooltip arrow */
25+
.theia-tooltip::before,
26+
.theia-tooltip::after {
27+
border: none !important;
28+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/********************************************************************************
2+
* Copyright (C) 2021 Arm and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { injectable, inject, optional } from 'inversify';
18+
import * as React from 'react';
19+
import ReactTooltip from 'react-tooltip';
20+
import { ReactRenderer, RendererHost } from './widgets/react-renderer';
21+
import { v4 } from 'uuid';
22+
23+
export const TooltipService = Symbol('TooltipService');
24+
25+
export interface TooltipService {
26+
attachTo(host: HTMLElement): void;
27+
tooltipId: string;
28+
update(): void;
29+
}
30+
31+
/**
32+
* Attributes to be added to an HTML element to enable
33+
* rich HTML tooltip rendering
34+
*/
35+
export interface TooltipAttributes {
36+
/**
37+
* HTML to render in the tooltip.
38+
*/
39+
'data-tip': string;
40+
/**
41+
* The ID of the tooltip renderer. Should be TOOLTIP_ID.
42+
*/
43+
'data-for': string;
44+
}
45+
46+
@injectable()
47+
export class TooltipServiceImpl extends ReactRenderer implements TooltipService {
48+
public readonly tooltipId: string;
49+
protected rendered = false;
50+
51+
constructor(
52+
@inject(RendererHost) @optional() host?: RendererHost
53+
) {
54+
super(host);
55+
this.tooltipId = v4();
56+
}
57+
58+
public attachTo(host: HTMLElement): void {
59+
host.appendChild(this.host);
60+
}
61+
62+
public update(): void {
63+
if (!this.rendered) {
64+
this.render();
65+
this.rendered = true;
66+
}
67+
68+
ReactTooltip.rebuild();
69+
}
70+
71+
protected doRender(): React.ReactNode {
72+
return <ReactTooltip id={this.tooltipId} className='theia-tooltip' html={true} delayShow={1000} />;
73+
}
74+
}

packages/plugin-ext/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@theia/terminal": "1.17.0",
2727
"@theia/timeline": "1.17.0",
2828
"@theia/workspace": "1.17.0",
29+
"@types/markdown-it": "*",
2930
"@types/mime": "^2.0.1",
3031
"decompress": "^4.2.1",
3132
"escape-html": "^1.0.3",
@@ -34,6 +35,7 @@
3435
"jsonc-parser": "^2.2.0",
3536
"lodash.clonedeep": "^4.5.0",
3637
"macaddress": "^0.2.9",
38+
"markdown-it": "^8.4.0",
3739
"mime": "^2.4.4",
3840
"ps-tree": "^1.2.0",
3941
"request": "^2.82.0",

packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import {
2929
TREE_NODE_SEGMENT_GROW_CLASS,
3030
TREE_NODE_TAIL_CLASS,
3131
TreeModelImpl,
32-
TreeViewWelcomeWidget
32+
TreeViewWelcomeWidget,
33+
TooltipService,
34+
TooltipAttributes
3335
} from '@theia/core/lib/browser';
3436
import { TreeViewItem, TreeViewItemCollapsibleState } from '../../../common/plugin-api-rpc';
3537
import { MenuPath, MenuModelRegistry, ActionMenuNode } from '@theia/core/lib/common/menu';
@@ -42,6 +44,8 @@ import { MessageService } from '@theia/core/lib/common/message-service';
4244
import { View } from '../../../common/plugin-protocol';
4345
import CoreURI from '@theia/core/lib/common/uri';
4446
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
47+
import * as markdownit from 'markdown-it';
48+
import { isMarkdownString } from '../../../plugin/markdown-string';
4549

4650
export const TREE_NODE_HYPERLINK = 'theia-TreeNodeHyperlink';
4751
export const VIEW_ITEM_CONTEXT_MENU: MenuPath = ['view-item-context-menu'];
@@ -245,6 +249,9 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
245249
@inject(ContextKeyService)
246250
protected readonly contextKeyService: ContextKeyService;
247251

252+
@inject(TooltipService)
253+
protected readonly tooltipService: TooltipService;
254+
248255
protected readonly onDidChangeVisibilityEmitter = new Emitter<boolean>();
249256
readonly onDidChangeVisibility = this.onDidChangeVisibilityEmitter.event;
250257

@@ -274,13 +281,32 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
274281
classes.push(TREE_NODE_SEGMENT_GROW_CLASS);
275282
}
276283
const className = classes.join(' ');
277-
const title = node.tooltip ||
278-
(node.resourceUri && this.labelProvider.getLongName(new CoreURI(node.resourceUri)))
279-
|| this.toNodeName(node);
280-
const attrs = this.decorateCaption(node, {
281-
className, id: node.id,
282-
title
283-
});
284+
285+
let attrs: React.HTMLAttributes<HTMLElement> & Partial<TooltipAttributes> = {
286+
...this.decorateCaption(node, {}),
287+
className,
288+
id: node.id
289+
};
290+
291+
if (node.tooltip && isMarkdownString(node.tooltip)) {
292+
// Render markdown in custom tooltip
293+
const tooltip = markdownit().render(node.tooltip.value);
294+
295+
attrs = {
296+
...attrs,
297+
'data-tip': tooltip,
298+
'data-for': this.tooltipService.tooltipId
299+
};
300+
} else {
301+
const title = node.tooltip ||
302+
(node.resourceUri && this.labelProvider.getLongName(new CoreURI(node.resourceUri)))
303+
|| this.toNodeName(node);
304+
305+
attrs = {
306+
...attrs,
307+
title
308+
};
309+
}
284310

285311
const children: React.ReactNode[] = [];
286312
const caption = this.toNodeName(node);
@@ -444,7 +470,9 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
444470
}
445471

446472
protected render(): React.ReactNode {
447-
return React.createElement('div', this.createContainerAttributes(), this.renderSearchInfo(), this.renderTree(this.model));
473+
const node = React.createElement('div', this.createContainerAttributes(), this.renderSearchInfo(), this.renderTree(this.model));
474+
this.tooltipService.update();
475+
return node;
448476
}
449477

450478
protected renderSearchInfo(): React.ReactNode {

yarn.lock

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8672,6 +8672,14 @@ react-perfect-scrollbar@^1.5.3:
86728672
perfect-scrollbar "^1.5.0"
86738673
prop-types "^15.6.1"
86748674

8675+
react-tooltip@^4.2.21:
8676+
version "4.2.21"
8677+
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
8678+
integrity sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==
8679+
dependencies:
8680+
prop-types "^15.7.2"
8681+
uuid "^7.0.3"
8682+
86758683
react-virtualized@^9.20.0:
86768684
version "9.22.3"
86778685
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
@@ -10633,6 +10641,11 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.3.3:
1063310641
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
1063410642
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
1063510643

10644+
uuid@^7.0.3:
10645+
version "7.0.3"
10646+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
10647+
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
10648+
1063610649
uuid@^8.0.0, uuid@^8.3.2:
1063710650
version "8.3.2"
1063810651
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"

0 commit comments

Comments
 (0)