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' ;
2
4
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' ;
4
7
import React from '@theia/core/shared/react' ;
8
+ import { URI as CodeUri } from '@theia/core/shared/vscode-uri' ;
5
9
import { TreeViewWidget as TheiaTreeViewWidget } from '@theia/plugin-ext/lib/main/browser/view/tree-view-widget' ;
6
10
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
+
7
37
@injectable ( )
8
38
export class TreeViewWidget extends TheiaTreeViewWidget {
9
- // @inject (OpenerService)
10
- // private readonly openerService: OpenerService;
39
+ @inject ( OpenerService )
40
+ private readonly openerService : OpenerService ;
41
+
11
42
private readonly toDisposeBeforeUpdateViewWelcomeNodes =
12
43
new DisposableCollection ( ) ;
13
44
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
16
45
protected override updateViewWelcomeNodes ( ) : void {
17
- this . toDisposeBeforeUpdateViewWelcomeNodes . dispose ( ) ;
18
- const viewWelcomes = this . visibleItems . sort ( ( a , b ) => a . order - b . order ) ;
19
46
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
23
53
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 ) ) ) {
26
56
this . updateViewWelcomeNodes ( ) ;
27
57
this . update ( ) ;
28
58
}
29
59
} )
30
60
) ;
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 )
39
67
: undefined ;
40
- if ( enablementKeys ) {
41
- allEnablementKeys . push ( enablementKeys ) ;
68
+ if ( itemEnablementKeys ) {
69
+ enablementKeys . push ( itemEnablementKeys ) ;
42
70
}
43
71
const lines = content . split ( '\n' ) ;
44
72
@@ -49,180 +77,165 @@ export class TreeViewWidget extends TheiaTreeViewWidget {
49
77
continue ;
50
78
}
51
79
52
- const linkedText = parseLinkedText ( line ) ;
80
+ const linkedTextItems = this . parseLinkedText_patch14309 ( line ) ;
53
81
54
82
if (
55
- linkedText . nodes . length === 1 &&
56
- typeof linkedText . nodes [ 0 ] !== 'string'
83
+ linkedTextItems . length === 1 &&
84
+ typeof linkedTextItems [ 0 ] !== 'string'
57
85
) {
58
- const node = linkedText . nodes [ 0 ] ;
86
+ const node = linkedTextItems [ 0 ] ;
59
87
this . viewWelcomeNodes . push (
60
- this . renderButtonNode (
88
+ this . renderButtonNode_patch14309 (
61
89
node ,
62
90
this . viewWelcomeNodes . length ,
63
91
enablement
64
92
)
65
93
) ;
66
94
} 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
+ ) ;
90
105
}
91
106
}
92
107
}
93
108
}
94
109
95
- protected override renderButtonNode (
110
+ private renderButtonNode_patch14309 (
96
111
node : ILink ,
97
112
lineKey : string | number ,
98
- enablement : string | undefined = undefined
113
+ enablement : string | undefined
99
114
) : React . ReactNode {
100
115
return (
101
116
< div key = { `line-${ lineKey } ` } className = "theia-WelcomeViewButtonWrapper" >
102
117
< button
103
118
title = { node . title }
104
119
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 ) }
107
122
>
108
123
{ node . label }
109
124
</ button >
110
125
</ div >
111
126
) ;
112
127
}
113
128
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 (
115
149
node : ILink ,
116
150
linkKey : string | number ,
117
- enablement : string | undefined = undefined
151
+ enablement : string | undefined
118
152
) : React . ReactNode {
119
153
return (
120
154
< a
121
155
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 ) }
125
159
>
126
160
{ node . label }
127
161
</ a >
128
162
) ;
129
163
}
130
164
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 (
139
166
href : string ,
140
- enablement : string | undefined = undefined
167
+ enablement : string | undefined
141
168
) : string {
142
169
const classNames = [ 'theia-WelcomeViewCommandLink' ] ;
143
170
// 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
+ ) {
145
175
classNames . push ( 'disabled' ) ;
146
176
}
147
177
return classNames . join ( ' ' ) ;
148
178
}
149
179
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 {
166
181
return typeof enablement === 'string'
167
- ? this . contextKeyService . match ( enablement )
182
+ ? this . contextService . match ( enablement )
168
183
: true ;
169
184
}
170
- }
171
-
172
- interface EnablementAware {
173
- readonly enablement : string | undefined ;
174
- }
175
185
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
+ } ;
186
202
187
- export type LinkedTextNode = string | ILink ;
203
+ private parseLinkedText_patch14309 ( text : string ) : LinkedTextItem [ ] {
204
+ const result : LinkedTextItem [ ] = [ ] ;
188
205
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
+ / \[ ( [ ^ \] ] + ) \] \( ( (?: h t t p s ? : \/ \/ | c o m m a n d : | f i l e : ) [ ^ \) \s ] + ) (?: ( [ " ' ] ) ( .+ ?) ( \3) ) ? \) / gi;
208
+ let index = 0 ;
209
+ let match : RegExpExecArray | null ;
197
210
198
- const LINK_REGEX =
199
- / \[ ( [ ^ \] ] + ) \] \( ( (?: h t t p s ? : \/ \/ | c o m m a n d : | f i l e : ) [ ^ \) \s ] + ) (?: ( [ " ' ] ) ( .+ ?) ( \3) ) ? \) / gi;
211
+ while ( ( match = linkRegex . exec ( text ) ) ) {
212
+ if ( match . index - index > 0 ) {
213
+ result . push ( text . substring ( index , match . index ) ) ;
214
+ }
200
215
201
- export function parseLinkedText ( text : string ) : LinkedText {
202
- const result : LinkedTextNode [ ] = [ ] ;
216
+ const [ , label , href , , title ] = match ;
203
217
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
+ }
206
223
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 ;
210
225
}
211
226
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 ) ) ;
218
229
}
219
230
220
- index = match . index + match [ 0 ] . length ;
231
+ return result ;
221
232
}
233
+ }
222
234
223
- if ( index < text . length ) {
224
- result . push ( text . substring ( index ) ) ;
225
- }
235
+ interface EnablementAware {
236
+ readonly enablement : string | undefined ;
237
+ }
226
238
227
- return new LinkedText ( result ) ;
239
+ function isEnablementAware ( arg : unknown ) : arg is EnablementAware {
240
+ return ! ! arg && typeof arg === 'object' && 'enablement' in arg ;
228
241
}
0 commit comments