a131fcf4c8fce8c93e22e37624a0e0d6552bb5b2b843e551b2d0bcd322b373b722e8b9adc0b2cba219b1c0f4bf69f7020da0fb76676b37a0a8454b2eda52ba 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import { ElementsDragAndDropData } from '../list/listView.js';
  15. import { ComposedTreeDelegate } from './abstractTree.js';
  16. import { getVisibleState, isFilterResult } from './indexTreeModel.js';
  17. import { CompressibleObjectTree, ObjectTree } from './objectTree.js';
  18. import { TreeError, WeakMapper } from './tree.js';
  19. import { createCancelablePromise, Promises, timeout } from '../../../common/async.js';
  20. import { Codicon } from '../../../common/codicons.js';
  21. import { isCancellationError, onUnexpectedError } from '../../../common/errors.js';
  22. import { Emitter, Event } from '../../../common/event.js';
  23. import { Iterable } from '../../../common/iterator.js';
  24. import { DisposableStore, dispose } from '../../../common/lifecycle.js';
  25. import { isIterable } from '../../../common/types.js';
  26. function createAsyncDataTreeNode(props) {
  27. return Object.assign(Object.assign({}, props), { children: [], refreshPromise: undefined, stale: true, slow: false, collapsedByDefault: undefined });
  28. }
  29. function isAncestor(ancestor, descendant) {
  30. if (!descendant.parent) {
  31. return false;
  32. }
  33. else if (descendant.parent === ancestor) {
  34. return true;
  35. }
  36. else {
  37. return isAncestor(ancestor, descendant.parent);
  38. }
  39. }
  40. function intersects(node, other) {
  41. return node === other || isAncestor(node, other) || isAncestor(other, node);
  42. }
  43. class AsyncDataTreeNodeWrapper {
  44. constructor(node) {
  45. this.node = node;
  46. }
  47. get element() { return this.node.element.element; }
  48. get children() { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
  49. get depth() { return this.node.depth; }
  50. get visibleChildrenCount() { return this.node.visibleChildrenCount; }
  51. get visibleChildIndex() { return this.node.visibleChildIndex; }
  52. get collapsible() { return this.node.collapsible; }
  53. get collapsed() { return this.node.collapsed; }
  54. get visible() { return this.node.visible; }
  55. get filterData() { return this.node.filterData; }
  56. }
  57. class AsyncDataTreeRenderer {
  58. constructor(renderer, nodeMapper, onDidChangeTwistieState) {
  59. this.renderer = renderer;
  60. this.nodeMapper = nodeMapper;
  61. this.onDidChangeTwistieState = onDidChangeTwistieState;
  62. this.renderedNodes = new Map();
  63. this.templateId = renderer.templateId;
  64. }
  65. renderTemplate(container) {
  66. const templateData = this.renderer.renderTemplate(container);
  67. return { templateData };
  68. }
  69. renderElement(node, index, templateData, height) {
  70. this.renderer.renderElement(this.nodeMapper.map(node), index, templateData.templateData, height);
  71. }
  72. renderTwistie(element, twistieElement) {
  73. if (element.slow) {
  74. twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray);
  75. return true;
  76. }
  77. else {
  78. twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray);
  79. return false;
  80. }
  81. }
  82. disposeElement(node, index, templateData, height) {
  83. var _a, _b;
  84. (_b = (_a = this.renderer).disposeElement) === null || _b === void 0 ? void 0 : _b.call(_a, this.nodeMapper.map(node), index, templateData.templateData, height);
  85. }
  86. disposeTemplate(templateData) {
  87. this.renderer.disposeTemplate(templateData.templateData);
  88. }
  89. dispose() {
  90. this.renderedNodes.clear();
  91. }
  92. }
  93. function asTreeEvent(e) {
  94. return {
  95. browserEvent: e.browserEvent,
  96. elements: e.elements.map(e => e.element)
  97. };
  98. }
  99. function asTreeMouseEvent(e) {
  100. return {
  101. browserEvent: e.browserEvent,
  102. element: e.element && e.element.element,
  103. target: e.target
  104. };
  105. }
  106. class AsyncDataTreeElementsDragAndDropData extends ElementsDragAndDropData {
  107. constructor(data) {
  108. super(data.elements.map(node => node.element));
  109. this.data = data;
  110. }
  111. }
  112. function asAsyncDataTreeDragAndDropData(data) {
  113. if (data instanceof ElementsDragAndDropData) {
  114. return new AsyncDataTreeElementsDragAndDropData(data);
  115. }
  116. return data;
  117. }
  118. class AsyncDataTreeNodeListDragAndDrop {
  119. constructor(dnd) {
  120. this.dnd = dnd;
  121. }
  122. getDragURI(node) {
  123. return this.dnd.getDragURI(node.element);
  124. }
  125. getDragLabel(nodes, originalEvent) {
  126. if (this.dnd.getDragLabel) {
  127. return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
  128. }
  129. return undefined;
  130. }
  131. onDragStart(data, originalEvent) {
  132. var _a, _b;
  133. (_b = (_a = this.dnd).onDragStart) === null || _b === void 0 ? void 0 : _b.call(_a, asAsyncDataTreeDragAndDropData(data), originalEvent);
  134. }
  135. onDragOver(data, targetNode, targetIndex, originalEvent, raw = true) {
  136. return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
  137. }
  138. drop(data, targetNode, targetIndex, originalEvent) {
  139. this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
  140. }
  141. onDragEnd(originalEvent) {
  142. var _a, _b;
  143. (_b = (_a = this.dnd).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, originalEvent);
  144. }
  145. }
  146. function asObjectTreeOptions(options) {
  147. return options && Object.assign(Object.assign({}, options), { collapseByDefault: true, identityProvider: options.identityProvider && {
  148. getId(el) {
  149. return options.identityProvider.getId(el.element);
  150. }
  151. }, dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd), multipleSelectionController: options.multipleSelectionController && {
  152. isSelectionSingleChangeEvent(e) {
  153. return options.multipleSelectionController.isSelectionSingleChangeEvent(Object.assign(Object.assign({}, e), { element: e.element }));
  154. },
  155. isSelectionRangeChangeEvent(e) {
  156. return options.multipleSelectionController.isSelectionRangeChangeEvent(Object.assign(Object.assign({}, e), { element: e.element }));
  157. }
  158. }, accessibilityProvider: options.accessibilityProvider && Object.assign(Object.assign({}, options.accessibilityProvider), { getPosInSet: undefined, getSetSize: undefined, getRole: options.accessibilityProvider.getRole ? (el) => {
  159. return options.accessibilityProvider.getRole(el.element);
  160. } : () => 'treeitem', isChecked: options.accessibilityProvider.isChecked ? (e) => {
  161. var _a;
  162. return !!((_a = options.accessibilityProvider) === null || _a === void 0 ? void 0 : _a.isChecked(e.element));
  163. } : undefined, getAriaLabel(e) {
  164. return options.accessibilityProvider.getAriaLabel(e.element);
  165. },
  166. getWidgetAriaLabel() {
  167. return options.accessibilityProvider.getWidgetAriaLabel();
  168. }, getWidgetRole: options.accessibilityProvider.getWidgetRole ? () => options.accessibilityProvider.getWidgetRole() : () => 'tree', getAriaLevel: options.accessibilityProvider.getAriaLevel && (node => {
  169. return options.accessibilityProvider.getAriaLevel(node.element);
  170. }), getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
  171. return options.accessibilityProvider.getActiveDescendantId(node.element);
  172. }) }), filter: options.filter && {
  173. filter(e, parentVisibility) {
  174. return options.filter.filter(e.element, parentVisibility);
  175. }
  176. }, keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && Object.assign(Object.assign({}, options.keyboardNavigationLabelProvider), { getKeyboardNavigationLabel(e) {
  177. return options.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(e.element);
  178. } }), sorter: undefined, expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : (typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : (e => options.expandOnlyOnTwistieClick(e.element))), additionalScrollHeight: options.additionalScrollHeight });
  179. }
  180. function dfs(node, fn) {
  181. fn(node);
  182. node.children.forEach(child => dfs(child, fn));
  183. }
  184. export class AsyncDataTree {
  185. constructor(user, container, delegate, renderers, dataSource, options = {}) {
  186. this.user = user;
  187. this.dataSource = dataSource;
  188. this.nodes = new Map();
  189. this.subTreeRefreshPromises = new Map();
  190. this.refreshPromises = new Map();
  191. this._onDidRender = new Emitter();
  192. this._onDidChangeNodeSlowState = new Emitter();
  193. this.nodeMapper = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node));
  194. this.disposables = new DisposableStore();
  195. this.identityProvider = options.identityProvider;
  196. this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
  197. this.sorter = options.sorter;
  198. this.collapseByDefault = options.collapseByDefault;
  199. this.tree = this.createTree(user, container, delegate, renderers, options);
  200. this.onDidChangeFindMode = this.tree.onDidChangeFindMode;
  201. this.root = createAsyncDataTreeNode({
  202. element: undefined,
  203. parent: null,
  204. hasChildren: true
  205. });
  206. if (this.identityProvider) {
  207. this.root = Object.assign(Object.assign({}, this.root), { id: null });
  208. }
  209. this.nodes.set(null, this.root);
  210. this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
  211. }
  212. get onDidChangeFocus() { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
  213. get onDidChangeSelection() { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
  214. get onMouseDblClick() { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
  215. get onPointer() { return Event.map(this.tree.onPointer, asTreeMouseEvent); }
  216. get onDidFocus() { return this.tree.onDidFocus; }
  217. get onDidChangeModel() { return this.tree.onDidChangeModel; }
  218. get onDidChangeCollapseState() { return this.tree.onDidChangeCollapseState; }
  219. get onDidChangeFindOpenState() { return this.tree.onDidChangeFindOpenState; }
  220. get onDidDispose() { return this.tree.onDidDispose; }
  221. createTree(user, container, delegate, renderers, options) {
  222. const objectTreeDelegate = new ComposedTreeDelegate(delegate);
  223. const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
  224. const objectTreeOptions = asObjectTreeOptions(options) || {};
  225. return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
  226. }
  227. updateOptions(options = {}) {
  228. this.tree.updateOptions(options);
  229. }
  230. // Widget
  231. getHTMLElement() {
  232. return this.tree.getHTMLElement();
  233. }
  234. get scrollTop() {
  235. return this.tree.scrollTop;
  236. }
  237. set scrollTop(scrollTop) {
  238. this.tree.scrollTop = scrollTop;
  239. }
  240. domFocus() {
  241. this.tree.domFocus();
  242. }
  243. layout(height, width) {
  244. this.tree.layout(height, width);
  245. }
  246. style(styles) {
  247. this.tree.style(styles);
  248. }
  249. // Model
  250. getInput() {
  251. return this.root.element;
  252. }
  253. setInput(input, viewState) {
  254. return __awaiter(this, void 0, void 0, function* () {
  255. this.refreshPromises.forEach(promise => promise.cancel());
  256. this.refreshPromises.clear();
  257. this.root.element = input;
  258. const viewStateContext = viewState && { viewState, focus: [], selection: [] };
  259. yield this._updateChildren(input, true, false, viewStateContext);
  260. if (viewStateContext) {
  261. this.tree.setFocus(viewStateContext.focus);
  262. this.tree.setSelection(viewStateContext.selection);
  263. }
  264. if (viewState && typeof viewState.scrollTop === 'number') {
  265. this.scrollTop = viewState.scrollTop;
  266. }
  267. });
  268. }
  269. _updateChildren(element = this.root.element, recursive = true, rerender = false, viewStateContext, options) {
  270. return __awaiter(this, void 0, void 0, function* () {
  271. if (typeof this.root.element === 'undefined') {
  272. throw new TreeError(this.user, 'Tree input not set');
  273. }
  274. if (this.root.refreshPromise) {
  275. yield this.root.refreshPromise;
  276. yield Event.toPromise(this._onDidRender.event);
  277. }
  278. const node = this.getDataNode(element);
  279. yield this.refreshAndRenderNode(node, recursive, viewStateContext, options);
  280. if (rerender) {
  281. try {
  282. this.tree.rerender(node);
  283. }
  284. catch (_a) {
  285. // missing nodes are fine, this could've resulted from
  286. // parallel refresh calls, removing `node` altogether
  287. }
  288. }
  289. });
  290. }
  291. // View
  292. rerender(element) {
  293. if (element === undefined || element === this.root.element) {
  294. this.tree.rerender();
  295. return;
  296. }
  297. const node = this.getDataNode(element);
  298. this.tree.rerender(node);
  299. }
  300. // Tree
  301. getNode(element = this.root.element) {
  302. const dataNode = this.getDataNode(element);
  303. const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
  304. return this.nodeMapper.map(node);
  305. }
  306. collapse(element, recursive = false) {
  307. const node = this.getDataNode(element);
  308. return this.tree.collapse(node === this.root ? null : node, recursive);
  309. }
  310. expand(element, recursive = false) {
  311. return __awaiter(this, void 0, void 0, function* () {
  312. if (typeof this.root.element === 'undefined') {
  313. throw new TreeError(this.user, 'Tree input not set');
  314. }
  315. if (this.root.refreshPromise) {
  316. yield this.root.refreshPromise;
  317. yield Event.toPromise(this._onDidRender.event);
  318. }
  319. const node = this.getDataNode(element);
  320. if (this.tree.hasElement(node) && !this.tree.isCollapsible(node)) {
  321. return false;
  322. }
  323. if (node.refreshPromise) {
  324. yield this.root.refreshPromise;
  325. yield Event.toPromise(this._onDidRender.event);
  326. }
  327. if (node !== this.root && !node.refreshPromise && !this.tree.isCollapsed(node)) {
  328. return false;
  329. }
  330. const result = this.tree.expand(node === this.root ? null : node, recursive);
  331. if (node.refreshPromise) {
  332. yield this.root.refreshPromise;
  333. yield Event.toPromise(this._onDidRender.event);
  334. }
  335. return result;
  336. });
  337. }
  338. setSelection(elements, browserEvent) {
  339. const nodes = elements.map(e => this.getDataNode(e));
  340. this.tree.setSelection(nodes, browserEvent);
  341. }
  342. getSelection() {
  343. const nodes = this.tree.getSelection();
  344. return nodes.map(n => n.element);
  345. }
  346. setFocus(elements, browserEvent) {
  347. const nodes = elements.map(e => this.getDataNode(e));
  348. this.tree.setFocus(nodes, browserEvent);
  349. }
  350. getFocus() {
  351. const nodes = this.tree.getFocus();
  352. return nodes.map(n => n.element);
  353. }
  354. reveal(element, relativeTop) {
  355. this.tree.reveal(this.getDataNode(element), relativeTop);
  356. }
  357. // Tree navigation
  358. getParentElement(element) {
  359. const node = this.tree.getParentElement(this.getDataNode(element));
  360. return (node && node.element);
  361. }
  362. getFirstElementChild(element = this.root.element) {
  363. const dataNode = this.getDataNode(element);
  364. const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
  365. return (node && node.element);
  366. }
  367. // Implementation
  368. getDataNode(element) {
  369. const node = this.nodes.get((element === this.root.element ? null : element));
  370. if (!node) {
  371. throw new TreeError(this.user, `Data tree node not found: ${element}`);
  372. }
  373. return node;
  374. }
  375. refreshAndRenderNode(node, recursive, viewStateContext, options) {
  376. return __awaiter(this, void 0, void 0, function* () {
  377. yield this.refreshNode(node, recursive, viewStateContext);
  378. this.render(node, viewStateContext, options);
  379. });
  380. }
  381. refreshNode(node, recursive, viewStateContext) {
  382. return __awaiter(this, void 0, void 0, function* () {
  383. let result;
  384. this.subTreeRefreshPromises.forEach((refreshPromise, refreshNode) => {
  385. if (!result && intersects(refreshNode, node)) {
  386. result = refreshPromise.then(() => this.refreshNode(node, recursive, viewStateContext));
  387. }
  388. });
  389. if (result) {
  390. return result;
  391. }
  392. if (node !== this.root) {
  393. const treeNode = this.tree.getNode(node);
  394. if (treeNode.collapsed) {
  395. node.hasChildren = !!this.dataSource.hasChildren(node.element);
  396. node.stale = true;
  397. return;
  398. }
  399. }
  400. return this.doRefreshSubTree(node, recursive, viewStateContext);
  401. });
  402. }
  403. doRefreshSubTree(node, recursive, viewStateContext) {
  404. return __awaiter(this, void 0, void 0, function* () {
  405. let done;
  406. node.refreshPromise = new Promise(c => done = c);
  407. this.subTreeRefreshPromises.set(node, node.refreshPromise);
  408. node.refreshPromise.finally(() => {
  409. node.refreshPromise = undefined;
  410. this.subTreeRefreshPromises.delete(node);
  411. });
  412. try {
  413. const childrenToRefresh = yield this.doRefreshNode(node, recursive, viewStateContext);
  414. node.stale = false;
  415. yield Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
  416. }
  417. finally {
  418. done();
  419. }
  420. });
  421. }
  422. doRefreshNode(node, recursive, viewStateContext) {
  423. return __awaiter(this, void 0, void 0, function* () {
  424. node.hasChildren = !!this.dataSource.hasChildren(node.element);
  425. let childrenPromise;
  426. if (!node.hasChildren) {
  427. childrenPromise = Promise.resolve(Iterable.empty());
  428. }
  429. else {
  430. const children = this.doGetChildren(node);
  431. if (isIterable(children)) {
  432. childrenPromise = Promise.resolve(children);
  433. }
  434. else {
  435. const slowTimeout = timeout(800);
  436. slowTimeout.then(() => {
  437. node.slow = true;
  438. this._onDidChangeNodeSlowState.fire(node);
  439. }, _ => null);
  440. childrenPromise = children.finally(() => slowTimeout.cancel());
  441. }
  442. }
  443. try {
  444. const children = yield childrenPromise;
  445. return this.setChildren(node, children, recursive, viewStateContext);
  446. }
  447. catch (err) {
  448. if (node !== this.root && this.tree.hasElement(node)) {
  449. this.tree.collapse(node);
  450. }
  451. if (isCancellationError(err)) {
  452. return [];
  453. }
  454. throw err;
  455. }
  456. finally {
  457. if (node.slow) {
  458. node.slow = false;
  459. this._onDidChangeNodeSlowState.fire(node);
  460. }
  461. }
  462. });
  463. }
  464. doGetChildren(node) {
  465. let result = this.refreshPromises.get(node);
  466. if (result) {
  467. return result;
  468. }
  469. const children = this.dataSource.getChildren(node.element);
  470. if (isIterable(children)) {
  471. return this.processChildren(children);
  472. }
  473. else {
  474. result = createCancelablePromise(() => __awaiter(this, void 0, void 0, function* () { return this.processChildren(yield children); }));
  475. this.refreshPromises.set(node, result);
  476. return result.finally(() => { this.refreshPromises.delete(node); });
  477. }
  478. }
  479. _onDidChangeCollapseState({ node, deep }) {
  480. if (node.element === null) {
  481. return;
  482. }
  483. if (!node.collapsed && node.element.stale) {
  484. if (deep) {
  485. this.collapse(node.element.element);
  486. }
  487. else {
  488. this.refreshAndRenderNode(node.element, false)
  489. .catch(onUnexpectedError);
  490. }
  491. }
  492. }
  493. setChildren(node, childrenElementsIterable, recursive, viewStateContext) {
  494. const childrenElements = [...childrenElementsIterable];
  495. // perf: if the node was and still is a leaf, avoid all this hassle
  496. if (node.children.length === 0 && childrenElements.length === 0) {
  497. return [];
  498. }
  499. const nodesToForget = new Map();
  500. const childrenTreeNodesById = new Map();
  501. for (const child of node.children) {
  502. nodesToForget.set(child.element, child);
  503. if (this.identityProvider) {
  504. const collapsed = this.tree.isCollapsed(child);
  505. childrenTreeNodesById.set(child.id, { node: child, collapsed });
  506. }
  507. }
  508. const childrenToRefresh = [];
  509. const children = childrenElements.map(element => {
  510. const hasChildren = !!this.dataSource.hasChildren(element);
  511. if (!this.identityProvider) {
  512. const asyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, hasChildren });
  513. if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
  514. asyncDataTreeNode.collapsedByDefault = false;
  515. childrenToRefresh.push(asyncDataTreeNode);
  516. }
  517. return asyncDataTreeNode;
  518. }
  519. const id = this.identityProvider.getId(element).toString();
  520. const result = childrenTreeNodesById.get(id);
  521. if (result) {
  522. const asyncDataTreeNode = result.node;
  523. nodesToForget.delete(asyncDataTreeNode.element);
  524. this.nodes.delete(asyncDataTreeNode.element);
  525. this.nodes.set(element, asyncDataTreeNode);
  526. asyncDataTreeNode.element = element;
  527. asyncDataTreeNode.hasChildren = hasChildren;
  528. if (recursive) {
  529. if (result.collapsed) {
  530. asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element)));
  531. asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
  532. asyncDataTreeNode.stale = true;
  533. }
  534. else {
  535. childrenToRefresh.push(asyncDataTreeNode);
  536. }
  537. }
  538. else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
  539. asyncDataTreeNode.collapsedByDefault = false;
  540. childrenToRefresh.push(asyncDataTreeNode);
  541. }
  542. return asyncDataTreeNode;
  543. }
  544. const childAsyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, id, hasChildren });
  545. if (viewStateContext && viewStateContext.viewState.focus && viewStateContext.viewState.focus.indexOf(id) > -1) {
  546. viewStateContext.focus.push(childAsyncDataTreeNode);
  547. }
  548. if (viewStateContext && viewStateContext.viewState.selection && viewStateContext.viewState.selection.indexOf(id) > -1) {
  549. viewStateContext.selection.push(childAsyncDataTreeNode);
  550. }
  551. if (viewStateContext && viewStateContext.viewState.expanded && viewStateContext.viewState.expanded.indexOf(id) > -1) {
  552. childrenToRefresh.push(childAsyncDataTreeNode);
  553. }
  554. else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
  555. childAsyncDataTreeNode.collapsedByDefault = false;
  556. childrenToRefresh.push(childAsyncDataTreeNode);
  557. }
  558. return childAsyncDataTreeNode;
  559. });
  560. for (const node of nodesToForget.values()) {
  561. dfs(node, node => this.nodes.delete(node.element));
  562. }
  563. for (const child of children) {
  564. this.nodes.set(child.element, child);
  565. }
  566. node.children.splice(0, node.children.length, ...children);
  567. // TODO@joao this doesn't take filter into account
  568. if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
  569. children[0].collapsedByDefault = false;
  570. childrenToRefresh.push(children[0]);
  571. }
  572. return childrenToRefresh;
  573. }
  574. render(node, viewStateContext, options) {
  575. const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
  576. const objectTreeOptions = options && Object.assign(Object.assign({}, options), { diffIdentityProvider: options.diffIdentityProvider && {
  577. getId(node) {
  578. return options.diffIdentityProvider.getId(node.element);
  579. }
  580. } });
  581. this.tree.setChildren(node === this.root ? null : node, children, objectTreeOptions);
  582. if (node !== this.root) {
  583. this.tree.setCollapsible(node, node.hasChildren);
  584. }
  585. this._onDidRender.fire();
  586. }
  587. asTreeElement(node, viewStateContext) {
  588. if (node.stale) {
  589. return {
  590. element: node,
  591. collapsible: node.hasChildren,
  592. collapsed: true
  593. };
  594. }
  595. let collapsed;
  596. if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
  597. collapsed = false;
  598. }
  599. else {
  600. collapsed = node.collapsedByDefault;
  601. }
  602. node.collapsedByDefault = undefined;
  603. return {
  604. element: node,
  605. children: node.hasChildren ? Iterable.map(node.children, child => this.asTreeElement(child, viewStateContext)) : [],
  606. collapsible: node.hasChildren,
  607. collapsed
  608. };
  609. }
  610. processChildren(children) {
  611. if (this.sorter) {
  612. children = [...children].sort(this.sorter.compare.bind(this.sorter));
  613. }
  614. return children;
  615. }
  616. dispose() {
  617. this.disposables.dispose();
  618. }
  619. }
  620. class CompressibleAsyncDataTreeNodeWrapper {
  621. constructor(node) {
  622. this.node = node;
  623. }
  624. get element() {
  625. return {
  626. elements: this.node.element.elements.map(e => e.element),
  627. incompressible: this.node.element.incompressible
  628. };
  629. }
  630. get children() { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); }
  631. get depth() { return this.node.depth; }
  632. get visibleChildrenCount() { return this.node.visibleChildrenCount; }
  633. get visibleChildIndex() { return this.node.visibleChildIndex; }
  634. get collapsible() { return this.node.collapsible; }
  635. get collapsed() { return this.node.collapsed; }
  636. get visible() { return this.node.visible; }
  637. get filterData() { return this.node.filterData; }
  638. }
  639. class CompressibleAsyncDataTreeRenderer {
  640. constructor(renderer, nodeMapper, compressibleNodeMapperProvider, onDidChangeTwistieState) {
  641. this.renderer = renderer;
  642. this.nodeMapper = nodeMapper;
  643. this.compressibleNodeMapperProvider = compressibleNodeMapperProvider;
  644. this.onDidChangeTwistieState = onDidChangeTwistieState;
  645. this.renderedNodes = new Map();
  646. this.disposables = [];
  647. this.templateId = renderer.templateId;
  648. }
  649. renderTemplate(container) {
  650. const templateData = this.renderer.renderTemplate(container);
  651. return { templateData };
  652. }
  653. renderElement(node, index, templateData, height) {
  654. this.renderer.renderElement(this.nodeMapper.map(node), index, templateData.templateData, height);
  655. }
  656. renderCompressedElements(node, index, templateData, height) {
  657. this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node), index, templateData.templateData, height);
  658. }
  659. renderTwistie(element, twistieElement) {
  660. if (element.slow) {
  661. twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray);
  662. return true;
  663. }
  664. else {
  665. twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray);
  666. return false;
  667. }
  668. }
  669. disposeElement(node, index, templateData, height) {
  670. var _a, _b;
  671. (_b = (_a = this.renderer).disposeElement) === null || _b === void 0 ? void 0 : _b.call(_a, this.nodeMapper.map(node), index, templateData.templateData, height);
  672. }
  673. disposeCompressedElements(node, index, templateData, height) {
  674. var _a, _b;
  675. (_b = (_a = this.renderer).disposeCompressedElements) === null || _b === void 0 ? void 0 : _b.call(_a, this.compressibleNodeMapperProvider().map(node), index, templateData.templateData, height);
  676. }
  677. disposeTemplate(templateData) {
  678. this.renderer.disposeTemplate(templateData.templateData);
  679. }
  680. dispose() {
  681. this.renderedNodes.clear();
  682. this.disposables = dispose(this.disposables);
  683. }
  684. }
  685. function asCompressibleObjectTreeOptions(options) {
  686. const objectTreeOptions = options && asObjectTreeOptions(options);
  687. return objectTreeOptions && Object.assign(Object.assign({}, objectTreeOptions), { keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && Object.assign(Object.assign({}, objectTreeOptions.keyboardNavigationLabelProvider), { getCompressedNodeKeyboardNavigationLabel(els) {
  688. return options.keyboardNavigationLabelProvider.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element));
  689. } }) });
  690. }
  691. export class CompressibleAsyncDataTree extends AsyncDataTree {
  692. constructor(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, options = {}) {
  693. super(user, container, virtualDelegate, renderers, dataSource, options);
  694. this.compressionDelegate = compressionDelegate;
  695. this.compressibleNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
  696. this.filter = options.filter;
  697. }
  698. createTree(user, container, delegate, renderers, options) {
  699. const objectTreeDelegate = new ComposedTreeDelegate(delegate);
  700. const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
  701. const objectTreeOptions = asCompressibleObjectTreeOptions(options) || {};
  702. return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
  703. }
  704. asTreeElement(node, viewStateContext) {
  705. return Object.assign({ incompressible: this.compressionDelegate.isIncompressible(node.element) }, super.asTreeElement(node, viewStateContext));
  706. }
  707. updateOptions(options = {}) {
  708. this.tree.updateOptions(options);
  709. }
  710. render(node, viewStateContext) {
  711. if (!this.identityProvider) {
  712. return super.render(node, viewStateContext);
  713. }
  714. // Preserve traits across compressions. Hacky but does the trick.
  715. // This is hard to fix properly since it requires rewriting the traits
  716. // across trees and lists. Let's just keep it this way for now.
  717. const getId = (element) => this.identityProvider.getId(element).toString();
  718. const getUncompressedIds = (nodes) => {
  719. const result = new Set();
  720. for (const node of nodes) {
  721. const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);
  722. if (!compressedNode.element) {
  723. continue;
  724. }
  725. for (const node of compressedNode.element.elements) {
  726. result.add(getId(node.element));
  727. }
  728. }
  729. return result;
  730. };
  731. const oldSelection = getUncompressedIds(this.tree.getSelection());
  732. const oldFocus = getUncompressedIds(this.tree.getFocus());
  733. super.render(node, viewStateContext);
  734. const selection = this.getSelection();
  735. let didChangeSelection = false;
  736. const focus = this.getFocus();
  737. let didChangeFocus = false;
  738. const visit = (node) => {
  739. const compressedNode = node.element;
  740. if (compressedNode) {
  741. for (let i = 0; i < compressedNode.elements.length; i++) {
  742. const id = getId(compressedNode.elements[i].element);
  743. const element = compressedNode.elements[compressedNode.elements.length - 1].element;
  744. // github.com/microsoft/vscode/issues/85938
  745. if (oldSelection.has(id) && selection.indexOf(element) === -1) {
  746. selection.push(element);
  747. didChangeSelection = true;
  748. }
  749. if (oldFocus.has(id) && focus.indexOf(element) === -1) {
  750. focus.push(element);
  751. didChangeFocus = true;
  752. }
  753. }
  754. }
  755. node.children.forEach(visit);
  756. };
  757. visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));
  758. if (didChangeSelection) {
  759. this.setSelection(selection);
  760. }
  761. if (didChangeFocus) {
  762. this.setFocus(focus);
  763. }
  764. }
  765. // For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
  766. // and we have to filter everything beforehand
  767. // Related to #85193 and #85835
  768. processChildren(children) {
  769. if (this.filter) {
  770. children = Iterable.filter(children, e => {
  771. const result = this.filter.filter(e, 1 /* TreeVisibility.Visible */);
  772. const visibility = getVisibility(result);
  773. if (visibility === 2 /* TreeVisibility.Recurse */) {
  774. throw new Error('Recursive tree visibility not supported in async data compressed trees');
  775. }
  776. return visibility === 1 /* TreeVisibility.Visible */;
  777. });
  778. }
  779. return super.processChildren(children);
  780. }
  781. }
  782. function getVisibility(filterResult) {
  783. if (typeof filterResult === 'boolean') {
  784. return filterResult ? 1 /* TreeVisibility.Visible */ : 0 /* TreeVisibility.Hidden */;
  785. }
  786. else if (isFilterResult(filterResult)) {
  787. return getVisibleState(filterResult.visibility);
  788. }
  789. else {
  790. return getVisibleState(filterResult);
  791. }
  792. }