@@ -66,7 +66,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
66
66
private autoExpandDisposable : IDisposable = Disposable . None ;
67
67
private readonly disposables = new DisposableStore ( ) ;
68
68
69
- constructor ( private model : ITreeModel < T , TFilterData , TRef > , private dnd : ITreeDragAndDrop < T > ) { }
69
+ constructor ( private modelProvider : ( ) => ITreeModel < T , TFilterData , TRef > , private dnd : ITreeDragAndDrop < T > ) { }
70
70
71
71
getDragURI ( node : ITreeNode < T , TFilterData > ) : string | null {
72
72
return this . dnd . getDragURI ( node . element ) ;
@@ -99,10 +99,11 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
99
99
100
100
if ( didChangeAutoExpandNode && typeof result !== 'boolean' && result . autoExpand ) {
101
101
this . autoExpandDisposable = disposableTimeout ( ( ) => {
102
- const ref = this . model . getNodeLocation ( targetNode ) ;
102
+ const model = this . modelProvider ( ) ;
103
+ const ref = model . getNodeLocation ( targetNode ) ;
103
104
104
- if ( this . model . isCollapsed ( ref ) ) {
105
- this . model . setCollapsed ( ref , false ) ;
105
+ if ( model . isCollapsed ( ref ) ) {
106
+ model . setCollapsed ( ref , false ) ;
106
107
}
107
108
108
109
this . autoExpandNode = undefined ;
@@ -120,17 +121,19 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
120
121
}
121
122
122
123
if ( result . bubble === TreeDragOverBubble . Up ) {
123
- const ref = this . model . getNodeLocation ( targetNode ) ;
124
- const parentRef = this . model . getParentNodeLocation ( ref ) ;
125
- const parentNode = this . model . getNode ( parentRef ) ;
126
- const parentIndex = parentRef && this . model . getListIndex ( parentRef ) ;
124
+ const model = this . modelProvider ( ) ;
125
+ const ref = model . getNodeLocation ( targetNode ) ;
126
+ const parentRef = model . getParentNodeLocation ( ref ) ;
127
+ const parentNode = model . getNode ( parentRef ) ;
128
+ const parentIndex = parentRef && model . getListIndex ( parentRef ) ;
127
129
128
130
return this . onDragOver ( data , parentNode , parentIndex , targetSector , originalEvent , false ) ;
129
131
}
130
132
131
- const ref = this . model . getNodeLocation ( targetNode ) ;
132
- const start = this . model . getListIndex ( ref ) ;
133
- const length = this . model . getListRenderCount ( ref ) ;
133
+ const model = this . modelProvider ( ) ;
134
+ const ref = model . getNodeLocation ( targetNode ) ;
135
+ const start = model . getListIndex ( ref ) ;
136
+ const length = model . getListRenderCount ( ref ) ;
134
137
135
138
return { ...result , feedback : range ( start , start + length ) } ;
136
139
}
@@ -152,15 +155,15 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
152
155
}
153
156
}
154
157
155
- function asListOptions < T , TFilterData , TRef > ( model : ITreeModel < T , TFilterData , TRef > , options ?: IAbstractTreeOptions < T , TFilterData > ) : IListOptions < ITreeNode < T , TFilterData > > | undefined {
158
+ function asListOptions < T , TFilterData , TRef > ( modelProvider : ( ) => ITreeModel < T , TFilterData , TRef > , options ?: IAbstractTreeOptions < T , TFilterData > ) : IListOptions < ITreeNode < T , TFilterData > > | undefined {
156
159
return options && {
157
160
...options ,
158
161
identityProvider : options . identityProvider && {
159
162
getId ( el ) {
160
163
return options . identityProvider ! . getId ( el . element ) ;
161
164
}
162
165
} ,
163
- dnd : options . dnd && new TreeNodeListDragAndDrop ( model , options . dnd ) ,
166
+ dnd : options . dnd && new TreeNodeListDragAndDrop ( modelProvider , options . dnd ) ,
164
167
multipleSelectionController : options . multipleSelectionController && {
165
168
isSelectionSingleChangeEvent ( e ) {
166
169
return options . multipleSelectionController ! . isSelectionSingleChangeEvent ( { ...e , element : e . element } as any ) ;
@@ -172,6 +175,7 @@ function asListOptions<T, TFilterData, TRef>(model: ITreeModel<T, TFilterData, T
172
175
accessibilityProvider : options . accessibilityProvider && {
173
176
...options . accessibilityProvider ,
174
177
getSetSize ( node ) {
178
+ const model = modelProvider ( ) ;
175
179
const ref = model . getNodeLocation ( node ) ;
176
180
const parentRef = model . getParentNodeLocation ( ref ) ;
177
181
const parentNode = model . getNode ( parentRef ) ;
@@ -560,6 +564,10 @@ export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListR
560
564
this . activeIndentNodes = set ;
561
565
}
562
566
567
+ setModel ( model : ITreeModel < T , TFilterData , TRef > ) : void {
568
+ this . model = model ;
569
+ }
570
+
563
571
dispose ( ) : void {
564
572
this . renderedNodes . clear ( ) ;
565
573
this . renderedElements . clear ( ) ;
@@ -1039,15 +1047,13 @@ class FindController<T, TFilterData> implements IDisposable {
1039
1047
1040
1048
constructor (
1041
1049
private tree : AbstractTree < T , TFilterData , any > ,
1042
- model : ITreeModel < T , TFilterData , any > ,
1043
1050
private view : List < ITreeNode < T , TFilterData > > ,
1044
1051
private filter : FindFilter < T > ,
1045
1052
private readonly contextViewProvider : IContextViewProvider ,
1046
1053
private readonly options : IFindControllerOptions = { }
1047
1054
) {
1048
1055
this . _mode = tree . options . defaultFindMode ?? TreeFindMode . Highlight ;
1049
1056
this . _matchType = tree . options . defaultFindMatchType ?? TreeFindMatchType . Fuzzy ;
1050
- model . onDidSpliceModel ( this . onDidSpliceModel , this , this . disposables ) ;
1051
1057
}
1052
1058
1053
1059
updateOptions ( optionsUpdate : IAbstractTreeOptionsUpdate = { } ) : void {
@@ -1060,6 +1066,10 @@ class FindController<T, TFilterData> implements IDisposable {
1060
1066
}
1061
1067
}
1062
1068
1069
+ isOpened ( ) : boolean {
1070
+ return ! ! this . widget ;
1071
+ }
1072
+
1063
1073
open ( ) : void {
1064
1074
if ( this . widget ) {
1065
1075
this . widget . focus ( ) ;
@@ -1125,16 +1135,7 @@ class FindController<T, TFilterData> implements IDisposable {
1125
1135
this . render ( ) ;
1126
1136
}
1127
1137
1128
- private onDidSpliceModel ( ) : void {
1129
- if ( ! this . widget || this . pattern . length === 0 ) {
1130
- return ;
1131
- }
1132
-
1133
- this . tree . refilter ( ) ;
1134
- this . render ( ) ;
1135
- }
1136
-
1137
- private render ( ) : void {
1138
+ render ( ) : void {
1138
1139
const noMatches = this . filter . totalCount > 0 && this . filter . matchCount === 0 ;
1139
1140
1140
1141
if ( this . pattern && noMatches ) {
@@ -1272,7 +1273,7 @@ class StickyScrollController<T, TFilterData, TRef> extends Disposable {
1272
1273
1273
1274
constructor (
1274
1275
private readonly tree : AbstractTree < T , TFilterData , TRef > ,
1275
- private readonly model : ITreeModel < T , TFilterData , TRef > ,
1276
+ private model : ITreeModel < T , TFilterData , TRef > ,
1276
1277
private readonly view : List < ITreeNode < T , TFilterData > > ,
1277
1278
renderers : TreeRenderer < T , TFilterData , TRef , any > [ ] ,
1278
1279
private readonly treeDelegate : IListVirtualDelegate < ITreeNode < T , TFilterData > > ,
@@ -1552,6 +1553,10 @@ class StickyScrollController<T, TFilterData, TRef> extends Disposable {
1552
1553
}
1553
1554
return { stickyScrollMaxItemCount } ;
1554
1555
}
1556
+
1557
+ setModel ( model : ITreeModel < T , TFilterData , TRef > ) : void {
1558
+ this . model = model ;
1559
+ }
1555
1560
}
1556
1561
1557
1562
class StickyScrollWidget < T , TFilterData , TRef > implements IDisposable {
@@ -2493,7 +2498,14 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
2493
2498
get onDidFocus ( ) : Event < void > { return this . view . onDidFocus ; }
2494
2499
get onDidBlur ( ) : Event < void > { return this . view . onDidBlur ; }
2495
2500
2496
- get onDidChangeModel ( ) : Event < void > { return Event . signal ( this . model . onDidSpliceModel ) ; }
2501
+ private readonly onDidSwapModel = this . disposables . add ( new Emitter < void > ( ) ) ;
2502
+ private readonly onDidChangeModelRelay = this . disposables . add ( new Relay < void > ( ) ) ;
2503
+ private readonly onDidSpliceModelRelay = this . disposables . add ( new Relay < ITreeModelSpliceEvent < T , TFilterData > > ( ) ) ;
2504
+ private readonly onDidChangeCollapseStateRelay = this . disposables . add ( new Relay < ICollapseStateChangeEvent < T , TFilterData > > ( ) ) ;
2505
+ private readonly onDidChangeRenderNodeCountRelay = this . disposables . add ( new Relay < ITreeNode < T , TFilterData > > ( ) ) ;
2506
+ private readonly onDidChangeActiveNodesRelay = this . disposables . add ( new Relay < ITreeNode < T , TFilterData > [ ] > ( ) ) ;
2507
+
2508
+ get onDidChangeModel ( ) : Event < void > { return Event . any ( this . onDidChangeModelRelay . event , this . onDidSwapModel . event ) ; }
2497
2509
get onDidChangeCollapseState ( ) : Event < ICollapseStateChangeEvent < T , TFilterData > > { return this . model . onDidChangeCollapseState ; }
2498
2510
get onDidChangeRenderNodeCount ( ) : Event < ITreeNode < T , TFilterData > > { return this . model . onDidChangeRenderNodeCount ; }
2499
2511
@@ -2535,56 +2547,19 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
2535
2547
this . model = this . createModel ( _user , _options ) ;
2536
2548
this . treeDelegate = new ComposedTreeDelegate < T , ITreeNode < T , TFilterData > > ( delegate ) ;
2537
2549
2538
- const onDidChangeCollapseStateRelay = new Relay < ICollapseStateChangeEvent < T , TFilterData > > ( ) ;
2539
- const onDidChangeActiveNodes = new Relay < ITreeNode < T , TFilterData > [ ] > ( ) ;
2540
- const activeNodes = this . disposables . add ( new EventCollection ( onDidChangeActiveNodes . event ) ) ;
2550
+ const activeNodes = this . disposables . add ( new EventCollection ( this . onDidChangeActiveNodesRelay . event ) ) ;
2541
2551
const renderedIndentGuides = new SetMap < ITreeNode < T , TFilterData > , HTMLDivElement > ( ) ;
2542
- this . renderers = renderers . map ( r => new TreeRenderer < T , TFilterData , TRef , any > ( r , this . model , onDidChangeCollapseStateRelay . event , activeNodes , renderedIndentGuides , _options ) ) ;
2552
+ this . renderers = renderers . map ( r => new TreeRenderer < T , TFilterData , TRef , any > ( r , this . model , this . onDidChangeCollapseStateRelay . event , activeNodes , renderedIndentGuides , _options ) ) ;
2543
2553
for ( const r of this . renderers ) {
2544
2554
this . disposables . add ( r ) ;
2545
2555
}
2546
2556
2547
2557
this . focus = new Trait ( ( ) => this . view . getFocusedElements ( ) [ 0 ] , _options . identityProvider ) ;
2548
2558
this . selection = new Trait ( ( ) => this . view . getSelectedElements ( ) [ 0 ] , _options . identityProvider ) ;
2549
2559
this . anchor = new Trait ( ( ) => this . view . getAnchorElement ( ) , _options . identityProvider ) ;
2550
- this . view = new TreeNodeList ( _user , container , this . treeDelegate , this . renderers , this . focus , this . selection , this . anchor , { ...asListOptions ( this . model , _options ) , tree : this , stickyScrollProvider : ( ) => this . stickyScrollController } ) ;
2551
-
2552
- this . disposables . add ( this . model . onDidSpliceRenderedNodes ( ( { start, deleteCount, elements } ) => this . view . splice ( start , deleteCount , elements ) ) ) ;
2553
-
2554
- onDidChangeCollapseStateRelay . input = this . model . onDidChangeCollapseState ;
2555
-
2556
- const onDidModelSplice = Event . forEach ( this . model . onDidSpliceModel , e => {
2557
- this . eventBufferer . bufferEvents ( ( ) => {
2558
- this . focus . onDidModelSplice ( e ) ;
2559
- this . selection . onDidModelSplice ( e ) ;
2560
- } ) ;
2561
- } , this . disposables ) ;
2562
-
2563
- // Make sure the `forEach` always runs
2564
- onDidModelSplice ( ( ) => null , null , this . disposables ) ;
2565
-
2566
- // Active nodes can change when the model changes or when focus or selection change.
2567
- // We debounce it with 0 delay since these events may fire in the same stack and we only
2568
- // want to run this once. It also doesn't matter if it runs on the next tick since it's only
2569
- // a nice to have UI feature.
2570
- const activeNodesEmitter = this . disposables . add ( new Emitter < ITreeNode < T , TFilterData > [ ] > ( ) ) ;
2571
- const activeNodesDebounce = this . disposables . add ( new Delayer ( 0 ) ) ;
2572
- this . disposables . add ( Event . any < any > ( onDidModelSplice , this . focus . onDidChange , this . selection . onDidChange ) ( ( ) => {
2573
- activeNodesDebounce . trigger ( ( ) => {
2574
- const set = new Set < ITreeNode < T , TFilterData > > ( ) ;
2560
+ this . view = new TreeNodeList ( _user , container , this . treeDelegate , this . renderers , this . focus , this . selection , this . anchor , { ...asListOptions ( ( ) => this . model , _options ) , tree : this , stickyScrollProvider : ( ) => this . stickyScrollController } ) ;
2575
2561
2576
- for ( const node of this . focus . getNodes ( ) ) {
2577
- set . add ( node ) ;
2578
- }
2579
-
2580
- for ( const node of this . selection . getNodes ( ) ) {
2581
- set . add ( node ) ;
2582
- }
2583
-
2584
- activeNodesEmitter . fire ( [ ...set . values ( ) ] ) ;
2585
- } ) ;
2586
- } ) ) ;
2587
- onDidChangeActiveNodes . input = activeNodesEmitter . event ;
2562
+ this . setupModel ( this . model ) ; // model needs to be setup after the traits have been created
2588
2563
2589
2564
if ( _options . keyboardSupport !== false ) {
2590
2565
const onKeyDown = Event . chain ( this . view . onKeyDown , $ =>
@@ -2599,12 +2574,19 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
2599
2574
2600
2575
if ( ( _options . findWidgetEnabled ?? true ) && _options . keyboardNavigationLabelProvider && _options . contextViewProvider ) {
2601
2576
const opts = this . options . findWidgetStyles ? { styles : this . options . findWidgetStyles } : undefined ;
2602
- this . findController = new FindController ( this , this . model , this . view , filter ! , _options . contextViewProvider , opts ) ;
2577
+ this . findController = new FindController ( this , this . view , filter ! , _options . contextViewProvider , opts ) ;
2603
2578
this . focusNavigationFilter = node => this . findController ! . shouldAllowFocus ( node ) ;
2604
2579
this . onDidChangeFindOpenState = this . findController . onDidChangeOpenState ;
2605
2580
this . disposables . add ( this . findController ) ;
2606
2581
this . onDidChangeFindMode = this . findController . onDidChangeMode ;
2607
2582
this . onDidChangeFindMatchType = this . findController . onDidChangeMatchType ;
2583
+ this . disposables . add ( this . onDidSpliceModelRelay . event ( ( ) => {
2584
+ if ( ! this . findController ! . isOpened ( ) || this . findController ! . pattern . length === 0 ) {
2585
+ return ;
2586
+ }
2587
+ this . refilter ( ) ;
2588
+ this . findController ! . render ( ) ;
2589
+ } ) ) ;
2608
2590
} else {
2609
2591
this . onDidChangeFindMode = Event . None ;
2610
2592
this . onDidChangeFindMatchType = Event . None ;
@@ -3109,6 +3091,69 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
3109
3091
3110
3092
protected abstract createModel ( user : string , options : IAbstractTreeOptions < T , TFilterData > ) : ITreeModel < T , TFilterData , TRef > ;
3111
3093
3094
+ private readonly modelDisposables = new DisposableStore ( ) ;
3095
+ private setupModel ( model : ITreeModel < T , TFilterData , TRef > ) {
3096
+ this . modelDisposables . clear ( ) ;
3097
+
3098
+ this . modelDisposables . add ( model . onDidSpliceRenderedNodes ( ( { start, deleteCount, elements } ) => this . view . splice ( start , deleteCount , elements ) ) ) ;
3099
+
3100
+ const onDidModelSplice = Event . forEach ( model . onDidSpliceModel , e => {
3101
+ this . eventBufferer . bufferEvents ( ( ) => {
3102
+ this . focus . onDidModelSplice ( e ) ;
3103
+ this . selection . onDidModelSplice ( e ) ;
3104
+ } ) ;
3105
+ } , this . modelDisposables ) ;
3106
+
3107
+ // Make sure the `forEach` always runs
3108
+ onDidModelSplice ( ( ) => null , null , this . modelDisposables ) ;
3109
+
3110
+ // Active nodes can change when the model changes or when focus or selection change.
3111
+ // We debounce it with 0 delay since these events may fire in the same stack and we only
3112
+ // want to run this once. It also doesn't matter if it runs on the next tick since it's only
3113
+ // a nice to have UI feature.
3114
+ const activeNodesEmitter = this . modelDisposables . add ( new Emitter < ITreeNode < T , TFilterData > [ ] > ( ) ) ;
3115
+ const activeNodesDebounce = this . modelDisposables . add ( new Delayer ( 0 ) ) ;
3116
+ this . modelDisposables . add ( Event . any < any > ( onDidModelSplice , this . focus . onDidChange , this . selection . onDidChange ) ( ( ) => {
3117
+ activeNodesDebounce . trigger ( ( ) => {
3118
+ const set = new Set < ITreeNode < T , TFilterData > > ( ) ;
3119
+
3120
+ for ( const node of this . focus . getNodes ( ) ) {
3121
+ set . add ( node ) ;
3122
+ }
3123
+
3124
+ for ( const node of this . selection . getNodes ( ) ) {
3125
+ set . add ( node ) ;
3126
+ }
3127
+
3128
+ activeNodesEmitter . fire ( [ ...set . values ( ) ] ) ;
3129
+ } ) ;
3130
+ } ) ) ;
3131
+
3132
+ this . onDidChangeActiveNodesRelay . input = activeNodesEmitter . event ;
3133
+ this . onDidChangeModelRelay . input = Event . signal ( model . onDidSpliceModel ) ;
3134
+ this . onDidChangeCollapseStateRelay . input = model . onDidChangeCollapseState ;
3135
+ this . onDidChangeRenderNodeCountRelay . input = model . onDidChangeRenderNodeCount ;
3136
+ }
3137
+
3138
+ setModel ( newModel : ITreeModel < T , TFilterData , TRef > ) {
3139
+ const oldModel = this . model ;
3140
+
3141
+ this . model = newModel ;
3142
+ this . setupModel ( newModel ) ;
3143
+
3144
+ this . renderers . forEach ( r => r . setModel ( newModel ) ) ;
3145
+ this . stickyScrollController ?. setModel ( newModel ) ;
3146
+
3147
+ this . view . splice ( 0 , oldModel . getListRenderCount ( oldModel . rootRef ) ) ;
3148
+ this . model . refilter ( ) ;
3149
+
3150
+ this . onDidSwapModel . fire ( ) ;
3151
+ }
3152
+
3153
+ getModel ( ) : ITreeModel < T , TFilterData , TRef > {
3154
+ return this . model ;
3155
+ }
3156
+
3112
3157
navigate ( start ?: TRef ) : ITreeNavigator < T > {
3113
3158
return new TreeNavigator ( this . view , this . model , start ) ;
3114
3159
}
@@ -3117,6 +3162,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
3117
3162
dispose ( this . disposables ) ;
3118
3163
this . stickyScrollController ?. dispose ( ) ;
3119
3164
this . view . dispose ( ) ;
3165
+ this . modelDisposables . dispose ( ) ;
3120
3166
}
3121
3167
}
3122
3168
0 commit comments