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:
Anthony Dresser
2019-12-04 19:28:22 -08:00
committed by GitHub
parent a8818ab0df
commit f5ce7fb2a5
1507 changed files with 42813 additions and 27370 deletions

View File

@@ -764,5 +764,5 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries:
}
}
return Promise.reject(lastError);
throw lastError;
}

View File

@@ -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));
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -195,7 +195,6 @@ export function getErrorMessage(err: any): string {
return String(err);
}
export class NotImplementedError extends Error {
constructor(message?: string) {
super('NotImplemented');

View File

@@ -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));
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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';
}
}

View File

@@ -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}`);

View File

@@ -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;

View File

@@ -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.
*

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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') {

View 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

View 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;
}

View File

@@ -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.
*/

View File

@@ -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));
}