Merge remote-tracking branch 'origin/master'

This commit is contained in:
João Moreno
2020-07-15 15:58:12 +02:00
7 changed files with 318 additions and 114 deletions
+144 -83
View File
@@ -25,7 +25,6 @@ const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench'
const args = minimist(process.argv, {
boolean: [
'watch',
'no-launch',
'help'
],
@@ -33,19 +32,20 @@ const args = minimist(process.argv, {
'scheme',
'host',
'port',
'local_port'
'local_port',
'extension'
],
});
if (args.help) {
console.log(
'yarn web [options]\n' +
' --watch Watch extensions that require browser specific builds\n' +
' --no-launch Do not open VSCode web in the browser\n' +
' --scheme Protocol (https or http)\n' +
' --host Remote host\n' +
' --port Remote/Local port\n' +
' --local_port Local port override\n' +
' --no-launch Do not open VSCode web in the browser\n' +
' --scheme Protocol (https or http)\n' +
' --host Remote host\n' +
' --port Remote/Local port\n' +
' --local_port Local port override\n' +
' --extension Path of an extension to include\n' +
' --help\n' +
'[Example]\n' +
' yarn web --scheme https --host example.com --port 8080 --local_port 30000'
@@ -61,76 +61,114 @@ const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
const exists = (path) => util.promisify(fs.exists)(path);
const readFile = (path) => util.promisify(fs.readFile)(path);
const readdir = (path) => util.promisify(fs.readdir)(path);
const readdirWithFileTypes = (path) => util.promisify(fs.readdir)(path, { withFileTypes: true });
let unbuiltExensions = [];
async function initialize() {
async function getBuiltInExtensionInfos(extensionsRoot) {
const builtinExtensions = [];
const children = await util.promisify(fs.readdir)(EXTENSIONS_ROOT, { withFileTypes: true });
const folders = children.filter(c => !c.isFile());
await Promise.all(folders.map(async folder => {
const folderName = folder.name;
const extensionPath = path.join(EXTENSIONS_ROOT, folderName);
let children = [];
try {
children = await util.promisify(fs.readdir)(extensionPath);
} catch (error) {
console.log(error);
return;
}
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmePath = readme ? path.join(extensionPath, readme) : undefined;
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
const changelogPath = changelog ? path.join(extensionPath, changelog) : undefined;
const packageJSONPath = path.join(EXTENSIONS_ROOT, folderName, 'package.json');
if (await exists(packageJSONPath)) {
try {
let packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
if (packageJSON.main && !packageJSON.browser) {
return; // unsupported
}
if (packageJSON.browser) {
packageJSON.main = packageJSON.browser;
let mainFilePath = path.join(EXTENSIONS_ROOT, folderName, packageJSON.browser);
if (path.extname(mainFilePath) !== '.js') {
mainFilePath += '.js';
}
if (!await exists(mainFilePath)) {
unbuiltExensions.push(path.relative(EXTENSIONS_ROOT, mainFilePath));
}
}
packageJSON.extensionKind = ['web']; // enable for Web
const packageNLSPath = path.join(folderName, 'package.nls.json');
const packageNLSExists = await exists(path.join(EXTENSIONS_ROOT, packageNLSPath));
if (packageNLSExists) {
packageJSON = extensions.translatePackageJSON(packageJSON, path.join(EXTENSIONS_ROOT, packageNLSPath)); // temporary, until fixed in core
}
builtinExtensions.push({
extensionPath: folderName,
packageJSON,
packageNLSPath: packageNLSExists ? packageNLSPath : undefined,
readmePath,
changelogPath
});
} catch (e) {
console.log(e);
const children = await readdirWithFileTypes(extensionsRoot);
await Promise.all(children.map(async child => {
if (child.isDirectory()) {
const info = await getBuiltInExtensionInfo(path.join(extensionsRoot, child.name));
if (info) {
builtinExtensions.push(info);
}
}
}));
if (unbuiltExensions.length) {
fancyLog(`${ansiColors.yellow('Warning')}: Make sure to run ${ansiColors.cyan('yarn gulp watch-web')}\nCould not find the following browser main files: \n${unbuiltExensions.join('\n')}`);
}
return builtinExtensions;
}
const builtinExtensionsPromise = initialize();
async function getBuiltInExtensionInfo(extensionPath) {
const packageJSON = await getExtensionPackageJSON(extensionPath);
if (!packageJSON) {
return undefined;
}
const builtInExtensionPath = path.basename(extensionPath);
let children = [];
try {
children = await readdir(extensionPath);
} catch (error) {
console.log(`Can not read extension folder ${extensionPath}: ${error}`);
return;
}
const readme = children.find(child => /^readme(\.txt|\.md|)$/i.test(child));
const changelog = children.find(child => /^changelog(\.txt|\.md|)$/i.test(child));
const packageJSONNLS = children.find(child => /^package.nls.json$/i.test(child));
return {
extensionPath: builtInExtensionPath,
packageJSON,
packageNLSPath: packageJSONNLS ? `${builtInExtensionPath}/${packageJSONNLS}` : undefined,
readmePath: readme ? `${builtInExtensionPath}/${readme}` : undefined,
changelogPath: changelog ? `${builtInExtensionPath}/${changelog}` : undefined
};
}
async function getDefaultExtensionInfos() {
const extensions = [];
const locations = {};
let extensionArg = args['extension'];
if (!extensionArg) {
return { extensions, locations }
}
const extensionPaths = Array.isArray(extensionArg) ? extensionArg : [extensionArg];
await Promise.all(extensionPaths.map(async extensionPath => {
extensionPath = path.resolve(process.cwd(), extensionPath);
const packageJSON = await getExtensionPackageJSON(extensionPath);
if (packageJSON) {
const extensionId = `${packageJSON.publisher}.${packageJSON.name}`;
extensions.push({
packageJSON,
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/extension/${extensionId}` }
});
locations[extensionId] = extensionPath;
}
}));
return { extensions, locations };
}
async function getExtensionPackageJSON(extensionPath) {
const packageJSONPath = path.join(extensionPath, 'package.json');
if (await exists(packageJSONPath)) {
try {
let packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
if (packageJSON.main && !packageJSON.browser) {
return; // unsupported
}
if (packageJSON.browser) {
packageJSON.main = packageJSON.browser;
let mainFilePath = path.join(extensionPath, packageJSON.browser);
if (path.extname(mainFilePath) !== '.js') {
mainFilePath += '.js';
}
if (!await exists(mainFilePath)) {
fancyLog(`${ansiColors.yellow('Warning')}: Could not find ${mainFilePath}. Use ${ansiColors.cyan('yarn gulp watch-web')} to build the built-in extensions.`);
}
}
packageJSON.extensionKind = ['web']; // enable for Web
const packageNLSPath = path.join(extensionPath, 'package.nls.json');
const packageNLSExists = await exists(packageNLSPath);
if (packageNLSExists) {
packageJSON = extensions.translatePackageJSON(packageJSON, packageNLSPath); // temporary, until fixed in core
}
return packageJSON;
} catch (e) {
console.log(e);
}
}
return undefined;
}
const builtinExtensionsPromise = getBuiltInExtensionInfos(EXTENSIONS_ROOT);
const defaultExtensionsPromise = getDefaultExtensionInfos();
const mapCallbackUriToRequestId = new Map();
@@ -158,9 +196,13 @@ const server = http.createServer((req, res) => {
// static requests
return handleStatic(req, res, parsedUrl);
}
if (/^\/static-extension\//.test(pathname)) {
// static extension requests
return handleStaticExtension(req, res, parsedUrl);
if (/^\/extension\//.test(pathname)) {
// default extension requests
return handleExtension(req, res, parsedUrl);
}
if (/^\/builtin-extension\//.test(pathname)) {
// builtin extension requests
return handleBuiltinExtension(req, res, parsedUrl);
}
if (pathname === '/') {
// main web
@@ -211,13 +253,34 @@ function handleStatic(req, res, parsedUrl) {
* @param {import('http').ServerResponse} res
* @param {import('url').UrlWithParsedQuery} parsedUrl
*/
function handleStaticExtension(req, res, parsedUrl) {
async function handleExtension(req, res, parsedUrl) {
// Strip `/extension/` from the path
const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/extension/'.length));
const firstSlash = relativePath.indexOf('/');
if (firstSlash === -1) {
return serveError(req, res, 400, `Bad request.`);
}
const extensionId = relativePath.substr(0, firstSlash);
const { locations } = await defaultExtensionsPromise;
// Strip `/static-extension/` from the path
const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static-extension/'.length)));
const extensionPath = locations[extensionId];
if (!extensionPath) {
return serveError(req, res, 400, `Bad request.`);
}
const filePath = path.join(EXTENSIONS_ROOT, relativeFilePath);
const filePath = path.join(extensionPath, relativePath.substr(firstSlash + 1));
return serveFile(req, res, filePath);
}
/**
* @param {import('http').IncomingMessage} req
* @param {import('http').ServerResponse} res
* @param {import('url').UrlWithParsedQuery} parsedUrl
*/
async function handleBuiltinExtension(req, res, parsedUrl) {
// Strip `/builtin-extension/` from the path
const relativePath = decodeURIComponent(parsedUrl.pathname.substr('/builtin-extension/'.length));
const filePath = path.join(EXTENSIONS_ROOT, relativePath);
return serveFile(req, res, filePath);
}
@@ -254,13 +317,15 @@ async function handleRoot(req, res) {
}
const builtinExtensions = await builtinExtensionsPromise;
const { extensions } = await defaultExtensionsPromise;
const webConfigJSON = escapeAttribute(JSON.stringify({
folderUri: folderUri,
builtinExtensionsServiceUrl: `${SCHEME}://${AUTHORITY}/static-extension`
staticExtensions: extensions,
builtinExtensionsServiceUrl: `${SCHEME}://${AUTHORITY}/builtin-extension`
}));
const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
const data = (await readFile(WEB_MAIN)).toString()
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfigJSON) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
.replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(builtinExtensions)))
.replace('{{WEBVIEW_ENDPOINT}}', '')
@@ -422,10 +487,6 @@ async function serveFile(req, res, filePath, responseHeaders = Object.create(nul
// Sanity checks
filePath = path.normalize(filePath); // ensure no "." and ".."
if (filePath.indexOf(`${APP_ROOT}${path.sep}`) !== 0) {
// invalid location outside of APP_ROOT
return serveError(req, res, 400, `Bad request.`);
}
const stat = await util.promisify(fs.stat)(filePath);
+4 -16
View File
@@ -24,6 +24,7 @@ import Severity from 'vs/base/common/severity';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { mixin } from 'vs/base/common/objects';
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
@@ -650,9 +651,9 @@ export interface ITreeItem {
}
export class ResolvableTreeItem implements ITreeItem {
handle: string;
handle!: string;
parentHandle?: string;
collapsibleState: TreeItemCollapsibleState;
collapsibleState!: TreeItemCollapsibleState;
label?: ITreeItemLabel;
description?: string | boolean;
icon?: UriComponents;
@@ -668,20 +669,7 @@ export class ResolvableTreeItem implements ITreeItem {
private resolved: boolean = false;
private _hasResolve: boolean = false;
constructor(treeItem: ITreeItem, resolve?: (() => Promise<ITreeItem | undefined>)) {
this.handle = treeItem.handle;
this.parentHandle = treeItem.parentHandle;
this.collapsibleState = treeItem.collapsibleState;
this.label = treeItem.label;
this.description = treeItem.description;
this.icon = treeItem.icon;
this.iconDark = treeItem.iconDark;
this.themeIcon = treeItem.themeIcon;
this.resourceUri = treeItem.resourceUri;
this.tooltip = treeItem.tooltip;
this.contextValue = treeItem.contextValue;
this.command = treeItem.command;
this.children = treeItem.children;
this.accessibilityInformation = treeItem.accessibilityInformation;
mixin(this, treeItem);
this._hasResolve = !!resolve;
this.resolve = async () => {
if (resolve && !this.resolved) {
@@ -94,13 +94,16 @@ export function convertBufferRangeToViewport(bufferRange: IBufferRange, viewport
}
export function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number, cols: number): string {
let line = '';
let content = '';
for (let i = lineStart; i <= lineEnd; i++) {
// Make sure only 0 to cols are considered as resizing when windows mode is enabled will
// retain buffer data outside of the terminal width as reflow is disabled.
line += buffer.getLine(i)!.translateToString(true, 0, cols);
const line = buffer.getLine(i);
if (line) {
content += line.translateToString(true, 0, cols);
}
}
return line;
return content;
}
export function positionIsInRange(position: IBufferCellPosition, range: IBufferRange): boolean {
@@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE } from 'vs/workbench/contrib/update/browser/update';
import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE, SwitchProductQualityContribution } from 'vs/workbench/contrib/update/browser/update';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import product from 'vs/platform/product/common/product';
import { StateType } from 'vs/platform/update/common/update';
@@ -18,6 +18,7 @@ const workbench = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensio
workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Restored);
workbench.registerWorkbenchContribution(UpdateContribution, LifecyclePhase.Restored);
workbench.registerWorkbenchContribution(SwitchProductQualityContribution, LifecyclePhase.Restored);
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
@@ -28,6 +28,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IProductService } from 'vs/platform/product/common/productService';
import product from 'vs/platform/product/common/product';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
export const CONTEXT_UPDATE_STATE = new RawContextKey<string>('updateState', StateType.Idle);
@@ -181,7 +182,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
@IActivityService private readonly activityService: IActivityService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IProductService private readonly productService: IProductService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
super();
@@ -221,7 +222,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
case StateType.Idle:
if (state.error) {
this.onError(state.error);
} else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.workbenchEnvironmentService.configuration.sessionId) {
} else if (this.state.type === StateType.CheckingForUpdates && this.state.context === this.environmentService.configuration.sessionId) {
this.onUpdateNotAvailable();
}
break;
@@ -402,7 +403,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
}
private registerGlobalActivityActions(): void {
CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.workbenchEnvironmentService.configuration.sessionId));
CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates(this.environmentService.configuration.sessionId));
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
command: {
@@ -477,6 +478,48 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu
}
}
export class SwitchProductQualityContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IProductService private readonly productService: IProductService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
this.registerGlobalActivityActions();
}
private registerGlobalActivityActions(): void {
const quality = this.productService.quality;
const productQualityChangeHandler = this.environmentService.options?.productQualityChangeHandler;
if (productQualityChangeHandler && (quality === 'stable' || quality === 'insider')) {
const newQuality = quality === 'stable' ? 'insider' : 'stable';
const commandId = `update.switchQuality.${newQuality}`;
CommandsRegistry.registerCommand(commandId, async accessor => {
const dialogService = accessor.get(IDialogService);
const res = await dialogService.confirm({
type: 'info',
message: nls.localize('relaunchMessage', "Changing the version requires a reload to take effect"),
detail: newQuality === 'insider' ? nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the insiders version.") : nls.localize('relaunchDetailStable', "Press the reload button to switch to the stable version."),
primaryButton: nls.localize('reload', "&&Reload")
});
if (res.confirmed) {
productQualityChangeHandler(newQuality);
}
});
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '6_update',
command: {
id: commandId,
title: newQuality === 'insider' ? nls.localize('switchToInsiders', "Switch to Insiders Version...") : nls.localize('switchToStable', "Switch to Stable Version...")
}
});
}
}
}
export class CheckForVSCodeUpdateAction extends Action {
static readonly ID = CheckForVSCodeUpdateActionId;
@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ExtHostTreeViewsShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/base/test/common/mock';
import { ITreeItem, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, ITreeViewDescriptor, ITreeView, ViewContainer, IViewDescriptorService, TreeItemCollapsibleState } from 'vs/workbench/common/views';
import { NullLogService } from 'vs/platform/log/common/log';
import { MainThreadTreeViews } from 'vs/workbench/api/browser/mainThreadTreeViews';
import { TestViewsService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { CustomTreeView } from 'vs/workbench/contrib/views/browser/treeView';
import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService';
suite('MainThreadHostTreeView', function () {
const testTreeViewId = 'testTreeView';
const customValue = 'customValue';
const ViewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
interface CustomTreeItem extends ITreeItem {
customProp: string;
}
class MockExtHostTreeViewsShape extends mock<ExtHostTreeViewsShape>() {
async $getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]> {
return [<CustomTreeItem>{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }];
}
async $hasResolve(): Promise<boolean> {
return false;
}
$setVisible(): void { }
}
let container: ViewContainer;
let mainThreadTreeViews: MainThreadTreeViews;
let extHostTreeViewsShape: MockExtHostTreeViewsShape;
setup(async () => {
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
const viewDescriptorService = instantiationService.createInstance(ViewDescriptorService);
instantiationService.stub(IViewDescriptorService, viewDescriptorService);
container = Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', name: 'test', ctorDescriptor: new SyncDescriptor(<any>{}) }, ViewContainerLocation.Sidebar);
const viewDescriptor: ITreeViewDescriptor = {
id: testTreeViewId,
ctorDescriptor: null!,
name: 'Test View 1',
treeView: instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title'),
};
ViewsRegistry.registerViews([viewDescriptor], container);
const testExtensionService = new TestExtensionService();
extHostTreeViewsShape = new MockExtHostTreeViewsShape();
mainThreadTreeViews = new MainThreadTreeViews(
new class implements IExtHostContext {
remoteAuthority = '';
assertRegistered() { }
set(v: any): any { return null; }
getProxy(): any {
return extHostTreeViewsShape;
}
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false });
await testExtensionService.whenInstalledExtensionsRegistered();
});
teardown(() => {
ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container);
});
test('getChildren keeps custom properties', async () => {
const treeView: ITreeView = (<ITreeViewDescriptor>ViewsRegistry.getView(testTreeViewId)).treeView;
const children = await treeView.dataProvider?.getChildren({ handle: 'root', collapsibleState: TreeItemCollapsibleState.Expanded });
assert(children!.length === 1, 'Exactly one child should be returned');
assert((<CustomTreeItem>children![0]).customProp === customValue, 'Tree Items should keep custom properties');
});
});
+30 -8
View File
@@ -38,10 +38,12 @@ interface IExternalUriResolver {
}
interface ITunnelProvider {
/**
* Support for creating tunnels.
*/
tunnelFactory?: ITunnelFactory;
/**
* Support for filtering candidate ports
*/
@@ -169,14 +171,23 @@ interface IDefaultEditor {
}
interface IDefaultLayout {
/** @deprecated Use views instead */
/** @deprecated Use views instead (TODO@eamodio remove eventually) */
readonly sidebar?: IDefaultSideBarLayout;
/** @deprecated Use views instead */
/** @deprecated Use views instead (TODO@eamodio remove eventually) */
readonly panel?: IDefaultPanelLayout;
readonly views?: IDefaultView[];
readonly editors?: IDefaultEditor[];
}
interface IProductQualityChangeHandler {
/**
* Handler is being called when the user wants to switch between
* `insider` or `stable` product qualities.
*/
(newQuality: 'insider' | 'stable'): void;
}
interface IWorkbenchConstructionOptions {
//#region Connection related configuration
@@ -265,11 +276,6 @@ interface IWorkbenchConstructionOptions {
*/
readonly urlCallbackProvider?: IURLCallbackProvider;
/**
* Support for update reporting.
*/
readonly updateProvider?: IUpdateProvider;
/**
* Support adding additional properties to telemetry.
*/
@@ -291,6 +297,21 @@ interface IWorkbenchConstructionOptions {
//#endregion
//#region Update/Quality related
/**
* Support for update reporting
*/
readonly updateProvider?: IUpdateProvider;
/**
* Support for product quality switching
*/
readonly productQualityChangeHandler?: IProductQualityChangeHandler;
//#endregion
//#region Branding
/**
@@ -426,9 +447,10 @@ export {
// LogLevel
LogLevel,
// Updates
// Updates/Quality
IUpdateProvider,
IUpdate,
IProductQualityChangeHandler,
// Telemetry
ICommonTelemetryPropertiesResolver,