mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 09:35:40 -05:00
Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
This commit is contained in:
@@ -764,5 +764,5 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries:
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(lastError);
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as streams from 'vs/base/common/stream';
|
||||
|
||||
declare var Buffer: any;
|
||||
export const hasBuffer = (typeof Buffer !== 'undefined');
|
||||
|
||||
const hasBuffer = (typeof Buffer !== 'undefined');
|
||||
const hasTextEncoder = (typeof TextEncoder !== 'undefined');
|
||||
const hasTextDecoder = (typeof TextDecoder !== 'undefined');
|
||||
|
||||
let textEncoder: TextEncoder | null;
|
||||
let textDecoder: TextDecoder | null;
|
||||
@@ -31,11 +37,13 @@ export class VSBuffer {
|
||||
static fromString(source: string): VSBuffer {
|
||||
if (hasBuffer) {
|
||||
return new VSBuffer(Buffer.from(source));
|
||||
} else {
|
||||
} else if (hasTextEncoder) {
|
||||
if (!textEncoder) {
|
||||
textEncoder = new TextEncoder();
|
||||
}
|
||||
return new VSBuffer(textEncoder.encode(source));
|
||||
} else {
|
||||
return new VSBuffer(strings.encodeUTF8(source));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +77,13 @@ export class VSBuffer {
|
||||
toString(): string {
|
||||
if (hasBuffer) {
|
||||
return this.buffer.toString();
|
||||
} else {
|
||||
} else if (hasTextDecoder) {
|
||||
if (!textDecoder) {
|
||||
textDecoder = new TextDecoder();
|
||||
}
|
||||
return textDecoder.decode(this.buffer);
|
||||
} else {
|
||||
return strings.decodeUTF8(this.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,335 +142,32 @@ function writeUInt8(destination: Uint8Array, value: number, offset: number): voi
|
||||
destination[offset] = value;
|
||||
}
|
||||
|
||||
export interface VSBufferReadable {
|
||||
export interface VSBufferReadable extends streams.Readable<VSBuffer> { }
|
||||
|
||||
/**
|
||||
* Read data from the underlying source. Will return
|
||||
* null to indicate that no more data can be read.
|
||||
*/
|
||||
read(): VSBuffer | null;
|
||||
}
|
||||
export interface VSBufferReadableStream extends streams.ReadableStream<VSBuffer> { }
|
||||
|
||||
export interface ReadableStream<T> {
|
||||
export interface VSBufferWriteableStream extends streams.WriteableStream<VSBuffer> { }
|
||||
|
||||
/**
|
||||
* The 'data' event is emitted whenever the stream is
|
||||
* relinquishing ownership of a chunk of data to a consumer.
|
||||
*/
|
||||
on(event: 'data', callback: (chunk: T) => void): void;
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
|
||||
/**
|
||||
* The 'end' event is emitted when there is no more data
|
||||
* to be consumed from the stream. The 'end' event will
|
||||
* not be emitted unless the data is completely consumed.
|
||||
*/
|
||||
on(event: 'end', callback: () => void): void;
|
||||
|
||||
/**
|
||||
* Stops emitting any events until resume() is called.
|
||||
*/
|
||||
pause?(): void;
|
||||
|
||||
/**
|
||||
* Starts emitting events again after pause() was called.
|
||||
*/
|
||||
resume?(): void;
|
||||
|
||||
/**
|
||||
* Destroys the stream and stops emitting any event.
|
||||
*/
|
||||
destroy?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A readable stream that sends data via VSBuffer.
|
||||
*/
|
||||
export interface VSBufferReadableStream extends ReadableStream<VSBuffer> {
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export function isVSBufferReadableStream(obj: any): obj is VSBufferReadableStream {
|
||||
const candidate: VSBufferReadableStream = obj;
|
||||
|
||||
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer readable into a single buffer.
|
||||
*/
|
||||
export function readableToBuffer(readable: VSBufferReadable): VSBuffer {
|
||||
const chunks: VSBuffer[] = [];
|
||||
|
||||
let chunk: VSBuffer | null;
|
||||
while (chunk = readable.read()) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return VSBuffer.concat(chunks);
|
||||
return streams.consumeReadable<VSBuffer>(readable, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert a buffer into a readable buffer.
|
||||
*/
|
||||
export function bufferToReadable(buffer: VSBuffer): VSBufferReadable {
|
||||
let done = false;
|
||||
|
||||
return {
|
||||
read: () => {
|
||||
if (done) {
|
||||
return null;
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
return streams.toReadable<VSBuffer>(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a VSBuffer stream into a single buffer.
|
||||
*/
|
||||
export function streamToBuffer(stream: VSBufferReadableStream): Promise<VSBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: VSBuffer[] = [];
|
||||
|
||||
stream.on('data', chunk => chunks.push(chunk));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(VSBuffer.concat(chunks)));
|
||||
});
|
||||
export function streamToBuffer(stream: streams.ReadableStream<VSBuffer>): Promise<VSBuffer> {
|
||||
return streams.consumeStream<VSBuffer>(stream, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream from an existing VSBuffer.
|
||||
*/
|
||||
export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream {
|
||||
const stream = writeableBufferStream();
|
||||
|
||||
stream.end(buffer);
|
||||
|
||||
return stream;
|
||||
export function bufferToStream(buffer: VSBuffer): streams.ReadableStream<VSBuffer> {
|
||||
return streams.toStream<VSBuffer>(buffer, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream from a Uint8Array stream.
|
||||
*/
|
||||
export function toVSBufferReadableStream(stream: ReadableStream<Uint8Array | string>): VSBufferReadableStream {
|
||||
const vsbufferStream = writeableBufferStream();
|
||||
|
||||
stream.on('data', data => vsbufferStream.write(typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data)));
|
||||
stream.on('end', () => vsbufferStream.end());
|
||||
stream.on('error', error => vsbufferStream.error(error));
|
||||
|
||||
return vsbufferStream;
|
||||
export function streamToBufferReadableStream(stream: streams.ReadableStreamEvents<Uint8Array | string>): streams.ReadableStream<VSBuffer> {
|
||||
return streams.transform<Uint8Array | string, VSBuffer>(stream, { data: data => typeof data === 'string' ? VSBuffer.fromString(data) : VSBuffer.wrap(data) }, chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a VSBufferStream that can be pushed
|
||||
* buffers to. Will only start to emit data when a listener
|
||||
* is added.
|
||||
*/
|
||||
export function writeableBufferStream(): VSBufferWriteableStream {
|
||||
return new VSBufferWriteableStreamImpl();
|
||||
}
|
||||
|
||||
export interface VSBufferWriteableStream extends VSBufferReadableStream {
|
||||
write(chunk: VSBuffer): void;
|
||||
error(error: Error): void;
|
||||
end(result?: VSBuffer | Error): void;
|
||||
}
|
||||
|
||||
class VSBufferWriteableStreamImpl implements VSBufferWriteableStream {
|
||||
|
||||
private readonly state = {
|
||||
flowing: false,
|
||||
ended: false,
|
||||
destroyed: false
|
||||
};
|
||||
|
||||
private readonly buffer = {
|
||||
data: [] as VSBuffer[],
|
||||
error: [] as Error[]
|
||||
};
|
||||
|
||||
private readonly listeners = {
|
||||
data: [] as { (chunk: VSBuffer): void }[],
|
||||
error: [] as { (error: Error): void }[],
|
||||
end: [] as { (): void }[]
|
||||
};
|
||||
|
||||
pause(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.flowing = false;
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.flowing) {
|
||||
this.state.flowing = true;
|
||||
|
||||
// emit buffered events
|
||||
this.flowData();
|
||||
this.flowErrors();
|
||||
this.flowEnd();
|
||||
}
|
||||
}
|
||||
|
||||
write(chunk: VSBuffer): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(chunk));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
else {
|
||||
this.buffer.data.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
else {
|
||||
this.buffer.error.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: VSBuffer | Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (result) {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// not yet flowing: remember state
|
||||
else {
|
||||
this.state.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (chunk: VSBuffer) => void): void;
|
||||
on(event: 'error', callback: (err: any) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
this.resume();
|
||||
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
//
|
||||
// finish() when it went through
|
||||
if (this.state.flowing && this.flowEnd()) {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
if (this.state.flowing) {
|
||||
this.flowErrors();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private flowData(): void {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = VSBuffer.concat(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this.state.destroyed) {
|
||||
this.state.destroyed = true;
|
||||
this.state.ended = true;
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
this.buffer.error.length = 0;
|
||||
|
||||
this.listeners.data.length = 0;
|
||||
this.listeners.error.length = 0;
|
||||
this.listeners.end.length = 0;
|
||||
}
|
||||
}
|
||||
export function newWriteableBufferStream(): streams.WriteableStream<VSBuffer> {
|
||||
return streams.newWriteableStream<VSBuffer>(chunks => VSBuffer.concat(chunks));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export class Cache<T> {
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
const promise = this.task(cts.token);
|
||||
promise.finally(() => cts.dispose());
|
||||
|
||||
this.result = {
|
||||
promise,
|
||||
|
||||
@@ -399,6 +399,23 @@ export class Color {
|
||||
return new Color(new RGBA(r, g, b, a));
|
||||
}
|
||||
|
||||
makeOpaque(opaqueBackground: Color): Color {
|
||||
if (this.isOpaque() || opaqueBackground.rgba.a !== 1) {
|
||||
// only allow to blend onto a non-opaque color onto a opaque color
|
||||
return this;
|
||||
}
|
||||
|
||||
const { r, g, b, a } = this.rgba;
|
||||
|
||||
// https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity
|
||||
return new Color(new RGBA(
|
||||
opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r),
|
||||
opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g),
|
||||
opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b),
|
||||
1
|
||||
));
|
||||
}
|
||||
|
||||
flatten(...backgrounds: Color[]): Color {
|
||||
const background = backgrounds.reduceRight((accumulator, color) => {
|
||||
return Color._flatten(color, accumulator);
|
||||
|
||||
@@ -195,7 +195,6 @@ export function getErrorMessage(err: any): string {
|
||||
return String(err);
|
||||
}
|
||||
|
||||
|
||||
export class NotImplementedError extends Error {
|
||||
constructor(message?: string) {
|
||||
super('NotImplemented');
|
||||
|
||||
@@ -7,6 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
@@ -653,27 +654,39 @@ export interface IWaitUntil {
|
||||
|
||||
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
|
||||
private _asyncDeliveryQueue?: [Listener<T>, T, Promise<any>[]][];
|
||||
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
|
||||
|
||||
async fireAsync(eventFn: (thenables: Promise<any>[], listener: Function) => T): Promise<void> {
|
||||
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<any>, listener: Function) => Promise<any>): Promise<void> {
|
||||
if (!this._listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
// put all [listener,event]-pairs into delivery queue
|
||||
// then emit all event. an inner/nested event might be
|
||||
// the driver of this
|
||||
if (!this._asyncDeliveryQueue) {
|
||||
this._asyncDeliveryQueue = [];
|
||||
this._asyncDeliveryQueue = new LinkedList();
|
||||
}
|
||||
|
||||
for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) {
|
||||
const thenables: Promise<void>[] = [];
|
||||
this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]);
|
||||
this._asyncDeliveryQueue.push([e.value, data]);
|
||||
}
|
||||
|
||||
while (this._asyncDeliveryQueue.length > 0) {
|
||||
const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!;
|
||||
while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) {
|
||||
|
||||
const [listener, data] = this._asyncDeliveryQueue.shift()!;
|
||||
const thenables: Promise<any>[] = [];
|
||||
|
||||
const event = <T>{
|
||||
...data,
|
||||
waitUntil: (p: Promise<any>): void => {
|
||||
if (Object.isFrozen(thenables)) {
|
||||
throw new Error('waitUntil can NOT be called asynchronous');
|
||||
}
|
||||
if (promiseJoin) {
|
||||
p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
|
||||
}
|
||||
thenables.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (typeof listener === 'function') {
|
||||
listener.call(undefined, event);
|
||||
@@ -688,7 +701,7 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
// freeze thenables-collection to enforce sync-calls to
|
||||
// wait until and then wait for all thenables to resolve
|
||||
Object.freeze(thenables);
|
||||
await Promise.all(thenables);
|
||||
await Promise.all(thenables).catch(e => onUnexpectedError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ export function fuzzyScore(pattern: string, patternLow: string, patternStart: nu
|
||||
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||
|
||||
if (patternStart >= patternLen || wordStart >= wordLen || patternLen > wordLen) {
|
||||
if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,13 +172,28 @@ export module Iterator {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function chain<T>(iterator: Iterator<T>): ChainableIterator<T> {
|
||||
return new ChainableIterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChainableIterator<T> implements Iterator<T> {
|
||||
|
||||
constructor(private it: Iterator<T>) { }
|
||||
|
||||
next(): IteratorResult<T> { return this.it.next(); }
|
||||
map<R>(fn: (t: T) => R): ChainableIterator<R> { return new ChainableIterator(Iterator.map(this.it, fn)); }
|
||||
filter(fn: (t: T) => boolean): ChainableIterator<T> { return new ChainableIterator(Iterator.filter(this.it, fn)); }
|
||||
}
|
||||
|
||||
export type ISequence<T> = Iterator<T> | T[];
|
||||
|
||||
export function getSequenceIterator<T>(arg: Iterator<T> | T[]): Iterator<T> {
|
||||
export function getSequenceIterator<T>(arg: ISequence<T> | undefined): Iterator<T> {
|
||||
if (Array.isArray(arg)) {
|
||||
return Iterator.fromArray(arg);
|
||||
} else if (!arg) {
|
||||
return Iterator.empty();
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
@@ -271,7 +286,7 @@ export interface INavigator<T> extends INextIterator<T> {
|
||||
|
||||
export class MappedNavigator<T, R> extends MappedIterator<T, R> implements INavigator<R> {
|
||||
|
||||
constructor(protected navigator: INavigator<T>, fn: (item: T) => R) {
|
||||
constructor(protected navigator: INavigator<T>, fn: (item: T | null) => R) {
|
||||
super(navigator, fn);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface Location {
|
||||
export interface ParseOptions {
|
||||
disallowComments?: boolean;
|
||||
allowTrailingComma?: boolean;
|
||||
allowEmptyContent?: boolean;
|
||||
}
|
||||
|
||||
export namespace ParseOptions {
|
||||
@@ -785,7 +786,7 @@ export function getLocation(text: string, position: number): Location {
|
||||
if (position < offset) {
|
||||
throw earlyReturnException;
|
||||
}
|
||||
setPreviousNode(value, offset, length, getLiteralNodeType(value));
|
||||
setPreviousNode(value, offset, length, getNodeType(value));
|
||||
|
||||
if (position <= offset + length) {
|
||||
throw earlyReturnException;
|
||||
@@ -848,7 +849,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt
|
||||
function onValue(value: any) {
|
||||
if (Array.isArray(currentParent)) {
|
||||
(<any[]>currentParent).push(value);
|
||||
} else if (currentProperty) {
|
||||
} else if (currentProperty !== null) {
|
||||
currentParent[currentProperty] = value;
|
||||
}
|
||||
}
|
||||
@@ -927,7 +928,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars
|
||||
ensurePropertyComplete(offset + length);
|
||||
},
|
||||
onLiteralValue: (value: any, offset: number, length: number) => {
|
||||
onValue({ type: getLiteralNodeType(value), offset, length, parent: currentParent, value });
|
||||
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
||||
ensurePropertyComplete(offset + length);
|
||||
},
|
||||
onSeparator: (sep: string, offset: number, length: number) => {
|
||||
@@ -1287,7 +1288,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions
|
||||
|
||||
scanNext();
|
||||
if (_scanner.getToken() === SyntaxKind.EOF) {
|
||||
return true;
|
||||
if (options.allowEmptyContent) {
|
||||
return true;
|
||||
}
|
||||
handleError(ParseErrorCode.ValueExpected, [], []);
|
||||
return false;
|
||||
}
|
||||
if (!parseValue()) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], []);
|
||||
@@ -1333,11 +1338,19 @@ export function stripComments(text: string, replaceCh?: string): string {
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function getLiteralNodeType(value: any): NodeType {
|
||||
export function getNodeType(value: any): NodeType {
|
||||
switch (typeof value) {
|
||||
case 'boolean': return 'boolean';
|
||||
case 'number': return 'number';
|
||||
case 'string': return 'string';
|
||||
case 'object': {
|
||||
if (!value) {
|
||||
return 'null';
|
||||
} else if (Array.isArray(value)) {
|
||||
return 'array';
|
||||
}
|
||||
return 'object';
|
||||
}
|
||||
default: return 'null';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,40 +84,36 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
}
|
||||
} else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) {
|
||||
const insertIndex = lastSegment;
|
||||
if (insertIndex === -1) {
|
||||
if (value !== undefined) {
|
||||
// Insert
|
||||
const newProperty = `${JSON.stringify(value)}`;
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: newProperty };
|
||||
if (parent.children.length === 0 || lastSegment === 0) {
|
||||
edit = { offset: parent.offset + 1, length: 0, content: parent.children.length === 0 ? newProperty : newProperty + ',' };
|
||||
} else {
|
||||
const previous = parent.children[parent.children.length - 1];
|
||||
const index = lastSegment === -1 || lastSegment > parent.children.length ? parent.children.length : lastSegment;
|
||||
const previous = parent.children[index - 1];
|
||||
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
} else {
|
||||
if (value === undefined && parent.children.length >= 0) {
|
||||
//Removal
|
||||
const removalIndex = lastSegment;
|
||||
const toRemove = parent.children[removalIndex];
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 1) {
|
||||
// only item
|
||||
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
||||
} else if (parent.children.length - 1 === removalIndex) {
|
||||
// last item
|
||||
const previous = parent.children[removalIndex - 1];
|
||||
const offset = previous.offset + previous.length;
|
||||
const parentEndOffset = parent.offset + parent.length;
|
||||
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
|
||||
} else {
|
||||
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
//Removal
|
||||
const removalIndex = lastSegment;
|
||||
const toRemove = parent.children[removalIndex];
|
||||
let edit: Edit;
|
||||
if (parent.children.length === 1) {
|
||||
// only item
|
||||
edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' };
|
||||
} else if (parent.children.length - 1 === removalIndex) {
|
||||
// last item
|
||||
const previous = parent.children[removalIndex - 1];
|
||||
const offset = previous.offset + previous.length;
|
||||
const parentEndOffset = parent.offset + parent.length;
|
||||
edit = { offset, length: parentEndOffset - 2 - offset, content: '' };
|
||||
} else {
|
||||
throw new Error('Array modification not supported yet');
|
||||
edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' };
|
||||
}
|
||||
return withFormatting(text, edit, formattingOptions);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
|
||||
|
||||
export interface IJSONSchema {
|
||||
id?: string;
|
||||
$id?: string;
|
||||
$schema?: string;
|
||||
type?: string | string[];
|
||||
type?: JSONSchemaType | JSONSchemaType[];
|
||||
title?: string;
|
||||
default?: any;
|
||||
definitions?: IJSONSchemaMap;
|
||||
|
||||
@@ -54,6 +54,11 @@ export class Lazy<T> {
|
||||
return this._value!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapped value without forcing evaluation.
|
||||
*/
|
||||
get rawValue(): T | undefined { return this._value; }
|
||||
|
||||
/**
|
||||
* Create a new lazy value that is the result of applying `f` to the wrapped value.
|
||||
*
|
||||
|
||||
@@ -206,43 +206,6 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class that stores a disposable that is not currently "owned" by anyone.
|
||||
*
|
||||
* Example use cases:
|
||||
*
|
||||
* - Express that a function/method will take ownership of a disposable parameter.
|
||||
* - Express that a function returns a disposable that the caller must explicitly take ownership of.
|
||||
*/
|
||||
export class UnownedDisposable<T extends IDisposable> extends Disposable {
|
||||
private _hasBeenAcquired = false;
|
||||
private _value?: T;
|
||||
|
||||
public constructor(value: T) {
|
||||
super();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public acquire(): T {
|
||||
if (this._hasBeenAcquired) {
|
||||
throw new Error('This disposable has already been acquired');
|
||||
}
|
||||
this._hasBeenAcquired = true;
|
||||
const value = this._value!;
|
||||
this._value = undefined;
|
||||
return value;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
if (!this._hasBeenAcquired) {
|
||||
this._hasBeenAcquired = true;
|
||||
this._value!.dispose();
|
||||
this._value = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IReference<T> extends IDisposable {
|
||||
readonly object: T;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,14 @@ export function suggestFilename(mode: string | undefined, prefix: string): strin
|
||||
.filter(assoc => startsWith(assoc, '.'));
|
||||
|
||||
if (extensionsWithDotFirst.length > 0) {
|
||||
return prefix + extensionsWithDotFirst[0];
|
||||
const candidateExtension = extensionsWithDotFirst[0];
|
||||
if (endsWith(prefix, candidateExtension)) {
|
||||
// do not add the prefix if it already exists
|
||||
// https://github.com/microsoft/vscode/issues/83603
|
||||
return prefix;
|
||||
}
|
||||
|
||||
return prefix + candidateExtension;
|
||||
}
|
||||
|
||||
return extensions[0] || prefix;
|
||||
|
||||
@@ -69,11 +69,11 @@ function validateString(value: string, name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isPathSeparator(code: number) {
|
||||
function isPathSeparator(code: number | undefined) {
|
||||
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
|
||||
}
|
||||
|
||||
function isPosixPathSeparator(code: number) {
|
||||
function isPosixPathSeparator(code: number | undefined) {
|
||||
return code === CHAR_FORWARD_SLASH;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ let _isMacintosh = false;
|
||||
let _isLinux = false;
|
||||
let _isNative = false;
|
||||
let _isWeb = false;
|
||||
let _isIOS = false;
|
||||
let _locale: string | undefined = undefined;
|
||||
let _language: string = LANGUAGE_DEFAULT;
|
||||
let _translationsConfigFile: string | undefined = undefined;
|
||||
@@ -41,6 +42,7 @@ declare const global: any;
|
||||
interface INavigator {
|
||||
userAgent: string;
|
||||
language: string;
|
||||
maxTouchPoints?: number;
|
||||
}
|
||||
declare const navigator: INavigator;
|
||||
declare const self: any;
|
||||
@@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
_userAgent = navigator.userAgent;
|
||||
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
||||
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
||||
_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
||||
_isWeb = true;
|
||||
_locale = navigator.language;
|
||||
@@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
|
||||
export const isLinux = _isLinux;
|
||||
export const isNative = _isNative;
|
||||
export const isWeb = _isWeb;
|
||||
export const isIOS = _isIOS;
|
||||
export const platform = _platform;
|
||||
export const userAgent = _userAgent;
|
||||
|
||||
@@ -189,7 +193,7 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() {
|
||||
id: myId,
|
||||
callback: callback
|
||||
});
|
||||
globals.postMessage({ vscodeSetImmediateId: myId });
|
||||
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
|
||||
};
|
||||
}
|
||||
if (typeof process !== 'undefined' && typeof process.nextTick === 'function') {
|
||||
|
||||
487
src/vs/base/common/stream.ts
Normal file
487
src/vs/base/common/stream.ts
Normal file
@@ -0,0 +1,487 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* The payload that flows in readable stream events.
|
||||
*/
|
||||
export type ReadableStreamEventPayload<T> = T | Error | 'end';
|
||||
|
||||
export interface ReadableStreamEvents<T> {
|
||||
|
||||
/**
|
||||
* The 'data' event is emitted whenever the stream is
|
||||
* relinquishing ownership of a chunk of data to a consumer.
|
||||
*/
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
|
||||
/**
|
||||
* Emitted when any error occurs.
|
||||
*/
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
|
||||
/**
|
||||
* The 'end' event is emitted when there is no more data
|
||||
* to be consumed from the stream. The 'end' event will
|
||||
* not be emitted unless the data is completely consumed.
|
||||
*/
|
||||
on(event: 'end', callback: () => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js readable
|
||||
* stream for use in desktop and web environments.
|
||||
*/
|
||||
export interface ReadableStream<T> extends ReadableStreamEvents<T> {
|
||||
|
||||
/**
|
||||
* Stops emitting any events until resume() is called.
|
||||
*/
|
||||
pause(): void;
|
||||
|
||||
/**
|
||||
* Starts emitting events again after pause() was called.
|
||||
*/
|
||||
resume(): void;
|
||||
|
||||
/**
|
||||
* Destroys the stream and stops emitting any event.
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js readable
|
||||
* for use in desktop and web environments.
|
||||
*/
|
||||
export interface Readable<T> {
|
||||
|
||||
/**
|
||||
* Read data from the underlying source. Will return
|
||||
* null to indicate that no more data can be read.
|
||||
*/
|
||||
read(): T | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A interface that emulates the API shape of a node.js writeable
|
||||
* stream for use in desktop and web environments.
|
||||
*/
|
||||
export interface WriteableStream<T> extends ReadableStream<T> {
|
||||
|
||||
/**
|
||||
* Writing data to the stream will trigger the on('data')
|
||||
* event listener if the stream is flowing and buffer the
|
||||
* data otherwise until the stream is flowing.
|
||||
*/
|
||||
write(data: T): void;
|
||||
|
||||
/**
|
||||
* Signals an error to the consumer of the stream via the
|
||||
* on('error') handler if the stream is flowing.
|
||||
*/
|
||||
error(error: Error): void;
|
||||
|
||||
/**
|
||||
* Signals the end of the stream to the consumer. If the
|
||||
* result is not an error, will trigger the on('data') event
|
||||
* listener if the stream is flowing and buffer the data
|
||||
* otherwise until the stream is flowing.
|
||||
*
|
||||
* In case of an error, the on('error') event will be used
|
||||
* if the stream is flowing.
|
||||
*/
|
||||
end(result?: T | Error): void;
|
||||
}
|
||||
|
||||
export function isReadableStream<T>(obj: any): obj is ReadableStream<T> {
|
||||
const candidate: ReadableStream<T> = obj;
|
||||
|
||||
return candidate && [candidate.on, candidate.pause, candidate.resume, candidate.destroy].every(fn => typeof fn === 'function');
|
||||
}
|
||||
|
||||
export interface IReducer<T> {
|
||||
(data: T[]): T;
|
||||
}
|
||||
|
||||
export interface IDataTransformer<Original, Transformed> {
|
||||
(data: Original): Transformed;
|
||||
}
|
||||
|
||||
export interface IErrorTransformer {
|
||||
(error: Error): Error;
|
||||
}
|
||||
|
||||
export interface ITransformer<Original, Transformed> {
|
||||
data: IDataTransformer<Original, Transformed>;
|
||||
error?: IErrorTransformer;
|
||||
}
|
||||
|
||||
export function newWriteableStream<T>(reducer: IReducer<T>): WriteableStream<T> {
|
||||
return new WriteableStreamImpl<T>(reducer);
|
||||
}
|
||||
|
||||
class WriteableStreamImpl<T> implements WriteableStream<T> {
|
||||
|
||||
private readonly state = {
|
||||
flowing: false,
|
||||
ended: false,
|
||||
destroyed: false
|
||||
};
|
||||
|
||||
private readonly buffer = {
|
||||
data: [] as T[],
|
||||
error: [] as Error[]
|
||||
};
|
||||
|
||||
private readonly listeners = {
|
||||
data: [] as { (data: T): void }[],
|
||||
error: [] as { (error: Error): void }[],
|
||||
end: [] as { (): void }[]
|
||||
};
|
||||
|
||||
constructor(private reducer: IReducer<T>) { }
|
||||
|
||||
pause(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.flowing = false;
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.flowing) {
|
||||
this.state.flowing = true;
|
||||
|
||||
// emit buffered events
|
||||
this.flowData();
|
||||
this.flowErrors();
|
||||
this.flowEnd();
|
||||
}
|
||||
}
|
||||
|
||||
write(data: T): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the data to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.data.forEach(listener => listener(data));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer data until flowing
|
||||
else {
|
||||
this.buffer.data.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
error(error: Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flowing: directly send the error to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
// not yet flowing: buffer errors until flowing
|
||||
else {
|
||||
this.buffer.error.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
end(result?: T | Error): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// end with data or error if provided
|
||||
if (result instanceof Error) {
|
||||
this.error(result);
|
||||
} else if (result) {
|
||||
this.write(result);
|
||||
}
|
||||
|
||||
// flowing: send end event to listeners
|
||||
if (this.state.flowing) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
// not yet flowing: remember state
|
||||
else {
|
||||
this.state.ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
on(event: 'data', callback: (data: T) => void): void;
|
||||
on(event: 'error', callback: (err: Error) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void {
|
||||
if (this.state.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'data':
|
||||
this.listeners.data.push(callback);
|
||||
|
||||
// switch into flowing mode as soon as the first 'data'
|
||||
// listener is added and we are not yet in flowing mode
|
||||
this.resume();
|
||||
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
this.listeners.end.push(callback);
|
||||
|
||||
// emit 'end' event directly if we are flowing
|
||||
// and the end has already been reached
|
||||
//
|
||||
// finish() when it went through
|
||||
if (this.state.flowing && this.flowEnd()) {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
this.listeners.error.push(callback);
|
||||
|
||||
// emit buffered 'error' events unless done already
|
||||
// now that we know that we have at least one listener
|
||||
if (this.state.flowing) {
|
||||
this.flowErrors();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private flowData(): void {
|
||||
if (this.buffer.data.length > 0) {
|
||||
const fullDataBuffer = this.reducer(this.buffer.data);
|
||||
|
||||
this.listeners.data.forEach(listener => listener(fullDataBuffer));
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowErrors(): void {
|
||||
if (this.listeners.error.length > 0) {
|
||||
for (const error of this.buffer.error) {
|
||||
this.listeners.error.forEach(listener => listener(error));
|
||||
}
|
||||
|
||||
this.buffer.error.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private flowEnd(): boolean {
|
||||
if (this.state.ended) {
|
||||
this.listeners.end.forEach(listener => listener());
|
||||
|
||||
return this.listeners.end.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this.state.destroyed) {
|
||||
this.state.destroyed = true;
|
||||
this.state.ended = true;
|
||||
|
||||
this.buffer.data.length = 0;
|
||||
this.buffer.error.length = 0;
|
||||
|
||||
this.listeners.data.length = 0;
|
||||
this.listeners.error.length = 0;
|
||||
this.listeners.end.length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a T readable into a T.
|
||||
*/
|
||||
export function consumeReadable<T>(readable: Readable<T>, reducer: IReducer<T>): T {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let chunk: T | null;
|
||||
while ((chunk = readable.read()) !== null) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
return reducer(chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a T readable up to a maximum of chunks. If the limit is
|
||||
* reached, will return a readable instead to ensure all data can still
|
||||
* be read.
|
||||
*/
|
||||
export function consumeReadableWithLimit<T>(readable: Readable<T>, reducer: IReducer<T>, maxChunks: number): T | Readable<T> {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let chunk: T | null | undefined = undefined;
|
||||
while ((chunk = readable.read()) !== null && chunks.length < maxChunks) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// If the last chunk is null, it means we reached the end of
|
||||
// the readable and return all the data at once
|
||||
if (chunk === null && chunks.length > 0) {
|
||||
return reducer(chunks);
|
||||
}
|
||||
|
||||
// Otherwise, we still have a chunk, it means we reached the maxChunks
|
||||
// value and as such we return a new Readable that first returns
|
||||
// the existing read chunks and then continues with reading from
|
||||
// the underlying readable.
|
||||
return {
|
||||
read: () => {
|
||||
|
||||
// First consume chunks from our array
|
||||
if (chunks.length > 0) {
|
||||
return chunks.shift()!;
|
||||
}
|
||||
|
||||
// Then ensure to return our last read chunk
|
||||
if (typeof chunk !== 'undefined') {
|
||||
const lastReadChunk = chunk;
|
||||
|
||||
// explicitly use undefined here to indicate that we consumed
|
||||
// the chunk, which could have either been null or valued.
|
||||
chunk = undefined;
|
||||
|
||||
return lastReadChunk;
|
||||
}
|
||||
|
||||
// Finally delegate back to the Readable
|
||||
return readable.read();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to fully read a T stream into a T.
|
||||
*/
|
||||
export function consumeStream<T>(stream: ReadableStream<T>, reducer: IReducer<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: T[] = [];
|
||||
|
||||
stream.on('data', data => chunks.push(data));
|
||||
stream.on('error', error => reject(error));
|
||||
stream.on('end', () => resolve(reducer(chunks)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a T stream up to a maximum of chunks. If the limit is
|
||||
* reached, will return a stream instead to ensure all data can still
|
||||
* be read.
|
||||
*/
|
||||
export function consumeStreamWithLimit<T>(stream: ReadableStream<T>, reducer: IReducer<T>, maxChunks: number): Promise<T | ReadableStream<T>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: T[] = [];
|
||||
|
||||
let wrapperStream: WriteableStream<T> | undefined = undefined;
|
||||
|
||||
stream.on('data', data => {
|
||||
|
||||
// If we reach maxChunks, we start to return a stream
|
||||
// and make sure that any data we have already read
|
||||
// is in it as well
|
||||
if (!wrapperStream && chunks.length === maxChunks) {
|
||||
wrapperStream = newWriteableStream(reducer);
|
||||
while (chunks.length) {
|
||||
wrapperStream.write(chunks.shift()!);
|
||||
}
|
||||
|
||||
wrapperStream.write(data);
|
||||
|
||||
return resolve(wrapperStream);
|
||||
}
|
||||
|
||||
if (wrapperStream) {
|
||||
wrapperStream.write(data);
|
||||
} else {
|
||||
chunks.push(data);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', error => {
|
||||
if (wrapperStream) {
|
||||
wrapperStream.error(error);
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (wrapperStream) {
|
||||
while (chunks.length) {
|
||||
wrapperStream.write(chunks.shift()!);
|
||||
}
|
||||
|
||||
wrapperStream.end();
|
||||
} else {
|
||||
return resolve(reducer(chunks));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a readable stream from an existing T.
|
||||
*/
|
||||
export function toStream<T>(t: T, reducer: IReducer<T>): ReadableStream<T> {
|
||||
const stream = newWriteableStream<T>(reducer);
|
||||
|
||||
stream.end(t);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert a T into a Readable<T>.
|
||||
*/
|
||||
export function toReadable<T>(t: T): Readable<T> {
|
||||
let consumed = false;
|
||||
|
||||
return {
|
||||
read: () => {
|
||||
if (consumed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
consumed = true;
|
||||
|
||||
return t;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to transform a readable stream into another stream.
|
||||
*/
|
||||
export function transform<Original, Transformed>(stream: ReadableStreamEvents<Original>, transformer: ITransformer<Original, Transformed>, reducer: IReducer<Transformed>): ReadableStream<Transformed> {
|
||||
const target = newWriteableStream<Transformed>(reducer);
|
||||
|
||||
stream.on('data', data => target.write(transformer.data(data)));
|
||||
stream.on('end', () => target.end());
|
||||
stream.on('error', error => target.error(transformer.error ? transformer.error(error) : error));
|
||||
|
||||
return target;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
12
src/vs/base/common/styler.ts
Normal file
12
src/vs/base/common/styler.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export type styleFn = (colors: { [name: string]: Color | undefined }) => void;
|
||||
|
||||
export interface IThemable {
|
||||
style: styleFn;
|
||||
}
|
||||
@@ -93,6 +93,13 @@ export function isUndefinedOrNull(obj: any): obj is undefined | null {
|
||||
return isUndefined(obj) || obj === null;
|
||||
}
|
||||
|
||||
|
||||
export function assertType(condition: any, type?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the argument passed in is neither undefined nor null.
|
||||
*/
|
||||
|
||||
@@ -10,10 +10,10 @@ const _schemePattern = /^\w[\w\d+.-]*$/;
|
||||
const _singleSlashStart = /^\//;
|
||||
const _doubleSlashStart = /^\/\//;
|
||||
|
||||
function _validateUri(ret: URI): void {
|
||||
function _validateUri(ret: URI, _strict?: boolean): void {
|
||||
|
||||
// scheme, must be set
|
||||
if (!ret.scheme) {
|
||||
if (!ret.scheme && _strict) {
|
||||
throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`);
|
||||
}
|
||||
|
||||
@@ -41,11 +41,13 @@ function _validateUri(ret: URI): void {
|
||||
}
|
||||
}
|
||||
|
||||
// graceful behaviour when scheme is missing: fallback to using 'file'-scheme
|
||||
function _schemeFix(scheme: string): string {
|
||||
if (!scheme) {
|
||||
console.trace('BAD uri lacks scheme, falling back to file-scheme.');
|
||||
scheme = 'file';
|
||||
// for a while we allowed uris *without* schemes and this is the migration
|
||||
// for them, e.g. an uri without scheme and without strict-mode warns and falls
|
||||
// back to the file-scheme. that should cause the least carnage and still be a
|
||||
// clear warning
|
||||
function _schemeFix(scheme: string, _strict: boolean): string {
|
||||
if (!scheme && !_strict) {
|
||||
return 'file';
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
@@ -138,7 +140,7 @@ export class URI implements UriComponents {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string);
|
||||
protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -148,7 +150,7 @@ export class URI implements UriComponents {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) {
|
||||
protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) {
|
||||
|
||||
if (typeof schemeOrData === 'object') {
|
||||
this.scheme = schemeOrData.scheme || _empty;
|
||||
@@ -160,13 +162,13 @@ export class URI implements UriComponents {
|
||||
// that creates uri components.
|
||||
// _validateUri(this);
|
||||
} else {
|
||||
this.scheme = _schemeFix(schemeOrData);
|
||||
this.scheme = _schemeFix(schemeOrData, _strict);
|
||||
this.authority = authority || _empty;
|
||||
this.path = _referenceResolution(this.scheme, path || _empty);
|
||||
this.query = query || _empty;
|
||||
this.fragment = fragment || _empty;
|
||||
|
||||
_validateUri(this);
|
||||
_validateUri(this, _strict);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +207,7 @@ export class URI implements UriComponents {
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null; }): URI {
|
||||
with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI {
|
||||
|
||||
if (!change) {
|
||||
return this;
|
||||
@@ -258,17 +260,18 @@ export class URI implements UriComponents {
|
||||
*
|
||||
* @param value A string which represents an URI (see `URI#toString`).
|
||||
*/
|
||||
static parse(value: string): URI {
|
||||
static parse(value: string, _strict: boolean = false): URI {
|
||||
const match = _regexp.exec(value);
|
||||
if (!match) {
|
||||
return new _URI(_empty, _empty, _empty, _empty, _empty);
|
||||
}
|
||||
return new _URI(
|
||||
match[2] || _empty,
|
||||
decodeURIComponent(match[4] || _empty),
|
||||
decodeURIComponent(match[5] || _empty),
|
||||
decodeURIComponent(match[7] || _empty),
|
||||
decodeURIComponent(match[9] || _empty)
|
||||
percentDecode(match[4] || _empty),
|
||||
percentDecode(match[5] || _empty),
|
||||
percentDecode(match[7] || _empty),
|
||||
percentDecode(match[9] || _empty),
|
||||
_strict
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,7 +323,7 @@ export class URI implements UriComponents {
|
||||
return new _URI('file', authority, path, _empty, _empty);
|
||||
}
|
||||
|
||||
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string; }): URI {
|
||||
static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI {
|
||||
return new _URI(
|
||||
components.scheme,
|
||||
components.authority,
|
||||
@@ -445,7 +448,7 @@ class _URI extends URI {
|
||||
}
|
||||
|
||||
// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
const encodeTable: { [ch: number]: string; } = {
|
||||
const encodeTable: { [ch: number]: string } = {
|
||||
[CharCode.Colon]: '%3A', // gen-delims
|
||||
[CharCode.Slash]: '%2F',
|
||||
[CharCode.QuestionMark]: '%3F',
|
||||
@@ -646,3 +649,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// --- decode
|
||||
|
||||
function decodeURIComponentGraceful(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch {
|
||||
if (str.length > 3) {
|
||||
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
|
||||
|
||||
function percentDecode(str: string): string {
|
||||
if (!str.match(_rEncodedAsHex)) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user