diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 6d41e85e42..f845d0bf9e 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -48,7 +48,9 @@ export namespace Schemas { export const command: string = 'command'; - export const vscodeRemote: string = 'vscode-remote'; + // NOTE@coder: Changed this so it'll be reflected in the explorer to prevent + // confusion with vscode-remote itself. + export const vscodeRemote: string = 'code-server'; export const vscodeRemoteResource: string = 'vscode-remote-resource'; @@ -96,12 +98,12 @@ class RemoteAuthoritiesImpl { if (host && host.indexOf(':') !== -1) { host = `[${host}]`; } - const port = this._ports[authority]; + // NOTE@coder: Changed this to work against the current path. const connectionToken = this._connectionTokens[authority]; return URI.from({ scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, - authority: `${host}:${port}`, - path: `/vscode-remote-resource`, + authority: window.location.host, + path: `${window.location.pathname.replace(/\/+$/, '')}/vscode-remote-resource`, query: `path=${encodeURIComponent(uri.path)}&tkn=${encodeURIComponent(connectionToken)}` }); } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index a657f4a4d9..66bd13dffa 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -56,6 +56,16 @@ if (typeof navigator === 'object' && !isElectronRenderer) { _isWeb = true; _locale = navigator.language; _language = _locale; + const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); + const rawNlsConfig = el && el.getAttribute('data-settings'); + if (rawNlsConfig) { + try { + const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); + _locale = nlsConfig.locale; + _translationsConfigFile = nlsConfig._translationsConfigFile; + _language = nlsConfig.availableLanguages['*'] || LANGUAGE_DEFAULT; + } catch (error) { /* Oh well. */ } + } } else if (typeof process === 'object') { _isWindows = (process.platform === 'win32'); _isMacintosh = (process.platform === 'darwin'); diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js index 3ae24454cb..fac8679290 100644 --- a/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js @@ -146,7 +146,10 @@ function factory(nodeRequire, path, fs, perf) { function getLanguagePackConfigurations(userDataPath) { const configFile = path.join(userDataPath, 'languagepacks.json'); try { - return nodeRequire(configFile); + // NOTE@coder: Swapped require with readFile since require is cached and + // we don't restart the server-side portion of code-server when the + // language changes. + return JSON.parse(fs.readFileSync(configFile, "utf8")); } catch (err) { // Do nothing. If we can't read the file we have no // language pack config. diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 990755c4f3..06449bb9cb 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -36,6 +36,8 @@ export interface ParsedArgs { logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; 'builtin-extensions-dir'?: string; + 'extra-extensions-dir'?: string[]; + 'extra-builtin-extensions-dir'?: string[]; extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI 'extension-development-confirm-save'?: boolean; @@ -169,4 +171,6 @@ export interface IEnvironmentService { driverVerbose: boolean; galleryMachineIdResource?: URI; + extraExtensionPaths: string[]; + extraBuiltinExtensionPaths: string[]; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 3e48fe4ddd..e0962b8736 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -58,6 +58,8 @@ export const OPTIONS: OptionDescriptions> = { 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'builtin-extensions-dir': { type: 'string' }, + 'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' }, + 'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, 'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extension.") }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index f7d207009d..5c37b52dab 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -260,6 +260,12 @@ export class EnvironmentService implements IEnvironmentService { get driverHandle(): string | undefined { return this._args['driver']; } get driverVerbose(): boolean { return !!this._args['driver-verbose']; } + @memoize get extraExtensionPaths(): string[] { + return (this._args['extra-extensions-dir'] || []).map((p) => parsePathArg(p, process)); + } + @memoize get extraBuiltinExtensionPaths(): string[] { + return (this._args['extra-builtin-extensions-dir'] || []).map((p) => parsePathArg(p, process)); + } constructor(private _args: ParsedArgs, private _execPath: string) { if (!process.env['VSCODE_LOGS']) { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index f0eaa74a59..3abf9e1752 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -731,11 +731,15 @@ export class ExtensionManagementService extends Disposable implements IExtension private scanSystemExtensions(): Promise { this.logService.trace('Started scanning system extensions'); - const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System) - .then(result => { - this.logService.trace('Scanned system extensions:', result.length); - return result; - }); + const systemExtensionsPromise = Promise.all([ + this.scanExtensions(this.systemExtensionsPath, ExtensionType.System), + ...this.environmentService.extraBuiltinExtensionPaths + .map((path) => this.scanExtensions(path, ExtensionType.System)) + ]).then((results) => { + const result = results.reduce((flat, current) => flat.concat(current), []); + this.logService.trace('Scanned system extensions:', result.length); + return result; + }); if (this.environmentService.isBuilt) { return systemExtensionsPromise; } @@ -757,9 +761,16 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(([systemExtensions, devSystemExtensions]) => [...systemExtensions, ...devSystemExtensions]); } + private scanAllUserExtensions(folderName: string, type: ExtensionType): Promise { + return Promise.all([ + this.scanExtensions(folderName, type), + ...this.environmentService.extraExtensionPaths.map((p) => this.scanExtensions(p, ExtensionType.User)) + ]).then((results) => results.reduce((flat, current) => flat.concat(current), [])); + } + private scanUserExtensions(excludeOutdated: boolean): Promise { this.logService.trace('Started scanning user extensions'); - return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)]) + return Promise.all([this.getUninstalledExtensions(), this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User)]) .then(([uninstalled, extensions]) => { extensions = extensions.filter(e => !uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]); if (excludeOutdated) { @@ -774,6 +785,12 @@ export class ExtensionManagementService extends Disposable implements IExtension private scanExtensions(root: string, type: ExtensionType): Promise { const limiter = new Limiter(10); return pfs.readdir(root) + .catch((error) => { + if (error.code !== 'ENOENT') { + throw error; + } + return []; + }) .then(extensionsFolders => Promise.all(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type))))) .then(extensions => extensions.filter(e => e && e.identifier)); } @@ -812,7 +829,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private async removeUninstalledExtensions(): Promise { const uninstalled = await this.getUninstalledExtensions(); - const extensions = await this.scanExtensions(this.extensionsPath, ExtensionType.User); // All user extensions + const extensions = await this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User); // All user extensions const installed: Set = new Set(); for (const e of extensions) { if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) { @@ -831,7 +848,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } private removeOutdatedExtensions(): Promise { - return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions + return this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions .then(extensions => { const toRemove: ILocalExtension[] = []; diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 2de51e8d32..837770990e 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -22,10 +22,16 @@ if (isWeb) { if (Object.keys(product).length === 0) { assign(product, { version: '1.39.0-dev', + codeServerVersion: 'dev', nameLong: 'Visual Studio Code Web Dev', nameShort: 'VSCode Web Dev' }); } + const el = document.getElementById('vscode-remote-product-configuration'); + const rawProductConfiguration = el && el.getAttribute('data-settings'); + if (rawProductConfiguration) { + assign(product, JSON.parse(rawProductConfiguration)); + } } // Node: AMD loader @@ -35,7 +41,7 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === ' const rootPath = path.dirname(getPathFromAmdModule(require, '')); product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration); - const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; }; + const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; codeServerVersion: string; }; // Running out of sources if (env['VSCODE_DEV']) { @@ -47,7 +53,8 @@ else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === ' } assign(product, { - version: pkg.version + version: pkg.version, + codeServerVersion: pkg.codeServerVersion, }); } diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 5aa5c32d7e..e4e7fd4174 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -15,6 +15,7 @@ export interface IProductService extends Readonly { export interface IProductConfiguration { readonly version: string; + readonly codeServerVersion: string; readonly date?: string; readonly quality?: string; readonly commit?: string; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index d0f6e6b18a..1966fd297d 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -205,7 +205,8 @@ export class BrowserSocketFactory implements ISocketFactory { } connect(host: string, port: number, query: string, callback: IConnectCallback): void { - const socket = this._webSocketFactory.create(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`); + // NOTE@coder: Modified to work against the current path. + const socket = this._webSocketFactory.create(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}${window.location.pathname}?${query}&skipWebSocketFrames=false`); const errorListener = socket.onError((err) => callback(err, undefined)); socket.onOpen(() => { errorListener.dispose(); @@ -213,6 +214,3 @@ export class BrowserSocketFactory implements ISocketFactory { }); } } - - - diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 8a1c95d37b..8225a85d47 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -6,7 +6,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import product from 'vs/platform/product/common/product'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -44,7 +43,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } constructor( - @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + _: any, // NOTE@coder: This depends on Electron so we skip it. @IConfigurationService protected configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IRequestService protected requestService: IRequestService, @@ -152,15 +151,8 @@ export abstract class AbstractUpdateService implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleMainService.quit(true /* from update */).then(vetod => { - this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); - if (vetod) { - return; - } - this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); this.doQuitAndInstall(); - }); return Promise.resolve(undefined); } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 2905c52411..303ddf211f 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -57,6 +57,7 @@ import './mainThreadComments'; import './mainThreadTask'; import './mainThreadLabelService'; import 'vs/workbench/api/common/apiCommands'; +import 'vs/server/src/browser/mainThreadNodeProxy'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 230d09a290..84753e6ac7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -86,6 +87,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const rpcProtocol = accessor.get(IExtHostRpcService); const extHostStorage = accessor.get(IExtHostStorage); const extHostLogService = accessor.get(ILogService); + const extHostNodeProxy = accessor.get(IExtHostNodeProxy); // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); @@ -93,6 +95,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); + rpcProtocol.set(ExtHostContext.ExtHostNodeProxy, extHostNodeProxy); // automatically create and register addressable instances const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 7bd6aa6cb7..7c28136366 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -627,6 +627,10 @@ export interface MainThreadLabelServiceShape extends IDisposable { $unregisterResourceLabelFormatter(handle: number): void; } +export interface MainThreadNodeProxyShape extends IDisposable { + $send(message: string): void; +} + export interface MainThreadSearchShape extends IDisposable { $registerFileSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void; @@ -860,6 +864,13 @@ export interface ExtHostLabelServiceShape { $registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable; } +export interface ExtHostNodeProxyShape { + $onMessage(message: string): void; + $onClose(): void; + $onDown(): void; + $onUp(): void; +} + export interface ExtHostSearchShape { $provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise; $provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise; @@ -1384,7 +1395,8 @@ export const MainContext = { MainThreadSearch: createMainId('MainThreadSearch'), MainThreadTask: createMainId('MainThreadTask'), MainThreadWindow: createMainId('MainThreadWindow'), - MainThreadLabelService: createMainId('MainThreadLabelService') + MainThreadLabelService: createMainId('MainThreadLabelService'), + MainThreadNodeProxy: createMainId('MainThreadNodeProxy') }; export const ExtHostContext = { @@ -1418,5 +1430,6 @@ export const ExtHostContext = { ExtHostStorage: createMainId('ExtHostStorage'), ExtHostUrls: createExtId('ExtHostUrls'), ExtHostOutputService: createMainId('ExtHostOutputService'), - ExtHosLabelService: createMainId('ExtHostLabelService') + ExtHosLabelService: createMainId('ExtHostLabelService'), + ExtHostNodeProxy: createMainId('ExtHostNodeProxy') }; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index b5ce835e07..22be8516c1 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; -import { originalFSPath, joinPath } from 'vs/base/common/resources'; +import { originalFSPath } from 'vs/base/common/resources'; import { Barrier } from 'vs/base/common/async'; import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; @@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy'; interface ITestRunner { /** Old test runner API, as exported from `vscode/lib/testrunner` */ @@ -76,6 +77,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio protected readonly _extHostWorkspace: ExtHostWorkspace; protected readonly _extHostConfiguration: ExtHostConfiguration; protected readonly _logService: ILogService; + protected readonly _nodeProxy: IExtHostNodeProxy; protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; @@ -104,7 +106,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio @IExtHostConfiguration extHostConfiguration: IExtHostConfiguration, @ILogService logService: ILogService, @IExtHostInitDataService initData: IExtHostInitDataService, - @IExtensionStoragePaths storagePath: IExtensionStoragePaths + @IExtensionStoragePaths storagePath: IExtensionStoragePaths, + @IExtHostNodeProxy nodeProxy: IExtHostNodeProxy, ) { this._hostUtils = hostUtils; this._extHostContext = extHostContext; @@ -113,6 +116,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio this._extHostWorkspace = extHostWorkspace; this._extHostConfiguration = extHostConfiguration; this._logService = logService; + this._nodeProxy = nodeProxy; this._disposables = new DisposableStore(); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); @@ -331,14 +335,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ - this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder), + this._loadCommonJSModule(extensionDescription, activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); }); } - protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; + protected abstract _loadCommonJSModule(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index a227d8a67b..92553a976c 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -26,6 +26,8 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy'; // register singleton services registerSingleton(ILogService, ExtHostLogService); @@ -42,3 +44,19 @@ registerSingleton(IExtHostSearch, ExtHostSearch); registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); registerSingleton(IExtHostStorage, ExtHostStorage); + +function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { + return class { + constructor() { + return new Proxy({}, { + get(target: any, prop: string | number) { + if (target[prop]) { + return target[prop]; + } + throw new Error(`Not Implemented: ${name}->${String(prop)}`); + } + }); + } + }; +} +registerSingleton(IExtHostNodeProxy, class extends NotImplementedProxy(IExtHostNodeProxy) {}); diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 49a8e254fd..99d233aed5 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -13,6 +13,8 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { joinPath } from 'vs/base/common/resources'; class NodeModuleRequireInterceptor extends RequireInterceptor { @@ -75,7 +77,10 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { }; } - protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected _loadCommonJSModule(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + if (!URI.isUri(module)) { + module = joinPath(module.extensionLocation, module.main!); + } if (module.scheme !== Schemas.file) { throw new Error(`Cannot load URI: '${module}', must be of file-scheme`); } diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index afd82468c0..289145be54 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -9,6 +9,9 @@ import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHost import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; +import { joinPath } from 'vs/base/common/resources'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { loadCommonJSModule } from 'vs/server/src/browser/worker'; class WorkerRequireInterceptor extends RequireInterceptor { @@ -41,7 +44,14 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { await this._fakeModules.install(); } - protected async _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected async _loadCommonJSModule(module: URI | IExtensionDescription, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + if (!URI.isUri(module) && module.extensionKind !== 'web') { + return loadCommonJSModule(module, activationTimesBuilder, this._nodeProxy, this._logService, this._fakeModules.getModule('vscode', module.extensionLocation)); + } + + if (!URI.isUri(module)) { + module = joinPath(module.extensionLocation, module.main!); + } module = module.with({ path: ensureSuffix(module.path, '.js') }); const response = await fetch(module.toString(true)); @@ -57,7 +67,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { const _exports = {}; const _module = { exports: _exports }; const _require = (request: string) => { - const result = this._fakeModules.getModule(request, module); + const result = this._fakeModules.getModule(request, module); if (result === undefined) { throw new Error(`Cannot load module '${request}'`); } diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 005a025aa9..ab392630c0 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -32,6 +32,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo import { withNullAsUndefined } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IUploadService } from 'vs/server/src/browser/upload'; export interface IDraggedResource { resource: URI; @@ -167,14 +168,15 @@ export class ResourcesDropHandler { @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IUploadService private readonly uploadService: IUploadService, ) { } async handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): Promise { const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled); if (!untitledOrFileResources.length) { - return; + return this.uploadService.handleDrop(event, resolveTargetGroup, afterDrop, targetIndex); } // Make the window active to handle the drop properly within diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 84c46faa36..957e8412e1 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -48,6 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider'; import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; +import { initialize } from 'vs/server/src/browser/client'; class BrowserMain extends Disposable { @@ -84,6 +85,7 @@ class BrowserMain extends Disposable { // Startup workbench.startup(); + await initialize(services.serviceCollection); } private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { @@ -238,6 +240,7 @@ class BrowserMain extends Disposable { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); const remoteFileSystemProvider = this._register(new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); + fileService.registerProvider(Schemas.file, remoteFileSystemProvider); if (!this.configuration.userDataProvider) { const remoteUserDataUri = this.getRemoteUserDataUri(); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 1f4cd95f65..061931cbde 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -209,7 +209,7 @@ configurationRegistry.registerConfiguration({ 'files.exclude': { 'type': 'object', 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), - 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, + 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true, '**/.code-server-partial-upload-*': true }, 'scope': ConfigurationScope.RESOURCE, 'additionalProperties': { 'anyOf': [ diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index cc4bcb28c5..98679a8b32 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -47,6 +47,7 @@ import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/work import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { Emitter } from 'vs/base/common/event'; +import { IUploadService } from 'vs/server/src/browser/upload'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -444,7 +445,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IInstantiationService private instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IHostService private hostService: IHostService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, + @IUploadService private readonly uploadService: IUploadService, ) { this.toDispose = []; @@ -605,6 +607,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { + return this.uploadService.handleExternalDrop(data, target, originalEvent); const droppedResources = extractResources(originalEvent, true); // Check for dropped external files to be folders const result = await this.fileService.resolveAll(droppedResources); diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index e6b9fd854b..a3d0a46e3a 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -308,7 +308,8 @@ } else { // Rewrite vscode-resource in csp if (data.endpoint) { - csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint)); + // NOTE@coder: Add back the trailing slash so it'll work for sub-paths. + csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint + "/")); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 5f221e07ff..bfd592382c 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -15,7 +15,6 @@ import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/window import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; -import product from 'vs/platform/product/common/product'; export class BrowserWindowConfiguration implements IWindowConfiguration { @@ -180,12 +179,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment driverHandle?: string; driverVerbose: boolean; galleryMachineIdResource?: URI; + extraExtensionPaths: string[]; + extraBuiltinExtensionPaths: string[]; readonly logFile: URI; get webviewExternalEndpoint(): string { - // TODO: get fallback from product.json - return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') - .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); + // NOTE@coder: Modified to work against the current URL. + return `${window.location.origin}${window.location.pathname.replace(/\/+$/, '')}/webview/`; } get webviewResourceRoot(): string { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 000e5f7b4a..39f46e68a1 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -119,6 +119,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } else { // remote: only enabled and none-web'ish extension + localExtensions.push(...remoteEnv.extensions.filter(extension => this._isEnabled(extension) && isWebExtension(extension, this._configService))); remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService)); this._checkEnableProposedApi(remoteEnv.extensions); diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 49b2d270c0..45200ccdbb 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -12,7 +12,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { const extensionKind = getExtensionKind(manifest, configurationService); - return extensionKind === 'web'; + return extensionKind === 'web' || manifest.name === 'vim'; } export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 9f5a14f6cb..ca952f3d4d 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -42,12 +42,13 @@ const args = minimist(process.argv.slice(2), { const Module = require.__$__nodeRequire('module') as any; const originalLoad = Module._load; - Module._load = function (request: string) { + Module._load = function (request: string, parent: object, isMain: boolean) { if (request === 'natives') { throw new Error('Either the extension or a NPM dependency is using the "natives" node module which is unsupported as it can cause a crash of the extension host. Click [here](https://go.microsoft.com/fwlink/?linkid=871887) to find out more'); } - return originalLoad.apply(this, arguments); + // NOTE@coder: Map node_module.asar requests to regular node_modules. + return originalLoad.apply(this, [request.replace(/node_modules\.asar(\.unpacked)?/, 'node_modules'), parent, isMain]); }; })(); @@ -120,8 +121,11 @@ function _createExtHostProtocol(): Promise { // Wait for rich client to reconnect protocol.onSocketClose(() => { - // The socket has closed, let's give the renderer a certain amount of time to reconnect - disconnectRunner1.schedule(); + // NOTE@coder: Inform the server so we can manage offline + // connections there instead. Our goal is to persist connections + // forever (to a reasonable point) to account for things like + // hibernating overnight. + process.send!({ type: 'VSCODE_EXTHOST_DISCONNECTED' }); }); } } diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index 3bdfa1a79f..ded21cf9c6 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -21,6 +21,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensio import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; +import { ExtHostNodeProxy, IExtHostNodeProxy } from 'vs/server/src/browser/extHostNodeProxy'; // register singleton services registerSingleton(ILogService, ExtHostLogService); @@ -32,6 +33,7 @@ registerSingleton(IExtHostCommands, ExtHostCommands); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); registerSingleton(IExtHostStorage, ExtHostStorage); registerSingleton(IExtHostExtensionService, ExtHostExtensionService); +registerSingleton(IExtHostNodeProxy, ExtHostNodeProxy); // register services that only throw errors function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { diff --git a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts index 99394090da..4891e0fece 100644 --- a/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts +++ b/src/vs/workbench/services/localizations/electron-browser/localizationsService.ts @@ -5,17 +5,17 @@ import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class LocalizationsService { _serviceBrand: undefined; constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, ) { - return createChannelSender(sharedProcessService.getChannel('localizations')); + return createChannelSender(remoteAgentService.getConnection()!.getChannel('localizations')); } } diff --git a/src/vs/workbench/services/update/electron-browser/updateService.ts b/src/vs/workbench/services/update/electron-browser/updateService.ts index b8f6558b2c..b1fe6b14fd 100644 --- a/src/vs/workbench/services/update/electron-browser/updateService.ts +++ b/src/vs/workbench/services/update/electron-browser/updateService.ts @@ -6,7 +6,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { IUpdateService, State } from 'vs/platform/update/common/update'; -import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class NativeUpdateService implements IUpdateService { @@ -21,8 +21,9 @@ export class NativeUpdateService implements IUpdateService { private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('update'); + // NOTE@coder: patched to work in the browser. + constructor(@IRemoteAgentService remoteAgentService: IRemoteAgentService) { + this.channel = remoteAgentService.getConnection()!.getChannel('update'); // always set this._state as the state changes this.onStateChange(state => this._state = state); diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index fa9c9dd7a9..688d6c1934 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -34,11 +34,14 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; -import 'vs/workbench/services/telemetry/browser/telemetryService'; +// NOTE@coder: We send it all to the server side to be processed there instead. +// import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; -import 'vs/workbench/services/update/browser/updateService'; +// NOTE@coder: Use the electron-browser version since it already comes with a +// channel which lets us actually perform updates. +import 'vs/workbench/services/update/electron-browser/updateService'; import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; import 'vs/workbench/services/workspaces/browser/workspacesService'; import 'vs/workbench/services/workspaces/browser/workspaceEditingService';