19
19
* Licensed under the MIT License. See License.txt in the project root for license information.
20
20
*--------------------------------------------------------------------------------------------*/
21
21
// 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
22
23
23
24
import React = require( 'react' ) ;
24
25
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' ;
26
29
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' ;
27
34
import { TreeModel } from './tree-model' ;
28
35
import { TreeWidget } from './tree-widget' ;
29
- import { WindowService } from '../window/window-service' ;
30
36
31
- interface ViewWelcome {
37
+ export interface ViewWelcome {
32
38
readonly view : string ;
33
39
readonly content : string ;
34
40
readonly when ?: string ;
41
+ readonly enablement ?: string ;
35
42
readonly order : number ;
36
43
}
37
44
38
- interface IItem {
45
+ export interface IItem {
39
46
readonly welcomeInfo : ViewWelcome ;
40
47
visible : boolean ;
41
48
}
42
49
43
- interface ILink {
50
+ export interface ILink {
44
51
readonly label : string ;
45
52
readonly href : string ;
46
53
readonly title ?: string ;
@@ -60,6 +67,14 @@ export class TreeViewWelcomeWidget extends TreeWidget {
60
67
@inject ( WindowService )
61
68
protected readonly windowService : WindowService ;
62
69
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
+
63
78
protected viewWelcomeNodes : React . ReactNode [ ] = [ ] ;
64
79
protected defaultItem : IItem | undefined ;
65
80
protected items : IItem [ ] = [ ] ;
@@ -130,13 +145,31 @@ export class TreeViewWelcomeWidget extends TreeWidget {
130
145
131
146
protected updateViewWelcomeNodes ( ) : void {
132
147
this . viewWelcomeNodes = [ ] ;
148
+ this . toDisposeBeforeUpdateViewWelcomeNodes . dispose ( ) ;
133
149
const items = this . visibleItems . sort ( ( a , b ) => a . order - b . order ) ;
134
150
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
+ }
136
170
const lines = content . split ( '\n' ) ;
137
171
138
- for ( let [ lIndex , line ] of lines . entries ( ) ) {
139
- const lineKey = `${ iIndex } -${ lIndex } ` ;
172
+ for ( let line of lines ) {
140
173
line = line . trim ( ) ;
141
174
142
175
if ( ! line ) {
@@ -146,95 +179,98 @@ export class TreeViewWelcomeWidget extends TreeWidget {
146
179
const linkedTextItems = this . parseLinkedText ( line ) ;
147
180
148
181
if ( linkedTextItems . length === 1 && typeof linkedTextItems [ 0 ] !== 'string' ) {
182
+ const node = linkedTextItems [ 0 ] ;
149
183
this . viewWelcomeNodes . push (
150
- this . renderButtonNode ( linkedTextItems [ 0 ] , lineKey )
184
+ this . renderButtonNode (
185
+ node ,
186
+ this . viewWelcomeNodes . length ,
187
+ enablement
188
+ )
151
189
) ;
152
190
} 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 ) ;
168
194
169
195
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 >
173
199
) ;
174
200
}
175
201
}
176
202
}
177
203
}
178
204
179
- protected renderButtonNode ( node : ILink , lineKey : string ) : React . ReactNode {
205
+ protected renderButtonNode ( node : ILink , lineKey : string | number , enablement : string | undefined ) : React . ReactNode {
180
206
return (
181
207
< div key = { `line-${ lineKey } ` } className = 'theia-WelcomeViewButtonWrapper' >
182
208
< button title = { node . title }
183
209
className = 'theia-button theia-WelcomeViewButton'
184
- disabled = { ! this . isEnabledClick ( node . href ) }
210
+ disabled = { ! this . isEnabledClick ( enablement ) }
185
211
onClick = { e => this . openLinkOrCommand ( e , node . href ) } >
186
212
{ node . label }
187
213
</ button >
188
214
</ div >
189
215
) ;
190
216
}
191
217
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 > ;
194
228
}
195
229
196
- protected renderCommandLinkNode ( node : ILink , linkKey : string ) : React . ReactNode {
230
+ protected renderLinkNode ( node : ILink , linkKey : string | number , enablement : string | undefined ) : React . ReactNode {
197
231
return (
198
232
< a key = { `link-${ linkKey } ` }
199
- className = { this . getLinkClassName ( node . href ) }
233
+ className = { this . getLinkClassName ( node . href , enablement ) }
200
234
title = { node . title || '' }
201
235
onClick = { e => this . openLinkOrCommand ( e , node . href ) } >
202
236
{ node . label }
203
237
</ a >
204
238
) ;
205
239
}
206
240
207
- protected getLinkClassName ( href : string ) : string {
241
+ protected getLinkClassName ( href : string , enablement : string | undefined ) : string {
208
242
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 ) ) {
210
245
classNames . push ( 'disabled' ) ;
211
246
}
212
247
return classNames . join ( ' ' ) ;
213
248
}
214
249
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 ;
221
254
}
222
255
223
- protected openLinkOrCommand = ( event : React . MouseEvent , href : string ) : void => {
256
+ protected openLinkOrCommand = ( event : React . MouseEvent , value : string ) : void => {
224
257
event . stopPropagation ( ) ;
225
258
226
- if ( href . startsWith ( 'command:' ) ) {
227
- const command = href . replace ( 'command:' , '' ) ;
259
+ if ( value . startsWith ( 'command:' ) ) {
260
+ const command = value . replace ( 'command:' , '' ) ;
228
261
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 ( ) ) ) ;
229
265
} else {
230
- this . windowService . openNewWindow ( href , { external : true } ) ;
266
+ this . windowService . openNewWindow ( value , { external : true } ) ;
231
267
}
232
268
} ;
233
269
234
270
protected parseLinkedText ( text : string ) : LinkedTextItem [ ] {
235
271
const result : LinkedTextItem [ ] = [ ] ;
236
272
237
- const linkRegex = / \[ ( [ ^ \] ] + ) \] \( ( (?: h t t p s ? : \/ \/ | c o m m a n d : ) [ ^ \) \s ] + ) (?: ( " | ' ) ( [ ^ \3 ] + ) ( \3) ) ? \) / gi;
273
+ const linkRegex = / \[ ( [ ^ \] ] + ) \] \( ( (?: h t t p s ? : \/ \/ | c o m m a n d : | f i l e : ) [ ^ \) \s ] + ) (?: ( [ " ' ] ) ( . + ? ) ( \3) ) ? \) / gi;
238
274
let index = 0 ;
239
275
let match : RegExpExecArray | null ;
240
276
0 commit comments