791ad5b34963f26b789577e57a39b1262c83ffe1ba0a35ebbb9197be12afa6497d1a13507480b79608ef159bd3cdd85b7d7e87be02b418129e2734354eb4be 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. import { ObjectTreeModel } from './objectTreeModel.js';
  6. import { TreeError, WeakMapper } from './tree.js';
  7. import { Event } from '../../../common/event.js';
  8. import { Iterable } from '../../../common/iterator.js';
  9. function noCompress(element) {
  10. const elements = [element.element];
  11. const incompressible = element.incompressible || false;
  12. return {
  13. element: { elements, incompressible },
  14. children: Iterable.map(Iterable.from(element.children), noCompress),
  15. collapsible: element.collapsible,
  16. collapsed: element.collapsed
  17. };
  18. }
  19. // Exported only for test reasons, do not use directly
  20. export function compress(element) {
  21. const elements = [element.element];
  22. const incompressible = element.incompressible || false;
  23. let childrenIterator;
  24. let children;
  25. while (true) {
  26. [children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2);
  27. if (children.length !== 1) {
  28. break;
  29. }
  30. if (children[0].incompressible) {
  31. break;
  32. }
  33. element = children[0];
  34. elements.push(element.element);
  35. }
  36. return {
  37. element: { elements, incompressible },
  38. children: Iterable.map(Iterable.concat(children, childrenIterator), compress),
  39. collapsible: element.collapsible,
  40. collapsed: element.collapsed
  41. };
  42. }
  43. function _decompress(element, index = 0) {
  44. let children;
  45. if (index < element.element.elements.length - 1) {
  46. children = [_decompress(element, index + 1)];
  47. }
  48. else {
  49. children = Iterable.map(Iterable.from(element.children), el => _decompress(el, 0));
  50. }
  51. if (index === 0 && element.element.incompressible) {
  52. return {
  53. element: element.element.elements[index],
  54. children,
  55. incompressible: true,
  56. collapsible: element.collapsible,
  57. collapsed: element.collapsed
  58. };
  59. }
  60. return {
  61. element: element.element.elements[index],
  62. children,
  63. collapsible: element.collapsible,
  64. collapsed: element.collapsed
  65. };
  66. }
  67. // Exported only for test reasons, do not use directly
  68. export function decompress(element) {
  69. return _decompress(element, 0);
  70. }
  71. function splice(treeElement, element, children) {
  72. if (treeElement.element === element) {
  73. return Object.assign(Object.assign({}, treeElement), { children });
  74. }
  75. return Object.assign(Object.assign({}, treeElement), { children: Iterable.map(Iterable.from(treeElement.children), e => splice(e, element, children)) });
  76. }
  77. const wrapIdentityProvider = (base) => ({
  78. getId(node) {
  79. return node.elements.map(e => base.getId(e).toString()).join('\0');
  80. }
  81. });
  82. // Exported only for test reasons, do not use directly
  83. export class CompressedObjectTreeModel {
  84. constructor(user, list, options = {}) {
  85. this.user = user;
  86. this.rootRef = null;
  87. this.nodes = new Map();
  88. this.model = new ObjectTreeModel(user, list, options);
  89. this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
  90. this.identityProvider = options.identityProvider;
  91. }
  92. get onDidSplice() { return this.model.onDidSplice; }
  93. get onDidChangeCollapseState() { return this.model.onDidChangeCollapseState; }
  94. get onDidChangeRenderNodeCount() { return this.model.onDidChangeRenderNodeCount; }
  95. setChildren(element, children = Iterable.empty(), options) {
  96. // Diffs must be deem, since the compression can affect nested elements.
  97. // @see https://github.com/microsoft/vscode/pull/114237#issuecomment-759425034
  98. const diffIdentityProvider = options.diffIdentityProvider && wrapIdentityProvider(options.diffIdentityProvider);
  99. if (element === null) {
  100. const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress);
  101. this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDepth: Infinity });
  102. return;
  103. }
  104. const compressedNode = this.nodes.get(element);
  105. if (!compressedNode) {
  106. throw new Error('Unknown compressed tree node');
  107. }
  108. const node = this.model.getNode(compressedNode);
  109. const compressedParentNode = this.model.getParentNodeLocation(compressedNode);
  110. const parent = this.model.getNode(compressedParentNode);
  111. const decompressedElement = decompress(node);
  112. const splicedElement = splice(decompressedElement, element, children);
  113. const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement);
  114. const parentChildren = parent.children
  115. .map(child => child === node ? recompressedElement : child);
  116. this._setChildren(parent.element, parentChildren, {
  117. diffIdentityProvider,
  118. diffDepth: node.depth - parent.depth,
  119. });
  120. }
  121. setCompressionEnabled(enabled) {
  122. if (enabled === this.enabled) {
  123. return;
  124. }
  125. this.enabled = enabled;
  126. const root = this.model.getNode();
  127. const rootChildren = root.children;
  128. const decompressedRootChildren = Iterable.map(rootChildren, decompress);
  129. const recompressedRootChildren = Iterable.map(decompressedRootChildren, enabled ? compress : noCompress);
  130. // it should be safe to always use deep diff mode here if an identity
  131. // provider is available, since we know the raw nodes are unchanged.
  132. this._setChildren(null, recompressedRootChildren, {
  133. diffIdentityProvider: this.identityProvider,
  134. diffDepth: Infinity,
  135. });
  136. }
  137. _setChildren(node, children, options) {
  138. const insertedElements = new Set();
  139. const onDidCreateNode = (node) => {
  140. for (const element of node.element.elements) {
  141. insertedElements.add(element);
  142. this.nodes.set(element, node.element);
  143. }
  144. };
  145. const onDidDeleteNode = (node) => {
  146. for (const element of node.element.elements) {
  147. if (!insertedElements.has(element)) {
  148. this.nodes.delete(element);
  149. }
  150. }
  151. };
  152. this.model.setChildren(node, children, Object.assign(Object.assign({}, options), { onDidCreateNode, onDidDeleteNode }));
  153. }
  154. has(element) {
  155. return this.nodes.has(element);
  156. }
  157. getListIndex(location) {
  158. const node = this.getCompressedNode(location);
  159. return this.model.getListIndex(node);
  160. }
  161. getListRenderCount(location) {
  162. const node = this.getCompressedNode(location);
  163. return this.model.getListRenderCount(node);
  164. }
  165. getNode(location) {
  166. if (typeof location === 'undefined') {
  167. return this.model.getNode();
  168. }
  169. const node = this.getCompressedNode(location);
  170. return this.model.getNode(node);
  171. }
  172. // TODO: review this
  173. getNodeLocation(node) {
  174. const compressedNode = this.model.getNodeLocation(node);
  175. if (compressedNode === null) {
  176. return null;
  177. }
  178. return compressedNode.elements[compressedNode.elements.length - 1];
  179. }
  180. // TODO: review this
  181. getParentNodeLocation(location) {
  182. const compressedNode = this.getCompressedNode(location);
  183. const parentNode = this.model.getParentNodeLocation(compressedNode);
  184. if (parentNode === null) {
  185. return null;
  186. }
  187. return parentNode.elements[parentNode.elements.length - 1];
  188. }
  189. getFirstElementChild(location) {
  190. const compressedNode = this.getCompressedNode(location);
  191. return this.model.getFirstElementChild(compressedNode);
  192. }
  193. isCollapsible(location) {
  194. const compressedNode = this.getCompressedNode(location);
  195. return this.model.isCollapsible(compressedNode);
  196. }
  197. setCollapsible(location, collapsible) {
  198. const compressedNode = this.getCompressedNode(location);
  199. return this.model.setCollapsible(compressedNode, collapsible);
  200. }
  201. isCollapsed(location) {
  202. const compressedNode = this.getCompressedNode(location);
  203. return this.model.isCollapsed(compressedNode);
  204. }
  205. setCollapsed(location, collapsed, recursive) {
  206. const compressedNode = this.getCompressedNode(location);
  207. return this.model.setCollapsed(compressedNode, collapsed, recursive);
  208. }
  209. expandTo(location) {
  210. const compressedNode = this.getCompressedNode(location);
  211. this.model.expandTo(compressedNode);
  212. }
  213. rerender(location) {
  214. const compressedNode = this.getCompressedNode(location);
  215. this.model.rerender(compressedNode);
  216. }
  217. refilter() {
  218. this.model.refilter();
  219. }
  220. getCompressedNode(element) {
  221. if (element === null) {
  222. return null;
  223. }
  224. const node = this.nodes.get(element);
  225. if (!node) {
  226. throw new TreeError(this.user, `Tree element not found: ${element}`);
  227. }
  228. return node;
  229. }
  230. }
  231. export const DefaultElementMapper = elements => elements[elements.length - 1];
  232. class CompressedTreeNodeWrapper {
  233. constructor(unwrapper, node) {
  234. this.unwrapper = unwrapper;
  235. this.node = node;
  236. }
  237. get element() { return this.node.element === null ? null : this.unwrapper(this.node.element); }
  238. get children() { return this.node.children.map(node => new CompressedTreeNodeWrapper(this.unwrapper, node)); }
  239. get depth() { return this.node.depth; }
  240. get visibleChildrenCount() { return this.node.visibleChildrenCount; }
  241. get visibleChildIndex() { return this.node.visibleChildIndex; }
  242. get collapsible() { return this.node.collapsible; }
  243. get collapsed() { return this.node.collapsed; }
  244. get visible() { return this.node.visible; }
  245. get filterData() { return this.node.filterData; }
  246. }
  247. function mapList(nodeMapper, list) {
  248. return {
  249. splice(start, deleteCount, toInsert) {
  250. list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)));
  251. },
  252. updateElementHeight(index, height) {
  253. list.updateElementHeight(index, height);
  254. }
  255. };
  256. }
  257. function mapOptions(compressedNodeUnwrapper, options) {
  258. return Object.assign(Object.assign({}, options), { identityProvider: options.identityProvider && {
  259. getId(node) {
  260. return options.identityProvider.getId(compressedNodeUnwrapper(node));
  261. }
  262. }, sorter: options.sorter && {
  263. compare(node, otherNode) {
  264. return options.sorter.compare(node.elements[0], otherNode.elements[0]);
  265. }
  266. }, filter: options.filter && {
  267. filter(node, parentVisibility) {
  268. return options.filter.filter(compressedNodeUnwrapper(node), parentVisibility);
  269. }
  270. } });
  271. }
  272. export class CompressibleObjectTreeModel {
  273. constructor(user, list, options = {}) {
  274. this.rootRef = null;
  275. this.elementMapper = options.elementMapper || DefaultElementMapper;
  276. const compressedNodeUnwrapper = node => this.elementMapper(node.elements);
  277. this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node));
  278. this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
  279. }
  280. get onDidSplice() {
  281. return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({
  282. insertedNodes: insertedNodes.map(node => this.nodeMapper.map(node)),
  283. deletedNodes: deletedNodes.map(node => this.nodeMapper.map(node)),
  284. }));
  285. }
  286. get onDidChangeCollapseState() {
  287. return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
  288. node: this.nodeMapper.map(node),
  289. deep
  290. }));
  291. }
  292. get onDidChangeRenderNodeCount() {
  293. return Event.map(this.model.onDidChangeRenderNodeCount, node => this.nodeMapper.map(node));
  294. }
  295. setChildren(element, children = Iterable.empty(), options = {}) {
  296. this.model.setChildren(element, children, options);
  297. }
  298. setCompressionEnabled(enabled) {
  299. this.model.setCompressionEnabled(enabled);
  300. }
  301. has(location) {
  302. return this.model.has(location);
  303. }
  304. getListIndex(location) {
  305. return this.model.getListIndex(location);
  306. }
  307. getListRenderCount(location) {
  308. return this.model.getListRenderCount(location);
  309. }
  310. getNode(location) {
  311. return this.nodeMapper.map(this.model.getNode(location));
  312. }
  313. getNodeLocation(node) {
  314. return node.element;
  315. }
  316. getParentNodeLocation(location) {
  317. return this.model.getParentNodeLocation(location);
  318. }
  319. getFirstElementChild(location) {
  320. const result = this.model.getFirstElementChild(location);
  321. if (result === null || typeof result === 'undefined') {
  322. return result;
  323. }
  324. return this.elementMapper(result.elements);
  325. }
  326. isCollapsible(location) {
  327. return this.model.isCollapsible(location);
  328. }
  329. setCollapsible(location, collapsed) {
  330. return this.model.setCollapsible(location, collapsed);
  331. }
  332. isCollapsed(location) {
  333. return this.model.isCollapsed(location);
  334. }
  335. setCollapsed(location, collapsed, recursive) {
  336. return this.model.setCollapsed(location, collapsed, recursive);
  337. }
  338. expandTo(location) {
  339. return this.model.expandTo(location);
  340. }
  341. rerender(location) {
  342. return this.model.rerender(location);
  343. }
  344. refilter() {
  345. return this.model.refilter();
  346. }
  347. getCompressedTreeNode(location = null) {
  348. return this.model.getNode(location);
  349. }
  350. }