3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
+ import * as dom from 'vs/base/browser/dom' ;
6
7
import { IListRenderer , IListVirtualDelegate } from 'vs/base/browser/ui/list/list' ;
7
- import { Disposable } from 'vs/base/common/lifecycle' ;
8
+ import { Emitter } from 'vs/base/common/event' ;
9
+ import { Disposable , IDisposable } from 'vs/base/common/lifecycle' ;
10
+ import { Range } from 'vs/editor/common/core/range' ;
8
11
import { localize } from 'vs/nls' ;
12
+ import { MenuId } from 'vs/platform/actions/common/actions' ;
13
+ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView' ;
9
14
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
10
15
import { ILabelService } from 'vs/platform/label/common/label' ;
11
16
import { WorkbenchList } from 'vs/platform/list/browser/listService' ;
12
- import { ITestMessageStackTrace } from 'vs/workbench/contrib/testing/common/testTypes' ;
17
+ import { ITestMessageStackFrame } from 'vs/workbench/contrib/testing/common/testTypes' ;
18
+ import { IEditorService , SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService' ;
13
19
14
20
const stackItemDelegate : IListVirtualDelegate < void > = {
15
21
getHeight : ( ) => 22 ,
16
22
getTemplateId : ( ) => 's' ,
17
23
} ;
18
24
19
25
export class TestResultStackWidget extends Disposable {
20
- private readonly list : WorkbenchList < ITestMessageStackTrace > ;
26
+ private readonly list : WorkbenchList < ITestMessageStackFrame > ;
27
+ private readonly changeStackFrameEmitter = this . _register ( new Emitter < ITestMessageStackFrame > ( ) ) ;
28
+
29
+ public readonly onDidChangeStackFrame = this . changeStackFrameEmitter . event ;
21
30
22
31
constructor (
23
- container : HTMLElement ,
32
+ private readonly container : HTMLElement ,
24
33
@IInstantiationService instantiationService : IInstantiationService ,
25
34
@ILabelService labelService : ILabelService ,
35
+ @IContextMenuService contextMenuService : IContextMenuService
26
36
) {
27
37
super ( ) ;
28
38
@@ -33,43 +43,110 @@ export class TestResultStackWidget extends Disposable {
33
43
stackItemDelegate ,
34
44
[ instantiationService . createInstance ( StackRenderer ) ] ,
35
45
{
46
+ multipleSelectionSupport : false ,
36
47
accessibilityProvider : {
37
48
getWidgetAriaLabel : ( ) => localize ( 'testStackTrace' , 'Test stack trace' ) ,
38
- getAriaLabel : ( e : ITestMessageStackTrace ) => e . position && e . uri ? localize ( {
49
+ getAriaLabel : ( e : ITestMessageStackFrame ) => e . position && e . uri ? localize ( {
39
50
comment : [ '{0} is an extension-defined label, then line number and filename' ] ,
40
51
key : 'stackTraceLabel' ,
41
- } , '{0}, line {1} in {2}' , e . label , e . position . lineNumber , labelService . getUriLabel ( e . uri ) ) : e . label ,
52
+ } , '{0}, line {1} in {2}' , e . label , e . position . lineNumber , labelService . getUriLabel ( e . uri , { relative : true } ) ) : e . label ,
42
53
}
43
54
}
44
- ) as WorkbenchList < ITestMessageStackTrace > ) ;
55
+ ) as WorkbenchList < ITestMessageStackFrame > ) ;
56
+
57
+ this . _register ( this . list . onDidChangeSelection ( e => {
58
+ if ( e . elements . length ) {
59
+ this . changeStackFrameEmitter . fire ( e . elements [ 0 ] ) ;
60
+ }
61
+ } ) ) ;
62
+
63
+ this . _register ( dom . addDisposableListener ( container , dom . EventType . CONTEXT_MENU , e => {
64
+ contextMenuService . showContextMenu ( {
65
+ getAnchor : ( ) => ( { x : e . x , y : e . y } ) ,
66
+ menuId : MenuId . TestCallStackContext
67
+ } ) ;
68
+ } ) ) ;
45
69
}
46
70
47
- public update ( stack : ITestMessageStackTrace [ ] ) {
71
+ public update ( stack : ITestMessageStackFrame [ ] , selection ?: ITestMessageStackFrame ) {
48
72
this . list . splice ( 0 , this . list . length , stack ) ;
73
+ this . list . layout ( ) ;
74
+
75
+ const i = selection && stack . indexOf ( selection ) ;
76
+ if ( i && i !== - 1 ) {
77
+ this . list . setSelection ( [ i ] ) ;
78
+ this . list . setFocus ( [ i ] ) ;
79
+ // selection is triggered actioning on the call stack from a different
80
+ // editor, ensure the stack item is still focused in this editor
81
+ this . list . domFocus ( ) ;
82
+ }
49
83
}
50
84
51
85
public layout ( height ?: number , width ?: number ) {
52
- this . list . layout ( height , width ) ;
86
+ this . list . layout ( height ?? this . container . clientHeight , width ) ;
53
87
}
54
88
}
55
89
56
90
interface ITemplateData {
57
91
container : HTMLElement ;
92
+ label : HTMLElement ;
93
+ location : HTMLElement ;
94
+ current ?: ITestMessageStackFrame ;
95
+ disposable : IDisposable ;
58
96
}
59
97
60
- class StackRenderer implements IListRenderer < ITestMessageStackTrace , ITemplateData > {
98
+ class StackRenderer implements IListRenderer < ITestMessageStackFrame , ITemplateData > {
61
99
public readonly templateId = 's' ;
62
100
101
+ constructor (
102
+ @ILabelService private readonly labelService : ILabelService ,
103
+ @IEditorService private readonly openerService : IEditorService ,
104
+ ) { }
105
+
63
106
renderTemplate ( container : HTMLElement ) : ITemplateData {
64
- return { container } ;
107
+ const label = dom . $ ( '.label' ) ;
108
+ const location = dom . $ ( '.location' ) ;
109
+ container . appendChild ( label ) ;
110
+ container . appendChild ( location ) ;
111
+ const data : ITemplateData = {
112
+ container,
113
+ label,
114
+ location,
115
+ disposable : dom . addDisposableListener ( container , dom . EventType . CLICK , e => {
116
+ if ( e . ctrlKey || e . metaKey ) {
117
+ if ( data . current ?. uri ) {
118
+ this . openerService . openEditor ( {
119
+ resource : data . current . uri ,
120
+ options : {
121
+ selection : data . current . position ? Range . fromPositions ( data . current . position ) : undefined ,
122
+ }
123
+ } , SIDE_GROUP ) ;
124
+ e . preventDefault ( ) ;
125
+ e . stopPropagation ( ) ;
126
+ }
127
+ }
128
+ } ) ,
129
+ } ;
130
+
131
+ return data ;
65
132
}
66
133
67
- renderElement ( element : ITestMessageStackTrace , index : number , templateData : ITemplateData , height : number | undefined ) : void {
68
- templateData . container . innerText = element . label ;
134
+ renderElement ( element : ITestMessageStackFrame , index : number , templateData : ITemplateData , height : number | undefined ) : void {
135
+ templateData . label . innerText = element . label ;
136
+ templateData . current = element ;
137
+ templateData . container . classList . toggle ( 'no-source' , ! element . uri ) ;
138
+
139
+ if ( element . uri ) {
140
+ templateData . location . innerText = this . labelService . getUriBasenameLabel ( element . uri ) ;
141
+ templateData . location . title = this . labelService . getUriLabel ( element . uri , { relative : true } ) ;
142
+ if ( element . position ) {
143
+ templateData . location . innerText += `:${ element . position . lineNumber } :${ element . position . column } ` ;
144
+ }
145
+ }
69
146
}
70
147
71
- disposeTemplate ( _templateData : ITemplateData ) : void {
72
- // no-op
148
+ disposeTemplate ( templateData : ITemplateData ) : void {
149
+ templateData . disposable . dispose ( ) ;
73
150
}
74
151
}
75
152
0 commit comments