@sussudio/base
Version:
Internal APIs for VS Code's utilities and user interface building blocks.
137 lines (136 loc) • 3.9 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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)),
};
}