Skip to content

Commit

Permalink
Add rich tooltips for plugin trees and VSX extensions (#10108)
Browse files Browse the repository at this point in the history
* Add markdown renderer for plugin tree tooltips

Signed-off-by: robmor01 <[email protected]>

* Removed launch.json fixes

* Don't use readme details

* Add preference for tooltip delay

* Ensure extensions have a version starting with 'v'

* Add fullRender option

* Update packages/core/src/browser/tooltip-service.tsx
  • Loading branch information
thegecko authored Oct 14, 2021
1 parent 3ccbfb0 commit 5d88bf5
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"perfect-scrollbar": "^1.3.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-tooltip": "^4.2.21",
"react-virtualized": "^9.20.0",
"reconnecting-websocket": "^4.2.0",
"reflect-metadata": "^0.1.10",
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export const corePreferenceSchema: PreferenceSchema = {
default: 'onHover',
description: 'Controls whether the tree should render indent guides.'
},
'workbench.hover.delay': {
type: 'number',
default: isOSX ? 1500 : 500,
description: 'Controls the delay in milliseconds after which the hover is shown for workbench items (ex. some extension provided tree view items).'
},
}
};

Expand All @@ -142,6 +147,7 @@ export interface CoreConfiguration {
'workbench.silentNotifications': boolean;
'workbench.statusBar.visible': boolean;
'workbench.tree.renderIndentGuides': 'onHover' | 'none' | 'always';
'workbench.hover.delay': number;
}

export const CorePreferenceContribution = Symbol('CorePreferenceContribution');
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ import {
DefaultBreadcrumbRenderer,
} from './breadcrumbs';
import { RendererHost } from './widgets';
import { TooltipService, TooltipServiceImpl } from './tooltip-service';

export { bindResourceProvider, bindMessageService, bindPreferenceService };

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

bind(TooltipServiceImpl).toSelf().inSingletonScope();
bind(TooltipService).toService(TooltipServiceImpl);

bindContributionProvider(bind, ApplicationShellLayoutMigration);
bind<ApplicationShellLayoutMigration>(ApplicationShellLayoutMigration).toConstantValue({
layoutVersion: 2.0,
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/browser/frontend-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { FrontendApplicationStateService } from './frontend-application-state';
import { preventNavigation, parseCssTime, animationFrame } from './browser';
import { CorePreferences } from './core-preferences';
import { WindowService } from './window/window-service';
import { TooltipService } from './tooltip-service';

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

@inject(TooltipService)
protected readonly tooltipService: TooltipService;

constructor(
@inject(CommandRegistry) protected readonly commands: CommandRegistry,
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry,
Expand Down Expand Up @@ -130,6 +134,7 @@ export class FrontendApplication {

const host = await this.getHost();
this.attachShell(host);
this.attachTooltip(host);
await animationFrame();
this.stateService.state = 'attached_shell';

Expand Down Expand Up @@ -221,6 +226,13 @@ export class FrontendApplication {
Widget.attach(this.shell, host, ref);
}

/**
* Attach the tooltip container to the host element.
*/
protected attachTooltip(host: HTMLElement): void {
this.tooltipService.attachTo(host);
}

/**
* If a startup indicator is present, it is first hidden with the `theia-hidden` CSS class and then
* removed after a while. The delay until removal is taken from the CSS transition duration.
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ export * from './diff-uris';
export * from './core-preferences';
export * from './view-container';
export * from './breadcrumbs';
export * from './tooltip-service';
1 change: 1 addition & 0 deletions packages/core/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,4 @@ button.secondary[disabled], .theia-button.secondary[disabled] {
@import './quick-title-bar.css';
@import './progress-bar.css';
@import './breadcrumbs.css';
@import './tooltip.css';
28 changes: 28 additions & 0 deletions packages/core/src/browser/style/tooltip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* Copyright (C) 2021 Arm and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

.theia-tooltip {
color: var(--theia-list-hoverForeground) !important;
background: var(--theia-list-hoverBackground) !important;
border: 1px solid !important;
border-color: var(--theia-list-hoverForeground) !important;
}

/* Hide tooltip arrow */
.theia-tooltip::before,
.theia-tooltip::after {
border: none !important;
}
98 changes: 98 additions & 0 deletions packages/core/src/browser/tooltip-service.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/********************************************************************************
* Copyright (C) 2021 Arm and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable, inject, optional, postConstruct } from 'inversify';
import * as React from 'react';
import ReactTooltip from 'react-tooltip';
import { ReactRenderer, RendererHost } from './widgets/react-renderer';
import { CorePreferences } from './core-preferences';
import { DisposableCollection } from '../common/disposable';
import { v4 } from 'uuid';

export const TooltipService = Symbol('TooltipService');

export interface TooltipService {
tooltipId: string;
attachTo(host: HTMLElement): void;
update(fullRender?: boolean): void;
}

/**
* Attributes to be added to an HTML element to enable
* rich HTML tooltip rendering
*/
export interface TooltipAttributes {
/**
* HTML to render in the tooltip.
*/
'data-tip': string;
/**
* The ID of the tooltip renderer. Should be TOOLTIP_ID.
*/
'data-for': string;
}

const DELAY_PREFERENCE = 'workbench.hover.delay';

@injectable()
export class TooltipServiceImpl extends ReactRenderer implements TooltipService {

@inject(CorePreferences)
protected readonly corePreferences: CorePreferences;

public readonly tooltipId: string;
protected rendered = false;
protected toDispose: DisposableCollection = new DisposableCollection();

constructor(
@inject(RendererHost) @optional() host?: RendererHost
) {
super(host);
this.tooltipId = v4();
}

@postConstruct()
protected init(): void {
this.toDispose.push(this.corePreferences.onPreferenceChanged(preference => {
if (preference.preferenceName === DELAY_PREFERENCE) {
this.update(true);
}
}));
}

public attachTo(host: HTMLElement): void {
host.appendChild(this.host);
}

public update(fullRender = false): void {
if (fullRender || !this.rendered) {
this.render();
this.rendered = true;
}

ReactTooltip.rebuild();
}

protected doRender(): React.ReactNode {
const hoverDelay = this.corePreferences.get(DELAY_PREFERENCE);
return <ReactTooltip id={this.tooltipId} className='theia-tooltip' html={true} delayShow={hoverDelay} />;
}

public dispose(): void {
this.toDispose.dispose();
super.dispose();
}
}
2 changes: 2 additions & 0 deletions packages/plugin-ext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@theia/terminal": "1.18.0",
"@theia/timeline": "1.18.0",
"@theia/workspace": "1.18.0",
"@types/markdown-it": "*",
"@types/mime": "^2.0.1",
"decompress": "^4.2.1",
"escape-html": "^1.0.3",
Expand All @@ -34,6 +35,7 @@
"jsonc-parser": "^2.2.0",
"lodash.clonedeep": "^4.5.0",
"macaddress": "^0.2.9",
"markdown-it": "^8.4.0",
"mime": "^2.4.4",
"ps-tree": "^1.2.0",
"request": "^2.82.0",
Expand Down
46 changes: 37 additions & 9 deletions packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import {
TREE_NODE_SEGMENT_GROW_CLASS,
TREE_NODE_TAIL_CLASS,
TreeModelImpl,
TreeViewWelcomeWidget
TreeViewWelcomeWidget,
TooltipService,
TooltipAttributes
} from '@theia/core/lib/browser';
import { TreeViewItem, TreeViewItemCollapsibleState } from '../../../common/plugin-api-rpc';
import { MenuPath, MenuModelRegistry, ActionMenuNode } from '@theia/core/lib/common/menu';
Expand All @@ -42,6 +44,8 @@ import { MessageService } from '@theia/core/lib/common/message-service';
import { View } from '../../../common/plugin-protocol';
import CoreURI from '@theia/core/lib/common/uri';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
import * as markdownit from 'markdown-it';
import { isMarkdownString } from '../../../plugin/markdown-string';

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

@inject(TooltipService)
protected readonly tooltipService: TooltipService;

protected readonly onDidChangeVisibilityEmitter = new Emitter<boolean>();
readonly onDidChangeVisibility = this.onDidChangeVisibilityEmitter.event;

Expand Down Expand Up @@ -274,13 +281,32 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
classes.push(TREE_NODE_SEGMENT_GROW_CLASS);
}
const className = classes.join(' ');
const title = node.tooltip ||
(node.resourceUri && this.labelProvider.getLongName(new CoreURI(node.resourceUri)))
|| this.toNodeName(node);
const attrs = this.decorateCaption(node, {
className, id: node.id,
title
});

let attrs: React.HTMLAttributes<HTMLElement> & Partial<TooltipAttributes> = {
...this.decorateCaption(node, {}),
className,
id: node.id
};

if (node.tooltip && isMarkdownString(node.tooltip)) {
// Render markdown in custom tooltip
const tooltip = markdownit().render(node.tooltip.value);

attrs = {
...attrs,
'data-tip': tooltip,
'data-for': this.tooltipService.tooltipId
};
} else {
const title = node.tooltip ||
(node.resourceUri && this.labelProvider.getLongName(new CoreURI(node.resourceUri)))
|| this.toNodeName(node);

attrs = {
...attrs,
title
};
}

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

protected render(): React.ReactNode {
return React.createElement('div', this.createContainerAttributes(), this.renderSearchInfo(), this.renderTree(this.model));
const node = React.createElement('div', this.createContainerAttributes(), this.renderSearchInfo(), this.renderTree(this.model));
this.tooltipService.update();
return node;
}

protected renderSearchInfo(): React.ReactNode {
Expand Down
2 changes: 2 additions & 0 deletions packages/vsx-registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"@theia/plugin-ext-vscode": "1.18.0",
"@theia/preferences": "1.18.0",
"@theia/workspace": "1.18.0",
"@types/markdown-it": "*",
"@types/bent": "^7.0.1",
"@types/showdown": "^1.7.1",
"bent": "^7.1.0",
"markdown-it": "^8.4.0",
"p-debounce": "^2.1.0",
"requestretry": "^3.1.0",
"semver": "^5.4.1",
Expand Down
Loading

0 comments on commit 5d88bf5

Please sign in to comment.