@@ -12,20 +12,24 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
12
12
import { Codicon } from 'vs/base/common/codicons' ;
13
13
import { Emitter , Event } from 'vs/base/common/event' ;
14
14
import { Disposable , DisposableStore , IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
15
- import { autorun , autorunWithStore , derived , IObservable , ISettableObservable , observableValue } from 'vs/base/common/observable' ;
15
+ import { autorun , autorunWithStore , derived , IObservable , ISettableObservable , observableValue , transaction } from 'vs/base/common/observable' ;
16
16
import { ThemeIcon } from 'vs/base/common/themables' ;
17
17
import { Constants } from 'vs/base/common/uint' ;
18
18
import { URI } from 'vs/base/common/uri' ;
19
19
import { generateUuid } from 'vs/base/common/uuid' ;
20
20
import 'vs/css!./media/callStackWidget' ;
21
21
import { ICodeEditor } from 'vs/editor/browser/editorBrowser' ;
22
- import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService ' ;
22
+ import { EditorContributionCtor , EditorContributionInstantiation , IEditorContributionDescription } from 'vs/editor/browser/editorExtensions ' ;
23
23
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget' ;
24
24
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget' ;
25
25
import { IEditorOptions } from 'vs/editor/common/config/editorOptions' ;
26
+ import { Position } from 'vs/editor/common/core/position' ;
26
27
import { Range } from 'vs/editor/common/core/range' ;
28
+ import { IWordAtPosition } from 'vs/editor/common/core/wordHelper' ;
29
+ import { IEditorContribution , IEditorDecorationsCollection } from 'vs/editor/common/editorCommon' ;
27
30
import { Location } from 'vs/editor/common/languages' ;
28
31
import { ITextModelService } from 'vs/editor/common/services/resolverService' ;
32
+ import { ClickLinkGesture , ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture' ;
29
33
import { localize , localize2 } from 'vs/nls' ;
30
34
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem' ;
31
35
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar' ;
@@ -38,7 +42,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
38
42
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles' ;
39
43
import { ResourceLabel } from 'vs/workbench/browser/labels' ;
40
44
import { makeStackFrameColumnDecoration , TOP_STACK_FRAME_DECORATION } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution' ;
41
- import { IEditorService } from 'vs/workbench/services/editor/common/editorService' ;
45
+ import { IEditorService , SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService' ;
42
46
43
47
44
48
export class CallStackFrame {
@@ -97,6 +101,9 @@ class WrappedCustomStackFrame implements IFrameLikeItem {
97
101
constructor ( public readonly original : CustomStackFrame ) { }
98
102
}
99
103
104
+ const isFrameLike = ( item : unknown ) : item is IFrameLikeItem =>
105
+ item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame ;
106
+
100
107
type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame ;
101
108
102
109
const WIDGET_CLASS_NAME = 'multiCallStackWidget' ;
@@ -157,6 +164,17 @@ export class CallStackWidget extends Disposable {
157
164
this . layoutEmitter . fire ( ) ;
158
165
}
159
166
167
+ public collapseAll ( ) {
168
+ transaction ( tx => {
169
+ for ( let i = 0 ; i < this . list . length ; i ++ ) {
170
+ const frame = this . list . element ( i ) ;
171
+ if ( isFrameLike ( frame ) ) {
172
+ frame . collapsed . set ( true , tx ) ;
173
+ }
174
+ }
175
+ } ) ;
176
+ }
177
+
160
178
private async loadFrame ( replacing : SkippedCallFrames ) : Promise < void > {
161
179
if ( ! this . cts ) {
162
180
return ;
@@ -356,9 +374,9 @@ abstract class AbstractFrameRenderer<T extends IAbstractFrameRendererTemplateDat
356
374
collapse . element . ariaExpanded = String ( ! collapsed ) ;
357
375
elements . root . classList . toggle ( 'collapsed' , collapsed ) ;
358
376
} ) ) ;
359
- elementStore . add ( collapse . onDidClick ( ( ) => {
360
- item . collapsed . set ( ! item . collapsed . get ( ) , undefined ) ;
361
- } ) ) ;
377
+ const toggleCollapse = ( ) => item . collapsed . set ( ! item . collapsed . get ( ) , undefined ) ;
378
+ elementStore . add ( collapse . onDidClick ( toggleCollapse ) ) ;
379
+ elementStore . add ( dom . addDisposableListener ( elements . title , 'click' , toggleCollapse ) ) ;
362
380
}
363
381
364
382
disposeElement ( element : ListItem , index : number , templateData : T , height : number | undefined ) : void {
@@ -382,26 +400,33 @@ class FrameCodeRenderer extends AbstractFrameRenderer<IStackTemplateData> {
382
400
private readonly containingEditor : ICodeEditor | undefined ,
383
401
private readonly onLayout : Event < void > ,
384
402
@ITextModelService private readonly modelService : ITextModelService ,
385
- @ICodeEditorService private readonly editorService : ICodeEditorService ,
386
403
@IInstantiationService instantiationService : IInstantiationService ,
387
404
) {
388
405
super ( instantiationService ) ;
389
406
}
390
407
391
408
protected override finishRenderTemplate ( data : IAbstractFrameRendererTemplateData ) : IStackTemplateData {
409
+ // override default e.g. language contributions, only allow users to click
410
+ // on code in the call stack to go to its source location
411
+ const contributions : IEditorContributionDescription [ ] = [ {
412
+ id : ClickToLocationContribution . ID ,
413
+ instantiation : EditorContributionInstantiation . BeforeFirstInteraction ,
414
+ ctor : ClickToLocationContribution as EditorContributionCtor ,
415
+ } ] ;
416
+
392
417
const editor = this . containingEditor
393
418
? this . instantiationService . createInstance (
394
419
EmbeddedCodeEditorWidget ,
395
420
data . elements . editor ,
396
421
editorOptions ,
397
- { isSimpleWidget : true } ,
422
+ { isSimpleWidget : true , contributions } ,
398
423
this . containingEditor ,
399
424
)
400
425
: this . instantiationService . createInstance (
401
426
CodeEditorWidget ,
402
427
data . elements . editor ,
403
428
editorOptions ,
404
- { isSimpleWidget : true } ,
429
+ { isSimpleWidget : true , contributions } ,
405
430
) ;
406
431
407
432
data . templateStore . add ( editor ) ;
@@ -423,20 +448,6 @@ class FrameCodeRenderer extends AbstractFrameRenderer<IStackTemplateData> {
423
448
const uri = item . source ! ;
424
449
425
450
template . label . element . setFile ( uri ) ;
426
- template . elements . title . role = 'link' ;
427
- elementStore . add ( dom . addDisposableListener ( template . elements . title , 'click' , e => {
428
- this . editorService . openCodeEditor ( {
429
- resource : uri ,
430
- options : {
431
- selection : Range . fromPositions ( {
432
- column : item . column ?? 1 ,
433
- lineNumber : item . line ?? 1 ,
434
- } ) ,
435
- selectionRevealType : TextEditorSelectionRevealType . CenterIfOutsideViewport ,
436
- } ,
437
- } , this . containingEditor || null , e . ctrlKey || e . metaKey ) ;
438
- } ) ) ;
439
-
440
451
const cts = new CancellationTokenSource ( ) ;
441
452
elementStore . add ( toDisposable ( ( ) => cts . dispose ( true ) ) ) ;
442
453
this . modelService . createModelReference ( uri ) . then ( reference => {
@@ -632,6 +643,73 @@ class SkippedRenderer implements IListRenderer<ListItem, ISkippedTemplateData> {
632
643
}
633
644
}
634
645
646
+ /** A simple contribution that makes all data in the editor clickable to go to the location */
647
+ class ClickToLocationContribution extends Disposable implements IEditorContribution {
648
+ public static readonly ID = 'clickToLocation' ;
649
+ private readonly linkDecorations : IEditorDecorationsCollection ;
650
+ private current : { line : number ; word : IWordAtPosition } | undefined ;
651
+
652
+ constructor (
653
+ private readonly editor : ICodeEditor ,
654
+ @IEditorService editorService : IEditorService ,
655
+ ) {
656
+ super ( ) ;
657
+ this . linkDecorations = editor . createDecorationsCollection ( ) ;
658
+ this . _register ( toDisposable ( ( ) => this . linkDecorations . clear ( ) ) ) ;
659
+
660
+ const clickLinkGesture = this . _register ( new ClickLinkGesture ( editor ) ) ;
661
+
662
+ this . _register ( clickLinkGesture . onMouseMoveOrRelevantKeyDown ( ( [ mouseEvent , keyboardEvent ] ) => {
663
+ this . onMove ( mouseEvent ) ;
664
+ } ) ) ;
665
+ this . _register ( clickLinkGesture . onExecute ( ( e ) => {
666
+ const model = this . editor . getModel ( ) ;
667
+ if ( ! this . current || ! model ) {
668
+ return ;
669
+ }
670
+
671
+ editorService . openEditor ( {
672
+ resource : model . uri ,
673
+ options : {
674
+ selection : Range . fromPositions ( new Position ( this . current . line , this . current . word . startColumn ) ) ,
675
+ selectionRevealType : TextEditorSelectionRevealType . CenterIfOutsideViewport ,
676
+ } ,
677
+ } , e . hasSideBySideModifier ? SIDE_GROUP : undefined ) ;
678
+ } ) ) ;
679
+ }
680
+
681
+ private onMove ( mouseEvent : ClickLinkMouseEvent ) {
682
+ if ( ! mouseEvent . hasTriggerModifier ) {
683
+ return this . clear ( ) ;
684
+ }
685
+
686
+ const position = mouseEvent . target . position ;
687
+ const word = position && this . editor . getModel ( ) ?. getWordAtPosition ( position ) ;
688
+ if ( ! word ) {
689
+ return this . clear ( ) ;
690
+ }
691
+
692
+ const prev = this . current ?. word ;
693
+ if ( prev && prev . startColumn === word . startColumn && prev . endColumn === word . endColumn && prev . word === word . word ) {
694
+ return ;
695
+ }
696
+
697
+ this . current = { word, line : position . lineNumber } ;
698
+ this . linkDecorations . set ( [ {
699
+ range : new Range ( position . lineNumber , word . startColumn , position . lineNumber , word . endColumn ) ,
700
+ options : {
701
+ description : 'call-stack-go-to-file-link' ,
702
+ inlineClassName : 'call-stack-go-to-file-link' ,
703
+ } ,
704
+ } ] ) ;
705
+ }
706
+
707
+ private clear ( ) {
708
+ this . linkDecorations . clear ( ) ;
709
+ this . current = undefined ;
710
+ }
711
+ }
712
+
635
713
registerAction2 ( class extends Action2 {
636
714
constructor ( ) {
637
715
super ( {
0 commit comments