observablelist.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ArrayExt,
  5. ArrayIterator,
  6. IIterator,
  7. IterableOrArrayLike,
  8. each,
  9. toArray
  10. } from '@lumino/algorithm';
  11. import { IDisposable } from '@lumino/disposable';
  12. import { ISignal, Signal } from '@lumino/signaling';
  13. /**
  14. * A list which can be observed for changes.
  15. */
  16. export interface IObservableList<T> extends IDisposable {
  17. /**
  18. * A signal emitted when the list has changed.
  19. */
  20. readonly changed: ISignal<this, IObservableList.IChangedArgs<T>>;
  21. /**
  22. * The type of this object.
  23. */
  24. readonly type: 'List';
  25. /**
  26. * The length of the list.
  27. *
  28. * #### Notes
  29. * This is a read-only property.
  30. */
  31. length: number;
  32. /**
  33. * Create an iterator over the values in the list.
  34. *
  35. * @returns A new iterator starting at the front of the list.
  36. *
  37. * #### Complexity
  38. * Constant.
  39. *
  40. * #### Iterator Validity
  41. * No changes.
  42. */
  43. iter(): IIterator<T>;
  44. /**
  45. * Remove all values from the list.
  46. *
  47. * #### Complexity
  48. * Linear.
  49. *
  50. * #### Iterator Validity
  51. * All current iterators are invalidated.
  52. */
  53. clear(): void;
  54. /**
  55. * Get the value at the specified index.
  56. *
  57. * @param index - The positive integer index of interest.
  58. *
  59. * @returns The value at the specified index.
  60. *
  61. * #### Undefined Behavior
  62. * An `index` which is non-integral or out of range.
  63. */
  64. get(index: number): T;
  65. /**
  66. * Insert a value into the list at a specific index.
  67. *
  68. * @param index - The index at which to insert the value.
  69. *
  70. * @param value - The value to set at the specified index.
  71. *
  72. * #### Complexity
  73. * Linear.
  74. *
  75. * #### Iterator Validity
  76. * No changes.
  77. *
  78. * #### Notes
  79. * The `index` will be clamped to the bounds of the list.
  80. *
  81. * #### Undefined Behavior
  82. * An `index` which is non-integral.
  83. */
  84. insert(index: number, value: T): void;
  85. /**
  86. * Insert a set of items into the list at the specified index.
  87. *
  88. * @param index - The index at which to insert the values.
  89. *
  90. * @param values - The values to insert at the specified index.
  91. *
  92. * #### Complexity.
  93. * Linear.
  94. *
  95. * #### Iterator Validity
  96. * No changes.
  97. *
  98. * #### Notes
  99. * The `index` will be clamped to the bounds of the list.
  100. *
  101. * #### Undefined Behavior.
  102. * An `index` which is non-integral.
  103. */
  104. insertAll(index: number, values: IterableOrArrayLike<T>): void;
  105. /**
  106. * Move a value from one index to another.
  107. *
  108. * @parm fromIndex - The index of the element to move.
  109. *
  110. * @param toIndex - The index to move the element to.
  111. *
  112. * #### Complexity
  113. * Constant.
  114. *
  115. * #### Iterator Validity
  116. * Iterators pointing at the lesser of the `fromIndex` and the `toIndex`
  117. * and beyond are invalidated.
  118. *
  119. * #### Undefined Behavior
  120. * A `fromIndex` or a `toIndex` which is non-integral.
  121. */
  122. move(fromIndex: number, toIndex: number): void;
  123. /**
  124. * Add a value to the back of the list.
  125. *
  126. * @param value - The value to add to the back of the list.
  127. *
  128. * @returns The new length of the list.
  129. *
  130. * #### Complexity
  131. * Constant.
  132. *
  133. * #### Iterator Validity
  134. * No changes.
  135. */
  136. push(value: T): number;
  137. /**
  138. * Push a set of values to the back of the list.
  139. *
  140. * @param values - An iterable or array-like set of values to add.
  141. *
  142. * @returns The new length of the list.
  143. *
  144. * #### Complexity
  145. * Linear.
  146. *
  147. * #### Iterator Validity
  148. * No changes.
  149. */
  150. pushAll(values: IterableOrArrayLike<T>): number;
  151. /**
  152. * Remove and return the value at a specific index.
  153. *
  154. * @param index - The index of the value of interest.
  155. *
  156. * @returns The value at the specified index, or `undefined` if the
  157. * index is out of range.
  158. *
  159. * #### Complexity
  160. * Constant.
  161. *
  162. * #### Iterator Validity
  163. * Iterators pointing at the removed value and beyond are invalidated.
  164. *
  165. * #### Undefined Behavior
  166. * An `index` which is non-integral.
  167. */
  168. remove(index: number): T | undefined;
  169. /**
  170. * Remove a range of items from the list.
  171. *
  172. * @param startIndex - The start index of the range to remove (inclusive).
  173. *
  174. * @param endIndex - The end index of the range to remove (exclusive).
  175. *
  176. * @returns The new length of the list.
  177. *
  178. * #### Complexity
  179. * Linear.
  180. *
  181. * #### Iterator Validity
  182. * Iterators pointing to the first removed value and beyond are invalid.
  183. *
  184. * #### Undefined Behavior
  185. * A `startIndex` or `endIndex` which is non-integral.
  186. */
  187. removeRange(startIndex: number, endIndex: number): number;
  188. /**
  189. * Remove the first occurrence of a value from the list.
  190. *
  191. * @param value - The value of interest.
  192. *
  193. * @returns The index of the removed value, or `-1` if the value
  194. * is not contained in the list.
  195. *
  196. * #### Complexity
  197. * Linear.
  198. *
  199. * #### Iterator Validity
  200. * Iterators pointing at the removed value and beyond are invalidated.
  201. */
  202. removeValue(value: T): number;
  203. /**
  204. * Set the value at the specified index.
  205. *
  206. * @param index - The positive integer index of interest.
  207. *
  208. * @param value - The value to set at the specified index.
  209. *
  210. * #### Complexity
  211. * Constant.
  212. *
  213. * #### Iterator Validity
  214. * No changes.
  215. *
  216. * #### Undefined Behavior
  217. * An `index` which is non-integral or out of range.
  218. */
  219. set(index: number, value: T): void;
  220. }
  221. /**
  222. * The namespace for IObservableList related interfaces.
  223. */
  224. export namespace IObservableList {
  225. /**
  226. * The change types which occur on an observable list.
  227. */
  228. export type ChangeType =
  229. /**
  230. * Item(s) were added to the list.
  231. */
  232. | 'add'
  233. /**
  234. * An item was moved within the list.
  235. */
  236. | 'move'
  237. /**
  238. * Item(s) were removed from the list.
  239. */
  240. | 'remove'
  241. /**
  242. * An item was set in the list.
  243. */
  244. | 'set';
  245. /**
  246. * The changed args object which is emitted by an observable list.
  247. */
  248. export interface IChangedArgs<T> {
  249. /**
  250. * The type of change undergone by the vector.
  251. */
  252. type: ChangeType;
  253. /**
  254. * The new index associated with the change.
  255. */
  256. newIndex: number;
  257. /**
  258. * The new values associated with the change.
  259. *
  260. * #### Notes
  261. * The values will be contiguous starting at the `newIndex`.
  262. */
  263. newValues: T[];
  264. /**
  265. * The old index associated with the change.
  266. */
  267. oldIndex: number;
  268. /**
  269. * The old values associated with the change.
  270. *
  271. * #### Notes
  272. * The values will be contiguous starting at the `oldIndex`.
  273. */
  274. oldValues: T[];
  275. }
  276. }
  277. /**
  278. * A concrete implementation of [[IObservableList]].
  279. */
  280. export class ObservableList<T> implements IObservableList<T> {
  281. /**
  282. * Construct a new observable map.
  283. */
  284. constructor(options: ObservableList.IOptions<T> = {}) {
  285. if (options.values !== void 0) {
  286. each(options.values, value => {
  287. this._array.push(value);
  288. });
  289. }
  290. this._itemCmp = options.itemCmp || Private.itemCmp;
  291. }
  292. /**
  293. * The type of this object.
  294. */
  295. get type(): 'List' {
  296. return 'List';
  297. }
  298. /**
  299. * A signal emitted when the list has changed.
  300. */
  301. get changed(): ISignal<this, IObservableList.IChangedArgs<T>> {
  302. return this._changed;
  303. }
  304. /**
  305. * The length of the list.
  306. */
  307. get length(): number {
  308. return this._array.length;
  309. }
  310. /**
  311. * Test whether the list has been disposed.
  312. */
  313. get isDisposed(): boolean {
  314. return this._isDisposed;
  315. }
  316. /**
  317. * Dispose of the resources held by the list.
  318. */
  319. dispose(): void {
  320. if (this._isDisposed) {
  321. return;
  322. }
  323. this._isDisposed = true;
  324. Signal.clearData(this);
  325. this.clear();
  326. }
  327. /**
  328. * Create an iterator over the values in the list.
  329. *
  330. * @returns A new iterator starting at the front of the list.
  331. *
  332. * #### Complexity
  333. * Constant.
  334. *
  335. * #### Iterator Validity
  336. * No changes.
  337. */
  338. iter(): IIterator<T> {
  339. return new ArrayIterator(this._array);
  340. }
  341. /**
  342. * Get the value at the specified index.
  343. *
  344. * @param index - The positive integer index of interest.
  345. *
  346. * @returns The value at the specified index.
  347. *
  348. * #### Undefined Behavior
  349. * An `index` which is non-integral or out of range.
  350. */
  351. get(index: number): T {
  352. return this._array[index];
  353. }
  354. /**
  355. * Set the value at the specified index.
  356. *
  357. * @param index - The positive integer index of interest.
  358. *
  359. * @param value - The value to set at the specified index.
  360. *
  361. * #### Complexity
  362. * Constant.
  363. *
  364. * #### Iterator Validity
  365. * No changes.
  366. *
  367. * #### Undefined Behavior
  368. * An `index` which is non-integral or out of range.
  369. */
  370. set(index: number, value: T): void {
  371. let oldValue = this._array[index];
  372. if (value === undefined) {
  373. throw new Error('Cannot set an undefined item');
  374. }
  375. // Bail if the value does not change.
  376. let itemCmp = this._itemCmp;
  377. if (itemCmp(oldValue, value)) {
  378. return;
  379. }
  380. this._array[index] = value;
  381. this._changed.emit({
  382. type: 'set',
  383. oldIndex: index,
  384. newIndex: index,
  385. oldValues: [oldValue],
  386. newValues: [value]
  387. });
  388. }
  389. /**
  390. * Add a value to the end of the list.
  391. *
  392. * @param value - The value to add to the end of the list.
  393. *
  394. * @returns The new length of the list.
  395. *
  396. * #### Complexity
  397. * Constant.
  398. *
  399. * #### Iterator Validity
  400. * No changes.
  401. */
  402. push(value: T): number {
  403. let num = this._array.push(value);
  404. this._changed.emit({
  405. type: 'add',
  406. oldIndex: -1,
  407. newIndex: this.length - 1,
  408. oldValues: [],
  409. newValues: [value]
  410. });
  411. return num;
  412. }
  413. /**
  414. * Insert a value into the list at a specific index.
  415. *
  416. * @param index - The index at which to insert the value.
  417. *
  418. * @param value - The value to set at the specified index.
  419. *
  420. * #### Complexity
  421. * Linear.
  422. *
  423. * #### Iterator Validity
  424. * No changes.
  425. *
  426. * #### Notes
  427. * The `index` will be clamped to the bounds of the list.
  428. *
  429. * #### Undefined Behavior
  430. * An `index` which is non-integral.
  431. */
  432. insert(index: number, value: T): void {
  433. ArrayExt.insert(this._array, index, value);
  434. this._changed.emit({
  435. type: 'add',
  436. oldIndex: -1,
  437. newIndex: index,
  438. oldValues: [],
  439. newValues: [value]
  440. });
  441. }
  442. /**
  443. * Remove the first occurrence of a value from the list.
  444. *
  445. * @param value - The value of interest.
  446. *
  447. * @returns The index of the removed value, or `-1` if the value
  448. * is not contained in the list.
  449. *
  450. * #### Complexity
  451. * Linear.
  452. *
  453. * #### Iterator Validity
  454. * Iterators pointing at the removed value and beyond are invalidated.
  455. */
  456. removeValue(value: T): number {
  457. let itemCmp = this._itemCmp;
  458. let index = ArrayExt.findFirstIndex(this._array, item => {
  459. return itemCmp(item, value);
  460. });
  461. this.remove(index);
  462. return index;
  463. }
  464. /**
  465. * Remove and return the value at a specific index.
  466. *
  467. * @param index - The index of the value of interest.
  468. *
  469. * @returns The value at the specified index, or `undefined` if the
  470. * index is out of range.
  471. *
  472. * #### Complexity
  473. * Constant.
  474. *
  475. * #### Iterator Validity
  476. * Iterators pointing at the removed value and beyond are invalidated.
  477. *
  478. * #### Undefined Behavior
  479. * An `index` which is non-integral.
  480. */
  481. remove(index: number): T | undefined {
  482. let value = ArrayExt.removeAt(this._array, index);
  483. if (value === undefined) {
  484. return;
  485. }
  486. this._changed.emit({
  487. type: 'remove',
  488. oldIndex: index,
  489. newIndex: -1,
  490. newValues: [],
  491. oldValues: [value]
  492. });
  493. return value;
  494. }
  495. /**
  496. * Remove all values from the list.
  497. *
  498. * #### Complexity
  499. * Linear.
  500. *
  501. * #### Iterator Validity
  502. * All current iterators are invalidated.
  503. */
  504. clear(): void {
  505. let copy = this._array.slice();
  506. this._array.length = 0;
  507. this._changed.emit({
  508. type: 'remove',
  509. oldIndex: 0,
  510. newIndex: 0,
  511. newValues: [],
  512. oldValues: copy
  513. });
  514. }
  515. /**
  516. * Move a value from one index to another.
  517. *
  518. * @parm fromIndex - The index of the element to move.
  519. *
  520. * @param toIndex - The index to move the element to.
  521. *
  522. * #### Complexity
  523. * Constant.
  524. *
  525. * #### Iterator Validity
  526. * Iterators pointing at the lesser of the `fromIndex` and the `toIndex`
  527. * and beyond are invalidated.
  528. *
  529. * #### Undefined Behavior
  530. * A `fromIndex` or a `toIndex` which is non-integral.
  531. */
  532. move(fromIndex: number, toIndex: number): void {
  533. if (this.length <= 1 || fromIndex === toIndex) {
  534. return;
  535. }
  536. let values = [this._array[fromIndex]];
  537. ArrayExt.move(this._array, fromIndex, toIndex);
  538. this._changed.emit({
  539. type: 'move',
  540. oldIndex: fromIndex,
  541. newIndex: toIndex,
  542. oldValues: values,
  543. newValues: values
  544. });
  545. }
  546. /**
  547. * Push a set of values to the back of the list.
  548. *
  549. * @param values - An iterable or array-like set of values to add.
  550. *
  551. * @returns The new length of the list.
  552. *
  553. * #### Complexity
  554. * Linear.
  555. *
  556. * #### Iterator Validity
  557. * No changes.
  558. */
  559. pushAll(values: IterableOrArrayLike<T>): number {
  560. let newIndex = this.length;
  561. each(values, value => {
  562. this._array.push(value);
  563. });
  564. this._changed.emit({
  565. type: 'add',
  566. oldIndex: -1,
  567. newIndex,
  568. oldValues: [],
  569. newValues: toArray(values)
  570. });
  571. return this.length;
  572. }
  573. /**
  574. * Insert a set of items into the list at the specified index.
  575. *
  576. * @param index - The index at which to insert the values.
  577. *
  578. * @param values - The values to insert at the specified index.
  579. *
  580. * #### Complexity.
  581. * Linear.
  582. *
  583. * #### Iterator Validity
  584. * No changes.
  585. *
  586. * #### Notes
  587. * The `index` will be clamped to the bounds of the list.
  588. *
  589. * #### Undefined Behavior.
  590. * An `index` which is non-integral.
  591. */
  592. insertAll(index: number, values: IterableOrArrayLike<T>): void {
  593. let newIndex = index;
  594. each(values, value => {
  595. ArrayExt.insert(this._array, index++, value);
  596. });
  597. this._changed.emit({
  598. type: 'add',
  599. oldIndex: -1,
  600. newIndex,
  601. oldValues: [],
  602. newValues: toArray(values)
  603. });
  604. }
  605. /**
  606. * Remove a range of items from the list.
  607. *
  608. * @param startIndex - The start index of the range to remove (inclusive).
  609. *
  610. * @param endIndex - The end index of the range to remove (exclusive).
  611. *
  612. * @returns The new length of the list.
  613. *
  614. * #### Complexity
  615. * Linear.
  616. *
  617. * #### Iterator Validity
  618. * Iterators pointing to the first removed value and beyond are invalid.
  619. *
  620. * #### Undefined Behavior
  621. * A `startIndex` or `endIndex` which is non-integral.
  622. */
  623. removeRange(startIndex: number, endIndex: number): number {
  624. let oldValues = this._array.slice(startIndex, endIndex);
  625. for (let i = startIndex; i < endIndex; i++) {
  626. ArrayExt.removeAt(this._array, startIndex);
  627. }
  628. this._changed.emit({
  629. type: 'remove',
  630. oldIndex: startIndex,
  631. newIndex: -1,
  632. oldValues,
  633. newValues: []
  634. });
  635. return this.length;
  636. }
  637. private _array: Array<T> = [];
  638. private _isDisposed = false;
  639. private _itemCmp: (first: T, second: T) => boolean;
  640. private _changed = new Signal<this, IObservableList.IChangedArgs<T>>(this);
  641. }
  642. /**
  643. * The namespace for `ObservableList` class statics.
  644. */
  645. export namespace ObservableList {
  646. /**
  647. * The options used to initialize an observable map.
  648. */
  649. export interface IOptions<T> {
  650. /**
  651. * An optional initial set of values.
  652. */
  653. values?: IterableOrArrayLike<T>;
  654. /**
  655. * The item comparison function for change detection on `set`.
  656. *
  657. * If not given, strict `===` equality will be used.
  658. */
  659. itemCmp?: (first: T, second: T) => boolean;
  660. }
  661. }
  662. /**
  663. * The namespace for module private data.
  664. */
  665. namespace Private {
  666. /**
  667. * The default strict equality item cmp.
  668. */
  669. export function itemCmp(first: any, second: any): boolean {
  670. return first === second;
  671. }
  672. }