UNPKG

@sussudio/base

Version:

Internal APIs for VS Code's utilities and user interface building blocks.

137 lines (136 loc) 3.9 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { range } from './arrays.mjs'; import { CancellationTokenSource } from './cancellation.mjs'; import { canceled } from './errors.mjs'; function createPage(elements) { return { isResolved: !!elements, promise: null, cts: null, promiseIndexes: new Set(), elements: elements || [], }; } export function singlePagePager(elements) { return { firstPage: elements, total: elements.length, pageSize: elements.length, getPage: (pageIndex, cancellationToken) => { return Promise.resolve(elements); }, }; } export class PagedModel { pager; pages = []; get length() { return this.pager.total; } constructor(arg) { this.pager = Array.isArray(arg) ? singlePagePager(arg) : arg; const totalPages = Math.ceil(this.pager.total / this.pager.pageSize); this.pages = [createPage(this.pager.firstPage.slice()), ...range(totalPages - 1).map(() => createPage())]; } isResolved(index) { const pageIndex = Math.floor(index / this.pager.pageSize); const page = this.pages[pageIndex]; return !!page.isResolved; } get(index) { const pageIndex = Math.floor(index / this.pager.pageSize); const indexInPage = index % this.pager.pageSize; const page = this.pages[pageIndex]; return page.elements[indexInPage]; } resolve(index, cancellationToken) { if (cancellationToken.isCancellationRequested) { return Promise.reject(canceled()); } const pageIndex = Math.floor(index / this.pager.pageSize); const indexInPage = index % this.pager.pageSize; const page = this.pages[pageIndex]; if (page.isResolved) { return Promise.resolve(page.elements[indexInPage]); } if (!page.promise) { page.cts = new CancellationTokenSource(); page.promise = this.pager.getPage(pageIndex, page.cts.token).then( (elements) => { page.elements = elements; page.isResolved = true; page.promise = null; page.cts = null; }, (err) => { page.isResolved = false; page.promise = null; page.cts = null; return Promise.reject(err); }, ); } cancellationToken.onCancellationRequested(() => { if (!page.cts) { return; } page.promiseIndexes.delete(index); if (page.promiseIndexes.size === 0) { page.cts.cancel(); } }); page.promiseIndexes.add(index); return page.promise.then(() => page.elements[indexInPage]); } } export class DelayedPagedModel { model; timeout; get length() { return this.model.length; } constructor(model, timeout = 500) { this.model = model; this.timeout = timeout; } isResolved(index) { return this.model.isResolved(index); } get(index) { return this.model.get(index); } resolve(index, cancellationToken) { return new Promise((c, e) => { if (cancellationToken.isCancellationRequested) { return e(canceled()); } const timer = setTimeout(() => { if (cancellationToken.isCancellationRequested) { return e(canceled()); } timeoutCancellation.dispose(); this.model.resolve(index, cancellationToken).then(c, e); }, this.timeout); const timeoutCancellation = cancellationToken.onCancellationRequested(() => { clearTimeout(timer); timeoutCancellation.dispose(); e(canceled()); }); }); } } /** * Similar to array.map, `mapPager` lets you map the elements of an * abstract paged collection to another type. */ export function mapPager(pager, fn) { return { firstPage: pager.firstPage.map(fn), total: pager.total, pageSize: pager.pageSize, getPage: (pageIndex, token) => pager.getPage(pageIndex, token).then((r) => r.map(fn)), }; }