mirror of
https://github.com/coder/code-server.git
synced 2026-05-05 20:15:19 +02:00
Update to VS Code 1.52.1
This commit is contained in:
@@ -16,10 +16,12 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
|
||||
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
|
||||
const groups = menu.getActions(options);
|
||||
const useAlternativeActions = ModifierKeyEmitter.getInstance().keyStatus.altKey;
|
||||
const modifierKeyEmitter = ModifierKeyEmitter.getInstance();
|
||||
const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey);
|
||||
fillInActions(groups, target, useAlternativeActions, isPrimaryGroup);
|
||||
return asDisposable(groups);
|
||||
}
|
||||
@@ -102,7 +104,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
|
||||
|
||||
let mouseOver = false;
|
||||
|
||||
let alternativeKeyDown = this._altKey.keyStatus.altKey;
|
||||
let alternativeKeyDown = this._altKey.keyStatus.altKey || ((isWindows || isLinux) && this._altKey.keyStatus.shiftKey);
|
||||
|
||||
const updateAltState = () => {
|
||||
const wantsAltCommand = mouseOver && alternativeKeyDown;
|
||||
@@ -116,7 +118,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
|
||||
|
||||
if (this._action.alt) {
|
||||
this._register(this._altKey.event(value => {
|
||||
alternativeKeyDown = value.altKey;
|
||||
alternativeKeyDown = value.altKey || ((isWindows || isLinux) && value.shiftKey);
|
||||
updateAltState();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export class MenuId {
|
||||
static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu');
|
||||
static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu');
|
||||
static readonly MenubarViewMenu = new MenuId('MenubarViewMenu');
|
||||
static readonly MenubarWebNavigationMenu = new MenuId('MenubarWebNavigationMenu');
|
||||
static readonly MenubarHomeMenu = new MenuId('MenubarHomeMenu');
|
||||
static readonly OpenEditorsContext = new MenuId('OpenEditorsContext');
|
||||
static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext');
|
||||
static readonly SCMChangeContext = new MenuId('SCMChangeContext');
|
||||
|
||||
@@ -57,9 +57,6 @@ export class BackupMainService implements IBackupMainService {
|
||||
// read empty workspaces backups first
|
||||
if (backups.emptyWorkspaceInfos) {
|
||||
this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos);
|
||||
} else if (Array.isArray(backups.emptyWorkspaces)) {
|
||||
// read legacy entries
|
||||
this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaces.map(emptyWindow => ({ backupFolder: emptyWindow })));
|
||||
}
|
||||
|
||||
// read workspace backups
|
||||
@@ -67,8 +64,6 @@ export class BackupMainService implements IBackupMainService {
|
||||
try {
|
||||
if (Array.isArray(backups.rootURIWorkspaces)) {
|
||||
rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, remoteAuthority: workspace.remoteAuthority }));
|
||||
} else if (Array.isArray(backups.rootWorkspaces)) {
|
||||
rootWorkspaces = backups.rootWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } }));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore URI parsing exceptions
|
||||
@@ -81,18 +76,6 @@ export class BackupMainService implements IBackupMainService {
|
||||
try {
|
||||
if (Array.isArray(backups.folderURIWorkspaces)) {
|
||||
workspaceFolders = backups.folderURIWorkspaces.map(folder => URI.parse(folder));
|
||||
} else if (Array.isArray(backups.folderWorkspaces)) {
|
||||
// migrate legacy folder paths
|
||||
workspaceFolders = [];
|
||||
for (const folderPath of backups.folderWorkspaces) {
|
||||
const oldFolderHash = this.getLegacyFolderHash(folderPath);
|
||||
const folderUri = URI.file(folderPath);
|
||||
const newFolderHash = this.getFolderHash(folderUri);
|
||||
if (newFolderHash !== oldFolderHash) {
|
||||
await this.moveBackupFolder(this.getBackupPath(newFolderHash), this.getBackupPath(oldFolderHash));
|
||||
}
|
||||
workspaceFolders.push(folderUri);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore URI parsing exceptions
|
||||
@@ -174,23 +157,6 @@ export class BackupMainService implements IBackupMainService {
|
||||
}
|
||||
}
|
||||
|
||||
private async moveBackupFolder(backupPath: string, moveFromPath: string): Promise<void> {
|
||||
|
||||
// Target exists: make sure to convert existing backups to empty window backups
|
||||
if (await exists(backupPath)) {
|
||||
await this.convertToEmptyWindowBackup(backupPath);
|
||||
}
|
||||
|
||||
// When we have data to migrate from, move it over to the target location
|
||||
if (await exists(moveFromPath)) {
|
||||
try {
|
||||
await rename(moveFromPath, backupPath);
|
||||
} catch (ex) {
|
||||
this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void {
|
||||
const id = workspace.id;
|
||||
const index = this.workspaces.findIndex(workspace => workspace.workspace.id === id);
|
||||
@@ -475,8 +441,7 @@ export class BackupMainService implements IBackupMainService {
|
||||
return {
|
||||
rootURIWorkspaces: this.workspaces.map(workspace => ({ id: workspace.workspace.id, configURIPath: workspace.workspace.configPath.toString(), remoteAuthority: workspace.remoteAuthority })),
|
||||
folderURIWorkspaces: this.folders.map(folder => folder.toString()),
|
||||
emptyWorkspaceInfos: this.emptyWindows,
|
||||
emptyWorkspaces: this.emptyWindows.map(emptyWindow => emptyWindow.backupFolder)
|
||||
emptyWorkspaceInfos: this.emptyWindows
|
||||
};
|
||||
}
|
||||
|
||||
@@ -496,8 +461,4 @@ export class BackupMainService implements IBackupMainService {
|
||||
|
||||
return crypto.createHash('md5').update(key).digest('hex');
|
||||
}
|
||||
|
||||
protected getLegacyFolderHash(folderPath: string): string {
|
||||
return crypto.createHash('md5').update(platform.isLinux ? folderPath : folderPath.toLowerCase()).digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,6 @@ export interface IBackupWorkspacesFormat {
|
||||
rootURIWorkspaces: ISerializedWorkspace[];
|
||||
folderURIWorkspaces: string[];
|
||||
emptyWorkspaceInfos: IEmptyWindowBackupInfo[];
|
||||
|
||||
// deprecated
|
||||
folderWorkspaces?: string[]; // use folderURIWorkspaces instead
|
||||
emptyWorkspaces?: string[];
|
||||
rootWorkspaces?: { id: string, configPath: string }[]; // use rootURIWorkspaces instead
|
||||
}
|
||||
|
||||
export interface IEmptyWindowBackupInfo {
|
||||
|
||||
@@ -53,10 +53,6 @@ suite('BackupMainService', () => {
|
||||
getFolderHash(folderUri: URI): string {
|
||||
return super.getFolderHash(folderUri);
|
||||
}
|
||||
|
||||
toLegacyBackupPath(folderPath: string): string {
|
||||
return path.join(this.backupHome, super.getLegacyFolderHash(folderPath));
|
||||
}
|
||||
}
|
||||
|
||||
function toWorkspace(path: string): IWorkspaceIdentifier {
|
||||
@@ -266,68 +262,6 @@ suite('BackupMainService', () => {
|
||||
assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length);
|
||||
});
|
||||
|
||||
suite('migrate path to URI', () => {
|
||||
|
||||
test('migration folder path to URI makes sure to preserve existing backups', async () => {
|
||||
let path1 = path.join(parentDir, 'folder1');
|
||||
let path2 = path.join(parentDir, 'FOLDER2');
|
||||
let uri1 = URI.file(path1);
|
||||
let uri2 = URI.file(path2);
|
||||
|
||||
if (!fs.existsSync(path1)) {
|
||||
fs.mkdirSync(path1);
|
||||
}
|
||||
if (!fs.existsSync(path2)) {
|
||||
fs.mkdirSync(path2);
|
||||
}
|
||||
const backupFolder1 = service.toLegacyBackupPath(path1);
|
||||
if (!fs.existsSync(backupFolder1)) {
|
||||
fs.mkdirSync(backupFolder1);
|
||||
fs.mkdirSync(path.join(backupFolder1, Schemas.file));
|
||||
await pfs.writeFile(path.join(backupFolder1, Schemas.file, 'unsaved1.txt'), 'Legacy');
|
||||
}
|
||||
const backupFolder2 = service.toLegacyBackupPath(path2);
|
||||
if (!fs.existsSync(backupFolder2)) {
|
||||
fs.mkdirSync(backupFolder2);
|
||||
fs.mkdirSync(path.join(backupFolder2, Schemas.file));
|
||||
await pfs.writeFile(path.join(backupFolder2, Schemas.file, 'unsaved2.txt'), 'Legacy');
|
||||
}
|
||||
|
||||
const workspacesJson = { rootWorkspaces: [], folderWorkspaces: [path1, path2], emptyWorkspaces: [] };
|
||||
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
|
||||
await service.initialize();
|
||||
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepEqual(json.folderURIWorkspaces, [uri1.toString(), uri2.toString()]);
|
||||
const newBackupFolder1 = service.toBackupPath(uri1);
|
||||
assert.ok(fs.existsSync(path.join(newBackupFolder1, Schemas.file, 'unsaved1.txt')));
|
||||
const newBackupFolder2 = service.toBackupPath(uri2);
|
||||
assert.ok(fs.existsSync(path.join(newBackupFolder2, Schemas.file, 'unsaved2.txt')));
|
||||
});
|
||||
|
||||
test('migrate storage file', async () => {
|
||||
let folderPath = path.join(parentDir, 'f1');
|
||||
ensureFolderExists(URI.file(folderPath));
|
||||
const backupFolderPath = service.toLegacyBackupPath(folderPath);
|
||||
await createBackupFolder(backupFolderPath);
|
||||
|
||||
let workspacePath = path.join(parentDir, 'f2.code-workspace');
|
||||
const workspace = toWorkspace(workspacePath);
|
||||
await ensureWorkspaceExists(workspace);
|
||||
|
||||
const workspacesJson = { rootWorkspaces: [{ id: workspace.id, configPath: workspacePath }], folderWorkspaces: [folderPath], emptyWorkspaces: [] };
|
||||
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
|
||||
await service.initialize();
|
||||
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepEqual(json.folderURIWorkspaces, [URI.file(folderPath).toString()]);
|
||||
assert.deepEqual(json.rootURIWorkspaces, [{ id: workspace.id, configURIPath: URI.file(workspacePath).toString() }]);
|
||||
|
||||
assertEqualUris(service.getWorkspaceBackups().map(window => window.workspace.configPath), [workspace.configPath]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
suite('loadSync', () => {
|
||||
test('getFolderBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
|
||||
assertEqualUris(service.getFolderBackupPaths(), []);
|
||||
@@ -650,12 +584,12 @@ suite('BackupMainService', () => {
|
||||
|
||||
const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
|
||||
assert.deepEqual(json.emptyWorkspaces, ['bar']);
|
||||
assert.deepEqual(json.emptyWorkspaceInfos, [{ backupFolder: 'bar' }]);
|
||||
service.unregisterEmptyWindowBackupSync('bar');
|
||||
|
||||
const content = await pfs.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepEqual(json2.emptyWorkspaces, []);
|
||||
assert.deepEqual(json2.emptyWorkspaceInfos, []);
|
||||
});
|
||||
|
||||
test('should fail gracefully when removing a path that doesn\'t exist', async () => {
|
||||
|
||||
@@ -114,7 +114,7 @@ export interface IConfigurationService {
|
||||
|
||||
inspect<T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<T>;
|
||||
|
||||
reloadConfiguration(folder?: IWorkspaceFolder): Promise<void>;
|
||||
reloadConfiguration(target?: ConfigurationTarget | IWorkspaceFolder): Promise<void>;
|
||||
|
||||
keys(): {
|
||||
default: string[];
|
||||
|
||||
@@ -478,6 +478,9 @@ const configurationRegistry = new ConfigurationRegistry();
|
||||
Registry.add(Extensions.Configuration, configurationRegistry);
|
||||
|
||||
export function validateProperty(property: string): string | null {
|
||||
if (!property.trim()) {
|
||||
return nls.localize('config.property.empty', "Cannot register an empty property");
|
||||
}
|
||||
if (OVERRIDE_PROPERTY_PATTERN.test(property)) {
|
||||
return nls.localize('config.property.languageDefault', "Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.", property);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace';
|
||||
|
||||
suite('ConfigurationModel', () => {
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { distinct } from 'vs/base/common/objects';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -90,10 +92,9 @@ class NullContext extends Context {
|
||||
}
|
||||
|
||||
class ConfigAwareContextValuesContainer extends Context {
|
||||
|
||||
private static readonly _keyPrefix = 'config.';
|
||||
|
||||
private readonly _values = new Map<string, any>();
|
||||
private readonly _values = TernarySearchTree.forConfigKeys<any>();
|
||||
private readonly _listener: IDisposable;
|
||||
|
||||
constructor(
|
||||
@@ -106,18 +107,26 @@ class ConfigAwareContextValuesContainer extends Context {
|
||||
this._listener = this._configurationService.onDidChangeConfiguration(event => {
|
||||
if (event.source === ConfigurationTarget.DEFAULT) {
|
||||
// new setting, reset everything
|
||||
const allKeys = Array.from(this._values.keys());
|
||||
const allKeys = Array.from(Iterable.map(this._values, ([k]) => k));
|
||||
this._values.clear();
|
||||
emitter.fire(new ArrayContextKeyChangeEvent(allKeys));
|
||||
} else {
|
||||
const changedKeys: string[] = [];
|
||||
for (const configKey of event.affectedKeys) {
|
||||
const contextKey = `config.${configKey}`;
|
||||
|
||||
const cachedItems = this._values.findSuperstr(contextKey);
|
||||
if (cachedItems !== undefined) {
|
||||
changedKeys.push(...Iterable.map(cachedItems, ([key]) => key));
|
||||
this._values.deleteSuperstr(contextKey);
|
||||
}
|
||||
|
||||
if (this._values.has(contextKey)) {
|
||||
this._values.delete(contextKey);
|
||||
changedKeys.push(contextKey);
|
||||
this._values.delete(contextKey);
|
||||
}
|
||||
}
|
||||
|
||||
emitter.fire(new ArrayContextKeyChangeEvent(changedKeys));
|
||||
}
|
||||
});
|
||||
@@ -149,6 +158,8 @@ class ConfigAwareContextValuesContainer extends Context {
|
||||
default:
|
||||
if (Array.isArray(configValue)) {
|
||||
value = JSON.stringify(configValue);
|
||||
} else {
|
||||
value = configValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +416,9 @@ class ScopedContextKeyService extends AbstractContextKeyService {
|
||||
|
||||
if (domNode) {
|
||||
this._domNode = domNode;
|
||||
if (this._domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
|
||||
console.error('Element already has context attribute');
|
||||
}
|
||||
this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
|
||||
import { userAgent, isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
|
||||
|
||||
let _userAgent = userAgent || '';
|
||||
const STATIC_VALUES = new Map<string, boolean>();
|
||||
STATIC_VALUES.set('false', false);
|
||||
STATIC_VALUES.set('true', true);
|
||||
@@ -16,6 +17,11 @@ STATIC_VALUES.set('isLinux', isLinux);
|
||||
STATIC_VALUES.set('isWindows', isWindows);
|
||||
STATIC_VALUES.set('isWeb', isWeb);
|
||||
STATIC_VALUES.set('isMacNative', isMacintosh && !isWeb);
|
||||
STATIC_VALUES.set('isEdge', _userAgent.indexOf('Edg/') >= 0);
|
||||
STATIC_VALUES.set('isFirefox', _userAgent.indexOf('Firefox') >= 0);
|
||||
STATIC_VALUES.set('isChrome', _userAgent.indexOf('Chrome') >= 0);
|
||||
STATIC_VALUES.set('isSafari', _userAgent.indexOf('Safari') >= 0);
|
||||
STATIC_VALUES.set('isIPad', _userAgent.indexOf('iPad') >= 0);
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
@@ -32,6 +38,10 @@ export const enum ContextKeyExprType {
|
||||
Or = 9,
|
||||
In = 10,
|
||||
NotIn = 11,
|
||||
Greater = 12,
|
||||
GreaterEquals = 13,
|
||||
Smaller = 14,
|
||||
SmallerEquals = 15,
|
||||
}
|
||||
|
||||
export interface IContextKeyExprMapper {
|
||||
@@ -39,6 +49,10 @@ export interface IContextKeyExprMapper {
|
||||
mapNot(key: string): ContextKeyExpression;
|
||||
mapEquals(key: string, value: any): ContextKeyExpression;
|
||||
mapNotEquals(key: string, value: any): ContextKeyExpression;
|
||||
mapGreater(key: string, value: any): ContextKeyExpression;
|
||||
mapGreaterEquals(key: string, value: any): ContextKeyExpression;
|
||||
mapSmaller(key: string, value: any): ContextKeyExpression;
|
||||
mapSmallerEquals(key: string, value: any): ContextKeyExpression;
|
||||
mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr;
|
||||
mapIn(key: string, valueKey: string): ContextKeyInExpr;
|
||||
}
|
||||
@@ -57,7 +71,9 @@ export interface IContextKeyExpression {
|
||||
export type ContextKeyExpression = (
|
||||
ContextKeyFalseExpr | ContextKeyTrueExpr | ContextKeyDefinedExpr | ContextKeyNotExpr
|
||||
| ContextKeyEqualsExpr | ContextKeyNotEqualsExpr | ContextKeyRegexExpr
|
||||
| ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr | ContextKeyNotInExpr
|
||||
| ContextKeyNotRegexExpr | ContextKeyAndExpr | ContextKeyOrExpr | ContextKeyInExpr
|
||||
| ContextKeyNotInExpr | ContextKeyGreaterExpr | ContextKeyGreaterEqualsExpr
|
||||
| ContextKeySmallerExpr | ContextKeySmallerEqualsExpr
|
||||
);
|
||||
|
||||
export abstract class ContextKeyExpr {
|
||||
@@ -102,6 +118,14 @@ export abstract class ContextKeyExpr {
|
||||
return ContextKeyOrExpr.create(expr);
|
||||
}
|
||||
|
||||
public static greater(key: string, value: any): ContextKeyExpression {
|
||||
return ContextKeyGreaterExpr.create(key, value);
|
||||
}
|
||||
|
||||
public static less(key: string, value: any): ContextKeyExpression {
|
||||
return ContextKeySmallerExpr.create(key, value);
|
||||
}
|
||||
|
||||
public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpression | undefined {
|
||||
if (!serialized) {
|
||||
return undefined;
|
||||
@@ -143,6 +167,26 @@ export abstract class ContextKeyExpr {
|
||||
return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim());
|
||||
}
|
||||
|
||||
if (/^[^<=>]+>=[^<=>]+$/.test(serializedOne)) {
|
||||
const pieces = serializedOne.split('>=');
|
||||
return ContextKeyGreaterEqualsExpr.create(pieces[0].trim(), pieces[1].trim());
|
||||
}
|
||||
|
||||
if (/^[^<=>]+>[^<=>]+$/.test(serializedOne)) {
|
||||
const pieces = serializedOne.split('>');
|
||||
return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim());
|
||||
}
|
||||
|
||||
if (/^[^<=>]+<=[^<=>]+$/.test(serializedOne)) {
|
||||
const pieces = serializedOne.split('<=');
|
||||
return ContextKeySmallerEqualsExpr.create(pieces[0].trim(), pieces[1].trim());
|
||||
}
|
||||
|
||||
if (/^[^<=>]+<[^<=>]+$/.test(serializedOne)) {
|
||||
const pieces = serializedOne.split('<');
|
||||
return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim());
|
||||
}
|
||||
|
||||
if (/^\!\s*/.test(serializedOne)) {
|
||||
return ContextKeyNotExpr.create(serializedOne.substr(1).trim());
|
||||
}
|
||||
@@ -302,13 +346,7 @@ export class ContextKeyDefinedExpr implements IContextKeyExpression {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return cmp1(this.key, other.key);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
@@ -362,19 +400,7 @@ export class ContextKeyEqualsExpr implements IContextKeyExpression {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
if (this.value < other.value) {
|
||||
return -1;
|
||||
}
|
||||
if (this.value > other.value) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
@@ -391,7 +417,7 @@ export class ContextKeyEqualsExpr implements IContextKeyExpression {
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key + ' == \'' + this.value + '\'';
|
||||
return `${this.key} == '${this.value}'`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
@@ -422,19 +448,7 @@ export class ContextKeyInExpr implements IContextKeyExpression {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
if (this.valueKey < other.valueKey) {
|
||||
return -1;
|
||||
}
|
||||
if (this.valueKey > other.valueKey) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return cmp2(this.key, this.valueKey, other.key, other.valueKey);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
@@ -460,7 +474,7 @@ export class ContextKeyInExpr implements IContextKeyExpression {
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key + ' in \'' + this.valueKey + '\'';
|
||||
return `${this.key} in '${this.valueKey}'`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
@@ -549,19 +563,7 @@ export class ContextKeyNotEqualsExpr implements IContextKeyExpression {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
if (this.value < other.value) {
|
||||
return -1;
|
||||
}
|
||||
if (this.value > other.value) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
@@ -578,7 +580,7 @@ export class ContextKeyNotEqualsExpr implements IContextKeyExpression {
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return this.key + ' != \'' + this.value + '\'';
|
||||
return `${this.key} != '${this.value}'`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
@@ -613,13 +615,7 @@ export class ContextKeyNotExpr implements IContextKeyExpression {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
if (this.key < other.key) {
|
||||
return -1;
|
||||
}
|
||||
if (this.key > other.key) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return cmp1(this.key, other.key);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
@@ -634,7 +630,7 @@ export class ContextKeyNotExpr implements IContextKeyExpression {
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return '!' + this.key;
|
||||
return `!${this.key}`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
@@ -650,6 +646,200 @@ export class ContextKeyNotExpr implements IContextKeyExpression {
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyGreaterExpr implements IContextKeyExpression {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpression {
|
||||
return new ContextKeyGreaterExpr(key, value);
|
||||
}
|
||||
|
||||
public readonly type = ContextKeyExprType.Greater;
|
||||
|
||||
private constructor(
|
||||
private readonly key: string,
|
||||
private readonly value: any
|
||||
) { }
|
||||
|
||||
public cmp(other: ContextKeyExpression): number {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
if (other.type === this.type) {
|
||||
return (this.key === other.key && this.value === other.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return (parseFloat(<any>context.getValue(this.key)) > parseFloat(this.value));
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return `${this.key} > ${this.value}`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return [this.key];
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
|
||||
return mapFnc.mapGreater(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpression {
|
||||
return ContextKeySmallerEqualsExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyGreaterEqualsExpr implements IContextKeyExpression {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpression {
|
||||
return new ContextKeyGreaterEqualsExpr(key, value);
|
||||
}
|
||||
|
||||
public readonly type = ContextKeyExprType.GreaterEquals;
|
||||
|
||||
private constructor(
|
||||
private readonly key: string,
|
||||
private readonly value: any
|
||||
) { }
|
||||
|
||||
public cmp(other: ContextKeyExpression): number {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
if (other.type === this.type) {
|
||||
return (this.key === other.key && this.value === other.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return (parseFloat(<any>context.getValue(this.key)) >= parseFloat(this.value));
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return `${this.key} >= ${this.value}`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return [this.key];
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
|
||||
return mapFnc.mapGreaterEquals(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpression {
|
||||
return ContextKeySmallerExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeySmallerExpr implements IContextKeyExpression {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpression {
|
||||
return new ContextKeySmallerExpr(key, value);
|
||||
}
|
||||
|
||||
public readonly type = ContextKeyExprType.Smaller;
|
||||
|
||||
private constructor(
|
||||
private readonly key: string,
|
||||
private readonly value: any
|
||||
) {
|
||||
}
|
||||
|
||||
public cmp(other: ContextKeyExpression): number {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
if (other.type === this.type) {
|
||||
return (this.key === other.key && this.value === other.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return (parseFloat(<any>context.getValue(this.key)) < parseFloat(this.value));
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return `${this.key} < ${this.value}`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return [this.key];
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
|
||||
return mapFnc.mapSmaller(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpression {
|
||||
return ContextKeyGreaterEqualsExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeySmallerEqualsExpr implements IContextKeyExpression {
|
||||
|
||||
public static create(key: string, value: any): ContextKeyExpression {
|
||||
return new ContextKeySmallerEqualsExpr(key, value);
|
||||
}
|
||||
|
||||
public readonly type = ContextKeyExprType.SmallerEquals;
|
||||
|
||||
private constructor(
|
||||
private readonly key: string,
|
||||
private readonly value: any
|
||||
) {
|
||||
}
|
||||
|
||||
public cmp(other: ContextKeyExpression): number {
|
||||
if (other.type !== this.type) {
|
||||
return this.type - other.type;
|
||||
}
|
||||
return cmp2(this.key, this.value, other.key, other.value);
|
||||
}
|
||||
|
||||
public equals(other: ContextKeyExpression): boolean {
|
||||
if (other.type === this.type) {
|
||||
return (this.key === other.key && this.value === other.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public evaluate(context: IContext): boolean {
|
||||
return (parseFloat(<any>context.getValue(this.key)) <= parseFloat(this.value));
|
||||
}
|
||||
|
||||
public serialize(): string {
|
||||
return `${this.key} <= ${this.value}`;
|
||||
}
|
||||
|
||||
public keys(): string[] {
|
||||
return [this.key];
|
||||
}
|
||||
|
||||
public map(mapFnc: IContextKeyExprMapper): ContextKeyExpression {
|
||||
return mapFnc.mapSmallerEquals(this.key, this.value);
|
||||
}
|
||||
|
||||
public negate(): ContextKeyExpression {
|
||||
return ContextKeyGreaterExpr.create(this.key, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextKeyRegexExpr implements IContextKeyExpression {
|
||||
|
||||
public static create(key: string, regexp: RegExp | null): ContextKeyRegexExpr {
|
||||
@@ -1143,3 +1333,29 @@ export interface IContextKeyService {
|
||||
}
|
||||
|
||||
export const SET_CONTEXT_COMMAND_ID = 'setContext';
|
||||
|
||||
function cmp1(key1: string, key2: string): number {
|
||||
if (key1 < key2) {
|
||||
return -1;
|
||||
}
|
||||
if (key1 > key2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function cmp2(key1: string, value1: any, key2: string, value2: any): number {
|
||||
if (key1 < key2) {
|
||||
return -1;
|
||||
}
|
||||
if (key1 > key2) {
|
||||
return 1;
|
||||
}
|
||||
if (value1 < value2) {
|
||||
return -1;
|
||||
}
|
||||
if (value1 > value2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -186,4 +186,84 @@ suite('ContextKeyExpr', () => {
|
||||
);
|
||||
assert.equal(actual!.equals(expected!), true);
|
||||
});
|
||||
|
||||
test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => {
|
||||
function checkEvaluate(expr: string, ctx: any, expected: any): void {
|
||||
const _expr = ContextKeyExpr.deserialize(expr)!;
|
||||
assert.equal(_expr.evaluate(createContext(ctx)), expected);
|
||||
}
|
||||
|
||||
checkEvaluate('a>1', {}, false);
|
||||
checkEvaluate('a>1', { a: 0 }, false);
|
||||
checkEvaluate('a>1', { a: 1 }, false);
|
||||
checkEvaluate('a>1', { a: 2 }, true);
|
||||
checkEvaluate('a>1', { a: '0' }, false);
|
||||
checkEvaluate('a>1', { a: '1' }, false);
|
||||
checkEvaluate('a>1', { a: '2' }, true);
|
||||
checkEvaluate('a>1', { a: 'a' }, false);
|
||||
|
||||
checkEvaluate('a>10', { a: 2 }, false);
|
||||
checkEvaluate('a>10', { a: 11 }, true);
|
||||
checkEvaluate('a>10', { a: '11' }, true);
|
||||
checkEvaluate('a>10', { a: '2' }, false);
|
||||
checkEvaluate('a>10', { a: '11' }, true);
|
||||
|
||||
checkEvaluate('a>1.1', { a: 1 }, false);
|
||||
checkEvaluate('a>1.1', { a: 2 }, true);
|
||||
checkEvaluate('a>1.1', { a: 11 }, true);
|
||||
checkEvaluate('a>1.1', { a: '1.1' }, false);
|
||||
checkEvaluate('a>1.1', { a: '2' }, true);
|
||||
checkEvaluate('a>1.1', { a: '11' }, true);
|
||||
|
||||
checkEvaluate('a>b', { a: 'b' }, false);
|
||||
checkEvaluate('a>b', { a: 'c' }, false);
|
||||
checkEvaluate('a>b', { a: 1000 }, false);
|
||||
|
||||
checkEvaluate('a >= 2', { a: '1' }, false);
|
||||
checkEvaluate('a >= 2', { a: '2' }, true);
|
||||
checkEvaluate('a >= 2', { a: '3' }, true);
|
||||
|
||||
checkEvaluate('a < 2', { a: '1' }, true);
|
||||
checkEvaluate('a < 2', { a: '2' }, false);
|
||||
checkEvaluate('a < 2', { a: '3' }, false);
|
||||
|
||||
checkEvaluate('a <= 2', { a: '1' }, true);
|
||||
checkEvaluate('a <= 2', { a: '2' }, true);
|
||||
checkEvaluate('a <= 2', { a: '3' }, false);
|
||||
});
|
||||
|
||||
test('Greater, GreaterEquals, Smaller, SmallerEquals negate', () => {
|
||||
function checkNegate(expr: string, expected: string): void {
|
||||
const a = ContextKeyExpr.deserialize(expr)!;
|
||||
const b = a.negate();
|
||||
assert.equal(b.serialize(), expected);
|
||||
}
|
||||
|
||||
checkNegate('a>1', 'a <= 1');
|
||||
checkNegate('a>1.1', 'a <= 1.1');
|
||||
checkNegate('a>b', 'a <= b');
|
||||
|
||||
checkNegate('a>=1', 'a < 1');
|
||||
checkNegate('a>=1.1', 'a < 1.1');
|
||||
checkNegate('a>=b', 'a < b');
|
||||
|
||||
checkNegate('a<1', 'a >= 1');
|
||||
checkNegate('a<1.1', 'a >= 1.1');
|
||||
checkNegate('a<b', 'a >= b');
|
||||
|
||||
checkNegate('a<=1', 'a > 1');
|
||||
checkNegate('a<=1.1', 'a > 1.1');
|
||||
checkNegate('a<=b', 'a > b');
|
||||
});
|
||||
|
||||
test('issue #111899: context keys can use `<` or `>` ', () => {
|
||||
const actual = ContextKeyExpr.deserialize('editorTextFocus && vim.active && vim.use<C-r>')!;
|
||||
assert.ok(actual.equals(
|
||||
ContextKeyExpr.and(
|
||||
ContextKeyExpr.has('editorTextFocus'),
|
||||
ContextKeyExpr.has('vim.active'),
|
||||
ContextKeyExpr.has('vim.use<C-r>'),
|
||||
)!
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,7 @@ export class ContextMenuHandler {
|
||||
getAnchor: () => delegate.getAnchor(),
|
||||
canRelayout: false,
|
||||
anchorAlignment: delegate.anchorAlignment,
|
||||
anchorAxisAlignment: delegate.anchorAxisAlignment,
|
||||
|
||||
render: (container) => {
|
||||
let className = delegate.getMenuClassName ? delegate.getMenuClassName() : '';
|
||||
@@ -79,7 +80,7 @@ export class ContextMenuHandler {
|
||||
const menuDisposables = new DisposableStore();
|
||||
|
||||
const actionRunner = delegate.actionRunner || new ActionRunner();
|
||||
actionRunner.onDidBeforeRun(this.onActionRun, this, menuDisposables);
|
||||
actionRunner.onBeforeRun(this.onActionRun, this, menuDisposables);
|
||||
actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables);
|
||||
menu = new Menu(container, actions, {
|
||||
actionViewItemProvider: delegate.getActionViewItem,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { AnchorAlignment, AnchorAxisAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
export const IContextViewService = createDecorator<IContextViewService>('contextViewService');
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface IContextViewDelegate {
|
||||
onHide?(data?: any): void;
|
||||
focus?(): void;
|
||||
anchorAlignment?: AnchorAlignment;
|
||||
anchorAxisAlignment?: AnchorAxisAlignment;
|
||||
}
|
||||
|
||||
export const IContextMenuService = createDecorator<IContextMenuService>('contextMenuService');
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
|
||||
export const ID = 'diagnosticsService';
|
||||
export const IDiagnosticsService = createDecorator<IDiagnosticsService>(ID);
|
||||
@@ -163,12 +164,10 @@ function asSortedItems(items: Map<string, number>): WorkspaceStatItem[] {
|
||||
}
|
||||
|
||||
export function getMachineInfo(): IMachineInfo {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
const machineInfo: IMachineInfo = {
|
||||
os: `${osLib.type()} ${osLib.arch()} ${osLib.release()}`,
|
||||
memory: `${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`,
|
||||
memory: `${(osLib.totalmem() / ByteSize.GB).toFixed(2)}GB (${(osLib.freemem() / ByteSize.GB).toFixed(2)}GB free)`,
|
||||
vmHint: `${Math.round((virtualMachineHint.value() * 100))}%`,
|
||||
};
|
||||
|
||||
@@ -238,9 +237,6 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
}
|
||||
|
||||
private formatEnvironment(info: IMainProcessInfo): string {
|
||||
const MB = 1024 * 1024;
|
||||
const GB = 1024 * MB;
|
||||
|
||||
const output: string[] = [];
|
||||
output.push(`Version: ${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`);
|
||||
output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`);
|
||||
@@ -248,7 +244,7 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
if (cpus && cpus.length > 0) {
|
||||
output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`);
|
||||
}
|
||||
output.push(`Memory (System): ${(osLib.totalmem() / GB).toFixed(2)}GB (${(osLib.freemem() / GB).toFixed(2)}GB free)`);
|
||||
output.push(`Memory (System): ${(osLib.totalmem() / ByteSize.GB).toFixed(2)}GB (${(osLib.freemem() / ByteSize.GB).toFixed(2)}GB free)`);
|
||||
if (!isWindows) {
|
||||
output.push(`Load (avg): ${osLib.loadavg().map(l => Math.round(l)).join(', ')}`); // only provided on Linux/macOS
|
||||
}
|
||||
@@ -493,8 +489,6 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
private formatProcessItem(mainPid: number, mapPidToWindowTitle: Map<number, string>, output: string[], item: ProcessItem, indent: number): void {
|
||||
const isRoot = (indent === 0);
|
||||
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
// Format name with indent
|
||||
let name: string;
|
||||
if (isRoot) {
|
||||
@@ -508,7 +502,7 @@ export class DiagnosticsService implements IDiagnosticsService {
|
||||
}
|
||||
|
||||
const memory = process.platform === 'win32' ? item.mem : (osLib.totalmem() * (item.mem / 100));
|
||||
output.push(`${item.load.toFixed(0).padStart(5, ' ')}\t${(memory / MB).toFixed(0).padStart(6, ' ')}\t${item.pid.toFixed(0).padStart(6, ' ')}\t${name}`);
|
||||
output.push(`${item.load.toFixed(0).padStart(5, ' ')}\t${(memory / ByteSize.MB).toFixed(0).padStart(6, ' ')}\t${item.pid.toFixed(0).padStart(6, ' ')}\t${name}`);
|
||||
|
||||
// Recurse into children if any
|
||||
if (Array.isArray(item.children)) {
|
||||
|
||||
@@ -22,6 +22,29 @@ export interface ICheckbox {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export interface IConfirmDialogArgs {
|
||||
confirmation: IConfirmation;
|
||||
}
|
||||
|
||||
export interface IShowDialogArgs {
|
||||
severity: Severity;
|
||||
message: string;
|
||||
buttons: string[];
|
||||
options?: IDialogOptions;
|
||||
}
|
||||
|
||||
export interface IInputDialogArgs extends IShowDialogArgs {
|
||||
inputs: IInput[],
|
||||
}
|
||||
|
||||
export interface IDialog {
|
||||
confirmArgs?: IConfirmDialogArgs;
|
||||
showArgs?: IShowDialogArgs;
|
||||
inputArgs?: IInputDialogArgs;
|
||||
}
|
||||
|
||||
export type IDialogResult = IConfirmationResult | IInputResult | IShowResult;
|
||||
|
||||
export interface IConfirmation {
|
||||
title?: string;
|
||||
type?: DialogType;
|
||||
@@ -166,6 +189,40 @@ export interface IInput {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler to bring up modal dialogs.
|
||||
*/
|
||||
export interface IDialogHandler {
|
||||
/**
|
||||
* Ask the user for confirmation with a modal dialog.
|
||||
*/
|
||||
confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
|
||||
|
||||
/**
|
||||
* Present a modal dialog to the user.
|
||||
*
|
||||
* @returns A promise with the selected choice index. If the user refused to choose,
|
||||
* then a promise with index of `cancelId` option is returned. If there is no such
|
||||
* option then promise with index `0` is returned.
|
||||
*/
|
||||
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult>;
|
||||
|
||||
/**
|
||||
* Present a modal dialog to the user asking for input.
|
||||
*
|
||||
* @returns A promise with the selected choice index. If the user refused to choose,
|
||||
* then a promise with index of `cancelId` option is returned. If there is no such
|
||||
* option then promise with index `0` is returned. In addition, the values for the
|
||||
* inputs are returned as well.
|
||||
*/
|
||||
input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise<IInputResult>;
|
||||
|
||||
/**
|
||||
* Present the about dialog to the user.
|
||||
*/
|
||||
about(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A service to bring up modal dialogs.
|
||||
*
|
||||
@@ -218,20 +275,23 @@ export interface IFileDialogService {
|
||||
/**
|
||||
* The default path for a new file based on previously used files.
|
||||
* @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used.
|
||||
* Falls back to user home in the absence of enough information to find a better URI.
|
||||
*/
|
||||
defaultFilePath(schemeFilter?: string): URI | undefined;
|
||||
defaultFilePath(schemeFilter?: string): Promise<URI>;
|
||||
|
||||
/**
|
||||
* The default path for a new folder based on previously used folders.
|
||||
* @param schemeFilter The scheme of the folder path. If no filter given, the scheme of the current window is used.
|
||||
* Falls back to user home in the absence of enough information to find a better URI.
|
||||
*/
|
||||
defaultFolderPath(schemeFilter?: string): URI | undefined;
|
||||
defaultFolderPath(schemeFilter?: string): Promise<URI>;
|
||||
|
||||
/**
|
||||
* The default path for a new workspace based on previously used workspaces.
|
||||
* @param schemeFilter The scheme of the workspace path. If no filter given, the scheme of the current window is used.
|
||||
* Falls back to user home in the absence of enough information to find a better URI.
|
||||
*/
|
||||
defaultWorkspacePath(schemeFilter?: string, filename?: string): URI | undefined;
|
||||
defaultWorkspacePath(schemeFilter?: string, filename?: string): Promise<URI>;
|
||||
|
||||
/**
|
||||
* Shows a file-folder selection dialog and opens the selected entry.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IDisplayMainService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly onDidDisplayChanged: Event<void>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisplayMainService as ICommonDisplayMainService } from 'vs/platform/display/common/displayMainService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { app, Display, screen } from 'electron';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
|
||||
export const IDisplayMainService = createDecorator<IDisplayMainService>('displayMainService');
|
||||
|
||||
export interface IDisplayMainService extends ICommonDisplayMainService { }
|
||||
|
||||
export class DisplayMainService extends Disposable implements ICommonDisplayMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidDisplayChanged = this._register(new Emitter<void>());
|
||||
readonly onDidDisplayChanged = this._onDidDisplayChanged.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const displayChangedScheduler = this._register(new RunOnceScheduler(() => {
|
||||
this._onDidDisplayChanged.fire();
|
||||
}, 100));
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
const displayChangedListener = (event: Event, display: Display, changedMetrics?: string[]) => {
|
||||
displayChangedScheduler.schedule();
|
||||
};
|
||||
|
||||
screen.on('display-metrics-changed', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));
|
||||
|
||||
screen.on('display-added', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener)));
|
||||
|
||||
screen.on('display-removed', displayChangedListener);
|
||||
this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -74,12 +74,13 @@ export interface NativeParsedArgs {
|
||||
'driver'?: string;
|
||||
'driver-verbose'?: boolean;
|
||||
'remote'?: string;
|
||||
'disable-user-env-probe'?: boolean;
|
||||
'force'?: boolean;
|
||||
'do-not-sync'?: boolean;
|
||||
'force-user-env'?: boolean;
|
||||
'force-disable-user-env'?: boolean;
|
||||
'sync'?: 'on' | 'off';
|
||||
'__sandbox'?: boolean;
|
||||
'logsPath'?: string;
|
||||
|
||||
// chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches
|
||||
'no-proxy-server'?: boolean;
|
||||
|
||||
@@ -119,7 +119,7 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
||||
sharedIPCHandle: string;
|
||||
|
||||
// --- Extensions
|
||||
extensionsPath?: string;
|
||||
extensionsPath: string;
|
||||
extensionsDownloadPath: string;
|
||||
builtinExtensionsPath: string;
|
||||
extraExtensionPaths: string[]
|
||||
|
||||
@@ -19,6 +19,9 @@ export const IEnvironmentMainService = createDecorator<IEnvironmentMainService>(
|
||||
*/
|
||||
export interface IEnvironmentMainService extends INativeEnvironmentService {
|
||||
|
||||
// --- NLS cache path
|
||||
cachedLanguagesPath: string;
|
||||
|
||||
// --- backup paths
|
||||
backupHome: string;
|
||||
backupWorkspacesPath: string;
|
||||
@@ -35,7 +38,10 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
|
||||
disableUpdates: boolean;
|
||||
}
|
||||
|
||||
export class EnvironmentMainService extends NativeEnvironmentService {
|
||||
export class EnvironmentMainService extends NativeEnvironmentService implements IEnvironmentMainService {
|
||||
|
||||
@memoize
|
||||
get cachedLanguagesPath(): string { return join(this.userDataPath, 'clp'); }
|
||||
|
||||
@memoize
|
||||
get backupHome(): string { return join(this.userDataPath, 'Backups'); }
|
||||
|
||||
@@ -43,8 +43,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") },
|
||||
'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
|
||||
'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
|
||||
'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
|
||||
'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },
|
||||
'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
|
||||
'waitMarkerFilePath': { type: 'string' },
|
||||
'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
|
||||
@@ -59,7 +57,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'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.") },
|
||||
'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts. The identifier of an extension is always `${publisher}.${name}`. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") },
|
||||
'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") },
|
||||
'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
|
||||
'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
|
||||
|
||||
@@ -81,6 +79,9 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
|
||||
|
||||
'remote': { type: 'string' },
|
||||
'folder-uri': { type: 'string[]', cat: 'o', args: 'uri' },
|
||||
'file-uri': { type: 'string[]', cat: 'o', args: 'uri' },
|
||||
|
||||
'locate-extension': { type: 'string[]' },
|
||||
'extensionDevelopmentPath': { type: 'string[]' },
|
||||
'extensionTestsPath': { type: 'string' },
|
||||
@@ -98,7 +99,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'disable-crash-reporter': { type: 'boolean' },
|
||||
'crash-reporter-directory': { type: 'string' },
|
||||
'crash-reporter-id': { type: 'string' },
|
||||
'disable-user-env-probe': { type: 'boolean' },
|
||||
'skip-add-to-recently-opened': { type: 'boolean' },
|
||||
'unity-launch': { type: 'boolean' },
|
||||
'open-url': { type: 'boolean' },
|
||||
@@ -112,8 +112,10 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'trace-category-filter': { type: 'string' },
|
||||
'trace-options': { type: 'string' },
|
||||
'force-user-env': { type: 'boolean' },
|
||||
'force-disable-user-env': { type: 'boolean' },
|
||||
'open-devtools': { type: 'boolean' },
|
||||
'__sandbox': { type: 'boolean' },
|
||||
'logsPath': { type: 'string' },
|
||||
|
||||
// chromium flags
|
||||
'no-proxy-server': { type: 'boolean' },
|
||||
|
||||
@@ -52,7 +52,7 @@ export function parseMainProcessArgv(processArgv: string[]): NativeParsedArgs {
|
||||
}
|
||||
|
||||
// If called from CLI, don't report warnings as they are already reported.
|
||||
let reportWarnings = !process.env['VSCODE_CLI'];
|
||||
const reportWarnings = !isLaunchedFromCli(process.env);
|
||||
return parseAndValidate(args, reportWarnings);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export function parseMainProcessArgv(processArgv: string[]): NativeParsedArgs {
|
||||
* Use this to parse raw code CLI process.argv such as: `Electron cli.js . --verbose --wait`
|
||||
*/
|
||||
export function parseCLIProcessArgv(processArgv: string[]): NativeParsedArgs {
|
||||
let [, , ...args] = processArgv; // remove the first non-option argument: it's always the app location
|
||||
const [, , ...args] = processArgv; // remove the first non-option argument: it's always the app location
|
||||
|
||||
return parseAndValidate(args, true);
|
||||
}
|
||||
@@ -78,3 +78,7 @@ export function addArg(argv: string[], ...args: string[]): string[] {
|
||||
|
||||
return argv;
|
||||
}
|
||||
|
||||
export function isLaunchedFromCli(env: NodeJS.ProcessEnv): boolean {
|
||||
return env['VSCODE_CLI'] === '1';
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import * as paths from 'vs/base/node/paths';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
@@ -211,12 +211,11 @@ export class NativeEnvironmentService implements INativeEnvironmentService {
|
||||
get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; }
|
||||
|
||||
constructor(protected _args: NativeParsedArgs) {
|
||||
if (!process.env['VSCODE_LOGS']) {
|
||||
if (!_args.logsPath) {
|
||||
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');
|
||||
process.env['VSCODE_LOGS'] = path.join(this.userDataPath, 'logs', key);
|
||||
_args.logsPath = path.join(this.userDataPath, 'logs', key);
|
||||
}
|
||||
|
||||
this.logsPath = process.env['VSCODE_LOGS']!;
|
||||
this.logsPath = _args.logsPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,5 +252,5 @@ export function parsePathArg(arg: string | undefined, process: NodeJS.Process):
|
||||
}
|
||||
|
||||
export function parseUserDataDir(args: NativeParsedArgs, process: NodeJS.Process): string {
|
||||
return parsePathArg(args['user-data-dir'], process) || path.resolve(paths.getDefaultUserDataPath(process.platform));
|
||||
return parsePathArg(args['user-data-dir'], process) || path.resolve(paths.getDefaultUserDataPath());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { formatOptions, Option } from 'vs/platform/environment/node/argv';
|
||||
import { addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
|
||||
suite('formatOptions', () => {
|
||||
|
||||
function o(description: string): Option<any> {
|
||||
return {
|
||||
description, type: 'string'
|
||||
};
|
||||
}
|
||||
|
||||
test('Text should display small columns correctly', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o('bar')
|
||||
}, 80),
|
||||
[' --add bar']
|
||||
);
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o('bar'),
|
||||
'wait': o('ba'),
|
||||
'trace': o('b')
|
||||
}, 80),
|
||||
[
|
||||
' --add bar',
|
||||
' --wait ba',
|
||||
' --trace b'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should wrap', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o((<any>'bar ').repeat(9))
|
||||
}, 40),
|
||||
[
|
||||
' --add bar bar bar bar bar bar bar bar',
|
||||
' bar'
|
||||
]);
|
||||
});
|
||||
|
||||
test('Text should revert to the condensed view when the terminal is too narrow', () => {
|
||||
assert.deepEqual(
|
||||
formatOptions({
|
||||
'add': o((<any>'bar ').repeat(9))
|
||||
}, 30),
|
||||
[
|
||||
' --add',
|
||||
' bar bar bar bar bar bar bar bar bar '
|
||||
]);
|
||||
});
|
||||
|
||||
test('addArg', () => {
|
||||
assert.deepEqual(addArg([], 'foo'), ['foo']);
|
||||
assert.deepEqual(addArg([], 'foo', 'bar'), ['foo', 'bar']);
|
||||
assert.deepEqual(addArg(['foo'], 'bar'), ['foo', 'bar']);
|
||||
assert.deepEqual(addArg(['--wait'], 'bar'), ['--wait', 'bar']);
|
||||
assert.deepEqual(addArg(['--wait', '--', '--foo'], 'bar'), ['--wait', 'bar', '--', '--foo']);
|
||||
assert.deepEqual(addArg(['--', '--foo'], 'bar'), ['bar', '--', '--foo']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
|
||||
function testErrorMessage(module: string): string {
|
||||
return `Unable to load "${module}" dependency. It was probably not compiled for the right operating system architecture or had missing build tools.`;
|
||||
}
|
||||
|
||||
suite('Native Modules (all platforms)', () => {
|
||||
|
||||
test('native-is-elevated', async () => {
|
||||
const isElevated = await import('native-is-elevated');
|
||||
assert.ok(typeof isElevated === 'function', testErrorMessage('native-is-elevated '));
|
||||
});
|
||||
|
||||
test('native-keymap', async () => {
|
||||
const keyMap = await import('native-keymap');
|
||||
assert.ok(typeof keyMap.getCurrentKeyboardLayout === 'function', testErrorMessage('native-keymap'));
|
||||
});
|
||||
|
||||
test('native-watchdog', async () => {
|
||||
const watchDog = await import('native-watchdog');
|
||||
assert.ok(typeof watchDog.start === 'function', testErrorMessage('native-watchdog'));
|
||||
});
|
||||
|
||||
test('node-pty', async () => {
|
||||
const nodePty = await import('node-pty');
|
||||
assert.ok(typeof nodePty.spawn === 'function', testErrorMessage('node-pty'));
|
||||
});
|
||||
|
||||
test('spdlog', async () => {
|
||||
const spdlog = await import('spdlog');
|
||||
assert.ok(typeof spdlog.createRotatingLogger === 'function', testErrorMessage('spdlog'));
|
||||
});
|
||||
|
||||
test('v8-inspect-profiler', async () => {
|
||||
const profiler = await import('v8-inspect-profiler');
|
||||
assert.ok(typeof profiler.startProfiling === 'function', testErrorMessage('v8-inspect-profiler'));
|
||||
});
|
||||
|
||||
test('vscode-nsfw', async () => {
|
||||
const nsfWatcher = await import('vscode-nsfw');
|
||||
assert.ok(typeof nsfWatcher === 'function', testErrorMessage('vscode-nsfw'));
|
||||
});
|
||||
|
||||
test('vscode-sqlite3', async () => {
|
||||
const sqlite3 = await import('vscode-sqlite3');
|
||||
assert.ok(typeof sqlite3.Database === 'function', testErrorMessage('vscode-sqlite3'));
|
||||
});
|
||||
});
|
||||
|
||||
(!isMacintosh ? suite.skip : suite)('Native Modules (macOS)', () => {
|
||||
|
||||
test('chokidar (fsevents)', async () => {
|
||||
const chokidar = await import('chokidar');
|
||||
const watcher = chokidar.watch(__dirname);
|
||||
assert.ok(watcher.options.useFsEvents, testErrorMessage('chokidar (fsevents)'));
|
||||
|
||||
return watcher.close();
|
||||
});
|
||||
});
|
||||
|
||||
(!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => {
|
||||
|
||||
test('windows-mutex', async () => {
|
||||
const mutex = await import('windows-mutex');
|
||||
assert.ok(mutex && typeof mutex.isActive === 'function', testErrorMessage('windows-mutex'));
|
||||
assert.ok(typeof mutex.isActive === 'function', testErrorMessage('windows-mutex'));
|
||||
});
|
||||
|
||||
test('windows-foreground-love', async () => {
|
||||
const foregroundLove = await import('windows-foreground-love');
|
||||
assert.ok(typeof foregroundLove.allowSetForegroundWindow === 'function', testErrorMessage('windows-foreground-love'));
|
||||
});
|
||||
|
||||
test('windows-process-tree', async () => {
|
||||
const processTree = await import('windows-process-tree');
|
||||
assert.ok(typeof processTree.getProcessTree === 'function', testErrorMessage('windows-process-tree'));
|
||||
});
|
||||
|
||||
test('vscode-windows-registry', async () => {
|
||||
const windowsRegistry = await import('vscode-windows-registry');
|
||||
assert.ok(typeof windowsRegistry.GetStringRegKey === 'function', testErrorMessage('vscode-windows-registry'));
|
||||
});
|
||||
|
||||
test('vscode-windows-ca-certs', async () => {
|
||||
// @ts-ignore Windows only
|
||||
const windowsCerts = await import('vscode-windows-ca-certs');
|
||||
const store = windowsCerts();
|
||||
assert.ok(windowsCerts, testErrorMessage('vscode-windows-ca-certs'));
|
||||
let certCount = 0;
|
||||
try {
|
||||
while (store.next()) {
|
||||
certCount++;
|
||||
}
|
||||
} finally {
|
||||
store.done();
|
||||
}
|
||||
assert(certCount > 0);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionIdentifier, IGlobalExtensionEnablementService, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {
|
||||
@@ -96,7 +96,7 @@ export class StorageManager extends Disposable {
|
||||
|
||||
constructor(private storageService: IStorageService) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope): IExtensionIdentifier[] {
|
||||
@@ -127,14 +127,14 @@ export class StorageManager extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
if (!isUndefinedOrNull(this.storage[workspaceStorageChangeEvent.key])) {
|
||||
const newValue = this._get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
if (newValue !== this.storage[workspaceStorageChangeEvent.key]) {
|
||||
const oldValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
delete this.storage[workspaceStorageChangeEvent.key];
|
||||
const newValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) {
|
||||
const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
if (newValue !== this.storage[storageChangeEvent.key]) {
|
||||
const oldValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
delete this.storage[storageChangeEvent.key];
|
||||
const newValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue)));
|
||||
const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue)));
|
||||
if (added.length || removed.length) {
|
||||
@@ -151,7 +151,8 @@ export class StorageManager extends Disposable {
|
||||
|
||||
private _set(key: string, value: string | undefined, scope: StorageScope): void {
|
||||
if (value) {
|
||||
this.storageService.store(key, value, scope);
|
||||
// Enablement state is synced separately through extensions
|
||||
this.storageService.store(key, value, scope, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.storageService.remove(key, scope);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
@@ -803,7 +803,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: {
|
||||
get: (key: string, scope: StorageScope) => string | undefined,
|
||||
store: (key: string, value: string, scope: StorageScope) => void
|
||||
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
|
||||
} | undefined): Promise<{ [key: string]: string; }> {
|
||||
const headers: IHeaders = {
|
||||
'X-Market-Client-Id': `VSCode ${version}`,
|
||||
|
||||
@@ -201,7 +201,8 @@ export class ExtensionManagementError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean };
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean };
|
||||
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
|
||||
|
||||
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
|
||||
export interface IExtensionManagementService {
|
||||
@@ -218,7 +219,7 @@ export interface IExtensionManagementService {
|
||||
install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
canInstall(extension: IGalleryExtension): Promise<boolean>;
|
||||
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
uninstall(extension: ILocalExtension, force?: boolean): Promise<void>;
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
|
||||
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
|
||||
return URI.revive(transformer ? transformer.transformIncoming(uri) : uri);
|
||||
@@ -77,18 +78,31 @@ export class ExtensionManagementChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionManagementChannelClient implements IExtensionManagementService {
|
||||
export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
readonly onInstallExtension = this._onInstallExtension.event;
|
||||
|
||||
private readonly _onDidInstallExtension = this._register(new Emitter<DidInstallExtensionEvent>());
|
||||
readonly onDidInstallExtension = this._onDidInstallExtension.event;
|
||||
|
||||
private readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
|
||||
readonly onUninstallExtension = this._onUninstallExtension.event;
|
||||
|
||||
private readonly _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
|
||||
readonly onDidUninstallExtension = this._onDidUninstallExtension.event;
|
||||
|
||||
constructor(
|
||||
private readonly channel: IChannel,
|
||||
) { }
|
||||
|
||||
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
|
||||
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return Event.map(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); }
|
||||
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
|
||||
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
|
||||
) {
|
||||
super();
|
||||
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire(e)));
|
||||
this._register(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension')(e => this._onDidInstallExtension.fire({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local })));
|
||||
this._register(this.channel.listen<IExtensionIdentifier>('onUninstallExtension')(e => this._onUninstallExtension.fire(e)));
|
||||
this._register(this.channel.listen<DidUninstallExtensionEvent>('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e)));
|
||||
}
|
||||
|
||||
zip(extension: ILocalExtension): Promise<URI> {
|
||||
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
|
||||
@@ -114,8 +128,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
|
||||
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
|
||||
}
|
||||
|
||||
uninstall(extension: ILocalExtension, force = false): Promise<void> {
|
||||
return Promise.resolve(this.channel.call('uninstall', [extension!, force]));
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
|
||||
return Promise.resolve(this.channel.call('uninstall', [extension!, options]));
|
||||
}
|
||||
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const IExtensionUrlTrustService = createDecorator<IExtensionUrlTrustService>('extensionUrlTrustService');
|
||||
|
||||
export interface IExtensionUrlTrustService {
|
||||
readonly _serviceBrand: undefined;
|
||||
isExtensionUrlTrusted(extensionId: string, url: string): Promise<boolean>;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { disposableTimeout, timeout } from 'vs/base/common/async';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
type ExeExtensionRecommendationsClassification = {
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
@@ -241,7 +241,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
|
||||
}
|
||||
|
||||
private updateLastPromptedMediumExeTime(value: number): void {
|
||||
this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL);
|
||||
this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private getPromptedExecutableTips(): IStringDictionary<string[]> {
|
||||
@@ -251,7 +251,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
|
||||
private addToRecommendedExecutables(exeName: string, tips: IExecutableBasedExtensionTip[]) {
|
||||
const promptedExecutableTips = this.getPromptedExecutableTips();
|
||||
promptedExecutableTips[exeName] = tips.map(({ extensionId }) => extensionId.toLowerCase());
|
||||
this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL);
|
||||
this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { rename } from 'vs/base/node/pfs';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -13,6 +14,7 @@ import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/ex
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
@@ -36,8 +38,21 @@ export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
const vsixName = this.getName(extension);
|
||||
const location = joinPath(this.extensionsDownloadDir, vsixName);
|
||||
|
||||
// Download only if vsix does not exist
|
||||
if (!await this.fileService.exists(location)) {
|
||||
// Download to temporary location first only if vsix does not exist
|
||||
const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`);
|
||||
if (!await this.fileService.exists(tempLocation)) {
|
||||
await this.extensionGalleryService.download(extension, tempLocation, operation);
|
||||
}
|
||||
|
||||
// Rename temp location to original
|
||||
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -45,9 +60,15 @@ export class ExtensionsDownloader extends Disposable {
|
||||
// noop as caching is enabled always
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
|
||||
try {
|
||||
await rename(from.fsPath, to.fsPath);
|
||||
} catch (error) {
|
||||
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
|
||||
this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`);
|
||||
return this.rename(from, to, retryUntil);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
INSTALL_ERROR_MALICIOUS,
|
||||
INSTALL_ERROR_INCOMPATIBLE,
|
||||
ExtensionManagementError,
|
||||
InstallOptions
|
||||
InstallOptions,
|
||||
UninstallOptions
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -297,11 +298,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
|
||||
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
if (!options.donotIncludePackAndDependencies) {
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
|
||||
@@ -471,7 +474,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await Promise.all(extensionsToUninstall.map(local => this.uninstall(local)));
|
||||
}
|
||||
|
||||
async uninstall(extension: ILocalExtension): Promise<void> {
|
||||
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
|
||||
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
|
||||
@@ -480,7 +483,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
try {
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed);
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options);
|
||||
} catch (error) {
|
||||
throw this.joinErrors(error);
|
||||
}
|
||||
@@ -533,15 +536,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}, new Error(''));
|
||||
}
|
||||
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
try {
|
||||
await this.preUninstallExtension(extension);
|
||||
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
if (packedExtensions.length) {
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed);
|
||||
} else {
|
||||
await this.uninstallExtensions(extension, [], installed);
|
||||
}
|
||||
const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed, options);
|
||||
} catch (error) {
|
||||
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
|
||||
throw error;
|
||||
@@ -549,10 +548,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await this.postUninstallExtension(extension);
|
||||
}
|
||||
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
const extensionsToUninstall = [extension, ...otherExtensionsToUninstall];
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
if (!options.donotCheckDependents) {
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
}
|
||||
}
|
||||
await Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class ExtensionUrlTrustService implements IExtensionUrlTrustService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private trustedExtensionUrlPublicKeys = new Map<string, (crypto.KeyObject | string | null)[]>();
|
||||
|
||||
constructor(
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
async isExtensionUrlTrusted(extensionId: string, url: string): Promise<boolean> {
|
||||
if (!this.productService.trustedExtensionUrlPublicKeys) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'There are no configured trusted keys');
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = /^(.*)#([^#]+)$/.exec(url);
|
||||
|
||||
if (!match) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri has no fragment', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, originalUrl, fragment] = match;
|
||||
|
||||
let keys = this.trustedExtensionUrlPublicKeys.get(extensionId);
|
||||
|
||||
if (!keys) {
|
||||
keys = this.productService.trustedExtensionUrlPublicKeys[extensionId];
|
||||
|
||||
if (!keys || keys.length === 0) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Extension doesn\'t have any trusted keys', extensionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.trustedExtensionUrlPublicKeys.set(extensionId, [...keys]);
|
||||
}
|
||||
|
||||
const fragmentBuffer = Buffer.from(decodeURIComponent(fragment), 'base64');
|
||||
|
||||
if (fragmentBuffer.length <= 6) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri fragment is not a signature', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const timestampBuffer = fragmentBuffer.slice(0, 6);
|
||||
const timestamp = fragmentBuffer.readUIntBE(0, 6);
|
||||
const diff = Date.now() - timestamp;
|
||||
|
||||
if (diff < 0 || diff > 3_600_000) { // 1 hour
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri has expired', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const signatureBuffer = fragmentBuffer.slice(6);
|
||||
const verify = crypto.createVerify('SHA256');
|
||||
verify.write(timestampBuffer);
|
||||
verify.write(Buffer.from(originalUrl));
|
||||
verify.end();
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
|
||||
if (key === null) { // failed to be parsed before
|
||||
continue;
|
||||
} else if (typeof key === 'string') { // needs to be parsed
|
||||
try {
|
||||
key = crypto.createPublicKey({ key: Buffer.from(key, 'base64'), format: 'der', type: 'spki' });
|
||||
keys[i] = key;
|
||||
} catch (err) {
|
||||
this.logService.warn('ExtensionUrlTrustService#isExtensionUrlTrusted', `Failed to parse trusted extension uri public key #${i + 1} for ${extensionId}:`, err);
|
||||
keys[i] = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (verify.verify(key, signatureBuffer)) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri is valid', url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri could not be verified', url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
this.extensionsPath = environmentService.extensionsPath!;
|
||||
this.extensionsPath = environmentService.extensionsPath;
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat>;
|
||||
private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
const provider = await this.withProvider(resource);
|
||||
const isPathCaseSensitive = this.isPathCaseSensitive(provider);
|
||||
|
||||
const resolveTo = options?.resolveTo;
|
||||
const resolveSingleChildDescendants = options?.resolveSingleChildDescendants;
|
||||
@@ -193,7 +194,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// lazy trie to check for recursive resolving
|
||||
if (!trie) {
|
||||
trie = TernarySearchTree.forUris<true>();
|
||||
trie = TernarySearchTree.forUris<true>(() => !isPathCaseSensitive);
|
||||
trie.set(resource, true);
|
||||
if (isNonEmptyArray(resolveTo)) {
|
||||
resolveTo.forEach(uri => trie!.set(uri, true));
|
||||
|
||||
@@ -521,19 +521,19 @@ export class FileChangesEvent {
|
||||
switch (change.type) {
|
||||
case FileChangeType.ADDED:
|
||||
if (!this.added) {
|
||||
this.added = TernarySearchTree.forUris<IFileChange>(this.ignorePathCasing);
|
||||
this.added = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
|
||||
}
|
||||
this.added.set(change.resource, change);
|
||||
break;
|
||||
case FileChangeType.UPDATED:
|
||||
if (!this.updated) {
|
||||
this.updated = TernarySearchTree.forUris<IFileChange>(this.ignorePathCasing);
|
||||
this.updated = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
|
||||
}
|
||||
this.updated.set(change.resource, change);
|
||||
break;
|
||||
case FileChangeType.DELETED:
|
||||
if (!this.deleted) {
|
||||
this.deleted = TernarySearchTree.forUris<IFileChange>(this.ignorePathCasing);
|
||||
this.deleted = TernarySearchTree.forUris<IFileChange>(() => this.ignorePathCasing);
|
||||
}
|
||||
this.deleted.set(change.resource, change);
|
||||
break;
|
||||
@@ -971,33 +971,33 @@ export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096;
|
||||
/**
|
||||
* Helper to format a raw byte size into a human readable label.
|
||||
*/
|
||||
export class BinarySize {
|
||||
export class ByteSize {
|
||||
static readonly KB = 1024;
|
||||
static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
static readonly MB = ByteSize.KB * ByteSize.KB;
|
||||
static readonly GB = ByteSize.MB * ByteSize.KB;
|
||||
static readonly TB = ByteSize.GB * ByteSize.KB;
|
||||
|
||||
static formatSize(size: number): string {
|
||||
if (!isNumber(size)) {
|
||||
size = 0;
|
||||
}
|
||||
|
||||
if (size < BinarySize.KB) {
|
||||
if (size < ByteSize.KB) {
|
||||
return localize('sizeB', "{0}B", size.toFixed(0));
|
||||
}
|
||||
|
||||
if (size < BinarySize.MB) {
|
||||
return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
|
||||
if (size < ByteSize.MB) {
|
||||
return localize('sizeKB', "{0}KB", (size / ByteSize.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.GB) {
|
||||
return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
|
||||
if (size < ByteSize.GB) {
|
||||
return localize('sizeMB', "{0}MB", (size / ByteSize.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.TB) {
|
||||
return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
|
||||
if (size < ByteSize.TB) {
|
||||
return localize('sizeGB', "{0}GB", (size / ByteSize.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
|
||||
return localize('sizeTB', "{0}TB", (size / ByteSize.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,27 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatcherService';
|
||||
import { IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher';
|
||||
|
||||
class TestNsfwWatcherService extends NsfwWatcherService {
|
||||
suite('NSFW Watcher Service', async () => {
|
||||
|
||||
normalizeRoots(roots: string[]): string[] {
|
||||
// Load `nsfwWatcherService` within the suite to prevent all tests
|
||||
// from failing to start if `vscode-nsfw` was not properly installed
|
||||
const { NsfwWatcherService } = await import('vs/platform/files/node/watcher/nsfw/nsfwWatcherService');
|
||||
|
||||
// Work with strings as paths to simplify testing
|
||||
const requests: IWatcherRequest[] = roots.map(r => {
|
||||
return { path: r, excludes: [] };
|
||||
});
|
||||
class TestNsfwWatcherService extends NsfwWatcherService {
|
||||
|
||||
return this._normalizeRoots(requests).map(r => r.path);
|
||||
normalizeRoots(roots: string[]): string[] {
|
||||
|
||||
// Work with strings as paths to simplify testing
|
||||
const requests: IWatcherRequest[] = roots.map(r => {
|
||||
return { path: r, excludes: [] };
|
||||
});
|
||||
|
||||
return this._normalizeRoots(requests).map(r => r.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('NSFW Watcher Service', () => {
|
||||
suite('_normalizeRoots', () => {
|
||||
test('should not impacts roots that don\'t overlap', () => {
|
||||
const service = new TestNsfwWatcherService();
|
||||
|
||||
@@ -142,7 +142,7 @@ export class ChokidarWatcherService extends Disposable implements IWatcherServic
|
||||
}
|
||||
|
||||
if (this.verboseLogging) {
|
||||
this.log(`Start watching with chockidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
|
||||
this.log(`Start watching with chokidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`);
|
||||
}
|
||||
|
||||
let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts);
|
||||
|
||||
@@ -5,32 +5,36 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { normalizeRoots } from 'vs/platform/files/node/watcher/unix/chokidarWatcherService';
|
||||
import { IWatcherRequest } from 'vs/platform/files/node/watcher/unix/watcher';
|
||||
|
||||
function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest {
|
||||
return { path: basePath, excludes: ignored };
|
||||
}
|
||||
suite('Chokidar normalizeRoots', async () => {
|
||||
|
||||
function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) {
|
||||
const requests = inputPaths.map(path => newRequest(path));
|
||||
const actual = normalizeRoots(requests);
|
||||
assert.deepEqual(Object.keys(actual).sort(), expectedPaths);
|
||||
}
|
||||
// Load `chokidarWatcherService` within the suite to prevent all tests
|
||||
// from failing to start if `chokidar` was not properly installed
|
||||
const { normalizeRoots } = await import('vs/platform/files/node/watcher/unix/chokidarWatcherService');
|
||||
|
||||
function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) {
|
||||
const actual = normalizeRoots(inputRequests);
|
||||
const actualPath = Object.keys(actual).sort();
|
||||
const expectedPaths = Object.keys(expectedRequests).sort();
|
||||
assert.deepEqual(actualPath, expectedPaths);
|
||||
for (let path of actualPath) {
|
||||
let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
|
||||
let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
|
||||
assert.deepEqual(a, e);
|
||||
function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest {
|
||||
return { path: basePath, excludes: ignored };
|
||||
}
|
||||
|
||||
function assertNormalizedRootPath(inputPaths: string[], expectedPaths: string[]) {
|
||||
const requests = inputPaths.map(path => newRequest(path));
|
||||
const actual = normalizeRoots(requests);
|
||||
assert.deepEqual(Object.keys(actual).sort(), expectedPaths);
|
||||
}
|
||||
|
||||
function assertNormalizedRequests(inputRequests: IWatcherRequest[], expectedRequests: { [path: string]: IWatcherRequest[] }) {
|
||||
const actual = normalizeRoots(inputRequests);
|
||||
const actualPath = Object.keys(actual).sort();
|
||||
const expectedPaths = Object.keys(expectedRequests).sort();
|
||||
assert.deepEqual(actualPath, expectedPaths);
|
||||
for (let path of actualPath) {
|
||||
let a = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
|
||||
let e = expectedRequests[path].sort((r1, r2) => r1.path.localeCompare(r2.path));
|
||||
assert.deepEqual(a, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('Chokidar normalizeRoots', () => {
|
||||
test('should not impacts roots that don\'t overlap', () => {
|
||||
if (platform.isWindows) {
|
||||
assertNormalizedRootPath(['C:\\a'], ['C:\\a']);
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface IssueReporterData extends WindowData {
|
||||
enabledExtensions: IssueReporterExtensionData[];
|
||||
issueType?: IssueType;
|
||||
extensionId?: string;
|
||||
experiments?: string;
|
||||
readonly issueTitle?: string;
|
||||
readonly issueBody?: string;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export class IssueMainService implements ICommonIssueService {
|
||||
.then(result => {
|
||||
const [info, remoteData] = result;
|
||||
this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => {
|
||||
event.sender.send('vscode:issueSystemInfoResponse', msg);
|
||||
this.safeSend(event, 'vscode:issueSystemInfoResponse', msg);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export class IssueMainService implements ICommonIssueService {
|
||||
this.logService.error(`Listing processes failed: ${e}`);
|
||||
}
|
||||
|
||||
event.sender.send('vscode:listProcessesResponse', processes);
|
||||
this.safeSend(event, 'vscode:listProcessesResponse', processes);
|
||||
});
|
||||
|
||||
ipcMain.on('vscode:issueReporterClipboard', (event: IpcMainEvent) => {
|
||||
@@ -102,14 +102,14 @@ export class IssueMainService implements ICommonIssueService {
|
||||
if (this._issueWindow) {
|
||||
this.dialogMainService.showMessageBox(messageOptions, this._issueWindow)
|
||||
.then(result => {
|
||||
event.sender.send('vscode:issueReporterClipboardResponse', result.response === 0);
|
||||
this.safeSend(event, 'vscode:issueReporterClipboardResponse', result.response === 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('vscode:issuePerformanceInfoRequest', (event: IpcMainEvent) => {
|
||||
this.getPerformanceInfo().then(msg => {
|
||||
event.sender.send('vscode:issuePerformanceInfoResponse', msg);
|
||||
this.safeSend(event, 'vscode:issuePerformanceInfoResponse', msg);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -174,11 +174,17 @@ export class IssueMainService implements ICommonIssueService {
|
||||
|
||||
ipcMain.on('vscode:windowsInfoRequest', (event: IpcMainEvent) => {
|
||||
this.launchMainService.getMainProcessInfo().then(info => {
|
||||
event.sender.send('vscode:windowsInfoResponse', info.windows);
|
||||
this.safeSend(event, 'vscode:windowsInfoResponse', info.windows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private safeSend(event: IpcMainEvent, channel: string, ...args: unknown[]): void {
|
||||
if (!event.sender.isDestroyed()) {
|
||||
event.sender.send(channel, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
openReporter(data: IssueReporterData): Promise<void> {
|
||||
return new Promise(_ => {
|
||||
if (!this._issueWindow) {
|
||||
@@ -203,18 +209,8 @@ export class IssueMainService implements ICommonIssueService {
|
||||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
|
||||
...this.environmentService.sandbox ?
|
||||
|
||||
// Sandbox
|
||||
{
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
} :
|
||||
|
||||
// No Sandbox
|
||||
{
|
||||
nodeIntegration: true
|
||||
}
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -269,18 +265,8 @@ export class IssueMainService implements ICommonIssueService {
|
||||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
|
||||
...this.environmentService.sandbox ?
|
||||
|
||||
// Sandbox
|
||||
{
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
} :
|
||||
|
||||
// No Sandbox
|
||||
{
|
||||
nodeIntegration: true
|
||||
}
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -288,7 +274,6 @@ export class IssueMainService implements ICommonIssueService {
|
||||
|
||||
const windowConfiguration = {
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
windowId: this._processExplorerWindow.id,
|
||||
userEnv: this.userEnv,
|
||||
machineId: this.machineId,
|
||||
@@ -416,7 +401,6 @@ export class IssueMainService implements ICommonIssueService {
|
||||
|
||||
const windowConfiguration = {
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
windowId: this._issueWindow.id,
|
||||
machineId: this.machineId,
|
||||
userEnv: this.userEnv,
|
||||
@@ -452,7 +436,7 @@ function toWindowUrl<T>(modulePathToHtml: string, windowConfiguration: T): strin
|
||||
}
|
||||
|
||||
return FileAccess
|
||||
.asBrowserUri(modulePathToHtml, require)
|
||||
._asCodeFileUri(modulePathToHtml, require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true);
|
||||
}
|
||||
|
||||
@@ -383,14 +383,9 @@ function printWhenExplanation(when: ContextKeyExpression | undefined): string {
|
||||
}
|
||||
|
||||
function printSourceExplanation(kb: ResolvedKeybindingItem): string {
|
||||
if (kb.isDefault) {
|
||||
if (kb.extensionId) {
|
||||
return `built-in extension ${kb.extensionId}`;
|
||||
}
|
||||
return `built-in`;
|
||||
}
|
||||
if (kb.extensionId) {
|
||||
return `user extension ${kb.extensionId}`;
|
||||
}
|
||||
return `user`;
|
||||
return (
|
||||
kb.extensionId
|
||||
? (kb.isBuiltinExtension ? `built-in extension ${kb.extensionId}` : `user extension ${kb.extensionId}`)
|
||||
: (kb.isDefault ? `built-in` : `user`)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface IKeybindingItem {
|
||||
weight1: number;
|
||||
weight2: number;
|
||||
extensionId: string | null;
|
||||
isBuiltinExtension: boolean;
|
||||
}
|
||||
|
||||
export interface IKeybindings {
|
||||
@@ -53,6 +54,7 @@ export interface IKeybindingRule2 {
|
||||
weight: number;
|
||||
when: ContextKeyExpression | undefined;
|
||||
extensionId?: string;
|
||||
isBuiltinExtension?: boolean;
|
||||
}
|
||||
|
||||
export const enum KeybindingWeight {
|
||||
@@ -164,7 +166,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
|
||||
when: rule.when,
|
||||
weight1: rule.weight,
|
||||
weight2: 0,
|
||||
extensionId: rule.extensionId || null
|
||||
extensionId: rule.extensionId || null,
|
||||
isBuiltinExtension: rule.isBuiltinExtension || false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -223,7 +226,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
|
||||
when: when,
|
||||
weight1: weight1,
|
||||
weight2: weight2,
|
||||
extensionId: null
|
||||
extensionId: null,
|
||||
isBuiltinExtension: false
|
||||
});
|
||||
this._cachedMergedKeybindings = null;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ export class ResolvedKeybindingItem {
|
||||
public readonly when: ContextKeyExpression | undefined;
|
||||
public readonly isDefault: boolean;
|
||||
public readonly extensionId: string | null;
|
||||
public readonly isBuiltinExtension: boolean;
|
||||
|
||||
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null) {
|
||||
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null, isBuiltinExtension: boolean) {
|
||||
this.resolvedKeybinding = resolvedKeybinding;
|
||||
this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : [];
|
||||
this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false);
|
||||
@@ -28,6 +29,7 @@ export class ResolvedKeybindingItem {
|
||||
this.when = when;
|
||||
this.isDefault = isDefault;
|
||||
this.extensionId = extensionId;
|
||||
this.isBuiltinExtension = isBuiltinExtension;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,8 @@ suite('AbstractKeybindingService', () => {
|
||||
null,
|
||||
when,
|
||||
true,
|
||||
null
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ suite('KeybindingResolver', () => {
|
||||
commandArgs,
|
||||
when,
|
||||
isDefault,
|
||||
null
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export const enum DispatchConfig {
|
||||
Code,
|
||||
KeyCode
|
||||
}
|
||||
|
||||
export function getDispatchConfig(configurationService: IConfigurationService): DispatchConfig {
|
||||
const keyboard = configurationService.getValue('keyboard');
|
||||
const r = (keyboard ? (<any>keyboard).dispatch : null);
|
||||
return (r === 'keyCode' ? DispatchConfig.KeyCode : DispatchConfig.Code);
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ScanCode, ScanCodeUtils } from 'vs/base/common/scanCode';
|
||||
import { IKeyboardMapper } from 'vs/platform/keyboardLayout/common/keyboardMapper';
|
||||
import { DispatchConfig } from 'vs/platform/keyboardLayout/common/dispatchConfig';
|
||||
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export const IKeyboardLayoutService = createDecorator<IKeyboardLayoutService>('keyboardLayoutService');
|
||||
|
||||
export interface IWindowsKeyMapping {
|
||||
vkey: string;
|
||||
value: string;
|
||||
withShift: string;
|
||||
withAltGr: string;
|
||||
withShiftAltGr: string;
|
||||
}
|
||||
export interface IWindowsKeyboardMapping {
|
||||
[code: string]: IWindowsKeyMapping;
|
||||
}
|
||||
export interface ILinuxKeyMapping {
|
||||
value: string;
|
||||
withShift: string;
|
||||
withAltGr: string;
|
||||
withShiftAltGr: string;
|
||||
}
|
||||
export interface ILinuxKeyboardMapping {
|
||||
[code: string]: ILinuxKeyMapping;
|
||||
}
|
||||
export interface IMacKeyMapping {
|
||||
value: string;
|
||||
valueIsDeadKey: boolean;
|
||||
withShift: string;
|
||||
withShiftIsDeadKey: boolean;
|
||||
withAltGr: string;
|
||||
withAltGrIsDeadKey: boolean;
|
||||
withShiftAltGr: string;
|
||||
withShiftAltGrIsDeadKey: boolean;
|
||||
}
|
||||
export interface IMacKeyboardMapping {
|
||||
[code: string]: IMacKeyMapping;
|
||||
}
|
||||
|
||||
export type IMacLinuxKeyMapping = IMacKeyMapping | ILinuxKeyMapping;
|
||||
export type IMacLinuxKeyboardMapping = IMacKeyboardMapping | ILinuxKeyboardMapping;
|
||||
export type IKeyboardMapping = IWindowsKeyboardMapping | ILinuxKeyboardMapping | IMacKeyboardMapping;
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IKeyboardLayoutInfo" : {
|
||||
"name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"id": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"text": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
export interface IWindowsKeyboardLayoutInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IKeyboardLayoutInfo" : {
|
||||
"model" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"layout": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"variant": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"options": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"rules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
export interface ILinuxKeyboardLayoutInfo {
|
||||
model: string;
|
||||
layout: string;
|
||||
variant: string;
|
||||
options: string;
|
||||
rules: string;
|
||||
}
|
||||
|
||||
/* __GDPR__FRAGMENT__
|
||||
"IKeyboardLayoutInfo" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"lang": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"localizedName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
export interface IMacKeyboardLayoutInfo {
|
||||
id: string;
|
||||
lang: string;
|
||||
localizedName?: string;
|
||||
}
|
||||
|
||||
export type IKeyboardLayoutInfo = (IWindowsKeyboardLayoutInfo | ILinuxKeyboardLayoutInfo | IMacKeyboardLayoutInfo) & { isUserKeyboardLayout?: boolean; isUSStandard?: true };
|
||||
|
||||
export interface IKeyboardLayoutService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onDidChangeKeyboardLayout: Event<void>;
|
||||
|
||||
getRawKeyboardMapping(): IKeyboardMapping | null;
|
||||
getCurrentKeyboardLayout(): IKeyboardLayoutInfo | null;
|
||||
getAllKeyboardLayouts(): IKeyboardLayoutInfo[];
|
||||
getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper;
|
||||
validateCurrentKeyboardMapping(keyboardEvent: IKeyboardEvent): void;
|
||||
}
|
||||
|
||||
export function areKeyboardLayoutsEqual(a: IKeyboardLayoutInfo | null, b: IKeyboardLayoutInfo | null): boolean {
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((<IWindowsKeyboardLayoutInfo>a).name && (<IWindowsKeyboardLayoutInfo>b).name && (<IWindowsKeyboardLayoutInfo>a).name === (<IWindowsKeyboardLayoutInfo>b).name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((<IMacKeyboardLayoutInfo>a).id && (<IMacKeyboardLayoutInfo>b).id && (<IMacKeyboardLayoutInfo>a).id === (<IMacKeyboardLayoutInfo>b).id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((<ILinuxKeyboardLayoutInfo>a).model &&
|
||||
(<ILinuxKeyboardLayoutInfo>b).model &&
|
||||
(<ILinuxKeyboardLayoutInfo>a).model === (<ILinuxKeyboardLayoutInfo>b).model &&
|
||||
(<ILinuxKeyboardLayoutInfo>a).layout === (<ILinuxKeyboardLayoutInfo>b).layout
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseKeyboardLayoutDescription(layout: IKeyboardLayoutInfo | null): { label: string, description: string } {
|
||||
if (!layout) {
|
||||
return { label: '', description: '' };
|
||||
}
|
||||
|
||||
if ((<IWindowsKeyboardLayoutInfo>layout).name) {
|
||||
// windows
|
||||
let windowsLayout = <IWindowsKeyboardLayoutInfo>layout;
|
||||
return {
|
||||
label: windowsLayout.text,
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
|
||||
if ((<IMacKeyboardLayoutInfo>layout).id) {
|
||||
let macLayout = <IMacKeyboardLayoutInfo>layout;
|
||||
if (macLayout.localizedName) {
|
||||
return {
|
||||
label: macLayout.localizedName,
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
|
||||
if (/^com\.apple\.keylayout\./.test(macLayout.id)) {
|
||||
return {
|
||||
label: macLayout.id.replace(/^com\.apple\.keylayout\./, '').replace(/-/, ' '),
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
if (/^.*inputmethod\./.test(macLayout.id)) {
|
||||
return {
|
||||
label: macLayout.id.replace(/^.*inputmethod\./, '').replace(/[-\.]/, ' '),
|
||||
description: `Input Method (${macLayout.lang})`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: macLayout.lang,
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
|
||||
let linuxLayout = <ILinuxKeyboardLayoutInfo>layout;
|
||||
|
||||
return {
|
||||
label: linuxLayout.layout,
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
|
||||
export function getKeyboardLayoutId(layout: IKeyboardLayoutInfo): string {
|
||||
if ((<IWindowsKeyboardLayoutInfo>layout).name) {
|
||||
return (<IWindowsKeyboardLayoutInfo>layout).name;
|
||||
}
|
||||
|
||||
if ((<IMacKeyboardLayoutInfo>layout).id) {
|
||||
return (<IMacKeyboardLayoutInfo>layout).id;
|
||||
}
|
||||
|
||||
return (<ILinuxKeyboardLayoutInfo>layout).layout;
|
||||
}
|
||||
|
||||
function windowsKeyMappingEquals(a: IWindowsKeyMapping, b: IWindowsKeyMapping): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
a.vkey === b.vkey
|
||||
&& a.value === b.value
|
||||
&& a.withShift === b.withShift
|
||||
&& a.withAltGr === b.withAltGr
|
||||
&& a.withShiftAltGr === b.withShiftAltGr
|
||||
);
|
||||
}
|
||||
|
||||
export function windowsKeyboardMappingEquals(a: IWindowsKeyboardMapping | null, b: IWindowsKeyboardMapping | null): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
for (let scanCode = 0; scanCode < ScanCode.MAX_VALUE; scanCode++) {
|
||||
const strScanCode = ScanCodeUtils.toString(scanCode);
|
||||
const aEntry = a[strScanCode];
|
||||
const bEntry = b[strScanCode];
|
||||
if (!windowsKeyMappingEquals(aEntry, bEntry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function macLinuxKeyMappingEquals(a: IMacLinuxKeyMapping, b: IMacLinuxKeyMapping): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
a.value === b.value
|
||||
&& a.withShift === b.withShift
|
||||
&& a.withAltGr === b.withAltGr
|
||||
&& a.withShiftAltGr === b.withShiftAltGr
|
||||
);
|
||||
}
|
||||
|
||||
export function macLinuxKeyboardMappingEquals(a: IMacLinuxKeyboardMapping | null, b: IMacLinuxKeyboardMapping | null): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
for (let scanCode = 0; scanCode < ScanCode.MAX_VALUE; scanCode++) {
|
||||
const strScanCode = ScanCodeUtils.toString(scanCode);
|
||||
const aEntry = a[strScanCode];
|
||||
const bEntry = b[strScanCode];
|
||||
if (!macLinuxKeyMappingEquals(aEntry, bEntry)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IKeyboardLayoutInfo, IKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IKeyboardLayoutData {
|
||||
keyboardLayoutInfo: IKeyboardLayoutInfo;
|
||||
keyboardMapping: IKeyboardMapping;
|
||||
}
|
||||
|
||||
export interface IKeyboardLayoutMainService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly onDidChangeKeyboardLayout: Event<IKeyboardLayoutData>;
|
||||
getKeyboardLayoutData(): Promise<IKeyboardLayoutData>;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { ScanCodeBinding } from 'vs/base/common/scanCode';
|
||||
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export interface IKeyboardMapper {
|
||||
dumpDebugInfo(): string;
|
||||
resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
|
||||
resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
|
||||
resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[];
|
||||
}
|
||||
|
||||
export class CachedKeyboardMapper implements IKeyboardMapper {
|
||||
|
||||
private _actual: IKeyboardMapper;
|
||||
private _cache: Map<string, ResolvedKeybinding[]>;
|
||||
|
||||
constructor(actual: IKeyboardMapper) {
|
||||
this._actual = actual;
|
||||
this._cache = new Map<string, ResolvedKeybinding[]>();
|
||||
}
|
||||
|
||||
public dumpDebugInfo(): string {
|
||||
return this._actual.dumpDebugInfo();
|
||||
}
|
||||
|
||||
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] {
|
||||
const hashCode = keybinding.getHashCode();
|
||||
const resolved = this._cache.get(hashCode);
|
||||
if (!resolved) {
|
||||
const r = this._actual.resolveKeybinding(keybinding);
|
||||
this._cache.set(hashCode, r);
|
||||
return r;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {
|
||||
return this._actual.resolveKeyboardEvent(keyboardEvent);
|
||||
}
|
||||
|
||||
public resolveUserBinding(parts: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] {
|
||||
return this._actual.resolveUserBinding(parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeyboardLayoutData, IKeyboardLayoutMainService as ICommonKeyboardLayoutMainService } from 'vs/platform/keyboardLayout/common/keyboardLayoutMainService';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as nativeKeymap from 'native-keymap';
|
||||
|
||||
export const IKeyboardLayoutMainService = createDecorator<IKeyboardLayoutMainService>('keyboardLayoutMainService');
|
||||
|
||||
export interface IKeyboardLayoutMainService extends ICommonKeyboardLayoutMainService { }
|
||||
|
||||
export class KeyboardLayoutMainService extends Disposable implements ICommonKeyboardLayoutMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeKeyboardLayout = this._register(new Emitter<IKeyboardLayoutData>());
|
||||
readonly onDidChangeKeyboardLayout = this._onDidChangeKeyboardLayout.event;
|
||||
|
||||
private _initPromise: Promise<void> | null;
|
||||
private _keyboardLayoutData: IKeyboardLayoutData | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._initPromise = null;
|
||||
this._keyboardLayoutData = null;
|
||||
}
|
||||
|
||||
private _initialize(): Promise<void> {
|
||||
if (!this._initPromise) {
|
||||
this._initPromise = this._doInitialize();
|
||||
}
|
||||
return this._initPromise;
|
||||
}
|
||||
|
||||
private async _doInitialize(): Promise<void> {
|
||||
const nativeKeymapMod = await import('native-keymap');
|
||||
|
||||
this._keyboardLayoutData = readKeyboardLayoutData(nativeKeymapMod);
|
||||
nativeKeymapMod.onDidChangeKeyboardLayout(() => {
|
||||
this._keyboardLayoutData = readKeyboardLayoutData(nativeKeymapMod);
|
||||
this._onDidChangeKeyboardLayout.fire(this._keyboardLayoutData);
|
||||
});
|
||||
}
|
||||
|
||||
public async getKeyboardLayoutData(): Promise<IKeyboardLayoutData> {
|
||||
await this._initialize();
|
||||
return this._keyboardLayoutData!;
|
||||
}
|
||||
}
|
||||
|
||||
function readKeyboardLayoutData(nativeKeymapMod: typeof nativeKeymap): IKeyboardLayoutData {
|
||||
const keyboardMapping = nativeKeymapMod.getKeyMap();
|
||||
const keyboardLayoutInfo = nativeKeymapMod.getCurrentKeyboardLayout();
|
||||
return { keyboardMapping, keyboardLayoutInfo };
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/node/launch';
|
||||
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const ID = 'launchMainService';
|
||||
export const ILaunchMainService = createDecorator<ILaunchMainService>(ID);
|
||||
@@ -33,14 +35,14 @@ export interface IRemoteDiagnosticOptions {
|
||||
includeWorkspaceMetadata?: boolean;
|
||||
}
|
||||
|
||||
function parseOpenUrl(args: NativeParsedArgs): URI[] {
|
||||
function parseOpenUrl(args: NativeParsedArgs): { uri: URI, url: string }[] {
|
||||
if (args['open-url'] && args._urls && args._urls.length > 0) {
|
||||
// --open-url must contain -- followed by the url(s)
|
||||
// process.argv is used over args._ as args._ are resolved to file paths at this point
|
||||
return coalesce(args._urls
|
||||
.map(url => {
|
||||
try {
|
||||
return URI.parse(url);
|
||||
return { uri: URI.parse(url), url };
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
@@ -99,8 +101,8 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
|
||||
// Make sure a window is open, ready to receive the url event
|
||||
whenWindowReady.then(() => {
|
||||
for (const url of urlsToOpen) {
|
||||
this.urlService.open(url);
|
||||
for (const { uri, url } of urlsToOpen) {
|
||||
this.urlService.open(uri, { originalUrl: url });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -112,7 +114,7 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
}
|
||||
|
||||
private startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
|
||||
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
const context = isLaunchedFromCli(userEnv) ? OpenContext.CLI : OpenContext.DESKTOP;
|
||||
let usedWindows: ICodeWindow[] = [];
|
||||
|
||||
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
|
||||
@@ -138,7 +140,7 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
|
||||
// Otherwise check for settings
|
||||
else {
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
|
||||
const openWithoutArgumentsInNewWindowConfig = windowConfig?.openWithoutArgumentsInNewWindow || 'default' /* default */;
|
||||
switch (openWithoutArgumentsInNewWindowConfig) {
|
||||
case 'on':
|
||||
@@ -247,7 +249,7 @@ export class LaunchMainService implements ILaunchMainService {
|
||||
folders: options.includeWorkspaceMetadata ? this.getFolderURIs(window) : undefined
|
||||
};
|
||||
|
||||
window.sendWhenReady('vscode:getDiagnosticInfo', { replyChannel, args });
|
||||
window.sendWhenReady('vscode:getDiagnosticInfo', CancellationToken.None, { replyChannel, args });
|
||||
|
||||
ipcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => {
|
||||
// No data is returned if getting the connection fails.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcMain as ipc, app, BrowserWindow } from 'electron';
|
||||
import { ipcMain, app, BrowserWindow } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -371,7 +371,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
// Only reload when the window has not vetoed this
|
||||
const veto = await this.unload(window, UnloadReason.RELOAD);
|
||||
if (!veto) {
|
||||
window.reload(undefined, cli);
|
||||
window.reload(cli);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,11 +437,11 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
const okChannel = `vscode:ok${oneTimeEventToken}`;
|
||||
const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
|
||||
|
||||
ipc.once(okChannel, () => {
|
||||
ipcMain.once(okChannel, () => {
|
||||
resolve(false); // no veto
|
||||
});
|
||||
|
||||
ipc.once(cancelChannel, () => {
|
||||
ipcMain.once(cancelChannel, () => {
|
||||
resolve(true); // veto
|
||||
});
|
||||
|
||||
@@ -468,7 +468,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
|
||||
const replyChannel = `vscode:reply${oneTimeEventToken}`;
|
||||
|
||||
ipc.once(replyChannel, () => resolve());
|
||||
ipcMain.once(replyChannel, () => resolve());
|
||||
|
||||
window.send('vscode:onWillUnload', { replyChannel, reason });
|
||||
});
|
||||
|
||||
@@ -118,14 +118,15 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
|
||||
return result;
|
||||
}
|
||||
|
||||
export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
|
||||
export const openModeSettingKey = 'workbench.list.openMode';
|
||||
export const horizontalScrollingKey = 'workbench.list.horizontalScrolling';
|
||||
export const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';
|
||||
export const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation';
|
||||
const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
|
||||
const openModeSettingKey = 'workbench.list.openMode';
|
||||
const horizontalScrollingKey = 'workbench.list.horizontalScrolling';
|
||||
const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';
|
||||
const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation';
|
||||
const treeIndentKey = 'workbench.tree.indent';
|
||||
const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides';
|
||||
const listSmoothScrolling = 'workbench.list.smoothScrolling';
|
||||
const treeExpandMode = 'workbench.tree.expandMode';
|
||||
|
||||
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
|
||||
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
|
||||
@@ -444,11 +445,11 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
private readonly openOnFocus: boolean;
|
||||
private openOnSingleClick: boolean;
|
||||
|
||||
private readonly _onDidOpen = new Emitter<IOpenEvent<T | null>>();
|
||||
readonly onDidOpen: Event<IOpenEvent<T | null>> = this._onDidOpen.event;
|
||||
private readonly _onDidOpen = this._register(new Emitter<IOpenEvent<T | undefined>>());
|
||||
readonly onDidOpen: Event<IOpenEvent<T | undefined>> = this._onDidOpen.event;
|
||||
|
||||
constructor(
|
||||
private readonly widget: ListWidget,
|
||||
protected readonly widget: ListWidget,
|
||||
options?: IResourceNavigatorOptions
|
||||
) {
|
||||
super();
|
||||
@@ -456,8 +457,8 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
this.openOnFocus = options?.openOnFocus ?? false;
|
||||
|
||||
this._register(Event.filter(this.widget.onDidChangeSelection, e => e.browserEvent instanceof KeyboardEvent)(e => this.onSelectionFromKeyboard(e)));
|
||||
this._register(this.widget.onPointer((e: { browserEvent: MouseEvent }) => this.onPointer(e.browserEvent)));
|
||||
this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent }) => this.onMouseDblClick(e.browserEvent)));
|
||||
this._register(this.widget.onPointer((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onPointer(e.element, e.browserEvent)));
|
||||
this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent)));
|
||||
|
||||
if (this.openOnFocus) {
|
||||
this._register(Event.filter(this.widget.onDidChangeFocus, e => e.browserEvent instanceof KeyboardEvent)(e => this.onFocusFromKeyboard(e)));
|
||||
@@ -478,10 +479,10 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
this.widget.setSelection(focus, event.browserEvent);
|
||||
|
||||
const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true;
|
||||
const pinned = false;
|
||||
const pinned = !preserveFocus;
|
||||
const sideBySide = false;
|
||||
|
||||
this._open(preserveFocus, pinned, sideBySide, event.browserEvent);
|
||||
this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent);
|
||||
}
|
||||
|
||||
private onSelectionFromKeyboard(event: ITreeEvent<any>): void {
|
||||
@@ -490,13 +491,13 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
}
|
||||
|
||||
const preserveFocus = typeof (event.browserEvent as SelectionKeyboardEvent).preserveFocus === 'boolean' ? (event.browserEvent as SelectionKeyboardEvent).preserveFocus! : true;
|
||||
const pinned = false;
|
||||
const pinned = !preserveFocus;
|
||||
const sideBySide = false;
|
||||
|
||||
this._open(preserveFocus, pinned, sideBySide, event.browserEvent);
|
||||
this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent);
|
||||
}
|
||||
|
||||
private onPointer(browserEvent: MouseEvent): void {
|
||||
private onPointer(element: T | undefined, browserEvent: MouseEvent): void {
|
||||
if (!this.openOnSingleClick) {
|
||||
return;
|
||||
}
|
||||
@@ -512,10 +513,10 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
const pinned = isMiddleClick;
|
||||
const sideBySide = browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey;
|
||||
|
||||
this._open(preserveFocus, pinned, sideBySide, browserEvent);
|
||||
this._open(element, preserveFocus, pinned, sideBySide, browserEvent);
|
||||
}
|
||||
|
||||
private onMouseDblClick(browserEvent?: MouseEvent): void {
|
||||
private onMouseDblClick(element: T | undefined, browserEvent?: MouseEvent): void {
|
||||
if (!browserEvent) {
|
||||
return;
|
||||
}
|
||||
@@ -524,10 +525,14 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
const pinned = true;
|
||||
const sideBySide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);
|
||||
|
||||
this._open(preserveFocus, pinned, sideBySide, browserEvent);
|
||||
this._open(element, preserveFocus, pinned, sideBySide, browserEvent);
|
||||
}
|
||||
|
||||
private _open(preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void {
|
||||
private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onDidOpen.fire({
|
||||
editorOptions: {
|
||||
preserveFocus,
|
||||
@@ -535,27 +540,39 @@ abstract class ResourceNavigator<T> extends Disposable {
|
||||
revealIfVisible: true
|
||||
},
|
||||
sideBySide,
|
||||
element: this.widget.getSelection()[0],
|
||||
element,
|
||||
browserEvent
|
||||
});
|
||||
}
|
||||
|
||||
abstract getSelectedElement(): T | undefined;
|
||||
}
|
||||
|
||||
export class ListResourceNavigator<T> extends ResourceNavigator<number> {
|
||||
export class ListResourceNavigator<T> extends ResourceNavigator<T> {
|
||||
|
||||
constructor(
|
||||
list: List<T> | PagedList<T>,
|
||||
protected readonly widget: List<T> | PagedList<T>,
|
||||
options?: IResourceNavigatorOptions
|
||||
) {
|
||||
super(list, options);
|
||||
super(widget, options);
|
||||
}
|
||||
|
||||
getSelectedElement(): T | undefined {
|
||||
return this.widget.getSelectedElements()[0];
|
||||
}
|
||||
}
|
||||
|
||||
class TreeResourceNavigator<T, TFilterData> extends ResourceNavigator<T> {
|
||||
|
||||
constructor(
|
||||
tree: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>,
|
||||
protected readonly widget: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>,
|
||||
options: IResourceNavigatorOptions
|
||||
) {
|
||||
super(tree, options);
|
||||
super(widget, options);
|
||||
}
|
||||
|
||||
getSelectedElement(): T | undefined {
|
||||
return this.widget.getSelection()[0] ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +607,7 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
|
||||
private internals: WorkbenchTreeInternals<any, T, TFilterData>;
|
||||
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
|
||||
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.internals.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -626,7 +643,7 @@ export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilter
|
||||
private internals: WorkbenchTreeInternals<any, T, TFilterData>;
|
||||
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
|
||||
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.internals.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -670,7 +687,7 @@ export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<T
|
||||
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
|
||||
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
|
||||
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.internals.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -715,7 +732,7 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
|
||||
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
|
||||
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
|
||||
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.internals.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -757,7 +774,7 @@ export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> e
|
||||
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
|
||||
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
|
||||
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.internals.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
@@ -831,7 +848,8 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
|
||||
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService),
|
||||
additionalScrollHeight,
|
||||
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
|
||||
expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick'
|
||||
expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick',
|
||||
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick')
|
||||
} as TOptions
|
||||
};
|
||||
}
|
||||
@@ -847,7 +865,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
private styler: IDisposable | undefined;
|
||||
private navigator: TreeResourceNavigator<T, TFilterData>;
|
||||
|
||||
get onDidOpen(): Event<IOpenEvent<T | null>> { return this.navigator.onDidOpen; }
|
||||
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
|
||||
|
||||
constructor(
|
||||
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
|
||||
@@ -933,6 +951,9 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
if (e.affectsConfiguration(openModeSettingKey)) {
|
||||
newOptions = { ...newOptions, expandOnlyOnDoubleClick: configurationService.getValue(openModeSettingKey) === 'doubleClick' };
|
||||
}
|
||||
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {
|
||||
newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };
|
||||
}
|
||||
if (Object.keys(newOptions).length > 0) {
|
||||
tree.updateOptions(newOptions);
|
||||
}
|
||||
@@ -1036,6 +1057,12 @@ configurationRegistry.registerConfiguration({
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
markdownDescription: localize('automatic keyboard navigation setting', "Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to `false`, keyboard navigation is only triggered when executing the `list.toggleKeyboardNavigation` command, for which you can assign a keyboard shortcut.")
|
||||
},
|
||||
[treeExpandMode]: {
|
||||
type: 'string',
|
||||
enum: ['singleClick', 'doubleClick'],
|
||||
default: 'singleClick',
|
||||
description: localize('expand mode', "Controls how tree folders are expanded when clicking the folder names."),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ILogService, LogLevel, AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files';
|
||||
import { ByteSize, FileOperationError, FileOperationResult, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { dirname, joinPath, basename } from 'vs/base/common/resources';
|
||||
@@ -13,7 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
|
||||
const MAX_FILE_SIZE = 1024 * 1024 * 5;
|
||||
const MAX_FILE_SIZE = 5 * ByteSize.MB;
|
||||
|
||||
export class FileLogService extends AbstractLogService implements ILogService {
|
||||
|
||||
|
||||
@@ -419,23 +419,43 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel {
|
||||
return LogLevel.Trace;
|
||||
}
|
||||
if (typeof environmentService.logLevel === 'string') {
|
||||
const logLevel = environmentService.logLevel.toLowerCase();
|
||||
switch (logLevel) {
|
||||
case 'trace':
|
||||
return LogLevel.Trace;
|
||||
case 'debug':
|
||||
return LogLevel.Debug;
|
||||
case 'info':
|
||||
return LogLevel.Info;
|
||||
case 'warn':
|
||||
return LogLevel.Warning;
|
||||
case 'error':
|
||||
return LogLevel.Error;
|
||||
case 'critical':
|
||||
return LogLevel.Critical;
|
||||
case 'off':
|
||||
return LogLevel.Off;
|
||||
const logLevel = parseLogLevel(environmentService.logLevel.toLowerCase());
|
||||
if (logLevel !== undefined) {
|
||||
return logLevel;
|
||||
}
|
||||
}
|
||||
return DEFAULT_LOG_LEVEL;
|
||||
}
|
||||
|
||||
export function parseLogLevel(logLevel: string): LogLevel | undefined {
|
||||
switch (logLevel) {
|
||||
case 'trace':
|
||||
return LogLevel.Trace;
|
||||
case 'debug':
|
||||
return LogLevel.Debug;
|
||||
case 'info':
|
||||
return LogLevel.Info;
|
||||
case 'warn':
|
||||
return LogLevel.Warning;
|
||||
case 'error':
|
||||
return LogLevel.Error;
|
||||
case 'critical':
|
||||
return LogLevel.Critical;
|
||||
case 'off':
|
||||
return LogLevel.Off;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function LogLevelToString(logLevel: LogLevel): string {
|
||||
switch (logLevel) {
|
||||
case LogLevel.Trace: return 'trace';
|
||||
case LogLevel.Debug: return 'debug';
|
||||
case LogLevel.Info: return 'info';
|
||||
case LogLevel.Warning: return 'warn';
|
||||
case LogLevel.Error: return 'error';
|
||||
case LogLevel.Critical: return 'critical';
|
||||
case LogLevel.Off: return 'off';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log';
|
||||
import * as spdlog from 'spdlog';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
|
||||
async function createSpdLogLogger(processName: string, logsFolder: string): Promise<spdlog.RotatingLogger | null> {
|
||||
// Do not crash if spdlog cannot be loaded
|
||||
@@ -13,7 +14,7 @@ async function createSpdLogLogger(processName: string, logsFolder: string): Prom
|
||||
const _spdlog = await import('spdlog');
|
||||
_spdlog.setAsyncMode(8192, 500);
|
||||
const logfilePath = path.join(logsFolder, `${processName}.log`);
|
||||
return _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6);
|
||||
return _spdlog.createRotatingLoggerAsync(processName, logfilePath, 5 * ByteSize.MB, 6);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
||||
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const telemetryFrom = 'menu';
|
||||
|
||||
@@ -82,7 +83,7 @@ export class Menubar {
|
||||
this.menubarMenus = Object.create(null);
|
||||
this.keybindings = Object.create(null);
|
||||
|
||||
if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') {
|
||||
if (isMacintosh || getTitleBarStyle(this.configurationService) === 'native') {
|
||||
this.restoreCachedMenubarData();
|
||||
}
|
||||
|
||||
@@ -416,7 +417,7 @@ export class Menubar {
|
||||
|
||||
private shouldDrawMenu(menuId: string): boolean {
|
||||
// We need to draw an empty menu to override the electron default
|
||||
if (!isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
|
||||
if (!isMacintosh && getTitleBarStyle(this.configurationService) === 'custom') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -754,10 +755,10 @@ export class Menubar {
|
||||
|
||||
if (invocation.type === 'commandId') {
|
||||
const runActionPayload: INativeRunActionInWindowRequest = { id: invocation.commandId, from: 'menu' };
|
||||
activeWindow.sendWhenReady('vscode:runAction', runActionPayload);
|
||||
activeWindow.sendWhenReady('vscode:runAction', CancellationToken.None, runActionPayload);
|
||||
} else {
|
||||
const runKeybindingPayload: INativeRunKeybindingInWindowRequest = { userSettingsLabel: invocation.userSettingsLabel };
|
||||
activeWindow.sendWhenReady('vscode:runKeybinding', runKeybindingPayload);
|
||||
activeWindow.sendWhenReady('vscode:runKeybinding', CancellationToken.None, runKeybindingPayload);
|
||||
}
|
||||
} else {
|
||||
this.logService.trace('menubar#runActionInRenderer: no active window found', invocation);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { isMacintosh, isWindows, isRootUser, isLinux } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
@@ -364,7 +364,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
if (isWindows) {
|
||||
isAdmin = (await import('native-is-elevated'))();
|
||||
} else {
|
||||
isAdmin = isRootUser();
|
||||
isAdmin = process.getuid() === 0;
|
||||
}
|
||||
|
||||
return isAdmin;
|
||||
@@ -550,7 +550,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
async reload(windowId: number | undefined, options?: { disableExtensions?: boolean }): Promise<void> {
|
||||
const window = this.windowById(windowId);
|
||||
if (window) {
|
||||
return this.lifecycleMainService.reload(window, options?.disableExtensions ? { _: [], 'disable-extensions': true } : undefined);
|
||||
return this.lifecycleMainService.reload(window, options?.disableExtensions !== undefined ? { _: [], 'disable-extensions': options?.disableExtensions } : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,11 +615,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
const window = this.windowById(windowId);
|
||||
if (window) {
|
||||
const contents = window.win.webContents;
|
||||
if (isMacintosh && window.hasHiddenTitleBarStyle && !window.isFullScreen && !contents.isDevToolsOpened()) {
|
||||
contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647
|
||||
} else {
|
||||
contents.toggleDevTools();
|
||||
}
|
||||
contents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ export interface INotificationActions {
|
||||
/**
|
||||
* Primary actions show up as buttons as part of the message and will close
|
||||
* the notification once clicked.
|
||||
*
|
||||
* Pass `ActionWithMenuAction` for an action that has additional menu actions.
|
||||
*/
|
||||
readonly primary?: ReadonlyArray<IAction>;
|
||||
|
||||
@@ -209,19 +211,13 @@ export interface INotificationHandle {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface IPromptChoice {
|
||||
interface IBasePromptChoice {
|
||||
|
||||
/**
|
||||
* Label to show for the choice to the user.
|
||||
*/
|
||||
readonly label: string;
|
||||
|
||||
/**
|
||||
* Primary choices show up as buttons in the notification below the message.
|
||||
* Secondary choices show up under the gear icon in the header of the notification.
|
||||
*/
|
||||
readonly isSecondary?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to keep the notification open after the choice was selected
|
||||
* by the user. By default, will close the notification upon click.
|
||||
@@ -234,6 +230,28 @@ export interface IPromptChoice {
|
||||
run: () => void;
|
||||
}
|
||||
|
||||
export interface IPromptChoice extends IBasePromptChoice {
|
||||
|
||||
/**
|
||||
* Primary choices show up as buttons in the notification below the message.
|
||||
* Secondary choices show up under the gear icon in the header of the notification.
|
||||
*/
|
||||
readonly isSecondary?: boolean;
|
||||
}
|
||||
|
||||
export interface IPromptChoiceWithMenu extends IPromptChoice {
|
||||
|
||||
/**
|
||||
* Additional choices those will be shown in the dropdown menu for this choice.
|
||||
*/
|
||||
readonly menu: IBasePromptChoice[];
|
||||
|
||||
/**
|
||||
* Menu is not supported on secondary choices
|
||||
*/
|
||||
readonly isSecondary: false | undefined;
|
||||
}
|
||||
|
||||
export interface IPromptOptions extends INotificationProperties {
|
||||
|
||||
/**
|
||||
@@ -327,7 +345,7 @@ export interface INotificationService {
|
||||
*
|
||||
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
|
||||
*/
|
||||
prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle;
|
||||
prompt(severity: Severity, message: string, choices: (IPromptChoice | IPromptChoiceWithMenu)[], options?: IPromptOptions): INotificationHandle;
|
||||
|
||||
/**
|
||||
* Shows a status message in the status area with the provided text.
|
||||
|
||||
@@ -7,6 +7,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { equalsIgnoreCase, startsWithIgnoreCase } from 'vs/base/common/strings';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export const IOpenerService = createDecorator<IOpenerService>('openerService');
|
||||
|
||||
@@ -18,6 +19,11 @@ type OpenInternalOptions = {
|
||||
*/
|
||||
readonly openToSide?: boolean;
|
||||
|
||||
/**
|
||||
* Extra editor options to apply in case an editor is used to open.
|
||||
*/
|
||||
readonly editorOptions?: IEditorOptions;
|
||||
|
||||
/**
|
||||
* Signals that the editor to open was triggered through a user
|
||||
* action, such as keyboard or mouse usage.
|
||||
|
||||
@@ -20,7 +20,7 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire !
|
||||
// Running out of sources
|
||||
if (Object.keys(product).length === 0) {
|
||||
Object.assign(product, {
|
||||
version: '1.51.0-dev',
|
||||
version: '1.52.0-dev',
|
||||
nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
|
||||
nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
|
||||
applicationName: 'code-oss',
|
||||
@@ -32,7 +32,6 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire !
|
||||
extensionAllowedProposedApi: [
|
||||
'ms-vscode.vscode-js-profile-flame',
|
||||
'ms-vscode.vscode-js-profile-table',
|
||||
'ms-vscode.references-view',
|
||||
'ms-vscode.github-browser'
|
||||
],
|
||||
});
|
||||
|
||||
@@ -82,6 +82,7 @@ export interface IProductConfiguration {
|
||||
readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; };
|
||||
readonly extensionKeywords?: { [extension: string]: readonly string[]; };
|
||||
readonly keymapExtensionTips?: readonly string[];
|
||||
readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[]; };
|
||||
|
||||
readonly crashReporter?: {
|
||||
readonly companyName: string;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecyc
|
||||
import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -21,7 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export interface ICommandQuickPick extends IPickerQuickAccessItem {
|
||||
commandId: string;
|
||||
@@ -204,14 +203,9 @@ export class CommandsHistory extends Disposable {
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
super();
|
||||
|
||||
// opt-in to syncing
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_CACHE, version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: CommandsHistory.PREF_KEY_COUNTER, version: 1 });
|
||||
|
||||
this.updateConfiguration();
|
||||
this.load();
|
||||
|
||||
@@ -279,8 +273,8 @@ export class CommandsHistory extends Disposable {
|
||||
const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] };
|
||||
CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value }));
|
||||
|
||||
storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL);
|
||||
storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL);
|
||||
storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number {
|
||||
|
||||
@@ -26,8 +26,12 @@ export interface TunnelOptions {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface TunnelCreationOptions {
|
||||
elevationRequired?: boolean;
|
||||
}
|
||||
|
||||
export interface ITunnelProvider {
|
||||
forwardPort(tunnelOptions: TunnelOptions): Promise<RemoteTunnel> | undefined;
|
||||
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel> | undefined;
|
||||
}
|
||||
|
||||
export interface ITunnelService {
|
||||
@@ -56,8 +60,14 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address:
|
||||
};
|
||||
}
|
||||
|
||||
export const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1'];
|
||||
export function isLocalhost(host: string): boolean {
|
||||
return host === 'localhost' || host === '127.0.0.1';
|
||||
return LOCALHOST_ADDRESSES.indexOf(host) >= 0;
|
||||
}
|
||||
|
||||
export const ALL_INTERFACES_ADDRESSES = ['0.0.0.0', '0:0:0:0:0:0:0:0', '::'];
|
||||
export function isAllInterfaces(host: string): boolean {
|
||||
return ALL_INTERFACES_ADDRESSES.indexOf(host) >= 0;
|
||||
}
|
||||
|
||||
function getOtherLocalhost(host: string): string | undefined {
|
||||
@@ -198,6 +208,10 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
}
|
||||
|
||||
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
|
||||
|
||||
protected isPortPrivileged(port: number): boolean {
|
||||
return port < 1024;
|
||||
}
|
||||
}
|
||||
|
||||
export class TunnelService extends AbstractTunnelService {
|
||||
@@ -209,7 +223,10 @@ export class TunnelService extends AbstractTunnelService {
|
||||
}
|
||||
|
||||
if (this._tunnelProvider) {
|
||||
const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } });
|
||||
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
|
||||
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
|
||||
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
|
||||
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
|
||||
if (tunnel) {
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,10 @@ export class TunnelService extends AbstractTunnelService {
|
||||
}
|
||||
|
||||
if (this._tunnelProvider) {
|
||||
const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort });
|
||||
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
|
||||
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
|
||||
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
|
||||
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
|
||||
if (tunnel) {
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isUUID, generateUuid } from 'vs/base/common/uuid';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: {
|
||||
get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined,
|
||||
store: (key: string, value: string, scope: StorageScope) => void
|
||||
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
|
||||
} | undefined): Promise<string> {
|
||||
let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null : null;
|
||||
if (uuid) {
|
||||
@@ -34,7 +34,7 @@ export async function getServiceMachineId(environmentService: IEnvironmentServic
|
||||
}
|
||||
}
|
||||
if (storageService) {
|
||||
storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL);
|
||||
storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
|
||||
@@ -16,15 +16,7 @@ import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
|
||||
|
||||
export class BrowserStorageService extends Disposable implements IStorageService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
readonly onWillSaveState = this._onWillSaveState.event;
|
||||
export class BrowserStorageService extends AbstractStorageService {
|
||||
|
||||
private globalStorage: IStorage | undefined;
|
||||
private workspaceStorage: IStorage | undefined;
|
||||
@@ -40,6 +32,10 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */));
|
||||
private runWhenIdleDisposable: IDisposable | undefined = undefined;
|
||||
|
||||
get hasPendingUpdate(): boolean {
|
||||
return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
@@ -66,13 +62,13 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
|
||||
this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService));
|
||||
this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase));
|
||||
this._register(this.workspaceStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.WORKSPACE })));
|
||||
this._register(this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)));
|
||||
|
||||
// Global Storage
|
||||
this.globalStorageFile = joinPath(stateRoot, 'global.json');
|
||||
this.globalStorageDatabase = this._register(new FileStorageDatabase(this.globalStorageFile, true /* watch for external changes */, this.fileService));
|
||||
this.globalStorage = this._register(new Storage(this.globalStorageDatabase));
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL })));
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
|
||||
// Init both
|
||||
await Promise.all([
|
||||
@@ -122,11 +118,16 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
return this.getStorage(scope).getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
return this.getStorage(scope).set(key, value);
|
||||
=======
|
||||
protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
||||
this.getStorage(scope).set(key, value);
|
||||
>>>>>>> e4a830e9b7ca039c7c70697786d29f5b6679d775
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
this.getStorage(scope).delete(key);
|
||||
}
|
||||
|
||||
@@ -149,6 +150,13 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
throw new Error('Migrating storage is currently unsupported in Web');
|
||||
}
|
||||
|
||||
protected async doFlush(): Promise<void> {
|
||||
await Promise.all([
|
||||
this.getStorage(StorageScope.GLOBAL).whenFlushed(),
|
||||
this.getStorage(StorageScope.WORKSPACE).whenFlushed()
|
||||
]);
|
||||
}
|
||||
|
||||
private doFlushWhenIdle(): void {
|
||||
|
||||
// Dispose any previous idle runner
|
||||
@@ -175,14 +183,6 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
});
|
||||
}
|
||||
|
||||
get hasPendingUpdate(): boolean {
|
||||
return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate);
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// We explicitly do not close our DBs because writing data onBeforeUnload()
|
||||
// can result in unexpected results. Namely, it seems that - even though this
|
||||
@@ -195,10 +195,6 @@ export class BrowserStorageService extends Disposable implements IStorageService
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
isNew(scope: StorageScope): boolean {
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.runWhenIdleDisposable);
|
||||
this.runWhenIdleDisposable = undefined;
|
||||
|
||||
@@ -4,18 +4,27 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter, PauseableEmitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export const IS_NEW_KEY = '__$__isNewStorageMarker';
|
||||
const TARGET_KEY = '__$__targetStorageMarker';
|
||||
|
||||
export const IStorageService = createDecorator<IStorageService>('storageService');
|
||||
|
||||
export enum WillSaveStateReason {
|
||||
NONE = 0,
|
||||
SHUTDOWN = 1
|
||||
|
||||
/**
|
||||
* No specific reason to save state.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* A hint that the workbench is about to shutdown.
|
||||
*/
|
||||
SHUTDOWN
|
||||
}
|
||||
|
||||
export interface IWillSaveStateEvent {
|
||||
@@ -29,7 +38,12 @@ export interface IStorageService {
|
||||
/**
|
||||
* Emitted whenever data is updated or deleted.
|
||||
*/
|
||||
readonly onDidChangeStorage: Event<IWorkspaceStorageChangeEvent>;
|
||||
readonly onDidChangeValue: Event<IStorageValueChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted whenever target of a storage entry changes.
|
||||
*/
|
||||
readonly onDidChangeTarget: Event<IStorageTargetChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the storage is about to persist. This is the right time
|
||||
@@ -48,44 +62,53 @@ export interface IStorageService {
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
* the provided `defaultValue` if the element is `null` or `undefined`.
|
||||
*
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
* @param scope allows to define the scope of the storage operation
|
||||
* to either the current workspace only or all workspaces.
|
||||
*/
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a boolean.
|
||||
* the provided `defaultValue` if the element is `null` or `undefined`.
|
||||
* The element will be converted to a `boolean`.
|
||||
*
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
* @param scope allows to define the scope of the storage operation
|
||||
* to either the current workspace only or all workspaces.
|
||||
*/
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a number using parseInt with a base of 10.
|
||||
* the provided `defaultValue` if the element is `null` or `undefined`.
|
||||
* The element will be converted to a `number` using `parseInt` with a
|
||||
* base of `10`.
|
||||
*
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
* @param scope allows to define the scope of the storage operation
|
||||
* to either the current workspace only or all workspaces.
|
||||
*/
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
||||
|
||||
/**
|
||||
* Store a value under the given key to storage. The value will be converted to a string.
|
||||
* Storing either undefined or null will remove the entry under the key.
|
||||
* Store a value under the given key to storage. The value will be
|
||||
* converted to a `string`. Storing either `undefined` or `null` will
|
||||
* remove the entry under the key.
|
||||
*
|
||||
* The scope argument allows to define the scope of the storage
|
||||
* operation to either the current workspace only or all workspaces.
|
||||
* @param scope allows to define the scope of the storage operation
|
||||
* to either the current workspace only or all workspaces.
|
||||
*
|
||||
* @param target allows to define the target of the storage operation
|
||||
* to either the current machine or user.
|
||||
*/
|
||||
<<<<<<< HEAD
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> | void;
|
||||
=======
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void;
|
||||
>>>>>>> e4a830e9b7ca039c7c70697786d29f5b6679d775
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
@@ -95,6 +118,22 @@ export interface IStorageService {
|
||||
*/
|
||||
remove(key: string, scope: StorageScope): void;
|
||||
|
||||
/**
|
||||
* Returns all the keys used in the storage for the provided `scope`
|
||||
* and `target`.
|
||||
*
|
||||
* Note: this will NOT return all keys stored in the storage layer.
|
||||
* Some keys may not have an associated `StorageTarget` and thus
|
||||
* will be excluded from the results.
|
||||
*
|
||||
* @param scope allows to define the scope for the keys
|
||||
* to either the current workspace only or all workspaces.
|
||||
*
|
||||
* @param target allows to define the target for the keys
|
||||
* to either the current machine or user.
|
||||
*/
|
||||
keys(scope: StorageScope, target: StorageTarget): string[];
|
||||
|
||||
/**
|
||||
* Log the contents of the storage to the console.
|
||||
*/
|
||||
@@ -108,16 +147,18 @@ export interface IStorageService {
|
||||
/**
|
||||
* Whether the storage for the given scope was created during this session or
|
||||
* existed before.
|
||||
*
|
||||
*/
|
||||
isNew(scope: StorageScope): boolean;
|
||||
|
||||
/**
|
||||
* Allows to flush state, e.g. in cases where a shutdown is
|
||||
* imminent. This will send out the onWillSaveState to ask
|
||||
* imminent. This will send out the `onWillSaveState` to ask
|
||||
* everyone for latest state.
|
||||
*
|
||||
* @returns a `Promise` that can be awaited on when all updates
|
||||
* to the underlying storage have been flushed.
|
||||
*/
|
||||
flush(): void;
|
||||
flush(): Promise<void>;
|
||||
}
|
||||
|
||||
export const enum StorageScope {
|
||||
@@ -133,21 +174,247 @@ export const enum StorageScope {
|
||||
WORKSPACE
|
||||
}
|
||||
|
||||
export interface IWorkspaceStorageChangeEvent {
|
||||
export const enum StorageTarget {
|
||||
|
||||
/**
|
||||
* The stored data is user specific and applies across machines.
|
||||
*/
|
||||
USER,
|
||||
|
||||
/**
|
||||
* The stored data is machine specific.
|
||||
*/
|
||||
MACHINE
|
||||
}
|
||||
|
||||
export interface IStorageValueChangeEvent {
|
||||
|
||||
/**
|
||||
* The scope for the storage entry that changed
|
||||
* or was removed.
|
||||
*/
|
||||
readonly scope: StorageScope;
|
||||
|
||||
/**
|
||||
* The `key` of the storage entry that was changed
|
||||
* or was removed.
|
||||
*/
|
||||
readonly key: string;
|
||||
|
||||
/**
|
||||
* The `target` can be `undefined` if a key is being
|
||||
* removed.
|
||||
*/
|
||||
readonly target: StorageTarget | undefined;
|
||||
}
|
||||
|
||||
export interface IStorageTargetChangeEvent {
|
||||
|
||||
/**
|
||||
* The scope for the target that changed. Listeners
|
||||
* should use `keys(scope, target)` to get an updated
|
||||
* list of keys for the given `scope` and `target`.
|
||||
*/
|
||||
readonly scope: StorageScope;
|
||||
}
|
||||
|
||||
export class InMemoryStorageService extends Disposable implements IStorageService {
|
||||
interface IKeyTargets {
|
||||
[key: string]: StorageTarget
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageService extends Disposable implements IStorageService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
private readonly _onDidChangeValue = this._register(new PauseableEmitter<IStorageValueChangeEvent>());
|
||||
readonly onDidChangeValue = this._onDidChangeValue.event;
|
||||
|
||||
protected readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
private readonly _onDidChangeTarget = this._register(new PauseableEmitter<IStorageTargetChangeEvent>());
|
||||
readonly onDidChangeTarget = this._onDidChangeTarget.event;
|
||||
|
||||
private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
readonly onWillSaveState = this._onWillSaveState.event;
|
||||
|
||||
protected emitDidChangeValue(scope: StorageScope, key: string): void {
|
||||
|
||||
// Specially handle `TARGET_KEY`
|
||||
if (key === TARGET_KEY) {
|
||||
|
||||
// Clear our cached version which is now out of date
|
||||
if (scope === StorageScope.GLOBAL) {
|
||||
this._globalKeyTargets = undefined;
|
||||
} else if (scope === StorageScope.WORKSPACE) {
|
||||
this._workspaceKeyTargets = undefined;
|
||||
}
|
||||
|
||||
// Emit as `didChangeTarget` event
|
||||
this._onDidChangeTarget.fire({ scope });
|
||||
}
|
||||
|
||||
// Emit any other key to outside
|
||||
else {
|
||||
this._onDidChangeValue.fire({ scope, key, target: this.getKeyTargets(scope)[key] });
|
||||
}
|
||||
}
|
||||
|
||||
protected emitWillSaveState(reason: WillSaveStateReason): void {
|
||||
this._onWillSaveState.fire({ reason });
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void {
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
if (isUndefinedOrNull(value)) {
|
||||
this.remove(key, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update our datastructures but send events only after
|
||||
this.withPausedEmitters(() => {
|
||||
|
||||
// Update key-target map
|
||||
this.updateKeyTarget(key, scope, target);
|
||||
|
||||
// Store actual value
|
||||
this.doStore(key, value, scope);
|
||||
});
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
|
||||
// Update our datastructures but send events only after
|
||||
this.withPausedEmitters(() => {
|
||||
|
||||
// Update key-target map
|
||||
this.updateKeyTarget(key, scope, undefined);
|
||||
|
||||
// Remove actual key
|
||||
this.doRemove(key, scope);
|
||||
});
|
||||
}
|
||||
|
||||
private withPausedEmitters(fn: Function): void {
|
||||
|
||||
// Pause emitters
|
||||
this._onDidChangeValue.pause();
|
||||
this._onDidChangeTarget.pause();
|
||||
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
|
||||
// Resume emitters
|
||||
this._onDidChangeValue.resume();
|
||||
this._onDidChangeTarget.resume();
|
||||
}
|
||||
}
|
||||
|
||||
keys(scope: StorageScope, target: StorageTarget): string[] {
|
||||
const keys: string[] = [];
|
||||
|
||||
const keyTargets = this.getKeyTargets(scope);
|
||||
for (const key of Object.keys(keyTargets)) {
|
||||
const keyTarget = keyTargets[key];
|
||||
if (keyTarget === target) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private updateKeyTarget(key: string, scope: StorageScope, target: StorageTarget | undefined): void {
|
||||
|
||||
// Add
|
||||
const keyTargets = this.getKeyTargets(scope);
|
||||
if (typeof target === 'number') {
|
||||
if (keyTargets[key] !== target) {
|
||||
keyTargets[key] = target;
|
||||
this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove
|
||||
else {
|
||||
if (typeof keyTargets[key] === 'number') {
|
||||
delete keyTargets[key];
|
||||
this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _workspaceKeyTargets: IKeyTargets | undefined = undefined;
|
||||
private get workspaceKeyTargets(): IKeyTargets {
|
||||
if (!this._workspaceKeyTargets) {
|
||||
this._workspaceKeyTargets = this.loadKeyTargets(StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
return this._workspaceKeyTargets;
|
||||
}
|
||||
|
||||
private _globalKeyTargets: IKeyTargets | undefined = undefined;
|
||||
private get globalKeyTargets(): IKeyTargets {
|
||||
if (!this._globalKeyTargets) {
|
||||
this._globalKeyTargets = this.loadKeyTargets(StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
return this._globalKeyTargets;
|
||||
}
|
||||
|
||||
private getKeyTargets(scope: StorageScope): IKeyTargets {
|
||||
return scope === StorageScope.GLOBAL ? this.globalKeyTargets : this.workspaceKeyTargets;
|
||||
}
|
||||
|
||||
private loadKeyTargets(scope: StorageScope): { [key: string]: StorageTarget } {
|
||||
const keysRaw = this.get(TARGET_KEY, scope);
|
||||
if (keysRaw) {
|
||||
try {
|
||||
return JSON.parse(keysRaw);
|
||||
} catch (error) {
|
||||
// Fail gracefully
|
||||
}
|
||||
}
|
||||
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
isNew(scope: StorageScope): boolean {
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
|
||||
flush(): Promise<void> {
|
||||
|
||||
// Signal event to collect changes
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
|
||||
|
||||
// Await flush
|
||||
return this.doFlush();
|
||||
}
|
||||
|
||||
// --- abstract
|
||||
|
||||
abstract get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
abstract get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
|
||||
|
||||
abstract getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
abstract getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
abstract getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
abstract getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
||||
|
||||
protected abstract doStore(key: string, value: string | boolean | number, scope: StorageScope): void;
|
||||
|
||||
protected abstract doRemove(key: string, scope: StorageScope): void;
|
||||
|
||||
protected abstract doFlush(): Promise<void>;
|
||||
|
||||
abstract migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void>;
|
||||
|
||||
abstract logStorage(): void;
|
||||
}
|
||||
|
||||
export class InMemoryStorageService extends AbstractStorageService {
|
||||
|
||||
private readonly globalCache = new Map<string, string>();
|
||||
private readonly workspaceCache = new Map<string, string>();
|
||||
|
||||
@@ -188,12 +455,7 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
if (isUndefinedOrNull(value)) {
|
||||
return this.remove(key, scope);
|
||||
}
|
||||
protected doStore(key: string, value: string | boolean | number, scope: StorageScope): void {
|
||||
|
||||
// Otherwise, convert to String and store
|
||||
const valueStr = String(value);
|
||||
@@ -201,28 +463,24 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
|
||||
// Return early if value already set
|
||||
const currentValue = this.getCache(scope).get(key);
|
||||
if (currentValue === valueStr) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update in cache
|
||||
this.getCache(scope).set(key, valueStr);
|
||||
|
||||
// Events
|
||||
this._onDidChangeStorage.fire({ scope, key });
|
||||
|
||||
return Promise.resolve();
|
||||
this.emitDidChangeValue(scope, key);
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): Promise<void> {
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
const wasDeleted = this.getCache(scope).delete(key);
|
||||
if (!wasDeleted) {
|
||||
return Promise.resolve(); // Return early if value already deleted
|
||||
return; // Return early if value already deleted
|
||||
}
|
||||
|
||||
// Events
|
||||
this._onDidChangeStorage.fire({ scope, key });
|
||||
|
||||
return Promise.resolve();
|
||||
this.emitDidChangeValue(scope, key);
|
||||
}
|
||||
|
||||
logStorage(): void {
|
||||
@@ -233,13 +491,7 @@ export class InMemoryStorageService extends Disposable implements IStorageServic
|
||||
// not supported
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
|
||||
}
|
||||
|
||||
isNew(): boolean {
|
||||
return true; // always new when in-memory
|
||||
}
|
||||
async doFlush(): Promise<void> { }
|
||||
|
||||
async close(): Promise<void> { }
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, WillSaveStateReason, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorageDatabase, IStorage, StorageHint } from 'vs/base/parts/storage/common/storage';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
@@ -17,19 +16,11 @@ import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderW
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
|
||||
|
||||
export class NativeStorageService extends Disposable implements IStorageService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
export class NativeStorageService extends AbstractStorageService {
|
||||
|
||||
private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb';
|
||||
private static readonly WORKSPACE_META_NAME = 'workspace.json';
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IWorkspaceStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
readonly onWillSaveState = this._onWillSaveState.event;
|
||||
|
||||
private readonly globalStorage = new Storage(this.globalStorageDatabase);
|
||||
|
||||
private workspaceStoragePath: string | undefined;
|
||||
@@ -54,11 +45,7 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
private registerListeners(): void {
|
||||
|
||||
// Global Storage change events
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL)));
|
||||
}
|
||||
|
||||
private handleDidChangeStorage(key: string, scope: StorageScope): void {
|
||||
this._onDidChangeStorage.fire({ key, scope });
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
}
|
||||
|
||||
initialize(payload?: IWorkspaceInitializationPayload): Promise<void> {
|
||||
@@ -134,7 +121,7 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
// Create new
|
||||
this.workspaceStoragePath = workspaceStoragePath;
|
||||
this.workspaceStorage = new Storage(new SQLiteStorageDatabase(workspaceStoragePath, { logging: workspaceLoggingOptions }), { hint });
|
||||
this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.WORKSPACE));
|
||||
this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key));
|
||||
|
||||
return this.workspaceStorage;
|
||||
}
|
||||
@@ -201,11 +188,16 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
return this.getStorage(scope).getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
return this.getStorage(scope).set(key, value);
|
||||
=======
|
||||
protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
|
||||
this.getStorage(scope).set(key, value);
|
||||
>>>>>>> e4a830e9b7ca039c7c70697786d29f5b6679d775
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
this.getStorage(scope).delete(key);
|
||||
}
|
||||
|
||||
@@ -213,6 +205,19 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage);
|
||||
}
|
||||
|
||||
protected async doFlush(): Promise<void> {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
if (this.globalStorage) {
|
||||
promises.push(this.globalStorage.whenFlushed());
|
||||
}
|
||||
|
||||
if (this.workspaceStorage) {
|
||||
promises.push(this.workspaceStorage.whenFlushed());
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private doFlushWhenIdle(): void {
|
||||
|
||||
// Dispose any previous idle runner
|
||||
@@ -229,10 +234,6 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
});
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// Stop periodic scheduler and idle runner as we now collect state normally
|
||||
@@ -241,7 +242,7 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
this.runWhenIdleDisposable = undefined;
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
|
||||
this.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
|
||||
|
||||
// Do it
|
||||
await Promise.all([
|
||||
@@ -277,8 +278,4 @@ export class NativeStorageService extends Disposable implements IStorageService
|
||||
// Recreate and init workspace storage
|
||||
return this.createWorkspaceStorage(newWorkspaceStoragePath).init();
|
||||
}
|
||||
|
||||
isNew(scope: StorageScope): boolean {
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual, ok, equal } from 'assert';
|
||||
import { StorageScope, InMemoryStorageService, StorageTarget, IStorageValueChangeEvent, IStorageTargetChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
|
||||
suite('StorageService', function () {
|
||||
|
||||
test('Get Data, Integer, Boolean (global, in-memory)', () => {
|
||||
storeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Get Data, Integer, Boolean (workspace, in-memory)', () => {
|
||||
storeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function storeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageValueChangeEvents: IStorageValueChangeEvent[] = [];
|
||||
storage.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
|
||||
strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar');
|
||||
strictEqual(storage.get('test.get', scope, ''), '');
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 5), 5);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 0), 0);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, true), true);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, false), false);
|
||||
|
||||
storage.store('test.get', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar');
|
||||
let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get');
|
||||
equal(storageValueChangeEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.key, 'test.get');
|
||||
storageValueChangeEvents = [];
|
||||
|
||||
storage.store('test.get', '', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), '');
|
||||
storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get');
|
||||
equal(storageValueChangeEvent!.scope, scope);
|
||||
equal(storageValueChangeEvent!.key, 'test.get');
|
||||
|
||||
storage.store('test.getNumber', 5, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5);
|
||||
|
||||
storage.store('test.getNumber', 0, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0);
|
||||
|
||||
storage.store('test.getBoolean', true, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true);
|
||||
|
||||
storage.store('test.getBoolean', false, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false);
|
||||
|
||||
strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault');
|
||||
strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5);
|
||||
strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true);
|
||||
}
|
||||
|
||||
test('Remove Data (global, in-memory)', () => {
|
||||
removeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Remove Data (workspace, in-memory)', () => {
|
||||
removeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function removeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageValueChangeEvents: IStorageValueChangeEvent[] = [];
|
||||
storage.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
|
||||
storage.store('test.remove', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual('foobar', storage.get('test.remove', scope, (undefined)!));
|
||||
|
||||
storage.remove('test.remove', scope);
|
||||
ok(!storage.get('test.remove', scope, (undefined)!));
|
||||
let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.remove');
|
||||
equal(storageValueChangeEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.key, 'test.remove');
|
||||
}
|
||||
|
||||
test('Keys (in-memory)', () => {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageTargetEvent: IStorageTargetChangeEvent | undefined = undefined;
|
||||
storage.onDidChangeTarget(e => storageTargetEvent = e);
|
||||
|
||||
let storageValueChangeEvent: IStorageValueChangeEvent | undefined = undefined;
|
||||
storage.onDidChangeValue(e => storageValueChangeEvent = e);
|
||||
|
||||
// Empty
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Add values
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
storageTargetEvent = Object.create(null);
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
equal(storageTargetEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.key, 'test.target1');
|
||||
equal(storageValueChangeEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.target, target);
|
||||
|
||||
storageTargetEvent = undefined;
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', 'otherValue1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
equal(storageTargetEvent, undefined);
|
||||
equal(storageValueChangeEvent?.key, 'test.target1');
|
||||
equal(storageValueChangeEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.target, target);
|
||||
|
||||
storage.store('test.target2', 'value2', scope, target);
|
||||
storage.store('test.target3', 'value3', scope, target);
|
||||
|
||||
strictEqual(storage.keys(scope, target).length, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove values
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
const keysLength = storage.keys(scope, target).length;
|
||||
|
||||
storage.store('test.target4', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, keysLength + 1);
|
||||
|
||||
storageTargetEvent = Object.create(null);
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.remove('test.target4', scope);
|
||||
strictEqual(storage.keys(scope, target).length, keysLength);
|
||||
equal(storageTargetEvent?.scope, scope);
|
||||
equal(storageValueChangeEvent?.key, 'test.target4');
|
||||
equal(storageValueChangeEvent?.scope, scope);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
const keys = storage.keys(scope, target);
|
||||
|
||||
for (const key of keys) {
|
||||
storage.remove(key, scope);
|
||||
}
|
||||
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Adding undefined or null removes value
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
storage.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
|
||||
storageTargetEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', undefined, scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
equal(storageTargetEvent?.scope, scope);
|
||||
|
||||
storage.store('test.target1', '', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
|
||||
storage.store('test.target1', null, scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Target change
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.USER);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(!storageTargetEvent); // no change in target
|
||||
});
|
||||
});
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual, ok, equal } from 'assert';
|
||||
import { StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { equal } from 'assert';
|
||||
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
@@ -16,66 +16,7 @@ import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
suite('StorageService', function () {
|
||||
|
||||
test('Remove Data (global, in-memory)', () => {
|
||||
removeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Remove Data (workspace, in-memory)', () => {
|
||||
removeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function removeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
storage.store('test.remove', 'foobar', scope);
|
||||
strictEqual('foobar', storage.get('test.remove', scope, (undefined)!));
|
||||
|
||||
storage.remove('test.remove', scope);
|
||||
ok(!storage.get('test.remove', scope, (undefined)!));
|
||||
}
|
||||
|
||||
test('Get Data, Integer, Boolean (global, in-memory)', () => {
|
||||
storeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Get Data, Integer, Boolean (workspace, in-memory)', () => {
|
||||
storeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function storeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar');
|
||||
strictEqual(storage.get('test.get', scope, ''), '');
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 5), 5);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 0), 0);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, true), true);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, false), false);
|
||||
|
||||
storage.store('test.get', 'foobar', scope);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar');
|
||||
|
||||
storage.store('test.get', '', scope);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), '');
|
||||
|
||||
storage.store('test.getNumber', 5, scope);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5);
|
||||
|
||||
storage.store('test.getNumber', 0, scope);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0);
|
||||
|
||||
storage.store('test.getBoolean', true, scope);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true);
|
||||
|
||||
storage.store('test.getBoolean', false, scope);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false);
|
||||
|
||||
strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault');
|
||||
strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5);
|
||||
strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true);
|
||||
}
|
||||
suite('NativeStorageService', function () {
|
||||
|
||||
function uniqueStorageDir(): string {
|
||||
const id = generateUuid();
|
||||
@@ -111,9 +52,13 @@ suite('StorageService', function () {
|
||||
const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(storageDir), storageDir));
|
||||
await storage.initialize({ id: String(Date.now()) });
|
||||
|
||||
storage.store('bar', 'foo', StorageScope.WORKSPACE);
|
||||
storage.store('barNumber', 55, StorageScope.WORKSPACE);
|
||||
storage.store('barBoolean', true, StorageScope.GLOBAL);
|
||||
storage.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storage.store('barNumber', 55, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storage.store('barBoolean', true, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
equal(storage.get('bar', StorageScope.WORKSPACE), 'foo');
|
||||
equal(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55);
|
||||
equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true);
|
||||
|
||||
await storage.migrate({ id: String(Date.now() + 100) });
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { globals } from 'vs/base/common/platform';
|
||||
import BaseErrorTelemetry, { ErrorEvent } from '../common/errorTelemetry';
|
||||
import BaseErrorTelemetry, { ErrorEvent } from 'vs/platform/telemetry/common/errorTelemetry';
|
||||
|
||||
export default class ErrorTelemetry extends BaseErrorTelemetry {
|
||||
protected installErrorListeners(): void {
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as appInsights from 'applicationinsights';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
|
||||
function getClient(aiKey: string): appInsights.TelemetryClient {
|
||||
|
||||
async function getClient(aiKey: string): Promise<appInsights.TelemetryClient> {
|
||||
const appInsights = await import('applicationinsights');
|
||||
let client: appInsights.TelemetryClient;
|
||||
if (appInsights.defaultClient) {
|
||||
client = new appInsights.TelemetryClient(aiKey);
|
||||
@@ -36,7 +37,8 @@ function getClient(aiKey: string): appInsights.TelemetryClient {
|
||||
|
||||
export class AppInsightsAppender implements ITelemetryAppender {
|
||||
|
||||
private _aiClient?: appInsights.TelemetryClient;
|
||||
private _aiClient: string | appInsights.TelemetryClient | undefined;
|
||||
private _asyncAIClient: Promise<appInsights.TelemetryClient> | null;
|
||||
|
||||
constructor(
|
||||
private _eventPrefix: string,
|
||||
@@ -47,11 +49,37 @@ export class AppInsightsAppender implements ITelemetryAppender {
|
||||
this._defaultData = Object.create(null);
|
||||
}
|
||||
|
||||
if (typeof aiKeyOrClientFactory === 'string') {
|
||||
this._aiClient = getClient(aiKeyOrClientFactory);
|
||||
} else if (typeof aiKeyOrClientFactory === 'function') {
|
||||
if (typeof aiKeyOrClientFactory === 'function') {
|
||||
this._aiClient = aiKeyOrClientFactory();
|
||||
} else {
|
||||
this._aiClient = aiKeyOrClientFactory;
|
||||
}
|
||||
this._asyncAIClient = null;
|
||||
}
|
||||
|
||||
private _withAIClient(callback: (aiClient: appInsights.TelemetryClient) => void): void {
|
||||
if (!this._aiClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this._aiClient !== 'string') {
|
||||
callback(this._aiClient);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._asyncAIClient) {
|
||||
this._asyncAIClient = getClient(this._aiClient);
|
||||
}
|
||||
|
||||
this._asyncAIClient.then(
|
||||
(aiClient) => {
|
||||
callback(aiClient);
|
||||
},
|
||||
(err) => {
|
||||
onUnexpectedError(err);
|
||||
console.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
log(eventName: string, data?: any): void {
|
||||
@@ -61,22 +89,24 @@ export class AppInsightsAppender implements ITelemetryAppender {
|
||||
data = mixin(data, this._defaultData);
|
||||
data = validateTelemetryData(data);
|
||||
|
||||
this._aiClient.trackEvent({
|
||||
this._withAIClient((aiClient) => aiClient.trackEvent({
|
||||
name: this._eventPrefix + '/' + eventName,
|
||||
properties: data.properties,
|
||||
measurements: data.measurements
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
flush(): Promise<any> {
|
||||
if (this._aiClient) {
|
||||
return new Promise(resolve => {
|
||||
this._aiClient!.flush({
|
||||
callback: () => {
|
||||
// all data flushed
|
||||
this._aiClient = undefined;
|
||||
resolve(undefined);
|
||||
}
|
||||
this._withAIClient((aiClient) => {
|
||||
aiClient.flush({
|
||||
callback: () => {
|
||||
// all data flushed
|
||||
this._aiClient = undefined;
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import BaseErrorTelemetry from '../common/errorTelemetry';
|
||||
import BaseErrorTelemetry from 'vs/platform/telemetry/common/errorTelemetry';
|
||||
|
||||
export default class ErrorTelemetry extends BaseErrorTelemetry {
|
||||
protected installErrorListeners(): void {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
|
||||
export class ThemableCheckboxActionViewItem extends CheckboxActionViewItem {
|
||||
|
||||
constructor(context: any, action: IAction, options: IBaseActionViewItemOptions | undefined, private readonly themeService: IThemeService) {
|
||||
super(context, action, options);
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
if (this.checkbox) {
|
||||
this.disposables.add(attachCheckboxStyler(this.checkbox, this.themeService));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ export const textBlockQuoteBorder = registerColor('textBlockQuote.border', { lig
|
||||
export const textCodeBlockBackground = registerColor('textCodeBlock.background', { light: '#dcdcdc66', dark: '#0a0a0a66', hc: Color.black }, nls.localize('textCodeBlockBackground', "Background color for code blocks in text."));
|
||||
|
||||
// ----- widgets
|
||||
export const widgetShadow = registerColor('widget.shadow', { dark: '#000000', light: '#A8A8A8', hc: null }, nls.localize('widgetShadow', 'Shadow color of widgets such as find/replace inside the editor.'));
|
||||
export const widgetShadow = registerColor('widget.shadow', { dark: transparent(Color.black, .36), light: transparent(Color.black, .16), hc: null }, nls.localize('widgetShadow', 'Shadow color of widgets such as find/replace inside the editor.'));
|
||||
|
||||
export const inputBackground = registerColor('input.background', { dark: '#3C3C3C', light: Color.white, hc: Color.black }, nls.localize('inputBoxBackground', "Input box background."));
|
||||
export const inputForeground = registerColor('input.foreground', { dark: foreground, light: foreground, hc: foreground }, nls.localize('inputBoxForeground', "Input box foreground."));
|
||||
@@ -243,18 +243,23 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac
|
||||
|
||||
export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hc: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations."));
|
||||
|
||||
export const editorErrorBackground = registerColor('editorError.background', { dark: null, light: null, hc: null }, nls.localize('editorError.background', 'Background color of error text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
export const editorErrorForeground = registerColor('editorError.foreground', { dark: '#F48771', light: '#E51400', hc: null }, nls.localize('editorError.foreground', 'Foreground color of error squigglies in the editor.'));
|
||||
export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hc: Color.fromHex('#E47777').transparent(0.8) }, nls.localize('errorBorder', 'Border color of error boxes in the editor.'));
|
||||
|
||||
export const editorWarningBackground = registerColor('editorWarning.background', { dark: null, light: null, hc: null }, nls.localize('editorWarning.background', 'Background color of warning text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#CCA700', light: '#E9A700', hc: null }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.'));
|
||||
export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hc: Color.fromHex('#FFCC00').transparent(0.8) }, nls.localize('warningBorder', 'Border color of warning boxes in the editor.'));
|
||||
|
||||
export const editorInfoBackground = registerColor('editorInfo.background', { dark: null, light: null, hc: null }, nls.localize('editorInfo.background', 'Background color of info text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#75BEFF', light: '#75BEFF', hc: null }, nls.localize('editorInfo.foreground', 'Foreground color of info squigglies in the editor.'));
|
||||
export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#75BEFF').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info boxes in the editor.'));
|
||||
|
||||
export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('editorHint.foreground', 'Foreground color of hint squigglies in the editor.'));
|
||||
export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint boxes in the editor.'));
|
||||
|
||||
export const sashHoverBorder = registerColor('sash.hoverBorder', { dark: null, light: null, hc: null }, nls.localize('sashActiveBorder', "Border color of active sashes."));
|
||||
|
||||
/**
|
||||
* Editor background color.
|
||||
* Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254
|
||||
@@ -353,7 +358,7 @@ export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark:
|
||||
*/
|
||||
export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0060C0', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
|
||||
export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not."));
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as Codicons from 'vs/base/common/codicons';
|
||||
|
||||
|
||||
// ------ API types
|
||||
|
||||
|
||||
@@ -29,14 +30,14 @@ export interface IconDefinition {
|
||||
|
||||
export interface IconContribution {
|
||||
id: string;
|
||||
description: string;
|
||||
description: string | undefined;
|
||||
deprecationMessage?: string;
|
||||
defaults: IconDefaults;
|
||||
}
|
||||
|
||||
export interface IIconRegistry {
|
||||
|
||||
readonly onDidChangeSchema: Event<void>;
|
||||
readonly onDidChange: Event<void>;
|
||||
|
||||
/**
|
||||
* Register a icon to the registry.
|
||||
@@ -44,7 +45,7 @@ export interface IIconRegistry {
|
||||
* @param defaults The default values
|
||||
* @description the description
|
||||
*/
|
||||
registerIcon(id: string, defaults: IconDefaults, description: string): ThemeIcon;
|
||||
registerIcon(id: string, defaults: IconDefaults, description?: string): ThemeIcon;
|
||||
|
||||
/**
|
||||
* Register a icon to the registry.
|
||||
@@ -71,12 +72,17 @@ export interface IIconRegistry {
|
||||
*/
|
||||
getIconReferenceSchema(): IJSONSchema;
|
||||
|
||||
/**
|
||||
* The CSS for all icons
|
||||
*/
|
||||
getCSS(): string;
|
||||
|
||||
}
|
||||
|
||||
class IconRegistry implements IIconRegistry {
|
||||
|
||||
private readonly _onDidChangeSchema = new Emitter<void>();
|
||||
readonly onDidChangeSchema: Event<void> = this._onDidChangeSchema.event;
|
||||
private readonly _onDidChange = new Emitter<void>();
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private iconsById: { [key: string]: IconContribution };
|
||||
private iconSchema: IJSONSchema & { properties: IJSONSchemaMap } = {
|
||||
@@ -101,8 +107,18 @@ class IconRegistry implements IIconRegistry {
|
||||
}
|
||||
|
||||
public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon {
|
||||
if (!description) {
|
||||
description = localize('icon.defaultDescription', 'Icon with identifier \'{0}\'', id);
|
||||
const existing = this.iconsById[id];
|
||||
if (existing) {
|
||||
if (description && !existing.description) {
|
||||
existing.description = description;
|
||||
this.iconSchema.properties[id].markdownDescription = `${description} $(${id})`;
|
||||
const enumIndex = this.iconReferenceSchema.enum.indexOf(id);
|
||||
if (enumIndex !== -1) {
|
||||
this.iconReferenceSchema.enumDescriptions[enumIndex] = description;
|
||||
}
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
let iconContribution: IconContribution = { id, description, defaults, deprecationMessage };
|
||||
this.iconsById[id] = iconContribution;
|
||||
@@ -110,12 +126,14 @@ class IconRegistry implements IIconRegistry {
|
||||
if (deprecationMessage) {
|
||||
propertySchema.deprecationMessage = deprecationMessage;
|
||||
}
|
||||
propertySchema.markdownDescription = `${description}: $(${id})`;
|
||||
if (description) {
|
||||
propertySchema.markdownDescription = `${description}: $(${id})`;
|
||||
}
|
||||
this.iconSchema.properties[id] = propertySchema;
|
||||
this.iconReferenceSchema.enum.push(id);
|
||||
this.iconReferenceSchema.enumDescriptions.push(description);
|
||||
this.iconReferenceSchema.enumDescriptions.push(description || '');
|
||||
|
||||
this._onDidChangeSchema.fire();
|
||||
this._onDidChange.fire();
|
||||
return { id };
|
||||
}
|
||||
|
||||
@@ -128,7 +146,7 @@ class IconRegistry implements IIconRegistry {
|
||||
this.iconReferenceSchema.enum.splice(index, 1);
|
||||
this.iconReferenceSchema.enumDescriptions.splice(index, 1);
|
||||
}
|
||||
this._onDidChangeSchema.fire();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
public getIcons(): IconContribution[] {
|
||||
@@ -147,6 +165,29 @@ class IconRegistry implements IIconRegistry {
|
||||
return this.iconReferenceSchema;
|
||||
}
|
||||
|
||||
public getCSS() {
|
||||
const rules = [];
|
||||
for (let id in this.iconsById) {
|
||||
const rule = this.formatRule(id);
|
||||
if (rule) {
|
||||
rules.push(rule);
|
||||
}
|
||||
}
|
||||
return rules.join('\n');
|
||||
}
|
||||
|
||||
private formatRule(id: string): string | undefined {
|
||||
let definition = this.iconsById[id].defaults;
|
||||
while (ThemeIcon.isThemeIcon(definition)) {
|
||||
const c = this.iconsById[definition.id];
|
||||
if (!c) {
|
||||
return undefined;
|
||||
}
|
||||
definition = c.defaults;
|
||||
}
|
||||
return `.codicon-${id}:before { content: '${definition.character}'; }`;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
const sorter = (i1: IconContribution, i2: IconContribution) => {
|
||||
const isThemeIcon1 = ThemeIcon.isThemeIcon(i1.defaults);
|
||||
@@ -169,7 +210,7 @@ class IconRegistry implements IIconRegistry {
|
||||
const contributions = Object.keys(this.iconsById).map(key => this.iconsById[key]);
|
||||
|
||||
for (const i of contributions.sort(sorter)) {
|
||||
reference.push(`|<i class="${classNames(i)}"></i>|${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|`);
|
||||
reference.push(`|<i class="${classNames(i)}"></i>|${i.id}|${ThemeIcon.isThemeIcon(i.defaults) ? i.defaults.id : ''}|${i.description || ''}|`);
|
||||
|
||||
if (!ThemeIcon.isThemeIcon((i.defaults))) {
|
||||
docCss.push(`.codicon-${i.id}:before { content: "${i.defaults.character}" }`);
|
||||
@@ -183,7 +224,7 @@ class IconRegistry implements IIconRegistry {
|
||||
const iconRegistry = new IconRegistry();
|
||||
platform.Registry.add(Extensions.IconContribution, iconRegistry);
|
||||
|
||||
export function registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon {
|
||||
export function registerIcon(id: string, defaults: IconDefaults, description: string, deprecationMessage?: string): ThemeIcon {
|
||||
return iconRegistry.registerIcon(id, defaults, description, deprecationMessage);
|
||||
}
|
||||
|
||||
@@ -193,20 +234,19 @@ export function getIconRegistry(): IIconRegistry {
|
||||
|
||||
function initialize() {
|
||||
for (const icon of Codicons.iconRegistry.all) {
|
||||
registerIcon(icon.id, icon.definition);
|
||||
iconRegistry.registerIcon(icon.id, icon.definition, icon.description);
|
||||
}
|
||||
Codicons.iconRegistry.onDidRegister(icon => registerIcon(icon.id, icon.definition));
|
||||
Codicons.iconRegistry.onDidRegister(icon => iconRegistry.registerIcon(icon.id, icon.definition, icon.description));
|
||||
}
|
||||
initialize();
|
||||
|
||||
|
||||
export const iconsSchemaId = 'vscode://schemas/icons';
|
||||
|
||||
let schemaRegistry = platform.Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
schemaRegistry.registerSchema(iconsSchemaId, iconRegistry.getIconSchema());
|
||||
|
||||
const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(iconsSchemaId), 200);
|
||||
iconRegistry.onDidChangeSchema(() => {
|
||||
iconRegistry.onDidChange(() => {
|
||||
if (!delayer.isScheduled()) {
|
||||
delayer.schedule();
|
||||
}
|
||||
@@ -214,3 +254,11 @@ iconRegistry.onDidChangeSchema(() => {
|
||||
|
||||
|
||||
//setTimeout(_ => console.log(iconRegistry.toString()), 5000);
|
||||
|
||||
|
||||
// common icons
|
||||
|
||||
export const widgetClose = registerIcon('widget-close', Codicons.Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.'));
|
||||
|
||||
export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicons.Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.'));
|
||||
export const gotoNextLocation = registerIcon('goto-next-location', Codicons.Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.'));
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
import { CSSIcon } from 'vs/base/common/codicons';
|
||||
|
||||
export const IThemeService = createDecorator<IThemeService>('themeService');
|
||||
|
||||
@@ -18,6 +19,12 @@ export interface ThemeColor {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export namespace ThemeColor {
|
||||
export function isThemeColor(obj: any): obj is ThemeColor {
|
||||
return obj && typeof obj === 'object' && typeof (<ThemeColor>obj).id === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
export function themeColorFromId(id: ColorIdentifier) {
|
||||
return { id };
|
||||
}
|
||||
@@ -29,8 +36,8 @@ export interface ThemeIcon {
|
||||
}
|
||||
|
||||
export namespace ThemeIcon {
|
||||
export function isThemeIcon(obj: any): obj is ThemeIcon | { id: string } {
|
||||
return obj && typeof obj === 'object' && typeof (<ThemeIcon>obj).id === 'string';
|
||||
export function isThemeIcon(obj: any): obj is ThemeIcon {
|
||||
return obj && typeof obj === 'object' && typeof (<ThemeIcon>obj).id === 'string' && (typeof (<ThemeIcon>obj).color === 'undefined' || ThemeColor.isThemeColor((<ThemeIcon>obj).color));
|
||||
}
|
||||
|
||||
const _regexFromString = /^\$\(([a-z.]+\/)?([a-z-~]+)\)$/i;
|
||||
@@ -41,26 +48,67 @@ export namespace ThemeIcon {
|
||||
return undefined;
|
||||
}
|
||||
let [, owner, name] = match;
|
||||
if (!owner) {
|
||||
owner = `codicon/`;
|
||||
if (!owner || owner === 'codicon/') {
|
||||
return { id: name };
|
||||
}
|
||||
return { id: owner + name };
|
||||
}
|
||||
|
||||
export function modify(icon: ThemeIcon, modifier: 'disabled' | 'spin' | undefined): ThemeIcon {
|
||||
let id = icon.id;
|
||||
const tildeIndex = id.lastIndexOf('~');
|
||||
if (tildeIndex !== -1) {
|
||||
id = id.substring(0, tildeIndex);
|
||||
}
|
||||
if (modifier) {
|
||||
id = `${id}~${modifier}`;
|
||||
}
|
||||
return { id };
|
||||
}
|
||||
|
||||
export function isEqual(ti1: ThemeIcon, ti2: ThemeIcon): boolean {
|
||||
return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id;
|
||||
}
|
||||
|
||||
const _regexAsClassName = /^(codicon\/)?([a-z-]+)(~[a-z]+)?$/i;
|
||||
|
||||
export function asClassName(icon: ThemeIcon): string | undefined {
|
||||
// todo@martin,joh -> this should go into the ThemeService
|
||||
export function asClassNameArray(icon: ThemeIcon): string[] {
|
||||
const match = _regexAsClassName.exec(icon.id);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
return ['codicon', 'codicon-error'];
|
||||
}
|
||||
let [, , name, modifier] = match;
|
||||
let className = `codicon codicon-${name}`;
|
||||
let className = `codicon-${name}`;
|
||||
if (modifier) {
|
||||
className += ` ${modifier.substr(1)}`;
|
||||
return ['codicon', className, modifier.substr(1)];
|
||||
}
|
||||
return className;
|
||||
return ['codicon', className];
|
||||
}
|
||||
|
||||
|
||||
export function asClassName(icon: ThemeIcon): string {
|
||||
return asClassNameArray(icon).join(' ');
|
||||
}
|
||||
|
||||
export function asCSSSelector(icon: ThemeIcon): string {
|
||||
return '.' + asClassNameArray(icon).join('.');
|
||||
}
|
||||
|
||||
export function asCSSIcon(icon: ThemeIcon): CSSIcon {
|
||||
return {
|
||||
classNames: asClassName(icon)
|
||||
};
|
||||
}
|
||||
|
||||
export function asCodiconLabel(icon: ThemeIcon): string {
|
||||
return '$(' + icon.id + ')';
|
||||
}
|
||||
|
||||
export function revive(icon: any): ThemeIcon | undefined {
|
||||
if (ThemeIcon.isThemeIcon(icon)) {
|
||||
return { id: icon.id, color: icon.color ? { id: icon.color.id } : undefined };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -522,8 +522,9 @@ function createDefaultTokenClassificationRegistry(): TokenClassificationRegistry
|
||||
registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), [['entity.name.type.parameter']]);
|
||||
|
||||
registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]);
|
||||
registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function.member'], ['support.function']]);
|
||||
registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.other.preprocessor.macro']]);
|
||||
registerTokenType('member', nls.localize('member', "Style for member functions"), [], 'method', 'Deprecated use `method` instead');
|
||||
registerTokenType('method', nls.localize('method', "Style for method (member functions)"), [['entity.name.function.member'], ['support.function']]);
|
||||
registerTokenType('macro', nls.localize('macro', "Style for macros."), [['entity.name.function.preprocessor']]);
|
||||
|
||||
registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable.other.readwrite'], ['entity.name.variable']]);
|
||||
registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), [['variable.parameter']]);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { ipcMain as ipc, nativeTheme } from 'electron';
|
||||
import { ipcMain, nativeTheme } from 'electron';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ThemeMainService implements IThemeMainService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IStateService private stateService: IStateService) {
|
||||
ipc.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => {
|
||||
ipcMain.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => {
|
||||
// Theme changes
|
||||
if (typeof broadcast === 'string') {
|
||||
this.storeBackgroundColor(JSON.parse(broadcast));
|
||||
|
||||
@@ -81,6 +81,27 @@ export class UndoRedoGroup {
|
||||
public static None = new UndoRedoGroup();
|
||||
}
|
||||
|
||||
export class UndoRedoSource {
|
||||
private static _ID = 0;
|
||||
|
||||
public readonly id: number;
|
||||
private order: number;
|
||||
|
||||
constructor() {
|
||||
this.id = UndoRedoSource._ID++;
|
||||
this.order = 1;
|
||||
}
|
||||
|
||||
public nextOrder(): number {
|
||||
if (this.id === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this.order++;
|
||||
}
|
||||
|
||||
public static None = new UndoRedoSource();
|
||||
}
|
||||
|
||||
export interface IUndoRedoService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -100,7 +121,7 @@ export interface IUndoRedoService {
|
||||
* Add a new element to the `undo` stack.
|
||||
* This will destroy the `redo` stack.
|
||||
*/
|
||||
pushElement(element: IUndoRedoElement, group?: UndoRedoGroup): void;
|
||||
pushElement(element: IUndoRedoElement, group?: UndoRedoGroup, source?: UndoRedoSource): void;
|
||||
|
||||
/**
|
||||
* Get the last pushed element for a resource.
|
||||
@@ -133,9 +154,9 @@ export interface IUndoRedoService {
|
||||
*/
|
||||
restoreSnapshot(snapshot: ResourceEditStackSnapshot): void;
|
||||
|
||||
canUndo(resource: URI): boolean;
|
||||
undo(resource: URI): Promise<void> | void;
|
||||
canUndo(resource: URI | UndoRedoSource): boolean;
|
||||
undo(resource: URI | UndoRedoSource): Promise<void> | void;
|
||||
|
||||
canRedo(resource: URI): boolean;
|
||||
redo(resource: URI): Promise<void> | void;
|
||||
canRedo(resource: URI | UndoRedoSource): boolean;
|
||||
redo(resource: URI | UndoRedoSource): Promise<void> | void;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements, ResourceEditStackSnapshot, UriComparisonKeyComputer, IResourceUndoRedoElement, UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { IUndoRedoService, IWorkspaceUndoRedoElement, UndoRedoElementType, IUndoRedoElement, IPastFutureElements, ResourceEditStackSnapshot, UriComparisonKeyComputer, IResourceUndoRedoElement, UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
@@ -34,9 +34,11 @@ class ResourceStackElement {
|
||||
public readonly strResources: string[];
|
||||
public readonly groupId: number;
|
||||
public readonly groupOrder: number;
|
||||
public readonly sourceId: number;
|
||||
public readonly sourceOrder: number;
|
||||
public isValid: boolean;
|
||||
|
||||
constructor(actual: IUndoRedoElement, resourceLabel: string, strResource: string, groupId: number, groupOrder: number) {
|
||||
constructor(actual: IUndoRedoElement, resourceLabel: string, strResource: string, groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) {
|
||||
this.actual = actual;
|
||||
this.label = actual.label;
|
||||
this.resourceLabel = resourceLabel;
|
||||
@@ -45,6 +47,8 @@ class ResourceStackElement {
|
||||
this.strResources = [this.strResource];
|
||||
this.groupId = groupId;
|
||||
this.groupOrder = groupOrder;
|
||||
this.sourceId = sourceId;
|
||||
this.sourceOrder = sourceOrder;
|
||||
this.isValid = true;
|
||||
}
|
||||
|
||||
@@ -130,16 +134,20 @@ class WorkspaceStackElement {
|
||||
public readonly strResources: string[];
|
||||
public readonly groupId: number;
|
||||
public readonly groupOrder: number;
|
||||
public readonly sourceId: number;
|
||||
public readonly sourceOrder: number;
|
||||
public removedResources: RemovedResources | null;
|
||||
public invalidatedResources: RemovedResources | null;
|
||||
|
||||
constructor(actual: IWorkspaceUndoRedoElement, resourceLabels: string[], strResources: string[], groupId: number, groupOrder: number) {
|
||||
constructor(actual: IWorkspaceUndoRedoElement, resourceLabels: string[], strResources: string[], groupId: number, groupOrder: number, sourceId: number, sourceOrder: number) {
|
||||
this.actual = actual;
|
||||
this.label = actual.label;
|
||||
this.resourceLabels = resourceLabels;
|
||||
this.strResources = strResources;
|
||||
this.groupId = groupId;
|
||||
this.groupOrder = groupOrder;
|
||||
this.sourceId = sourceId;
|
||||
this.sourceOrder = sourceOrder;
|
||||
this.removedResources = null;
|
||||
this.invalidatedResources = null;
|
||||
}
|
||||
@@ -490,11 +498,11 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
console.log(str.join('\n'));
|
||||
}
|
||||
|
||||
public pushElement(element: IUndoRedoElement, group: UndoRedoGroup = UndoRedoGroup.None): void {
|
||||
public pushElement(element: IUndoRedoElement, group: UndoRedoGroup = UndoRedoGroup.None, source: UndoRedoSource = UndoRedoSource.None): void {
|
||||
if (element.type === UndoRedoElementType.Resource) {
|
||||
const resourceLabel = getResourceLabel(element.resource);
|
||||
const strResource = this.getUriComparisonKey(element.resource);
|
||||
this._pushElement(new ResourceStackElement(element, resourceLabel, strResource, group.id, group.nextOrder()));
|
||||
this._pushElement(new ResourceStackElement(element, resourceLabel, strResource, group.id, group.nextOrder(), source.id, source.nextOrder()));
|
||||
} else {
|
||||
const seen = new Set<string>();
|
||||
const resourceLabels: string[] = [];
|
||||
@@ -512,9 +520,9 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
|
||||
if (resourceLabels.length === 1) {
|
||||
this._pushElement(new ResourceStackElement(element, resourceLabels[0], strResources[0], group.id, group.nextOrder()));
|
||||
this._pushElement(new ResourceStackElement(element, resourceLabels[0], strResources[0], group.id, group.nextOrder(), source.id, source.nextOrder()));
|
||||
} else {
|
||||
this._pushElement(new WorkspaceStackElement(element, resourceLabels, strResources, group.id, group.nextOrder()));
|
||||
this._pushElement(new WorkspaceStackElement(element, resourceLabels, strResources, group.id, group.nextOrder(), source.id, source.nextOrder()));
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
@@ -558,7 +566,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
for (const _element of individualArr) {
|
||||
const resourceLabel = getResourceLabel(_element.resource);
|
||||
const strResource = this.getUriComparisonKey(_element.resource);
|
||||
const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0);
|
||||
const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0, 0, 0);
|
||||
individualMap.set(element.strResource, element);
|
||||
}
|
||||
|
||||
@@ -577,7 +585,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
for (const _element of individualArr) {
|
||||
const resourceLabel = getResourceLabel(_element.resource);
|
||||
const strResource = this.getUriComparisonKey(_element.resource);
|
||||
const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0);
|
||||
const element = new ResourceStackElement(_element, resourceLabel, strResource, 0, 0, 0, 0);
|
||||
individualMap.set(element.strResource, element);
|
||||
}
|
||||
|
||||
@@ -657,8 +665,37 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
return { past: [], future: [] };
|
||||
}
|
||||
|
||||
public canUndo(resource: URI): boolean {
|
||||
const strResource = this.getUriComparisonKey(resource);
|
||||
private _findClosestUndoElementWithSource(sourceId: number): [StackElement | null, string | null] {
|
||||
if (!sourceId) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// find an element with the sourceId and with the highest sourceOrder ready to be undone
|
||||
let matchedElement: StackElement | null = null;
|
||||
let matchedStrResource: string | null = null;
|
||||
|
||||
for (const [strResource, editStack] of this._editStacks) {
|
||||
const candidate = editStack.getClosestPastElement();
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
if (candidate.sourceId === sourceId) {
|
||||
if (!matchedElement || candidate.sourceOrder > matchedElement.sourceOrder) {
|
||||
matchedElement = candidate;
|
||||
matchedStrResource = strResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [matchedElement, matchedStrResource];
|
||||
}
|
||||
|
||||
public canUndo(resourceOrSource: URI | UndoRedoSource): boolean {
|
||||
if (resourceOrSource instanceof UndoRedoSource) {
|
||||
const [, matchedStrResource] = this._findClosestUndoElementWithSource(resourceOrSource.id);
|
||||
return matchedStrResource ? true : false;
|
||||
}
|
||||
const strResource = this.getUriComparisonKey(resourceOrSource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
return editStack.hasPastElements();
|
||||
@@ -774,7 +811,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
if (element.canSplit()) {
|
||||
this._splitPastWorkspaceElement(element, ignoreResources);
|
||||
this._notificationService.info(message);
|
||||
return new WorkspaceVerificationError(this.undo(strResource));
|
||||
return new WorkspaceVerificationError(this._undo(strResource));
|
||||
} else {
|
||||
// Cannot safely split this workspace element => flush all undo/redo stacks
|
||||
for (const strResource of element.strResources) {
|
||||
@@ -922,7 +959,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
if (result.choice === 1) {
|
||||
// choice: undo this file
|
||||
this._splitPastWorkspaceElement(element, null);
|
||||
return this.undo(strResource);
|
||||
return this._undo(strResource);
|
||||
}
|
||||
|
||||
// choice: undo in all files
|
||||
@@ -1007,12 +1044,22 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
const [, matchedStrResource] = this._findClosestUndoElementInGroup(groupId);
|
||||
if (matchedStrResource) {
|
||||
return this.undo(matchedStrResource);
|
||||
return this._undo(matchedStrResource);
|
||||
}
|
||||
}
|
||||
|
||||
public undo(resource: URI | string): Promise<void> | void {
|
||||
const strResource = typeof resource === 'string' ? resource : this.getUriComparisonKey(resource);
|
||||
public undo(resourceOrSource: URI | UndoRedoSource): Promise<void> | void {
|
||||
if (resourceOrSource instanceof UndoRedoSource) {
|
||||
const [, matchedStrResource] = this._findClosestUndoElementWithSource(resourceOrSource.id);
|
||||
return matchedStrResource ? this._undo(matchedStrResource, resourceOrSource.id) : undefined;
|
||||
}
|
||||
if (typeof resourceOrSource === 'string') {
|
||||
return this._undo(resourceOrSource);
|
||||
}
|
||||
return this._undo(this.getUriComparisonKey(resourceOrSource));
|
||||
}
|
||||
|
||||
private _undo(strResource: string, sourceId: number = 0): Promise<void> | void {
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
}
|
||||
@@ -1028,10 +1075,15 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
const [matchedElement, matchedStrResource] = this._findClosestUndoElementInGroup(element.groupId);
|
||||
if (element !== matchedElement && matchedStrResource) {
|
||||
// there is an element in the same group that should be undone before this one
|
||||
return this.undo(matchedStrResource);
|
||||
return this._undo(matchedStrResource);
|
||||
}
|
||||
}
|
||||
|
||||
if (element.sourceId !== sourceId) {
|
||||
// Hit a different source, prompt for confirmation
|
||||
return this._confirmDifferentSourceAndContinueUndo(strResource, element);
|
||||
}
|
||||
|
||||
try {
|
||||
if (element.type === UndoRedoElementType.Workspace) {
|
||||
return this._workspaceUndo(strResource, element);
|
||||
@@ -1045,8 +1097,59 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
}
|
||||
}
|
||||
|
||||
public canRedo(resource: URI): boolean {
|
||||
const strResource = this.getUriComparisonKey(resource);
|
||||
private async _confirmDifferentSourceAndContinueUndo(strResource: string, element: StackElement): Promise<void> {
|
||||
const result = await this._dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('confirmDifferentSource', "Would you like to undo '{0}'?", element.label),
|
||||
[
|
||||
nls.localize('confirmDifferentSource.ok', "Undo"),
|
||||
nls.localize('cancel', "Cancel"),
|
||||
],
|
||||
{
|
||||
cancelId: 1
|
||||
}
|
||||
);
|
||||
|
||||
if (result.choice === 1) {
|
||||
// choice: cancel
|
||||
return;
|
||||
}
|
||||
|
||||
// choice: undo
|
||||
return this._undo(strResource, element.sourceId);
|
||||
}
|
||||
|
||||
private _findClosestRedoElementWithSource(sourceId: number): [StackElement | null, string | null] {
|
||||
if (!sourceId) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
// find an element with sourceId and with the lowest sourceOrder ready to be redone
|
||||
let matchedElement: StackElement | null = null;
|
||||
let matchedStrResource: string | null = null;
|
||||
|
||||
for (const [strResource, editStack] of this._editStacks) {
|
||||
const candidate = editStack.getClosestFutureElement();
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
if (candidate.sourceId === sourceId) {
|
||||
if (!matchedElement || candidate.sourceOrder < matchedElement.sourceOrder) {
|
||||
matchedElement = candidate;
|
||||
matchedStrResource = strResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [matchedElement, matchedStrResource];
|
||||
}
|
||||
|
||||
public canRedo(resourceOrSource: URI | UndoRedoSource): boolean {
|
||||
if (resourceOrSource instanceof UndoRedoSource) {
|
||||
const [, matchedStrResource] = this._findClosestRedoElementWithSource(resourceOrSource.id);
|
||||
return matchedStrResource ? true : false;
|
||||
}
|
||||
const strResource = this.getUriComparisonKey(resourceOrSource);
|
||||
if (this._editStacks.has(strResource)) {
|
||||
const editStack = this._editStacks.get(strResource)!;
|
||||
return editStack.hasFutureElements();
|
||||
@@ -1058,7 +1161,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
if (element.canSplit()) {
|
||||
this._splitFutureWorkspaceElement(element, ignoreResources);
|
||||
this._notificationService.info(message);
|
||||
return new WorkspaceVerificationError(this.redo(strResource));
|
||||
return new WorkspaceVerificationError(this._redo(strResource));
|
||||
} else {
|
||||
// Cannot safely split this workspace element => flush all undo/redo stacks
|
||||
for (const strResource of element.strResources) {
|
||||
@@ -1230,12 +1333,22 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
const [, matchedStrResource] = this._findClosestRedoElementInGroup(groupId);
|
||||
if (matchedStrResource) {
|
||||
return this.redo(matchedStrResource);
|
||||
return this._redo(matchedStrResource);
|
||||
}
|
||||
}
|
||||
|
||||
public redo(resource: URI | string): Promise<void> | void {
|
||||
const strResource = typeof resource === 'string' ? resource : this.getUriComparisonKey(resource);
|
||||
public redo(resourceOrSource: URI | UndoRedoSource | string): Promise<void> | void {
|
||||
if (resourceOrSource instanceof UndoRedoSource) {
|
||||
const [, matchedStrResource] = this._findClosestRedoElementWithSource(resourceOrSource.id);
|
||||
return matchedStrResource ? this._redo(matchedStrResource) : undefined;
|
||||
}
|
||||
if (typeof resourceOrSource === 'string') {
|
||||
return this._redo(resourceOrSource);
|
||||
}
|
||||
return this._redo(this.getUriComparisonKey(resourceOrSource));
|
||||
}
|
||||
|
||||
private _redo(strResource: string): Promise<void> | void {
|
||||
if (!this._editStacks.has(strResource)) {
|
||||
return;
|
||||
}
|
||||
@@ -1251,7 +1364,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
const [matchedElement, matchedStrResource] = this._findClosestRedoElementInGroup(element.groupId);
|
||||
if (element !== matchedElement && matchedStrResource) {
|
||||
// there is an element in the same group that should be redone before this one
|
||||
return this.redo(matchedStrResource);
|
||||
return this._redo(matchedStrResource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,8 @@ export class DarwinUpdateService extends AbstractUpdateService {
|
||||
}
|
||||
|
||||
protected buildUpdateFeedUrl(quality: string): string | undefined {
|
||||
const url = createUpdateURL('darwin', quality);
|
||||
const assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64';
|
||||
const url = createUpdateURL(assetID, quality);
|
||||
try {
|
||||
electron.autoUpdater.setFeedURL({ url });
|
||||
} catch (e) {
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface IOpenURLOptions {
|
||||
* might be shown to the user.
|
||||
*/
|
||||
trusted?: boolean;
|
||||
|
||||
originalUrl?: string;
|
||||
}
|
||||
|
||||
export interface IURLHandler {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class URLHandlerChannel implements IServerChannel {
|
||||
|
||||
call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'handleURL': return this.handler.handleURL(URI.revive(arg));
|
||||
case 'handleURL': return this.handler.handleURL(URI.revive(arg[0]), arg[1]);
|
||||
}
|
||||
|
||||
throw new Error(`Call not found: ${command}`);
|
||||
@@ -31,7 +31,7 @@ export class URLHandlerChannelClient implements IURLHandler {
|
||||
constructor(private channel: IChannel) { }
|
||||
|
||||
handleURL(uri: URI, options?: IOpenURLOptions): Promise<boolean> {
|
||||
return this.channel.call('handleURL', uri.toJSON());
|
||||
return this.channel.call('handleURL', [uri.toJSON(), options]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ function uriFromRawUrl(url: string): URI | null {
|
||||
*/
|
||||
export class ElectronURLListener {
|
||||
|
||||
private uris: URI[] = [];
|
||||
private uris: { uri: URI, url: string }[] = [];
|
||||
private retryCount = 0;
|
||||
private flushDisposable: IDisposable = Disposable.None;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
initialUrisToHandle: URI[],
|
||||
initialUrisToHandle: { uri: URI, url: string }[],
|
||||
private readonly urlService: IURLService,
|
||||
windowsMainService: IWindowsMainService,
|
||||
environmentService: IEnvironmentMainService
|
||||
@@ -64,8 +64,15 @@ export class ElectronURLListener {
|
||||
return url;
|
||||
});
|
||||
|
||||
const onOpenUrl = Event.filter<URI | null, URI>(Event.map(onOpenElectronUrl, uriFromRawUrl), (uri): uri is URI => !!uri);
|
||||
onOpenUrl(this.urlService.open, this.urlService, this.disposables);
|
||||
this.disposables.add(onOpenElectronUrl(url => {
|
||||
const uri = uriFromRawUrl(url);
|
||||
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.urlService.open(uri, { originalUrl: url });
|
||||
}));
|
||||
|
||||
// Send initial links to the window once it has loaded
|
||||
const isWindowReady = windowsMainService.getWindows()
|
||||
@@ -84,13 +91,13 @@ export class ElectronURLListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const uris: URI[] = [];
|
||||
const uris: { uri: URI, url: string }[] = [];
|
||||
|
||||
for (const uri of this.uris) {
|
||||
const handled = await this.urlService.open(uri);
|
||||
for (const obj of this.uris) {
|
||||
const handled = await this.urlService.open(obj.uri, { originalUrl: obj.url });
|
||||
|
||||
if (!handled) {
|
||||
uris.push(uri);
|
||||
uris.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export interface IExtensionIdWithVersion {
|
||||
id: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const IExtensionsStorageSyncService = createDecorator<IExtensionsStorageSyncService>('IExtensionsStorageSyncService');
|
||||
|
||||
export interface IExtensionsStorageSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onDidChangeExtensionsStorage: Event<void>;
|
||||
setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void;
|
||||
getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined;
|
||||
|
||||
}
|
||||
|
||||
const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
|
||||
|
||||
export class ExtensionsStorageSyncService extends Disposable implements IExtensionsStorageSyncService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static toKey(extension: IExtensionIdWithVersion): string {
|
||||
return `extensionKeys/${extension.id}@${extension.version}`;
|
||||
}
|
||||
|
||||
private static fromKey(key: string): IExtensionIdWithVersion | undefined {
|
||||
const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key);
|
||||
if (matches && matches[1]) {
|
||||
return { id: matches[1], version: matches[2] };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly _onDidChangeExtensionsStorage = this._register(new Emitter<void>());
|
||||
readonly onDidChangeExtensionsStorage = this._onDidChangeExtensionsStorage.event;
|
||||
|
||||
private readonly extensionsWithKeysForSync = new Set<string>();
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this.initialize();
|
||||
this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorageValue(e)));
|
||||
}
|
||||
|
||||
private initialize(): void {
|
||||
const keys = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
for (const key of keys) {
|
||||
const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(key);
|
||||
if (extensionIdWithVersion) {
|
||||
this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeStorageValue(e: IStorageValueChangeEvent): void {
|
||||
if (e.scope !== StorageScope.GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// State of extension with keys for sync has changed
|
||||
if (this.extensionsWithKeysForSync.has(e.key.toLowerCase())) {
|
||||
this._onDidChangeExtensionsStorage.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
// Keys for sync of an extension has changed
|
||||
const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(e.key);
|
||||
if (extensionIdWithVersion) {
|
||||
this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase());
|
||||
this._onDidChangeExtensionsStorage.fire();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this.storageService.store(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined {
|
||||
const keysForSyncValue = this.storageService.get(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), StorageScope.GLOBAL);
|
||||
return keysForSyncValue ? JSON.parse(keysForSyncValue) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,12 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { forEach, IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
|
||||
interface IExtensionResourceMergeResult extends IAcceptResult {
|
||||
readonly added: ISyncExtension[];
|
||||
@@ -105,7 +105,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IExtensionsStorageSyncService private readonly extensionsStorageSyncService: IExtensionsStorageSyncService,
|
||||
) {
|
||||
super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(
|
||||
@@ -114,16 +114,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)),
|
||||
this.extensionEnablementService.onDidChangeEnablement,
|
||||
this.storageKeysSyncRegistryService.onDidChangeExtensionStorageKeys,
|
||||
Event.filter(this.storageService.onDidChangeStorage, e => e.scope === StorageScope.GLOBAL
|
||||
&& this.storageKeysSyncRegistryService.extensionsStorageKeys.some(([extensionIdentifier]) => areSameExtensions(extensionIdentifier, { id: e.key })))),
|
||||
this.extensionsStorageSyncService.onDidChangeExtensionsStorage),
|
||||
() => undefined, 500)(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData?.skippedExtensions || [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
@@ -352,7 +350,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r)));
|
||||
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
|
||||
removeFromSkipped.push(extensionToRemove.identifier);
|
||||
}));
|
||||
@@ -365,9 +363,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// Builtin Extension Sync: Enablement & State
|
||||
if (installedExtension && installedExtension.isBuiltin) {
|
||||
if (e.state && installedExtension.manifest.version === e.version) {
|
||||
const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(e.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version);
|
||||
}
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||
@@ -393,9 +389,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
(installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */
|
||||
: !!extension /* Installable */)
|
||||
) {
|
||||
const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(e.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version);
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
@@ -413,7 +407,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// Install only if the extension does not exist
|
||||
if (!installedExtension) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
}
|
||||
@@ -442,6 +436,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
return newSkippedExtensions;
|
||||
}
|
||||
|
||||
private updateExtensionState(state: IStringDictionary<any>, id: string, version?: string): void {
|
||||
const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}');
|
||||
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined;
|
||||
if (keys) {
|
||||
keys.forEach(key => extensionState[key] = state[key]);
|
||||
} else {
|
||||
forEach(state, ({ key, value }) => extensionState[key] = value);
|
||||
}
|
||||
this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
@@ -457,10 +462,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
if (!isBuiltin) {
|
||||
syncExntesion.installed = true;
|
||||
}
|
||||
const keys = this.storageKeysSyncRegistryService.getExtensioStorageKeys({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
|
||||
try {
|
||||
try {
|
||||
const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
|
||||
const extensionStorageState = JSON.parse(extensionStorageValue);
|
||||
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
|
||||
if (keys.includes(key)) {
|
||||
@@ -468,9 +473,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
return state;
|
||||
}, {});
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
}
|
||||
return syncExntesion;
|
||||
});
|
||||
@@ -499,6 +504,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initializeRemoteExtensions(remoteExtensions);
|
||||
}
|
||||
|
||||
protected async initializeRemoteExtensions(remoteExtensions: ISyncExtension[]): Promise<ILocalExtension[]> {
|
||||
const newlyEnabledExtensions: ILocalExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const newExtensionsToSync = new Map<string, ISyncExtension>();
|
||||
const installedExtensionsToSync: ISyncExtension[] = [];
|
||||
@@ -528,10 +538,13 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
try {
|
||||
const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!;
|
||||
if (extensionToSync.state) {
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
if (!toDisable.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
|
||||
newlyEnabledExtensions.push(local);
|
||||
}
|
||||
this.logService.info(`Installed extension.`, galleryExtension.identifier.id);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
@@ -551,11 +564,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
if (extensionToSync.state) {
|
||||
const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
return newlyEnabledExtensions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,24 +6,22 @@
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export interface IMergeResult {
|
||||
local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
remote: IStringDictionary<IStorageValue> | null;
|
||||
skipped: string[];
|
||||
}
|
||||
|
||||
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: ReadonlyArray<IStorageKey>, previouslySkipped: string[], logService: ILogService): IMergeResult {
|
||||
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: { machine: ReadonlyArray<string>, unregistered: ReadonlyArray<string> }, logService: ILogService): IMergeResult {
|
||||
if (!remoteStorage) {
|
||||
return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} }, skipped: [] };
|
||||
return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const localToRemote = compare(localStorage, remoteStorage);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { remote: null, local: { added: {}, removed: [], updated: {} }, skipped: [] };
|
||||
return { remote: null, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
@@ -31,26 +29,48 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
|
||||
const local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> } = { added: {}, removed: [], updated: {} };
|
||||
const remote: IStringDictionary<IStorageValue> = objects.deepClone(remoteStorage);
|
||||
const skipped: string[] = [];
|
||||
|
||||
// Added in local
|
||||
for (const key of baseToLocal.added.values()) {
|
||||
// Skip if local was not synced before and remote also has the key
|
||||
// In this case, remote gets precedence
|
||||
if (!baseStorage && baseToRemote.added.has(key)) {
|
||||
continue;
|
||||
} else {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updated in local
|
||||
for (const key of baseToLocal.updated.values()) {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
|
||||
// Removed in local
|
||||
for (const key of baseToLocal.removed.values()) {
|
||||
// Do not remove from remote if key is not registered.
|
||||
if (storageKeys.unregistered.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
delete remote[key];
|
||||
}
|
||||
|
||||
// Added in remote
|
||||
for (const key of baseToRemote.added.values()) {
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped adding ${key} in local storage as it is not registered.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.info(`GlobalState: Skipped adding ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped adding ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
// Skip if the value is also added in local from the time it is last synced
|
||||
if (baseStorage && baseToLocal.added.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
if (localValue && localValue.value === remoteValue.value) {
|
||||
continue;
|
||||
}
|
||||
if (baseToLocal.added.has(key)) {
|
||||
if (localValue) {
|
||||
local.updated[key] = remoteValue;
|
||||
} else {
|
||||
local.added[key] = remoteValue;
|
||||
@@ -60,14 +80,12 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
// Updated in Remote
|
||||
for (const key of baseToRemote.updated.values()) {
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped updating ${key} in local storage as is not registered.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
// Skip if the value is also updated or removed in local
|
||||
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
@@ -79,67 +97,18 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
|
||||
// Removed in remote
|
||||
for (const key of baseToRemote.removed.values()) {
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in local storage. It is not registered to sync.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
// Skip if the value is also updated or removed in local
|
||||
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
local.removed.push(key);
|
||||
}
|
||||
|
||||
// Added in local
|
||||
for (const key of baseToLocal.added.values()) {
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updated in local
|
||||
for (const key of baseToLocal.updated.values()) {
|
||||
if (baseToRemote.updated.has(key) || baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const remoteValue = remote[key];
|
||||
const localValue = localStorage[key];
|
||||
if (localValue.version < remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${localValue.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
remote[key] = localValue;
|
||||
}
|
||||
|
||||
// Removed in local
|
||||
for (const key of baseToLocal.removed.values()) {
|
||||
// do not remove from remote if it is updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
// do not remove from remote if storage key is not found
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in remote storage. It is not registered to sync.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const remoteValue = remote[key];
|
||||
// do not remove from remote if local data version is old
|
||||
if (storageKey.version < remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// add to local if it was skipped before
|
||||
if (previouslySkipped.indexOf(key) !== -1) {
|
||||
local.added[key] = remote[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
delete remote[key];
|
||||
}
|
||||
|
||||
return { local, remote: areSame(remote, remoteStorage) ? null : remote, skipped };
|
||||
return { local, remote: areSame(remote, remoteStorage) ? null : remote };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
|
||||
@@ -21,29 +21,34 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const argvStoragePrefx = 'globalState.argv.';
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
type StorageKeys = { machine: string[], user: string[], unregistered: string[] };
|
||||
|
||||
interface IGlobalStateResourceMergeResult extends IAcceptResult {
|
||||
readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
readonly remote: IStringDictionary<IStorageValue> | null;
|
||||
}
|
||||
|
||||
export interface IGlobalStateResourcePreview extends IResourcePreview {
|
||||
readonly skippedStorageKeys: string[];
|
||||
readonly localUserData: IGlobalState;
|
||||
readonly previewResult: IGlobalStateResourceMergeResult;
|
||||
readonly storageKeys: StorageKeys;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
skippedStorageKeys: string[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronises global state that includes
|
||||
* - Global storage with user scope
|
||||
* - Locale from argv properties
|
||||
*
|
||||
* Global storage is synced without checking version just like other resources (settings, keybindings).
|
||||
* If there is a change in format of the value of a storage key which requires migration then
|
||||
* Owner of that key should remove that key from user scope and replace that with new user scoped key.
|
||||
*/
|
||||
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` });
|
||||
@@ -63,23 +68,22 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(SyncResource.GlobalState, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(this.fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
|
||||
this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
|
||||
this._register(
|
||||
Event.any(
|
||||
/* Locale change */
|
||||
Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
|
||||
/* Storage change */
|
||||
Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key)),
|
||||
/* Storage key registered */
|
||||
this.storageKeysSyncRegistryService.onDidChangeStorageKeys
|
||||
Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
|
||||
/* Global storage with user target has changed */
|
||||
Event.filter(storageService.onDidChangeValue, e => e.scope === StorageScope.GLOBAL && e.target !== undefined ? e.target === StorageTarget.USER : storageService.keys(StorageScope.GLOBAL, StorageTarget.USER).includes(e.key)),
|
||||
/* Storage key target has changed */
|
||||
this.storageService.onDidChangeTarget
|
||||
)((() => this.triggerLocalChange()))
|
||||
);
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
|
||||
@@ -91,7 +95,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
|
||||
}
|
||||
|
||||
const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const storageKeys = this.getStorageKeys(lastSyncGlobalState);
|
||||
const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, storageKeys, this.logService);
|
||||
const previewResult: IGlobalStateResourceMergeResult = {
|
||||
content: null,
|
||||
local,
|
||||
@@ -101,7 +106,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
};
|
||||
|
||||
return [{
|
||||
skippedStorageKeys: skipped,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localGloablState),
|
||||
localUserData: localGloablState,
|
||||
@@ -112,6 +116,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange,
|
||||
acceptedResource: this.acceptedResource,
|
||||
storageKeys
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -152,7 +157,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
|
||||
if (resourcePreview.remoteContent !== null) {
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
|
||||
const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), resourcePreview.skippedStorageKeys, this.logService);
|
||||
const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, resourcePreview.storageKeys, this.logService);
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
local,
|
||||
@@ -171,8 +176,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { localUserData, skippedStorageKeys } = resourcePreviews[0][0];
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { localUserData } = resourcePreviews[0][0];
|
||||
let { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
@@ -195,10 +200,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`);
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref || !equals(lastSyncUserData.skippedStorageKeys, skippedStorageKeys)) {
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
// update last sync
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`);
|
||||
await this.updateLastSyncUserData(remoteUserData, { skippedStorageKeys });
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
|
||||
}
|
||||
}
|
||||
@@ -267,10 +272,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
|
||||
}
|
||||
}
|
||||
for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) {
|
||||
for (const key of this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER)) {
|
||||
const value = this.storageService.get(key, StorageScope.GLOBAL);
|
||||
if (value) {
|
||||
storage[key] = { version, value };
|
||||
storage[key] = { version: 1, value };
|
||||
}
|
||||
}
|
||||
return { storage };
|
||||
@@ -317,7 +322,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
if (updatedStorageKeys.length) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`);
|
||||
for (const key of Object.keys(updatedStorage)) {
|
||||
this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL);
|
||||
this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage));
|
||||
}
|
||||
@@ -336,8 +341,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
private getSyncStorageKeys(): IStorageKey[] {
|
||||
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
|
||||
private getStorageKeys(lastSyncGlobalState: IGlobalState | null): StorageKeys {
|
||||
const user = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER);
|
||||
const machine = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
const registered = [...user, ...machine];
|
||||
const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && this.storageService.get(key, StorageScope.GLOBAL) !== undefined) : [];
|
||||
return { user, machine, unregistered };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +394,7 @@ export class GlobalStateInitializer extends AbstractInitializer {
|
||||
|
||||
if (Object.keys(storage).length) {
|
||||
for (const key of Object.keys(storage)) {
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL);
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -35,6 +36,10 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
platformSpecific?: boolean;
|
||||
}
|
||||
|
||||
export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (!platformSpecific) {
|
||||
@@ -72,11 +77,12 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IKeybindingsResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IKeybindingsResourcePreview[]> {
|
||||
const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
const lastSyncContent: string | null = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
|
||||
const lastSyncContent: string | null = lastSyncUserData ? this.getKeybindingsContentFromLastSyncUserData(lastSyncUserData) : null;
|
||||
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
@@ -204,7 +210,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
if (localChange !== Change.None) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
|
||||
if (fileContent) {
|
||||
await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null));
|
||||
await this.backupLocal(this.toSyncContent(fileContent.value.toString()));
|
||||
}
|
||||
await this.updateLocalFileContent(content || '[]', fileContent, force);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
|
||||
@@ -212,7 +218,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (remoteChange !== Change.None) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`);
|
||||
const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData ? remoteUserData.syncData.content : null);
|
||||
const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData?.content);
|
||||
remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`);
|
||||
}
|
||||
@@ -224,15 +230,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`);
|
||||
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : remoteUserData.syncData?.content;
|
||||
await this.updateLastSyncUserData({
|
||||
ref: remoteUserData.ref,
|
||||
syncData: lastSyncContent ? {
|
||||
version: remoteUserData.syncData ? remoteUserData.syncData.version : this.version,
|
||||
machineId: remoteUserData.syncData!.machineId,
|
||||
content: lastSyncContent
|
||||
} : null
|
||||
});
|
||||
await this.updateLastSyncUserData(remoteUserData, { platformSpecific: this.syncKeybindingsPerPlatform() });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`);
|
||||
}
|
||||
|
||||
@@ -281,6 +279,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
return null;
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromLastSyncUserData(lastSyncUserData: ILastSyncUserData): string | null {
|
||||
if (!lastSyncUserData.syncData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return null if there is a change in platform specific property from last time sync.
|
||||
if (lastSyncUserData.platformSpecific !== undefined && lastSyncUserData.platformSpecific !== this.syncKeybindingsPerPlatform()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content);
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform());
|
||||
@@ -290,7 +301,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
}
|
||||
|
||||
private toSyncContent(keybindingsContent: string, syncContent: string | null): string {
|
||||
private toSyncContent(keybindingsContent: string, syncContent?: string): string {
|
||||
let parsed: ISyncContent = {};
|
||||
try {
|
||||
parsed = JSON.parse(syncContent || '{}');
|
||||
|
||||
@@ -13,7 +13,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
@@ -198,6 +198,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString())));
|
||||
}
|
||||
await this.updateLocalFileContent(content, fileContent, force);
|
||||
await this.configurationService.reloadConfiguration(ConfigurationTarget.USER_LOCAL);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IStorageKey {
|
||||
|
||||
readonly key: string;
|
||||
readonly version: number;
|
||||
|
||||
}
|
||||
|
||||
export interface IExtensionIdWithVersion {
|
||||
id: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export namespace ExtensionIdWithVersion {
|
||||
|
||||
const EXTENSION_ID_VERSION_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
|
||||
|
||||
export function toKey(extension: IExtensionIdWithVersion): string {
|
||||
return `${extension.id}@${extension.version}`;
|
||||
}
|
||||
|
||||
export function fromKey(key: string): IExtensionIdWithVersion | undefined {
|
||||
const matches = EXTENSION_ID_VERSION_REGEX.exec(key);
|
||||
if (matches && matches[1]) {
|
||||
return { id: matches[1], version: matches[2] };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const IStorageKeysSyncRegistryService = createDecorator<IStorageKeysSyncRegistryService>('IStorageKeysSyncRegistryService');
|
||||
|
||||
export interface IStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* All registered storage keys
|
||||
*/
|
||||
readonly storageKeys: ReadonlyArray<IStorageKey>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when storage keys are changed
|
||||
*/
|
||||
readonly onDidChangeStorageKeys: Event<ReadonlyArray<IStorageKey>>;
|
||||
|
||||
/**
|
||||
* Register a storage key that has to be synchronized during sync.
|
||||
*/
|
||||
registerStorageKey(key: IStorageKey): void;
|
||||
|
||||
/**
|
||||
* All registered extensions storage keys
|
||||
*/
|
||||
readonly extensionsStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray<string>]>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when extension storage keys are changed
|
||||
*/
|
||||
onDidChangeExtensionStorageKeys: Event<[IExtensionIdWithVersion, ReadonlyArray<string>]>;
|
||||
|
||||
/**
|
||||
* Register storage keys that has to be synchronized for the given extension.
|
||||
*/
|
||||
registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void;
|
||||
|
||||
/**
|
||||
* Returns storage keys of the given extension that has to be synchronized.
|
||||
*/
|
||||
getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray<string> | undefined;
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
protected readonly _storageKeys = new Map<string, IStorageKey>();
|
||||
get storageKeys(): ReadonlyArray<IStorageKey> { return [...this._storageKeys.values()]; }
|
||||
|
||||
protected readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
|
||||
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
|
||||
|
||||
protected readonly _extensionsStorageKeys = new Map<string, string[]>();
|
||||
get extensionsStorageKeys() {
|
||||
const result: [IExtensionIdWithVersion, ReadonlyArray<string>][] = [];
|
||||
this._extensionsStorageKeys.forEach((keys, extension) => result.push([ExtensionIdWithVersion.fromKey(extension)!, keys]));
|
||||
return result;
|
||||
}
|
||||
protected readonly _onDidChangeExtensionStorageKeys = this._register(new Emitter<[IExtensionIdWithVersion, ReadonlyArray<string>]>());
|
||||
readonly onDidChangeExtensionStorageKeys = this._onDidChangeExtensionStorageKeys.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._register(toDisposable(() => this._storageKeys.clear()));
|
||||
}
|
||||
|
||||
getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray<string> | undefined {
|
||||
return this._extensionsStorageKeys.get(ExtensionIdWithVersion.toKey(extension));
|
||||
}
|
||||
|
||||
protected updateExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this._extensionsStorageKeys.set(ExtensionIdWithVersion.toKey(extension), keys);
|
||||
this._onDidChangeExtensionStorageKeys.fire([extension, keys]);
|
||||
}
|
||||
|
||||
abstract registerStorageKey(key: IStorageKey): void;
|
||||
abstract registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void;
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryService extends AbstractStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
if (!this._storageKeys.has(storageKey.key)) {
|
||||
this._storageKeys.set(storageKey.key, storageKey);
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
}
|
||||
|
||||
registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this.updateExtensionStorageKeys(extension, keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -55,7 +55,7 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU
|
||||
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
isEnabled(defaultEnablement?: boolean): boolean {
|
||||
@@ -73,15 +73,15 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU
|
||||
}
|
||||
|
||||
setEnablement(enabled: boolean): void {
|
||||
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
|
||||
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope !== StorageScope.GLOBAL) {
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope !== StorageScope.GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enablementKey === workspaceStorageChangeEvent.key) {
|
||||
if (enablementKey === storageChangeEvent.key) {
|
||||
this._onDidChangeEnablement.fire(this.isEnabled());
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
private set syncUrl(syncUrl: URI | undefined) {
|
||||
if (syncUrl) {
|
||||
this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL);
|
||||
this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.storageService.remove(storeUrlKey, StorageScope.GLOBAL);
|
||||
}
|
||||
@@ -325,7 +325,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
|
||||
private async disableMachineEventually(): Promise<void> {
|
||||
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL);
|
||||
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
await timeout(1000 * 60 * 10);
|
||||
|
||||
// Return if got stopped meanwhile.
|
||||
@@ -526,7 +526,7 @@ class AutoSync extends Disposable {
|
||||
|
||||
// Update local session id
|
||||
if (manifest && manifest.session !== sessionId) {
|
||||
this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL);
|
||||
this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
// Return if cancellation is requested
|
||||
|
||||
@@ -220,7 +220,9 @@ export enum UserDataSyncErrorCode {
|
||||
TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */
|
||||
|
||||
// Local Errors
|
||||
ConnectionRefused = 'ConnectionRefused',
|
||||
RequestFailed = 'RequestFailed',
|
||||
RequestCanceled = 'RequestCanceled',
|
||||
RequestTimeout = 'RequestTimeout',
|
||||
NoRef = 'NoRef',
|
||||
TurnedOff = 'TurnedOff',
|
||||
SessionExpired = 'SessionExpired',
|
||||
@@ -252,7 +254,7 @@ export class UserDataSyncError extends Error {
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreError extends UserDataSyncError {
|
||||
constructor(message: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) {
|
||||
constructor(message: string, readonly url: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) {
|
||||
super(message, code, undefined, operationId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncServic
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey, IExtensionIdWithVersion, AbstractStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
||||
export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
@@ -175,68 +173,6 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
}
|
||||
|
||||
type StorageKeysSyncRegistryServiceInitData = { storageKeys: ReadonlyArray<IStorageKey>, extensionsStorageKeys: ReadonlyArray<[IExtensionIdWithVersion, ReadonlyArray<string>]> };
|
||||
|
||||
export class StorageKeysSyncRegistryChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IStorageKeysSyncRegistryService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys;
|
||||
case 'onDidChangeExtensionStorageKeys': return this.service.onDidChangeExtensionStorageKeys;
|
||||
}
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case '_getInitialData': return Promise.resolve<StorageKeysSyncRegistryServiceInitData>({ storageKeys: this.service.storageKeys, extensionsStorageKeys: this.service.extensionsStorageKeys });
|
||||
case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0]));
|
||||
case 'registerExtensionStorageKeys': return Promise.resolve(this.service.registerExtensionStorageKeys(args[0], args[1]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryChannelClient extends AbstractStorageKeysSyncRegistryService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
super();
|
||||
this.channel.call<StorageKeysSyncRegistryServiceInitData>('_getInitialData').then(({ storageKeys, extensionsStorageKeys }) => {
|
||||
this.updateStorageKeys(storageKeys);
|
||||
this.updateExtensionsStorageKeys(extensionsStorageKeys);
|
||||
this._register(this.channel.listen<ReadonlyArray<IStorageKey>>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys)));
|
||||
this._register(this.channel.listen<[IExtensionIdentifierWithVersion, string[]]>('onDidChangeExtensionStorageKeys')(e => this.updateExtensionStorageKeys(e[0], e[1])));
|
||||
});
|
||||
}
|
||||
|
||||
private async updateStorageKeys(storageKeys: ReadonlyArray<IStorageKey>): Promise<void> {
|
||||
this._storageKeys.clear();
|
||||
for (const storageKey of storageKeys) {
|
||||
this._storageKeys.set(storageKey.key, storageKey);
|
||||
}
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
|
||||
private async updateExtensionsStorageKeys(extensionStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray<string>]>): Promise<void> {
|
||||
for (const [extension, keys] of extensionStorageKeys) {
|
||||
this.updateExtensionStorageKeys(extension, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
this.channel.call('registerStorageKey', [storageKey]);
|
||||
}
|
||||
|
||||
registerExtensionStorageKeys(extension: IExtensionIdentifierWithVersion, keys: string[]): void {
|
||||
this.channel.call('registerExtensionStorageKeys', [extension, keys]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncMachinesServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncMachinesService) { }
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -103,7 +103,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData
|
||||
machine.name = name;
|
||||
await this.writeMachinesData(machineData);
|
||||
if (machineData.machines.some(({ id }) => id === currentMachineId)) {
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL);
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type SyncEnablementClassification = {
|
||||
@@ -28,7 +28,7 @@ export class UserDataSyncResourceEnablementService extends Disposable implements
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
isResourceEnabled(resource: SyncResource): boolean {
|
||||
@@ -39,13 +39,13 @@ export class UserDataSyncResourceEnablementService extends Disposable implements
|
||||
if (this.isResourceEnabled(resource) !== enabled) {
|
||||
const resourceEnablementKey = getEnablementKey(resource);
|
||||
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled });
|
||||
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL);
|
||||
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0];
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === storageChangeEvent.key)[0];
|
||||
if (resourceKey) {
|
||||
this._onDidChangeResourceEnablement.fire([resourceKey, this.isResourceEnabled(resourceKey)]);
|
||||
return;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user