Browse Source

Removes namespaces for better tree-shaking

main
Eric Amodio 4 years ago
parent
commit
b6503e2b0c
15 changed files with 1050 additions and 1092 deletions
  1. +8
    -8
      src/system.ts
  2. +161
    -163
      src/system/array.ts
  3. +9
    -11
      src/system/date.ts
  4. +2
    -2
      src/system/decorators/gate.ts
  5. +9
    -11
      src/system/decorators/log.ts
  6. +3
    -3
      src/system/decorators/timeout.ts
  7. +168
    -174
      src/system/function.ts
  8. +113
    -120
      src/system/iterable.ts
  9. +63
    -65
      src/system/object.ts
  10. +129
    -137
      src/system/promise.ts
  11. +6
    -6
      src/system/searchTree.ts
  12. +331
    -341
      src/system/string.ts
  13. +39
    -46
      src/system/version.ts
  14. +7
    -3
      src/trackers/documentTracker.ts
  15. +2
    -2
      src/trackers/lineTracker.ts

+ 8
- 8
src/system.ts View File

@ -18,16 +18,16 @@ declare global {
export type NarrowRepos<T extends { repos?: unknown }> = ExcludeSome<T, 'repos', string | string[] | undefined>;
}
export * from './system/array';
export * from './system/date';
export * as Arrays from './system/array';
export * as Dates from './system/date';
export * from './system/decorators/gate';
export * from './system/decorators/log';
export * from './system/decorators/memoize';
export * from './system/decorators/timeout';
export * from './system/function';
export * from './system/iterable';
export * from './system/object';
export * from './system/promise';
export * as Functions from './system/function';
export * as Iterables from './system/iterable';
export * as Objects from './system/object';
export * as Promises from './system/promise';
export * from './system/searchTree';
export * from './system/string';
export * from './system/version';
export * as Strings from './system/string';
export * as Versions from './system/version';

+ 161
- 163
src/system/array.ts View File

@ -6,195 +6,193 @@ import {
xor as _xor,
} from 'lodash-es';
export namespace Arrays {
export function countUniques<T>(source: T[], accessor: (item: T) => string): Record<string, number> {
const uniqueCounts = Object.create(null) as Record<string, number>;
for (const item of source) {
const value = accessor(item);
uniqueCounts[value] = (uniqueCounts[value] ?? 0) + 1;
}
return uniqueCounts;
export function countUniques<T>(source: T[], accessor: (item: T) => string): Record<string, number> {
const uniqueCounts = Object.create(null) as Record<string, number>;
for (const item of source) {
const value = accessor(item);
uniqueCounts[value] = (uniqueCounts[value] ?? 0) + 1;
}
return uniqueCounts;
}
export function filterMap<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => TMapped | null | undefined,
): TMapped[] {
let index = 0;
return source.reduce((accumulator, current) => {
const mapped = predicateMapper(current, index++);
if (mapped != null) {
accumulator.push(mapped);
}
return accumulator;
}, [] as TMapped[]);
}
export function filterMap<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => TMapped | null | undefined,
): TMapped[] {
let index = 0;
return source.reduce((accumulator, current) => {
const mapped = predicateMapper(current, index++);
if (mapped != null) {
accumulator.push(mapped);
}
return accumulator;
}, [] as TMapped[]);
}
export function filterMapAsync<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => Promise<TMapped | null | undefined>,
): Promise<TMapped[]> {
let index = 0;
return source.reduce(async (accumulator, current) => {
const mapped = await predicateMapper(current, index++);
if (mapped != null) {
accumulator.push(mapped);
}
return accumulator;
}, [] as any);
}
export function filterMapAsync<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => Promise<TMapped | null | undefined>,
): Promise<TMapped[]> {
let index = 0;
return source.reduce(async (accumulator, current) => {
const mapped = await predicateMapper(current, index++);
if (mapped != null) {
accumulator.push(mapped);
}
return accumulator;
}, [] as any);
}
export const findLastIndex = _findLastIndex;
export const findLastIndex = _findLastIndex;
export function groupBy<T>(source: T[], accessor: (item: T) => string): Record<string, T[]> {
return source.reduce((groupings, current) => {
const value = accessor(current);
groupings[value] = groupings[value] ?? [];
groupings[value].push(current);
return groupings;
}, Object.create(null) as Record<string, T[]>);
}
export function groupBy<T>(source: T[], accessor: (item: T) => string): Record<string, T[]> {
return source.reduce((groupings, current) => {
const value = accessor(current);
groupings[value] = groupings[value] ?? [];
groupings[value].push(current);
return groupings;
}, Object.create(null) as Record<string, T[]>);
}
export function groupByMap<TKey, TValue>(source: TValue[], accessor: (item: TValue) => TKey): Map<TKey, TValue[]> {
return source.reduce((groupings, current) => {
export function groupByMap<TKey, TValue>(source: TValue[], accessor: (item: TValue) => TKey): Map<TKey, TValue[]> {
return source.reduce((groupings, current) => {
const value = accessor(current);
const group = groupings.get(value) ?? [];
groupings.set(value, group);
group.push(current);
return groupings;
}, new Map<TKey, TValue[]>());
}
export function groupByFilterMap<TKey, TValue, TMapped>(
source: TValue[],
accessor: (item: TValue) => TKey,
predicateMapper: (item: TValue) => TMapped | null | undefined,
): Map<TKey, TMapped[]> {
return source.reduce((groupings, current) => {
const mapped = predicateMapper(current);
if (mapped != null) {
const value = accessor(current);
const group = groupings.get(value) ?? [];
groupings.set(value, group);
group.push(current);
return groupings;
}, new Map<TKey, TValue[]>());
}
group.push(mapped);
}
return groupings;
}, new Map<TKey, TMapped[]>());
}
export function groupByFilterMap<TKey, TValue, TMapped>(
source: TValue[],
accessor: (item: TValue) => TKey,
predicateMapper: (item: TValue) => TMapped | null | undefined,
): Map<TKey, TMapped[]> {
return source.reduce((groupings, current) => {
const mapped = predicateMapper(current);
if (mapped != null) {
const value = accessor(current);
const group = groupings.get(value) ?? [];
groupings.set(value, group);
group.push(mapped);
}
return groupings;
}, new Map<TKey, TMapped[]>());
}
export const intersection = _intersectionWith;
export const isEqual = _isEqual;
export const intersection = _intersectionWith;
export const isEqual = _isEqual;
export function areEquivalent<T>(value: T[], other: T[]) {
return _xor(value, other).length === 0;
}
export function areEquivalent<T>(value: T[], other: T[]) {
return _xor(value, other).length === 0;
}
export function isStringArray<T extends any[]>(array: string[] | T): array is string[] {
return typeof array[0] === 'string';
}
export function isStringArray<T extends any[]>(array: string[] | T): array is string[] {
return typeof array[0] === 'string';
}
export interface HierarchicalItem<T> {
name: string;
relativePath: string;
value?: T;
export interface HierarchicalItem<T> {
name: string;
relativePath: string;
value?: T;
parent?: HierarchicalItem<T>;
children: Map<string, HierarchicalItem<T>> | undefined;
descendants: T[] | undefined;
}
parent?: HierarchicalItem<T>;
children: Map<string, HierarchicalItem<T>> | undefined;
descendants: T[] | undefined;
}
export function makeHierarchical<T>(
values: T[],
splitPath: (i: T) => string[],
joinPath: (...paths: string[]) => string,
compact: boolean = false,
canCompact?: (i: T) => boolean,
): HierarchicalItem<T> {
const seed = {
name: '',
relativePath: '',
children: new Map(),
descendants: [],
};
let hierarchy = values.reduce((root: HierarchicalItem<T>, value) => {
let folder = root;
let relativePath = '';
for (const folderName of splitPath(value)) {
relativePath = joinPath(relativePath, folderName);
if (folder.children === undefined) {
folder.children = new Map();
}
export function makeHierarchical<T>(
values: T[],
splitPath: (i: T) => string[],
joinPath: (...paths: string[]) => string,
compact: boolean = false,
canCompact?: (i: T) => boolean,
): HierarchicalItem<T> {
const seed = {
name: '',
relativePath: '',
children: new Map(),
descendants: [],
};
let hierarchy = values.reduce((root: HierarchicalItem<T>, value) => {
let folder = root;
let relativePath = '';
for (const folderName of splitPath(value)) {
relativePath = joinPath(relativePath, folderName);
if (folder.children === undefined) {
folder.children = new Map();
}
let f = folder.children.get(folderName);
if (f === undefined) {
f = {
name: folderName,
relativePath: relativePath,
parent: folder,
children: undefined,
descendants: undefined,
};
folder.children.set(folderName, f);
}
if (folder.descendants === undefined) {
folder.descendants = [];
}
folder.descendants.push(value);
folder = f;
let f = folder.children.get(folderName);
if (f === undefined) {
f = {
name: folderName,
relativePath: relativePath,
parent: folder,
children: undefined,
descendants: undefined,
};
folder.children.set(folderName, f);
}
folder.value = value;
if (folder.descendants === undefined) {
folder.descendants = [];
}
folder.descendants.push(value);
folder = f;
}
return root;
}, seed);
folder.value = value;
if (compact) {
hierarchy = compactHierarchy(hierarchy, joinPath, true, canCompact);
}
return root;
}, seed);
return hierarchy;
if (compact) {
hierarchy = compactHierarchy(hierarchy, joinPath, true, canCompact);
}
export function compactHierarchy<T>(
root: HierarchicalItem<T>,
joinPath: (...paths: string[]) => string,
isRoot: boolean = true,
canCompact?: (i: T) => boolean,
): HierarchicalItem<T> {
if (root.children === undefined) return root;
const children = [...root.children.values()];
for (const child of children) {
compactHierarchy(child, joinPath, false, canCompact);
}
return hierarchy;
}
if (!isRoot && children.length === 1) {
const child = children[0];
if (child.value === undefined || canCompact?.(child.value)) {
root.name = joinPath(root.name, child.name);
root.relativePath = child.relativePath;
root.children = child.children;
root.descendants = child.descendants;
root.value = child.value;
}
}
export function compactHierarchy<T>(
root: HierarchicalItem<T>,
joinPath: (...paths: string[]) => string,
isRoot: boolean = true,
canCompact?: (i: T) => boolean,
): HierarchicalItem<T> {
if (root.children === undefined) return root;
const children = [...root.children.values()];
for (const child of children) {
compactHierarchy(child, joinPath, false, canCompact);
}
return root;
if (!isRoot && children.length === 1) {
const child = children[0];
if (child.value === undefined || canCompact?.(child.value)) {
root.name = joinPath(root.name, child.name);
root.relativePath = child.relativePath;
root.children = child.children;
root.descendants = child.descendants;
root.value = child.value;
}
}
export function uniqueBy<T>(source: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] {
const uniqueValues = Object.create(null) as Record<string, any>;
return source.filter(item => {
const value = accessor(item);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (uniqueValues[value]) return false;
return root;
}
uniqueValues[value] = accessor;
return predicate?.(item) ?? true;
});
}
export function uniqueBy<T>(source: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] {
const uniqueValues = Object.create(null) as Record<string, any>;
return source.filter(item => {
const value = accessor(item);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (uniqueValues[value]) return false;
uniqueValues[value] = accessor;
return predicate?.(item) ?? true;
});
}

+ 9
- 11
src/system/date.ts View File

@ -6,17 +6,15 @@ import * as relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(advancedFormat);
dayjs.extend(relativeTime);
export namespace Dates {
export const MillisecondsPerMinute = 60000; // 60 * 1000
export const MillisecondsPerHour = 3600000; // 60 * 60 * 1000
export const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000
export const MillisecondsPerMinute = 60000; // 60 * 1000
export const MillisecondsPerHour = 3600000; // 60 * 60 * 1000
export const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000
export interface DateFormatter {
fromNow(): string;
format(format: string): string;
}
export interface DateFormatter {
fromNow(): string;
format(format: string): string;
}
export function getFormatter(date: Date): DateFormatter {
return dayjs(date);
}
export function getFormatter(date: Date): DateFormatter {
return dayjs(date);
}

+ 2
- 2
src/system/decorators/gate.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Promises } from '../promise';
import { is as isPromise } from '../promise';
const emptyStr = '';
@ -48,7 +48,7 @@ export function gate any>(resolver?: (...args: Parame
let result;
try {
result = fn!.apply(this, args);
if (result == null || !Promises.is(result)) {
if (result == null || !isPromise(result)) {
return result;
}

+ 9
- 11
src/system/decorators/log.ts View File

@ -1,9 +1,9 @@
'use strict';
import { filterMap } from '../array';
import { LogCorrelationContext, Logger, TraceLevel } from '../../logger';
import { Functions } from '../function';
import { Promises } from '../promise';
import { Strings } from '../string';
import { Arrays } from '../array';
import { getParameters } from '../function';
import { is as isPromise } from '../promise';
import { getDurationMilliseconds } from '../string';
const emptyStr = '';
@ -99,7 +99,7 @@ export function log any>(
}
if (fn == null || fnKey == null) throw new Error('Not supported');
const parameters = Functions.getParameters(fn);
const parameters = getParameters(fn);
descriptor[fnKey] = function (this: any, ...args: Parameters<T>) {
const correlationId = getNextCorrelationId();
@ -165,7 +165,7 @@ export function log any>(
const argFns = typeof options.args === 'object' ? options.args : undefined;
let argFn;
let loggable;
loggableParams = Arrays.filterMap(args, (v: any, index: number) => {
loggableParams = filterMap(args, (v: any, index: number) => {
const p = parameters[index];
argFn = argFns !== undefined ? argFns[index] : undefined;
@ -192,8 +192,7 @@ export function log any>(
const start = options.timed ? process.hrtime() : undefined;
const logError = (ex: Error) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : emptyStr;
const timing = start !== undefined ? ` \u2022 ${getDurationMilliseconds(start)} ms` : emptyStr;
if (options.singleLine) {
Logger.error(
ex,
@ -227,8 +226,7 @@ export function log any>(
}
const logResult = (r: any) => {
const timing =
start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : emptyStr;
const timing = start !== undefined ? ` \u2022 ${getDurationMilliseconds(start)} ms` : emptyStr;
let exit;
if (options.exit != null) {
try {
@ -269,7 +267,7 @@ export function log any>(
}
};
if (result != null && Promises.is(result)) {
if (result != null && isPromise(result)) {
const promise = result.then(logResult);
promise.catch(logError);
} else {

+ 3
- 3
src/system/decorators/timeout.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Promises } from '../promise';
import { CancellationError, is as isPromise } from '../promise';
export function timeout(timeout: number): any;
export function timeout(timeoutFromLastArg: true, defaultTimeout?: number): any;
@ -30,7 +30,7 @@ export function timeout(timeoutOrTimeoutFromLastArg: number | boolean, defaultTi
}
const result = fn?.apply(this, args);
if (timeout == null || timeout < 1 || !Promises.is(result)) return result;
if (timeout == null || timeout < 1 || !isPromise(result)) return result;
// const cc = Logger.getCorrelationContext();
@ -48,7 +48,7 @@ export function timeout(timeoutOrTimeoutFromLastArg: number | boolean, defaultTi
new Promise((_, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject(new Promises.CancellationError(result, `Timed out after ${timeout} ms`));
reject(new CancellationError(result, `Timed out after ${timeout} ms`));
}, timeout!);
}),
]);

+ 168
- 174
src/system/function.ts View File

@ -14,209 +14,203 @@ interface PropOfValue {
value: string | undefined;
}
export namespace Functions {
export function cachedOnce<T>(fn: (...args: any[]) => Promise<T>, seed: T): (...args: any[]) => Promise<T> {
let cached: T | undefined = seed;
return (...args: any[]) => {
if (cached !== undefined) {
const promise = Promise.resolve(cached);
cached = undefined;
return promise;
}
return fn(...args);
};
}
export function cachedOnce<T>(fn: (...args: any[]) => Promise<T>, seed: T): (...args: any[]) => Promise<T> {
let cached: T | undefined = seed;
return (...args: any[]) => {
if (cached !== undefined) {
const promise = Promise.resolve(cached);
cached = undefined;
return promise;
}
return fn(...args);
};
}
export interface DebounceOptions {
leading?: boolean;
maxWait?: number;
track?: boolean;
trailing?: boolean;
}
export interface DebounceOptions {
leading?: boolean;
maxWait?: number;
track?: boolean;
trailing?: boolean;
}
export function debounce<T extends (...args: any[]) => any>(
fn: T,
wait?: number,
options?: DebounceOptions,
): Deferrable<T> {
const { track, ...opts }: DebounceOptions = {
track: false,
...(options ?? {}),
};
if (track !== true) return _debounce(fn, wait, opts);
let pending = false;
const debounced = _debounce(
(function (this: any, ...args: any[]) {
pending = false;
return fn.apply(this, args);
} as any) as T,
wait,
options,
);
export function debounce<T extends (...args: any[]) => any>(
fn: T,
wait?: number,
options?: DebounceOptions,
): Deferrable<T> {
const { track, ...opts }: DebounceOptions = {
track: false,
...(options ?? {}),
};
if (track !== true) return _debounce(fn, wait, opts);
let pending = false;
const debounced = _debounce(
(function (this: any, ...args: any[]) {
pending = false;
return fn.apply(this, args);
} as any) as T,
wait,
options,
);
const tracked: Deferrable<T> = function (this: any, ...args: Parameters<T>) {
pending = true;
return debounced.apply(this, args);
} as any;
tracked.pending = function () {
return pending;
};
tracked.cancel = function () {
return debounced.cancel.apply(debounced);
};
tracked.flush = function () {
return debounced.flush.apply(debounced);
};
return tracked;
}
const tracked: Deferrable<T> = function (this: any, ...args: Parameters<T>) {
pending = true;
return debounced.apply(this, args);
} as any;
tracked.pending = function () {
return pending;
};
tracked.cancel = function () {
return debounced.cancel.apply(debounced);
};
tracked.flush = function () {
return debounced.flush.apply(debounced);
};
return tracked;
}
// export function debounceMemoized<T extends (...args: any[]) => any>(
// fn: T,
// wait?: number,
// options?: DebounceOptions & { resolver?(...args: any[]): any }
// ): T {
// const { resolver, ...opts } = options || ({} as DebounceOptions & { resolver?: T });
// const memo = _memoize(() => {
// return debounce(fn, wait, opts);
// }, resolver);
// export function debounceMemoized<T extends (...args: any[]) => any>(
// fn: T,
// wait?: number,
// options?: DebounceOptions & { resolver?(...args: any[]): any }
// ): T {
// const { resolver, ...opts } = options || ({} as DebounceOptions & { resolver?: T });
// return function(this: any, ...args: []) {
// return memo.apply(this, args).apply(this, args);
// } as T;
// }
// const memo = _memoize(() => {
// return debounce(fn, wait, opts);
// }, resolver);
const comma = ',';
const emptyStr = '';
const equals = '=';
const openBrace = '{';
const openParen = '(';
const closeParen = ')';
// return function(this: any, ...args: []) {
// return memo.apply(this, args).apply(this, args);
// } as T;
// }
const fnBodyRegex = /\(([\s\S]*)\)/;
const fnBodyStripCommentsRegex = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm;
const fnBodyStripParamDefaultValueRegex = /\s?=.*$/;
const comma = ',';
const emptyStr = '';
const equals = '=';
const openBrace = '{';
const openParen = '(';
const closeParen = ')';
export function getParameters(fn: Function): string[] {
if (typeof fn !== 'function') throw new Error('Not supported');
const fnBodyRegex = /\(([\s\S]*)\)/;
const fnBodyStripCommentsRegex = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm;
const fnBodyStripParamDefaultValueRegex = /\s?=.*$/;
if (fn.length === 0) return [];
export function getParameters(fn: Function): string[] {
if (typeof fn !== 'function') throw new Error('Not supported');
let fnBody: string = Function.prototype.toString.call(fn);
fnBody = fnBody.replace(fnBodyStripCommentsRegex, emptyStr) || fnBody;
fnBody = fnBody.slice(0, fnBody.indexOf(openBrace));
if (fn.length === 0) return [];
let open = fnBody.indexOf(openParen);
let close = fnBody.indexOf(closeParen);
let fnBody: string = Function.prototype.toString.call(fn);
fnBody = fnBody.replace(fnBodyStripCommentsRegex, emptyStr) || fnBody;
fnBody = fnBody.slice(0, fnBody.indexOf(openBrace));
open = open >= 0 ? open + 1 : 0;
close = close > 0 ? close : fnBody.indexOf(equals);
let open = fnBody.indexOf(openParen);
let close = fnBody.indexOf(closeParen);
fnBody = fnBody.slice(open, close);
fnBody = `(${fnBody})`;
open = open >= 0 ? open + 1 : 0;
close = close > 0 ? close : fnBody.indexOf(equals);
const match = fnBodyRegex.exec(fnBody);
return match != null
? match[1].split(comma).map(param => param.trim().replace(fnBodyStripParamDefaultValueRegex, emptyStr))
: [];
}
fnBody = fnBody.slice(open, close);
fnBody = `(${fnBody})`;
export function is<T extends object>(o: T | null | undefined): o is T;
export function is<T extends object>(o: object, prop: keyof T, value?: any): o is T;
export function is<T extends object>(o: object, matcher: (o: object) => boolean): o is T;
export function is<T extends object>(o: object, propOrMatcher?: keyof T | ((o: any) => boolean), value?: any): o is T {
if (propOrMatcher == null) return o != null;
if (typeof propOrMatcher === 'function') return propOrMatcher(o);
const match = fnBodyRegex.exec(fnBody);
return match != null
? match[1].split(comma).map(param => param.trim().replace(fnBodyStripParamDefaultValueRegex, emptyStr))
: [];
}
return value === undefined ? (o as any)[propOrMatcher] !== undefined : (o as any)[propOrMatcher] === value;
}
export function is<T extends object>(o: T | null | undefined): o is T;
export function is<T extends object>(o: object, prop: keyof T, value?: any): o is T;
export function is<T extends object>(o: object, matcher: (o: object) => boolean): o is T;
export function is<T extends object>(
o: object,
propOrMatcher?: keyof T | ((o: any) => boolean),
value?: any,
): o is T {
if (propOrMatcher == null) return o != null;
if (typeof propOrMatcher === 'function') return propOrMatcher(o);
return value === undefined ? (o as any)[propOrMatcher] !== undefined : (o as any)[propOrMatcher] === value;
}
export function once<T extends (...args: any[]) => any>(fn: T): T {
return _once(fn);
}
export function once<T extends (...args: any[]) => any>(fn: T): T {
return _once(fn);
}
export function propOf<T, K extends Extract<keyof T, string>>(o: T, key: K) {
const propOfCore = <T, K extends Extract<keyof T, string>>(o: T, key: K) => {
const value: string =
(propOfCore as PropOfValue).value === undefined ? key : `${(propOfCore as PropOfValue).value}.${key}`;
(propOfCore as PropOfValue).value = value;
const fn = <Y extends Extract<keyof T[K], string>>(k: Y) => propOfCore(o[key], k);
return Object.assign(fn, { value: value });
};
return propOfCore(o, key);
}
export function propOf<T, K extends Extract<keyof T, string>>(o: T, key: K) {
const propOfCore = <T, K extends Extract<keyof T, string>>(o: T, key: K) => {
const value: string =
(propOfCore as PropOfValue).value === undefined ? key : `${(propOfCore as PropOfValue).value}.${key}`;
(propOfCore as PropOfValue).value = value;
const fn = <Y extends Extract<keyof T[K], string>>(k: Y) => propOfCore(o[key], k);
return Object.assign(fn, { value: value });
};
return propOfCore(o, key);
}
export function interval(fn: (...args: any[]) => void, ms: number): Disposable {
let timer: NodeJS.Timer | undefined;
const disposable = {
dispose: () => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
},
};
timer = global.setInterval(fn, ms);
return disposable;
}
export function interval(fn: (...args: any[]) => void, ms: number): Disposable {
export function progress<T>(promise: Promise<T>, intervalMs: number, onProgress: () => boolean): Promise<T> {
return new Promise((resolve, reject) => {
let timer: NodeJS.Timer | undefined;
const disposable = {
dispose: () => {
timer = global.setInterval(() => {
if (onProgress()) {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
},
};
timer = global.setInterval(fn, ms);
}
}, intervalMs);
return disposable;
}
promise.then(
() => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
export function progress<T>(promise: Promise<T>, intervalMs: number, onProgress: () => boolean): Promise<T> {
return new Promise((resolve, reject) => {
let timer: NodeJS.Timer | undefined;
timer = global.setInterval(() => {
if (onProgress()) {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
resolve(promise);
},
ex => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
}, intervalMs);
promise.then(
() => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
resolve(promise);
},
ex => {
if (timer !== undefined) {
clearInterval(timer);
timer = undefined;
}
reject(ex);
},
);
});
}
export async function wait(ms: number) {
await new Promise(resolve => setTimeout(resolve, ms));
}
reject(ex);
},
);
});
}
export async function waitUntil(fn: (...args: any[]) => boolean, timeout: number): Promise<boolean> {
const max = Math.round(timeout / 100);
let counter = 0;
while (true) {
if (fn()) return true;
if (counter > max) return false;
export async function wait(ms: number) {
await new Promise(resolve => setTimeout(resolve, ms));
}
await wait(100);
counter++;
}
export async function waitUntil(fn: (...args: any[]) => boolean, timeout: number): Promise<boolean> {
const max = Math.round(timeout / 100);
let counter = 0;
while (true) {
if (fn()) return true;
if (counter > max) return false;
await wait(100);
counter++;
}
}

+ 113
- 120
src/system/iterable.ts View File

@ -1,168 +1,161 @@
'use strict';
export namespace Iterables {
export function count<T>(source: Iterable<T> | IterableIterator<T>, predicate?: (item: T) => boolean): number {
let count = 0;
let next: IteratorResult<T>;
while (true) {
next = (source as IterableIterator<T>).next();
if (next.done) break;
if (predicate === undefined || predicate(next.value)) {
count++;
}
}
export function count<T>(source: Iterable<T> | IterableIterator<T>, predicate?: (item: T) => boolean): number {
let count = 0;
let next: IteratorResult<T>;
return count;
}
while (true) {
next = (source as IterableIterator<T>).next();
if (next.done) break;
export function every<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
for (const item of source) {
if (!predicate(item)) return false;
if (predicate === undefined || predicate(next.value)) {
count++;
}
return true;
}
export function filter<T>(
source: Iterable<T | undefined | null> | IterableIterator<T | undefined | null>,
): Iterable<T>;
export function filter<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): Iterable<T>;
export function* filter<T>(
source: Iterable<T> | IterableIterator<T>,
predicate?: (item: T) => boolean,
): Iterable<T> {
if (predicate === undefined) {
for (const item of source) {
if (item != null) yield item;
}
} else {
for (const item of source) {
if (predicate(item)) yield item;
}
}
}
return count;
}
export function* filterMap<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
predicateMapper: (item: T) => TMapped | undefined | null,
): Iterable<TMapped> {
for (const item of source) {
const mapped = predicateMapper(item);
if (mapped != null) yield mapped;
}
export function every<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
for (const item of source) {
if (!predicate(item)) return false;
}
return true;
}
export function forEach<T>(source: Iterable<T> | IterableIterator<T>, fn: (item: T, index: number) => void): void {
let i = 0;
export function filter<T>(source: Iterable<T | undefined | null> | IterableIterator<T | undefined | null>): Iterable<T>;
export function filter<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): Iterable<T>;
export function* filter<T>(source: Iterable<T> | IterableIterator<T>, predicate?: (item: T) => boolean): Iterable<T> {
if (predicate === undefined) {
for (const item of source) {
fn(item, i);
i++;
if (item != null) yield item;
}
}
export function find<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): T | null {
} else {
for (const item of source) {
if (predicate(item)) return item;
if (predicate(item)) yield item;
}
return null;
}
}
export function first<T>(source: Iterable<T>): T {
return source[Symbol.iterator]().next().value;
export function* filterMap<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
predicateMapper: (item: T) => TMapped | undefined | null,
): Iterable<TMapped> {
for (const item of source) {
const mapped = predicateMapper(item);
if (mapped != null) yield mapped;
}
}
export function* flatMap<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
mapper: (item: T) => Iterable<TMapped>,
): Iterable<TMapped> {
for (const item of source) {
yield* mapper(item);
}
export function forEach<T>(source: Iterable<T> | IterableIterator<T>, fn: (item: T, index: number) => void): void {
let i = 0;
for (const item of source) {
fn(item, i);
i++;
}
}
export function has<T>(source: Iterable<T> | IterableIterator<T>, item: T): boolean {
return some(source, i => i === item);
export function find<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): T | null {
for (const item of source) {
if (predicate(item)) return item;
}
return null;
}
export function first<T>(source: Iterable<T>): T {
return source[Symbol.iterator]().next().value;
}
export function isIterable(source: Iterable<any>): boolean {
return typeof source[Symbol.iterator] === 'function';
export function* flatMap<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
mapper: (item: T) => Iterable<TMapped>,
): Iterable<TMapped> {
for (const item of source) {
yield* mapper(item);
}
}
export function join(source: Iterable<any>, separator: string): string {
let value = '';
export function has<T>(source: Iterable<T> | IterableIterator<T>, item: T): boolean {
return some(source, i => i === item);
}
const iterator = source[Symbol.iterator]();
let next = iterator.next();
if (next.done) return value;
export function isIterable(source: Iterable<any>): boolean {
return typeof source[Symbol.iterator] === 'function';
}
export function join(source: Iterable<any>, separator: string): string {
let value = '';
while (true) {
const s = next.value.toString();
const iterator = source[Symbol.iterator]();
let next = iterator.next();
if (next.done) return value;
next = iterator.next();
if (next.done) {
value += s;
break;
}
while (true) {
const s = next.value.toString();
value += `${s}${separator}`;
next = iterator.next();
if (next.done) {
value += s;
break;
}
return value;
value += `${s}${separator}`;
}
export function last<T>(source: Iterable<T>): T | undefined {
let item: T | undefined;
for (item of source) {
/* noop */
}
return item;
return value;
}
export function last<T>(source: Iterable<T>): T | undefined {
let item: T | undefined;
for (item of source) {
/* noop */
}
return item;
}
export function* map<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
mapper: (item: T) => TMapped,
): Iterable<TMapped> {
for (const item of source) {
yield mapper(item);
}
export function* map<T, TMapped>(
source: Iterable<T> | IterableIterator<T>,
mapper: (item: T) => TMapped,
): Iterable<TMapped> {
for (const item of source) {
yield mapper(item);
}
}
export function next<T>(source: IterableIterator<T>): T {
return source.next().value;
}
export function* skip<T>(source: Iterable<T> | IterableIterator<T>, count: number): IterableIterator<T> {
let i = 0;
for (const item of source) {
if (i >= count) yield item;
i++;
}
}
export function next<T>(source: IterableIterator<T>): T {
return source.next().value;
export function some<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
for (const item of source) {
if (predicate(item)) return true;
}
return false;
}
export function* skip<T>(source: Iterable<T> | IterableIterator<T>, count: number): IterableIterator<T> {
export function* take<T>(source: Iterable<T> | IterableIterator<T>, count: number): Iterable<T> {
if (count > 0) {
let i = 0;
for (const item of source) {
if (i >= count) yield item;
yield item;
i++;
if (i >= count) break;
}
}
}
export function some<T>(source: Iterable<T> | IterableIterator<T>, predicate: (item: T) => boolean): boolean {
export function* union<T>(...sources: (Iterable<T> | IterableIterator<T>)[]): Iterable<T> {
for (const source of sources) {
for (const item of source) {
if (predicate(item)) return true;
}
return false;
}
export function* take<T>(source: Iterable<T> | IterableIterator<T>, count: number): Iterable<T> {
if (count > 0) {
let i = 0;
for (const item of source) {
yield item;
i++;
if (i >= count) break;
}
}
}
export function* union<T>(...sources: (Iterable<T> | IterableIterator<T>)[]): Iterable<T> {
for (const source of sources) {
for (const item of source) {
yield item;
}
yield item;
}
}
}

+ 63
- 65
src/system/object.ts View File

@ -1,86 +1,84 @@
'use strict';
import { isEqual as _isEqual } from 'lodash-es';
import { Arrays } from './array';
import { areEquivalent as arraysAreEquivalent } from './array';
export namespace Objects {
export function entries<T>(o: Record<string, T>): IterableIterator<[string, T]>;
export function entries<T>(o: Record<number, T>): IterableIterator<[string, T]>;
export function* entries<T>(o: any): IterableIterator<[string, T]> {
for (const key in o) {
yield [key, o[key]];
}
export function entries<T>(o: Record<string, T>): IterableIterator<[string, T]>;
export function entries<T>(o: Record<number, T>): IterableIterator<[string, T]>;
export function* entries<T>(o: any): IterableIterator<[string, T]> {
for (const key in o) {
yield [key, o[key]];
}
}
export function flatten(o: any, prefix: string = '', stringify: boolean = false): Record<string, any> {
const flattened = Object.create(null) as Record<string, any>;
_flatten(flattened, prefix, o, stringify);
return flattened;
}
export function flatten(o: any, prefix: string = '', stringify: boolean = false): Record<string, any> {
const flattened = Object.create(null) as Record<string, any>;
_flatten(flattened, prefix, o, stringify);
return flattened;
}
function _flatten(flattened: Record<string, any>, key: string, value: any, stringify: boolean = false) {
if (Object(value) !== value) {
if (stringify) {
if (value == null) {
flattened[key] = null;
} else if (typeof value === 'string') {
flattened[key] = value;
} else {
flattened[key] = JSON.stringify(value);
}
} else {
flattened[key] = value;
}
} else if (Array.isArray(value)) {
const len = value.length;
for (let i = 0; i < len; i++) {
_flatten(flattened, `${key}[${i}]`, value[i], stringify);
}
if (len === 0) {
function _flatten(flattened: Record<string, any>, key: string, value: any, stringify: boolean = false) {
if (Object(value) !== value) {
if (stringify) {
if (value == null) {
flattened[key] = null;
} else if (typeof value === 'string') {
flattened[key] = value;
} else {
flattened[key] = JSON.stringify(value);
}
} else {
let isEmpty = true;
for (const p in value) {
isEmpty = false;
_flatten(flattened, key ? `${key}.${p}` : p, value[p], stringify);
}
if (isEmpty && key) {
flattened[key] = null;
}
flattened[key] = value;
}
} else if (Array.isArray(value)) {
const len = value.length;
for (let i = 0; i < len; i++) {
_flatten(flattened, `${key}[${i}]`, value[i], stringify);
}
if (len === 0) {
flattened[key] = null;
}
} else {
let isEmpty = true;
for (const p in value) {
isEmpty = false;
_flatten(flattened, key ? `${key}.${p}` : p, value[p], stringify);
}
if (isEmpty && key) {
flattened[key] = null;
}
}
}
export function isEqual(value: any, other: any) {
return _isEqual(value, other);
}
export function isEqual(value: any, other: any) {
return _isEqual(value, other);
}
export function areEquivalent(value: any, other: any) {
if (Array.isArray(value) && Array.isArray(other)) {
return Arrays.areEquivalent(value, other);
}
return isEqual(value, other);
export function areEquivalent(value: any, other: any) {
if (Array.isArray(value) && Array.isArray(other)) {
return arraysAreEquivalent(value, other);
}
return isEqual(value, other);
}
export function paths(o: Record<string, any>, path?: string): string[] {
const results = [];
export function paths(o: Record<string, any>, path?: string): string[] {
const results = [];
for (const key in o) {
const child = o[key];
if (typeof child === 'object') {
results.push(...paths(child, path === undefined ? key : `${path}.${key}`));
} else {
results.push(path === undefined ? key : `${path}.${key}`);
}
for (const key in o) {
const child = o[key];
if (typeof child === 'object') {
results.push(...paths(child, path === undefined ? key : `${path}.${key}`));
} else {
results.push(path === undefined ? key : `${path}.${key}`);
}
return results;
}
export function values<T>(o: Record<string, T>): IterableIterator<T>;
export function values<T>(o: Record<number, T>): IterableIterator<T>;
export function* values<T>(o: any): IterableIterator<T> {
for (const key in o) {
yield o[key];
}
return results;
}
export function values<T>(o: Record<string, T>): IterableIterator<T>;
export function values<T>(o: Record<number, T>): IterableIterator<T>;
export function* values<T>(o: any): IterableIterator<T> {
for (const key in o) {
yield o[key];
}
}

+ 129
- 137
src/system/promise.ts View File

@ -1,157 +1,149 @@
'use strict';
import { CancellationToken } from 'vscode';
import { Iterables } from './iterable';
import { map } from './iterable';
export namespace Promises {
export class CancellationError<TPromise = any> extends Error {
constructor(public readonly promise: TPromise, message: string) {
super(message);
}
export class CancellationError<TPromise = any> extends Error {
constructor(public readonly promise: TPromise, message: string) {
super(message);
}
}
export class CancellationErrorWithId<T, TPromise = any> extends CancellationError<TPromise> {
constructor(public readonly id: T, promise: TPromise, message: string) {
super(promise, message);
}
export class CancellationErrorWithId<T, TPromise = any> extends CancellationError<TPromise> {
constructor(public readonly id: T, promise: TPromise, message: string) {
super(promise, message);
}
}
export function cancellable<T>(
promise: Thenable<T>,
timeoutOrToken: number | CancellationToken,
options: {
cancelMessage?: string;
onDidCancel?(
resolve: (value?: T | PromiseLike<T> | undefined) => void,
reject: (reason?: any) => void,
): void;
} = {},
): Promise<T> {
return new Promise((resolve, reject) => {
let fulfilled = false;
let timer: NodeJS.Timer | undefined;
if (typeof timeoutOrToken === 'number') {
timer = global.setTimeout(() => {
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new CancellationError(promise, options.cancelMessage ?? 'TIMED OUT'));
}
}, timeoutOrToken);
} else {
timeoutOrToken.onCancellationRequested(() => {
if (fulfilled) return;
export function cancellable<T>(
promise: Thenable<T>,
timeoutOrToken: number | CancellationToken,
options: {
cancelMessage?: string;
onDidCancel?(resolve: (value?: T | PromiseLike<T> | undefined) => void, reject: (reason?: any) => void): void;
} = {},
): Promise<T> {
return new Promise((resolve, reject) => {
let fulfilled = false;
let timer: NodeJS.Timer | undefined;
if (typeof timeoutOrToken === 'number') {
timer = global.setTimeout(() => {
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new CancellationError(promise, options.cancelMessage ?? 'TIMED OUT'));
}
}, timeoutOrToken);
} else {
timeoutOrToken.onCancellationRequested(() => {
if (fulfilled) return;
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new CancellationError(promise, options.cancelMessage ?? 'CANCELLED'));
}
});
}
if (typeof options.onDidCancel === 'function') {
options.onDidCancel(resolve, reject);
} else {
reject(new CancellationError(promise, options.cancelMessage ?? 'CANCELLED'));
}
});
}
promise.then(
() => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
}
resolve(promise);
},
ex => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
promise.then(
() => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
}
resolve(promise);
},
ex => {
fulfilled = true;
if (timer !== undefined) {
clearTimeout(timer);
}
reject(ex);
},
);
});
}
export function first<T>(promises: Promise<T>[], predicate: (value: T) => boolean): Promise<T | undefined> {
const newPromises: Promise<T | undefined>[] = promises.map(
p =>
new Promise<T>((resolve, reject) =>
p.then(value => {
if (predicate(value)) {
resolve(value);
}
reject(ex);
},
);
});
}
}, reject),
),
);
newPromises.push(Promise.all(promises).then(() => undefined));
return Promise.race(newPromises);
}
export function first<T>(promises: Promise<T>[], predicate: (value: T) => boolean): Promise<T | undefined> {
const newPromises: Promise<T | undefined>[] = promises.map(
p =>
new Promise<T>((resolve, reject) =>
p.then(value => {
if (predicate(value)) {
resolve(value);
}
}, reject),
),
);
newPromises.push(Promise.all(promises).then(() => undefined));
return Promise.race(newPromises);
}
export function is<T>(obj: T | Promise<T>): obj is Promise<T> {
return obj != null && typeof (obj as Promise<T>).then === 'function';
}
export function is<T>(obj: T | Promise<T>): obj is Promise<T> {
return obj != null && typeof (obj as Promise<T>).then === 'function';
export function raceAll<TPromise>(
promises: Promise<TPromise>[],
timeout?: number,
): Promise<(TPromise | CancellationError<Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(
promises: Map<T, Promise<TPromise>>,
timeout?: number,
): Promise<Map<T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>>>;
export function raceAll<TPromise, T>(
ids: Iterable<T>,
fn: (id: T) => Promise<TPromise>,
timeout?: number,
): Promise<Map<T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>>>;
export async function raceAll<TPromise, T>(
promisesOrIds: Promise<TPromise>[] | Map<T, Promise<TPromise>> | Iterable<T>,
timeoutOrFn?: number | ((id: T) => Promise<TPromise>),
timeout?: number,
) {
let promises;
if (timeoutOrFn != null && typeof timeoutOrFn !== 'number') {
promises = new Map(
map<T, [T, Promise<TPromise>]>(promisesOrIds as Iterable<T>, id => [id, timeoutOrFn(id)]),
);
} else {
timeout = timeoutOrFn;
promises = promisesOrIds as Promise<TPromise>[] | Map<T, Promise<TPromise>>;
}
export function raceAll<TPromise>(
promises: Promise<TPromise>[],
timeout?: number,
): Promise<(TPromise | Promises.CancellationError<Promise<TPromise>>)[]>;
export function raceAll<TPromise, T>(
promises: Map<T, Promise<TPromise>>,
timeout?: number,
): Promise<Map<T, TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>>>;
export function raceAll<TPromise, T>(
ids: Iterable<T>,
fn: (id: T) => Promise<TPromise>,
timeout?: number,
): Promise<Map<T, TPromise | Promises.CancellationErrorWithId<T, Promise<TPromise>>>>;
export async function raceAll<TPromise, T>(
promisesOrIds: Promise<TPromise>[] | Map<T, Promise<TPromise>> | Iterable<T>,
timeoutOrFn?: number | ((id: T) => Promise<TPromise>),
timeout?: number,
) {
let promises;
if (timeoutOrFn != null && typeof timeoutOrFn !== 'number') {
promises = new Map(
Iterables.map<T, [T, Promise<TPromise>]>(promisesOrIds as Iterable<T>, id => [id, timeoutOrFn(id)]),
);
} else {
timeout = timeoutOrFn;
promises = promisesOrIds as Promise<TPromise>[] | Map<T, Promise<TPromise>>;
}
if (promises instanceof Map) {
return new Map(
await Promise.all(
Iterables.map<
[T, Promise<TPromise>],
Promise<[T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>]>
>(
promises.entries(),
timeout == null
? ([id, promise]) => promise.then(p => [id, p])
: ([id, promise]) =>
Promise.race([
promise,
if (promises instanceof Map) {
return new Map(
await Promise.all(
map<[T, Promise<TPromise>], Promise<[T, TPromise | CancellationErrorWithId<T, Promise<TPromise>>]>>(
promises.entries(),
timeout == null
? ([id, promise]) => promise.then(p => [id, p])
: ([id, promise]) =>
Promise.race([
promise,
new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
setTimeout(
() => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')),
timeout!,
),
new Promise<CancellationErrorWithId<T, Promise<TPromise>>>(resolve =>
setTimeout(
() => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')),
timeout!,
),
]).then(p => [id, p]),
),
),
]).then(p => [id, p]),
),
);
}
return Promise.all(
timeout == null
? promises
: promises.map(p =>
Promise.race([
p,
new Promise<CancellationError<Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!),
),
]),
),
),
);
}
return Promise.all(
timeout == null
? promises
: promises.map(p =>
Promise.race([
p,
new Promise<CancellationError<Promise<TPromise>>>(resolve =>
setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!),
),
]),
),
);
}

+ 6
- 6
src/system/searchTree.ts View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
'use strict';
import { Iterables } from './iterable';
import { Strings } from './string';
import { count, map, some } from './iterable';
import { CharCode } from './string';
// Code stolen from https://github.com/Microsoft/vscode/blob/b3e6d5bb039a4a9362b52a2c8726267ca68cf64e/src/vs/base/common/map.ts#L352
@ -70,7 +70,7 @@ export class PathIterator implements IKeyIterator {
let justSeps = true;
for (; this._to < this._value.length; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === Strings.CharCode.Slash || ch === Strings.CharCode.Backslash) {
if (ch === CharCode.Slash || ch === CharCode.Backslash) {
if (justSeps) {
this._from++;
} else {
@ -374,7 +374,7 @@ export class TernarySearchTree {
count(predicate?: (entry: E) => boolean): number {
if (this._root === undefined || this._root.isEmpty()) return 0;
return Iterables.count(this.entries(), predicate === undefined ? undefined : ([e]) => predicate(e));
return count(this.entries(), predicate === undefined ? undefined : ([e]) => predicate(e));
}
entries(): Iterable<[E, string]> {
@ -382,7 +382,7 @@ export class TernarySearchTree {
}
values(): Iterable<E> {
return Iterables.map(this.entries(), ([e]) => e);
return map(this.entries(), ([e]) => e);
}
highlander(): [E, string] | undefined {
@ -410,7 +410,7 @@ export class TernarySearchTree {
some(predicate: (entry: E) => boolean): boolean {
if (this._root === undefined || this._root.isEmpty()) return false;
return Iterables.some(this.entries(), ([e]) => predicate(e));
return some(this.entries(), ([e]) => predicate(e));
}
private *_iterator(node: TernarySearchTreeNode<E> | undefined): IterableIterator<[E, string]> {

+ 331
- 341
src/system/string.ts View File

@ -4,418 +4,408 @@ import { isWindows } from '../git/shell';
const emptyStr = '';
export namespace Strings {
export const enum CharCode {
/**
* The `/` character.
*/
Slash = 47,
/**
* The `\` character.
*/
Backslash = 92,
}
export const enum CharCode {
/**
* The `/` character.
*/
Slash = 47,
/**
* The `\` character.
*/
Backslash = 92,
}
export function base64(s: string): string {
const buffer = Buffer.from(s);
return buffer.toString('base64');
}
export function base64(s: string): string {
const buffer = Buffer.from(s);
return buffer.toString('base64');
}
const escapeMarkdownRegex = /[\\`*_{}[\]()#+\-.!]/g;
const escapeMarkdownHeaderRegex = /^===/gm;
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
const markdownQuotedRegex = /\n/g;
const escapeMarkdownRegex = /[\\`*_{}[\]()#+\-.!]/g;
const escapeMarkdownHeaderRegex = /^===/gm;
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
const markdownQuotedRegex = /\n/g;
export function escapeMarkdown(s: string, options: { quoted?: boolean } = {}): string {
s = s
// Escape markdown
.replace(escapeMarkdownRegex, '\\$&')
// Escape markdown header (since the above regex won't match it)
.replace(escapeMarkdownHeaderRegex, '\u200b===');
export function escapeMarkdown(s: string, options: { quoted?: boolean } = {}): string {
s = s
// Escape markdown
.replace(escapeMarkdownRegex, '\\$&')
// Escape markdown header (since the above regex won't match it)
.replace(escapeMarkdownHeaderRegex, '\u200b===');
if (!options.quoted) return s;
if (!options.quoted) return s;
// Keep under the same block-quote but with line breaks
return s.replace(markdownQuotedRegex, '\t\n> ');
}
// Keep under the same block-quote but with line breaks
return s.replace(markdownQuotedRegex, '\t\n> ');
}
export function escapeRegex(s: string) {
return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
export function escapeRegex(s: string) {
return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
export function getCommonBase(s1: string, s2: string, delimiter: string) {
let char;
let index = 0;
for (let i = 0; i < s1.length; i++) {
char = s1[i];
if (char !== s2[i]) break;
export function getCommonBase(s1: string, s2: string, delimiter: string) {
let char;
let index = 0;
for (let i = 0; i < s1.length; i++) {
char = s1[i];
if (char !== s2[i]) break;
if (char === delimiter) {
index = i;
}
if (char === delimiter) {
index = i;
}
return index > 0 ? s1.substring(0, index + 1) : undefined;
}
export function getDurationMilliseconds(start: [number, number]) {
const [secs, nanosecs] = process.hrtime(start);
return secs * 1000 + Math.floor(nanosecs / 1000000);
}
return index > 0 ? s1.substring(0, index + 1) : undefined;
}
const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'];
export function getDurationMilliseconds(start: [number, number]) {
const [secs, nanosecs] = process.hrtime(start);
return secs * 1000 + Math.floor(nanosecs / 1000000);
}
export function getSuperscript(num: number) {
return superscripts[num - 1] ?? '';
}
const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'];
const driveLetterNormalizeRegex = /(?<=^\/?)([A-Z])(?=:\/)/;
const pathNormalizeRegex = /\\/g;
const pathStripTrailingSlashRegex = /\/$/g;
const tokenRegex = /\$\{(".*?"|\W*)?([^|]*?)(?:\|(\d+)(-|\?)?)?(".*?"|\W*)?\}/g;
const tokenSanitizeRegex = /\$\{(?:".*?"|\W*)?(\w*?)(?:".*?"|[\W\d]*)\}/g;
// eslint-disable-next-line no-template-curly-in-string
const tokenSanitizeReplacement = '$${this.$1}';
export interface TokenOptions {
collapseWhitespace: boolean;
padDirection: 'left' | 'right';
prefix: string | undefined;
suffix: string | undefined;
truncateTo: number | undefined;
}
export function getSuperscript(num: number) {
return superscripts[num - 1] ?? '';
}
export function getTokensFromTemplate(template: string) {
const tokens: { key: string; options: TokenOptions }[] = [];
let match;
do {
match = tokenRegex.exec(template);
if (match == null) break;
const [, prefix, key, truncateTo, option, suffix] = match;
tokens.push({
key: key,
options: {
collapseWhitespace: option === '?',
padDirection: option === '-' ? 'left' : 'right',
prefix:
prefix?.length > 1 && prefix?.startsWith('"') && prefix?.endsWith('"')
? prefix.substr(1, prefix.length - 2)
: prefix,
suffix: suffix,
truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10),
},
});
} while (true);
return tokens;
}
const driveLetterNormalizeRegex = /(?<=^\/?)([A-Z])(?=:\/)/;
const pathNormalizeRegex = /\\/g;
const pathStripTrailingSlashRegex = /\/$/g;
const tokenRegex = /\$\{(".*?"|\W*)?([^|]*?)(?:\|(\d+)(-|\?)?)?(".*?"|\W*)?\}/g;
const tokenSanitizeRegex = /\$\{(?:".*?"|\W*)?(\w*?)(?:".*?"|[\W\d]*)\}/g;
// eslint-disable-next-line no-template-curly-in-string
const tokenSanitizeReplacement = '$${this.$1}';
export interface TokenOptions {
collapseWhitespace: boolean;
padDirection: 'left' | 'right';
prefix: string | undefined;
suffix: string | undefined;
truncateTo: number | undefined;
}
const interpolationMap = new Map<string, Function>();
export function getTokensFromTemplate(template: string) {
const tokens: { key: string; options: TokenOptions }[] = [];
let match;
do {
match = tokenRegex.exec(template);
if (match == null) break;
const [, prefix, key, truncateTo, option, suffix] = match;
tokens.push({
key: key,
options: {
collapseWhitespace: option === '?',
padDirection: option === '-' ? 'left' : 'right',
prefix:
prefix?.length > 1 && prefix?.startsWith('"') && prefix?.endsWith('"')
? prefix.substr(1, prefix.length - 2)
: prefix,
suffix: suffix,
truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10),
},
});
} while (true);
return tokens;
}
export function interpolate(template: string, context: object | undefined): string {
if (template == null || template.length === 0) return template;
if (context == null) return template.replace(tokenSanitizeRegex, emptyStr);
const interpolationMap = new Map<string, Function>();
let fn = interpolationMap.get(template);
if (fn == null) {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
fn = new Function(`return \`${template.replace(tokenSanitizeRegex, tokenSanitizeReplacement)}\`;`);
interpolationMap.set(template, fn);
}
export function interpolate(template: string, context: object | undefined): string {
if (template == null || template.length === 0) return template;
if (context == null) return template.replace(tokenSanitizeRegex, emptyStr);
return fn.call(context);
let fn = interpolationMap.get(template);
if (fn == null) {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
fn = new Function(`return \`${template.replace(tokenSanitizeRegex, tokenSanitizeReplacement)}\`;`);
interpolationMap.set(template, fn);
}
export function* lines(s: string): IterableIterator<string> {
let i = 0;
while (i < s.length) {
let j = s.indexOf('\n', i);
if (j === -1) {
j = s.length;
}
return fn.call(context);
}
yield s.substring(i, j);
i = j + 1;
export function* lines(s: string): IterableIterator<string> {
let i = 0;
while (i < s.length) {
let j = s.indexOf('\n', i);
if (j === -1) {
j = s.length;
}
}
export function md5(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string {
return createHash('md5').update(s).digest(encoding);
yield s.substring(i, j);
i = j + 1;
}
}
export function normalizePath(
fileName: string,
options: { addLeadingSlash?: boolean; stripTrailingSlash?: boolean } = { stripTrailingSlash: true },
) {
if (fileName == null || fileName.length === 0) return fileName;
export function md5(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string {
return createHash('md5').update(s).digest(encoding);
}
let normalized = fileName.replace(pathNormalizeRegex, '/');
export function normalizePath(
fileName: string,
options: { addLeadingSlash?: boolean; stripTrailingSlash?: boolean } = { stripTrailingSlash: true },
) {
if (fileName == null || fileName.length === 0) return fileName;
const { addLeadingSlash, stripTrailingSlash } = { stripTrailingSlash: true, ...options };
let normalized = fileName.replace(pathNormalizeRegex, '/');
if (stripTrailingSlash) {
normalized = normalized.replace(pathStripTrailingSlashRegex, emptyStr);
}
const { addLeadingSlash, stripTrailingSlash } = { stripTrailingSlash: true, ...options };
if (addLeadingSlash && normalized.charCodeAt(0) !== CharCode.Slash) {
normalized = `/${normalized}`;
}
if (stripTrailingSlash) {
normalized = normalized.replace(pathStripTrailingSlashRegex, emptyStr);
}
if (isWindows) {
// Ensure that drive casing is normalized (lower case)
normalized = normalized.replace(driveLetterNormalizeRegex, (drive: string) => drive.toLowerCase());
}
if (addLeadingSlash && normalized.charCodeAt(0) !== CharCode.Slash) {
normalized = `/${normalized}`;
}
return normalized;
if (isWindows) {
// Ensure that drive casing is normalized (lower case)
normalized = normalized.replace(driveLetterNormalizeRegex, (drive: string) => drive.toLowerCase());
}
export function pad(s: string, before: number = 0, after: number = 0, padding: string = '\u00a0') {
if (before === 0 && after === 0) return s;
return normalized;
}
return `${before === 0 ? emptyStr : padding.repeat(before)}${s}${
after === 0 ? emptyStr : padding.repeat(after)
}`;
}
export function pad(s: string, before: number = 0, after: number = 0, padding: string = '\u00a0') {
if (before === 0 && after === 0) return s;
export function padLeft(s: string, padTo: number, padding: string = '\u00a0', width?: number) {
const diff = padTo - (width ?? getWidth(s));
return diff <= 0 ? s : padding.repeat(diff) + s;
}
return `${before === 0 ? emptyStr : padding.repeat(before)}${s}${after === 0 ? emptyStr : padding.repeat(after)}`;
}
export function padLeftOrTruncate(s: string, max: number, padding?: string, width?: number) {
width = width ?? getWidth(s);
if (width < max) return padLeft(s, max, padding, width);
if (width > max) return truncate(s, max, undefined, width);
return s;
}
export function padLeft(s: string, padTo: number, padding: string = '\u00a0', width?: number) {
const diff = padTo - (width ?? getWidth(s));
return diff <= 0 ? s : padding.repeat(diff) + s;
}
export function padRight(s: string, padTo: number, padding: string = '\u00a0', width?: number) {
const diff = padTo - (width ?? getWidth(s));
return diff <= 0 ? s : s + padding.repeat(diff);
}
export function padLeftOrTruncate(s: string, max: number, padding?: string, width?: number) {
width = width ?? getWidth(s);
if (width < max) return padLeft(s, max, padding, width);
if (width > max) return truncate(s, max, undefined, width);
return s;
}
export function padOrTruncate(s: string, max: number, padding?: string, width?: number) {
const left = max < 0;
max = Math.abs(max);
export function padRight(s: string, padTo: number, padding: string = '\u00a0', width?: number) {
const diff = padTo - (width ?? getWidth(s));
return diff <= 0 ? s : s + padding.repeat(diff);
}
width = width ?? getWidth(s);
if (width < max) return left ? padLeft(s, max, padding, width) : padRight(s, max, padding, width);
if (width > max) return truncate(s, max, undefined, width);
return s;
}
export function padOrTruncate(s: string, max: number, padding?: string, width?: number) {
const left = max < 0;
max = Math.abs(max);
export function padRightOrTruncate(s: string, max: number, padding?: string, width?: number) {
width = width ?? getWidth(s);
if (width < max) return padRight(s, max, padding, width);
if (width > max) return truncate(s, max);
return s;
}
width = width ?? getWidth(s);
if (width < max) return left ? padLeft(s, max, padding, width) : padRight(s, max, padding, width);
if (width > max) return truncate(s, max, undefined, width);
return s;
}
export function pluralize(
s: string,
count: number,
options?: { number?: string; plural?: string; suffix?: string; zero?: string },
) {
if (options == null) return `${count} ${s}${count === 1 ? emptyStr : 's'}`;
return `${
count === 0
? options.zero != null
? options.zero
: count
: options.number != null
? options.number
: count
} ${count === 1 ? s : options.plural ?? `${s}${options.suffix ?? 's'}`}`;
}
export function padRightOrTruncate(s: string, max: number, padding?: string, width?: number) {
width = width ?? getWidth(s);
if (width < max) return padRight(s, max, padding, width);
if (width > max) return truncate(s, max);
return s;
}
export function pluralize(
s: string,
count: number,
options?: { number?: string; plural?: string; suffix?: string; zero?: string },
) {
if (options == null) return `${count} ${s}${count === 1 ? emptyStr : 's'}`;
return `${
count === 0 ? (options.zero != null ? options.zero : count) : options.number != null ? options.number : count
} ${count === 1 ? s : options.plural ?? `${s}${options.suffix ?? 's'}`}`;
}
// Removes \ / : * ? " < > | and C0 and C1 control codes
// eslint-disable-next-line no-control-regex
const illegalCharsForFSRegex = /[\\/:*?"<>|\x00-\x1f\x80-\x9f]/g;
export function sanitizeForFileSystem(s: string, replacement: string = '_') {
if (!s) return s;
return s.replace(illegalCharsForFSRegex, replacement);
}
export function sha1(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string {
return createHash('sha1').update(s).digest(encoding);
}
// Removes \ / : * ? " < > | and C0 and C1 control codes
// eslint-disable-next-line no-control-regex
const illegalCharsForFSRegex = /[\\/:*?"<>|\x00-\x1f\x80-\x9f]/g;
export function splitLast(s: string, splitter: string) {
const index = s.lastIndexOf(splitter);
if (index === -1) return [s];
export function sanitizeForFileSystem(s: string, replacement: string = '_') {
if (!s) return s;
return s.replace(illegalCharsForFSRegex, replacement);
return [s.substr(index), s.substring(0, index - 1)];
}
export function splitSingle(s: string, splitter: string) {
const parts = s.split(splitter, 1);
const first = parts[0];
return first.length === s.length ? parts : [first, s.substr(first.length + 1)];
}
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
width = width ?? getWidth(s);
if (width <= truncateTo) return s;
if (width === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
let chars = Math.floor(truncateTo / (width / s.length));
let count = getWidth(s.substring(0, chars));
while (count < truncateTo) {
count += getWidth(s[chars++]);
}
export function sha1(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string {
return createHash('sha1').update(s).digest(encoding);
if (count >= truncateTo) {
chars--;
}
export function splitLast(s: string, splitter: string) {
const index = s.lastIndexOf(splitter);
if (index === -1) return [s];
return `${s.substring(0, chars)}${ellipsis}`;
}
export function truncateLeft(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
return [s.substr(index), s.substring(0, index - 1)];
width = width ?? getWidth(s);
if (width <= truncateTo) return s;
if (width === s.length) return `${ellipsis}${s.substring(width - truncateTo)}`;
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
let chars = Math.floor(truncateTo / (width / s.length));
let count = getWidth(s.substring(0, chars));
while (count < truncateTo) {
count += getWidth(s[chars++]);
}
export function splitSingle(s: string, splitter: string) {
const parts = s.split(splitter, 1);
const first = parts[0];
return first.length === s.length ? parts : [first, s.substr(first.length + 1)];
if (count >= truncateTo) {
chars--;
}
export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
return `${ellipsis}${s.substring(s.length - chars)}`;
}
width = width ?? getWidth(s);
if (width <= truncateTo) return s;
if (width === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`;
export function truncateMiddle(s: string, truncateTo: number, ellipsis: string = '\u2026') {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
let chars = Math.floor(truncateTo / (width / s.length));
let count = getWidth(s.substring(0, chars));
while (count < truncateTo) {
count += getWidth(s[chars++]);
}
const width = getWidth(s);
if (width <= truncateTo) return s;
if (count >= truncateTo) {
chars--;
}
return `${s.slice(0, Math.floor(truncateTo / 2) - 1)}${ellipsis}${s.slice(width - Math.ceil(truncateTo / 2))}`;
}
return `${s.substring(0, chars)}${ellipsis}`;
}
// Lifted from https://github.com/chalk/ansi-regex
// eslint-disable-next-line no-control-regex
const ansiRegex = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g;
const containsNonAsciiRegex = /[^\x20-\x7F\u00a0\u2026]/;
export function truncateLeft(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
export function getWidth(s: string): number {
if (s == null || s.length === 0) return 0;
width = width ?? getWidth(s);
if (width <= truncateTo) return s;
if (width === s.length) return `${ellipsis}${s.substring(width - truncateTo)}`;
// Shortcut to avoid needless string `RegExp`s, replacements, and allocations
if (!containsNonAsciiRegex.test(s)) return s.length;
// Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated
let chars = Math.floor(truncateTo / (width / s.length));
let count = getWidth(s.substring(0, chars));
while (count < truncateTo) {
count += getWidth(s[chars++]);
}
s = s.replace(ansiRegex, emptyStr);
if (count >= truncateTo) {
chars--;
}
let count = 0;
let emoji = 0;
let joiners = 0;
return `${ellipsis}${s.substring(s.length - chars)}`;
}
const graphemes = [...s];
for (let i = 0; i < graphemes.length; i++) {
const code = graphemes[i].codePointAt(0)!;
export function truncateMiddle(s: string, truncateTo: number, ellipsis: string = '\u2026') {
if (!s) return s;
if (truncateTo <= 1) return ellipsis;
// Ignore control characters
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) continue;
const width = getWidth(s);
if (width <= truncateTo) return s;
// Ignore combining characters
if (code >= 0x300 && code <= 0x36f) continue;
return `${s.slice(0, Math.floor(truncateTo / 2) - 1)}${ellipsis}${s.slice(width - Math.ceil(truncateTo / 2))}`;
}
// https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
if (
(code >= 0x1f600 && code <= 0x1f64f) || // Emoticons
(code >= 0x1f300 && code <= 0x1f5ff) || // Misc Symbols and Pictographs
(code >= 0x1f680 && code <= 0x1f6ff) || // Transport and Map
(code >= 0x2600 && code <= 0x26ff) || // Misc symbols
(code >= 0x2700 && code <= 0x27bf) || // Dingbats
(code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors
(code >= 0x1f900 && code <= 0x1f9ff) || // Supplemental Symbols and Pictographs
(code >= 65024 && code <= 65039) || // Variation selector
(code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols
) {
if (code >= 0x1f3fb && code <= 0x1f3ff) continue; // emoji modifier fitzpatrick type
// Lifted from https://github.com/chalk/ansi-regex
// eslint-disable-next-line no-control-regex
const ansiRegex = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g;
const containsNonAsciiRegex = /[^\x20-\x7F\u00a0\u2026]/;
export function getWidth(s: string): number {
if (s == null || s.length === 0) return 0;
// Shortcut to avoid needless string `RegExp`s, replacements, and allocations
if (!containsNonAsciiRegex.test(s)) return s.length;
s = s.replace(ansiRegex, emptyStr);
let count = 0;
let emoji = 0;
let joiners = 0;
const graphemes = [...s];
for (let i = 0; i < graphemes.length; i++) {
const code = graphemes[i].codePointAt(0)!;
// Ignore control characters
if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) continue;
// Ignore combining characters
if (code >= 0x300 && code <= 0x36f) continue;
// https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
if (
(code >= 0x1f600 && code <= 0x1f64f) || // Emoticons
(code >= 0x1f300 && code <= 0x1f5ff) || // Misc Symbols and Pictographs
(code >= 0x1f680 && code <= 0x1f6ff) || // Transport and Map
(code >= 0x2600 && code <= 0x26ff) || // Misc symbols
(code >= 0x2700 && code <= 0x27bf) || // Dingbats
(code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors
(code >= 0x1f900 && code <= 0x1f9ff) || // Supplemental Symbols and Pictographs
(code >= 65024 && code <= 65039) || // Variation selector
(code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols
) {
if (code >= 0x1f3fb && code <= 0x1f3ff) continue; // emoji modifier fitzpatrick type
emoji++;
count += 2;
continue;
}
// Ignore zero-width joiners '\u200d'
if (code === 8205) {
joiners++;
count -= 2;
continue;
}
// Surrogates
if (code > 0xffff) {
i++;
}
count += isFullwidthCodePoint(code) ? 2 : 1;
emoji++;
count += 2;
continue;
}
const offset = emoji - joiners;
if (offset > 1) {
count += offset - 1;
// Ignore zero-width joiners '\u200d'
if (code === 8205) {
joiners++;
count -= 2;
continue;
}
return count;
}
function isFullwidthCodePoint(cp: number) {
// code points are derived from:
// http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt
if (
cp >= 0x1100 &&
(cp <= 0x115f || // Hangul Jamo
cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET
cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET
// CJK Radicals Supplement .. Enclosed CJK Letters and Months
(cp >= 0x2e80 && cp <= 0x3247 && cp !== 0x303f) ||
// Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
(cp >= 0x3250 && cp <= 0x4dbf) ||
// CJK Unified Ideographs .. Yi Radicals
(cp >= 0x4e00 && cp <= 0xa4c6) ||
// Hangul Jamo Extended-A
(cp >= 0xa960 && cp <= 0xa97c) ||
// Hangul Syllables
(cp >= 0xac00 && cp <= 0xd7a3) ||
// CJK Compatibility Ideographs
(cp >= 0xf900 && cp <= 0xfaff) ||
// Vertical Forms
(cp >= 0xfe10 && cp <= 0xfe19) ||
// CJK Compatibility Forms .. Small Form Variants
(cp >= 0xfe30 && cp <= 0xfe6b) ||
// Halfwidth and Fullwidth Forms
(cp >= 0xff01 && cp <= 0xff60) ||
(cp >= 0xffe0 && cp <= 0xffe6) ||
// Kana Supplement
(cp >= 0x1b000 && cp <= 0x1b001) ||
// Enclosed Ideographic Supplement
(cp >= 0x1f200 && cp <= 0x1f251) ||
// CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
(cp >= 0x20000 && cp <= 0x3fffd))
) {
return true;
// Surrogates
if (code > 0xffff) {
i++;
}
return false;
count += isFullwidthCodePoint(code) ? 2 : 1;
}
const offset = emoji - joiners;
if (offset > 1) {
count += offset - 1;
}
return count;
}
function isFullwidthCodePoint(cp: number) {
// code points are derived from:
// http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt
if (
cp >= 0x1100 &&
(cp <= 0x115f || // Hangul Jamo
cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET
cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET
// CJK Radicals Supplement .. Enclosed CJK Letters and Months
(cp >= 0x2e80 && cp <= 0x3247 && cp !== 0x303f) ||
// Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
(cp >= 0x3250 && cp <= 0x4dbf) ||
// CJK Unified Ideographs .. Yi Radicals
(cp >= 0x4e00 && cp <= 0xa4c6) ||
// Hangul Jamo Extended-A
(cp >= 0xa960 && cp <= 0xa97c) ||
// Hangul Syllables
(cp >= 0xac00 && cp <= 0xd7a3) ||
// CJK Compatibility Ideographs
(cp >= 0xf900 && cp <= 0xfaff) ||
// Vertical Forms
(cp >= 0xfe10 && cp <= 0xfe19) ||
// CJK Compatibility Forms .. Small Form Variants
(cp >= 0xfe30 && cp <= 0xfe6b) ||
// Halfwidth and Fullwidth Forms
(cp >= 0xff01 && cp <= 0xff60) ||
(cp >= 0xffe0 && cp <= 0xffe6) ||
// Kana Supplement
(cp >= 0x1b000 && cp <= 0x1b001) ||
// Enclosed Ideographic Supplement
(cp >= 0x1f200 && cp <= 0x1f251) ||
// CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
(cp >= 0x20000 && cp <= 0x3fffd))
) {
return true;
}
return false;
}

+ 39
- 46
src/system/version.ts View File

@ -1,59 +1,52 @@
'use strict';
export namespace Versions {
declare type VersionComparisonResult = -1 | 0 | 1;
export interface Version {
major: number;
minor: number;
patch: number;
pre?: string;
}
declare type VersionComparisonResult = -1 | 0 | 1;
export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult {
if (typeof v1 === 'string') {
v1 = fromString(v1);
}
if (typeof v2 === 'string') {
v2 = fromString(v2);
}
export interface Version {
major: number;
minor: number;
patch: number;
pre?: string;
}
if (v1.major > v2.major) return 1;
if (v1.major < v2.major) return -1;
export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult {
if (typeof v1 === 'string') {
v1 = fromString(v1);
}
if (typeof v2 === 'string') {
v2 = fromString(v2);
}
if (v1.minor > v2.minor) return 1;
if (v1.minor < v2.minor) return -1;
if (v1.major > v2.major) return 1;
if (v1.major < v2.major) return -1;
if (v1.patch > v2.patch) return 1;
if (v1.patch < v2.patch) return -1;
if (v1.minor > v2.minor) return 1;
if (v1.minor < v2.minor) return -1;
if (v1.pre === undefined && v2.pre !== undefined) return 1;
if (v1.pre !== undefined && v2.pre === undefined) return -1;
if (v1.patch > v2.patch) return 1;
if (v1.patch < v2.patch) return -1;
if (v1.pre !== undefined && v2.pre !== undefined) {
return v1.pre.localeCompare(v2.pre) as VersionComparisonResult;
}
if (v1.pre === undefined && v2.pre !== undefined) return 1;
if (v1.pre !== undefined && v2.pre === undefined) return -1;
return 0;
if (v1.pre !== undefined && v2.pre !== undefined) {
return v1.pre.localeCompare(v2.pre) as VersionComparisonResult;
}
export function from(
major: string | number,
minor: string | number,
patch?: string | number,
pre?: string,
): Version {
return {
major: typeof major === 'string' ? parseInt(major, 10) : major,
minor: typeof minor === 'string' ? parseInt(minor, 10) : minor,
patch: patch == null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch,
pre: pre,
};
}
return 0;
}
export function fromString(version: string): Version {
const [ver, pre] = version.split('-');
const [major, minor, patch] = ver.split('.');
return from(major, minor, patch, pre);
}
export function from(major: string | number, minor: string | number, patch?: string | number, pre?: string): Version {
return {
major: typeof major === 'string' ? parseInt(major, 10) : major,
minor: typeof minor === 'string' ? parseInt(minor, 10) : minor,
patch: patch == null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch,
pre: pre,
};
}
export function fromString(version: string): Version {
const [ver, pre] = version.split('-');
const [major, minor, patch] = ver.split('.');
return from(major, minor, patch, pre);
}

+ 7
- 3
src/trackers/documentTracker.ts View File

@ -19,7 +19,7 @@ import {
import { configuration } from '../configuration';
import { CommandContext, DocumentSchemes, isActiveDocument, isTextEditor, setCommandContext } from '../constants';
import { GitUri } from '../git/gitUri';
import { Deferrable, Functions } from '../system';
import { Functions } from '../system';
import { DocumentBlameStateChangeEvent, TrackedDocument } from './trackedDocument';
export * from './trackedDocument';
@ -315,8 +315,12 @@ export class DocumentTracker implements Disposable {
return doc;
}
private _dirtyIdleTriggeredDebounced: Deferrable<(e: DocumentDirtyIdleTriggerEvent<T>) => void> | undefined;
private _dirtyStateChangedDebounced: Deferrable<(e: DocumentDirtyStateChangeEvent<T>) => void> | undefined;
private _dirtyIdleTriggeredDebounced:
| Functions.Deferrable<(e: DocumentDirtyIdleTriggerEvent<T>) => void>
| undefined;
private _dirtyStateChangedDebounced:
| Functions.Deferrable<(e: DocumentDirtyStateChangeEvent<T>) => void>
| undefined;
private fireDocumentDirtyStateChanged(e: DocumentDirtyStateChangeEvent<T>) {
if (e.dirty) {
setImmediate(async () => {

+ 2
- 2
src/trackers/lineTracker.ts View File

@ -1,7 +1,7 @@
'use strict';
import { Disposable, Event, EventEmitter, Selection, TextEditor, TextEditorSelectionChangeEvent, window } from 'vscode';
import { isTextEditor } from '../constants';
import { debug, Deferrable, Functions } from '../system';
import { debug, Functions } from '../system';
export interface LinesChangeEvent {
readonly editor: TextEditor | undefined;
@ -199,7 +199,7 @@ export class LineTracker implements Disposable {
this.onLinesChanged({ editor: this._editor, selections: this.selections, reason: reason });
}
private _linesChangedDebounced: Deferrable<(e: LinesChangeEvent) => void> | undefined;
private _linesChangedDebounced: Functions.Deferrable<(e: LinesChangeEvent) => void> | undefined;
private onLinesChanged(e: LinesChangeEvent) {
if (e.selections === undefined) {

Loading…
Cancel
Save