Skip to content

Commit 1e12b94

Browse files
committed
fix: align viewWelcome to VS Code
Closes: eclipse-theia#14309 Ref: arduino/arduino-ide#2543 Signed-off-by: dankeboy36 <dankeboy36@gmail.com>
1 parent ea62f67 commit 1e12b94

File tree

5 files changed

+86
-54
lines changed

5 files changed

+86
-54
lines changed

packages/core/src/browser/tree/tree-view-welcome-widget.tsx

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,35 @@
1919
* Licensed under the MIT License. See License.txt in the project root for license information.
2020
*--------------------------------------------------------------------------------------------*/
2121
// some code is copied and modified from: https://github.com/microsoft/vscode/blob/573e5145ae3b50523925a6f6315d373e649d1b06/src/vs/base/common/linkedText.ts
22+
// aligned the API and enablement behavior to https://github.com/microsoft/vscode/blob/c711bc9333ba339fde1a530de0094b3fa32f09de/src/vs/base/common/linkedText.ts
2223

2324
import React = require('react');
2425
import { inject, injectable } from 'inversify';
25-
import { CommandRegistry } from '../../common';
26+
import { URI as CodeUri } from 'vscode-uri';
27+
import { CommandRegistry, DisposableCollection } from '../../common';
28+
import URI from '../../common/uri';
2629
import { ContextKeyService } from '../context-key-service';
30+
import { LabelIcon, LabelParser } from '../label-parser';
31+
import { OpenerService, open } from '../opener-service';
32+
import { codicon } from '../widgets';
33+
import { WindowService } from '../window/window-service';
2734
import { TreeModel } from './tree-model';
2835
import { TreeWidget } from './tree-widget';
29-
import { WindowService } from '../window/window-service';
3036

31-
interface ViewWelcome {
37+
export interface ViewWelcome {
3238
readonly view: string;
3339
readonly content: string;
3440
readonly when?: string;
41+
readonly enablement?: string;
3542
readonly order: number;
3643
}
3744

38-
interface IItem {
45+
export interface IItem {
3946
readonly welcomeInfo: ViewWelcome;
4047
visible: boolean;
4148
}
4249

43-
interface ILink {
50+
export interface ILink {
4451
readonly label: string;
4552
readonly href: string;
4653
readonly title?: string;
@@ -60,6 +67,14 @@ export class TreeViewWelcomeWidget extends TreeWidget {
6067
@inject(WindowService)
6168
protected readonly windowService: WindowService;
6269

70+
@inject(LabelParser)
71+
protected readonly labelParser: LabelParser;
72+
73+
@inject(OpenerService)
74+
protected readonly openerService: OpenerService;
75+
76+
protected readonly toDisposeBeforeUpdateViewWelcomeNodes = new DisposableCollection();
77+
6378
protected viewWelcomeNodes: React.ReactNode[] = [];
6479
protected defaultItem: IItem | undefined;
6580
protected items: IItem[] = [];
@@ -130,13 +145,31 @@ export class TreeViewWelcomeWidget extends TreeWidget {
130145

131146
protected updateViewWelcomeNodes(): void {
132147
this.viewWelcomeNodes = [];
148+
this.toDisposeBeforeUpdateViewWelcomeNodes.dispose();
133149
const items = this.visibleItems.sort((a, b) => a.order - b.order);
134150

135-
for (const [iIndex, { content }] of items.entries()) {
151+
const enablementKeys: Set<string>[] = [];
152+
// the plugin-view-registry will push the changes when there is a change in the `when` prop which controls the visibility
153+
// this listener is to update the enablement of the components in the view welcome
154+
this.toDisposeBeforeUpdateViewWelcomeNodes.push(
155+
this.contextService.onDidChange(event => {
156+
if (enablementKeys.some(keys => event.affects(keys))) {
157+
this.updateViewWelcomeNodes();
158+
this.update();
159+
}
160+
})
161+
);
162+
// Note: VS Code does not support the `renderSecondaryButtons` prop in welcome content either.
163+
for (const { content, enablement } of items) {
164+
const itemEnablementKeys = enablement
165+
? this.contextService.parseKeys(enablement)
166+
: undefined;
167+
if (itemEnablementKeys) {
168+
enablementKeys.push(itemEnablementKeys);
169+
}
136170
const lines = content.split('\n');
137171

138-
for (let [lIndex, line] of lines.entries()) {
139-
const lineKey = `${iIndex}-${lIndex}`;
172+
for (let line of lines) {
140173
line = line.trim();
141174

142175
if (!line) {
@@ -146,95 +179,98 @@ export class TreeViewWelcomeWidget extends TreeWidget {
146179
const linkedTextItems = this.parseLinkedText(line);
147180

148181
if (linkedTextItems.length === 1 && typeof linkedTextItems[0] !== 'string') {
182+
const node = linkedTextItems[0];
149183
this.viewWelcomeNodes.push(
150-
this.renderButtonNode(linkedTextItems[0], lineKey)
184+
this.renderButtonNode(
185+
node,
186+
this.viewWelcomeNodes.length,
187+
enablement
188+
)
151189
);
152190
} else {
153-
const linkedTextNodes: React.ReactNode[] = [];
154-
155-
for (const [nIndex, node] of linkedTextItems.entries()) {
156-
const linkedTextKey = `${lineKey}-${nIndex}`;
157-
158-
if (typeof node === 'string') {
159-
linkedTextNodes.push(
160-
this.renderTextNode(node, linkedTextKey)
161-
);
162-
} else {
163-
linkedTextNodes.push(
164-
this.renderCommandLinkNode(node, linkedTextKey)
165-
);
166-
}
167-
}
191+
const renderNode = (item: LinkedTextItem, index: number) => typeof item == 'string'
192+
? this.renderTextNode(item, index)
193+
: this.renderLinkNode(item, index, enablement);
168194

169195
this.viewWelcomeNodes.push(
170-
<div key={`line-${lineKey}`}>
171-
{...linkedTextNodes}
172-
</div>
196+
<p key={`p-${this.viewWelcomeNodes.length}`}>
197+
{...linkedTextItems.flatMap(renderNode)}
198+
</p>
173199
);
174200
}
175201
}
176202
}
177203
}
178204

179-
protected renderButtonNode(node: ILink, lineKey: string): React.ReactNode {
205+
protected renderButtonNode(node: ILink, lineKey: string | number, enablement: string | undefined): React.ReactNode {
180206
return (
181207
<div key={`line-${lineKey}`} className='theia-WelcomeViewButtonWrapper'>
182208
<button title={node.title}
183209
className='theia-button theia-WelcomeViewButton'
184-
disabled={!this.isEnabledClick(node.href)}
210+
disabled={!this.isEnabledClick(enablement)}
185211
onClick={e => this.openLinkOrCommand(e, node.href)}>
186212
{node.label}
187213
</button>
188214
</div>
189215
);
190216
}
191217

192-
protected renderTextNode(node: string, textKey: string): React.ReactNode {
193-
return <span key={`text-${textKey}`}>{node}</span>;
218+
protected renderTextNode(node: string, textKey: string | number): React.ReactNode {
219+
return <span key={`text-${textKey}`}>
220+
{this.labelParser.parse(node)
221+
.map((segment, index) =>
222+
LabelIcon.is(segment)
223+
? <span
224+
key={index}
225+
className={codicon(segment.name)}
226+
/>
227+
: <span key={index}>{segment}</span>)}</span>;
194228
}
195229

196-
protected renderCommandLinkNode(node: ILink, linkKey: string): React.ReactNode {
230+
protected renderLinkNode(node: ILink, linkKey: string | number, enablement: string | undefined): React.ReactNode {
197231
return (
198232
<a key={`link-${linkKey}`}
199-
className={this.getLinkClassName(node.href)}
233+
className={this.getLinkClassName(node.href, enablement)}
200234
title={node.title || ''}
201235
onClick={e => this.openLinkOrCommand(e, node.href)}>
202236
{node.label}
203237
</a>
204238
);
205239
}
206240

207-
protected getLinkClassName(href: string): string {
241+
protected getLinkClassName(href: string, enablement: string | undefined): string {
208242
const classNames = ['theia-WelcomeViewCommandLink'];
209-
if (!this.isEnabledClick(href)) {
243+
// Only command-backed links can be disabled. All other, https:, file: remain enabled
244+
if (href.startsWith('command:') && !this.isEnabledClick(enablement)) {
210245
classNames.push('disabled');
211246
}
212247
return classNames.join(' ');
213248
}
214249

215-
protected isEnabledClick(href: string): boolean {
216-
if (href.startsWith('command:')) {
217-
const command = href.replace('command:', '');
218-
return this.commands.isEnabled(command);
219-
}
220-
return true;
250+
protected isEnabledClick(enablement: string | undefined): boolean {
251+
return typeof enablement === 'string'
252+
? this.contextService.match(enablement)
253+
: true;
221254
}
222255

223-
protected openLinkOrCommand = (event: React.MouseEvent, href: string): void => {
256+
protected openLinkOrCommand = (event: React.MouseEvent, value: string): void => {
224257
event.stopPropagation();
225258

226-
if (href.startsWith('command:')) {
227-
const command = href.replace('command:', '');
259+
if (value.startsWith('command:')) {
260+
const command = value.replace('command:', '');
228261
this.commands.executeCommand(command);
262+
} else if (value.startsWith('file:')) {
263+
const uri = value.replace('file:', '');
264+
open(this.openerService, new URI(CodeUri.file(uri).toString()));
229265
} else {
230-
this.windowService.openNewWindow(href, { external: true });
266+
this.windowService.openNewWindow(value, { external: true });
231267
}
232268
};
233269

234270
protected parseLinkedText(text: string): LinkedTextItem[] {
235271
const result: LinkedTextItem[] = [];
236272

237-
const linkRegex = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi;
273+
const linkRegex = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi;
238274
let index = 0;
239275
let match: RegExpExecArray | null;
240276

packages/filesystem/src/browser/breadcrumbs/filepath-breadcrumbs-container.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// *****************************************************************************
1616

1717
import { Container, interfaces, injectable, inject } from '@theia/core/shared/inversify';
18-
import { TreeProps, ContextMenuRenderer, TreeNode, OpenerService, open, NodeProps, defaultTreeProps } from '@theia/core/lib/browser';
18+
import { TreeProps, ContextMenuRenderer, TreeNode, open, NodeProps, defaultTreeProps } from '@theia/core/lib/browser';
1919
import { FileTreeModel, FileStatNode, createFileTreeContainer, FileTreeWidget } from '../file-tree';
2020

2121
const BREADCRUMBS_FILETREE_CLASS = 'theia-FilepathBreadcrumbFileTree';
@@ -35,9 +35,6 @@ export function createFileTreeBreadcrumbsWidget(parent: interfaces.Container): B
3535
@injectable()
3636
export class BreadcrumbsFileTreeWidget extends FileTreeWidget {
3737

38-
@inject(OpenerService)
39-
protected readonly openerService: OpenerService;
40-
4138
constructor(
4239
@inject(TreeProps) props: TreeProps,
4340
@inject(FileTreeModel) override readonly model: FileTreeModel,

packages/plugin-ext/src/common/plugin-protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export interface PluginPackageViewWelcome {
193193
view: string;
194194
contents: string;
195195
when?: string;
196+
enablement?: string;
196197
}
197198

198199
export interface PluginPackageCommand {
@@ -853,6 +854,7 @@ export interface ViewWelcome {
853854
view: string;
854855
content: string;
855856
when?: string;
857+
enablement?: string;
856858
order: number;
857859
}
858860

packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ export class TheiaPluginScanner extends AbstractPluginScanner {
730730
view: rawViewWelcome.view,
731731
content: rawViewWelcome.contents,
732732
when: rawViewWelcome.when,
733+
enablement: rawViewWelcome.enablement,
733734
// if the plugin contributes Welcome view to its own view - it will be ordered first
734735
order: pluginViewsIds.findIndex(v => v === rawViewWelcome.view) > -1 ? 0 : 1
735736
};

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import { View } from '../../../common/plugin-protocol';
4545
import { URI } from '@theia/core/lib/common/uri';
4646
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
4747
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
48-
import { LabelParser } from '@theia/core/lib/browser/label-parser';
4948
import { AccessibilityInformation } from '@theia/plugin';
5049
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
5150
import { DecoratedTreeNode } from '@theia/core/lib/browser/tree/tree-decorator';
@@ -456,9 +455,6 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
456455
@inject(HoverService)
457456
protected readonly hoverService: HoverService;
458457

459-
@inject(LabelParser)
460-
protected readonly labelParser: LabelParser;
461-
462458
@inject(ColorRegistry)
463459
protected readonly colorRegistry: ColorRegistry;
464460

0 commit comments

Comments
 (0)