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
This commit is contained in:
Asher 2019-05-19 17:58:47 -05:00 committed by GitHub
parent 8256252967
commit aa1474b675
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 341 additions and 13 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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());

View File

@ -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;
}

View File

@ -135,6 +135,16 @@ export class WorkingInit extends jspb.Message {
getExtensionsDirectory(): string;
setExtensionsDirectory(value: string): void;
clearExtraExtensionDirectoriesList(): void;
getExtraExtensionDirectoriesList(): Array<string>;
setExtraExtensionDirectoriesList(value: Array<string>): void;
addExtraExtensionDirectories(value: string, index?: number): string;
clearExtraBuiltinExtensionDirectoriesList(): void;
getExtraBuiltinExtensionDirectoriesList(): Array<string>;
setExtraBuiltinExtensionDirectoriesList(value: Array<string>): 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<string>,
extraBuiltinExtensionDirectoriesList: Array<string>,
}
export enum OperatingSystem {

View File

@ -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<number>}
* @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<string>}
*/
proto.WorkingInit.prototype.getExtraExtensionDirectoriesList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 9));
};
/** @param {!Array<string>} 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<string>}
*/
proto.WorkingInit.prototype.getExtraBuiltinExtensionDirectoriesList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 10));
};
/** @param {!Array<string>} 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);

View File

@ -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,

View File

@ -16,13 +16,20 @@ import opn = require("opn");
import * as commander from "commander";
const collect = <T>(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 <value>")
.option("--cert-key <value>")
.option("-e, --extensions-dir <dir>", "Set the root path for extensions.")
.option("-d --user-data-dir <dir>", " Specifies the directory that user data is kept in, useful when running as root.")
.option("-e, --extensions-dir <dir>", "Override the main default path for user extensions.")
.option("--builtin-extensions-dir <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 <dir>", "Specifies the directory that user data is kept in, useful when running as root.")
.option("--data-dir <value>", "DEPRECATED: Use '--user-data-dir' instead. Customize where user-data is stored.")
.option("-h, --host <value>", "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
(<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,

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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<boolean | undefined>([ // 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<boolean> => 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<vo
@@ -124 +139 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
- const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
+ const { appRoot, extensionsPath, extraExtensionPaths, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
@@ -138 +153 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
- piiPaths: [appRoot, extensionsPath]
+ piiPaths: [appRoot, extensionsPath, ...extraExtensionPaths]
@@ -223,0 +239 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise<vo
+startup({ machineId: "1" });
diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts
index 1f8b17a..2a875f9 100644
@ -383,6 +395,81 @@ index 9952574..908a9ae 100644
@@ -9 +9 @@ import { URI } from 'vs/base/common/uri';
-import { isMacintosh } from 'vs/base/common/platform';
+import { isMacintosh } from 'vs/base/browser/browser';
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
index eb1873c..dbbacd0 100644
--- a/src/vs/platform/environment/common/environment.ts
+++ b/src/vs/platform/environment/common/environment.ts
@@ -120,0 +121,2 @@ export interface IEnvironmentService {
+ extraExtensionPaths: string[];
+ extraBuiltinExtensionPaths: string[];
diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts
index 43866f8..e69b513 100644
--- a/src/vs/platform/environment/node/environmentService.ts
+++ b/src/vs/platform/environment/node/environmentService.ts
@@ -172,0 +173,8 @@ export class EnvironmentService implements IEnvironmentService {
+ @memoize
+ get extraExtensionPaths(): string[] {
+ return this._args['extra-extension-dirs'] || [];
+ }
+ @memoize
+ get extraBuiltinExtensionPaths(): string[] {
+ return this._args['extra-builtin-extension-dirs'] || [];
+ }
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index c897029..f84d9b6 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -733,5 +733,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
- const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System)
- .then(result => {
- 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<ILocalExtension[]> {
+ 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<IRenderer
- process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
+ // process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts
index 6e2179d..e6f38c9 100644
--- a/src/vs/workbench/services/extensions/node/extensionPoints.ts
+++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts
@@ -445,0 +446 @@ export class ExtensionScannerInput {
+ public extraMtimes: number[] = [];
@@ -455 +456,2 @@ export class ExtensionScannerInput {
- public readonly tanslations: Translations
+ public readonly tanslations: Translations,
+ public readonly extraFolderPaths: string[] = [],
@@ -469,0 +472,16 @@ export class ExtensionScannerInput {
+ // Allow extra folder paths in any order. Doesn't account for duplicates though.
+ const eq = (a: string[] = [], b: string[] = [], atimes: number[] = [], btimes: number[] = []): boolean => {
+ 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<IExtensionDescription[]> {
+ public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolvers: IExtensionResolver | IExtensionResolver[] | null = null): Promise<IExtensionDescription[]> {
@@ -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