Skip to content

Commit 5f318d8

Browse files
committed
fix: update change proposal from Theia as is
Ref: #2543 Signed-off-by: dankeboy36 <[email protected]>
1 parent 7a8acdc commit 5f318d8

File tree

1 file changed

+144
-131
lines changed

1 file changed

+144
-131
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,72 @@
1-
// import { OpenerService } from '@theia/core/lib/browser';
1+
import { LabelIcon } from '@theia/core/lib/browser/label-parser';
2+
import { OpenerService, open } from '@theia/core/lib/browser/opener-service';
3+
import { codicon } from '@theia/core/lib/browser/widgets/widget';
24
import { DisposableCollection } from '@theia/core/lib/common/disposable';
3-
import { /*inject,*/ injectable } from '@theia/core/shared/inversify';
5+
import { URI } from '@theia/core/lib/common/uri';
6+
import { inject, injectable } from '@theia/core/shared/inversify';
47
import React from '@theia/core/shared/react';
8+
import { URI as CodeUri } from '@theia/core/shared/vscode-uri';
59
import { TreeViewWidget as TheiaTreeViewWidget } from '@theia/plugin-ext/lib/main/browser/view/tree-view-widget';
610

11+
// Copied back from https://github.com/eclipse-theia/theia/pull/14391
12+
// Remove the patching when Arduino uses Eclipse Theia >1.55.0
13+
// https://github.com/eclipse-theia/theia/blob/8d3c5a11af65448b6700bedd096f8d68f0675541/packages/core/src/browser/tree/tree-view-welcome-widget.tsx#L37-L54
14+
// https://github.com/eclipse-theia/theia/blob/8d3c5a11af65448b6700bedd096f8d68f0675541/packages/core/src/browser/tree/tree-view-welcome-widget.tsx#L146-L298
15+
16+
interface ViewWelcome {
17+
readonly view: string;
18+
readonly content: string;
19+
readonly when?: string;
20+
readonly enablement?: string;
21+
readonly order: number;
22+
}
23+
24+
export interface IItem {
25+
readonly welcomeInfo: ViewWelcome;
26+
visible: boolean;
27+
}
28+
29+
export interface ILink {
30+
readonly label: string;
31+
readonly href: string;
32+
readonly title?: string;
33+
}
34+
35+
type LinkedTextItem = string | ILink;
36+
737
@injectable()
838
export class TreeViewWidget extends TheiaTreeViewWidget {
9-
// @inject(OpenerService)
10-
// private readonly openerService: OpenerService;
39+
@inject(OpenerService)
40+
private readonly openerService: OpenerService;
41+
1142
private readonly toDisposeBeforeUpdateViewWelcomeNodes =
1243
new DisposableCollection();
1344

14-
// The actual rewrite of the viewsWelcome rendering aligned to VS Code to fix https://github.com/eclipse-theia/theia/issues/14309
15-
// Based on https://github.com/microsoft/vscode/blob/56b535f40900080fac8202c77914c5ce49fa4aae/src/vs/workbench/browser/parts/views/viewPane.ts#L228-L299
1645
protected override updateViewWelcomeNodes(): void {
17-
this.toDisposeBeforeUpdateViewWelcomeNodes.dispose();
18-
const viewWelcomes = this.visibleItems.sort((a, b) => a.order - b.order);
1946
this.viewWelcomeNodes = [];
20-
const allEnablementKeys: Set<string>[] = [];
21-
// the plugin-view-registry will push the changes when there is a change in the when context
22-
// this listener is to update the view when the `enablement` of the viewWelcomes changes
47+
this.toDisposeBeforeUpdateViewWelcomeNodes.dispose();
48+
const items = this.visibleItems.sort((a, b) => a.order - b.order);
49+
50+
const enablementKeys: Set<string>[] = [];
51+
// the plugin-view-registry will push the changes when there is a change in the `when` prop which controls the visibility
52+
// this listener is to update the enablement of the components in the view welcome
2353
this.toDisposeBeforeUpdateViewWelcomeNodes.push(
24-
this.contextKeyService.onDidChange((event) => {
25-
if (allEnablementKeys.some((keys) => event.affects(keys))) {
54+
this.contextService.onDidChange((event) => {
55+
if (enablementKeys.some((keys) => event.affects(keys))) {
2656
this.updateViewWelcomeNodes();
2757
this.update();
2858
}
2959
})
3060
);
31-
// TODO: support `renderSecondaryButtons` prop from VS Code?
32-
for (const viewWelcome of viewWelcomes) {
33-
const { content } = viewWelcome;
34-
const enablement = isEnablementAware(viewWelcome)
35-
? viewWelcome.enablement
36-
: undefined;
37-
const enablementKeys = enablement
38-
? this.contextKeyService.parseKeys(enablement)
61+
// Note: VS Code does not support the `renderSecondaryButtons` prop in welcome content either.
62+
for (const item of items) {
63+
const { content } = item;
64+
const enablement = isEnablementAware(item) ? item.enablement : undefined;
65+
const itemEnablementKeys = enablement
66+
? this.contextService.parseKeys(enablement)
3967
: undefined;
40-
if (enablementKeys) {
41-
allEnablementKeys.push(enablementKeys);
68+
if (itemEnablementKeys) {
69+
enablementKeys.push(itemEnablementKeys);
4270
}
4371
const lines = content.split('\n');
4472

@@ -49,180 +77,165 @@ export class TreeViewWidget extends TheiaTreeViewWidget {
4977
continue;
5078
}
5179

52-
const linkedText = parseLinkedText(line);
80+
const linkedTextItems = this.parseLinkedText_patch14309(line);
5381

5482
if (
55-
linkedText.nodes.length === 1 &&
56-
typeof linkedText.nodes[0] !== 'string'
83+
linkedTextItems.length === 1 &&
84+
typeof linkedTextItems[0] !== 'string'
5785
) {
58-
const node = linkedText.nodes[0];
86+
const node = linkedTextItems[0];
5987
this.viewWelcomeNodes.push(
60-
this.renderButtonNode(
88+
this.renderButtonNode_patch14309(
6189
node,
6290
this.viewWelcomeNodes.length,
6391
enablement
6492
)
6593
);
6694
} else {
67-
const paragraphNodes: React.ReactNode[] = [];
68-
for (const node of linkedText.nodes) {
69-
if (typeof node === 'string') {
70-
paragraphNodes.push(
71-
this.renderTextNode(node, this.viewWelcomeNodes.length)
72-
);
73-
} else {
74-
paragraphNodes.push(
75-
this.renderCommandLinkNode(
76-
node,
77-
this.viewWelcomeNodes.length,
78-
enablement
79-
)
80-
);
81-
}
82-
}
83-
if (paragraphNodes.length) {
84-
this.viewWelcomeNodes.push(
85-
<p key={`p-${this.viewWelcomeNodes.length}`}>
86-
{...paragraphNodes}
87-
</p>
88-
);
89-
}
95+
const renderNode = (item: LinkedTextItem, index: number) =>
96+
typeof item == 'string'
97+
? this.renderTextNode_patch14309(item, index)
98+
: this.renderLinkNode_patch14309(item, index, enablement);
99+
100+
this.viewWelcomeNodes.push(
101+
<p key={`p-${this.viewWelcomeNodes.length}`}>
102+
{...linkedTextItems.flatMap(renderNode)}
103+
</p>
104+
);
90105
}
91106
}
92107
}
93108
}
94109

95-
protected override renderButtonNode(
110+
private renderButtonNode_patch14309(
96111
node: ILink,
97112
lineKey: string | number,
98-
enablement: string | undefined = undefined
113+
enablement: string | undefined
99114
): React.ReactNode {
100115
return (
101116
<div key={`line-${lineKey}`} className="theia-WelcomeViewButtonWrapper">
102117
<button
103118
title={node.title}
104119
className="theia-button theia-WelcomeViewButton"
105-
disabled={!this.isEnabled(enablement)}
106-
onClick={(e) => this.open(e, node)}
120+
disabled={!this.isEnabledClick_patch14309(enablement)}
121+
onClick={(e) => this.openLinkOrCommand_patch14309(e, node.href)}
107122
>
108123
{node.label}
109124
</button>
110125
</div>
111126
);
112127
}
113128

114-
protected override renderCommandLinkNode(
129+
private renderTextNode_patch14309(
130+
node: string,
131+
textKey: string | number
132+
): React.ReactNode {
133+
return (
134+
<span key={`text-${textKey}`}>
135+
{this.labelParser
136+
.parse(node)
137+
.map((segment, index) =>
138+
LabelIcon.is(segment) ? (
139+
<span key={index} className={codicon(segment.name)} />
140+
) : (
141+
<span key={index}>{segment}</span>
142+
)
143+
)}
144+
</span>
145+
);
146+
}
147+
148+
private renderLinkNode_patch14309(
115149
node: ILink,
116150
linkKey: string | number,
117-
enablement: string | undefined = undefined
151+
enablement: string | undefined
118152
): React.ReactNode {
119153
return (
120154
<a
121155
key={`link-${linkKey}`}
122-
className={this.getLinkClassName(node.href, enablement)}
123-
title={node.title ?? ''}
124-
onClick={(e) => this.open(e, node)}
156+
className={this.getLinkClassName_patch14309(node.href, enablement)}
157+
title={node.title || ''}
158+
onClick={(e) => this.openLinkOrCommand_patch14309(e, node.href)}
125159
>
126160
{node.label}
127161
</a>
128162
);
129163
}
130164

131-
protected override renderTextNode(
132-
node: string,
133-
textKey: string | number
134-
): React.ReactNode {
135-
return <span key={`text-${textKey}`}>{node}</span>;
136-
}
137-
138-
protected override getLinkClassName(
165+
private getLinkClassName_patch14309(
139166
href: string,
140-
enablement: string | undefined = undefined
167+
enablement: string | undefined
141168
): string {
142169
const classNames = ['theia-WelcomeViewCommandLink'];
143170
// Only command-backed links can be disabled. All other, https:, file: remain enabled
144-
if (href.startsWith('command:') && !this.isEnabled(enablement)) {
171+
if (
172+
href.startsWith('command:') &&
173+
!this.isEnabledClick_patch14309(enablement)
174+
) {
145175
classNames.push('disabled');
146176
}
147177
return classNames.join(' ');
148178
}
149179

150-
private open(event: React.MouseEvent, node: ILink): void {
151-
event.preventDefault();
152-
if (node.href.startsWith('command:')) {
153-
const commandId = node.href.substring('commands:'.length - 1);
154-
this.commands.executeCommand(commandId);
155-
} else if (node.href.startsWith('file:')) {
156-
// TODO: check what Code does
157-
} else if (node.href.startsWith('https:')) {
158-
this.windowService.openNewWindow(node.href, { external: true });
159-
}
160-
}
161-
162-
/**
163-
* @param enablement [when context](https://code.visualstudio.com/api/references/when-clause-contexts) expression string
164-
*/
165-
private isEnabled(enablement: string | undefined): boolean {
180+
private isEnabledClick_patch14309(enablement: string | undefined): boolean {
166181
return typeof enablement === 'string'
167-
? this.contextKeyService.match(enablement)
182+
? this.contextService.match(enablement)
168183
: true;
169184
}
170-
}
171-
172-
interface EnablementAware {
173-
readonly enablement: string | undefined;
174-
}
175185

176-
function isEnablementAware(arg: unknown): arg is EnablementAware {
177-
return !!arg && typeof arg === 'object' && 'enablement' in arg;
178-
}
179-
180-
// https://github.com/microsoft/vscode/blob/56b535f40900080fac8202c77914c5ce49fa4aae/src/vs/base/common/linkedText.ts#L8-L56
181-
export interface ILink {
182-
readonly label: string;
183-
readonly href: string;
184-
readonly title?: string;
185-
}
186+
private openLinkOrCommand_patch14309 = (
187+
event: React.MouseEvent,
188+
value: string
189+
): void => {
190+
event.stopPropagation();
191+
192+
if (value.startsWith('command:')) {
193+
const command = value.replace('command:', '');
194+
this.commands.executeCommand(command);
195+
} else if (value.startsWith('file:')) {
196+
const uri = value.replace('file:', '');
197+
open(this.openerService, new URI(CodeUri.file(uri).toString()));
198+
} else {
199+
this.windowService.openNewWindow(value, { external: true });
200+
}
201+
};
186202

187-
export type LinkedTextNode = string | ILink;
203+
private parseLinkedText_patch14309(text: string): LinkedTextItem[] {
204+
const result: LinkedTextItem[] = [];
188205

189-
export class LinkedText {
190-
constructor(readonly nodes: LinkedTextNode[]) {}
191-
toString(): string {
192-
return this.nodes
193-
.map((node) => (typeof node === 'string' ? node : node.label))
194-
.join('');
195-
}
196-
}
206+
const linkRegex =
207+
/\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi;
208+
let index = 0;
209+
let match: RegExpExecArray | null;
197210

198-
const LINK_REGEX =
199-
/\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi;
211+
while ((match = linkRegex.exec(text))) {
212+
if (match.index - index > 0) {
213+
result.push(text.substring(index, match.index));
214+
}
200215

201-
export function parseLinkedText(text: string): LinkedText {
202-
const result: LinkedTextNode[] = [];
216+
const [, label, href, , title] = match;
203217

204-
let index = 0;
205-
let match: RegExpExecArray | null;
218+
if (title) {
219+
result.push({ label, href, title });
220+
} else {
221+
result.push({ label, href });
222+
}
206223

207-
while ((match = LINK_REGEX.exec(text))) {
208-
if (match.index - index > 0) {
209-
result.push(text.substring(index, match.index));
224+
index = match.index + match[0].length;
210225
}
211226

212-
const [, label, href, , title] = match;
213-
214-
if (title) {
215-
result.push({ label, href, title });
216-
} else {
217-
result.push({ label, href });
227+
if (index < text.length) {
228+
result.push(text.substring(index));
218229
}
219230

220-
index = match.index + match[0].length;
231+
return result;
221232
}
233+
}
222234

223-
if (index < text.length) {
224-
result.push(text.substring(index));
225-
}
235+
interface EnablementAware {
236+
readonly enablement: string | undefined;
237+
}
226238

227-
return new LinkedText(result);
239+
function isEnablementAware(arg: unknown): arg is EnablementAware {
240+
return !!arg && typeof arg === 'object' && 'enablement' in arg;
228241
}

0 commit comments

Comments
 (0)