Merge from vscode 0f73473c08055054f317c1c94502f7f39fdbb164 (#6892)

* Merge from vscode 0f73473c08055054f317c1c94502f7f39fdbb164

* fix tslinting
This commit is contained in:
Anthony Dresser
2019-08-22 22:07:01 -07:00
committed by GitHub
parent 1372cbaee1
commit 658cf51887
91 changed files with 1092 additions and 317 deletions

View File

@@ -283,17 +283,30 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
// Respect option to reveal an editor if it is open (not necessarily visible)
// Still prefer to reveal an editor in a group where the editor is active though.
if (!targetGroup) {
if ((options && options.revealIfOpened) || this.configurationService.getValue<boolean>('workbench.editor.revealIfOpen')) {
let groupWithInputActive: IEditorGroup | undefined = undefined;
let groupWithInputOpened: IEditorGroup | undefined = undefined;
for (const group of groupsByLastActive) {
if (group.isOpened(input) && group.isActive(input)) {
targetGroup = group;
break;
if (group.isOpened(input)) {
if (!groupWithInputOpened) {
groupWithInputOpened = group;
}
if (!groupWithInputActive && group.isActive(input)) {
groupWithInputActive = group;
}
}
if (group.isOpened(input) && !targetGroup) {
targetGroup = group;
if (groupWithInputOpened && groupWithInputActive) {
break; // we found all groups we wanted
}
}
// Prefer a target group where the input is visible
targetGroup = groupWithInputActive || groupWithInputOpened;
}
}
}

View File

@@ -34,7 +34,6 @@ import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
// tslint:disable-next-line: import-patterns
import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint';
import { Disposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';

View File

@@ -5,23 +5,21 @@
import { KeyValueLogProvider } from 'vs/workbench/services/log/common/keyValueLogProvider';
export const INDEXEDDB_LOG_SCHEME = 'vscode-logs-indexedbd';
export const INDEXEDDB_LOGS_DB = 'vscode-logs-db';
export const INDEXEDDB_VSCODE_DB = 'vscode-web-db';
export const INDEXEDDB_LOGS_OBJECT_STORE = 'vscode-logs-store';
export class IndexedDBLogProvider extends KeyValueLogProvider {
private readonly database: Promise<IDBDatabase>;
readonly database: Promise<IDBDatabase>;
constructor(
) {
super(INDEXEDDB_LOG_SCHEME);
constructor(scheme: string) {
super(scheme);
this.database = this.openDatabase(1);
}
private openDatabase(version: number): Promise<IDBDatabase> {
return new Promise((c, e) => {
const request = window.indexedDB.open(INDEXEDDB_LOGS_DB, version);
const request = window.indexedDB.open(INDEXEDDB_VSCODE_DB, version);
request.onerror = (err) => e(request.error);
request.onsuccess = () => {
const db = request.result;

View File

@@ -6,17 +6,10 @@
import { KeyValueLogProvider } from 'vs/workbench/services/log/common/keyValueLogProvider';
import { keys } from 'vs/base/common/map';
export const INMEMORY_LOG_SCHEME = 'vscode-logs-inmemory';
export class InMemoryLogProvider extends KeyValueLogProvider {
private readonly logs: Map<string, string> = new Map<string, string>();
constructor(
) {
super(INMEMORY_LOG_SCHEME);
}
protected async getAllKeys(): Promise<string[]> {
return keys(this.logs);
}

View File

@@ -1028,6 +1028,67 @@ class SettingsContentBuilder {
}
export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) {
// Only for array of string
if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') {
const propItems = prop.items;
if (propItems && !isArray(propItems) && propItems.type === 'string') {
const withQuotes = (s: string) => `'` + s + `'`;
return value => {
if (!value) {
return null;
}
let message = '';
const stringArrayValue = value as string[];
if (prop.minItems && stringArrayValue.length < prop.minItems) {
message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems);
message += '\n';
}
if (prop.maxItems && stringArrayValue.length > prop.maxItems) {
message += nls.localize('validations.stringArrayMaxItem', 'Array must have less than {0} items', prop.maxItems);
message += '\n';
}
if (typeof propItems.pattern === 'string') {
const patternRegex = new RegExp(propItems.pattern);
stringArrayValue.forEach(v => {
if (!patternRegex.test(v)) {
message +=
propItems.patternErrorMessage ||
nls.localize(
'validations.stringArrayItemPattern',
'Value {0} must match regex {1}.',
withQuotes(v),
withQuotes(propItems.pattern!)
);
}
});
}
const propItemsEnum = propItems.enum;
if (propItemsEnum) {
stringArrayValue.forEach(v => {
if (propItemsEnum.indexOf(v) === -1) {
message += nls.localize(
'validations.stringArrayItemEnum',
'Value {0} is not one of {1}',
withQuotes(v),
'[' + propItemsEnum.map(withQuotes).join(', ') + ']'
);
message += '\n';
}
});
}
return message;
};
}
}
return value => {
let exclusiveMax: number | undefined;
let exclusiveMin: number | undefined;

View File

@@ -250,4 +250,82 @@ suite('Preferences Model test', () => {
withMessage.rejects(' ').withMessage('always error!');
withMessage.rejects('1').withMessage('always error!');
});
});
class ArrayTester {
private validator: (value: any) => string | null;
constructor(private settings: IConfigurationPropertySchema) {
this.validator = createValidator(settings)!;
}
public accepts(input: string[]) {
assert.equal(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${JSON.stringify(input)}\`. Got ${this.validator(input)}.`);
}
public rejects(input: any[]) {
assert.notEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${JSON.stringify(input)}\`.`);
return {
withMessage:
(message: string) => {
const actual = this.validator(input);
assert.ok(actual);
assert(actual!.indexOf(message) > -1,
`Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`);
}
};
}
}
test('simple array', () => {
{
const arr = new ArrayTester({ type: 'array', items: { type: 'string' } });
arr.accepts([]);
arr.accepts(['foo']);
arr.accepts(['foo', 'bar']);
}
});
test('min-max items array', () => {
{
const arr = new ArrayTester({ type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 2 });
arr.rejects([]).withMessage('Array must have at least 1 items');
arr.accepts(['a']);
arr.accepts(['a', 'a']);
arr.rejects(['a', 'a', 'a']).withMessage('Array must have less than 2 items');
}
});
test('array of enums', () => {
{
const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] } });
arr.accepts(['a']);
arr.accepts(['a', 'b']);
arr.rejects(['c']).withMessage(`Value 'c' is not one of`);
arr.rejects(['a', 'c']).withMessage(`Value 'c' is not one of`);
arr.rejects(['c', 'd']).withMessage(`Value 'c' is not one of`);
arr.rejects(['c', 'd']).withMessage(`Value 'd' is not one of`);
}
});
test('min-max and enum', () => {
const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 });
arr.rejects(['a', 'b', 'c']).withMessage('Array must have less than 2 items');
arr.rejects(['a', 'b', 'c']).withMessage(`Value 'c' is not one of`);
});
test('pattern', () => {
const arr = new ArrayTester({ type: 'array', items: { type: 'string', pattern: '^(hello)*$' } });
arr.accepts(['hello']);
arr.rejects(['a']).withMessage(`Value 'a' must match regex`);
});
test('pattern with error message', () => {
const arr = new ArrayTester({ type: 'array', items: { type: 'string', pattern: '^(hello)*$', patternErrorMessage: 'err: must be friendly' } });
arr.rejects(['a']).withMessage(`err: must be friendly`);
});
});

View File

@@ -46,7 +46,7 @@ export class ProgressService extends Disposable implements IProgressService {
super();
}
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> {
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: (choice?: number) => void): Promise<R> {
const { location } = options;
if (typeof location === 'string') {
if (this.viewletService.getProgressIndicator(location)) {
@@ -142,7 +142,7 @@ export class ProgressService extends Disposable implements IProgressService {
}
}
private withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
private withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P {
const toDispose = new DisposableStore();
const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => {
@@ -152,6 +152,29 @@ export class ProgressService extends Disposable implements IProgressService {
const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : [];
const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : [];
if (options.buttons) {
options.buttons.forEach((button, index) => {
const buttonAction = new class extends Action {
constructor() {
super(`progress.button.${button}`, button, undefined, true);
}
run(): Promise<any> {
if (typeof onDidCancel === 'function') {
onDidCancel(index);
}
return Promise.resolve(undefined);
}
};
toDispose.add(buttonAction);
primaryActions.push(buttonAction);
});
}
if (options.cancellable) {
const cancelAction = new class extends Action {
constructor() {
@@ -182,6 +205,10 @@ export class ProgressService extends Disposable implements IProgressService {
updateProgress(handle, increment);
Event.once(handle.onDidClose)(() => {
if (typeof onDidCancel === 'function') {
onDidCancel();
}
toDispose.dispose();
});
@@ -317,7 +344,7 @@ export class ProgressService extends Disposable implements IProgressService {
return promise;
}
private withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: () => void): P {
private withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: (choice?: number) => void): P {
const disposables = new DisposableStore();
const allowableCommands = [
'workbench.action.quit',
@@ -327,12 +354,17 @@ export class ProgressService extends Disposable implements IProgressService {
let dialog: Dialog;
const createDialog = (message: string) => {
const buttons = options.buttons || [];
buttons.push(options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss"));
dialog = new Dialog(
this.layoutService.container,
message,
[options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")],
buttons,
{
type: 'pending',
cancelId: buttons.length - 1,
keyEventProcessor: (event: StandardKeyboardEvent) => {
const resolved = this.keybindingService.softDispatch(event, this.layoutService.container);
if (resolved && resolved.commandId) {
@@ -347,9 +379,9 @@ export class ProgressService extends Disposable implements IProgressService {
disposables.add(dialog);
disposables.add(attachDialogStyler(dialog, this.themeService));
dialog.show().then(() => {
dialog.show().then((dialogResult) => {
if (typeof onDidCancel === 'function') {
onDidCancel();
onDidCancel(dialogResult.button);
}
dispose(dialog);

View File

@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IURLService } from 'vs/platform/url/common/url';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ServiceIdentifier, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { AbstractURLService } from 'vs/platform/url/common/urlService';
import { Event, Emitter } from 'vs/base/common/event';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IRequestService } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { streamToBuffer } from 'vs/base/common/buffer';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
export interface IURLCallbackProvider {
/**
* Indicates that a Uri has been opened outside of VSCode. The Uri
* will be forwarded to all installed Uri handlers in the system.
*/
readonly onCallback: Event<URI>;
/**
* Creates a Uri that - if opened in a browser - must result in
* the `onCallback` to fire.
*
* The optional `Partial<UriComponents>` must be properly restored for
* the Uri passed to the `onCallback` handler.
*
* For example: if a Uri is to be created with `scheme:"vscode"`,
* `authority:"foo"` and `path:"bar"` the `onCallback` should fire
* with a Uri `vscode://foo/bar`.
*
* If there are additional `query` values in the Uri, they should
* be added to the list of provided `query` arguments from the
* `Partial<UriComponents>`.
*/
create(options?: Partial<UriComponents>): URI;
}
export class BrowserURLService extends AbstractURLService {
_serviceBrand!: ServiceIdentifier<any>;
private provider: IURLCallbackProvider;
constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
this.provider = environmentService.options && environmentService.options.urlCallbackProvider ? environmentService.options.urlCallbackProvider : instantiationService.createInstance(SelfhostURLCallbackProvider);
this.registerListeners();
}
private registerListeners(): void {
this._register(this.provider.onCallback(uri => this.open(uri)));
}
create(options?: Partial<UriComponents>): URI {
return this.provider.create(options);
}
}
class SelfhostURLCallbackProvider extends Disposable implements IURLCallbackProvider {
static FETCH_INTERVAL = 500; // fetch every 500ms
static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min
static QUERY_KEYS = {
REQUEST_ID: 'vscode-requestId',
SCHEME: 'vscode-scheme',
AUTHORITY: 'vscode-authority',
PATH: 'vscode-path',
QUERY: 'vscode-query',
FRAGMENT: 'vscode-fragment'
};
private readonly _onCallback: Emitter<URI> = this._register(new Emitter<URI>());
readonly onCallback: Event<URI> = this._onCallback.event;
constructor(
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService
) {
super();
}
create(options?: Partial<UriComponents>): URI {
const queryValues: Map<string, string> = new Map();
const requestId = generateUuid();
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined };
if (scheme) {
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.SCHEME, scheme);
}
if (authority) {
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority);
}
if (path) {
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.PATH, path);
}
if (query) {
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.QUERY, query);
}
if (fragment) {
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment);
}
// Start to poll on the callback being fired
this.periodicFetchCallback(requestId, Date.now());
return this.doCreateUri('/callback', queryValues);
}
private async periodicFetchCallback(requestId: string, startTime: number): Promise<void> {
// Ask server for callback results
const queryValues: Map<string, string> = new Map();
queryValues.set(SelfhostURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
const result = await this.requestService.request({
url: this.doCreateUri('/fetch-callback', queryValues).toString(true)
}, CancellationToken.None);
// Check for callback results
const content = await streamToBuffer(result.stream);
if (content.byteLength > 0) {
try {
this._onCallback.fire(URI.revive(JSON.parse(content.toString())));
} catch (error) {
this.logService.error(error);
}
return; // done
}
// Continue fetching unless we hit the timeout
if (Date.now() - startTime < SelfhostURLCallbackProvider.FETCH_TIMEOUT) {
setTimeout(() => this.periodicFetchCallback(requestId, startTime), SelfhostURLCallbackProvider.FETCH_INTERVAL);
}
}
private doCreateUri(path: string, queryValues: Map<string, string>): URI {
let query: string | undefined = undefined;
if (queryValues) {
let index = 0;
queryValues.forEach((value, key) => {
if (!query) {
query = '';
}
const prefix = (index++ === 0) ? '' : '&';
query += `${prefix}${key}=${encodeURIComponent(value)}`;
});
}
return URI.parse(window.location.href).with({ path, query });
}
}
registerSingleton(IURLService, BrowserURLService, true);

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { URI } from 'vs/base/common/uri';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/node/urlIpc';
import { URLService } from 'vs/platform/url/node/urlService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import product from 'vs/platform/product/node/product';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class RelayURLService extends URLService implements IURLHandler {
private urlService: IURLService;
constructor(
@IMainProcessService mainProcessService: IMainProcessService,
@IOpenerService openerService: IOpenerService
) {
super();
this.urlService = new URLServiceChannelClient(mainProcessService.getChannel('url'));
mainProcessService.registerChannel('urlHandler', new URLHandlerChannel(this));
openerService.registerOpener(this);
}
async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise<boolean> {
if (options && options.openExternal) {
return false;
}
if (resource.scheme !== product.urlProtocol) {
return false;
}
return await this.urlService.open(resource);
}
handleURL(uri: URI): Promise<boolean> {
return super.open(uri);
}
}
registerSingleton(IURLService, RelayURLService);