From aa1474b675948f5454094e7b5a55ba158bb86f71 Mon Sep 17 00:00:00 2001 From: Asher Date: Sun, 19 May 2019 17:58:47 -0500 Subject: [PATCH] Extra extensions directories (#694) * Allow setting paths for builtin exts and extra dirs The extra directories aren't used yet, just available from the environment service and to the shared process. * Utilize extra builtin extensions path * Utilize extra extensions directory * Fix cached mtimes for extra extension dirs * Simplify extension cache equality check --- packages/protocol/src/browser/client.ts | 2 + packages/protocol/src/common/connection.ts | 2 + packages/protocol/src/node/server.ts | 4 + packages/protocol/src/proto/client.proto | 2 + packages/protocol/src/proto/client_pb.d.ts | 12 ++ packages/protocol/src/proto/client_pb.js | 99 ++++++++++- packages/protocol/test/server.test.ts | 2 + packages/server/src/cli.ts | 26 ++- packages/server/src/vscode/sharedProcess.ts | 4 + .../vscode/src/fill/environmentService.ts | 12 ++ packages/vscode/src/fill/paths.ts | 28 +++ scripts/vscode.patch | 161 +++++++++++++++++- 12 files changed, 341 insertions(+), 13 deletions(-) diff --git a/packages/protocol/src/browser/client.ts b/packages/protocol/src/browser/client.ts index 77bba1b6..3f2d35f6 100644 --- a/packages/protocol/src/browser/client.ts +++ b/packages/protocol/src/browser/client.ts @@ -278,6 +278,8 @@ export class Client { shell: init.getShell(), extensionsDirectory: init.getExtensionsDirectory(), builtInExtensionsDirectory: init.getBuiltinExtensionsDir(), + extraExtensionDirectories: init.getExtraExtensionDirectoriesList(), + extraBuiltinExtensionDirectories: init.getExtraBuiltinExtensionDirectoriesList(), }; this.initDataEmitter.emit(this._initData); break; diff --git a/packages/protocol/src/common/connection.ts b/packages/protocol/src/common/connection.ts index f4f60ded..901bc3d5 100644 --- a/packages/protocol/src/common/connection.ts +++ b/packages/protocol/src/common/connection.ts @@ -25,6 +25,8 @@ export interface InitData { readonly shell: string; readonly extensionsDirectory: string; readonly builtInExtensionsDirectory: string; + readonly extraExtensionDirectories: string[]; + readonly extraBuiltinExtensionDirectories: string[]; } export interface SharedProcessData { diff --git a/packages/protocol/src/node/server.ts b/packages/protocol/src/node/server.ts index be4abbf8..250f2d30 100644 --- a/packages/protocol/src/node/server.ts +++ b/packages/protocol/src/node/server.ts @@ -15,6 +15,8 @@ export interface ServerOptions { readonly cacheDirectory: string; readonly builtInExtensionsDirectory: string; readonly extensionsDirectory: string; + readonly extraExtensionDirectories?: string[]; + readonly extraBuiltinExtensionDirectories?: string[]; readonly fork?: ForkProvider; } @@ -99,6 +101,8 @@ export class Server { initMsg.setTmpDirectory(os.tmpdir()); initMsg.setOperatingSystem(platformToProto(os.platform())); initMsg.setShell(os.userInfo().shell || global.process.env.SHELL || ""); + initMsg.setExtraExtensionDirectoriesList(this.options.extraExtensionDirectories || []); + initMsg.setExtraBuiltinExtensionDirectoriesList(this.options.extraBuiltinExtensionDirectories || []); const srvMsg = new ServerMessage(); srvMsg.setInit(initMsg); connection.send(srvMsg.serializeBinary()); diff --git a/packages/protocol/src/proto/client.proto b/packages/protocol/src/proto/client.proto index 4681ecf6..33c5e342 100644 --- a/packages/protocol/src/proto/client.proto +++ b/packages/protocol/src/proto/client.proto @@ -42,4 +42,6 @@ message WorkingInit { string shell = 6; string builtin_extensions_dir = 7; string extensions_directory = 8; + repeated string extra_extension_directories = 9; + repeated string extra_builtin_extension_directories = 10; } diff --git a/packages/protocol/src/proto/client_pb.d.ts b/packages/protocol/src/proto/client_pb.d.ts index 9c4c1dd5..931f71c1 100644 --- a/packages/protocol/src/proto/client_pb.d.ts +++ b/packages/protocol/src/proto/client_pb.d.ts @@ -135,6 +135,16 @@ export class WorkingInit extends jspb.Message { getExtensionsDirectory(): string; setExtensionsDirectory(value: string): void; + clearExtraExtensionDirectoriesList(): void; + getExtraExtensionDirectoriesList(): Array; + setExtraExtensionDirectoriesList(value: Array): void; + addExtraExtensionDirectories(value: string, index?: number): string; + + clearExtraBuiltinExtensionDirectoriesList(): void; + getExtraBuiltinExtensionDirectoriesList(): Array; + setExtraBuiltinExtensionDirectoriesList(value: Array): void; + addExtraBuiltinExtensionDirectories(value: string, index?: number): string; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): WorkingInit.AsObject; static toObject(includeInstance: boolean, msg: WorkingInit): WorkingInit.AsObject; @@ -155,6 +165,8 @@ export namespace WorkingInit { shell: string, builtinExtensionsDir: string, extensionsDirectory: string, + extraExtensionDirectoriesList: Array, + extraBuiltinExtensionDirectoriesList: Array, } export enum OperatingSystem { diff --git a/packages/protocol/src/proto/client_pb.js b/packages/protocol/src/proto/client_pb.js index 20bf41ea..7dd7c8c2 100644 --- a/packages/protocol/src/proto/client_pb.js +++ b/packages/protocol/src/proto/client_pb.js @@ -72,7 +72,7 @@ if (goog.DEBUG && !COMPILED) { * @constructor */ proto.WorkingInit = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); + jspb.Message.initialize(this, opt_data, 0, -1, proto.WorkingInit.repeatedFields_, null); }; goog.inherits(proto.WorkingInit, jspb.Message); if (goog.DEBUG && !COMPILED) { @@ -759,6 +759,13 @@ proto.ServerMessage.prototype.hasSharedProcessActive = function() { +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.WorkingInit.repeatedFields_ = [9,10]; + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -795,7 +802,9 @@ proto.WorkingInit.toObject = function(includeInstance, msg) { operatingSystem: jspb.Message.getFieldWithDefault(msg, 5, 0), shell: jspb.Message.getFieldWithDefault(msg, 6, ""), builtinExtensionsDir: jspb.Message.getFieldWithDefault(msg, 7, ""), - extensionsDirectory: jspb.Message.getFieldWithDefault(msg, 8, "") + extensionsDirectory: jspb.Message.getFieldWithDefault(msg, 8, ""), + extraExtensionDirectoriesList: jspb.Message.getRepeatedField(msg, 9), + extraBuiltinExtensionDirectoriesList: jspb.Message.getRepeatedField(msg, 10) }; if (includeInstance) { @@ -864,6 +873,14 @@ proto.WorkingInit.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setExtensionsDirectory(value); break; + case 9: + var value = /** @type {string} */ (reader.readString()); + msg.addExtraExtensionDirectories(value); + break; + case 10: + var value = /** @type {string} */ (reader.readString()); + msg.addExtraBuiltinExtensionDirectories(value); + break; default: reader.skipField(); break; @@ -949,6 +966,20 @@ proto.WorkingInit.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getExtraExtensionDirectoriesList(); + if (f.length > 0) { + writer.writeRepeatedString( + 9, + f + ); + } + f = message.getExtraBuiltinExtensionDirectoriesList(); + if (f.length > 0) { + writer.writeRepeatedString( + 10, + f + ); + } }; @@ -1081,4 +1112,68 @@ proto.WorkingInit.prototype.setExtensionsDirectory = function(value) { }; +/** + * repeated string extra_extension_directories = 9; + * @return {!Array} + */ +proto.WorkingInit.prototype.getExtraExtensionDirectoriesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 9)); +}; + + +/** @param {!Array} value */ +proto.WorkingInit.prototype.setExtraExtensionDirectoriesList = function(value) { + jspb.Message.setField(this, 9, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + */ +proto.WorkingInit.prototype.addExtraExtensionDirectories = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 9, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + */ +proto.WorkingInit.prototype.clearExtraExtensionDirectoriesList = function() { + this.setExtraExtensionDirectoriesList([]); +}; + + +/** + * repeated string extra_builtin_extension_directories = 10; + * @return {!Array} + */ +proto.WorkingInit.prototype.getExtraBuiltinExtensionDirectoriesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 10)); +}; + + +/** @param {!Array} value */ +proto.WorkingInit.prototype.setExtraBuiltinExtensionDirectoriesList = function(value) { + jspb.Message.setField(this, 10, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + */ +proto.WorkingInit.prototype.addExtraBuiltinExtensionDirectories = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 10, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + */ +proto.WorkingInit.prototype.clearExtraBuiltinExtensionDirectoriesList = function() { + this.setExtraBuiltinExtensionDirectoriesList([]); +}; + + goog.object.extend(exports, proto); diff --git a/packages/protocol/test/server.test.ts b/packages/protocol/test/server.test.ts index 4749e094..0675d60f 100644 --- a/packages/protocol/test/server.test.ts +++ b/packages/protocol/test/server.test.ts @@ -3,9 +3,11 @@ import { createClient } from "./helpers"; describe("Server", () => { const dataDirectory = "/tmp/example"; const workingDirectory = "/working/dir"; + const extensionsDirectory = "/tmp/example"; const builtInExtensionsDirectory = "/tmp/example"; const cacheDirectory = "/tmp/cache"; const client = createClient({ + extensionsDirectory, builtInExtensionsDirectory, cacheDirectory, dataDirectory, diff --git a/packages/server/src/cli.ts b/packages/server/src/cli.ts index 41ca803c..4cc8291a 100644 --- a/packages/server/src/cli.ts +++ b/packages/server/src/cli.ts @@ -16,13 +16,20 @@ import opn = require("opn"); import * as commander from "commander"; +const collect = (value: T, previous: T[]): T[] => { + return previous.concat(value); +}; + commander.version(process.env.VERSION || "development") .name("code-server") .description("Run VS Code on a remote server.") .option("--cert ") .option("--cert-key ") - .option("-e, --extensions-dir ", "Set the root path for extensions.") - .option("-d --user-data-dir ", " Specifies the directory that user data is kept in, useful when running as root.") + .option("-e, --extensions-dir ", "Override the main default path for user extensions.") + .option("--builtin-extensions-dir ", "Override the main default path for built-in extensions.") + .option("--extra-extensions-dir [dir]", "Path to an extra user extension directory (repeatable).", collect, []) + .option("--extra-builtin-extensions-dir [dir]", "Path to an extra built-in extension directory (repeatable).", collect, []) + .option("-d --user-data-dir ", "Specifies the directory that user data is kept in, useful when running as root.") .option("--data-dir ", "DEPRECATED: Use '--user-data-dir' instead. Customize where user-data is stored.") .option("-h, --host ", "Customize the hostname.", "0.0.0.0") .option("-o, --open", "Open in the browser on startup.", false) @@ -59,6 +66,9 @@ const bold = (text: string | number): string | number => { readonly userDataDir?: string; readonly extensionsDir?: string; + readonly builtinExtensionsDir?: string; + readonly extraExtensionsDir?: string[]; + readonly extraBuiltinExtensionsDir?: string[]; readonly dataDir?: string; readonly password?: string; @@ -84,6 +94,10 @@ const bold = (text: string | number): string | number => { const dataDir = path.resolve(options.userDataDir || options.dataDir || path.join(dataHome, "code-server")); const extensionsDir = options.extensionsDir ? path.resolve(options.extensionsDir) : path.resolve(dataDir, "extensions"); + const builtInExtensionsDir = options.builtinExtensionsDir ? path.resolve(options.builtinExtensionsDir) + : path.resolve(buildDir || path.join(__dirname, ".."), "build/extensions"); + const extraExtensionDirs = options.extraExtensionsDir ? options.extraExtensionsDir.map((p) => path.resolve(p)) : []; + const extraBuiltinExtensionDirs = options.extraBuiltinExtensionsDir ? options.extraBuiltinExtensionsDir.map((p) => path.resolve(p)) : []; const workingDir = path.resolve(args[0] || process.cwd()); const dependenciesDir = path.join(os.tmpdir(), "code-server/dependencies"); @@ -99,8 +113,11 @@ const bold = (text: string | number): string | number => { fse.mkdirp(cacheHome), fse.mkdirp(dataDir), fse.mkdirp(extensionsDir), + fse.mkdirp(builtInExtensionsDir), fse.mkdirp(workingDir), fse.mkdirp(dependenciesDir), + ...extraExtensionDirs.map((p) => fse.mkdirp(p)), + ...extraBuiltinExtensionDirs.map((p) => fse.mkdirp(p)), ]); const unpackExecutable = (binaryName: string): void => { @@ -116,7 +133,6 @@ const bold = (text: string | number): string | number => { // tslint:disable-next-line no-any (global).RIPGREP_LOCATION = path.join(dependenciesDir, "rg"); - const builtInExtensionsDir = path.resolve(buildDir || path.join(__dirname, ".."), "build/extensions"); if (options.bootstrapFork) { const modulePath = options.bootstrapFork; if (!modulePath) { @@ -192,7 +208,7 @@ const bold = (text: string | number): string | number => { // TODO: fill in appropriate doc url logger.info("Additional documentation: http://github.com/cdr/code-server"); logger.info("Initializing", field("data-dir", dataDir), field("extensions-dir", extensionsDir), field("working-dir", workingDir), field("log-dir", logDir)); - const sharedProcess = new SharedProcess(dataDir, extensionsDir, builtInExtensionsDir); + const sharedProcess = new SharedProcess(dataDir, extensionsDir, builtInExtensionsDir, extraExtensionDirs, extraBuiltinExtensionDirs); const sendSharedProcessReady = (socket: WebSocket): void => { const active = new SharedProcessActive(); active.setSocketPath(sharedProcess.socketPath); @@ -247,6 +263,8 @@ const bold = (text: string | number): string | number => { serverOptions: { extensionsDirectory: extensionsDir, builtInExtensionsDirectory: builtInExtensionsDir, + extraExtensionDirectories: extraExtensionDirs, + extraBuiltinExtensionDirectories: extraBuiltinExtensionDirs, dataDirectory: dataDir, workingDirectory: workingDir, cacheDirectory: cacheHome, diff --git a/packages/server/src/vscode/sharedProcess.ts b/packages/server/src/vscode/sharedProcess.ts index 1348f1d8..6834c2e1 100644 --- a/packages/server/src/vscode/sharedProcess.ts +++ b/packages/server/src/vscode/sharedProcess.ts @@ -39,6 +39,8 @@ export class SharedProcess { private readonly userDataDir: string, private readonly extensionsDir: string, private readonly builtInExtensionsDir: string, + private readonly extraExtensionDirs: string[], + private readonly extraBuiltinExtensionDirs: string[], ) { this.retry.run(); } @@ -134,6 +136,8 @@ export class SharedProcess { "builtin-extensions-dir": this.builtInExtensionsDir, "user-data-dir": this.userDataDir, "extensions-dir": this.extensionsDir, + "extra-extension-dirs": this.extraExtensionDirs, + "extra-builtin-extension-dirs": this.extraBuiltinExtensionDirs, }, logLevel: this.logger.level, sharedIPCHandle: this.socketPath, diff --git a/packages/vscode/src/fill/environmentService.ts b/packages/vscode/src/fill/environmentService.ts index 67cf95d2..0539d1e3 100644 --- a/packages/vscode/src/fill/environmentService.ts +++ b/packages/vscode/src/fill/environmentService.ts @@ -12,6 +12,18 @@ export class EnvironmentService extends environment.EnvironmentService { public get extensionsPath(): string { return paths.getExtensionsDirectory(); } + + public get builtinExtensionsPath(): string { + return paths.getBuiltInExtensionsDirectory(); + } + + public get extraExtensionPaths(): string[] { + return paths.getExtraExtensionDirectories(); + } + + public get extraBuiltinExtensionPaths(): string[] { + return paths.getExtraBuiltinExtensionDirectories(); + } } const target = environment as typeof environment; diff --git a/packages/vscode/src/fill/paths.ts b/packages/vscode/src/fill/paths.ts index ed284fb3..88e19dc6 100644 --- a/packages/vscode/src/fill/paths.ts +++ b/packages/vscode/src/fill/paths.ts @@ -1,5 +1,8 @@ import { InitData, SharedProcessData } from "@coder/protocol"; +/** + * Provides paths. + */ class Paths { private _appData: string | undefined; private _defaultUserData: string | undefined; @@ -7,6 +10,8 @@ class Paths { private _extensionsDirectory: string | undefined; private _builtInExtensionsDirectory: string | undefined; private _workingDirectory: string | undefined; + private _extraExtensionDirectories: string[] | undefined; + private _extraBuiltinExtensionDirectories: string[] | undefined; public get appData(): string { if (typeof this._appData === "undefined") { @@ -48,6 +53,22 @@ class Paths { return this._builtInExtensionsDirectory; } + public get extraExtensionDirectories(): string[] { + if (!this._extraExtensionDirectories) { + throw new Error("trying to access extra extension directories before they have been set"); + } + + return this._extraExtensionDirectories; + } + + public get extraBuiltinExtensionDirectories(): string[] { + if (!this._extraBuiltinExtensionDirectories) { + throw new Error("trying to access extra builtin extension directories before they have been set"); + } + + return this._extraBuiltinExtensionDirectories; + } + public get workingDirectory(): string { if (!this._workingDirectory) { throw new Error("trying to access working directory before it has been set"); @@ -56,6 +77,9 @@ class Paths { return this._workingDirectory; } + /** + * Initialize paths using the provided data. + */ public initialize(data: InitData, sharedData: SharedProcessData): void { process.env.VSCODE_LOGS = sharedData.logPath; this._appData = data.dataDirectory; @@ -64,6 +88,8 @@ class Paths { this._extensionsDirectory = data.extensionsDirectory; this._builtInExtensionsDirectory = data.builtInExtensionsDirectory; this._workingDirectory = data.workingDirectory; + this._extraExtensionDirectories = data.extraExtensionDirectories; + this._extraBuiltinExtensionDirectories = data.extraBuiltinExtensionDirectories; } } @@ -73,4 +99,6 @@ export const getDefaultUserDataPath = (): string => _paths.defaultUserData; export const getWorkingDirectory = (): string => _paths.workingDirectory; export const getExtensionsDirectory = (): string => _paths.extensionsDirectory; export const getBuiltInExtensionsDirectory = (): string => _paths.builtInExtensionsDirectory; +export const getExtraExtensionDirectories = (): string[] => _paths.extraExtensionDirectories; +export const getExtraBuiltinExtensionDirectories = (): string[] => _paths.extraBuiltinExtensionDirectories; export const getSocketPath = (): string => _paths.socketPath; diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 8e89e69e..895830f3 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -129,9 +129,12 @@ index f91ca2b..ef6fce9 100644 - const isMac = platform.isMacintosh; + const isMac = browser.isMacintosh; diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts -index f08c996..f9de58c 100644 +index f08c996..7db13fa 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts +@@ -296 +296 @@ export class IssueReporter extends Disposable { +- const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; ++ const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath, ...this.environmentService.extraExtensionPaths]; @@ -425 +425 @@ export class IssueReporter extends Disposable { - const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + const cmdOrCtrlKey = browser.isMacintosh ? e.metaKey : e.ctrlKey; @@ -146,25 +149,34 @@ index e0ff793..885de12 100644 - const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey; + const cmdOrCtrlKey = browser.isMacintosh ? e.metaKey : e.ctrlKey; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts -index 6fd8249..04c0933 100644 +index 6fd8249..1101558 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,0 +51,2 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio +import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; +import { mkdirp } from 'vs/base/node/pfs'; -@@ -93,0 +96,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I +@@ -93,0 +96,11 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I + Promise.all([ // Copied from src/vs/code/electron-main/main.ts + environmentService.extensionsPath, + environmentService.nodeCachedDataDir, + environmentService.logsPath, + environmentService.globalStorageHome, + environmentService.workspaceStorageHome, -+ environmentService.backupHome ++ environmentService.backupHome, ++ environmentService.builtinExtensionsPath, ++ ...environmentService.extraExtensionPaths, ++ ...environmentService.extraBuiltinExtensionPaths, + ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); -@@ -119,0 +130,2 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I +@@ -119,0 +133,2 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I + const backupMainService = instantiationService.createInstance(BackupMainService) as BackupMainService; + backupMainService.initialize().catch(console.error); -@@ -223,0 +236 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise { +- this.logService.info('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.info('Scanned system extensions:', result.length); ++ return result; ++ }); +@@ -761 +765 @@ export class ExtensionManagementService extends Disposable implements IExtension +- return Promise.all([this.getUninstalledExtensions(), this.scanExtensions(this.extensionsPath, ExtensionType.User)]) ++ return Promise.all([this.getUninstalledExtensions(), this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User)]) +@@ -772,0 +777,7 @@ export class ExtensionManagementService extends Disposable implements IExtension ++ 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), [])); ++ } ++ +@@ -805 +816 @@ export class ExtensionManagementService extends Disposable implements IExtension +- .then(uninstalled => this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions ++ .then(uninstalled => this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions +@@ -814 +825 @@ export class ExtensionManagementService extends Disposable implements IExtension +- return this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions ++ return this.scanAllUserExtensions(this.extensionsPath, ExtensionType.User) // All user extensions +diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts +index 9845da1..567c195 100644 +--- a/src/vs/platform/storage/node/storageMainService.ts ++++ b/src/vs/platform/storage/node/storageMainService.ts +@@ -169 +169,6 @@ export class StorageMainService extends Disposable implements IStorageMainServic +- return readdir(this.environmentService.extensionsPath).then(extensions => { ++ return Promise.all([ ++ this.environmentService.extensionsPath, ++ ...this.environmentService.extraExtensionPaths, ++ ].map((p) => readdir(p))) ++ .then((results) => results.reduce((flat, current) => flat.concat(current), [])) ++ .then(extensions => { +diff --git a/src/vs/platform/telemetry/electron-browser/telemetryService.ts b/src/vs/platform/telemetry/electron-browser/telemetryService.ts +index 31d0309..5b166af 100644 +--- a/src/vs/platform/telemetry/electron-browser/telemetryService.ts ++++ b/src/vs/platform/telemetry/electron-browser/telemetryService.ts +@@ -42 +42 @@ export class TelemetryService extends Disposable implements ITelemetryService { +- piiPaths: [environmentService.appRoot, environmentService.extensionsPath] ++ piiPaths: [environmentService.appRoot, environmentService.extensionsPath, ...environmentService.extraExtensionPaths] diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index cbc55b3..9d27c01 100644 --- a/src/vs/platform/windows/common/windows.ts @@ -999,11 +1086,23 @@ index 75f0026..2e94683 100644 - if (!isMacintosh && getTitleBarStyle(configurationService, environmentService) === 'custom') { + if (!(isNative && isMacintosh) && getTitleBarStyle(configurationService, environmentService) === 'custom') { diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts -index 059f821..2dde675 100644 +index 059f821..b19f292 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -32,0 +33 @@ function getSystemExtensionsRoot(): string { + return (require('vs/../../../../packages/vscode/src/fill/paths') as typeof import ('vs/../../../../packages/vscode/src/fill/paths')).getBuiltInExtensionsDirectory(); +@@ -191,2 +192,3 @@ export class CachedExtensionScanner { +- const folderStat = await pfs.stat(input.absoluteFolderPath); +- input.mtime = folderStat.mtime.getTime(); ++ const folderStats = await Promise.all([pfs.stat(input.absoluteFolderPath), ...input.extraFolderPaths.map((p) => pfs.stat(p))]); ++ input.mtime = folderStats[0].mtime.getTime(); ++ input.extraMtimes = folderStats.slice(1).map((s) => s.mtime.getTime()); +@@ -259 +261 @@ export class CachedExtensionScanner { +- new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations), ++ new ExtensionScannerInput(version, commit, locale, devMode, getSystemExtensionsRoot(), true, false, translations, environmentService.extraBuiltinExtensionPaths), +@@ -290 +292 @@ export class CachedExtensionScanner { +- new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations), ++ new ExtensionScannerInput(version, commit, locale, devMode, environmentService.extensionsPath, false, false, translations, environmentService.extraExtensionPaths), diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index b337206..0477464 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -1021,6 +1120,54 @@ index 838a9c7..2308cee 100644 @@ -192 +192 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { ++ if (a.length !== b.length || atimes.length !== btimes.length) { ++ return false; ++ } ++ for (let i = 0; i < a.length; ++i) { ++ const index = b.indexOf(a[i]); ++ if (index === -1) { ++ return false; ++ } ++ if (atimes[i] !== btimes[index]) { ++ return false; ++ } ++ } ++ return true; ++ }; +@@ -479,0 +498 @@ export class ExtensionScannerInput { ++ && eq(a.extraFolderPaths, b.extraFolderPaths, a.extraMtimes, b.extraMtimes) +@@ -530 +549 @@ export class ExtensionScanner { +- * Scan a list of extensions defined in `absoluteFolderPath` ++ * Scan a list of extensions defined in `absoluteFolderPath` and `extraFolderPaths` +@@ -532 +551 @@ export class ExtensionScanner { +- public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver: IExtensionResolver | null = null): Promise { ++ public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolvers: IExtensionResolver | IExtensionResolver[] | null = null): Promise { +@@ -533,0 +553 @@ export class ExtensionScanner { ++ const extraFolderPaths = input.extraFolderPaths; +@@ -537,2 +557,4 @@ export class ExtensionScanner { +- if (!resolver) { +- resolver = new DefaultExtensionResolver(absoluteFolderPath); ++ if (!resolvers) { ++ resolvers = [absoluteFolderPath, ...extraFolderPaths].map((p) => new DefaultExtensionResolver(p)); ++ } else if (!Array.isArray(resolvers)) { ++ resolvers = [resolvers]; +@@ -552 +574,2 @@ export class ExtensionScanner { +- let refs = await resolver.resolveExtensions(); ++ let refs = await Promise.all(resolvers.map((resolver) => resolver.resolveExtensions())) ++ .then((results) => results.reduce((flat, current) => flat.concat(current), [])); diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index 33d3697..af71b01 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts