@@ -33,6 +33,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
33
33
import 'vs/base/browser/ui/codicons/codiconStyles' ; // The codicon symbol styles are defined here and must be loaded
34
34
import 'vs/editor/contrib/symbolIcons/browser/symbolIcons' ; // The codicon symbol colors are defined here and must be loaded to get colors
35
35
import { Codicon } from 'vs/base/common/codicons' ;
36
+ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar' ;
36
37
37
38
export const Context = {
38
39
Visible : new RawContextKey < boolean > ( 'CodeActionMenuVisible' , false , localize ( 'CodeActionMenuVisible' , "Whether the code action list widget is visible" ) )
@@ -74,6 +75,19 @@ export interface ICodeActionMenuItem {
74
75
headerTitle : string ;
75
76
index : number ;
76
77
disposables ?: IDisposable [ ] ;
78
+ params : ICodeActionMenuParameters ;
79
+ }
80
+
81
+ export interface ICodeActionMenuParameters {
82
+ options : CodeActionShowOptions ;
83
+ trigger : CodeActionTrigger ;
84
+ anchor : { x : number ; y : number } ;
85
+ menuActions : IAction [ ] ;
86
+ codeActions : CodeActionSet ;
87
+ visible : boolean ;
88
+ showDisabled : boolean ;
89
+ menuObj : CodeActionMenu ;
90
+
77
91
}
78
92
79
93
export interface ICodeMenuOptions {
@@ -97,8 +111,10 @@ const TEMPLATE_ID = 'codeActionWidget';
97
111
const codeActionLineHeight = 24 ;
98
112
const headerLineHeight = 26 ;
99
113
100
- class CodeMenuRenderer implements IListRenderer < ICodeActionMenuItem , ICodeActionMenuTemplateData > {
114
+ // TODO: Take a look at user storage for this so it is preserved across windows and on reload.
115
+ let showDisabled = false ;
101
116
117
+ class CodeMenuRenderer implements IListRenderer < ICodeActionMenuItem , ICodeActionMenuTemplateData > {
102
118
constructor (
103
119
private readonly acceptKeybindings : [ string , string ] ,
104
120
@IKeybindingService private readonly keybindingService : IKeybindingService ,
@@ -129,6 +145,7 @@ class CodeMenuRenderer implements IListRenderer<ICodeActionMenuItem, ICodeAction
129
145
const isSeparator = element . isSeparator ;
130
146
const isHeader = element . isHeader ;
131
147
148
+ // Renders differently based on element type.
132
149
if ( isSeparator ) {
133
150
data . root . classList . add ( 'separator' ) ;
134
151
data . root . style . height = '10px' ;
@@ -139,21 +156,50 @@ class CodeMenuRenderer implements IListRenderer<ICodeActionMenuItem, ICodeAction
139
156
data . root . classList . add ( 'group-header' ) ;
140
157
} else {
141
158
const text = element . action . label ;
142
- data . text . textContent = text ;
143
159
element . isEnabled = element . action . enabled ;
144
160
145
161
if ( element . action instanceof CodeActionAction ) {
162
+ const openedFromString = ( element . params ?. options . fromLightbulb ) ? CodeActionTriggerSource . Lightbulb : element . params ?. trigger . triggerAction ;
163
+
146
164
147
165
// Check documentation type
148
166
element . isDocumentation = element . action . action . kind === CodeActionMenu . documentationID ;
149
167
150
168
if ( element . isDocumentation ) {
151
- data . text . textContent = text ;
169
+ element . isEnabled = false ;
152
170
data . root . classList . add ( 'documentation' ) ;
171
+
172
+ const container = data . root ;
173
+
174
+ const actionbarContainer = dom . append ( container , dom . $ ( '.codeActionWidget-action-bar' ) ) ;
175
+
176
+ const reRenderAction = showDisabled ?
177
+ < IAction > {
178
+ id : 'hideMoreCodeActions' ,
179
+ label : localize ( 'hideMoreCodeActions' , 'Hide Disabled' ) ,
180
+ enabled : true ,
181
+ run : ( ) => CodeActionMenu . toggleDisabledOptions ( element . params )
182
+ } :
183
+ < IAction > {
184
+ id : 'showMoreCodeActions' ,
185
+ label : localize ( 'showMoreCodeActions' , 'Show Disabled' ) ,
186
+ enabled : true ,
187
+ run : ( ) => CodeActionMenu . toggleDisabledOptions ( element . params )
188
+ } ;
189
+
190
+ const actionbar = new ActionBar ( actionbarContainer ) ;
191
+ data . disposables . push ( actionbar ) ;
192
+
193
+ if ( openedFromString === CodeActionTriggerSource . Refactor && ( element . params . codeActions . validActions . length > 0 || element . params . codeActions . allActions . length === element . params . codeActions . validActions . length ) ) {
194
+ actionbar . push ( [ element . action , reRenderAction ] , { icon : false , label : true } ) ;
195
+ } else {
196
+ actionbar . push ( [ element . action ] , { icon : false , label : true } ) ;
197
+ }
153
198
} else {
199
+ data . text . textContent = text ;
200
+
154
201
// Icons and Label modifaction based on group
155
202
const group = element . action . action . kind ;
156
-
157
203
if ( CodeActionKind . SurroundWith . contains ( new CodeActionKind ( String ( group ) ) ) ) {
158
204
data . icon . className = Codicon . symbolArray . classNames ;
159
205
} else if ( CodeActionKind . Extract . contains ( new CodeActionKind ( String ( group ) ) ) ) {
@@ -249,6 +295,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
249
295
return this . _visible ;
250
296
}
251
297
298
+ /**
299
+ * Checks if the settings have enabled the new code action widget.
300
+ */
252
301
private isCodeActionWidgetEnabled ( model : ITextModel ) : boolean {
253
302
return this . _configurationService . getValue ( 'editor.experimental.useCustomCodeActionMenu' , {
254
303
resource : model . uri
@@ -291,7 +340,10 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
291
340
}
292
341
}
293
342
294
- private renderCodeActionMenuList ( element : HTMLElement , inputArray : IAction [ ] ) : IDisposable {
343
+ /**
344
+ * Renders the code action widget given the provided actions.
345
+ */
346
+ private renderCodeActionMenuList ( element : HTMLElement , inputArray : IAction [ ] , params : ICodeActionMenuParameters ) : IDisposable {
295
347
const renderDisposables = new DisposableStore ( ) ;
296
348
const renderMenu = document . createElement ( 'div' ) ;
297
349
@@ -332,10 +384,10 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
332
384
accessibilityProvider : {
333
385
getAriaLabel : element => {
334
386
if ( element . action instanceof CodeActionAction ) {
335
- const label = element . action . label ;
387
+ let label = element . action . label ;
336
388
if ( ! element . action . enabled ) {
337
389
if ( element . action instanceof CodeActionAction ) {
338
- localize ( { key : 'customCodeActionWidget.labels' , comment : [ 'Code action labels for accessibility.' ] } , "{0}, Disabled Reason: {1}" , label , element . action . action . disabled ) ;
390
+ label = localize ( { key : 'customCodeActionWidget.labels' , comment : [ 'Code action labels for accessibility.' ] } , "{0}, Disabled Reason: {1}" , label , element . action . action . disabled ) ;
339
391
}
340
392
}
341
393
return label ;
@@ -366,9 +418,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
366
418
367
419
renderDisposables . add ( this . codeActionList . value . onMouseClick ( e => this . _onListClick ( e ) ) ) ;
368
420
renderDisposables . add ( this . codeActionList . value . onMouseOver ( e => this . _onListHover ( e ) ) ) ;
369
- renderDisposables . add ( this . codeActionList . value . onDidChangeFocus ( e => this . codeActionList . value ?. domFocus ( ) ) ) ;
421
+ renderDisposables . add ( this . codeActionList . value . onDidChangeFocus ( ( ) => this . codeActionList . value ?. domFocus ( ) ) ) ;
370
422
renderDisposables . add ( this . codeActionList . value . onDidChangeSelection ( e => this . _onListSelection ( e ) ) ) ;
371
- renderDisposables . add ( this . _editor . onDidLayoutChange ( e => this . hideCodeActionWidget ( ) ) ) ;
423
+ renderDisposables . add ( this . _editor . onDidLayoutChange ( ( ) => this . hideCodeActionWidget ( ) ) ) ;
372
424
373
425
// Filters and groups code actions by their group
374
426
const menuEntries : IAction [ ] [ ] = [ ] ;
@@ -383,7 +435,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
383
435
const documentationGroup : IAction [ ] = [ ] ;
384
436
const otherGroup : IAction [ ] = [ ] ;
385
437
386
- inputArray . forEach ( ( item , index ) => {
438
+ inputArray . forEach ( ( item ) => {
387
439
if ( item instanceof CodeActionAction ) {
388
440
const optionKind = item . action . kind ;
389
441
@@ -440,7 +492,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
440
492
menuEntriesToPush ( localize ( 'codeAction.widget.id.more' , 'More Actions...' ) , entry ) ;
441
493
}
442
494
} else {
443
- // case for separator - not a code action action
495
+ // case for separator - separators are not codeActionAction typed
444
496
totalActionEntries . push ( ...entry ) ;
445
497
}
446
498
@@ -459,7 +511,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
459
511
this . hasSeparator = true ;
460
512
}
461
513
462
- const menuItem = < ICodeActionMenuItem > { action : item , isEnabled : item . enabled , isSeparator : currIsSeparator , index } ;
514
+ const menuItem = < ICodeActionMenuItem > { action : item , isEnabled : item . enabled , isSeparator : currIsSeparator , index, params } ;
463
515
if ( item . enabled ) {
464
516
this . viewItems . push ( menuItem ) ;
465
517
}
@@ -486,7 +538,12 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
486
538
} ) ;
487
539
488
540
// resize observer - can be used in the future since list widget supports dynamic height but not width
489
- const maxWidth = Math . max ( ...arr ) ;
541
+ let maxWidth = Math . max ( ...arr ) ;
542
+
543
+ // If there are no actions, the minimum width is the width of the list widget's action bar.
544
+ if ( params . trigger . triggerAction === CodeActionTriggerSource . Refactor && maxWidth < 230 ) {
545
+ maxWidth = 230 ;
546
+ }
490
547
491
548
// 52 is the additional padding for the list widget (26 left, 26 right)
492
549
renderMenu . style . width = maxWidth + 52 + 5 + 'px' ;
@@ -514,6 +571,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
514
571
return renderDisposables ;
515
572
}
516
573
574
+ /**
575
+ * Focuses on the previous item in the list using the list widget.
576
+ */
517
577
protected focusPrevious ( ) {
518
578
if ( typeof this . focusedEnabledItem === 'undefined' ) {
519
579
this . focusedEnabledItem = this . viewItems [ 0 ] . index ;
@@ -537,6 +597,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
537
597
return true ;
538
598
}
539
599
600
+ /**
601
+ * Focuses on the next item in the list using the list widget.
602
+ */
540
603
protected focusNext ( ) {
541
604
if ( typeof this . focusedEnabledItem === 'undefined' ) {
542
605
this . focusedEnabledItem = this . viewItems . length - 1 ;
@@ -582,7 +645,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
582
645
this . focusedEnabledItem = 0 ;
583
646
this . currSelectedItem = undefined ;
584
647
this . hasSeparator = false ;
585
- this . _contextViewService . hideContextView ( { source : this } ) ;
648
+ this . _contextViewService . hideContextView ( ) ;
586
649
}
587
650
588
651
codeActionTelemetry ( openedFromString : CodeActionTriggerSource , didCancel : boolean , CodeActions : CodeActionSet ) {
@@ -608,12 +671,52 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
608
671
} ) ;
609
672
}
610
673
674
+ /**
675
+ * Helper function to create a context view item using code action `params`.
676
+ */
677
+ private showContextViewHelper ( params : ICodeActionMenuParameters , menuActions : IAction [ ] ) {
678
+ this . _contextViewService . showContextView ( {
679
+ getAnchor : ( ) => params . anchor ,
680
+ render : ( container : HTMLElement ) => this . renderCodeActionMenuList ( container , menuActions , params ) ,
681
+ onHide : ( didCancel : boolean ) => {
682
+ const openedFromString = ( params . options . fromLightbulb ) ? CodeActionTriggerSource . Lightbulb : params . trigger . triggerAction ;
683
+ this . codeActionTelemetry ( openedFromString , didCancel , params . codeActions ) ;
684
+ this . _visible = false ;
685
+ this . _editor . focus ( ) ;
686
+ } ,
687
+ } ,
688
+ this . _editor . getDomNode ( ) ! , false ,
689
+ ) ;
690
+
691
+ }
692
+
693
+ /**
694
+ * Toggles whether the disabled actions in the code action widget are visible or not.
695
+ */
696
+ public static toggleDisabledOptions ( params : ICodeActionMenuParameters ) : void {
697
+ params . menuObj . hideCodeActionWidget ( ) ;
698
+
699
+ showDisabled = ! showDisabled ;
700
+
701
+ const actionsToShow = showDisabled ? params . codeActions . allActions : params . codeActions . validActions ;
702
+
703
+ const menuActions = params . menuObj . getMenuActions ( params . trigger , actionsToShow , params . codeActions . documentation ) ;
704
+
705
+ params . menuObj . showContextViewHelper ( params , menuActions ) ;
706
+ }
707
+
611
708
public async show ( trigger : CodeActionTrigger , codeActions : CodeActionSet , at : IAnchor | IPosition , options : CodeActionShowOptions ) : Promise < void > {
612
709
const model = this . _editor . getModel ( ) ;
613
710
if ( ! model ) {
614
711
return ;
615
712
}
616
- const actionsToShow = options . includeDisabledActions ? codeActions . allActions : codeActions . validActions ;
713
+
714
+ let actionsToShow = options . includeDisabledActions ? codeActions . allActions : codeActions . validActions ;
715
+
716
+ // If there are no refactorings, we should still show the menu and only displayed disabled actions without `enable` button.
717
+ if ( trigger . triggerAction === CodeActionTriggerSource . Refactor && codeActions . validActions . length > 0 ) {
718
+ actionsToShow = showDisabled ? codeActions . allActions : codeActions . validActions ;
719
+ }
617
720
618
721
if ( ! actionsToShow . length ) {
619
722
this . _visible = false ;
@@ -632,24 +735,15 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
632
735
const menuActions = this . getMenuActions ( trigger , actionsToShow , codeActions . documentation ) ;
633
736
634
737
const anchor = Position . isIPosition ( at ) ? this . _toCoords ( at ) : at || { x : 0 , y : 0 } ;
738
+
739
+ const params = < ICodeActionMenuParameters > { options, trigger, codeActions, anchor, menuActions, showDisabled : true , visible : this . _visible , menuObj : this } ;
635
740
const resolver = this . _keybindingResolver . getResolver ( ) ;
636
741
637
742
const useShadowDOM = this . _editor . getOption ( EditorOption . useShadowDOM ) ;
638
743
639
744
640
745
if ( this . isCodeActionWidgetEnabled ( model ) ) {
641
- this . _contextViewService . showContextView ( {
642
- getAnchor : ( ) => anchor ,
643
- render : ( container : HTMLElement ) => this . renderCodeActionMenuList ( container , menuActions ) ,
644
- onHide : ( didCancel ) => {
645
- const openedFromString = ( options . fromLightbulb ) ? CodeActionTriggerSource . Lightbulb : trigger . triggerAction ;
646
- this . codeActionTelemetry ( openedFromString , didCancel , codeActions ) ;
647
- this . _visible = false ;
648
- this . _editor . focus ( ) ;
649
- } ,
650
- } ,
651
- this . _editor . getDomNode ( ) ! , false ,
652
- ) ;
746
+ this . showContextViewHelper ( params , menuActions ) ;
653
747
} else {
654
748
this . _contextMenuService . showContextMenu ( {
655
749
domForShadowRoot : useShadowDOM ? this . _editor . getDomNode ( ) ! : undefined ,
0 commit comments