diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 80145240039..85548f51965 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -30,6 +30,16 @@ exports.load = function (modulePaths, resultCallback, options) { const path = require('path'); const args = parseURLQueryArgs(); + /** + * // configuration: IWindowConfiguration + * @type {{ + * zoomLevel?: number, + * extensionDevelopmentPath?: string | string[], + * extensionTestsPath?: string, + * userEnv?: { [key: string]: string | undefined }, + * appRoot?: string, + * nodeCachedDataDir?: string + * }} */ const configuration = JSON.parse(args['config'] || '{}') || {}; // Apply zoom level early to avoid glitches diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index a90c87136cd..dbec9eadbbb 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -49,7 +49,14 @@ bootstrapWindow.load([ }); /** - * @param {object} configuration + * // configuration: IWindowConfiguration + * @param {{ + * partsSplashPath?: string, + * highContrast?: boolean, + * extensionDevelopmentPath?: string | string[], + * folderUri?: object, + * workspace?: object + * }} configuration */ function showPartsSplash(configuration) { perf.mark('willShowPartsSplash'); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 987b51622b6..a7959f13d96 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -30,7 +30,7 @@ import { endsWith } from 'vs/base/common/strings'; export interface IWindowCreationOptions { state: IWindowState; - extensionDevelopmentPath?: string; + extensionDevelopmentPath?: string | string[]; isExtensionTestHost?: boolean; } @@ -217,9 +217,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.config.extensionTestsPath; } - get extensionDevelopmentPath(): string | undefined { + /* + get extensionDevelopmentPaths(): string | string[] | undefined { return this.config.extensionDevelopmentPath; } + */ get config(): IWindowConfiguration { return this.currentConfig; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 9eda074692f..2b5141f74ca 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -1153,7 +1153,7 @@ export class WindowsManager implements IWindowsMainService { return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow }; } - openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void { + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string | string[], openConfig: IOpenConfiguration): void { // Reload an existing extension development host window on the same path // We currently do not allow more than one extension development window @@ -1207,9 +1207,30 @@ export class WindowsManager implements IWindowsMainService { openConfig.cli['folder-uri'] = folderUris; openConfig.cli['file-uri'] = fileUris; - const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); - if (match) { - openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + if (Array.isArray(extensionDevelopmentPath)) { + let authority: string | undefined = undefined; + for (let p of extensionDevelopmentPath) { + const match = p.match(/^vscode-remote:\/\/([^\/]+)/); + if (match) { + const auth = URI.parse(p).authority; + if (authority) { + if (auth !== authority) { + console.log('more than one authority'); + } + } else { + authority = auth; + } + } + } + if (authority) { + openConfig.cli['remote'] = authority; + } + + } else { + const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); + if (match) { + openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + } } // Open it diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts index aa76820be2f..dc4b62e41ff 100644 --- a/src/vs/code/node/windowsFinder.ts +++ b/src/vs/code/node/windowsFinder.ts @@ -14,7 +14,7 @@ export interface ISimpleWindow { openedWorkspace?: IWorkspaceIdentifier; openedFolderUri?: URI; - extensionDevelopmentPath?: string; + extensionDevelopmentPath?: string | string[]; lastFocusTime: number; } @@ -95,13 +95,33 @@ export function findWindowOnWorkspace(windows: W[], wor return null; } -export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string): W | null { +export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string | string[]): W | null { + + const matches = (uriString: string): boolean => { + if (Array.isArray(extensionDevelopmentPath)) { + return extensionDevelopmentPath.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */)); + } else if (extensionDevelopmentPath) { + return extpath.isEqual(extensionDevelopmentPath, uriString, !platform.isLinux /* ignorecase */); + } + return false; + }; + for (const window of windows) { - // match on extension development path. The path can be a path or uri string, using paths.isEqual is not 100% correct but good enough - if (window.extensionDevelopmentPath && extpath.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) { - return window; + // match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough + + if (window.extensionDevelopmentPath) { + if (Array.isArray(window.extensionDevelopmentPath)) { + if (window.extensionDevelopmentPath.some(p => matches(p))) { + return window; + } + } else if (window.extensionDevelopmentPath) { + if (matches(window.extensionDevelopmentPath)) { + return window; + } + } } } + return null; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index fa3fe8d6880..7eda0801b81 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -37,7 +37,7 @@ export interface ParsedArgs { logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; 'builtin-extensions-dir'?: string; - extensionDevelopmentPath?: string; // either a local path or a URI + extensionDevelopmentPath?: string | string[]; // one or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; @@ -116,7 +116,7 @@ export interface IEnvironmentService { disableExtensions: boolean | string[]; builtinExtensionsPath: string; extensionsPath: string; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsLocationURI?: URI; debugExtensionHost: IExtensionHostDebugParams; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 43866f87376..375890840ec 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -172,9 +172,16 @@ export class EnvironmentService implements IEnvironmentService { } @memoize - get extensionDevelopmentLocationURI(): URI | undefined { + get extensionDevelopmentLocationURI(): URI | URI[] | undefined { const s = this._args.extensionDevelopmentPath; - if (s) { + if (Array.isArray(s)) { + return s.map(p => { + if (/^[^:/?#]+?:\/\//.test(p)) { + return URI.parse(p); + } + return URI.file(path.normalize(p)); + }); + } else if (s) { if (/^[^:/?#]+?:\/\//.test(s)) { return URI.parse(s); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 0f71862cb64..2095d2e400b 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -97,7 +97,7 @@ export interface IWindowsMainService { enterWorkspace(win: ICodeWindow, path: URI): Promise; closeWorkspace(win: ICodeWindow): void; open(openConfig: IOpenConfiguration): ICodeWindow[]; - openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void; + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string | string[], openConfig: IOpenConfiguration): void; pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; pickFileAndOpen(options: INativeOpenDialogOptions): Promise; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1795736b104..e8ad002e196 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ export interface IEnvironment { isExtensionDevelopmentDebug: boolean; appRoot?: URI; appSettingsHome?: URI; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsLocationURI?: URI; globalStorageHome: URI; userHome: URI; diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/nodeless.simpleservices.ts index ef70ffb3cf9..4f49d986f87 100644 --- a/src/vs/workbench/browser/nodeless.simpleservices.ts +++ b/src/vs/workbench/browser/nodeless.simpleservices.ts @@ -251,7 +251,7 @@ export class SimpleEnvironmentService implements IEnvironmentService { disableExtensions: boolean | string[]; builtinExtensionsPath: string; extensionsPath: string; - extensionDevelopmentLocationURI?: URI; + extensionDevelopmentLocationURI?: URI | URI[]; extensionTestsPath?: string; debugExtensionHost: IExtensionHostDebugParams; debugSearch: IDebugParams; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 0084ade5ccf..421b4e1c032 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -326,7 +326,7 @@ suite('ExtensionsTipsService Test', () => { }); test('ExtensionTipsService: No Prompt for valid workspace recommendations during extension development', () => { - instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: URI.file('/folder/file') }); + instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')] }); return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions); }); diff --git a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts index 29948328db9..4a77c8572b3 100644 --- a/src/vs/workbench/services/extensions/common/extensionDevOptions.ts +++ b/src/vs/workbench/services/extensions/common/extensionDevOptions.ts @@ -16,8 +16,19 @@ export interface IExtensionDevOptions { export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions { // handle extension host lifecycle a bit special when we know we are developing an extension that runs inside let isExtensionDevHost = environmentService.isExtensionDevelopment; - const extDevLoc = environmentService.extensionDevelopmentLocationURI; - const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file; + + let debugOk = true; + let extDevLoc = environmentService.extensionDevelopmentLocationURI; + if (Array.isArray(extDevLoc)) { + for (let x of extDevLoc) { + if (x.scheme !== Schemas.file) { + debugOk = false; + } + } + } else if (extDevLoc && extDevLoc.scheme !== Schemas.file) { + debugOk = false; + } + let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 547a6128b63..32113356456 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -294,10 +294,30 @@ export class CachedExtensionScanner { // Always load developed extensions while extensions development let developedExtensions: Promise = Promise.resolve([]); - if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { - developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( - new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log - ); + if (environmentService.isExtensionDevelopment) { + + if (Array.isArray(environmentService.extensionDevelopmentLocationURI)) { + + const extDescsP = environmentService.extensionDevelopmentLocationURI.filter(extLoc => extLoc.scheme === Schemas.file).map(extLoc => { + return ExtensionScanner.scanOneOrMultipleExtensions( + new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(extLoc), false, true, translations), log + ); + }); + developedExtensions = Promise.all(extDescsP).then((extDescArrays: IExtensionDescription[][]) => { + let extDesc: IExtensionDescription[] = []; + for (let eds of extDescArrays) { + extDesc = extDesc.concat(eds); + } + return extDesc; + }); + + } else if (environmentService.extensionDevelopmentLocationURI) { + if (environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { + developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( + new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log + ); + } + } } return Promise.all([finalBuiltinExtensions, userExtensions, developedExtensions]).then((extensionDescriptions: IExtensionDescription[][]) => { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 376f4f03271..95e1edae738 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -102,12 +102,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate())); this._toDispose.push(this._extensionHostDebugService.onClose(resource => { - if (this._isExtensionDevHost && isEqual(resource, this._environmentService.extensionDevelopmentLocationURI)) { + if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) { this._windowService.closeWindow(); } })); this._toDispose.push(this._extensionHostDebugService.onReload(resource => { - if (this._isExtensionDevHost && isEqual(resource, this._environmentService.extensionDevelopmentLocationURI)) { + if (this._isExtensionDevHost && this.matchesExtDevLocations(resource)) { this._windowService.reloadWindow(); } })); @@ -119,6 +119,18 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { })); } + // returns true if the given resource url matches one of the extension development paths passed to VS Code + private matchesExtDevLocations(resource: URI): boolean { + + const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; + if (Array.isArray(extDevLocs)) { + return extDevLocs.some(extDevLoc => isEqual(extDevLoc, resource)); + } else if (extDevLocs) { + return isEqual(extDevLocs, resource); + } + return false; + } + public dispose(): void { this.terminate(); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index b337206dc00..7c5e8a8dd58 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -645,6 +645,23 @@ export class ExtensionService extends Disposable implements IExtensionService { this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); } + private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { + if (this._environmentService.isExtensionDevelopment) { + const extDevLoc = this._environmentService.extensionDevelopmentLocationURI; + const extLocation = extension.extensionLocation; + if (Array.isArray(extDevLoc)) { + for (let p of extDevLoc) { + if (isEqualOrParent(extLocation, p)) { + return true; + } + } + } else if (extDevLoc) { + return isEqualOrParent(extLocation, extDevLoc); + } + } + return false; + } + private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { return this._extensionEnablementService.getDisabledExtensions() .then(disabledExtensions => { @@ -676,14 +693,11 @@ export class ExtensionService extends Disposable implements IExtensionService { (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') || (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); + for (const extension of allExtensions) { - const isExtensionUnderDevelopment = ( - this._environmentService.isExtensionDevelopment - && this._environmentService.extensionDevelopmentLocationURI - && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI) - ); + // Do not disable extensions under development - if (!isExtensionUnderDevelopment) { + if (!this.isExtensionUnderDevelopment(extension)) { if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) { continue; } diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 8e1fb06d0a9..3ceedd7853b 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -12,7 +12,7 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn export interface IGetEnvironmentDataArguments { language: string; remoteAuthority: string; - extensionDevelopmentPath: UriComponents | undefined; + extensionDevelopmentPath: UriComponents | UriComponents[] | undefined; } export interface IRemoteAgentEnvironmentDTO { @@ -34,7 +34,7 @@ export class RemoteExtensionEnvironmentChannelClient { constructor(private channel: IChannel) { } - getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI): Promise { + getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI | URI[]): Promise { const args: IGetEnvironmentDataArguments = { language: platform.language, remoteAuthority, diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index f02d7e267e8..77b8713f715 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -249,9 +249,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + const extDevLoc = this.environmentService.extensionDevelopmentLocationURI; + let uri: URI | undefined; + if (Array.isArray(extDevLoc)) { + // if there are more than one ext dev paths, use first + if (extDevLoc.length > 0) { + uri = extDevLoc[0]; + } + } else { + uri = extDevLoc; + } + return Promise.all([ this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - return this.colorThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } else { @@ -260,7 +271,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); }), this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - return this.iconThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + return this.iconThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } else {