diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index 7142aa19a4cc1..049e3e0255696 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -17,6 +17,8 @@ import product from 'vs/platform/node/product'; import { IRequestService } from 'vs/platform/request/node/request'; import { IRequestContext } from 'vs/base/node/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { offlineModeSetting, unSupportedInOfflineModeMsg } from 'vs/platform/actions/common/offlineMode'; interface PostResult { readonly blob_id: string; @@ -36,8 +38,14 @@ class Endpoint { export async function uploadLogs( channel: ILaunchChannel, requestService: IRequestService, - environmentService: IEnvironmentService + environmentService: IEnvironmentService, + configurationService: IConfigurationService ): Promise { + if (configurationService.getValue(offlineModeSetting) === true) { + console.error(unSupportedInOfflineModeMsg); + return; + } + const endpoint = Endpoint.getFromProduct(); if (!endpoint) { console.error(localize('invalidEndpoint', 'Invalid log uploader endpoint')); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 134f4379fb3d9..cd078850dbbbf 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -111,6 +111,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise { const logService = accessor.get(ILogService); const environmentService = accessor.get(IEnvironmentService); const requestService = accessor.get(IRequestService); + const configurationService = accessor.get(IConfigurationService); function allowSetForegroundWindow(service: LaunchChannelClient): TPromise { let promise = TPromise.wrap(void 0); @@ -204,7 +205,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise { // Log uploader if (typeof environmentService.args['upload-logs'] !== 'undefined') { - return uploadLogs(channel, requestService, environmentService) + return uploadLogs(channel, requestService, environmentService, configurationService) .then(() => TPromise.wrapError(new ExpectedError())); } diff --git a/src/vs/code/electron-main/menubar.ts b/src/vs/code/electron-main/menubar.ts index 84c15cd5cf064..494ebfff56462 100644 --- a/src/vs/code/electron-main/menubar.ts +++ b/src/vs/code/electron-main/menubar.ts @@ -23,6 +23,7 @@ import { IHistoryMainService } from 'vs/platform/history/common/history'; import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IMenubarData, IMenubarMenuItemAction, IMenubarMenuItemSeparator } from 'vs/platform/menubar/common/menubar'; import URI from 'vs/base/common/uri'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; // interface IExtensionViewlet { // id: string; @@ -581,6 +582,13 @@ export class Menubar { } private getUpdateMenuItems(): Electron.MenuItem[] { + if (this.configurationService.getValue(offlineModeSetting) === true) { + return [new MenuItem({ + label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => { + this.runActionInRenderer('workbench.action.notifyUnsupportedFeatureInOfflineMode'); + } + })]; + } const state = this.updateService.state; switch (state.type) { diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 54cf3480930bb..c051a1d8a0ce2 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -24,6 +24,7 @@ import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/wind import { IHistoryMainService } from 'vs/platform/history/common/history'; import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import URI from 'vs/base/common/uri'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; interface IMenuItemClickHandler { inDevTools: (contents: Electron.WebContents) => void; @@ -1124,6 +1125,13 @@ export class CodeMenu { } private getUpdateMenuItems(): Electron.MenuItem[] { + if (this.configurationService.getValue(offlineModeSetting) === true) { + return [new MenuItem({ + label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => { + this.runActionInRenderer('workbench.action.notifyUnsupportedFeatureInOfflineMode'); + } + })]; + } const state = this.updateService.state; switch (state.type) { diff --git a/src/vs/platform/actions/common/offlineMode.ts b/src/vs/platform/actions/common/offlineMode.ts new file mode 100644 index 0000000000000..ad60a30e3f09d --- /dev/null +++ b/src/vs/platform/actions/common/offlineMode.ts @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; + +export const offlineModeSetting = 'workbench.enableOfflineMode'; +export const unSupportedInOfflineModeMsg = localize('offlineModeUnsupportedFeature', "This feature is not supported in offline mode"); + +export class EnableOfflineMode extends Action { + static readonly ID = 'workbench.action.enableOfflineMode'; + static LABEL = localize('enableOfflineMode', 'Enable Offline Mode'); + + private disposables: IDisposable[] = []; + private readonly disclaimerStorageKey = 'workbench.offlineMode.disclaimer.dontShowAgain'; + + constructor( + id: string = EnableOfflineMode.ID, + label: string = EnableOfflineMode.LABEL, + @IConfigurationService private configurationService: IConfigurationService, + @IStorageService private storageService: IStorageService, + @INotificationService private notificationService: INotificationService + ) { + super(id, label); + this.updateEnabled(); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(offlineModeSetting)) { + this.updateEnabled(); + } + }, this, this.disposables); + } + + private updateEnabled() { + this.enabled = this.configurationService.getValue(offlineModeSetting) !== true; + } + + run(): TPromise { + if (this.storageService.getBoolean(this.disclaimerStorageKey, StorageScope.GLOBAL, false) === false) { + this.notificationService.prompt(Severity.Info, localize('offlineModeDisclaimer', "VS Code cannot stop extensions from making network requests in offline mode. If extensions make such requests, please log an issue against them."), [ + { + label: localize('DontShowAgain', "Don't Show Again"), + run: () => { + this.storageService.store(this.disclaimerStorageKey, true); + } + } + ]); + } + return this.configurationService.updateValue(offlineModeSetting, true); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + +export class DisableOfflineMode extends Action { + static readonly ID = 'workbench.action.disableOfflineMode'; + static LABEL = localize('disableOfflineMode', 'Disable Offline Mode'); + + private disposables: IDisposable[] = []; + + constructor( + id: string = DisableOfflineMode.ID, + label: string = DisableOfflineMode.LABEL, + @IConfigurationService private configurationService: IConfigurationService + ) { + super(id, label); + this.updateEnabled(); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(offlineModeSetting)) { + this.updateEnabled(); + } + }, this, this.disposables); + } + + private updateEnabled() { + this.enabled = this.configurationService.getValue(offlineModeSetting) === true; + } + + run(): TPromise { + return this.configurationService.updateValue(offlineModeSetting, false); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + +export class NotifyUnsupportedFeatureInOfflineMode extends Action { + static readonly ID = 'workbench.action.notifyUnsupportedFeatureInOfflineMode'; + + constructor( + id: string = NotifyUnsupportedFeatureInOfflineMode.ID, + label: string = '', + @IConfigurationService private configurationService: IConfigurationService, + @INotificationService private notificationService: INotificationService + ) { + super(id, label); + } + + run(): TPromise { + this.notificationService.prompt(Severity.Info, unSupportedInOfflineModeMsg, [ + { + label: DisableOfflineMode.LABEL, + run: () => { + return this.configurationService.updateValue(offlineModeSetting, false); + } + } + ]); + return TPromise.as(null); + } +} + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_offline', + command: { + id: EnableOfflineMode.ID, + title: localize({ key: 'miEnableOfflineMode', comment: ['&& denotes a mnemonic'] }, "Enable &&Offline Mode") + }, + order: 1, + when: ContextKeyExpr.not('config.' + offlineModeSetting) +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_offline', + command: { + id: DisableOfflineMode.ID, + title: localize({ key: 'miDisableOfflineMode', comment: ['&& denotes a mnemonic'] }, "Disable &&Offline Mode") + }, + order: 1, + when: ContextKeyExpr.has('config.' + offlineModeSetting) +}); + +CommandsRegistry.registerCommand(EnableOfflineMode.ID, serviceAccesor => { + serviceAccesor.get(IInstantiationService).createInstance(EnableOfflineMode, EnableOfflineMode.ID, EnableOfflineMode.LABEL).run(); +}); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: EnableOfflineMode.ID, + title: 'Preferences: Enable Offline Mode' + }, + when: ContextKeyExpr.not('config.' + offlineModeSetting) +}); + +CommandsRegistry.registerCommand(DisableOfflineMode.ID, serviceAccesor => { + serviceAccesor.get(IInstantiationService).createInstance(DisableOfflineMode, DisableOfflineMode.ID, DisableOfflineMode.LABEL).run(); +}); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: DisableOfflineMode.ID, + title: 'Preferences: Disable Offline Mode' + }, + when: ContextKeyExpr.has('config.' + offlineModeSetting) +}); + +CommandsRegistry.registerCommand(NotifyUnsupportedFeatureInOfflineMode.ID, serviceAccesor => { + serviceAccesor.get(IInstantiationService).createInstance(NotifyUnsupportedFeatureInOfflineMode, NotifyUnsupportedFeatureInOfflineMode.ID, '').run(); +}); \ No newline at end of file diff --git a/src/vs/platform/actions/test/offlineMode.test.ts b/src/vs/platform/actions/test/offlineMode.test.ts new file mode 100644 index 0000000000000..43abf4d07ebdf --- /dev/null +++ b/src/vs/platform/actions/test/offlineMode.test.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { EnableOfflineMode, DisableOfflineMode, offlineModeSetting, NotifyUnsupportedFeatureInOfflineMode, unSupportedInOfflineModeMsg } from 'vs/platform/actions/common/offlineMode'; +import { IAction } from 'vs/base/common/actions'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { Emitter } from 'vs/base/common/event'; +import { ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; + +suite('OfflineMode', () => { + let instantiationService: TestInstantiationService; + let offlineMode = false; + let dontShowAgain = false; + let promptShown = false; + let configChangeEmitter = new Emitter(); + let enableOfflineAction: IAction; + let disableOfflineAction: IAction; + + suiteSetup(() => { + instantiationService = new TestInstantiationService(); + instantiationService.stub(IConfigurationService, { + onDidUpdateConfiguration: () => { }, + onDidChangeConfiguration: configChangeEmitter.event, + getConfigurationData: () => ({}), + getValue: key => { + return key === offlineModeSetting ? offlineMode : undefined; + }, + updateValue: (key, value) => { + if (key === offlineModeSetting) { + offlineMode = value; + } + return TPromise.as(null); + } + }); + instantiationService.stub(IStorageService, { + getBoolean: key => { + return key === 'workbench.offlineMode.disclaimer.dontShowAgain' ? dontShowAgain : undefined; + }, + store: (key, value) => { + if (key === 'workbench.offlineMode.disclaimer.dontShowAgain') { + dontShowAgain = value; + } + } + }); + }); + + setup(() => { + promptShown = false; + dontShowAgain = false; + instantiationService.stub(INotificationService, { + prompt: (a, b, c) => { + promptShown = true; + } + }); + + enableOfflineAction = instantiationService.createInstance(EnableOfflineMode); + disableOfflineAction = instantiationService.createInstance(DisableOfflineMode); + + offlineMode = false; + configChangeEmitter.fire(new ConfigurationChangeEvent().change([offlineModeSetting])); + }); + + teardown(() => { + enableOfflineAction.dispose(); + disableOfflineAction.dispose(); + }); + + suiteTeardown(() => { + configChangeEmitter.dispose(); + }); + + test('Test Enablement of actions in online mode', () => { + assert.equal(enableOfflineAction.enabled, true); + assert.equal(disableOfflineAction.enabled, false); + }); + + test('Test Enablement of actions in offline mode', () => { + offlineMode = true; + configChangeEmitter.fire(new ConfigurationChangeEvent().change([offlineModeSetting])); + + assert.equal(enableOfflineAction.enabled, false); + assert.equal(disableOfflineAction.enabled, true); + }); + + test('EnableOfflineMode action enables offline mode with prompt', () => { + return enableOfflineAction.run().then(() => { + assert.equal(offlineMode, true); + assert.equal(promptShown, true); + assert.equal(dontShowAgain, false); + }); + }); + + test('EnableOfflineMode action prompt choose dont show again', () => { + instantiationService.stub(INotificationService, { + prompt: (a, b, c) => { + promptShown = true; + if (c[0].label === 'Don\'t Show Again') { + c[0].run(); + } + } + }); + enableOfflineAction.dispose(); + enableOfflineAction = instantiationService.createInstance(EnableOfflineMode); + return enableOfflineAction.run().then(() => { + assert.equal(offlineMode, true); + assert.equal(promptShown, true); + assert.equal(dontShowAgain, true); + }); + }); + + test('EnableOfflineMode action prompt dont show again if chosen so before', () => { + dontShowAgain = true; + return enableOfflineAction.run().then(() => { + assert.equal(offlineMode, true); + assert.equal(promptShown, false); + assert.equal(dontShowAgain, true); + }); + }); + + test('DisableOfflineMode action enables offline mode', () => { + offlineMode = true; + configChangeEmitter.fire(new ConfigurationChangeEvent().change([offlineModeSetting])); + return disableOfflineAction.run().then(() => { + assert.equal(offlineMode, false); + }); + }); + + test('Notify Action shows prompt', () => { + offlineMode = true; + const notifyAction: IAction = instantiationService.createInstance(NotifyUnsupportedFeatureInOfflineMode); + return notifyAction.run().then(() => { + assert.equal(promptShown, true); + assert.equal(offlineMode, true); + }); + }); + + test('Notify Action shows prompt that disables offline mode', () => { + offlineMode = true; + instantiationService.stub(INotificationService, { + prompt: (a, b, c) => { + promptShown = true; + if (c[0].label === DisableOfflineMode.LABEL && b === unSupportedInOfflineModeMsg) { + c[0].run(); + } + } + }); + const notifyAction: IAction = instantiationService.createInstance(NotifyUnsupportedFeatureInOfflineMode); + return notifyAction.run().then(() => { + assert.equal(promptShown, true); + assert.equal(offlineMode, false); + }); + }); +}); \ No newline at end of file diff --git a/src/vs/platform/request/node/request.ts b/src/vs/platform/request/node/request.ts index 878a5c4a33469..ff20d4d730b1e 100644 --- a/src/vs/platform/request/node/request.ts +++ b/src/vs/platform/request/node/request.ts @@ -25,6 +25,9 @@ export interface IHTTPConfiguration { proxyStrictSSL?: boolean; proxyAuthorization?: string; }; + workbench?: { + enableOfflineMode?: boolean; + }; } Registry.as(Extensions.Configuration) diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 84a66a3ca52c8..127c71b6dc577 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -24,6 +24,7 @@ export class RequestService implements IRequestService { private proxyUrl: string; private strictSSL: boolean; private authorization: string; + private isOfflineMode: boolean; private disposables: IDisposable[] = []; constructor( @@ -38,9 +39,14 @@ export class RequestService implements IRequestService { this.proxyUrl = config.http && config.http.proxy; this.strictSSL = config.http && config.http.proxyStrictSSL; this.authorization = config.http && config.http.proxyAuthorization; + this.isOfflineMode = config.workbench && config.workbench.enableOfflineMode === true; } request(options: IRequestOptions, requestFn: IRequestFunction = request): TPromise { + if (this.isOfflineMode) { + return TPromise.as({ res: { headers: {}, }, stream: null }); + } + this.logService.trace('RequestService#request', options.url); const { proxyUrl, strictSSL } = this; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 0b8470198a305..4618cfcc89781 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -17,6 +17,15 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { cloneAndChange, mixin } from 'vs/base/common/objects'; import { Registry } from 'vs/platform/registry/common/platform'; +interface ITelemetryConfig { + telemetry: { + enableTelemetry?: boolean; + }; + workbench: { + enableOfflineMode?: boolean + }; +} + export interface ITelemetryServiceConfig { appender: ITelemetryAppender; commonProperties?: TPromise<{ [name: string]: any }>; @@ -67,8 +76,12 @@ export class TelemetryService implements ITelemetryService { } private _updateUserOptIn(): void { - const config = this._configurationService.getValue(TELEMETRY_SECTION_ID); - this._userOptIn = config ? config.enableTelemetry : this._userOptIn; + const config: ITelemetryConfig = this._configurationService.getValue(); + if (config.workbench.enableOfflineMode === true) { + this._userOptIn = false; + return; + } + this._userOptIn = config.telemetry.enableTelemetry === true; } get isOptedIn(): boolean { @@ -155,11 +168,8 @@ export class TelemetryService implements ITelemetryService { } } - -const TELEMETRY_SECTION_ID = 'telemetry'; - Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': TELEMETRY_SECTION_ID, + 'id': 'telemetry', 'order': 110, 'type': 'object', 'title': localize('telemetryConfigurationTitle', "Telemetry"), diff --git a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts index 8cd91f0803bf0..2473e61337143 100644 --- a/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/telemetryService.test.ts @@ -730,6 +730,7 @@ suite('TelemetryService', () => { test('Telemetry Service checks with config service', function () { let enableTelemetry = false; + let enableOfflineMode = false; let emitter = new Emitter(); let testAppender = new TestTelemetryAppender(); @@ -739,7 +740,12 @@ suite('TelemetryService', () => { _serviceBrand: undefined, getValue() { return { - enableTelemetry: enableTelemetry + telemetry: { + enableTelemetry: enableTelemetry + }, + workbench: { + enableOfflineMode: enableOfflineMode + } } as any; }, updateValue(): TPromise { @@ -766,6 +772,14 @@ suite('TelemetryService', () => { emitter.fire({}); assert.equal(service.isOptedIn, true); + enableOfflineMode = true; + emitter.fire({}); + assert.equal(service.isOptedIn, false); + + enableOfflineMode = false; + emitter.fire({}); + assert.equal(service.isOptedIn, true); + enableTelemetry = false; emitter.fire({}); assert.equal(service.isOptedIn, false); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index b689dd6bd6791..4baaa8b29c88e 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -15,6 +15,7 @@ import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } fr import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IRequestService } from 'vs/platform/request/node/request'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; export function createUpdateURL(platform: string, quality: string): string { return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`; @@ -49,7 +50,7 @@ export abstract class AbstractUpdateService implements IUpdateService { @IRequestService protected requestService: IRequestService, @ILogService protected logService: ILogService, ) { - if (this.environmentService.disableUpdates) { + if (this.environmentService.disableUpdates || this.configurationService.getValue(offlineModeSetting) === true) { this.logService.info('update#ctor - updates are disabled'); return; } @@ -60,7 +61,6 @@ export abstract class AbstractUpdateService implements IUpdateService { } const quality = this.getProductQuality(); - if (!quality) { this.logService.info('update#ctor - updates are disabled'); return; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index e408207987d18..4c0eedf8bbb0e 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -489,6 +489,12 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('settingsTocVisible', "Controls whether the settings editor Table of Contents is visible."), 'default': true, 'scope': ConfigurationScope.WINDOW + }, + 'workbench.enableOfflineMode': { + 'type': 'boolean', + 'description': nls.localize('settingsOfflineMode', "Enables offline mode where no requests are made over the network by VS Code."), + 'default': false, + 'scope': ConfigurationScope.WINDOW } } }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index ab53ea18d8bab..27223232e6953 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -43,6 +43,7 @@ import { assign } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -162,7 +163,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private fetchProactiveRecommendations(calledDuringStartup?: boolean): TPromise { let fetchPromise = TPromise.as(null); - if (!this.proactiveRecommendationsFetched) { + if (!this.proactiveRecommendationsFetched + && this.configurationService.getValue(offlineModeSetting) !== true) { this.proactiveRecommendationsFetched = true; // Executable based recommendations carry out a lot of file stats, so run them after 10 secs @@ -499,7 +501,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe let hasSuggestion = false; const uri = model.uri; - if (!uri || !this.fileService.canHandleResource(uri)) { + if (!uri || !this.fileService.canHandleResource(uri) || this.configurationService.getValue(offlineModeSetting) === true) { return; } @@ -717,7 +719,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (filteredRecs.length === 0 || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand - || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { + || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false) + || this.configurationService.getValue(offlineModeSetting) === true) { return; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index ce4e5210290c1..1b7fa26daba3e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -52,6 +52,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorG import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import product from 'vs/platform/node/product'; import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { offlineModeSetting, NotifyUnsupportedFeatureInOfflineMode } from 'vs/platform/actions/common/offlineMode'; const promptDownloadManually = (extension: IGalleryExtension, message: string, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => { const downloadUrl = `${product.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; @@ -1157,27 +1158,36 @@ export class CheckForUpdatesAction extends Action { static readonly ID = 'workbench.extensions.action.checkForUpdates'; static LABEL = localize('checkForUpdates', "Check for Updates"); + private readonly notifyOfflineModeAction = new NotifyUnsupportedFeatureInOfflineMode(NotifyUnsupportedFeatureInOfflineMode.ID, '', this.configurationService, this.notificationService); constructor( id = UpdateAllAction.ID, label = UpdateAllAction.LABEL, - @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, + @IConfigurationService private configurationService: IConfigurationService, + @INotificationService private notificationService: INotificationService ) { super(id, label, '', true); } run(): TPromise { + if (this.configurationService.getValue(offlineModeSetting) === true) { + return this.notifyOfflineModeAction.run(); + } return this.extensionsWorkbenchService.checkForUpdates(); } } export class ToggleAutoUpdateAction extends Action { + private readonly notifyOfflineModeAction = new NotifyUnsupportedFeatureInOfflineMode(NotifyUnsupportedFeatureInOfflineMode.ID, '', this.configurationService, this.notificationService); + constructor( id: string, label: string, private autoUpdateValue: boolean, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @INotificationService private notificationService: INotificationService ) { super(id, label, '', true); this.updateEnablement(); @@ -1189,6 +1199,9 @@ export class ToggleAutoUpdateAction extends Action { } run(): TPromise { + if (this.autoUpdateValue) { + return this.notifyOfflineModeAction.run(); + } return this.configurationService.updateValue(AutoUpdateConfigurationKey, this.autoUpdateValue); } } @@ -1201,9 +1214,10 @@ export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { constructor( id = EnableAutoUpdateAction.ID, label = EnableAutoUpdateAction.LABEL, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService ) { - super(id, label, true, configurationService); + super(id, label, true, configurationService, notificationService); } } @@ -1215,9 +1229,10 @@ export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { constructor( id = EnableAutoUpdateAction.ID, label = EnableAutoUpdateAction.LABEL, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @INotificationService notificationService: INotificationService ) { - super(id, label, false, configurationService); + super(id, label, false, configurationService, notificationService); } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 0c222ed08c5dc..4a3a8390b4632 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -65,6 +65,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -297,7 +298,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); this.groupByServersContextKey = GroupByServersContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService); - this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); + this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey) && !this.configurationService.getValue(offlineModeSetting)); this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); this.configurationService.onDidChangeConfiguration(e => { @@ -305,8 +306,8 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.secondaryActions = null; this.updateTitleArea(); } - if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) { - this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); + if (e.affectsConfiguration(ShowRecommendationsOnlyOnDemandKey) || e.affectsConfiguration(offlineModeSetting)) { + this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey) && !this.configurationService.getValue(offlineModeSetting)); } }, this, this.disposables); @@ -483,7 +484,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.searchInstalledExtensionsContextKey.set(InstalledExtensionsView.isInstalledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.groupByServersContextKey.set(ExtensionsListView.isGroupByServersExtensionsQuery(value)); - this.recommendedExtensionsContextKey.set(ExtensionsListView.isRecommendedExtensionsQuery(value)); + this.recommendedExtensionsContextKey.set(ExtensionsListView.isRecommendedExtensionsQuery(value) && this.configurationService.getValue(offlineModeSetting) !== true); this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); if (value) { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 02cd5b25c9fd5..25244ffc141c8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -41,6 +41,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { distinct } from 'vs/base/common/arrays'; import URI from 'vs/base/common/uri'; import { IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; export class ExtensionsListView extends ViewletPanel { @@ -108,7 +109,10 @@ export class ExtensionsListView extends ViewletPanel { async show(query: string): Promise> { const model = await this.query(query); - this.setModel(model); + const emptyListMessage = (!InstalledExtensionsView.isInstalledExtensionsQuery(query) && this.configurationService.getValue(offlineModeSetting) === true) + ? localize('no extensions found in offline mode', "Marketplace search is not supported in offline mode.") + : localize('no extensions found', "No extensions found."); + this.setModel(model, emptyListMessage); return model; } @@ -217,26 +221,6 @@ export class ExtensionsListView extends ViewletPanel { return new PagedModel(this.sortExtensions(result, options)); } - const idMatch = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/.exec(value); - - if (idMatch) { - const name = idMatch[1]; - - return this.extensionsWorkbenchService.queryGallery({ names: [name], source: 'queryById' }) - .then(pager => new PagedModel(pager)); - } - - if (/@outdated/i.test(value)) { - value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - - const local = await this.extensionsWorkbenchService.queryLocal(); - const result = local - .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) - .filter(extension => extension.outdated && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); - - return new PagedModel(this.sortExtensions(result, options)); - } - if (/@disabled/i.test(value)) { value = value.replace(/@disabled/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); @@ -265,6 +249,30 @@ export class ExtensionsListView extends ViewletPanel { return new PagedModel(this.sortExtensions(result, options)); } + if (this.configurationService.getValue(offlineModeSetting) === true) { + return new PagedModel([]); + } + + const idMatch = /@id:(([a-z0-9A-Z][a-z0-9\-A-Z]*)\.([a-z0-9A-Z][a-z0-9\-A-Z]*))/.exec(value); + + if (idMatch) { + const name = idMatch[1]; + + return this.extensionsWorkbenchService.queryGallery({ names: [name], source: 'queryById' }) + .then(pager => new PagedModel(pager)); + } + + if (/@outdated/i.test(value)) { + value = value.replace(/@outdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); + + const local = await this.extensionsWorkbenchService.queryLocal(); + const result = local + .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) + .filter(extension => extension.outdated && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); + + return new PagedModel(this.sortExtensions(result, options)); + } + if (ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)) { return this.getWorkspaceRecommendationsModel(query, options); } else if (ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)) { @@ -544,7 +552,7 @@ export class ExtensionsListView extends ViewletPanel { }); } - private setModel(model: IPagedModel) { + private setModel(model: IPagedModel, emptyListMessage: string) { if (this.list) { this.list.model = model; this.list.scrollTop = 0; @@ -555,7 +563,7 @@ export class ExtensionsListView extends ViewletPanel { this.badge.setCount(count); if (count === 0 && this.isVisible()) { - this.messageBox.textContent = localize('no extensions found', "No extensions found."); + this.messageBox.textContent = emptyListMessage; } else { this.messageBox.textContent = ''; } diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index a492f570789d3..e9dcd4920d77c 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -39,6 +39,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { groupBy } from 'vs/base/common/collections'; import { Schemas } from 'vs/base/common/network'; import { posix } from 'path'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; interface IExtensionStateProvider { (extension: Extension): T; @@ -410,7 +411,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, urlService.registerHandler(this); this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { + if (e.affectsConfiguration(AutoUpdateConfigurationKey) || e.affectsConfiguration(offlineModeSetting)) { if (this.isAutoUpdateEnabled()) { this.checkForUpdates(); } @@ -607,10 +608,13 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } private isAutoUpdateEnabled(): boolean { - return this.configurationService.getValue(AutoUpdateConfigurationKey); + return this.configurationService.getValue(AutoUpdateConfigurationKey) === true && this.configurationService.getValue(offlineModeSetting) !== true; } private eventuallySyncWithGallery(immediate = false): void { + if (this.configurationService.getValue(offlineModeSetting) === true) { + return; + } const loop = () => this.syncWithGallery().then(() => this.eventuallySyncWithGallery()); const delay = immediate ? 0 : ExtensionsWorkbenchService.SyncPeriod; diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts index 89cbd28c3d693..a58939341f699 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -245,6 +245,7 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(INotificationService, new TestNotificationService2()); testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false, showRecommendationsOnlyOnDemand: false }); + testConfigurationService.setUserConfiguration('workbench', { enableOfflineMode: false }); instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { } }); instantiationService.stub(IModelService, { getModels(): any { return []; }, @@ -366,6 +367,17 @@ suite('ExtensionsTipsService Test', () => { }); }); + test('ExtensionTipsService: No Prompt for valid workspace recommendations if offline is set', () => { + testConfigurationService.setUserConfiguration('workbench', { enableOfflineMode: true }); + return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.loadWorkspaceConfigPromise.then(() => { + assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0); + assert.ok(!prompted); + }); + }); + }); + test('ExtensionTipsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => { instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c }); return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 704d31d312aa7..c8d72f917df38 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -66,7 +66,12 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IURLService, URLService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}) }); + instantiationService.stub(IConfigurationService, { + onDidUpdateConfiguration: () => { }, + onDidChangeConfiguration: () => { }, + getConfiguration: () => ({}), + getValue: () => { } + }); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); diff --git a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts index 67f0b27804f68..53a31eb84b86a 100644 --- a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts @@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { update: { channel: string; }; telemetry: { enableCrashReporter: boolean }; keyboard: { touchbar: { enabled: boolean } }; - workbench: { tree: { horizontalScrolling: boolean } }; + workbench: { tree: { horizontalScrolling: boolean }, enableOfflineMode: boolean }; files: { useExperimentalFileWatcher: boolean, watcherExclude: object }; } @@ -37,6 +37,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private clickThroughInactive: boolean; private updateChannel: string; private enableCrashReporter: boolean; + private enableOfflineMode: boolean; private touchbarEnabled: boolean; private treeHorizontalScrolling: boolean; private windowsSmoothScrollingWorkaround: boolean; @@ -107,6 +108,12 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } + // Offline Mode + if (config.workbench && typeof config.workbench.enableOfflineMode === 'boolean' && config.workbench.enableOfflineMode !== this.enableOfflineMode) { + this.enableOfflineMode = config.workbench.enableOfflineMode; + changed = true; + } + // Experimental File Watcher if (config.files && typeof config.files.useExperimentalFileWatcher === 'boolean' && config.files.useExperimentalFileWatcher !== this.experimentalFileWatcher) { this.experimentalFileWatcher = config.files.useExperimentalFileWatcher; diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index b50ecda176529..a5c45c6e61f93 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -27,6 +27,8 @@ import { IWebviewEditorService } from 'vs/workbench/parts/webview/electron-brows import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; function renderBody( body: string, @@ -61,7 +63,8 @@ export class ReleaseNotesManager { @IRequestService private readonly _requestService: IRequestService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IEditorService private readonly _editorService: IEditorService, - @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService + @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { TokenizationRegistry.onDidChange(async () => { if (!this._currentReleaseNotes || !this._lastText) { @@ -78,6 +81,9 @@ export class ReleaseNotesManager { accessor: ServicesAccessor, version: string ): Promise { + if (this._configurationService.getValue(offlineModeSetting) === true) { + return false; + } const releaseNoteText = await this.loadReleaseNotes(version); this._lastText = releaseNoteText; const html = await this.renderBody(releaseNoteText); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 941351830b6b5..bf7ad16f7d8bb 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -30,6 +30,7 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { ReleaseNotesManager } from './releaseNotesEditor'; import { isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { offlineModeSetting, NotifyUnsupportedFeatureInOfflineMode } from 'vs/platform/actions/common/offlineMode'; let releaseNotesManager: ReleaseNotesManager | undefined = undefined; @@ -74,10 +75,14 @@ export abstract class AbstractShowReleaseNotesAction extends Action { this.enabled = false; return TPromise.wrap(showReleaseNotes(this.instantiationService, this.version) - .then(null, () => { - const action = this.instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction); - return action.run().then(() => false); - })); + .then(result => { + return result ? TPromise.as(null) : this.openInBrowser(); + }, this.openInBrowser)); + } + + private openInBrowser(): TPromise { + const action = this.instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction); + return action.run().then(() => false); } } @@ -119,23 +124,24 @@ export class ProductContribution implements IWorkbenchContribution { ) { const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); + const openInBrowser = () => notificationService.prompt( + severity.Info, + nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version), + [{ + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const uri = URI.parse(product.releaseNotesUrl); + openerService.open(uri); + } + }] + ); // was there an update? if so, open release notes if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && product.releaseNotesUrl && lastVersion && pkg.version !== lastVersion) { showReleaseNotes(instantiationService, pkg.version) - .then(undefined, () => { - notificationService.prompt( - severity.Info, - nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version), - [{ - label: nls.localize('releaseNotes', "Release Notes"), - run: () => { - const uri = URI.parse(product.releaseNotesUrl); - openerService.open(uri); - } - }] - ); - }); + .then(result => { + if (!result) { openInBrowser(); } + }, openInBrowser); } // should we show the new license? @@ -320,7 +326,8 @@ export class UpdateContribution implements IGlobalActivity { @IDialogService private dialogService: IDialogService, @IUpdateService private updateService: IUpdateService, @IActivityService private activityService: IActivityService, - @IWindowService private windowService: IWindowService + @IWindowService private windowService: IWindowService, + @IConfigurationService private configurationService: IConfigurationService ) { this.state = updateService.state; @@ -544,6 +551,15 @@ export class UpdateContribution implements IGlobalActivity { } private getUpdateAction(): IAction | null { + if (this.configurationService.getValue(offlineModeSetting) === true) { + return new NotifyUnsupportedFeatureInOfflineMode( + NotifyUnsupportedFeatureInOfflineMode.ID, + nls.localize('checkForUpdates', "Check for Updates..."), + this.configurationService, + this.notificationService + ); + } + const state = this.updateService.state; switch (state.type) { diff --git a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts index 2b63e252efbfb..f2b84de9b26eb 100644 --- a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts +++ b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts @@ -18,6 +18,7 @@ import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; +import { offlineModeSetting } from 'vs/platform/actions/common/offlineMode'; export const ICrashReporterService = createDecorator('crashReporterService'); @@ -65,7 +66,7 @@ export class CrashReporterService implements ICrashReporterService { @IConfigurationService configurationService: IConfigurationService ) { const config = configurationService.getValue(TELEMETRY_SECTION_ID); - this.isEnabled = !!config.enableCrashReporter; + this.isEnabled = !!config.enableCrashReporter && configurationService.getValue(offlineModeSetting) !== true; if (this.isEnabled) { this.startCrashReporter();